diff options
author | Peter Harris <git@peter.is-a-geek.org> | 2011-01-02 21:55:35 -0500 |
---|---|---|
committer | Peter Harris <git@peter.is-a-geek.org> | 2011-01-02 21:55:35 -0500 |
commit | 7bf3bc1fe4789d0c4d5d49ee34206f2504ab90a1 (patch) | |
tree | f1be82d5ae41a9db947e4e791c3e4472a68aacb6 | |
parent | 7144e5d343e4d0756aff4d6b03709bef0dfc7614 (diff) |
xlsclients now works
-rw-r--r-- | Makefile | 2 | ||||
-rw-r--r-- | xgob.go | 176 | ||||
-rw-r--r-- | xgob/util/atom.go | 15 | ||||
-rw-r--r-- | xgob/xproto.go | 6 | ||||
-rw-r--r-- | xlsclients.go | 189 | ||||
-rw-r--r-- | xmu.go | 65 |
6 files changed, 431 insertions, 22 deletions
@@ -15,5 +15,5 @@ all: test noop xlsclients # Dependencies xgob/util/atom.6: xgob/xproto.6 test.6: xgob.6 -xlsclients.6: xgob.6 xgob/xproto.6 xgob/util/atom.6 +xlsclients.6: xgob.6 xmu.6 xgob/xproto.6 xgob/util/atom.6 noop.6: xgob.6 @@ -13,9 +13,64 @@ import ( const X_TCP_PORT = 6000 +const ( + LSBFirst = iota + MSBFirst = iota +) + +type Format struct { + Depth, BitsPerPixel, ScanlinePad uint8 +} + +type VisualType struct { + VisualID uint32 + Class uint8 + BitsPerRGBValue uint8 + ColormapEntries uint16 + RedMask uint32 + GreenMask uint32 + BlueMask uint32 +} + +type Depth struct { + Depth uint8 + Visuals []VisualType +} + +type Screen struct { + Root uint32 + DefaultColormap uint32 + WhitePixel uint32 + BlackPixel uint32 + CurrentInputMasks uint32 + WidthInPixels uint16 + HeightInPixels uint16 + WidthInMillimeters uint16 + HeightInMillimeters uint16 + MinInstalledMaps uint16 + MaxInstalledMaps uint16 + RootVisual uint32 + BackingStores uint8 + SaveUnders bool + RootDepth uint8 + AllowedDepths []Depth +} + type ConnectionBlock struct { Major, Minor uint16 - todo []byte + Release uint32 + ResourceIDBase uint32 + ResourceIDMask uint32 + MotionBufferSize uint32 + MaximumRequestLength uint16 + ImageByteOrder uint8 + BitmapBitOrder uint8 + BitmapScanlineUnit uint8 + BitmapScanlinePad uint8 + MinKeycode, MaxKeycode uint8 + Vendor string + PixmapFormats []Format + Roots []Screen } type Event struct { @@ -187,7 +242,7 @@ func (c *Connection) write_setup (name string, data string) { c.conn.Write(buf) } -func getUint16(in *bufio.Reader) uint16 { +func readUint16(in *bufio.Reader) uint16 { var rv uint16 i, _ := in.ReadByte() rv = uint16(i) << 8 @@ -196,7 +251,7 @@ func getUint16(in *bufio.Reader) uint16 { return rv } -func getUint32(in *bufio.Reader) uint32 { +func readUint32(in *bufio.Reader) uint32 { var rv uint32 i, _ := in.ReadByte() rv = uint32(i) << 24 @@ -227,26 +282,117 @@ func readAll(in *bufio.Reader, buf []byte) { } } +func getUint16(buf []byte) (uint16, []byte) { + var rv uint16 + rv = uint16(buf[0]) << 8 + rv |= uint16(buf[1]) + return rv, buf[2:] +} + +func getUint32(buf []byte) (uint32, []byte) { + var rv uint32 + rv = uint32(buf[0]) << 24 + rv |= uint32(buf[1]) << 16 + rv |= uint32(buf[2]) << 8 + rv |= uint32(buf[3]) + return rv, buf[4:] +} + func (c *Connection) read_setup(in *bufio.Reader) bool { status, _ := in.ReadByte() reason_len, _ := in.ReadByte() - c.Setup.Major = getUint16(in) - c.Setup.Minor = getUint16(in) - len := getUint16(in) + c.Setup.Major = readUint16(in) + c.Setup.Minor = readUint16(in) + length := readUint16(in) - c.Setup.todo = make([]byte, len * 4) - readAll(in, c.Setup.todo) + todo := make([]byte, length * 4) + readAll(in, todo) switch status { case 0: println("Server refused connection because") - println(bytes.NewBuffer(c.Setup.todo[:reason_len]).String()) + println(bytes.NewBuffer(todo[:reason_len]).String()) case 1: /* Success */ + c.Setup.Release, todo = getUint32(todo) + c.Setup.ResourceIDBase, todo = getUint32(todo) + c.Setup.ResourceIDMask, todo = getUint32(todo) + c.Setup.MotionBufferSize, todo = getUint32(todo) + vendorLen, todo := getUint16(todo) + c.Setup.MaximumRequestLength, todo = getUint16(todo) + rootsLen := todo[0] + pixmapFormatsLen := todo[1] + c.Setup.ImageByteOrder = todo[2] + c.Setup.BitmapBitOrder = todo[3] + c.Setup.BitmapScanlineUnit = todo[4] + c.Setup.BitmapScanlinePad = todo[5] + c.Setup.MinKeycode = todo[6] + c.Setup.MaxKeycode = todo[7] + todo = todo[12:] // 8 above, plus 4 pad + + c.Setup.Vendor = string(todo[:vendorLen]) + vendorLen += -vendorLen & 3 + todo = todo[vendorLen:] + + c.Setup.PixmapFormats = make([]Format, 0, pixmapFormatsLen) + for i := uint8(0); i < pixmapFormatsLen; i++ { + var f Format + f.Depth = todo[0] + f.BitsPerPixel = todo[1] + f.ScanlinePad = todo[2] + todo = todo[8:] + c.Setup.PixmapFormats = append(c.Setup.PixmapFormats, f) + } + + c.Setup.Roots = make([]Screen, rootsLen) + for i := uint8(0); i < rootsLen; i++ { + s := &c.Setup.Roots[i] + s.Root, todo = getUint32(todo) + s.DefaultColormap, todo = getUint32(todo) + s.WhitePixel, todo = getUint32(todo) + s.BlackPixel, todo = getUint32(todo) + s.CurrentInputMasks, todo = getUint32(todo) + s.WidthInPixels, todo = getUint16(todo) + s.HeightInPixels, todo = getUint16(todo) + s.WidthInMillimeters, todo = getUint16(todo) + s.HeightInMillimeters, todo = getUint16(todo) + s.MinInstalledMaps, todo = getUint16(todo) + s.MaxInstalledMaps, todo = getUint16(todo) + s.RootVisual, todo = getUint32(todo) + s.BackingStores = todo[0] + s.SaveUnders = todo[1] != 0 + s.RootDepth = todo[2] + depthsLen := todo[3] + + todo = todo[4:] + s.AllowedDepths = make([]Depth, depthsLen) + for j := uint8(0); j < depthsLen; j++ { + d := &s.AllowedDepths[j] + d.Depth = todo[0] + todo = todo[2:] + var visualsLen uint16 + visualsLen, todo = getUint16(todo) + todo = todo[4:] // Pad + d.Visuals = make([]VisualType, visualsLen) + for k := uint16(0); k < visualsLen; k++ { + v := &d.Visuals[k] + v.VisualID, todo = getUint32(todo) + v.Class = todo[0] + v.BitsPerRGBValue = todo[1] + todo = todo[2:] + v.ColormapEntries, todo = getUint16(todo) + v.RedMask, todo = getUint32(todo) + v.GreenMask, todo = getUint32(todo) + v.BlueMask, todo = getUint32(todo) + todo = todo[4:] // Pad + } + } + } + return true case 2: println("Server requires authentication") - println(bytes.NewBuffer(c.Setup.todo).String()) + println(bytes.NewBuffer(todo).String()) default: panic("Unexpected return value from server " + strconv.Uitoa(uint(status))) } @@ -312,17 +458,17 @@ func (c *Connection) read (in *bufio.Reader) { case 0: var e Error e.Error, _ = in.ReadByte() - e.Sequence = c.fullSeq(getUint16(in)) - e.BadValue = getUint32(in) - e.Minor = getUint16(in) + e.Sequence = c.fullSeq(readUint16(in)) + e.BadValue = readUint32(in) + e.Minor = readUint16(in) e.Major, _ = in.ReadByte() skipBytes(in, 20) c.postReply(e.Sequence, e) case 1: var r Reply r.Aux, _ = in.ReadByte() - r.Sequence = c.fullSeq(getUint16(in)) - r.Length = getUint32(in) + r.Sequence = c.fullSeq(readUint16(in)) + r.Length = readUint32(in) r.Remainder = make([]byte, r.Length * 4 + 24) readAll(in, r.Remainder) c.postReply(r.Sequence, r) diff --git a/xgob/util/atom.go b/xgob/util/atom.go index f79e20a..b5f47f8 100644 --- a/xgob/util/atom.go +++ b/xgob/util/atom.go @@ -1,6 +1,7 @@ package atom import ( + "fmt" "sync" "xgob" "xproto" @@ -36,10 +37,16 @@ func (s *state) internAtom (name string) xproto.Atom { } else { c = xproto.InternAtom(s.c, false, name) } - reply := <- c + reply, ok := <- c + if !ok { + s.c.Flush() + reply = <- c + } if reply.Error == nil { s.name[reply.Atom] = name s.atom[name] = reply.Atom + } else { + println("Atom Error",reply.Error.Error) } return reply.Atom } @@ -82,7 +89,7 @@ func PreloadAtom(c *xgob.Connection, name string) { func Atom (c *xgob.Connection, name string) xproto.Atom { lock.RLock() - + s := getState(c, false) atom, ok := s.atom[name] @@ -100,6 +107,6 @@ func Atom (c *xgob.Connection, name string) xproto.Atom { return atom } -func AtomName (c *xgob.Connection, atom uint32) string { - return "" +func AtomName (c *xgob.Connection, atom xproto.Atom) string { + return fmt.Sprintf("0x%X", atom) // TODO } diff --git a/xgob/xproto.go b/xgob/xproto.go index 31fa0aa..87ca85b 100644 --- a/xgob/xproto.go +++ b/xgob/xproto.go @@ -42,8 +42,8 @@ func appendUint32(buf []byte, val uint32) []byte { func getUint16(buf []byte) uint16 { var rv uint16 - rv = uint16(buf[2]) << 8 - rv |= uint16(buf[3]) + rv = uint16(buf[0]) << 8 + rv |= uint16(buf[1]) return rv } @@ -125,6 +125,8 @@ func InternAtom(c *xgob.Connection, only_if_exists bool, name string) chan Inter req = appendUint16(req, uint16(length/4)) req = appendUint16(req, uint16(len(name))) req = appendUint16(req, 0) // Pad + + req = req[0:length] // Expand for name copy(req[8:], name) rv := make(chan InternAtomReply, 1) diff --git a/xlsclients.go b/xlsclients.go index a0dec36..ea76b3f 100644 --- a/xlsclients.go +++ b/xlsclients.go @@ -1,9 +1,198 @@ package main import ( + "atom" + "bytes" "flag" + "fmt" + "strings" + "xgob" + "xmu" + "xproto" ) +var display = flag.String("display", "", "Display to connect to") +var maxcmdlen = flag.Uint("max", 10000, "Maximum command length to show") +var all = flag.Bool("all", false, "Show all screens") +var verbose = flag.Bool("long", false, "Verbose output") + +func initAtoms(c *xgob.Connection) { + atom.PreloadAtom(c, "WM_STATE") + + /* The rest of these are pre-defined atoms. + They can be removed when we have a real xproto generator. */ + atom.PreloadAtom(c, "WM_CLIENT_MACHINE") + atom.PreloadAtom(c, "WM_COMMAND") + atom.PreloadAtom(c, "WM_NAME") + atom.PreloadAtom(c, "WM_ICON_NAME") + atom.PreloadAtom(c, "WM_CLASS") + atom.PreloadAtom(c, "STRING") +} + +func formatTextField(c *xgob.Connection, s string, t *xproto.GetPropertyReply) string { + if t.Error != nil || t.Type == xproto.Atom(0) || t.Format == 0 { + return "''"; + } + + rv := s + + if t.Type == atom.Atom(c, "STRING") && t.Format == 8 { + rv += string(t.Value) + } else { + rv += "<unknown type " + if t.Type == xproto.Atom(0) { + rv += "None"; + } else { + /* This should happen so rarely as to make no odds. Eat a round-trip: */ + rv += atom.AtomName(c, t.Type) + } + rv += fmt.Sprintf(" (%d) or format %d>", t.Type, t.Format); + } + + if s != "" { rv += "\n" } + return rv +} + +func clientProperties(c *xgob.Connection, win xproto.Window, result chan string) { + machineCookie := xproto.GetProperty(c, false, win, atom.Atom(c, "WM_CLIENT_MACHINE"), xproto.Atom(0), 0, 1000000) + commandCookie := xproto.GetProperty(c, false, win, atom.Atom(c, "WM_COMMAND"), xproto.Atom(0), 0, 1000000) + var nameCookie chan xproto.GetPropertyReply + var iconNameCookie chan xproto.GetPropertyReply + var classCookie chan xproto.GetPropertyReply + if *verbose { + nameCookie = xproto.GetProperty(c, false, win, atom.Atom(c, "WM_NAME"), xproto.Atom(0), 0, 1000000) + iconNameCookie = xproto.GetProperty(c, false, win, atom.Atom(c, "WM_ICON_NAME"), xproto.Atom(0), 0, 1000000) + classCookie = xproto.GetProperty(c, false, win, atom.Atom(c, "WM_CLASS"), xproto.Atom(0), 0, 1000000) + } + c.Flush() + + machine := <-machineCookie + command := <-commandCookie + + if command.Error != nil || command.Format != 8 { + result <- "" + return + } + + var name, iconName, class xproto.GetPropertyReply + if *verbose { + name = <-nameCookie + iconName = <- iconNameCookie + class = <- classCookie + } + + list := make([]string, 0) + + // Header + if *verbose { + list = append(list, fmt.Sprintf("Window 0x%X:\n", win)) + list = append(list, formatTextField(c, " Machine: ", &machine)) + if name.Error == nil && name.Type != 0 { + list = append(list, formatTextField(c, " Name: ", &name)) + } + if iconName.Error == nil && iconName.Type != 0 { + list = append(list, formatTextField(c, " Icon Name: ", &iconName)) + } + } else { + list = append(list, formatTextField(c, "", &machine)) + } + + // Command + commandHeader := " " + if *verbose { + commandHeader = " Command: " + } + list = append(list, commandHeader) + + // TODO print_quoted_word + list = append(list, string(command.Value)) + list = append(list, "\n") + + // Trailer + if *verbose { + if class.Error == nil && class.Type != xproto.Atom(0) { + name := "" + cls := "(nil)" + index := bytes.IndexByte(class.Value, 0) + if index >= 0 { + name = string(class.Value[:index]) + cls = string(class.Value[index+1:]) + } else { + name = string(class.Value) + } + list = append(list, fmt.Sprintf(" Instance/Class: %s/%s\n", name, cls)) + } + } + result <- strings.Join(list, "") +} + +func findClientProperties(c *xgob.Connection, win xproto.Window, result chan string) { + client := xmu.ClientWindow(c, win) + if client == xproto.Window(0) { + result <- "" + return + } + clientProperties(c, client, result) +} + +func lookat(c *xgob.Connection, root xproto.Window, result chan string) { + var count uint + prop := make(chan string) + + /* + * clients are not allowed to stomp on the root and ICCCM doesn't yet + * say anything about window managers putting stuff there; but, try + * anyway. + */ + go clientProperties(c, root, prop) + count++ + + treeCookie := xproto.QueryTree(c, root) + c.Flush() + tree := <- treeCookie + for _, child := range tree.Children { + go findClientProperties(c, child, prop) + count++ + } + + list := make([]string, 0, count) + for i := uint(0); i < count; i++ { + list = append(list, <-prop) + } + result <- strings.Join(list, "") +} + func main () { flag.Parse() + c, screen := xgob.Connect(*display) + + if c.HasError() { + println("Cannot open display", *display) + return + } + + if screen >= len(c.Setup.Roots) && !*all { + println("Invalid Screen", screen) + return + } + + initAtoms(c) + + results := make([]chan string, 0) + + if *all { + for _, r := range c.Setup.Roots { + out := make(chan string) + go lookat(c, xproto.Window(r.Root), out) + results = append(results, out) + } + } else { + out := make(chan string) + go lookat(c, xproto.Window(c.Setup.Roots[screen].Root), out) + results = append(results, out) + } + + for _, result := range results { + fmt.Print(<-result) + } } @@ -0,0 +1,65 @@ +package xmu + +import ( + "atom" + "xgob" + "xproto" +) + +/* The main difference between this and C/libXmu is that this version + searches for the client window in the shallowest windows first, and + C/libXmu searches the topmost windows first. + + This change allows us to avoid as many round-trips as possible. + */ + +func tryChildren(c *xgob.Connection, parent xproto.Window) xproto.Window { + queryCookies := make([]chan xproto.QueryTreeReply, 0) + propCookies := make([]chan xproto.GetPropertyReply, 0) + + state := atom.Atom(c, "WM_STATE") + + queryCookies = append(queryCookies, xproto.QueryTree(c, parent)) + c.Flush() + + children := make([]xproto.Window, 0) + + for { + children = children[:0] + for _, cookie := range queryCookies { + children = append(children, (<-cookie).Children...) + } + + if len(children) == 0 { + break + } + + // Reset cookie jars + queryCookies = queryCookies[:0] + propCookies = propCookies[:0] + + for _, child := range children { + propCookies = append(propCookies, xproto.GetProperty(c, false, child, state, xproto.Atom(0), 0, 0)) + queryCookies = append(queryCookies, xproto.QueryTree(c, child)) + } + c.Flush() + for i, cookie := range propCookies { + s := <-cookie + if s.Error == nil && s.Type != xproto.Atom(0) { + return children[i] + } + } + } + return parent +} + +func ClientWindow(c *xgob.Connection, win xproto.Window) xproto.Window { + stateCookie := xproto.GetProperty(c, false, win, atom.Atom(c, "WM_STATE"), xproto.Atom(0), 0, 0) + c.Flush() + state := <- stateCookie + if state.Error != nil && state.Type != xproto.Atom(0) { + return win + } + + return tryChildren(c, win) +} |