summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPeter Harris <git@peter.is-a-geek.org>2011-01-02 21:55:35 -0500
committerPeter Harris <git@peter.is-a-geek.org>2011-01-02 21:55:35 -0500
commit7bf3bc1fe4789d0c4d5d49ee34206f2504ab90a1 (patch)
treef1be82d5ae41a9db947e4e791c3e4472a68aacb6
parent7144e5d343e4d0756aff4d6b03709bef0dfc7614 (diff)
xlsclients now works
-rw-r--r--Makefile2
-rw-r--r--xgob.go176
-rw-r--r--xgob/util/atom.go15
-rw-r--r--xgob/xproto.go6
-rw-r--r--xlsclients.go189
-rw-r--r--xmu.go65
6 files changed, 431 insertions, 22 deletions
diff --git a/Makefile b/Makefile
index f84e177..bdad028 100644
--- a/Makefile
+++ b/Makefile
@@ -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
diff --git a/xgob.go b/xgob.go
index 9211408..e231592 100644
--- a/xgob.go
+++ b/xgob.go
@@ -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)
+ }
}
diff --git a/xmu.go b/xmu.go
new file mode 100644
index 0000000..ea20614
--- /dev/null
+++ b/xmu.go
@@ -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)
+}