├── .gitignore ├── AUTHORS ├── CONTRIBUTORS ├── LICENSE ├── Makefile ├── README ├── STYLE ├── auth.go ├── bigreq └── bigreq.go ├── composite └── composite.go ├── conn.go ├── cookie.go ├── damage └── damage.go ├── doc.go ├── dpms └── dpms.go ├── dri2 └── dri2.go ├── examples ├── atoms │ ├── .gitignore │ ├── Makefile │ └── main.go ├── create-window │ └── main.go ├── doc.go ├── get-active-window │ └── main.go ├── randr │ └── main.go └── xinerama │ └── main.go ├── ge └── ge.go ├── glx └── glx.go ├── help.go ├── randr └── randr.go ├── record └── record.go ├── render └── render.go ├── res └── res.go ├── screensaver └── screensaver.go ├── shape └── shape.go ├── shm └── shm.go ├── sync.go ├── xcmisc └── xcmisc.go ├── xevie └── xevie.go ├── xf86dri └── xf86dri.go ├── xf86vidmode └── xf86vidmode.go ├── xfixes └── xfixes.go ├── xgb.go ├── xgbgen ├── COPYING ├── aligngap.go ├── context.go ├── doc.go ├── expression.go ├── field.go ├── go.go ├── go_error.go ├── go_event.go ├── go_list.go ├── go_request_reply.go ├── go_single_field.go ├── go_struct.go ├── go_union.go ├── main.go ├── misc.go ├── protocol.go ├── request_reply.go ├── size.go ├── translation.go ├── type.go ├── xml.go └── xml_fields.go ├── xinerama └── xinerama.go ├── xprint └── xprint.go ├── xproto ├── xproto.go └── xproto_test.go ├── xselinux └── xselinux.go ├── xtest └── xtest.go ├── xv └── xv.go └── xvmc └── xvmc.go /.gitignore: -------------------------------------------------------------------------------- 1 | xgbgen/xgbgen 2 | .*.swp 3 | -------------------------------------------------------------------------------- /AUTHORS: -------------------------------------------------------------------------------- 1 | Andrew Gallant is the maintainer of this fork. What follows is the original 2 | list of authors for the x-go-binding. 3 | 4 | # This is the official list of XGB authors for copyright purposes. 5 | # This file is distinct from the CONTRIBUTORS files. 6 | # See the latter for an explanation. 7 | 8 | # Names should be added to this file as 9 | # Name or Organization 10 | # The email address is not required for organizations. 11 | 12 | # Please keep the list sorted. 13 | 14 | Anthony Martin 15 | Firmansyah Adiputra 16 | Google Inc. 17 | Scott Lawrence 18 | Tor Andersson 19 | -------------------------------------------------------------------------------- /CONTRIBUTORS: -------------------------------------------------------------------------------- 1 | Andrew Gallant is the maintainer of this fork. What follows is the original 2 | list of contributors for the x-go-binding. 3 | 4 | # This is the official list of people who can contribute 5 | # (and typically have contributed) code to the XGB repository. 6 | # The AUTHORS file lists the copyright holders; this file 7 | # lists people. For example, Google employees are listed here 8 | # but not in AUTHORS, because Google holds the copyright. 9 | # 10 | # The submission process automatically checks to make sure 11 | # that people submitting code are listed in this file (by email address). 12 | # 13 | # Names should be added to this file only after verifying that 14 | # the individual or the individual's organization has agreed to 15 | # the appropriate Contributor License Agreement, found here: 16 | # 17 | # http://code.google.com/legal/individual-cla-v1.0.html 18 | # http://code.google.com/legal/corporate-cla-v1.0.html 19 | # 20 | # The agreement for individuals can be filled out on the web. 21 | # 22 | # When adding J Random Contributor's name to this file, 23 | # either J's name or J's organization's name should be 24 | # added to the AUTHORS file, depending on whether the 25 | # individual or corporate CLA was used. 26 | 27 | # Names should be added to this file like so: 28 | # Name 29 | 30 | # Please keep the list sorted. 31 | 32 | Anthony Martin 33 | Firmansyah Adiputra 34 | Ian Lance Taylor 35 | Nigel Tao 36 | Robert Griesemer 37 | Russ Cox 38 | Scott Lawrence 39 | Tor Andersson 40 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2009 The XGB Authors. All rights reserved. 2 | // 3 | // Redistribution and use in source and binary forms, with or without 4 | // modification, are permitted provided that the following conditions are 5 | // met: 6 | // 7 | // * Redistributions of source code must retain the above copyright 8 | // notice, this list of conditions and the following disclaimer. 9 | // * Redistributions in binary form must reproduce the above 10 | // copyright notice, this list of conditions and the following disclaimer 11 | // in the documentation and/or other materials provided with the 12 | // distribution. 13 | // * Neither the name of Google Inc. nor the names of its 14 | // contributors may be used to endorse or promote products derived from 15 | // this software without specific prior written permission. 16 | // 17 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 18 | // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 19 | // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 20 | // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 21 | // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 22 | // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 23 | // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 24 | // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 25 | // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26 | // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | // 29 | // Subject to the terms and conditions of this License, Google hereby 30 | // grants to You a perpetual, worldwide, non-exclusive, no-charge, 31 | // royalty-free, irrevocable (except as stated in this section) patent 32 | // license to make, have made, use, offer to sell, sell, import, and 33 | // otherwise transfer this implementation of XGB, where such license 34 | // applies only to those patent claims licensable by Google that are 35 | // necessarily infringed by use of this implementation of XGB. If You 36 | // institute patent litigation against any entity (including a 37 | // cross-claim or counterclaim in a lawsuit) alleging that this 38 | // implementation of XGB or a Contribution incorporated within this 39 | // implementation of XGB constitutes direct or contributory patent 40 | // infringement, then any patent licenses granted to You under this 41 | // License for this implementation of XGB shall terminate as of the date 42 | // such litigation is filed. 43 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # This Makefile is used by the developer. It is not needed in any way to build 2 | # a checkout of the XGB repository. 3 | # It will be useful, however, if you are hacking at the code generator. 4 | # i.e., after making a change to the code generator, run 'make' in the 5 | # xgb directory. This will build xgbgen and regenerate each sub-package. 6 | # 'make test' will then run any appropriate tests (just tests xproto right now). 7 | # 'make bench' will test a couple of benchmarks. 8 | # 'make build-all' will then try to build each extension. This isn't strictly 9 | # necessary, but it's a good idea to make sure each sub-package is a valid 10 | # Go package. 11 | 12 | # My path to the X protocol XML descriptions. 13 | XPROTO=/usr/share/xcb 14 | 15 | # All of the XML files in my /usr/share/xcb directory EXCEPT XKB. -_- 16 | # This is intended to build xgbgen and generate Go code for each supported 17 | # extension. 18 | all: build-xgbgen \ 19 | bigreq.xml composite.xml damage.xml dpms.xml dri2.xml \ 20 | ge.xml glx.xml randr.xml record.xml render.xml res.xml \ 21 | screensaver.xml shape.xml shm.xml xc_misc.xml \ 22 | xevie.xml xf86dri.xml xf86vidmode.xml xfixes.xml xinerama.xml \ 23 | xprint.xml xproto.xml xselinux.xml xtest.xml \ 24 | xvmc.xml xv.xml 25 | 26 | build-xgbgen: 27 | (cd xgbgen && go build) 28 | 29 | # Builds each individual sub-package to make sure its valid Go code. 30 | build-all: bigreq.b composite.b damage.b dpms.b dri2.b ge.b glx.b randr.b \ 31 | record.b render.b res.b screensaver.b shape.b shm.b xcmisc.b \ 32 | xevie.b xf86dri.b xf86vidmode.b xfixes.b xinerama.b \ 33 | xprint.b xproto.b xselinux.b xtest.b xv.b xvmc.b 34 | 35 | %.b: 36 | (cd $* ; go build) 37 | 38 | # Installs each individual sub-package. 39 | install: bigreq.i composite.i damage.i dpms.i dri2.i ge.i glx.i randr.i \ 40 | record.i render.i res.i screensaver.i shape.i shm.i xcmisc.i \ 41 | xevie.i xf86dri.i xf86vidmode.i xfixes.i xinerama.i \ 42 | xprint.i xproto.i xselinux.i xtest.i xv.i xvmc.i 43 | go install 44 | 45 | %.i: 46 | (cd $* ; go install) 47 | 48 | # xc_misc is special because it has an underscore. 49 | # There's probably a way to do this better, but Makefiles aren't my strong suit. 50 | xc_misc.xml: build-xgbgen 51 | mkdir -p xcmisc 52 | xgbgen/xgbgen --proto-path $(XPROTO) $(XPROTO)/xc_misc.xml > xcmisc/xcmisc.go 53 | 54 | %.xml: build-xgbgen 55 | mkdir -p $* 56 | xgbgen/xgbgen --proto-path $(XPROTO) $(XPROTO)/$*.xml > $*/$*.go 57 | 58 | # Just test the xproto core protocol for now. 59 | test: 60 | (cd xproto ; go test) 61 | 62 | # Force all xproto benchmarks to run and no tests. 63 | bench: 64 | (cd xproto ; go test -run 'nomatch' -bench '.*' -cpu 1,2,3,6) 65 | 66 | # gofmt all non-auto-generated code. 67 | # (auto-generated code is already gofmt'd.) 68 | # Also do a column check (80 cols) after a gofmt. 69 | # But don't check columns on auto-generated code, since I don't care if they 70 | # break 80 cols. 71 | gofmt: 72 | gofmt -w *.go xgbgen/*.go examples/*.go examples/*/*.go xproto/xproto_test.go 73 | colcheck *.go xgbgen/*.go examples/*.go examples/*/*.go xproto/xproto_test.go 74 | 75 | push: 76 | git push origin master 77 | git push github master 78 | 79 | -------------------------------------------------------------------------------- /README: -------------------------------------------------------------------------------- 1 | Note that this project is largely unmaintained as I don't have the time to do 2 | or support more development. Please consider using this fork instead: 3 | https://github.com/jezek/xgb 4 | 5 | 6 | XGB is the X Go Binding, which is a low-level API to communicate with the 7 | core X protocol and many of the X extensions. It is closely modeled after 8 | XCB and xpyb. 9 | 10 | It is thread safe and gets immediate improvement from parallelism when 11 | GOMAXPROCS > 1. (See the benchmarks in xproto/xproto_test.go for evidence.) 12 | 13 | Please see doc.go for more info. 14 | 15 | Note that unless you know you need XGB, you can probably make your life 16 | easier by using a slightly higher level library: xgbutil. 17 | 18 | Quick Usage 19 | =========== 20 | go get github.com/BurntSushi/xgb 21 | go run go/path/src/github.com/BurntSushi/xgb/examples/create-window/main.go 22 | 23 | BurntSushi's Fork 24 | ================= 25 | I've forked the XGB repository from Google Code due to inactivty upstream. 26 | 27 | Godoc documentation can be found here: 28 | https://godoc.org/github.com/BurntSushi/xgb 29 | 30 | Much of the code has been rewritten in an effort to support thread safety 31 | and multiple extensions. Namely, go_client.py has been thrown away in favor 32 | of an xgbgen package. 33 | 34 | The biggest parts that *haven't* been rewritten by me are the connection and 35 | authentication handshakes. They're inherently messy, and there's really no 36 | reason to re-work them. The rest of XGB has been completely rewritten. 37 | 38 | I like to release my code under the WTFPL, but since I'm starting with someone 39 | else's work, I'm leaving the original license/contributor/author information 40 | in tact. 41 | 42 | I suppose I can legitimately release xgbgen under the WTFPL. To be fair, it is 43 | at least as complex as XGB itself. *sigh* 44 | 45 | What follows is the original README: 46 | 47 | XGB README 48 | ========== 49 | XGB is the X protocol Go language Binding. 50 | 51 | It is the Go equivalent of XCB, the X protocol C-language Binding 52 | (http://xcb.freedesktop.org/). 53 | 54 | Unless otherwise noted, the XGB source files are distributed 55 | under the BSD-style license found in the LICENSE file. 56 | 57 | Contributions should follow the same procedure as for the Go project: 58 | http://golang.org/doc/contribute.html 59 | 60 | -------------------------------------------------------------------------------- /STYLE: -------------------------------------------------------------------------------- 1 | I like to keep all my code to 80 columns or less. I have plenty of screen real 2 | estate, but enjoy 80 columns so that I can have multiple code windows open side 3 | to side and not be plagued by the ugly auto-wrapping of a text editor. 4 | 5 | If you don't oblige me, I will fix any patch you submit to abide 80 columns. 6 | 7 | Note that this style restriction does not preclude gofmt, but introduces a few 8 | peculiarities. The first is that gofmt will occasionally add spacing (typically 9 | to comments) that ends up going over 80 columns. Either shorten the comment or 10 | put it on its own line. 11 | 12 | The second and more common hiccup is when a function definition extends beyond 13 | 80 columns. If one adds line breaks to keep it below 80 columns, gofmt will 14 | indent all subsequent lines in a function definition to the same indentation 15 | level of the function body. This results in a less-than-ideal separation 16 | between function definition and function body. To remedy this, simply add a 17 | line break like so: 18 | 19 | func RestackWindowExtra(xu *xgbutil.XUtil, win xproto.Window, stackMode int, 20 | sibling xproto.Window, source int) error { 21 | 22 | return ClientEvent(xu, win, "_NET_RESTACK_WINDOW", source, int(sibling), 23 | stackMode) 24 | } 25 | 26 | Something similar should also be applied to long 'if' or 'for' conditionals, 27 | although it would probably be preferrable to break up the conditional to 28 | smaller chunks with a few helper variables. 29 | 30 | -------------------------------------------------------------------------------- /auth.go: -------------------------------------------------------------------------------- 1 | package xgb 2 | 3 | /* 4 | auth.go contains functions to facilitate the parsing of .Xauthority files. 5 | 6 | It is largely unmodified from the original XGB package that I forked. 7 | */ 8 | 9 | import ( 10 | "encoding/binary" 11 | "errors" 12 | "io" 13 | "os" 14 | ) 15 | 16 | // readAuthority reads the X authority file for the DISPLAY. 17 | // If hostname == "" or hostname == "localhost", 18 | // then use the system's hostname (as returned by os.Hostname) instead. 19 | func readAuthority(hostname, display string) ( 20 | name string, data []byte, err error) { 21 | 22 | // b is a scratch buffer to use and should be at least 256 bytes long 23 | // (i.e. it should be able to hold a hostname). 24 | b := make([]byte, 256) 25 | 26 | // As per /usr/include/X11/Xauth.h. 27 | const familyLocal = 256 28 | const familyWild = 65535 29 | 30 | if len(hostname) == 0 || hostname == "localhost" { 31 | hostname, err = os.Hostname() 32 | if err != nil { 33 | return "", nil, err 34 | } 35 | } 36 | 37 | fname := os.Getenv("XAUTHORITY") 38 | if len(fname) == 0 { 39 | home := os.Getenv("HOME") 40 | if len(home) == 0 { 41 | err = errors.New("Xauthority not found: $XAUTHORITY, $HOME not set") 42 | return "", nil, err 43 | } 44 | fname = home + "/.Xauthority" 45 | } 46 | 47 | r, err := os.Open(fname) 48 | if err != nil { 49 | return "", nil, err 50 | } 51 | defer r.Close() 52 | 53 | for { 54 | var family uint16 55 | if err := binary.Read(r, binary.BigEndian, &family); err != nil { 56 | return "", nil, err 57 | } 58 | 59 | addr, err := getString(r, b) 60 | if err != nil { 61 | return "", nil, err 62 | } 63 | 64 | disp, err := getString(r, b) 65 | if err != nil { 66 | return "", nil, err 67 | } 68 | 69 | name0, err := getString(r, b) 70 | if err != nil { 71 | return "", nil, err 72 | } 73 | 74 | data0, err := getBytes(r, b) 75 | if err != nil { 76 | return "", nil, err 77 | } 78 | 79 | addrmatch := (family == familyWild) || 80 | (family == familyLocal && addr == hostname) 81 | dispmatch := (disp == "") || (disp == display) 82 | 83 | if addrmatch && dispmatch { 84 | return name0, data0, nil 85 | } 86 | } 87 | panic("unreachable") 88 | } 89 | 90 | func getBytes(r io.Reader, b []byte) ([]byte, error) { 91 | var n uint16 92 | if err := binary.Read(r, binary.BigEndian, &n); err != nil { 93 | return nil, err 94 | } else if n > uint16(len(b)) { 95 | return nil, errors.New("bytes too long for buffer") 96 | } 97 | 98 | if _, err := io.ReadFull(r, b[0:n]); err != nil { 99 | return nil, err 100 | } 101 | return b[0:n], nil 102 | } 103 | 104 | func getString(r io.Reader, b []byte) (string, error) { 105 | b, err := getBytes(r, b) 106 | if err != nil { 107 | return "", err 108 | } 109 | return string(b), nil 110 | } 111 | -------------------------------------------------------------------------------- /bigreq/bigreq.go: -------------------------------------------------------------------------------- 1 | // Package bigreq is the X client API for the BIG-REQUESTS extension. 2 | package bigreq 3 | 4 | // This file is automatically generated from bigreq.xml. Edit at your peril! 5 | 6 | import ( 7 | "github.com/BurntSushi/xgb" 8 | 9 | "github.com/BurntSushi/xgb/xproto" 10 | ) 11 | 12 | // Init must be called before using the BIG-REQUESTS extension. 13 | func Init(c *xgb.Conn) error { 14 | reply, err := xproto.QueryExtension(c, 12, "BIG-REQUESTS").Reply() 15 | switch { 16 | case err != nil: 17 | return err 18 | case !reply.Present: 19 | return xgb.Errorf("No extension named BIG-REQUESTS could be found on on the server.") 20 | } 21 | 22 | c.ExtLock.Lock() 23 | c.Extensions["BIG-REQUESTS"] = reply.MajorOpcode 24 | c.ExtLock.Unlock() 25 | for evNum, fun := range xgb.NewExtEventFuncs["BIG-REQUESTS"] { 26 | xgb.NewEventFuncs[int(reply.FirstEvent)+evNum] = fun 27 | } 28 | for errNum, fun := range xgb.NewExtErrorFuncs["BIG-REQUESTS"] { 29 | xgb.NewErrorFuncs[int(reply.FirstError)+errNum] = fun 30 | } 31 | return nil 32 | } 33 | 34 | func init() { 35 | xgb.NewExtEventFuncs["BIG-REQUESTS"] = make(map[int]xgb.NewEventFun) 36 | xgb.NewExtErrorFuncs["BIG-REQUESTS"] = make(map[int]xgb.NewErrorFun) 37 | } 38 | 39 | // Skipping definition for base type 'Bool' 40 | 41 | // Skipping definition for base type 'Byte' 42 | 43 | // Skipping definition for base type 'Card8' 44 | 45 | // Skipping definition for base type 'Char' 46 | 47 | // Skipping definition for base type 'Void' 48 | 49 | // Skipping definition for base type 'Double' 50 | 51 | // Skipping definition for base type 'Float' 52 | 53 | // Skipping definition for base type 'Int16' 54 | 55 | // Skipping definition for base type 'Int32' 56 | 57 | // Skipping definition for base type 'Int8' 58 | 59 | // Skipping definition for base type 'Card16' 60 | 61 | // Skipping definition for base type 'Card32' 62 | 63 | // EnableCookie is a cookie used only for Enable requests. 64 | type EnableCookie struct { 65 | *xgb.Cookie 66 | } 67 | 68 | // Enable sends a checked request. 69 | // If an error occurs, it will be returned with the reply by calling EnableCookie.Reply() 70 | func Enable(c *xgb.Conn) EnableCookie { 71 | c.ExtLock.RLock() 72 | defer c.ExtLock.RUnlock() 73 | if _, ok := c.Extensions["BIG-REQUESTS"]; !ok { 74 | panic("Cannot issue request 'Enable' using the uninitialized extension 'BIG-REQUESTS'. bigreq.Init(connObj) must be called first.") 75 | } 76 | cookie := c.NewCookie(true, true) 77 | c.NewRequest(enableRequest(c), cookie) 78 | return EnableCookie{cookie} 79 | } 80 | 81 | // EnableUnchecked sends an unchecked request. 82 | // If an error occurs, it can only be retrieved using xgb.WaitForEvent or xgb.PollForEvent. 83 | func EnableUnchecked(c *xgb.Conn) EnableCookie { 84 | c.ExtLock.RLock() 85 | defer c.ExtLock.RUnlock() 86 | if _, ok := c.Extensions["BIG-REQUESTS"]; !ok { 87 | panic("Cannot issue request 'Enable' using the uninitialized extension 'BIG-REQUESTS'. bigreq.Init(connObj) must be called first.") 88 | } 89 | cookie := c.NewCookie(false, true) 90 | c.NewRequest(enableRequest(c), cookie) 91 | return EnableCookie{cookie} 92 | } 93 | 94 | // EnableReply represents the data returned from a Enable request. 95 | type EnableReply struct { 96 | Sequence uint16 // sequence number of the request for this reply 97 | Length uint32 // number of bytes in this reply 98 | // padding: 1 bytes 99 | MaximumRequestLength uint32 100 | } 101 | 102 | // Reply blocks and returns the reply data for a Enable request. 103 | func (cook EnableCookie) Reply() (*EnableReply, error) { 104 | buf, err := cook.Cookie.Reply() 105 | if err != nil { 106 | return nil, err 107 | } 108 | if buf == nil { 109 | return nil, nil 110 | } 111 | return enableReply(buf), nil 112 | } 113 | 114 | // enableReply reads a byte slice into a EnableReply value. 115 | func enableReply(buf []byte) *EnableReply { 116 | v := new(EnableReply) 117 | b := 1 // skip reply determinant 118 | 119 | b += 1 // padding 120 | 121 | v.Sequence = xgb.Get16(buf[b:]) 122 | b += 2 123 | 124 | v.Length = xgb.Get32(buf[b:]) // 4-byte units 125 | b += 4 126 | 127 | v.MaximumRequestLength = xgb.Get32(buf[b:]) 128 | b += 4 129 | 130 | return v 131 | } 132 | 133 | // Write request to wire for Enable 134 | // enableRequest writes a Enable request to a byte slice. 135 | func enableRequest(c *xgb.Conn) []byte { 136 | size := 4 137 | b := 0 138 | buf := make([]byte, size) 139 | 140 | c.ExtLock.RLock() 141 | buf[b] = c.Extensions["BIG-REQUESTS"] 142 | c.ExtLock.RUnlock() 143 | b += 1 144 | 145 | buf[b] = 0 // request opcode 146 | b += 1 147 | 148 | xgb.Put16(buf[b:], uint16(size/4)) // write request size in 4-byte units 149 | b += 2 150 | 151 | return buf 152 | } 153 | -------------------------------------------------------------------------------- /conn.go: -------------------------------------------------------------------------------- 1 | package xgb 2 | 3 | /* 4 | conn.go contains a couple of functions that do some real dirty work related 5 | to the initial connection handshake with X. 6 | 7 | This code is largely unmodified from the original XGB package that I forked. 8 | */ 9 | 10 | import ( 11 | "errors" 12 | "fmt" 13 | "io" 14 | "net" 15 | "os" 16 | "strconv" 17 | "strings" 18 | ) 19 | 20 | // connect connects to the X server given in the 'display' string, 21 | // and does all the necessary setup handshaking. 22 | // If 'display' is empty it will be taken from os.Getenv("DISPLAY"). 23 | // Note that you should read and understand the "Connection Setup" of the 24 | // X Protocol Reference Manual before changing this function: 25 | // http://goo.gl/4zGQg 26 | func (c *Conn) connect(display string) error { 27 | err := c.dial(display) 28 | if err != nil { 29 | return err 30 | } 31 | 32 | return c.postConnect() 33 | } 34 | 35 | // connect init from to the net.Conn, 36 | func (c *Conn) connectNet(netConn net.Conn) error { 37 | c.conn = netConn 38 | return c.postConnect() 39 | } 40 | 41 | // do the postConnect action after Conn get it's underly net.Conn 42 | func (c *Conn) postConnect() error { 43 | // Get authentication data 44 | authName, authData, err := readAuthority(c.host, c.display) 45 | noauth := false 46 | if err != nil { 47 | Logger.Printf("Could not get authority info: %v", err) 48 | Logger.Println("Trying connection without authority info...") 49 | authName = "" 50 | authData = []byte{} 51 | noauth = true 52 | } 53 | 54 | // Assume that the authentication protocol is "MIT-MAGIC-COOKIE-1". 55 | if !noauth && (authName != "MIT-MAGIC-COOKIE-1" || len(authData) != 16) { 56 | return errors.New("unsupported auth protocol " + authName) 57 | } 58 | 59 | buf := make([]byte, 12+Pad(len(authName))+Pad(len(authData))) 60 | buf[0] = 0x6c 61 | buf[1] = 0 62 | Put16(buf[2:], 11) 63 | Put16(buf[4:], 0) 64 | Put16(buf[6:], uint16(len(authName))) 65 | Put16(buf[8:], uint16(len(authData))) 66 | Put16(buf[10:], 0) 67 | copy(buf[12:], []byte(authName)) 68 | copy(buf[12+Pad(len(authName)):], authData) 69 | if _, err = c.conn.Write(buf); err != nil { 70 | return err 71 | } 72 | 73 | head := make([]byte, 8) 74 | if _, err = io.ReadFull(c.conn, head[0:8]); err != nil { 75 | return err 76 | } 77 | code := head[0] 78 | reasonLen := head[1] 79 | major := Get16(head[2:]) 80 | minor := Get16(head[4:]) 81 | dataLen := Get16(head[6:]) 82 | 83 | if major != 11 || minor != 0 { 84 | return fmt.Errorf("x protocol version mismatch: %d.%d", major, minor) 85 | } 86 | 87 | buf = make([]byte, int(dataLen)*4+8, int(dataLen)*4+8) 88 | copy(buf, head) 89 | if _, err = io.ReadFull(c.conn, buf[8:]); err != nil { 90 | return err 91 | } 92 | 93 | if code == 0 { 94 | reason := buf[8 : 8+reasonLen] 95 | return fmt.Errorf("x protocol authentication refused: %s", 96 | string(reason)) 97 | } 98 | 99 | // Unfortunately, it isn't really feasible to read the setup bytes here, 100 | // since the code to do so is in a different package. 101 | // Users must call 'xproto.Setup(X)' to get the setup info. 102 | c.SetupBytes = buf 103 | 104 | // But also read stuff that we *need* to get started. 105 | c.setupResourceIdBase = Get32(buf[12:]) 106 | c.setupResourceIdMask = Get32(buf[16:]) 107 | 108 | return nil 109 | } 110 | 111 | // dial initializes the actual net connection with X. 112 | func (c *Conn) dial(display string) error { 113 | if len(display) == 0 { 114 | display = os.Getenv("DISPLAY") 115 | } 116 | 117 | display0 := display 118 | if len(display) == 0 { 119 | return errors.New("empty display string") 120 | } 121 | 122 | colonIdx := strings.LastIndex(display, ":") 123 | if colonIdx < 0 { 124 | return errors.New("bad display string: " + display0) 125 | } 126 | 127 | var protocol, socket string 128 | 129 | if display[0] == '/' { 130 | socket = display[0:colonIdx] 131 | } else { 132 | slashIdx := strings.LastIndex(display, "/") 133 | if slashIdx >= 0 { 134 | protocol = display[0:slashIdx] 135 | c.host = display[slashIdx+1 : colonIdx] 136 | } else { 137 | c.host = display[0:colonIdx] 138 | } 139 | } 140 | 141 | display = display[colonIdx+1 : len(display)] 142 | if len(display) == 0 { 143 | return errors.New("bad display string: " + display0) 144 | } 145 | 146 | var scr string 147 | dotIdx := strings.LastIndex(display, ".") 148 | if dotIdx < 0 { 149 | c.display = display[0:] 150 | } else { 151 | c.display = display[0:dotIdx] 152 | scr = display[dotIdx+1:] 153 | } 154 | 155 | var err error 156 | c.DisplayNumber, err = strconv.Atoi(c.display) 157 | if err != nil || c.DisplayNumber < 0 { 158 | return errors.New("bad display string: " + display0) 159 | } 160 | 161 | if len(scr) != 0 { 162 | c.DefaultScreen, err = strconv.Atoi(scr) 163 | if err != nil { 164 | return errors.New("bad display string: " + display0) 165 | } 166 | } 167 | 168 | // Connect to server 169 | if len(socket) != 0 { 170 | c.conn, err = net.Dial("unix", socket+":"+c.display) 171 | } else if len(c.host) != 0 && c.host != "unix" { 172 | if protocol == "" { 173 | protocol = "tcp" 174 | } 175 | c.conn, err = net.Dial(protocol, 176 | c.host+":"+strconv.Itoa(6000+c.DisplayNumber)) 177 | } else { 178 | c.host = "" 179 | c.conn, err = net.Dial("unix", "/tmp/.X11-unix/X"+c.display) 180 | } 181 | 182 | if err != nil { 183 | return errors.New("cannot connect to " + display0 + ": " + err.Error()) 184 | } 185 | return nil 186 | } 187 | -------------------------------------------------------------------------------- /cookie.go: -------------------------------------------------------------------------------- 1 | package xgb 2 | 3 | import ( 4 | "errors" 5 | ) 6 | 7 | // Cookie is the internal representation of a cookie, where one is generated 8 | // for *every* request sent by XGB. 9 | // 'cookie' is most frequently used by embedding it into a more specific 10 | // kind of cookie, i.e., 'GetInputFocusCookie'. 11 | type Cookie struct { 12 | conn *Conn 13 | Sequence uint16 14 | replyChan chan []byte 15 | errorChan chan error 16 | pingChan chan bool 17 | } 18 | 19 | // NewCookie creates a new cookie with the correct channels initialized 20 | // depending upon the values of 'checked' and 'reply'. Together, there are 21 | // four different kinds of cookies. (See more detailed comments in the 22 | // function for more info on those.) 23 | // Note that a sequence number is not set until just before the request 24 | // corresponding to this cookie is sent over the wire. 25 | // 26 | // Unless you're building requests from bytes by hand, this method should 27 | // not be used. 28 | func (c *Conn) NewCookie(checked, reply bool) *Cookie { 29 | cookie := &Cookie{ 30 | conn: c, 31 | Sequence: 0, // we add the sequence id just before sending a request 32 | replyChan: nil, 33 | errorChan: nil, 34 | pingChan: nil, 35 | } 36 | 37 | // There are four different kinds of cookies: 38 | // Checked requests with replies get a reply channel and an error channel. 39 | // Unchecked requests with replies get a reply channel and a ping channel. 40 | // Checked requests w/o replies get a ping channel and an error channel. 41 | // Unchecked requests w/o replies get no channels. 42 | // The reply channel is used to send reply data. 43 | // The error channel is used to send error data. 44 | // The ping channel is used when one of the 'reply' or 'error' channels 45 | // is missing but the other is present. The ping channel is way to force 46 | // the blocking to stop and basically say "the error has been received 47 | // in the main event loop" (when the ping channel is coupled with a reply 48 | // channel) or "the request you made that has no reply was successful" 49 | // (when the ping channel is coupled with an error channel). 50 | if checked { 51 | cookie.errorChan = make(chan error, 1) 52 | if !reply { 53 | cookie.pingChan = make(chan bool, 1) 54 | } 55 | } 56 | if reply { 57 | cookie.replyChan = make(chan []byte, 1) 58 | if !checked { 59 | cookie.pingChan = make(chan bool, 1) 60 | } 61 | } 62 | 63 | return cookie 64 | } 65 | 66 | // Reply detects whether this is a checked or unchecked cookie, and calls 67 | // 'replyChecked' or 'replyUnchecked' appropriately. 68 | // 69 | // Unless you're building requests from bytes by hand, this method should 70 | // not be used. 71 | func (c Cookie) Reply() ([]byte, error) { 72 | // checked 73 | if c.errorChan != nil { 74 | return c.replyChecked() 75 | } 76 | return c.replyUnchecked() 77 | } 78 | 79 | // replyChecked waits for a response on either the replyChan or errorChan 80 | // channels. If the former arrives, the bytes are returned with a nil error. 81 | // If the latter arrives, no bytes are returned (nil) and the error received 82 | // is returned. 83 | // 84 | // Unless you're building requests from bytes by hand, this method should 85 | // not be used. 86 | func (c Cookie) replyChecked() ([]byte, error) { 87 | if c.replyChan == nil { 88 | return nil, errors.New("Cannot call 'replyChecked' on a cookie that " + 89 | "is not expecting a *reply* or an error.") 90 | } 91 | if c.errorChan == nil { 92 | return nil, errors.New("Cannot call 'replyChecked' on a cookie that " + 93 | "is not expecting a reply or an *error*.") 94 | } 95 | 96 | select { 97 | case reply := <-c.replyChan: 98 | return reply, nil 99 | case err := <-c.errorChan: 100 | return nil, err 101 | } 102 | } 103 | 104 | // replyUnchecked waits for a response on either the replyChan or pingChan 105 | // channels. If the former arrives, the bytes are returned with a nil error. 106 | // If the latter arrives, no bytes are returned (nil) and a nil error 107 | // is returned. (In the latter case, the corresponding error can be retrieved 108 | // from (Wait|Poll)ForEvent asynchronously.) 109 | // In all honesty, you *probably* don't want to use this method. 110 | // 111 | // Unless you're building requests from bytes by hand, this method should 112 | // not be used. 113 | func (c Cookie) replyUnchecked() ([]byte, error) { 114 | if c.replyChan == nil { 115 | return nil, errors.New("Cannot call 'replyUnchecked' on a cookie " + 116 | "that is not expecting a *reply*.") 117 | } 118 | 119 | select { 120 | case reply := <-c.replyChan: 121 | return reply, nil 122 | case <-c.pingChan: 123 | return nil, nil 124 | } 125 | } 126 | 127 | // Check is used for checked requests that have no replies. It is a mechanism 128 | // by which to report "success" or "error" in a synchronous fashion. (Therefore, 129 | // unchecked requests without replies cannot use this method.) 130 | // If the request causes an error, it is sent to this cookie's errorChan. 131 | // If the request was successful, there is no response from the server. 132 | // Thus, pingChan is sent a value when the *next* reply is read. 133 | // If no more replies are being processed, we force a round trip request with 134 | // GetInputFocus. 135 | // 136 | // Unless you're building requests from bytes by hand, this method should 137 | // not be used. 138 | func (c Cookie) Check() error { 139 | if c.replyChan != nil { 140 | return errors.New("Cannot call 'Check' on a cookie that is " + 141 | "expecting a *reply*. Use 'Reply' instead.") 142 | } 143 | if c.errorChan == nil { 144 | return errors.New("Cannot call 'Check' on a cookie that is " + 145 | "not expecting a possible *error*.") 146 | } 147 | 148 | // First do a quick non-blocking check to see if we've been pinged. 149 | select { 150 | case err := <-c.errorChan: 151 | return err 152 | case <-c.pingChan: 153 | return nil 154 | default: 155 | } 156 | 157 | // Now force a round trip and try again, but block this time. 158 | c.conn.Sync() 159 | select { 160 | case err := <-c.errorChan: 161 | return err 162 | case <-c.pingChan: 163 | return nil 164 | } 165 | } 166 | -------------------------------------------------------------------------------- /doc.go: -------------------------------------------------------------------------------- 1 | /* 2 | Package XGB provides the X Go Binding, which is a low-level API to communicate 3 | with the core X protocol and many of the X extensions. 4 | 5 | It is *very* closely modeled on XCB, so that experience with XCB (or xpyb) is 6 | easily translatable to XGB. That is, it uses the same cookie/reply model 7 | and is thread safe. There are otherwise no major differences (in the API). 8 | 9 | Most uses of XGB typically fall under the realm of window manager and GUI kit 10 | development, but other applications (like pagers, panels, tilers, etc.) may 11 | also require XGB. Moreover, it is a near certainty that if you need to work 12 | with X, xgbutil will be of great use to you as well: 13 | https://github.com/BurntSushi/xgbutil 14 | 15 | Example 16 | 17 | This is an extremely terse example that demonstrates how to connect to X, 18 | create a window, listen to StructureNotify events and Key{Press,Release} 19 | events, map the window, and print out all events received. An example with 20 | accompanying documentation can be found in examples/create-window. 21 | 22 | package main 23 | 24 | import ( 25 | "fmt" 26 | "github.com/BurntSushi/xgb" 27 | "github.com/BurntSushi/xgb/xproto" 28 | ) 29 | 30 | func main() { 31 | X, err := xgb.NewConn() 32 | if err != nil { 33 | fmt.Println(err) 34 | return 35 | } 36 | 37 | wid, _ := xproto.NewWindowId(X) 38 | screen := xproto.Setup(X).DefaultScreen(X) 39 | xproto.CreateWindow(X, screen.RootDepth, wid, screen.Root, 40 | 0, 0, 500, 500, 0, 41 | xproto.WindowClassInputOutput, screen.RootVisual, 42 | xproto.CwBackPixel | xproto.CwEventMask, 43 | []uint32{ // values must be in the order defined by the protocol 44 | 0xffffffff, 45 | xproto.EventMaskStructureNotify | 46 | xproto.EventMaskKeyPress | 47 | xproto.EventMaskKeyRelease}) 48 | 49 | xproto.MapWindow(X, wid) 50 | for { 51 | ev, xerr := X.WaitForEvent() 52 | if ev == nil && xerr == nil { 53 | fmt.Println("Both event and error are nil. Exiting...") 54 | return 55 | } 56 | 57 | if ev != nil { 58 | fmt.Printf("Event: %s\n", ev) 59 | } 60 | if xerr != nil { 61 | fmt.Printf("Error: %s\n", xerr) 62 | } 63 | } 64 | } 65 | 66 | Xinerama Example 67 | 68 | This is another small example that shows how to query Xinerama for geometry 69 | information of each active head. Accompanying documentation for this example 70 | can be found in examples/xinerama. 71 | 72 | package main 73 | 74 | import ( 75 | "fmt" 76 | "log" 77 | "github.com/BurntSushi/xgb" 78 | "github.com/BurntSushi/xgb/xinerama" 79 | ) 80 | 81 | func main() { 82 | X, err := xgb.NewConn() 83 | if err != nil { 84 | log.Fatal(err) 85 | } 86 | 87 | // Initialize the Xinerama extension. 88 | // The appropriate 'Init' function must be run for *every* 89 | // extension before any of its requests can be used. 90 | err = xinerama.Init(X) 91 | if err != nil { 92 | log.Fatal(err) 93 | } 94 | 95 | reply, err := xinerama.QueryScreens(X).Reply() 96 | if err != nil { 97 | log.Fatal(err) 98 | } 99 | 100 | fmt.Printf("Number of heads: %d\n", reply.Number) 101 | for i, screen := range reply.ScreenInfo { 102 | fmt.Printf("%d :: X: %d, Y: %d, Width: %d, Height: %d\n", 103 | i, screen.XOrg, screen.YOrg, screen.Width, screen.Height) 104 | } 105 | } 106 | 107 | Parallelism 108 | 109 | XGB can benefit greatly from parallelism due to its concurrent design. For 110 | evidence of this claim, please see the benchmarks in xproto/xproto_test.go. 111 | 112 | Tests 113 | 114 | xproto/xproto_test.go contains a number of contrived tests that stress 115 | particular corners of XGB that I presume could be problem areas. Namely: 116 | requests with no replies, requests with replies, checked errors, unchecked 117 | errors, sequence number wrapping, cookie buffer flushing (i.e., forcing a round 118 | trip every N requests made that don't have a reply), getting/setting properties 119 | and creating a window and listening to StructureNotify events. 120 | 121 | Code Generator 122 | 123 | Both XCB and xpyb use the same Python module (xcbgen) for a code generator. XGB 124 | (before this fork) used the same code generator as well, but in my attempt to 125 | add support for more extensions, I found the code generator extremely difficult 126 | to work with. Therefore, I re-wrote the code generator in Go. It can be found 127 | in its own sub-package, xgbgen, of xgb. My design of xgbgen includes a rough 128 | consideration that it could be used for other languages. 129 | 130 | What works 131 | 132 | I am reasonably confident that the core X protocol is in full working form. I've 133 | also tested the Xinerama and RandR extensions sparingly. Many of the other 134 | existing extensions have Go source generated (and are compilable) and are 135 | included in this package, but I am currently unsure of their status. They 136 | *should* work. 137 | 138 | What does not work 139 | 140 | XKB is the only extension that intentionally does not work, although I suspect 141 | that GLX also does not work (however, there is Go source code for GLX that 142 | compiles, unlike XKB). I don't currently have any intention of getting XKB 143 | working, due to its complexity and my current mental incapacity to test it. 144 | 145 | */ 146 | package xgb 147 | -------------------------------------------------------------------------------- /examples/atoms/.gitignore: -------------------------------------------------------------------------------- 1 | atoms 2 | *.info 3 | *.prof 4 | -------------------------------------------------------------------------------- /examples/atoms/Makefile: -------------------------------------------------------------------------------- 1 | atoms: 2 | go build 3 | 4 | test: atoms 5 | ./atoms --requests 500000 --cpu 1 \ 6 | --cpuprof cpu1.prof --memprof mem1.prof > atoms1.info 2>&1 7 | ./atoms --requests 500000 --cpu 2 \ 8 | --cpuprof cpu2.prof --memprof mem2.prof > atoms2.info 2>&1 9 | ./atoms --requests 500000 --cpu 3 \ 10 | --cpuprof cpu3.prof --memprof mem3.prof > atoms3.info 2>&1 11 | ./atoms --requests 500000 --cpu 6 \ 12 | --cpuprof cpu6.prof --memprof mem6.prof > atoms6.info 2>&1 13 | -------------------------------------------------------------------------------- /examples/atoms/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | "log" 7 | "os" 8 | "runtime" 9 | "runtime/pprof" 10 | "time" 11 | 12 | "github.com/BurntSushi/xgb" 13 | "github.com/BurntSushi/xgb/xproto" 14 | ) 15 | 16 | var ( 17 | flagRequests int 18 | flagGOMAXPROCS int 19 | flagCpuProfName string 20 | flagMemProfName string 21 | ) 22 | 23 | func init() { 24 | flag.IntVar(&flagRequests, "requests", 100000, "Number of atoms to intern.") 25 | flag.IntVar(&flagGOMAXPROCS, "cpu", 1, "Value of GOMAXPROCS.") 26 | flag.StringVar(&flagCpuProfName, "cpuprof", "cpu.prof", 27 | "Name of CPU profile file.") 28 | flag.StringVar(&flagMemProfName, "memprof", "mem.prof", 29 | "Name of memory profile file.") 30 | 31 | flag.Parse() 32 | 33 | runtime.GOMAXPROCS(flagGOMAXPROCS) 34 | } 35 | 36 | func seqNames(n int) []string { 37 | names := make([]string, n) 38 | for i := range names { 39 | names[i] = fmt.Sprintf("NAME%d", i) 40 | } 41 | return names 42 | } 43 | 44 | func main() { 45 | X, err := xgb.NewConn() 46 | if err != nil { 47 | log.Fatal(err) 48 | } 49 | 50 | names := seqNames(flagRequests) 51 | 52 | fcpu, err := os.Create(flagCpuProfName) 53 | if err != nil { 54 | log.Fatal(err) 55 | } 56 | defer fcpu.Close() 57 | pprof.StartCPUProfile(fcpu) 58 | defer pprof.StopCPUProfile() 59 | 60 | start := time.Now() 61 | cookies := make([]xproto.InternAtomCookie, flagRequests) 62 | for i := 0; i < flagRequests; i++ { 63 | cookies[i] = xproto.InternAtom(X, 64 | false, uint16(len(names[i])), names[i]) 65 | } 66 | for _, cookie := range cookies { 67 | cookie.Reply() 68 | } 69 | fmt.Printf("Exec time: %s\n\n", time.Since(start)) 70 | 71 | fmem, err := os.Create(flagMemProfName) 72 | if err != nil { 73 | log.Fatal(err) 74 | } 75 | defer fmem.Close() 76 | pprof.WriteHeapProfile(fmem) 77 | 78 | memStats := &runtime.MemStats{} 79 | runtime.ReadMemStats(memStats) 80 | 81 | // This isn't right. I'm not sure what's wrong. 82 | lastGcTime := time.Unix(int64(memStats.LastGC/1000000000), 83 | int64(memStats.LastGC-memStats.LastGC/1000000000)) 84 | 85 | fmt.Printf("Alloc: %d\n", memStats.Alloc) 86 | fmt.Printf("TotalAlloc: %d\n", memStats.TotalAlloc) 87 | fmt.Printf("LastGC: %s\n", lastGcTime) 88 | fmt.Printf("PauseTotalNs: %d\n", memStats.PauseTotalNs) 89 | fmt.Printf("PauseNs: %d\n", memStats.PauseNs) 90 | fmt.Printf("NumGC: %d\n", memStats.NumGC) 91 | } 92 | -------------------------------------------------------------------------------- /examples/create-window/main.go: -------------------------------------------------------------------------------- 1 | // Example create-window shows how to create a window, map it, resize it, 2 | // and listen to structure and key events (i.e., when the window is resized 3 | // by the window manager, or when key presses/releases are made when the 4 | // window has focus). The events are printed to stdout. 5 | package main 6 | 7 | import ( 8 | "fmt" 9 | 10 | "github.com/BurntSushi/xgb" 11 | "github.com/BurntSushi/xgb/xproto" 12 | ) 13 | 14 | func main() { 15 | X, err := xgb.NewConn() 16 | if err != nil { 17 | fmt.Println(err) 18 | return 19 | } 20 | 21 | // xproto.Setup retrieves the Setup information from the setup bytes 22 | // gathered during connection. 23 | setup := xproto.Setup(X) 24 | 25 | // This is the default screen with all its associated info. 26 | screen := setup.DefaultScreen(X) 27 | 28 | // Any time a new resource (i.e., a window, pixmap, graphics context, etc.) 29 | // is created, we need to generate a resource identifier. 30 | // If the resource is a window, then use xproto.NewWindowId. If it's for 31 | // a pixmap, then use xproto.NewPixmapId. And so on... 32 | wid, _ := xproto.NewWindowId(X) 33 | 34 | // CreateWindow takes a boatload of parameters. 35 | xproto.CreateWindow(X, screen.RootDepth, wid, screen.Root, 36 | 0, 0, 500, 500, 0, 37 | xproto.WindowClassInputOutput, screen.RootVisual, 0, []uint32{}) 38 | 39 | // This call to ChangeWindowAttributes could be factored out and 40 | // included with the above CreateWindow call, but it is left here for 41 | // instructive purposes. It tells X to send us events when the 'structure' 42 | // of the window is changed (i.e., when it is resized, mapped, unmapped, 43 | // etc.) and when a key press or a key release has been made when the 44 | // window has focus. 45 | // We also set the 'BackPixel' to white so that the window isn't butt ugly. 46 | xproto.ChangeWindowAttributes(X, wid, 47 | xproto.CwBackPixel|xproto.CwEventMask, 48 | []uint32{ // values must be in the order defined by the protocol 49 | 0xffffffff, 50 | xproto.EventMaskStructureNotify | 51 | xproto.EventMaskKeyPress | 52 | xproto.EventMaskKeyRelease}) 53 | 54 | // MapWindow makes the window we've created appear on the screen. 55 | // We demonstrated the use of a 'checked' request here. 56 | // A checked request is a fancy way of saying, "do error handling 57 | // synchronously." Namely, if there is a problem with the MapWindow request, 58 | // we'll get the error *here*. If we were to do a normal unchecked 59 | // request (like the above CreateWindow and ChangeWindowAttributes 60 | // requests), then we would only see the error arrive in the main event 61 | // loop. 62 | // 63 | // Typically, checked requests are useful when you need to make sure they 64 | // succeed. Since they are synchronous, they incur a round trip cost before 65 | // the program can continue, but this is only going to be noticeable if 66 | // you're issuing tons of requests in succession. 67 | // 68 | // Note that requests without replies are by default unchecked while 69 | // requests *with* replies are checked by default. 70 | err = xproto.MapWindowChecked(X, wid).Check() 71 | if err != nil { 72 | fmt.Printf("Checked Error for mapping window %d: %s\n", wid, err) 73 | } else { 74 | fmt.Printf("Map window %d successful!\n", wid) 75 | } 76 | 77 | // This is an example of an invalid MapWindow request and what an error 78 | // looks like. 79 | err = xproto.MapWindowChecked(X, 0).Check() 80 | if err != nil { 81 | fmt.Printf("Checked Error for mapping window 0x1: %s\n", err) 82 | } else { // neva 83 | fmt.Printf("Map window 0x1 successful!\n") 84 | } 85 | 86 | // Start the main event loop. 87 | for { 88 | // WaitForEvent either returns an event or an error and never both. 89 | // If both are nil, then something went wrong and the loop should be 90 | // halted. 91 | // 92 | // An error can only be seen here as a response to an unchecked 93 | // request. 94 | ev, xerr := X.WaitForEvent() 95 | if ev == nil && xerr == nil { 96 | fmt.Println("Both event and error are nil. Exiting...") 97 | return 98 | } 99 | 100 | if ev != nil { 101 | fmt.Printf("Event: %s\n", ev) 102 | } 103 | if xerr != nil { 104 | fmt.Printf("Error: %s\n", xerr) 105 | } 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /examples/doc.go: -------------------------------------------------------------------------------- 1 | /* 2 | Package examples contains a few different use cases of XGB, like creating 3 | a window, reading properties, and querying for information about multiple 4 | heads using the Xinerama or RandR extensions. 5 | 6 | If you're looking to get started quickly, I recommend checking out the 7 | create-window example first. It is the most documented and probably covers 8 | some of the more common bare bones cases of creating windows and responding 9 | to events. 10 | 11 | If you're looking to query information about your window manager, 12 | get-active-window is a start. However, to do anything extensive requires 13 | a lot of boiler plate. To that end, I'd recommend use of my higher level 14 | library, xgbutil: https://github.com/BurntSushi/xgbutil 15 | 16 | There are also examples of using the Xinerama and RandR extensions, if you're 17 | interested in querying information about your active heads. In RandR's case, 18 | you can also reconfigure your heads, but the example doesn't cover that. 19 | 20 | */ 21 | package documentation 22 | -------------------------------------------------------------------------------- /examples/get-active-window/main.go: -------------------------------------------------------------------------------- 1 | // Example get-active-window reads the _NET_ACTIVE_WINDOW property of the root 2 | // window and uses the result (a window id) to get the name of the window. 3 | package main 4 | 5 | import ( 6 | "fmt" 7 | "log" 8 | 9 | "github.com/BurntSushi/xgb" 10 | "github.com/BurntSushi/xgb/xproto" 11 | ) 12 | 13 | func main() { 14 | X, err := xgb.NewConn() 15 | if err != nil { 16 | log.Fatal(err) 17 | } 18 | 19 | // Get the window id of the root window. 20 | setup := xproto.Setup(X) 21 | root := setup.DefaultScreen(X).Root 22 | 23 | // Get the atom id (i.e., intern an atom) of "_NET_ACTIVE_WINDOW". 24 | aname := "_NET_ACTIVE_WINDOW" 25 | activeAtom, err := xproto.InternAtom(X, true, uint16(len(aname)), 26 | aname).Reply() 27 | if err != nil { 28 | log.Fatal(err) 29 | } 30 | 31 | // Get the atom id (i.e., intern an atom) of "_NET_WM_NAME". 32 | aname = "_NET_WM_NAME" 33 | nameAtom, err := xproto.InternAtom(X, true, uint16(len(aname)), 34 | aname).Reply() 35 | if err != nil { 36 | log.Fatal(err) 37 | } 38 | 39 | // Get the actual value of _NET_ACTIVE_WINDOW. 40 | // Note that 'reply.Value' is just a slice of bytes, so we use an 41 | // XGB helper function, 'Get32', to pull an unsigned 32-bit integer out 42 | // of the byte slice. We then convert it to an X resource id so it can 43 | // be used to get the name of the window in the next GetProperty request. 44 | reply, err := xproto.GetProperty(X, false, root, activeAtom.Atom, 45 | xproto.GetPropertyTypeAny, 0, (1<<32)-1).Reply() 46 | if err != nil { 47 | log.Fatal(err) 48 | } 49 | windowId := xproto.Window(xgb.Get32(reply.Value)) 50 | fmt.Printf("Active window id: %X\n", windowId) 51 | 52 | // Now get the value of _NET_WM_NAME for the active window. 53 | // Note that this time, we simply convert the resulting byte slice, 54 | // reply.Value, to a string. 55 | reply, err = xproto.GetProperty(X, false, windowId, nameAtom.Atom, 56 | xproto.GetPropertyTypeAny, 0, (1<<32)-1).Reply() 57 | if err != nil { 58 | log.Fatal(err) 59 | } 60 | fmt.Printf("Active window name: %s\n", string(reply.Value)) 61 | } 62 | -------------------------------------------------------------------------------- /examples/randr/main.go: -------------------------------------------------------------------------------- 1 | // Example randr uses the randr protocol to get information about the active 2 | // heads. It also listens for events that are sent when the head configuration 3 | // changes. Since it listens to events, you'll have to manually kill this 4 | // process when you're done (i.e., ctrl+c.) 5 | // 6 | // While this program is running, if you use 'xrandr' to reconfigure your 7 | // heads, you should see event information dumped to standard out. 8 | // 9 | // For more information, please see the RandR protocol spec: 10 | // http://www.x.org/releases/X11R7.6/doc/randrproto/randrproto.txt 11 | package main 12 | 13 | import ( 14 | "fmt" 15 | "log" 16 | 17 | "github.com/BurntSushi/xgb" 18 | "github.com/BurntSushi/xgb/randr" 19 | "github.com/BurntSushi/xgb/xproto" 20 | ) 21 | 22 | func main() { 23 | X, _ := xgb.NewConn() 24 | 25 | // Every extension must be initialized before it can be used. 26 | err := randr.Init(X) 27 | if err != nil { 28 | log.Fatal(err) 29 | } 30 | 31 | // Get the root window on the default screen. 32 | root := xproto.Setup(X).DefaultScreen(X).Root 33 | 34 | // Gets the current screen resources. Screen resources contains a list 35 | // of names, crtcs, outputs and modes, among other things. 36 | resources, err := randr.GetScreenResources(X, root).Reply() 37 | if err != nil { 38 | log.Fatal(err) 39 | } 40 | 41 | // Iterate through all of the outputs and show some of their info. 42 | for _, output := range resources.Outputs { 43 | info, err := randr.GetOutputInfo(X, output, 0).Reply() 44 | if err != nil { 45 | log.Fatal(err) 46 | } 47 | 48 | if info.Connection == randr.ConnectionConnected { 49 | bestMode := info.Modes[0] 50 | for _, mode := range resources.Modes { 51 | if mode.Id == uint32(bestMode) { 52 | fmt.Printf("Width: %d, Height: %d\n", 53 | mode.Width, mode.Height) 54 | } 55 | } 56 | } 57 | } 58 | 59 | fmt.Println("\n") 60 | 61 | // Iterate through all of the crtcs and show some of their info. 62 | for _, crtc := range resources.Crtcs { 63 | info, err := randr.GetCrtcInfo(X, crtc, 0).Reply() 64 | if err != nil { 65 | log.Fatal(err) 66 | } 67 | fmt.Printf("X: %d, Y: %d, Width: %d, Height: %d\n", 68 | info.X, info.Y, info.Width, info.Height) 69 | } 70 | 71 | // Tell RandR to send us events. (I think these are all of them, as of 1.3.) 72 | err = randr.SelectInputChecked(X, root, 73 | randr.NotifyMaskScreenChange| 74 | randr.NotifyMaskCrtcChange| 75 | randr.NotifyMaskOutputChange| 76 | randr.NotifyMaskOutputProperty).Check() 77 | if err != nil { 78 | log.Fatal(err) 79 | } 80 | 81 | // Listen to events and just dump them to standard out. 82 | // A more involved approach will have to read the 'U' field of 83 | // RandrNotifyEvent, which is a union (really a struct) of type 84 | // RanrNotifyDataUnion. 85 | for { 86 | ev, err := X.WaitForEvent() 87 | if err != nil { 88 | log.Fatal(err) 89 | } 90 | fmt.Println(ev) 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /examples/xinerama/main.go: -------------------------------------------------------------------------------- 1 | // Example xinerama shows how to query the geometry of all active heads. 2 | package main 3 | 4 | import ( 5 | "fmt" 6 | "log" 7 | 8 | "github.com/BurntSushi/xgb" 9 | "github.com/BurntSushi/xgb/xinerama" 10 | ) 11 | 12 | func main() { 13 | X, err := xgb.NewConn() 14 | if err != nil { 15 | log.Fatal(err) 16 | } 17 | 18 | // Initialize the Xinerama extension. 19 | // The appropriate 'Init' function must be run for *every* 20 | // extension before any of its requests can be used. 21 | err = xinerama.Init(X) 22 | if err != nil { 23 | log.Fatal(err) 24 | } 25 | 26 | // Issue a request to get the screen information. 27 | reply, err := xinerama.QueryScreens(X).Reply() 28 | if err != nil { 29 | log.Fatal(err) 30 | } 31 | 32 | // reply.Number is the number of active heads, while reply.ScreenInfo 33 | // is a slice of XineramaScreenInfo containing the rectangle geometry 34 | // of each head. 35 | fmt.Printf("Number of heads: %d\n", reply.Number) 36 | for i, screen := range reply.ScreenInfo { 37 | fmt.Printf("%d :: X: %d, Y: %d, Width: %d, Height: %d\n", 38 | i, screen.XOrg, screen.YOrg, screen.Width, screen.Height) 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /ge/ge.go: -------------------------------------------------------------------------------- 1 | // Package ge is the X client API for the Generic Event Extension extension. 2 | package ge 3 | 4 | // This file is automatically generated from ge.xml. Edit at your peril! 5 | 6 | import ( 7 | "github.com/BurntSushi/xgb" 8 | 9 | "github.com/BurntSushi/xgb/xproto" 10 | ) 11 | 12 | // Init must be called before using the Generic Event Extension extension. 13 | func Init(c *xgb.Conn) error { 14 | reply, err := xproto.QueryExtension(c, 23, "Generic Event Extension").Reply() 15 | switch { 16 | case err != nil: 17 | return err 18 | case !reply.Present: 19 | return xgb.Errorf("No extension named Generic Event Extension could be found on on the server.") 20 | } 21 | 22 | c.ExtLock.Lock() 23 | c.Extensions["Generic Event Extension"] = reply.MajorOpcode 24 | c.ExtLock.Unlock() 25 | for evNum, fun := range xgb.NewExtEventFuncs["Generic Event Extension"] { 26 | xgb.NewEventFuncs[int(reply.FirstEvent)+evNum] = fun 27 | } 28 | for errNum, fun := range xgb.NewExtErrorFuncs["Generic Event Extension"] { 29 | xgb.NewErrorFuncs[int(reply.FirstError)+errNum] = fun 30 | } 31 | return nil 32 | } 33 | 34 | func init() { 35 | xgb.NewExtEventFuncs["Generic Event Extension"] = make(map[int]xgb.NewEventFun) 36 | xgb.NewExtErrorFuncs["Generic Event Extension"] = make(map[int]xgb.NewErrorFun) 37 | } 38 | 39 | // Skipping definition for base type 'Bool' 40 | 41 | // Skipping definition for base type 'Byte' 42 | 43 | // Skipping definition for base type 'Card8' 44 | 45 | // Skipping definition for base type 'Char' 46 | 47 | // Skipping definition for base type 'Void' 48 | 49 | // Skipping definition for base type 'Double' 50 | 51 | // Skipping definition for base type 'Float' 52 | 53 | // Skipping definition for base type 'Int16' 54 | 55 | // Skipping definition for base type 'Int32' 56 | 57 | // Skipping definition for base type 'Int8' 58 | 59 | // Skipping definition for base type 'Card16' 60 | 61 | // Skipping definition for base type 'Card32' 62 | 63 | // QueryVersionCookie is a cookie used only for QueryVersion requests. 64 | type QueryVersionCookie struct { 65 | *xgb.Cookie 66 | } 67 | 68 | // QueryVersion sends a checked request. 69 | // If an error occurs, it will be returned with the reply by calling QueryVersionCookie.Reply() 70 | func QueryVersion(c *xgb.Conn, ClientMajorVersion uint16, ClientMinorVersion uint16) QueryVersionCookie { 71 | c.ExtLock.RLock() 72 | defer c.ExtLock.RUnlock() 73 | if _, ok := c.Extensions["Generic Event Extension"]; !ok { 74 | panic("Cannot issue request 'QueryVersion' using the uninitialized extension 'Generic Event Extension'. ge.Init(connObj) must be called first.") 75 | } 76 | cookie := c.NewCookie(true, true) 77 | c.NewRequest(queryVersionRequest(c, ClientMajorVersion, ClientMinorVersion), cookie) 78 | return QueryVersionCookie{cookie} 79 | } 80 | 81 | // QueryVersionUnchecked sends an unchecked request. 82 | // If an error occurs, it can only be retrieved using xgb.WaitForEvent or xgb.PollForEvent. 83 | func QueryVersionUnchecked(c *xgb.Conn, ClientMajorVersion uint16, ClientMinorVersion uint16) QueryVersionCookie { 84 | c.ExtLock.RLock() 85 | defer c.ExtLock.RUnlock() 86 | if _, ok := c.Extensions["Generic Event Extension"]; !ok { 87 | panic("Cannot issue request 'QueryVersion' using the uninitialized extension 'Generic Event Extension'. ge.Init(connObj) must be called first.") 88 | } 89 | cookie := c.NewCookie(false, true) 90 | c.NewRequest(queryVersionRequest(c, ClientMajorVersion, ClientMinorVersion), cookie) 91 | return QueryVersionCookie{cookie} 92 | } 93 | 94 | // QueryVersionReply represents the data returned from a QueryVersion request. 95 | type QueryVersionReply struct { 96 | Sequence uint16 // sequence number of the request for this reply 97 | Length uint32 // number of bytes in this reply 98 | // padding: 1 bytes 99 | MajorVersion uint16 100 | MinorVersion uint16 101 | // padding: 20 bytes 102 | } 103 | 104 | // Reply blocks and returns the reply data for a QueryVersion request. 105 | func (cook QueryVersionCookie) Reply() (*QueryVersionReply, error) { 106 | buf, err := cook.Cookie.Reply() 107 | if err != nil { 108 | return nil, err 109 | } 110 | if buf == nil { 111 | return nil, nil 112 | } 113 | return queryVersionReply(buf), nil 114 | } 115 | 116 | // queryVersionReply reads a byte slice into a QueryVersionReply value. 117 | func queryVersionReply(buf []byte) *QueryVersionReply { 118 | v := new(QueryVersionReply) 119 | b := 1 // skip reply determinant 120 | 121 | b += 1 // padding 122 | 123 | v.Sequence = xgb.Get16(buf[b:]) 124 | b += 2 125 | 126 | v.Length = xgb.Get32(buf[b:]) // 4-byte units 127 | b += 4 128 | 129 | v.MajorVersion = xgb.Get16(buf[b:]) 130 | b += 2 131 | 132 | v.MinorVersion = xgb.Get16(buf[b:]) 133 | b += 2 134 | 135 | b += 20 // padding 136 | 137 | return v 138 | } 139 | 140 | // Write request to wire for QueryVersion 141 | // queryVersionRequest writes a QueryVersion request to a byte slice. 142 | func queryVersionRequest(c *xgb.Conn, ClientMajorVersion uint16, ClientMinorVersion uint16) []byte { 143 | size := 8 144 | b := 0 145 | buf := make([]byte, size) 146 | 147 | c.ExtLock.RLock() 148 | buf[b] = c.Extensions["Generic Event Extension"] 149 | c.ExtLock.RUnlock() 150 | b += 1 151 | 152 | buf[b] = 0 // request opcode 153 | b += 1 154 | 155 | xgb.Put16(buf[b:], uint16(size/4)) // write request size in 4-byte units 156 | b += 2 157 | 158 | xgb.Put16(buf[b:], ClientMajorVersion) 159 | b += 2 160 | 161 | xgb.Put16(buf[b:], ClientMinorVersion) 162 | b += 2 163 | 164 | return buf 165 | } 166 | -------------------------------------------------------------------------------- /help.go: -------------------------------------------------------------------------------- 1 | package xgb 2 | 3 | /* 4 | help.go is meant to contain a rough hodge podge of functions that are mainly 5 | used in the auto generated code. Indeed, several functions here are simple 6 | wrappers so that the sub-packages don't need to be smart about which stdlib 7 | packages to import. 8 | 9 | Also, the 'Get..' and 'Put..' functions are used through the core xgb package 10 | too. (xgbutil uses them too.) 11 | */ 12 | 13 | import ( 14 | "fmt" 15 | "strings" 16 | ) 17 | 18 | // StringsJoin is an alias to strings.Join. It allows us to avoid having to 19 | // import 'strings' in each of the generated Go files. 20 | func StringsJoin(ss []string, sep string) string { 21 | return strings.Join(ss, sep) 22 | } 23 | 24 | // Sprintf is so we don't need to import 'fmt' in the generated Go files. 25 | func Sprintf(format string, v ...interface{}) string { 26 | return fmt.Sprintf(format, v...) 27 | } 28 | 29 | // Errorf is just a wrapper for fmt.Errorf. Exists for the same reason 30 | // that 'stringsJoin' and 'sprintf' exists. 31 | func Errorf(format string, v ...interface{}) error { 32 | return fmt.Errorf(format, v...) 33 | } 34 | 35 | // Pad a length to align on 4 bytes. 36 | func Pad(n int) int { 37 | return (n + 3) & ^3 38 | } 39 | 40 | // PopCount counts the number of bits set in a value list mask. 41 | func PopCount(mask0 int) int { 42 | mask := uint32(mask0) 43 | n := 0 44 | for i := uint32(0); i < 32; i++ { 45 | if mask&(1<> 8) 56 | } 57 | 58 | // Put32 takes a 32 bit integer and copies it into a byte slice. 59 | func Put32(buf []byte, v uint32) { 60 | buf[0] = byte(v) 61 | buf[1] = byte(v >> 8) 62 | buf[2] = byte(v >> 16) 63 | buf[3] = byte(v >> 24) 64 | } 65 | 66 | // Put64 takes a 64 bit integer and copies it into a byte slice. 67 | func Put64(buf []byte, v uint64) { 68 | buf[0] = byte(v) 69 | buf[1] = byte(v >> 8) 70 | buf[2] = byte(v >> 16) 71 | buf[3] = byte(v >> 24) 72 | buf[4] = byte(v >> 32) 73 | buf[5] = byte(v >> 40) 74 | buf[6] = byte(v >> 48) 75 | buf[7] = byte(v >> 56) 76 | } 77 | 78 | // Get16 constructs a 16 bit integer from the beginning of a byte slice. 79 | func Get16(buf []byte) uint16 { 80 | v := uint16(buf[0]) 81 | v |= uint16(buf[1]) << 8 82 | return v 83 | } 84 | 85 | // Get32 constructs a 32 bit integer from the beginning of a byte slice. 86 | func Get32(buf []byte) uint32 { 87 | v := uint32(buf[0]) 88 | v |= uint32(buf[1]) << 8 89 | v |= uint32(buf[2]) << 16 90 | v |= uint32(buf[3]) << 24 91 | return v 92 | } 93 | 94 | // Get64 constructs a 64 bit integer from the beginning of a byte slice. 95 | func Get64(buf []byte) uint64 { 96 | v := uint64(buf[0]) 97 | v |= uint64(buf[1]) << 8 98 | v |= uint64(buf[2]) << 16 99 | v |= uint64(buf[3]) << 24 100 | v |= uint64(buf[4]) << 32 101 | v |= uint64(buf[5]) << 40 102 | v |= uint64(buf[6]) << 48 103 | v |= uint64(buf[7]) << 56 104 | return v 105 | } 106 | -------------------------------------------------------------------------------- /sync.go: -------------------------------------------------------------------------------- 1 | package xgb 2 | 3 | // Sync sends a round trip request and waits for the response. 4 | // This forces all pending cookies to be dealt with. 5 | // You actually shouldn't need to use this like you might with Xlib. Namely, 6 | // buffers are automatically flushed using Go's channels and round trip requests 7 | // are forced where appropriate automatically. 8 | func (c *Conn) Sync() { 9 | cookie := c.NewCookie(true, true) 10 | c.NewRequest(c.getInputFocusRequest(), cookie) 11 | cookie.Reply() // wait for the buffer to clear 12 | } 13 | 14 | // getInputFocusRequest writes the raw bytes to a buffer. 15 | // It is duplicated from xproto/xproto.go. 16 | func (c *Conn) getInputFocusRequest() []byte { 17 | size := 4 18 | b := 0 19 | buf := make([]byte, size) 20 | 21 | buf[b] = 43 // request opcode 22 | b += 1 23 | 24 | b += 1 // padding 25 | Put16(buf[b:], uint16(size/4)) // write request size in 4-byte units 26 | b += 2 27 | 28 | return buf 29 | } 30 | -------------------------------------------------------------------------------- /xcmisc/xcmisc.go: -------------------------------------------------------------------------------- 1 | // Package xcmisc is the X client API for the XC-MISC extension. 2 | package xcmisc 3 | 4 | // This file is automatically generated from xc_misc.xml. Edit at your peril! 5 | 6 | import ( 7 | "github.com/BurntSushi/xgb" 8 | 9 | "github.com/BurntSushi/xgb/xproto" 10 | ) 11 | 12 | // Init must be called before using the XC-MISC extension. 13 | func Init(c *xgb.Conn) error { 14 | reply, err := xproto.QueryExtension(c, 7, "XC-MISC").Reply() 15 | switch { 16 | case err != nil: 17 | return err 18 | case !reply.Present: 19 | return xgb.Errorf("No extension named XC-MISC could be found on on the server.") 20 | } 21 | 22 | c.ExtLock.Lock() 23 | c.Extensions["XC-MISC"] = reply.MajorOpcode 24 | c.ExtLock.Unlock() 25 | for evNum, fun := range xgb.NewExtEventFuncs["XC-MISC"] { 26 | xgb.NewEventFuncs[int(reply.FirstEvent)+evNum] = fun 27 | } 28 | for errNum, fun := range xgb.NewExtErrorFuncs["XC-MISC"] { 29 | xgb.NewErrorFuncs[int(reply.FirstError)+errNum] = fun 30 | } 31 | return nil 32 | } 33 | 34 | func init() { 35 | xgb.NewExtEventFuncs["XC-MISC"] = make(map[int]xgb.NewEventFun) 36 | xgb.NewExtErrorFuncs["XC-MISC"] = make(map[int]xgb.NewErrorFun) 37 | } 38 | 39 | // Skipping definition for base type 'Bool' 40 | 41 | // Skipping definition for base type 'Byte' 42 | 43 | // Skipping definition for base type 'Card8' 44 | 45 | // Skipping definition for base type 'Char' 46 | 47 | // Skipping definition for base type 'Void' 48 | 49 | // Skipping definition for base type 'Double' 50 | 51 | // Skipping definition for base type 'Float' 52 | 53 | // Skipping definition for base type 'Int16' 54 | 55 | // Skipping definition for base type 'Int32' 56 | 57 | // Skipping definition for base type 'Int8' 58 | 59 | // Skipping definition for base type 'Card16' 60 | 61 | // Skipping definition for base type 'Card32' 62 | 63 | // GetVersionCookie is a cookie used only for GetVersion requests. 64 | type GetVersionCookie struct { 65 | *xgb.Cookie 66 | } 67 | 68 | // GetVersion sends a checked request. 69 | // If an error occurs, it will be returned with the reply by calling GetVersionCookie.Reply() 70 | func GetVersion(c *xgb.Conn, ClientMajorVersion uint16, ClientMinorVersion uint16) GetVersionCookie { 71 | c.ExtLock.RLock() 72 | defer c.ExtLock.RUnlock() 73 | if _, ok := c.Extensions["XC-MISC"]; !ok { 74 | panic("Cannot issue request 'GetVersion' using the uninitialized extension 'XC-MISC'. xcmisc.Init(connObj) must be called first.") 75 | } 76 | cookie := c.NewCookie(true, true) 77 | c.NewRequest(getVersionRequest(c, ClientMajorVersion, ClientMinorVersion), cookie) 78 | return GetVersionCookie{cookie} 79 | } 80 | 81 | // GetVersionUnchecked sends an unchecked request. 82 | // If an error occurs, it can only be retrieved using xgb.WaitForEvent or xgb.PollForEvent. 83 | func GetVersionUnchecked(c *xgb.Conn, ClientMajorVersion uint16, ClientMinorVersion uint16) GetVersionCookie { 84 | c.ExtLock.RLock() 85 | defer c.ExtLock.RUnlock() 86 | if _, ok := c.Extensions["XC-MISC"]; !ok { 87 | panic("Cannot issue request 'GetVersion' using the uninitialized extension 'XC-MISC'. xcmisc.Init(connObj) must be called first.") 88 | } 89 | cookie := c.NewCookie(false, true) 90 | c.NewRequest(getVersionRequest(c, ClientMajorVersion, ClientMinorVersion), cookie) 91 | return GetVersionCookie{cookie} 92 | } 93 | 94 | // GetVersionReply represents the data returned from a GetVersion request. 95 | type GetVersionReply struct { 96 | Sequence uint16 // sequence number of the request for this reply 97 | Length uint32 // number of bytes in this reply 98 | // padding: 1 bytes 99 | ServerMajorVersion uint16 100 | ServerMinorVersion uint16 101 | } 102 | 103 | // Reply blocks and returns the reply data for a GetVersion request. 104 | func (cook GetVersionCookie) Reply() (*GetVersionReply, error) { 105 | buf, err := cook.Cookie.Reply() 106 | if err != nil { 107 | return nil, err 108 | } 109 | if buf == nil { 110 | return nil, nil 111 | } 112 | return getVersionReply(buf), nil 113 | } 114 | 115 | // getVersionReply reads a byte slice into a GetVersionReply value. 116 | func getVersionReply(buf []byte) *GetVersionReply { 117 | v := new(GetVersionReply) 118 | b := 1 // skip reply determinant 119 | 120 | b += 1 // padding 121 | 122 | v.Sequence = xgb.Get16(buf[b:]) 123 | b += 2 124 | 125 | v.Length = xgb.Get32(buf[b:]) // 4-byte units 126 | b += 4 127 | 128 | v.ServerMajorVersion = xgb.Get16(buf[b:]) 129 | b += 2 130 | 131 | v.ServerMinorVersion = xgb.Get16(buf[b:]) 132 | b += 2 133 | 134 | return v 135 | } 136 | 137 | // Write request to wire for GetVersion 138 | // getVersionRequest writes a GetVersion request to a byte slice. 139 | func getVersionRequest(c *xgb.Conn, ClientMajorVersion uint16, ClientMinorVersion uint16) []byte { 140 | size := 8 141 | b := 0 142 | buf := make([]byte, size) 143 | 144 | c.ExtLock.RLock() 145 | buf[b] = c.Extensions["XC-MISC"] 146 | c.ExtLock.RUnlock() 147 | b += 1 148 | 149 | buf[b] = 0 // request opcode 150 | b += 1 151 | 152 | xgb.Put16(buf[b:], uint16(size/4)) // write request size in 4-byte units 153 | b += 2 154 | 155 | xgb.Put16(buf[b:], ClientMajorVersion) 156 | b += 2 157 | 158 | xgb.Put16(buf[b:], ClientMinorVersion) 159 | b += 2 160 | 161 | return buf 162 | } 163 | 164 | // GetXIDListCookie is a cookie used only for GetXIDList requests. 165 | type GetXIDListCookie struct { 166 | *xgb.Cookie 167 | } 168 | 169 | // GetXIDList sends a checked request. 170 | // If an error occurs, it will be returned with the reply by calling GetXIDListCookie.Reply() 171 | func GetXIDList(c *xgb.Conn, Count uint32) GetXIDListCookie { 172 | c.ExtLock.RLock() 173 | defer c.ExtLock.RUnlock() 174 | if _, ok := c.Extensions["XC-MISC"]; !ok { 175 | panic("Cannot issue request 'GetXIDList' using the uninitialized extension 'XC-MISC'. xcmisc.Init(connObj) must be called first.") 176 | } 177 | cookie := c.NewCookie(true, true) 178 | c.NewRequest(getXIDListRequest(c, Count), cookie) 179 | return GetXIDListCookie{cookie} 180 | } 181 | 182 | // GetXIDListUnchecked sends an unchecked request. 183 | // If an error occurs, it can only be retrieved using xgb.WaitForEvent or xgb.PollForEvent. 184 | func GetXIDListUnchecked(c *xgb.Conn, Count uint32) GetXIDListCookie { 185 | c.ExtLock.RLock() 186 | defer c.ExtLock.RUnlock() 187 | if _, ok := c.Extensions["XC-MISC"]; !ok { 188 | panic("Cannot issue request 'GetXIDList' using the uninitialized extension 'XC-MISC'. xcmisc.Init(connObj) must be called first.") 189 | } 190 | cookie := c.NewCookie(false, true) 191 | c.NewRequest(getXIDListRequest(c, Count), cookie) 192 | return GetXIDListCookie{cookie} 193 | } 194 | 195 | // GetXIDListReply represents the data returned from a GetXIDList request. 196 | type GetXIDListReply struct { 197 | Sequence uint16 // sequence number of the request for this reply 198 | Length uint32 // number of bytes in this reply 199 | // padding: 1 bytes 200 | IdsLen uint32 201 | // padding: 20 bytes 202 | Ids []uint32 // size: xgb.Pad((int(IdsLen) * 4)) 203 | } 204 | 205 | // Reply blocks and returns the reply data for a GetXIDList request. 206 | func (cook GetXIDListCookie) Reply() (*GetXIDListReply, error) { 207 | buf, err := cook.Cookie.Reply() 208 | if err != nil { 209 | return nil, err 210 | } 211 | if buf == nil { 212 | return nil, nil 213 | } 214 | return getXIDListReply(buf), nil 215 | } 216 | 217 | // getXIDListReply reads a byte slice into a GetXIDListReply value. 218 | func getXIDListReply(buf []byte) *GetXIDListReply { 219 | v := new(GetXIDListReply) 220 | b := 1 // skip reply determinant 221 | 222 | b += 1 // padding 223 | 224 | v.Sequence = xgb.Get16(buf[b:]) 225 | b += 2 226 | 227 | v.Length = xgb.Get32(buf[b:]) // 4-byte units 228 | b += 4 229 | 230 | v.IdsLen = xgb.Get32(buf[b:]) 231 | b += 4 232 | 233 | b += 20 // padding 234 | 235 | v.Ids = make([]uint32, v.IdsLen) 236 | for i := 0; i < int(v.IdsLen); i++ { 237 | v.Ids[i] = xgb.Get32(buf[b:]) 238 | b += 4 239 | } 240 | 241 | return v 242 | } 243 | 244 | // Write request to wire for GetXIDList 245 | // getXIDListRequest writes a GetXIDList request to a byte slice. 246 | func getXIDListRequest(c *xgb.Conn, Count uint32) []byte { 247 | size := 8 248 | b := 0 249 | buf := make([]byte, size) 250 | 251 | c.ExtLock.RLock() 252 | buf[b] = c.Extensions["XC-MISC"] 253 | c.ExtLock.RUnlock() 254 | b += 1 255 | 256 | buf[b] = 2 // request opcode 257 | b += 1 258 | 259 | xgb.Put16(buf[b:], uint16(size/4)) // write request size in 4-byte units 260 | b += 2 261 | 262 | xgb.Put32(buf[b:], Count) 263 | b += 4 264 | 265 | return buf 266 | } 267 | 268 | // GetXIDRangeCookie is a cookie used only for GetXIDRange requests. 269 | type GetXIDRangeCookie struct { 270 | *xgb.Cookie 271 | } 272 | 273 | // GetXIDRange sends a checked request. 274 | // If an error occurs, it will be returned with the reply by calling GetXIDRangeCookie.Reply() 275 | func GetXIDRange(c *xgb.Conn) GetXIDRangeCookie { 276 | c.ExtLock.RLock() 277 | defer c.ExtLock.RUnlock() 278 | if _, ok := c.Extensions["XC-MISC"]; !ok { 279 | panic("Cannot issue request 'GetXIDRange' using the uninitialized extension 'XC-MISC'. xcmisc.Init(connObj) must be called first.") 280 | } 281 | cookie := c.NewCookie(true, true) 282 | c.NewRequest(getXIDRangeRequest(c), cookie) 283 | return GetXIDRangeCookie{cookie} 284 | } 285 | 286 | // GetXIDRangeUnchecked sends an unchecked request. 287 | // If an error occurs, it can only be retrieved using xgb.WaitForEvent or xgb.PollForEvent. 288 | func GetXIDRangeUnchecked(c *xgb.Conn) GetXIDRangeCookie { 289 | c.ExtLock.RLock() 290 | defer c.ExtLock.RUnlock() 291 | if _, ok := c.Extensions["XC-MISC"]; !ok { 292 | panic("Cannot issue request 'GetXIDRange' using the uninitialized extension 'XC-MISC'. xcmisc.Init(connObj) must be called first.") 293 | } 294 | cookie := c.NewCookie(false, true) 295 | c.NewRequest(getXIDRangeRequest(c), cookie) 296 | return GetXIDRangeCookie{cookie} 297 | } 298 | 299 | // GetXIDRangeReply represents the data returned from a GetXIDRange request. 300 | type GetXIDRangeReply struct { 301 | Sequence uint16 // sequence number of the request for this reply 302 | Length uint32 // number of bytes in this reply 303 | // padding: 1 bytes 304 | StartId uint32 305 | Count uint32 306 | } 307 | 308 | // Reply blocks and returns the reply data for a GetXIDRange request. 309 | func (cook GetXIDRangeCookie) Reply() (*GetXIDRangeReply, error) { 310 | buf, err := cook.Cookie.Reply() 311 | if err != nil { 312 | return nil, err 313 | } 314 | if buf == nil { 315 | return nil, nil 316 | } 317 | return getXIDRangeReply(buf), nil 318 | } 319 | 320 | // getXIDRangeReply reads a byte slice into a GetXIDRangeReply value. 321 | func getXIDRangeReply(buf []byte) *GetXIDRangeReply { 322 | v := new(GetXIDRangeReply) 323 | b := 1 // skip reply determinant 324 | 325 | b += 1 // padding 326 | 327 | v.Sequence = xgb.Get16(buf[b:]) 328 | b += 2 329 | 330 | v.Length = xgb.Get32(buf[b:]) // 4-byte units 331 | b += 4 332 | 333 | v.StartId = xgb.Get32(buf[b:]) 334 | b += 4 335 | 336 | v.Count = xgb.Get32(buf[b:]) 337 | b += 4 338 | 339 | return v 340 | } 341 | 342 | // Write request to wire for GetXIDRange 343 | // getXIDRangeRequest writes a GetXIDRange request to a byte slice. 344 | func getXIDRangeRequest(c *xgb.Conn) []byte { 345 | size := 4 346 | b := 0 347 | buf := make([]byte, size) 348 | 349 | c.ExtLock.RLock() 350 | buf[b] = c.Extensions["XC-MISC"] 351 | c.ExtLock.RUnlock() 352 | b += 1 353 | 354 | buf[b] = 1 // request opcode 355 | b += 1 356 | 357 | xgb.Put16(buf[b:], uint16(size/4)) // write request size in 4-byte units 358 | b += 2 359 | 360 | return buf 361 | } 362 | -------------------------------------------------------------------------------- /xgbgen/COPYING: -------------------------------------------------------------------------------- 1 | DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE 2 | Version 2, December 2004 3 | 4 | Copyright (C) 2004 Sam Hocevar 5 | 6 | Everyone is permitted to copy and distribute verbatim or modified 7 | copies of this license document, and changing it is allowed as long 8 | as the name is changed. 9 | 10 | DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE 11 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 12 | 13 | 0. You just DO WHAT THE FUCK YOU WANT TO. 14 | -------------------------------------------------------------------------------- /xgbgen/aligngap.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | ) 7 | 8 | func (p *Protocol) AddAlignGaps() { 9 | for i := range p.Imports { 10 | p.Imports[i].AddAlignGaps() 11 | } 12 | for i := range p.Types { 13 | switch t := p.Types[i].(type) { 14 | case *Struct: 15 | t.Fields = addAlignGapsToFields(t.xmlName, t.Fields) 16 | case *Event: 17 | t.Fields = addAlignGapsToFields(t.xmlName, t.Fields) 18 | case *Error: 19 | t.Fields = addAlignGapsToFields(t.xmlName, t.Fields) 20 | } 21 | } 22 | for i := range p.Requests { 23 | p.Requests[i].Fields = addAlignGapsToFields( 24 | p.Requests[i].xmlName, p.Requests[i].Fields) 25 | if p.Requests[i].Reply != nil { 26 | p.Requests[i].Reply.Fields = addAlignGapsToFields( 27 | p.Requests[i].xmlName, p.Requests[i].Reply.Fields) 28 | } 29 | } 30 | } 31 | 32 | func addAlignGapsToFields(name string, fields []Field) []Field { 33 | var i int 34 | for i = 0; i < len(fields); i++ { 35 | if _, ok := fields[i].(*ListField); ok { 36 | break 37 | } 38 | } 39 | if i >= len(fields) { 40 | return fields 41 | } 42 | 43 | r := make([]Field, 0, len(fields)+2) 44 | r = append(r, fields[:i]...) 45 | 46 | r = append(r, fields[i]) 47 | for i = i + 1; i < len(fields); i++ { 48 | switch f := fields[i].(type) { 49 | case *ListField: 50 | // ok, add padding 51 | sz := xcbSizeOfType(f.Type) 52 | switch { 53 | case sz == 1: 54 | // nothing 55 | case sz == 2: 56 | r = append(r, &PadField{0, 2}) 57 | case sz == 3: 58 | panic(fmt.Errorf("Alignment is not a power of 2")) 59 | case sz >= 4: 60 | r = append(r, &PadField{0, 4}) 61 | } 62 | case *LocalField: 63 | // nothing 64 | default: 65 | fmt.Fprintf(os.Stderr, 66 | "Can't add alignment gaps, mix of list and non-list "+ 67 | "fields: %s\n", name) 68 | return fields 69 | } 70 | r = append(r, fields[i]) 71 | } 72 | return r 73 | } 74 | 75 | func xcbSizeOfField(fld Field) int { 76 | switch f := fld.(type) { 77 | case *PadField: 78 | return int(f.Bytes) 79 | case *SingleField: 80 | return xcbSizeOfType(f.Type) 81 | case *ListField: 82 | return 0 83 | case *ExprField: 84 | return xcbSizeOfType(f.Type) 85 | case *ValueField: 86 | return xcbSizeOfType(f.MaskType) 87 | case *SwitchField: 88 | return 0 89 | default: 90 | return 0 91 | } 92 | } 93 | 94 | func xcbSizeOfType(typ Type) int { 95 | switch t := typ.(type) { 96 | case *Resource: 97 | return 4 98 | case *TypeDef: 99 | return t.Size().Eval() 100 | case *Base: 101 | return t.Size().Eval() 102 | case *Struct: 103 | sz := 0 104 | for i := range t.Fields { 105 | sz += xcbSizeOfField(t.Fields[i]) 106 | } 107 | return sz 108 | case *Union: 109 | sz := 0 110 | for i := range t.Fields { 111 | csz := xcbSizeOfField(t.Fields[i]) 112 | if csz > sz { 113 | sz = csz 114 | } 115 | } 116 | return sz 117 | default: 118 | return 0 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /xgbgen/context.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bytes" 5 | "encoding/xml" 6 | "fmt" 7 | "log" 8 | "sort" 9 | ) 10 | 11 | // Context represents the protocol we're converting to Go, and a writer 12 | // buffer to write the Go source to. 13 | type Context struct { 14 | protocol *Protocol 15 | out *bytes.Buffer 16 | } 17 | 18 | func newContext() *Context { 19 | return &Context{ 20 | out: bytes.NewBuffer([]byte{}), 21 | } 22 | } 23 | 24 | // Putln calls put and adds a new line to the end of 'format'. 25 | func (c *Context) Putln(format string, v ...interface{}) { 26 | c.Put(format+"\n", v...) 27 | } 28 | 29 | // Put is a short alias to write to 'out'. 30 | func (c *Context) Put(format string, v ...interface{}) { 31 | _, err := c.out.WriteString(fmt.Sprintf(format, v...)) 32 | if err != nil { 33 | log.Fatalf("There was an error writing to context buffer: %s", err) 34 | } 35 | } 36 | 37 | // Morph is the big daddy of them all. It takes in an XML byte slice, 38 | // parse it, transforms the XML types into more usable types, 39 | // and writes Go code to the 'out' buffer. 40 | func (c *Context) Morph(xmlBytes []byte) { 41 | parsedXml := &XML{} 42 | err := xml.Unmarshal(xmlBytes, parsedXml) 43 | if err != nil { 44 | log.Fatal(err) 45 | } 46 | 47 | // Parse all imports 48 | parsedXml.Imports.Eval() 49 | 50 | // Translate XML types to nice types 51 | c.protocol = parsedXml.Translate(nil) 52 | 53 | c.protocol.AddAlignGaps() 54 | 55 | // Start with Go header. 56 | c.Putln("// Package %s is the X client API for the %s extension.", 57 | c.protocol.PkgName(), c.protocol.ExtXName) 58 | c.Putln("package %s", c.protocol.PkgName()) 59 | c.Putln("") 60 | c.Putln("// This file is automatically generated from %s.xml. "+ 61 | "Edit at your peril!", c.protocol.Name) 62 | c.Putln("") 63 | 64 | // Write imports. We always need to import at least xgb. 65 | // We also need to import xproto if it's an extension. 66 | c.Putln("import (") 67 | c.Putln("\"github.com/BurntSushi/xgb\"") 68 | c.Putln("") 69 | if c.protocol.isExt() { 70 | c.Putln("\"github.com/BurntSushi/xgb/xproto\"") 71 | } 72 | 73 | sort.Sort(Protocols(c.protocol.Imports)) 74 | for _, imp := range c.protocol.Imports { 75 | // We always import xproto, so skip it if it's explicitly imported 76 | if imp.Name == "xproto" { 77 | continue 78 | } 79 | c.Putln("\"github.com/BurntSushi/xgb/%s\"", imp.Name) 80 | } 81 | c.Putln(")") 82 | c.Putln("") 83 | 84 | // If this is an extension, create a function to initialize the extension 85 | // before it can be used. 86 | if c.protocol.isExt() { 87 | xname := c.protocol.ExtXName 88 | 89 | c.Putln("// Init must be called before using the %s extension.", 90 | xname) 91 | c.Putln("func Init(c *xgb.Conn) error {") 92 | c.Putln("reply, err := xproto.QueryExtension(c, %d, \"%s\").Reply()", 93 | len(xname), xname) 94 | c.Putln("switch {") 95 | c.Putln("case err != nil:") 96 | c.Putln("return err") 97 | c.Putln("case !reply.Present:") 98 | c.Putln("return xgb.Errorf(\"No extension named %s could be found on "+ 99 | "on the server.\")", xname) 100 | c.Putln("}") 101 | c.Putln("") 102 | c.Putln("c.ExtLock.Lock()") 103 | c.Putln("c.Extensions[\"%s\"] = reply.MajorOpcode", xname) 104 | c.Putln("c.ExtLock.Unlock()") 105 | c.Putln("for evNum, fun := range xgb.NewExtEventFuncs[\"%s\"] {", 106 | xname) 107 | c.Putln("xgb.NewEventFuncs[int(reply.FirstEvent) + evNum] = fun") 108 | c.Putln("}") 109 | c.Putln("for errNum, fun := range xgb.NewExtErrorFuncs[\"%s\"] {", 110 | xname) 111 | c.Putln("xgb.NewErrorFuncs[int(reply.FirstError) + errNum] = fun") 112 | c.Putln("}") 113 | c.Putln("return nil") 114 | c.Putln("}") 115 | c.Putln("") 116 | 117 | // Make sure newExtEventFuncs["EXT_NAME"] map is initialized. 118 | // Same deal for newExtErrorFuncs["EXT_NAME"] 119 | c.Putln("func init() {") 120 | c.Putln("xgb.NewExtEventFuncs[\"%s\"] = make(map[int]xgb.NewEventFun)", 121 | xname) 122 | c.Putln("xgb.NewExtErrorFuncs[\"%s\"] = make(map[int]xgb.NewErrorFun)", 123 | xname) 124 | c.Putln("}") 125 | c.Putln("") 126 | } else { 127 | // In the xproto package, we must provide a Setup function that uses 128 | // SetupBytes in xgb.Conn to return a SetupInfo structure. 129 | c.Putln("// Setup parses the setup bytes retrieved when") 130 | c.Putln("// connecting into a SetupInfo struct.") 131 | c.Putln("func Setup(c *xgb.Conn) *SetupInfo {") 132 | c.Putln("setup := new(SetupInfo)") 133 | c.Putln("SetupInfoRead(c.SetupBytes, setup)") 134 | c.Putln("return setup") 135 | c.Putln("}") 136 | c.Putln("") 137 | c.Putln("// DefaultScreen gets the default screen info from SetupInfo.") 138 | c.Putln("func (s *SetupInfo) DefaultScreen(c *xgb.Conn) *ScreenInfo {") 139 | c.Putln("return &s.Roots[c.DefaultScreen]") 140 | c.Putln("}") 141 | c.Putln("") 142 | } 143 | 144 | // Now write Go source code 145 | sort.Sort(Types(c.protocol.Types)) 146 | sort.Sort(Requests(c.protocol.Requests)) 147 | for _, typ := range c.protocol.Types { 148 | typ.Define(c) 149 | } 150 | for _, req := range c.protocol.Requests { 151 | req.Define(c) 152 | } 153 | } 154 | -------------------------------------------------------------------------------- /xgbgen/doc.go: -------------------------------------------------------------------------------- 1 | /* 2 | xgbgen constructs Go source files from xproto XML description files. xgbgen 3 | accomplishes the same task as the Python code generator for XCB and xpyb. 4 | 5 | Usage: 6 | xgbgen [flags] some-protocol.xml 7 | 8 | The flags are: 9 | --proto-path path 10 | The path to a directory containing xproto XML description files. 11 | This is only necessary when 'some-protocol.xml' imports other 12 | protocol files. 13 | --gofmt=true 14 | When false, the outputted Go code will not be gofmt'd. And it won't 15 | be very pretty at all. This is typically useful if there are syntax 16 | errors that need to be debugged in code generation. gofmt will hiccup; 17 | this will allow you to see the raw code. 18 | 19 | How it works 20 | 21 | xgbgen works by parsing the input XML file using Go's encoding/xml package. 22 | The majority of this work is done in xml.go and xml_fields.go, where the 23 | appropriate types are declared. 24 | 25 | Due to the nature of the XML in the protocol description files, the types 26 | required to parse the XML are not well suited to reasoning about code 27 | generation. Because of this, all data parsed in the XML types is translated 28 | into more reasonable types. This translation is done in translation.go, 29 | and is mainly grunt work. (The only interesting tidbits are the translation 30 | of XML names to Go source names, and connecting fields with their 31 | appropriate types.) 32 | 33 | The organization of these types is greatly 34 | inspired by the description of the XML found here: 35 | http://cgit.freedesktop.org/xcb/proto/tree/doc/xml-xcb.txt 36 | 37 | These types come with a lot of supporting methods to make their use in 38 | code generation easier. They can be found in expression.go, field.go, 39 | protocol.go, request_reply.go and type.go. Of particular interest are 40 | expression evaluation and size calculation (in bytes). 41 | 42 | These types also come with supporting methods that convert their 43 | representation into Go source code. I've quartered such methods in 44 | go.go, go_error.go, go_event.go, go_list.go, go_request_reply.go, 45 | go_single_field.go, go_struct.go and go_union.go. The idea is to keep 46 | as much of the Go specific code generation in one area as possible. Namely, 47 | while not *all* Go related code is found in the 'go*.go' files, *most* 48 | of it is. (If there's any interest in using xgbgen for other languages, 49 | I'd be happy to try and make xgbgen a little more friendly in this regard. 50 | I did, however, design xgbgen with this in mind, so it shouldn't involve 51 | anything as serious as a re-design.) 52 | 53 | Why 54 | 55 | I wrote xgbgen because I found the existing code generator that was written in 56 | Python to be unwieldy. In particular, static and strong typing greatly helped 57 | me reason better about the code generation task. 58 | 59 | What does not work 60 | 61 | The core X protocol should be completely working. As far as I know, most 62 | extensions should work too, although I've only tested (and not much) the 63 | Xinerama and RandR extensions. 64 | 65 | XKB does not work. I don't have any real plans of working on this unless there 66 | is demand and I have some test cases to work with. (i.e., even if I could get 67 | something generated for XKB, I don't have the inclination to understand it 68 | enough to verify that it works.) XKB poses several extremely difficult 69 | problems that XCB also has trouble with. More info on that can be found at 70 | http://cgit.freedesktop.org/xcb/libxcb/tree/doc/xkb_issues and 71 | http://cgit.freedesktop.org/xcb/libxcb/tree/doc/xkb_internals. 72 | 73 | */ 74 | package main 75 | -------------------------------------------------------------------------------- /xgbgen/expression.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | ) 7 | 8 | // Expression represents all the different forms of expressions possible in 9 | // side an XML protocol description file. It's also received a few custom 10 | // addendums to make applying special functions (like padding) easier. 11 | type Expression interface { 12 | // Concrete determines whether this particular expression can be computed 13 | // to some constant value inside xgbgen. (The alternative is that the 14 | // expression can only be computed with values at run time of the 15 | // generated code.) 16 | Concrete() bool 17 | 18 | // Eval evaluates a concrete expression. It is an error to call Eval 19 | // on any expression that is not concrete (or contains any sub-expression 20 | // that is not concrete). 21 | Eval() int 22 | 23 | // Reduce attempts to evaluate any concrete sub-expressions. 24 | // i.e., (1 + 2 * (5 + 1 + someSizeOfStruct) reduces to 25 | // (3 * (6 + someSizeOfStruct)). 26 | // 'prefix' is used preprended to any field reference name. 27 | Reduce(prefix string) string 28 | 29 | // String is an alias for Reduce("") 30 | String() string 31 | 32 | // Initialize makes sure all names in this expression and any subexpressions 33 | // have been translated to Go source names. 34 | Initialize(p *Protocol) 35 | } 36 | 37 | // Function is a custom expression not found in the XML. It's simply used 38 | // to apply a function named in 'Name' to the Expr expression. 39 | type Function struct { 40 | Name string 41 | Expr Expression 42 | } 43 | 44 | func (e *Function) Concrete() bool { 45 | return false 46 | } 47 | 48 | func (e *Function) Eval() int { 49 | log.Fatalf("Cannot evaluate a 'Function'. It is not concrete.") 50 | panic("unreachable") 51 | } 52 | 53 | func (e *Function) Reduce(prefix string) string { 54 | return fmt.Sprintf("%s(%s)", e.Name, e.Expr.Reduce(prefix)) 55 | } 56 | 57 | func (e *Function) String() string { 58 | return e.Reduce("") 59 | } 60 | 61 | func (e *Function) Initialize(p *Protocol) { 62 | e.Expr.Initialize(p) 63 | } 64 | 65 | // BinaryOp is an expression that performs some operation (defined in the XML 66 | // file) with Expr1 and Expr2 as operands. 67 | type BinaryOp struct { 68 | Op string 69 | Expr1 Expression 70 | Expr2 Expression 71 | } 72 | 73 | // newBinaryOp constructs a new binary expression when both expr1 and expr2 74 | // are not nil. If one or both are nil, then the non-nil expression is 75 | // returned unchanged or nil is returned. 76 | func newBinaryOp(op string, expr1, expr2 Expression) Expression { 77 | switch { 78 | case expr1 != nil && expr2 != nil: 79 | return &BinaryOp{ 80 | Op: op, 81 | Expr1: expr1, 82 | Expr2: expr2, 83 | } 84 | case expr1 != nil && expr2 == nil: 85 | return expr1 86 | case expr1 == nil && expr2 != nil: 87 | return expr2 88 | case expr1 == nil && expr2 == nil: 89 | return nil 90 | } 91 | panic("unreachable") 92 | } 93 | 94 | func (e *BinaryOp) Concrete() bool { 95 | return e.Expr1.Concrete() && e.Expr2.Concrete() 96 | } 97 | 98 | func (e *BinaryOp) Eval() int { 99 | switch e.Op { 100 | case "+": 101 | return e.Expr1.Eval() + e.Expr2.Eval() 102 | case "-": 103 | return e.Expr1.Eval() - e.Expr2.Eval() 104 | case "*": 105 | return e.Expr1.Eval() * e.Expr2.Eval() 106 | case "/": 107 | return e.Expr1.Eval() / e.Expr2.Eval() 108 | case "&": 109 | return e.Expr1.Eval() & e.Expr2.Eval() 110 | case "<<": 111 | return int(uint(e.Expr1.Eval()) << uint(e.Expr2.Eval())) 112 | } 113 | 114 | log.Fatalf("Invalid binary operator '%s' for expression.", e.Op) 115 | panic("unreachable") 116 | } 117 | 118 | func (e *BinaryOp) Reduce(prefix string) string { 119 | if e.Concrete() { 120 | return fmt.Sprintf("%d", e.Eval()) 121 | } 122 | 123 | // An incredibly dirty hack to make sure any time we perform an operation 124 | // on a field, we're dealing with ints... 125 | expr1, expr2 := e.Expr1, e.Expr2 126 | switch expr1.(type) { 127 | case *FieldRef: 128 | expr1 = &Function{ 129 | Name: "int", 130 | Expr: expr1, 131 | } 132 | } 133 | switch expr2.(type) { 134 | case *FieldRef: 135 | expr2 = &Function{ 136 | Name: "int", 137 | Expr: expr2, 138 | } 139 | } 140 | return fmt.Sprintf("(%s %s %s)", 141 | expr1.Reduce(prefix), e.Op, expr2.Reduce(prefix)) 142 | } 143 | 144 | func (e *BinaryOp) String() string { 145 | return e.Reduce("") 146 | } 147 | 148 | func (e *BinaryOp) Initialize(p *Protocol) { 149 | e.Expr1.Initialize(p) 150 | e.Expr2.Initialize(p) 151 | } 152 | 153 | // UnaryOp is the same as BinaryOp, except it's a unary operator with only 154 | // one sub-expression. 155 | type UnaryOp struct { 156 | Op string 157 | Expr Expression 158 | } 159 | 160 | func (e *UnaryOp) Concrete() bool { 161 | return e.Expr.Concrete() 162 | } 163 | 164 | func (e *UnaryOp) Eval() int { 165 | switch e.Op { 166 | case "~": 167 | return ^e.Expr.Eval() 168 | } 169 | 170 | log.Fatalf("Invalid unary operator '%s' for expression.", e.Op) 171 | panic("unreachable") 172 | } 173 | 174 | func (e *UnaryOp) Reduce(prefix string) string { 175 | if e.Concrete() { 176 | return fmt.Sprintf("%d", e.Eval()) 177 | } 178 | return fmt.Sprintf("(%s (%s))", e.Op, e.Expr.Reduce(prefix)) 179 | } 180 | 181 | func (e *UnaryOp) String() string { 182 | return e.Reduce("") 183 | } 184 | 185 | func (e *UnaryOp) Initialize(p *Protocol) { 186 | e.Expr.Initialize(p) 187 | } 188 | 189 | // Padding represents the application of the 'pad' function to some 190 | // sub-expression. 191 | type Padding struct { 192 | Expr Expression 193 | } 194 | 195 | func (e *Padding) Concrete() bool { 196 | return e.Expr.Concrete() 197 | } 198 | 199 | func (e *Padding) Eval() int { 200 | return pad(e.Expr.Eval()) 201 | } 202 | 203 | func (e *Padding) Reduce(prefix string) string { 204 | if e.Concrete() { 205 | return fmt.Sprintf("%d", e.Eval()) 206 | } 207 | return fmt.Sprintf("xgb.Pad(%s)", e.Expr.Reduce(prefix)) 208 | } 209 | 210 | func (e *Padding) String() string { 211 | return e.Reduce("") 212 | } 213 | 214 | func (e *Padding) Initialize(p *Protocol) { 215 | e.Expr.Initialize(p) 216 | } 217 | 218 | // PopCount represents the application of the 'PopCount' function to 219 | // some sub-expression. 220 | type PopCount struct { 221 | Expr Expression 222 | } 223 | 224 | func (e *PopCount) Concrete() bool { 225 | return e.Expr.Concrete() 226 | } 227 | 228 | func (e *PopCount) Eval() int { 229 | return int(popCount(uint(e.Expr.Eval()))) 230 | } 231 | 232 | func (e *PopCount) Reduce(prefix string) string { 233 | if e.Concrete() { 234 | return fmt.Sprintf("%d", e.Eval()) 235 | } 236 | return fmt.Sprintf("xgb.PopCount(%s)", e.Expr.Reduce(prefix)) 237 | } 238 | 239 | func (e *PopCount) String() string { 240 | return e.Reduce("") 241 | } 242 | 243 | func (e *PopCount) Initialize(p *Protocol) { 244 | e.Expr.Initialize(p) 245 | } 246 | 247 | // Value represents some constant integer. 248 | type Value struct { 249 | v int 250 | } 251 | 252 | func (e *Value) Concrete() bool { 253 | return true 254 | } 255 | 256 | func (e *Value) Eval() int { 257 | return e.v 258 | } 259 | 260 | func (e *Value) Reduce(prefix string) string { 261 | return fmt.Sprintf("%d", e.v) 262 | } 263 | 264 | func (e *Value) String() string { 265 | return e.Reduce("") 266 | } 267 | 268 | func (e *Value) Initialize(p *Protocol) {} 269 | 270 | // Bit represents some bit whose value is computed by '1 << bit'. 271 | type Bit struct { 272 | b int 273 | } 274 | 275 | func (e *Bit) Concrete() bool { 276 | return true 277 | } 278 | 279 | func (e *Bit) Eval() int { 280 | return int(1 << uint(e.b)) 281 | } 282 | 283 | func (e *Bit) Reduce(prefix string) string { 284 | return fmt.Sprintf("%d", e.Eval()) 285 | } 286 | 287 | func (e *Bit) String() string { 288 | return e.Reduce("") 289 | } 290 | 291 | func (e *Bit) Initialize(p *Protocol) {} 292 | 293 | // FieldRef represents a reference to some variable in the generated code 294 | // with name Name. 295 | type FieldRef struct { 296 | Name string 297 | } 298 | 299 | func (e *FieldRef) Concrete() bool { 300 | return false 301 | } 302 | 303 | func (e *FieldRef) Eval() int { 304 | log.Fatalf("Cannot evaluate a 'FieldRef'. It is not concrete.") 305 | panic("unreachable") 306 | } 307 | 308 | func (e *FieldRef) Reduce(prefix string) string { 309 | val := e.Name 310 | if len(prefix) > 0 { 311 | val = fmt.Sprintf("%s%s", prefix, val) 312 | } 313 | return val 314 | } 315 | 316 | func (e *FieldRef) String() string { 317 | return e.Reduce("") 318 | } 319 | 320 | func (e *FieldRef) Initialize(p *Protocol) { 321 | e.Name = SrcName(p, e.Name) 322 | } 323 | 324 | // EnumRef represents a reference to some enumeration field. 325 | // EnumKind is the "group" an EnumItem is the name of the specific enumeration 326 | // value inside that group. 327 | type EnumRef struct { 328 | EnumKind Type 329 | EnumItem string 330 | } 331 | 332 | func (e *EnumRef) Concrete() bool { 333 | return false 334 | } 335 | 336 | func (e *EnumRef) Eval() int { 337 | log.Fatalf("Cannot evaluate an 'EnumRef'. It is not concrete.") 338 | panic("unreachable") 339 | } 340 | 341 | func (e *EnumRef) Reduce(prefix string) string { 342 | return fmt.Sprintf("%s%s", e.EnumKind, e.EnumItem) 343 | } 344 | 345 | func (e *EnumRef) String() string { 346 | return e.Reduce("") 347 | } 348 | 349 | func (e *EnumRef) Initialize(p *Protocol) { 350 | e.EnumKind = e.EnumKind.(*Translation).RealType(p) 351 | e.EnumItem = SrcName(p, e.EnumItem) 352 | } 353 | 354 | // SumOf represents a summation of the variable in the generated code named by 355 | // Name. It is not currently used. (It's XKB voodoo.) 356 | type SumOf struct { 357 | Name string 358 | } 359 | 360 | func (e *SumOf) Concrete() bool { 361 | return false 362 | } 363 | 364 | func (e *SumOf) Eval() int { 365 | log.Fatalf("Cannot evaluate a 'SumOf'. It is not concrete.") 366 | panic("unreachable") 367 | } 368 | 369 | func (e *SumOf) Reduce(prefix string) string { 370 | if len(prefix) > 0 { 371 | return fmt.Sprintf("sum(%s%s)", prefix, e.Name) 372 | } 373 | return fmt.Sprintf("sum(%s)", e.Name) 374 | } 375 | 376 | func (e *SumOf) String() string { 377 | return e.Reduce("") 378 | } 379 | 380 | func (e *SumOf) Initialize(p *Protocol) { 381 | e.Name = SrcName(p, e.Name) 382 | } 383 | -------------------------------------------------------------------------------- /xgbgen/field.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | "strings" 7 | ) 8 | 9 | // Field corresponds to any field described in an XML protocol description 10 | // file. This includes struct fields, union fields, request fields, 11 | // reply fields and so on. 12 | // To make code generation easier, fields that have types are also stored. 13 | // Note that not all fields support all methods defined in this interface. 14 | // For instance, a padding field does not have a source name. 15 | type Field interface { 16 | // Initialize sets up the source name of this field. 17 | Initialize(p *Protocol) 18 | 19 | // SrcName is the Go source name of this field. 20 | SrcName() string 21 | 22 | // XmlName is the name of this field from the XML file. 23 | XmlName() string 24 | 25 | // SrcType is the Go source type name of this field. 26 | SrcType() string 27 | 28 | // Size returns an expression that computes the size (in bytes) 29 | // of this field. 30 | Size() Size 31 | 32 | // Define writes the Go code to declare this field (in a struct definition). 33 | Define(c *Context) 34 | 35 | // Read writes the Go code to convert a byte slice to a Go value 36 | // of this field. 37 | // 'prefix' is the prefix of the name of the Go value. 38 | Read(c *Context, prefix string) 39 | 40 | // Write writes the Go code to convert a Go value to a byte slice of 41 | // this field. 42 | // 'prefix' is the prefix of the name of the Go value. 43 | Write(c *Context, prefix string) 44 | } 45 | 46 | func (pad *PadField) Initialize(p *Protocol) {} 47 | 48 | // PadField represents any type of padding. It is omitted from 49 | // definitions, but is used in Read/Write to increment the buffer index. 50 | // It is also used in size calculation. 51 | type PadField struct { 52 | Bytes uint 53 | Align uint16 54 | } 55 | 56 | func (p *PadField) SrcName() string { 57 | panic("illegal to take source name of a pad field") 58 | } 59 | 60 | func (p *PadField) XmlName() string { 61 | panic("illegal to take XML name of a pad field") 62 | } 63 | 64 | func (f *PadField) SrcType() string { 65 | panic("it is illegal to call SrcType on a PadField field") 66 | } 67 | 68 | func (p *PadField) Size() Size { 69 | if p.Align > 0 { 70 | return newFixedSize(uint(p.Align), false) 71 | } else { 72 | return newFixedSize(p.Bytes, true) 73 | } 74 | } 75 | 76 | // SingleField represents most of the fields in an XML protocol description. 77 | // It corresponds to any single value. 78 | type SingleField struct { 79 | srcName string 80 | xmlName string 81 | Type Type 82 | } 83 | 84 | func (f *SingleField) Initialize(p *Protocol) { 85 | f.srcName = SrcName(p, f.XmlName()) 86 | f.Type = f.Type.(*Translation).RealType(p) 87 | } 88 | 89 | func (f *SingleField) SrcName() string { 90 | if f.srcName == "Bytes" { 91 | return "Bytes_" 92 | } 93 | return f.srcName 94 | } 95 | 96 | func (f *SingleField) XmlName() string { 97 | return f.xmlName 98 | } 99 | 100 | func (f *SingleField) SrcType() string { 101 | return f.Type.SrcName() 102 | } 103 | 104 | func (f *SingleField) Size() Size { 105 | return f.Type.Size() 106 | } 107 | 108 | // ListField represents a list of values. 109 | type ListField struct { 110 | srcName string 111 | xmlName string 112 | Type Type 113 | LengthExpr Expression 114 | } 115 | 116 | func (f *ListField) SrcName() string { 117 | return f.srcName 118 | } 119 | 120 | func (f *ListField) XmlName() string { 121 | return f.xmlName 122 | } 123 | 124 | func (f *ListField) SrcType() string { 125 | if strings.ToLower(f.Type.XmlName()) == "char" { 126 | return fmt.Sprintf("string") 127 | } 128 | return fmt.Sprintf("[]%s", f.Type.SrcName()) 129 | } 130 | 131 | // Length computes the *number* of values in a list. 132 | // If this ListField does not have any length expression, we throw our hands 133 | // up and simply compute the 'len' of the field name of this list. 134 | func (f *ListField) Length() Size { 135 | if f.LengthExpr == nil { 136 | return newExpressionSize(&Function{ 137 | Name: "len", 138 | Expr: &FieldRef{ 139 | Name: f.SrcName(), 140 | }, 141 | }, true) 142 | } 143 | return newExpressionSize(f.LengthExpr, true) 144 | } 145 | 146 | // Size computes the *size* of a list (in bytes). 147 | // It it typically a simple matter of multiplying the length of the list by 148 | // the size of the type of the list. 149 | // But if it's a list of struct where the struct has a list field, we use a 150 | // special function written in go_struct.go to compute the size (since the 151 | // size in this case can only be computed recursively). 152 | func (f *ListField) Size() Size { 153 | elsz := f.Type.Size() 154 | simpleLen := &Padding{ 155 | Expr: newBinaryOp("*", f.Length().Expression, elsz.Expression), 156 | } 157 | 158 | switch field := f.Type.(type) { 159 | case *Struct: 160 | if field.HasList() { 161 | sizeFun := &Function{ 162 | Name: fmt.Sprintf("%sListSize", f.Type.SrcName()), 163 | Expr: &FieldRef{Name: f.SrcName()}, 164 | } 165 | return newExpressionSize(sizeFun, elsz.exact) 166 | } else { 167 | return newExpressionSize(simpleLen, elsz.exact) 168 | } 169 | case *Union: 170 | return newExpressionSize(simpleLen, elsz.exact) 171 | case *Base: 172 | return newExpressionSize(simpleLen, elsz.exact) 173 | case *Resource: 174 | return newExpressionSize(simpleLen, elsz.exact) 175 | case *TypeDef: 176 | return newExpressionSize(simpleLen, elsz.exact) 177 | default: 178 | log.Panicf("Cannot compute list size with type '%T'.", f.Type) 179 | } 180 | panic("unreachable") 181 | } 182 | 183 | func (f *ListField) Initialize(p *Protocol) { 184 | f.srcName = SrcName(p, f.XmlName()) 185 | f.Type = f.Type.(*Translation).RealType(p) 186 | if f.LengthExpr != nil { 187 | f.LengthExpr.Initialize(p) 188 | } 189 | } 190 | 191 | // LocalField is exactly the same as a regular SingleField, except it isn't 192 | // sent over the wire. (i.e., it's probably used to compute an ExprField). 193 | type LocalField struct { 194 | *SingleField 195 | } 196 | 197 | // ExprField is a field that is not parameterized, but is computed from values 198 | // of other fields. 199 | type ExprField struct { 200 | srcName string 201 | xmlName string 202 | Type Type 203 | Expr Expression 204 | } 205 | 206 | func (f *ExprField) SrcName() string { 207 | return f.srcName 208 | } 209 | 210 | func (f *ExprField) XmlName() string { 211 | return f.xmlName 212 | } 213 | 214 | func (f *ExprField) SrcType() string { 215 | return f.Type.SrcName() 216 | } 217 | 218 | func (f *ExprField) Size() Size { 219 | return f.Type.Size() 220 | } 221 | 222 | func (f *ExprField) Initialize(p *Protocol) { 223 | f.srcName = SrcName(p, f.XmlName()) 224 | f.Type = f.Type.(*Translation).RealType(p) 225 | f.Expr.Initialize(p) 226 | } 227 | 228 | // ValueField represents two fields in one: a mask and a list of 4-byte 229 | // integers. The mask specifies which kinds of values are in the list. 230 | // (i.e., See ConfigureWindow, CreateWindow, ChangeWindowAttributes, etc.) 231 | type ValueField struct { 232 | Parent interface{} 233 | MaskType Type 234 | MaskName string 235 | ListName string 236 | } 237 | 238 | func (f *ValueField) SrcName() string { 239 | panic("it is illegal to call SrcName on a ValueField field") 240 | } 241 | 242 | func (f *ValueField) XmlName() string { 243 | panic("it is illegal to call XmlName on a ValueField field") 244 | } 245 | 246 | func (f *ValueField) SrcType() string { 247 | return f.MaskType.SrcName() 248 | } 249 | 250 | // Size computes the size in bytes of the combination of the mask and list 251 | // in this value field. 252 | // The expression to compute this looks complicated, but it's really just 253 | // the number of bits set in the mask multiplied 4 (and padded of course). 254 | func (f *ValueField) Size() Size { 255 | maskSize := f.MaskType.Size() 256 | listSize := newExpressionSize(&Function{ 257 | Name: "xgb.Pad", 258 | Expr: &BinaryOp{ 259 | Op: "*", 260 | Expr1: &Value{v: 4}, 261 | Expr2: &PopCount{ 262 | Expr: &Function{ 263 | Name: "int", 264 | Expr: &FieldRef{ 265 | Name: f.MaskName, 266 | }, 267 | }, 268 | }, 269 | }, 270 | }, true) 271 | return maskSize.Add(listSize) 272 | } 273 | 274 | func (f *ValueField) ListLength() Size { 275 | return newExpressionSize(&PopCount{ 276 | Expr: &Function{ 277 | Name: "int", 278 | Expr: &FieldRef{ 279 | Name: f.MaskName, 280 | }, 281 | }, 282 | }, true) 283 | } 284 | 285 | func (f *ValueField) Initialize(p *Protocol) { 286 | f.MaskType = f.MaskType.(*Translation).RealType(p) 287 | f.MaskName = SrcName(p, f.MaskName) 288 | f.ListName = SrcName(p, f.ListName) 289 | } 290 | 291 | // SwitchField represents a 'switch' element in the XML protocol description 292 | // file. It is not currently used. (i.e., it is XKB voodoo.) 293 | type SwitchField struct { 294 | Name string 295 | Expr Expression 296 | Bitcases []*Bitcase 297 | } 298 | 299 | func (f *SwitchField) SrcName() string { 300 | panic("it is illegal to call SrcName on a SwitchField field") 301 | } 302 | 303 | func (f *SwitchField) XmlName() string { 304 | panic("it is illegal to call XmlName on a SwitchField field") 305 | } 306 | 307 | func (f *SwitchField) SrcType() string { 308 | panic("it is illegal to call SrcType on a SwitchField field") 309 | } 310 | 311 | // XXX: This is a bit tricky. The size has to be represented as a non-concrete 312 | // expression that finds *which* bitcase fields are included, and sums the 313 | // sizes of those fields. 314 | func (f *SwitchField) Size() Size { 315 | return newFixedSize(0, true) 316 | } 317 | 318 | func (f *SwitchField) Initialize(p *Protocol) { 319 | f.Name = SrcName(p, f.Name) 320 | f.Expr.Initialize(p) 321 | for _, bitcase := range f.Bitcases { 322 | bitcase.Expr.Initialize(p) 323 | for _, field := range bitcase.Fields { 324 | field.Initialize(p) 325 | } 326 | } 327 | } 328 | 329 | // Bitcase represents a single bitcase inside a switch expression. 330 | // It is not currently used. (i.e., it's XKB voodoo.) 331 | type Bitcase struct { 332 | Fields []Field 333 | Expr Expression 334 | } 335 | -------------------------------------------------------------------------------- /xgbgen/go.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | ) 6 | 7 | // BaseTypeMap is a map from X base types to Go types. 8 | // X base types should correspond to the smallest set of X types 9 | // that can be used to rewrite ALL X types in terms of Go types. 10 | // That is, if you remove any of the following types, at least one 11 | // XML protocol description will produce an invalid Go program. 12 | // The types on the left *never* show themselves in the source. 13 | var BaseTypeMap = map[string]string{ 14 | "CARD8": "byte", 15 | "CARD16": "uint16", 16 | "CARD32": "uint32", 17 | "INT8": "int8", 18 | "INT16": "int16", 19 | "INT32": "int32", 20 | "BYTE": "byte", 21 | "BOOL": "bool", 22 | "float": "float64", 23 | "double": "float64", 24 | "char": "byte", 25 | "void": "byte", 26 | } 27 | 28 | // BaseTypeSizes should have precisely the same keys as in BaseTypeMap, 29 | // and the values should correspond to the size of the type in bytes. 30 | var BaseTypeSizes = map[string]uint{ 31 | "CARD8": 1, 32 | "CARD16": 2, 33 | "CARD32": 4, 34 | "INT8": 1, 35 | "INT16": 2, 36 | "INT32": 4, 37 | "BYTE": 1, 38 | "BOOL": 1, 39 | "float": 4, 40 | "double": 8, 41 | "char": 1, 42 | "void": 1, 43 | 44 | // Id is a special type used to determine the size of all Xid types. 45 | // "Id" is not actually written in the source. 46 | "Id": 4, 47 | } 48 | 49 | // TypeMap is a map from types in the XML to type names that is used 50 | // in the functions that follow. Basically, every occurrence of the key 51 | // type is replaced with the value type. 52 | var TypeMap = map[string]string{ 53 | "VISUALTYPE": "VisualInfo", 54 | "DEPTH": "DepthInfo", 55 | "SCREEN": "ScreenInfo", 56 | "Setup": "SetupInfo", 57 | } 58 | 59 | // NameMap is the same as TypeMap, but for names. 60 | var NameMap = map[string]string{} 61 | 62 | // Reading, writing and defining... 63 | 64 | // Base types 65 | func (b *Base) Define(c *Context) { 66 | c.Putln("// Skipping definition for base type '%s'", 67 | SrcName(c.protocol, b.XmlName())) 68 | c.Putln("") 69 | } 70 | 71 | // Enum types 72 | func (enum *Enum) Define(c *Context) { 73 | c.Putln("const (") 74 | for _, item := range enum.Items { 75 | c.Putln("%s%s = %d", enum.SrcName(), item.srcName, item.Expr.Eval()) 76 | } 77 | c.Putln(")") 78 | c.Putln("") 79 | } 80 | 81 | // Resource types 82 | func (res *Resource) Define(c *Context) { 83 | c.Putln("type %s uint32", res.SrcName()) 84 | c.Putln("") 85 | c.Putln("func New%sId(c *xgb.Conn) (%s, error) {", 86 | res.SrcName(), res.SrcName()) 87 | c.Putln("id, err := c.NewId()") 88 | c.Putln("if err != nil {") 89 | c.Putln("return 0, err") 90 | c.Putln("}") 91 | c.Putln("return %s(id), nil", res.SrcName()) 92 | c.Putln("}") 93 | c.Putln("") 94 | } 95 | 96 | // TypeDef types 97 | func (td *TypeDef) Define(c *Context) { 98 | c.Putln("type %s %s", td.srcName, td.Old.SrcName()) 99 | c.Putln("") 100 | } 101 | 102 | // Field definitions, reads and writes. 103 | 104 | // Pad fields 105 | func (f *PadField) Define(c *Context) { 106 | if f.Align > 0 { 107 | c.Putln("// alignment gap to multiple of %d", f.Align) 108 | } else { 109 | c.Putln("// padding: %d bytes", f.Bytes) 110 | } 111 | } 112 | 113 | func (f *PadField) Read(c *Context, prefix string) { 114 | if f.Align > 0 { 115 | c.Putln("b = (b + %d) & ^%d // alignment gap", f.Align-1, f.Align-1) 116 | } else { 117 | c.Putln("b += %s // padding", f.Size()) 118 | } 119 | } 120 | 121 | func (f *PadField) Write(c *Context, prefix string) { 122 | if f.Align > 0 { 123 | c.Putln("b = (b + %d) & ^%d // alignment gap", f.Align-1, f.Align-1) 124 | } else { 125 | c.Putln("b += %s // padding", f.Size()) 126 | } 127 | } 128 | 129 | // Local fields 130 | func (f *LocalField) Define(c *Context) { 131 | c.Putln("// local field: %s %s", f.SrcName(), f.Type.SrcName()) 132 | panic("unreachable") 133 | } 134 | 135 | func (f *LocalField) Read(c *Context, prefix string) { 136 | c.Putln("// reading local field: %s (%s) :: %s", 137 | f.SrcName(), f.Size(), f.Type.SrcName()) 138 | panic("unreachable") 139 | } 140 | 141 | func (f *LocalField) Write(c *Context, prefix string) { 142 | c.Putln("// skip writing local field: %s (%s) :: %s", 143 | f.SrcName(), f.Size(), f.Type.SrcName()) 144 | } 145 | 146 | // Expr fields 147 | func (f *ExprField) Define(c *Context) { 148 | c.Putln("// expression field: %s %s (%s)", 149 | f.SrcName(), f.Type.SrcName(), f.Expr) 150 | panic("unreachable") 151 | } 152 | 153 | func (f *ExprField) Read(c *Context, prefix string) { 154 | c.Putln("// reading expression field: %s (%s) (%s) :: %s", 155 | f.SrcName(), f.Size(), f.Expr, f.Type.SrcName()) 156 | panic("unreachable") 157 | } 158 | 159 | func (f *ExprField) Write(c *Context, prefix string) { 160 | // Special case for bools, grrr. 161 | if f.Type.SrcName() == "bool" { 162 | c.Putln("buf[b] = byte(%s)", f.Expr.Reduce(prefix)) 163 | c.Putln("b += 1") 164 | } else { 165 | WriteSimpleSingleField(c, f.Expr.Reduce(prefix), f.Type) 166 | } 167 | } 168 | 169 | // Value field 170 | func (f *ValueField) Define(c *Context) { 171 | c.Putln("%s %s", f.MaskName, f.SrcType()) 172 | c.Putln("%s []uint32", f.ListName) 173 | } 174 | 175 | func (f *ValueField) Read(c *Context, prefix string) { 176 | ReadSimpleSingleField(c, 177 | fmt.Sprintf("%s%s", prefix, f.MaskName), f.MaskType) 178 | c.Putln("") 179 | c.Putln("%s%s = make([]uint32, %s)", 180 | prefix, f.ListName, f.ListLength().Reduce(prefix)) 181 | c.Putln("for i := 0; i < %s; i++ {", f.ListLength().Reduce(prefix)) 182 | c.Putln("%s%s[i] = xgb.Get32(buf[b:])", prefix, f.ListName) 183 | c.Putln("b += 4") 184 | c.Putln("}") 185 | c.Putln("b = xgb.Pad(b)") 186 | } 187 | 188 | func (f *ValueField) Write(c *Context, prefix string) { 189 | // big time mofos 190 | if rq, ok := f.Parent.(*Request); !ok || rq.SrcName() != "ConfigureWindow" { 191 | WriteSimpleSingleField(c, 192 | fmt.Sprintf("%s%s", prefix, f.MaskName), f.MaskType) 193 | } 194 | c.Putln("for i := 0; i < %s; i++ {", f.ListLength().Reduce(prefix)) 195 | c.Putln("xgb.Put32(buf[b:], %s%s[i])", prefix, f.ListName) 196 | c.Putln("b += 4") 197 | c.Putln("}") 198 | c.Putln("b = xgb.Pad(b)") 199 | } 200 | 201 | // Switch field 202 | func (f *SwitchField) Define(c *Context) { 203 | c.Putln("// switch field: %s (%s)", f.Name, f.Expr) 204 | panic("todo") 205 | } 206 | 207 | func (f *SwitchField) Read(c *Context, prefix string) { 208 | c.Putln("// reading switch field: %s (%s)", f.Name, f.Expr) 209 | panic("todo") 210 | } 211 | 212 | func (f *SwitchField) Write(c *Context, prefix string) { 213 | c.Putln("// writing switch field: %s (%s)", f.Name, f.Expr) 214 | panic("todo") 215 | } 216 | -------------------------------------------------------------------------------- /xgbgen/go_error.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | ) 6 | 7 | // Error types 8 | func (e *Error) Define(c *Context) { 9 | c.Putln("// %s is the error number for a %s.", e.ErrConst(), e.ErrConst()) 10 | c.Putln("const %s = %d", e.ErrConst(), e.Number) 11 | c.Putln("") 12 | c.Putln("type %s struct {", e.ErrType()) 13 | c.Putln("Sequence uint16") 14 | c.Putln("NiceName string") 15 | for _, field := range e.Fields { 16 | field.Define(c) 17 | } 18 | c.Putln("}") 19 | c.Putln("") 20 | 21 | // Read defines a function that transforms a byte slice into this 22 | // error struct. 23 | e.Read(c) 24 | 25 | // Makes sure this error type implements the xgb.Error interface. 26 | e.ImplementsError(c) 27 | 28 | // Let's the XGB event loop read this error. 29 | c.Putln("func init() {") 30 | if c.protocol.isExt() { 31 | c.Putln("xgb.NewExtErrorFuncs[\"%s\"][%d] = %sNew", 32 | c.protocol.ExtXName, e.Number, e.ErrType()) 33 | } else { 34 | c.Putln("xgb.NewErrorFuncs[%d] = %sNew", e.Number, e.ErrType()) 35 | } 36 | c.Putln("}") 37 | c.Putln("") 38 | } 39 | 40 | func (e *Error) Read(c *Context) { 41 | c.Putln("// %sNew constructs a %s value that implements xgb.Error from "+ 42 | "a byte slice.", e.ErrType(), e.ErrType()) 43 | c.Putln("func %sNew(buf []byte) xgb.Error {", e.ErrType()) 44 | c.Putln("v := %s{}", e.ErrType()) 45 | c.Putln("v.NiceName = \"%s\"", e.SrcName()) 46 | c.Putln("") 47 | c.Putln("b := 1 // skip error determinant") 48 | c.Putln("b += 1 // don't read error number") 49 | c.Putln("") 50 | c.Putln("v.Sequence = xgb.Get16(buf[b:])") 51 | c.Putln("b += 2") 52 | c.Putln("") 53 | for _, field := range e.Fields { 54 | field.Read(c, "v.") 55 | c.Putln("") 56 | } 57 | c.Putln("return v") 58 | c.Putln("}") 59 | c.Putln("") 60 | } 61 | 62 | // ImplementsError writes functions to implement the XGB Error interface. 63 | func (e *Error) ImplementsError(c *Context) { 64 | c.Putln("// SequenceId returns the sequence id attached to the %s error.", 65 | e.ErrConst()) 66 | c.Putln("// This is mostly used internally.") 67 | c.Putln("func (err %s) SequenceId() uint16 {", e.ErrType()) 68 | c.Putln("return err.Sequence") 69 | c.Putln("}") 70 | c.Putln("") 71 | c.Putln("// BadId returns the 'BadValue' number if one exists for the "+ 72 | "%s error. If no bad value exists, 0 is returned.", e.ErrConst()) 73 | c.Putln("func (err %s) BadId() uint32 {", e.ErrType()) 74 | if !c.protocol.isExt() { 75 | c.Putln("return err.BadValue") 76 | } else { 77 | c.Putln("return 0") 78 | } 79 | c.Putln("}") 80 | c.Putln("// Error returns a rudimentary string representation of the %s "+ 81 | "error.", e.ErrConst()) 82 | c.Putln("") 83 | c.Putln("func (err %s) Error() string {", e.ErrType()) 84 | ErrorFieldString(c, e.Fields, e.ErrConst()) 85 | c.Putln("}") 86 | c.Putln("") 87 | } 88 | 89 | // ErrorCopy types 90 | func (e *ErrorCopy) Define(c *Context) { 91 | c.Putln("// %s is the error number for a %s.", e.ErrConst(), e.ErrConst()) 92 | c.Putln("const %s = %d", e.ErrConst(), e.Number) 93 | c.Putln("") 94 | c.Putln("type %s %s", e.ErrType(), e.Old.(*Error).ErrType()) 95 | c.Putln("") 96 | 97 | // Read defines a function that transforms a byte slice into this 98 | // error struct. 99 | e.Read(c) 100 | 101 | // Makes sure this error type implements the xgb.Error interface. 102 | e.ImplementsError(c) 103 | 104 | // Let's the XGB know how to read this error. 105 | c.Putln("func init() {") 106 | if c.protocol.isExt() { 107 | c.Putln("xgb.NewExtErrorFuncs[\"%s\"][%d] = %sNew", 108 | c.protocol.ExtXName, e.Number, e.ErrType()) 109 | } else { 110 | c.Putln("xgb.NewErrorFuncs[%d] = %sNew", e.Number, e.ErrType()) 111 | } 112 | c.Putln("}") 113 | c.Putln("") 114 | } 115 | 116 | func (e *ErrorCopy) Read(c *Context) { 117 | c.Putln("// %sNew constructs a %s value that implements xgb.Error from "+ 118 | "a byte slice.", e.ErrType(), e.ErrType()) 119 | c.Putln("func %sNew(buf []byte) xgb.Error {", e.ErrType()) 120 | c.Putln("v := %s(%sNew(buf).(%s))", 121 | e.ErrType(), e.Old.(*Error).ErrType(), e.Old.(*Error).ErrType()) 122 | c.Putln("v.NiceName = \"%s\"", e.SrcName()) 123 | c.Putln("return v") 124 | c.Putln("}") 125 | c.Putln("") 126 | } 127 | 128 | // ImplementsError writes functions to implement the XGB Error interface. 129 | func (e *ErrorCopy) ImplementsError(c *Context) { 130 | c.Putln("// SequenceId returns the sequence id attached to the %s error.", 131 | e.ErrConst()) 132 | c.Putln("// This is mostly used internally.") 133 | c.Putln("func (err %s) SequenceId() uint16 {", e.ErrType()) 134 | c.Putln("return err.Sequence") 135 | c.Putln("}") 136 | c.Putln("") 137 | c.Putln("// BadId returns the 'BadValue' number if one exists for the "+ 138 | "%s error. If no bad value exists, 0 is returned.", e.ErrConst()) 139 | c.Putln("func (err %s) BadId() uint32 {", e.ErrType()) 140 | if !c.protocol.isExt() { 141 | c.Putln("return err.BadValue") 142 | } else { 143 | c.Putln("return 0") 144 | } 145 | c.Putln("}") 146 | c.Putln("") 147 | c.Putln("// Error returns a rudimentary string representation of the %s "+ 148 | "error.", e.ErrConst()) 149 | c.Putln("func (err %s) Error() string {", e.ErrType()) 150 | ErrorFieldString(c, e.Old.(*Error).Fields, e.ErrConst()) 151 | c.Putln("}") 152 | c.Putln("") 153 | } 154 | 155 | // ErrorFieldString works for both Error and ErrorCopy. It assembles all of the 156 | // fields in an error and formats them into a single string. 157 | func ErrorFieldString(c *Context, fields []Field, errName string) { 158 | c.Putln("fieldVals := make([]string, 0, %d)", len(fields)) 159 | c.Putln("fieldVals = append(fieldVals, \"NiceName: \" + err.NiceName)") 160 | c.Putln("fieldVals = append(fieldVals, "+ 161 | "xgb.Sprintf(\"Sequence: %s\", err.Sequence))", "%d") 162 | for _, field := range fields { 163 | switch field.(type) { 164 | case *PadField: 165 | continue 166 | default: 167 | if field.SrcType() == "string" { 168 | c.Putln("fieldVals = append(fieldVals, \"%s: \" + err.%s)", 169 | field.SrcName(), field.SrcName()) 170 | } else { 171 | format := fmt.Sprintf("xgb.Sprintf(\"%s: %s\", err.%s)", 172 | field.SrcName(), "%d", field.SrcName()) 173 | c.Putln("fieldVals = append(fieldVals, %s)", format) 174 | } 175 | } 176 | } 177 | c.Putln("return \"%s {\" + xgb.StringsJoin(fieldVals, \", \") + \"}\"", 178 | errName) 179 | } 180 | -------------------------------------------------------------------------------- /xgbgen/go_event.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | ) 6 | 7 | // Event types 8 | func (e *Event) Define(c *Context) { 9 | c.Putln("// %s is the event number for a %s.", e.SrcName(), e.EvType()) 10 | c.Putln("const %s = %d", e.SrcName(), e.Number) 11 | c.Putln("") 12 | c.Putln("type %s struct {", e.EvType()) 13 | if !e.NoSequence { 14 | c.Putln("Sequence uint16") 15 | } 16 | for _, field := range e.Fields { 17 | field.Define(c) 18 | } 19 | c.Putln("}") 20 | c.Putln("") 21 | 22 | // Read defines a function that transforms a byte slice into this 23 | // event struct. 24 | e.Read(c) 25 | 26 | // Write defines a function that transforms this event struct into 27 | // a byte slice. 28 | e.Write(c) 29 | 30 | // Makes sure that this event type is an Event interface. 31 | c.Putln("// SequenceId returns the sequence id attached to the %s event.", 32 | e.SrcName()) 33 | c.Putln("// Events without a sequence number (KeymapNotify) return 0.") 34 | c.Putln("// This is mostly used internally.") 35 | c.Putln("func (v %s) SequenceId() uint16 {", e.EvType()) 36 | if e.NoSequence { 37 | c.Putln("return uint16(0)") 38 | } else { 39 | c.Putln("return v.Sequence") 40 | } 41 | c.Putln("}") 42 | c.Putln("") 43 | c.Putln("// String is a rudimentary string representation of %s.", 44 | e.EvType()) 45 | c.Putln("func (v %s) String() string {", e.EvType()) 46 | EventFieldString(c, e.Fields, e.SrcName()) 47 | c.Putln("}") 48 | c.Putln("") 49 | 50 | // Let's the XGB event loop read this event. 51 | c.Putln("func init() {") 52 | if c.protocol.isExt() { 53 | c.Putln("xgb.NewExtEventFuncs[\"%s\"][%d] = %sNew", 54 | c.protocol.ExtXName, e.Number, e.EvType()) 55 | } else { 56 | c.Putln("xgb.NewEventFuncs[%d] = %sNew", e.Number, e.EvType()) 57 | } 58 | c.Putln("}") 59 | c.Putln("") 60 | } 61 | 62 | func (e *Event) Read(c *Context) { 63 | c.Putln("// %sNew constructs a %s value that implements xgb.Event from "+ 64 | "a byte slice.", e.EvType(), e.EvType()) 65 | c.Putln("func %sNew(buf []byte) xgb.Event {", e.EvType()) 66 | c.Putln("v := %s{}", e.EvType()) 67 | c.Putln("b := 1 // don't read event number") 68 | c.Putln("") 69 | for i, field := range e.Fields { 70 | if i == 1 && !e.NoSequence { 71 | c.Putln("v.Sequence = xgb.Get16(buf[b:])") 72 | c.Putln("b += 2") 73 | c.Putln("") 74 | } 75 | field.Read(c, "v.") 76 | c.Putln("") 77 | } 78 | c.Putln("return v") 79 | c.Putln("}") 80 | c.Putln("") 81 | } 82 | 83 | func (e *Event) Write(c *Context) { 84 | c.Putln("// Bytes writes a %s value to a byte slice.", e.EvType()) 85 | c.Putln("func (v %s) Bytes() []byte {", e.EvType()) 86 | c.Putln("buf := make([]byte, %s)", e.Size()) 87 | c.Putln("b := 0") 88 | c.Putln("") 89 | c.Putln("// write event number") 90 | c.Putln("buf[b] = %d", e.Number) 91 | c.Putln("b += 1") 92 | c.Putln("") 93 | for i, field := range e.Fields { 94 | if i == 1 && !e.NoSequence { 95 | c.Putln("b += 2 // skip sequence number") 96 | c.Putln("") 97 | } 98 | field.Write(c, "v.") 99 | c.Putln("") 100 | } 101 | c.Putln("return buf") 102 | c.Putln("}") 103 | c.Putln("") 104 | } 105 | 106 | // EventCopy types 107 | func (e *EventCopy) Define(c *Context) { 108 | c.Putln("// %s is the event number for a %s.", e.SrcName(), e.EvType()) 109 | c.Putln("const %s = %d", e.SrcName(), e.Number) 110 | c.Putln("") 111 | c.Putln("type %s %s", e.EvType(), e.Old.(*Event).EvType()) 112 | c.Putln("") 113 | 114 | // Read defines a function that transforms a byte slice into this 115 | // event struct. 116 | e.Read(c) 117 | 118 | // Write defines a function that transoforms this event struct into 119 | // a byte slice. 120 | e.Write(c) 121 | 122 | // Makes sure that this event type is an Event interface. 123 | c.Putln("// SequenceId returns the sequence id attached to the %s event.", 124 | e.SrcName()) 125 | c.Putln("// Events without a sequence number (KeymapNotify) return 0.") 126 | c.Putln("// This is mostly used internally.") 127 | c.Putln("func (v %s) SequenceId() uint16 {", e.EvType()) 128 | if e.Old.(*Event).NoSequence { 129 | c.Putln("return uint16(0)") 130 | } else { 131 | c.Putln("return v.Sequence") 132 | } 133 | c.Putln("}") 134 | c.Putln("") 135 | c.Putln("func (v %s) String() string {", e.EvType()) 136 | EventFieldString(c, e.Old.(*Event).Fields, e.SrcName()) 137 | c.Putln("}") 138 | c.Putln("") 139 | 140 | // Let's the XGB event loop read this event. 141 | c.Putln("func init() {") 142 | if c.protocol.isExt() { 143 | c.Putln("xgb.NewExtEventFuncs[\"%s\"][%d] = %sNew", 144 | c.protocol.ExtXName, e.Number, e.EvType()) 145 | } else { 146 | c.Putln("xgb.NewEventFuncs[%d] = %sNew", e.Number, e.EvType()) 147 | } 148 | c.Putln("}") 149 | c.Putln("") 150 | } 151 | 152 | func (e *EventCopy) Read(c *Context) { 153 | c.Putln("// %sNew constructs a %s value that implements xgb.Event from "+ 154 | "a byte slice.", e.EvType(), e.EvType()) 155 | c.Putln("func %sNew(buf []byte) xgb.Event {", e.EvType()) 156 | c.Putln("return %s(%sNew(buf).(%s))", 157 | e.EvType(), e.Old.(*Event).EvType(), e.Old.(*Event).EvType()) 158 | c.Putln("}") 159 | c.Putln("") 160 | } 161 | 162 | func (e *EventCopy) Write(c *Context) { 163 | c.Putln("// Bytes writes a %s value to a byte slice.", e.EvType()) 164 | c.Putln("func (v %s) Bytes() []byte {", e.EvType()) 165 | c.Putln("return %s(v).Bytes()", e.Old.(*Event).EvType()) 166 | c.Putln("}") 167 | c.Putln("") 168 | } 169 | 170 | // EventFieldString works for both Event and EventCopy. It assembles all of the 171 | // fields in an event and formats them into a single string. 172 | func EventFieldString(c *Context, fields []Field, evName string) { 173 | c.Putln("fieldVals := make([]string, 0, %d)", len(fields)) 174 | if evName != "KeymapNotify" { 175 | c.Putln("fieldVals = append(fieldVals, "+ 176 | "xgb.Sprintf(\"Sequence: %s\", v.Sequence))", "%d") 177 | } 178 | for _, field := range fields { 179 | switch f := field.(type) { 180 | case *PadField: 181 | continue 182 | case *SingleField: 183 | switch f.Type.(type) { 184 | case *Base: 185 | case *Resource: 186 | case *TypeDef: 187 | default: 188 | continue 189 | } 190 | 191 | switch field.SrcType() { 192 | case "string": 193 | format := fmt.Sprintf("xgb.Sprintf(\"%s: %s\", v.%s)", 194 | field.SrcName(), "%s", field.SrcName()) 195 | c.Putln("fieldVals = append(fieldVals, %s)", format) 196 | case "bool": 197 | format := fmt.Sprintf("xgb.Sprintf(\"%s: %s\", v.%s)", 198 | field.SrcName(), "%t", field.SrcName()) 199 | c.Putln("fieldVals = append(fieldVals, %s)", format) 200 | default: 201 | format := fmt.Sprintf("xgb.Sprintf(\"%s: %s\", v.%s)", 202 | field.SrcName(), "%d", field.SrcName()) 203 | c.Putln("fieldVals = append(fieldVals, %s)", format) 204 | } 205 | } 206 | } 207 | c.Putln("return \"%s {\" + xgb.StringsJoin(fieldVals, \", \") + \"}\"", 208 | evName) 209 | } 210 | -------------------------------------------------------------------------------- /xgbgen/go_list.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | "strings" 7 | ) 8 | 9 | // List fields 10 | func (f *ListField) Define(c *Context) { 11 | c.Putln("%s %s // size: %s", 12 | f.SrcName(), f.SrcType(), f.Size()) 13 | } 14 | 15 | func (f *ListField) Read(c *Context, prefix string) { 16 | switch t := f.Type.(type) { 17 | case *Resource: 18 | length := f.LengthExpr.Reduce(prefix) 19 | c.Putln("%s%s = make([]%s, %s)", 20 | prefix, f.SrcName(), t.SrcName(), length) 21 | c.Putln("for i := 0; i < int(%s); i++ {", length) 22 | ReadSimpleSingleField(c, fmt.Sprintf("%s%s[i]", prefix, f.SrcName()), t) 23 | c.Putln("}") 24 | case *Base: 25 | length := f.LengthExpr.Reduce(prefix) 26 | if strings.ToLower(t.XmlName()) == "char" { 27 | c.Putln("{") 28 | c.Putln("byteString := make([]%s, %s)", t.SrcName(), length) 29 | c.Putln("copy(byteString[:%s], buf[b:])", length) 30 | c.Putln("%s%s = string(byteString)", prefix, f.SrcName()) 31 | // This is apparently a special case. The "Str" type itself 32 | // doesn't specify any padding. I suppose it's up to the 33 | // request/reply spec that uses it to get the padding right? 34 | c.Putln("b += int(%s)", length) 35 | c.Putln("}") 36 | } else if t.SrcName() == "byte" { 37 | c.Putln("%s%s = make([]%s, %s)", 38 | prefix, f.SrcName(), t.SrcName(), length) 39 | c.Putln("copy(%s%s[:%s], buf[b:])", prefix, f.SrcName(), length) 40 | c.Putln("b += int(%s)", length) 41 | } else { 42 | c.Putln("%s%s = make([]%s, %s)", 43 | prefix, f.SrcName(), t.SrcName(), length) 44 | c.Putln("for i := 0; i < int(%s); i++ {", length) 45 | ReadSimpleSingleField(c, 46 | fmt.Sprintf("%s%s[i]", prefix, f.SrcName()), t) 47 | c.Putln("}") 48 | } 49 | case *TypeDef: 50 | length := f.LengthExpr.Reduce(prefix) 51 | c.Putln("%s%s = make([]%s, %s)", 52 | prefix, f.SrcName(), t.SrcName(), length) 53 | c.Putln("for i := 0; i < int(%s); i++ {", length) 54 | ReadSimpleSingleField(c, fmt.Sprintf("%s%s[i]", prefix, f.SrcName()), t) 55 | c.Putln("}") 56 | case *Union: 57 | c.Putln("%s%s = make([]%s, %s)", 58 | prefix, f.SrcName(), t.SrcName(), f.LengthExpr.Reduce(prefix)) 59 | c.Putln("b += %sReadList(buf[b:], %s%s)", 60 | t.SrcName(), prefix, f.SrcName()) 61 | case *Struct: 62 | c.Putln("%s%s = make([]%s, %s)", 63 | prefix, f.SrcName(), t.SrcName(), f.LengthExpr.Reduce(prefix)) 64 | c.Putln("b += %sReadList(buf[b:], %s%s)", 65 | t.SrcName(), prefix, f.SrcName()) 66 | default: 67 | log.Panicf("Cannot read list field '%s' with %T type.", 68 | f.XmlName(), f.Type) 69 | } 70 | } 71 | 72 | func (f *ListField) Write(c *Context, prefix string) { 73 | switch t := f.Type.(type) { 74 | case *Resource: 75 | length := f.Length().Reduce(prefix) 76 | c.Putln("for i := 0; i < int(%s); i++ {", length) 77 | WriteSimpleSingleField(c, 78 | fmt.Sprintf("%s%s[i]", prefix, f.SrcName()), t) 79 | c.Putln("}") 80 | case *Base: 81 | length := f.Length().Reduce(prefix) 82 | if t.SrcName() == "byte" { 83 | c.Putln("copy(buf[b:], %s%s[:%s])", prefix, f.SrcName(), length) 84 | c.Putln("b += int(%s)", length) 85 | } else { 86 | c.Putln("for i := 0; i < int(%s); i++ {", length) 87 | WriteSimpleSingleField(c, 88 | fmt.Sprintf("%s%s[i]", prefix, f.SrcName()), t) 89 | c.Putln("}") 90 | } 91 | case *TypeDef: 92 | length := f.Length().Reduce(prefix) 93 | c.Putln("for i := 0; i < int(%s); i++ {", length) 94 | WriteSimpleSingleField(c, 95 | fmt.Sprintf("%s%s[i]", prefix, f.SrcName()), t) 96 | c.Putln("}") 97 | case *Union: 98 | c.Putln("b += %sListBytes(buf[b:], %s%s)", 99 | t.SrcName(), prefix, f.SrcName()) 100 | case *Struct: 101 | c.Putln("b += %sListBytes(buf[b:], %s%s)", 102 | t.SrcName(), prefix, f.SrcName()) 103 | default: 104 | log.Panicf("Cannot write list field '%s' with %T type.", 105 | f.XmlName(), f.Type) 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /xgbgen/go_request_reply.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | ) 7 | 8 | func (r *Request) Define(c *Context) { 9 | c.Putln("// %s is a cookie used only for %s requests.", 10 | r.CookieName(), r.SrcName()) 11 | c.Putln("type %s struct {", r.CookieName()) 12 | c.Putln("*xgb.Cookie") 13 | c.Putln("}") 14 | c.Putln("") 15 | if r.Reply != nil { 16 | c.Putln("// %s sends a checked request.", r.SrcName()) 17 | c.Putln("// If an error occurs, it will be returned with the reply "+ 18 | "by calling %s.Reply()", r.CookieName()) 19 | c.Putln("func %s(c *xgb.Conn, %s) %s {", 20 | r.SrcName(), r.ParamNameTypes(), r.CookieName()) 21 | r.CheckExt(c) 22 | c.Putln("cookie := c.NewCookie(true, true)") 23 | c.Putln("c.NewRequest(%s(c, %s), cookie)", r.ReqName(), r.ParamNames()) 24 | c.Putln("return %s{cookie}", r.CookieName()) 25 | c.Putln("}") 26 | c.Putln("") 27 | 28 | c.Putln("// %sUnchecked sends an unchecked request.", r.SrcName()) 29 | c.Putln("// If an error occurs, it can only be retrieved using " + 30 | "xgb.WaitForEvent or xgb.PollForEvent.") 31 | c.Putln("func %sUnchecked(c *xgb.Conn, %s) %s {", 32 | r.SrcName(), r.ParamNameTypes(), r.CookieName()) 33 | r.CheckExt(c) 34 | c.Putln("cookie := c.NewCookie(false, true)") 35 | c.Putln("c.NewRequest(%s(c, %s), cookie)", r.ReqName(), r.ParamNames()) 36 | c.Putln("return %s{cookie}", r.CookieName()) 37 | c.Putln("}") 38 | c.Putln("") 39 | 40 | r.ReadReply(c) 41 | } else { 42 | c.Putln("// %s sends an unchecked request.", r.SrcName()) 43 | c.Putln("// If an error occurs, it can only be retrieved using " + 44 | "xgb.WaitForEvent or xgb.PollForEvent.") 45 | c.Putln("func %s(c *xgb.Conn, %s) %s {", 46 | r.SrcName(), r.ParamNameTypes(), r.CookieName()) 47 | r.CheckExt(c) 48 | c.Putln("cookie := c.NewCookie(false, false)") 49 | c.Putln("c.NewRequest(%s(c, %s), cookie)", r.ReqName(), r.ParamNames()) 50 | c.Putln("return %s{cookie}", r.CookieName()) 51 | c.Putln("}") 52 | c.Putln("") 53 | 54 | c.Putln("// %sChecked sends a checked request.", r.SrcName()) 55 | c.Putln("// If an error occurs, it can be retrieved using "+ 56 | "%s.Check()", r.CookieName()) 57 | c.Putln("func %sChecked(c *xgb.Conn, %s) %s {", 58 | r.SrcName(), r.ParamNameTypes(), r.CookieName()) 59 | r.CheckExt(c) 60 | c.Putln("cookie := c.NewCookie(true, false)") 61 | c.Putln("c.NewRequest(%s(c, %s), cookie)", r.ReqName(), r.ParamNames()) 62 | c.Putln("return %s{cookie}", r.CookieName()) 63 | c.Putln("}") 64 | c.Putln("") 65 | 66 | c.Putln("// Check returns an error if one occurred for checked " + 67 | "requests that are not expecting a reply.") 68 | c.Putln("// This cannot be called for requests expecting a reply, " + 69 | "nor for unchecked requests.") 70 | c.Putln("func (cook %s) Check() error {", r.CookieName()) 71 | c.Putln("return cook.Cookie.Check()") 72 | c.Putln("}") 73 | c.Putln("") 74 | } 75 | r.WriteRequest(c) 76 | } 77 | 78 | func (r *Request) CheckExt(c *Context) { 79 | if !c.protocol.isExt() { 80 | return 81 | } 82 | c.Putln("c.ExtLock.RLock()") 83 | c.Putln("defer c.ExtLock.RUnlock()") 84 | c.Putln("if _, ok := c.Extensions[\"%s\"]; !ok {", c.protocol.ExtXName) 85 | c.Putln("panic(\"Cannot issue request '%s' using the uninitialized "+ 86 | "extension '%s'. %s.Init(connObj) must be called first.\")", 87 | r.SrcName(), c.protocol.ExtXName, c.protocol.PkgName()) 88 | c.Putln("}") 89 | } 90 | 91 | func (r *Request) ReadReply(c *Context) { 92 | c.Putln("// %s represents the data returned from a %s request.", 93 | r.ReplyTypeName(), r.SrcName()) 94 | c.Putln("type %s struct {", r.ReplyTypeName()) 95 | c.Putln("Sequence uint16 // sequence number of the request for this reply") 96 | c.Putln("Length uint32 // number of bytes in this reply") 97 | for _, field := range r.Reply.Fields { 98 | field.Define(c) 99 | } 100 | c.Putln("}") 101 | c.Putln("") 102 | 103 | c.Putln("// Reply blocks and returns the reply data for a %s request.", 104 | r.SrcName()) 105 | c.Putln("func (cook %s) Reply() (*%s, error) {", 106 | r.CookieName(), r.ReplyTypeName()) 107 | c.Putln("buf, err := cook.Cookie.Reply()") 108 | c.Putln("if err != nil {") 109 | c.Putln("return nil, err") 110 | c.Putln("}") 111 | c.Putln("if buf == nil {") 112 | c.Putln("return nil, nil") 113 | c.Putln("}") 114 | c.Putln("return %s(buf), nil", r.ReplyName()) 115 | c.Putln("}") 116 | c.Putln("") 117 | 118 | c.Putln("// %s reads a byte slice into a %s value.", 119 | r.ReplyName(), r.ReplyTypeName()) 120 | c.Putln("func %s(buf []byte) *%s {", 121 | r.ReplyName(), r.ReplyTypeName()) 122 | c.Putln("v := new(%s)", r.ReplyTypeName()) 123 | c.Putln("b := 1 // skip reply determinant") 124 | c.Putln("") 125 | for i, field := range r.Reply.Fields { 126 | field.Read(c, "v.") 127 | c.Putln("") 128 | if i == 0 { 129 | c.Putln("v.Sequence = xgb.Get16(buf[b:])") 130 | c.Putln("b += 2") 131 | c.Putln("") 132 | c.Putln("v.Length = xgb.Get32(buf[b:]) // 4-byte units") 133 | c.Putln("b += 4") 134 | c.Putln("") 135 | } 136 | } 137 | c.Putln("return v") 138 | c.Putln("}") 139 | c.Putln("") 140 | } 141 | 142 | func (r *Request) WriteRequest(c *Context) { 143 | sz := r.Size(c) 144 | writeSize1 := func() { 145 | if sz.exact { 146 | c.Putln("xgb.Put16(buf[b:], uint16(size / 4)) " + 147 | "// write request size in 4-byte units") 148 | } else { 149 | c.Putln("blen := b") 150 | } 151 | c.Putln("b += 2") 152 | c.Putln("") 153 | } 154 | writeSize2 := func() { 155 | if sz.exact { 156 | c.Putln("return buf") 157 | return 158 | } 159 | c.Putln("b = xgb.Pad(b)") 160 | c.Putln("xgb.Put16(buf[blen:], uint16(b / 4)) " + 161 | "// write request size in 4-byte units") 162 | c.Putln("return buf[:b]") 163 | } 164 | c.Putln("// Write request to wire for %s", r.SrcName()) 165 | c.Putln("// %s writes a %s request to a byte slice.", 166 | r.ReqName(), r.SrcName()) 167 | c.Putln("func %s(c *xgb.Conn, %s) []byte {", 168 | r.ReqName(), r.ParamNameTypes()) 169 | c.Putln("size := %s", sz) 170 | c.Putln("b := 0") 171 | c.Putln("buf := make([]byte, size)") 172 | c.Putln("") 173 | if c.protocol.isExt() { 174 | c.Putln("c.ExtLock.RLock()") 175 | c.Putln("buf[b] = c.Extensions[\"%s\"]", c.protocol.ExtXName) 176 | c.Putln("c.ExtLock.RUnlock()") 177 | c.Putln("b += 1") 178 | c.Putln("") 179 | } 180 | c.Putln("buf[b] = %d // request opcode", r.Opcode) 181 | c.Putln("b += 1") 182 | c.Putln("") 183 | if len(r.Fields) == 0 { 184 | if !c.protocol.isExt() { 185 | c.Putln("b += 1 // padding") 186 | } 187 | writeSize1() 188 | } else if c.protocol.isExt() { 189 | writeSize1() 190 | } 191 | for i, field := range r.Fields { 192 | field.Write(c, "") 193 | c.Putln("") 194 | if i == 0 && !c.protocol.isExt() { 195 | writeSize1() 196 | } 197 | } 198 | writeSize2() 199 | c.Putln("}") 200 | c.Putln("") 201 | } 202 | 203 | func (r *Request) ParamNames() string { 204 | names := make([]string, 0, len(r.Fields)) 205 | for _, field := range r.Fields { 206 | switch f := field.(type) { 207 | case *ValueField: 208 | // mofos... 209 | if r.SrcName() != "ConfigureWindow" { 210 | names = append(names, f.MaskName) 211 | } 212 | names = append(names, f.ListName) 213 | case *PadField: 214 | continue 215 | case *ExprField: 216 | continue 217 | default: 218 | names = append(names, fmt.Sprintf("%s", field.SrcName())) 219 | } 220 | } 221 | return strings.Join(names, ", ") 222 | } 223 | 224 | func (r *Request) ParamNameTypes() string { 225 | nameTypes := make([]string, 0, len(r.Fields)) 226 | for _, field := range r.Fields { 227 | switch f := field.(type) { 228 | case *ValueField: 229 | // mofos... 230 | if r.SrcName() != "ConfigureWindow" { 231 | nameTypes = append(nameTypes, 232 | fmt.Sprintf("%s %s", f.MaskName, f.MaskType.SrcName())) 233 | } 234 | nameTypes = append(nameTypes, 235 | fmt.Sprintf("%s []uint32", f.ListName)) 236 | case *PadField: 237 | continue 238 | case *ExprField: 239 | continue 240 | default: 241 | nameTypes = append(nameTypes, 242 | fmt.Sprintf("%s %s", field.SrcName(), field.SrcType())) 243 | } 244 | } 245 | return strings.Join(nameTypes, ", ") 246 | } 247 | -------------------------------------------------------------------------------- /xgbgen/go_single_field.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | ) 7 | 8 | func (f *SingleField) Define(c *Context) { 9 | c.Putln("%s %s", f.SrcName(), f.Type.SrcName()) 10 | } 11 | 12 | func ReadSimpleSingleField(c *Context, name string, typ Type) { 13 | switch t := typ.(type) { 14 | case *Resource: 15 | c.Putln("%s = %s(xgb.Get32(buf[b:]))", name, t.SrcName()) 16 | case *TypeDef: 17 | switch t.Size().Eval() { 18 | case 1: 19 | c.Putln("%s = %s(buf[b])", name, t.SrcName()) 20 | case 2: 21 | c.Putln("%s = %s(xgb.Get16(buf[b:]))", name, t.SrcName()) 22 | case 4: 23 | c.Putln("%s = %s(xgb.Get32(buf[b:]))", name, t.SrcName()) 24 | case 8: 25 | c.Putln("%s = %s(xgb.Get64(buf[b:]))", name, t.SrcName()) 26 | } 27 | case *Base: 28 | // If this is a bool, stop short and do something special. 29 | if t.SrcName() == "bool" { 30 | c.Putln("if buf[b] == 1 {") 31 | c.Putln("%s = true", name) 32 | c.Putln("} else {") 33 | c.Putln("%s = false", name) 34 | c.Putln("}") 35 | break 36 | } 37 | 38 | var val string 39 | switch t.Size().Eval() { 40 | case 1: 41 | val = fmt.Sprintf("buf[b]") 42 | case 2: 43 | val = fmt.Sprintf("xgb.Get16(buf[b:])") 44 | case 4: 45 | val = fmt.Sprintf("xgb.Get32(buf[b:])") 46 | case 8: 47 | val = fmt.Sprintf("xgb.Get64(buf[b:])") 48 | } 49 | 50 | // We need to convert base types if they aren't uintXX or byte 51 | ty := t.SrcName() 52 | if ty != "byte" && ty != "uint16" && ty != "uint32" && ty != "uint64" { 53 | val = fmt.Sprintf("%s(%s)", ty, val) 54 | } 55 | c.Putln("%s = %s", name, val) 56 | default: 57 | log.Panicf("Cannot read field '%s' as a simple field with %T type.", 58 | name, typ) 59 | } 60 | 61 | c.Putln("b += %s", typ.Size()) 62 | } 63 | 64 | func (f *SingleField) Read(c *Context, prefix string) { 65 | switch t := f.Type.(type) { 66 | case *Resource: 67 | ReadSimpleSingleField(c, fmt.Sprintf("%s%s", prefix, f.SrcName()), t) 68 | case *TypeDef: 69 | ReadSimpleSingleField(c, fmt.Sprintf("%s%s", prefix, f.SrcName()), t) 70 | case *Base: 71 | ReadSimpleSingleField(c, fmt.Sprintf("%s%s", prefix, f.SrcName()), t) 72 | case *Struct: 73 | c.Putln("%s%s = %s{}", prefix, f.SrcName(), t.SrcName()) 74 | c.Putln("b += %sRead(buf[b:], &%s%s)", t.SrcName(), prefix, f.SrcName()) 75 | case *Union: 76 | c.Putln("%s%s = %s{}", prefix, f.SrcName(), t.SrcName()) 77 | c.Putln("b += %sRead(buf[b:], &%s%s)", t.SrcName(), prefix, f.SrcName()) 78 | default: 79 | log.Panicf("Cannot read field '%s' with %T type.", f.XmlName(), f.Type) 80 | } 81 | } 82 | 83 | func WriteSimpleSingleField(c *Context, name string, typ Type) { 84 | switch t := typ.(type) { 85 | case *Resource: 86 | c.Putln("xgb.Put32(buf[b:], uint32(%s))", name) 87 | case *TypeDef: 88 | switch t.Size().Eval() { 89 | case 1: 90 | c.Putln("buf[b] = byte(%s)", name) 91 | case 2: 92 | c.Putln("xgb.Put16(buf[b:], uint16(%s))", name) 93 | case 4: 94 | c.Putln("xgb.Put32(buf[b:], uint32(%s))", name) 95 | case 8: 96 | c.Putln("xgb.Put64(buf[b:], uint64(%s))", name) 97 | } 98 | case *Base: 99 | // If this is a bool, stop short and do something special. 100 | if t.SrcName() == "bool" { 101 | c.Putln("if %s {", name) 102 | c.Putln("buf[b] = 1") 103 | c.Putln("} else {") 104 | c.Putln("buf[b] = 0") 105 | c.Putln("}") 106 | break 107 | } 108 | 109 | switch t.Size().Eval() { 110 | case 1: 111 | if t.SrcName() != "byte" { 112 | c.Putln("buf[b] = byte(%s)", name) 113 | } else { 114 | c.Putln("buf[b] = %s", name) 115 | } 116 | case 2: 117 | if t.SrcName() != "uint16" { 118 | c.Putln("xgb.Put16(buf[b:], uint16(%s))", name) 119 | } else { 120 | c.Putln("xgb.Put16(buf[b:], %s)", name) 121 | } 122 | case 4: 123 | if t.SrcName() != "uint32" { 124 | c.Putln("xgb.Put32(buf[b:], uint32(%s))", name) 125 | } else { 126 | c.Putln("xgb.Put32(buf[b:], %s)", name) 127 | } 128 | case 8: 129 | if t.SrcName() != "uint64" { 130 | c.Putln("xgb.Put64(buf[b:], uint64(%s))", name) 131 | } else { 132 | c.Putln("xgb.Put64(buf[b:], %s)", name) 133 | } 134 | } 135 | default: 136 | log.Fatalf("Cannot read field '%s' as a simple field with %T type.", 137 | name, typ) 138 | } 139 | 140 | c.Putln("b += %s", typ.Size()) 141 | } 142 | 143 | func (f *SingleField) Write(c *Context, prefix string) { 144 | switch t := f.Type.(type) { 145 | case *Resource: 146 | WriteSimpleSingleField(c, fmt.Sprintf("%s%s", prefix, f.SrcName()), t) 147 | case *TypeDef: 148 | WriteSimpleSingleField(c, fmt.Sprintf("%s%s", prefix, f.SrcName()), t) 149 | case *Base: 150 | WriteSimpleSingleField(c, fmt.Sprintf("%s%s", prefix, f.SrcName()), t) 151 | case *Union: 152 | c.Putln("{") 153 | c.Putln("unionBytes := %s%s.Bytes()", prefix, f.SrcName()) 154 | c.Putln("copy(buf[b:], unionBytes)") 155 | c.Putln("b += len(unionBytes)") 156 | c.Putln("}") 157 | case *Struct: 158 | c.Putln("{") 159 | c.Putln("structBytes := %s%s.Bytes()", prefix, f.SrcName()) 160 | c.Putln("copy(buf[b:], structBytes)") 161 | c.Putln("b += len(structBytes)") 162 | c.Putln("}") 163 | default: 164 | log.Fatalf("Cannot read field '%s' with %T type.", f.XmlName(), f.Type) 165 | } 166 | } 167 | -------------------------------------------------------------------------------- /xgbgen/go_struct.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | func (s *Struct) Define(c *Context) { 4 | c.Putln("type %s struct {", s.SrcName()) 5 | for _, field := range s.Fields { 6 | field.Define(c) 7 | } 8 | c.Putln("}") 9 | c.Putln("") 10 | 11 | // Write function that reads bytes and produces this struct. 12 | s.Read(c) 13 | 14 | // Write function that reads bytes and produces a list of this struct. 15 | s.ReadList(c) 16 | 17 | // Write function that writes bytes given this struct. 18 | s.Write(c) 19 | 20 | // Write function that writes a list of this struct. 21 | s.WriteList(c) 22 | 23 | // Write function that computes the size of a list of these structs, 24 | // IF there is a list field in this struct. 25 | if s.HasList() { 26 | s.WriteListSize(c) 27 | } 28 | } 29 | 30 | // Read for a struct creates a function 'ReadStructName' that takes a source 31 | // byte slice (i.e., the buffer) and a destination struct, and returns 32 | // the number of bytes read off the buffer. 33 | // 'ReadStructName' should only be used to read raw reply data from the wire. 34 | func (s *Struct) Read(c *Context) { 35 | c.Putln("// %sRead reads a byte slice into a %s value.", 36 | s.SrcName(), s.SrcName()) 37 | c.Putln("func %sRead(buf []byte, v *%s) int {", s.SrcName(), s.SrcName()) 38 | 39 | c.Putln("b := 0") 40 | c.Putln("") 41 | for _, field := range s.Fields { 42 | field.Read(c, "v.") 43 | c.Putln("") 44 | } 45 | c.Putln("return b") 46 | 47 | c.Putln("}") 48 | c.Putln("") 49 | } 50 | 51 | // ReadList for a struct creates a function 'ReadStructNameList' that takes 52 | // a source (i.e., the buffer) byte slice, and a destination slice and returns 53 | // the number of bytes read from the byte slice. 54 | func (s *Struct) ReadList(c *Context) { 55 | c.Putln("// %sReadList reads a byte slice into a list of %s values.", 56 | s.SrcName(), s.SrcName()) 57 | c.Putln("func %sReadList(buf []byte, dest []%s) int {", 58 | s.SrcName(), s.SrcName()) 59 | c.Putln("b := 0") 60 | c.Putln("for i := 0; i < len(dest); i++ {") 61 | c.Putln("dest[i] = %s{}", s.SrcName()) 62 | c.Putln("b += %sRead(buf[b:], &dest[i])", s.SrcName()) 63 | c.Putln("}") 64 | 65 | c.Putln("return xgb.Pad(b)") 66 | 67 | c.Putln("}") 68 | c.Putln("") 69 | } 70 | 71 | func (s *Struct) Write(c *Context) { 72 | c.Putln("// Bytes writes a %s value to a byte slice.", s.SrcName()) 73 | c.Putln("func (v %s) Bytes() []byte {", s.SrcName()) 74 | c.Putln("buf := make([]byte, %s)", s.Size().Reduce("v.")) 75 | c.Putln("b := 0") 76 | c.Putln("") 77 | for _, field := range s.Fields { 78 | field.Write(c, "v.") 79 | c.Putln("") 80 | } 81 | c.Putln("return buf[:b]") 82 | c.Putln("}") 83 | c.Putln("") 84 | } 85 | 86 | func (s *Struct) WriteList(c *Context) { 87 | c.Putln("// %sListBytes writes a list of %s values to a byte slice.", 88 | s.SrcName(), s.SrcName()) 89 | c.Putln("func %sListBytes(buf []byte, list []%s) int {", 90 | s.SrcName(), s.SrcName()) 91 | c.Putln("b := 0") 92 | c.Putln("var structBytes []byte") 93 | c.Putln("for _, item := range list {") 94 | c.Putln("structBytes = item.Bytes()") 95 | c.Putln("copy(buf[b:], structBytes)") 96 | c.Putln("b += len(structBytes)") 97 | c.Putln("}") 98 | c.Putln("return xgb.Pad(b)") 99 | c.Putln("}") 100 | c.Putln("") 101 | } 102 | 103 | func (s *Struct) WriteListSize(c *Context) { 104 | c.Putln("// %sListSize computes the size (bytes) of a list of %s values.", 105 | s.SrcName(), s.SrcName()) 106 | c.Putln("func %sListSize(list []%s) int {", s.SrcName(), s.SrcName()) 107 | c.Putln("size := 0") 108 | if s.Size().Expression.Concrete() { 109 | c.Putln("for _ = range list {") 110 | } else { 111 | c.Putln("for _, item := range list {") 112 | } 113 | c.Putln("size += %s", s.Size().Reduce("item.")) 114 | c.Putln("}") 115 | c.Putln("return size") 116 | c.Putln("}") 117 | c.Putln("") 118 | } 119 | -------------------------------------------------------------------------------- /xgbgen/go_union.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | // Union types 4 | func (u *Union) Define(c *Context) { 5 | c.Putln("// %s is a represention of the %s union type.", 6 | u.SrcName(), u.SrcName()) 7 | c.Putln("// Note that to *create* a Union, you should *never* create") 8 | c.Putln("// this struct directly (unless you know what you're doing).") 9 | c.Putln("// Instead use one of the following constructors for '%s':", 10 | u.SrcName()) 11 | for _, field := range u.Fields { 12 | c.Putln("// %s%sNew(%s %s) %s", u.SrcName(), field.SrcName(), 13 | field.SrcName(), field.SrcType(), u.SrcName()) 14 | } 15 | 16 | c.Putln("type %s struct {", u.SrcName()) 17 | for _, field := range u.Fields { 18 | field.Define(c) 19 | } 20 | c.Putln("}") 21 | c.Putln("") 22 | 23 | // Write functions for each field that create instances of this 24 | // union using the corresponding field. 25 | u.New(c) 26 | 27 | // Write function that reads bytes and produces this union. 28 | u.Read(c) 29 | 30 | // Write function that reads bytes and produces a list of this union. 31 | u.ReadList(c) 32 | 33 | // Write function that writes bytes given this union. 34 | u.Write(c) 35 | 36 | // Write function that writes a list of this union. 37 | u.WriteList(c) 38 | } 39 | 40 | func (u *Union) New(c *Context) { 41 | for _, field := range u.Fields { 42 | c.Putln("// %s%sNew constructs a new %s union type with the %s field.", 43 | u.SrcName(), field.SrcName(), u.SrcName(), field.SrcName()) 44 | c.Putln("func %s%sNew(%s %s) %s {", 45 | u.SrcName(), field.SrcName(), field.SrcName(), 46 | field.SrcType(), u.SrcName()) 47 | c.Putln("var b int") 48 | c.Putln("buf := make([]byte, %s)", u.Size()) 49 | c.Putln("") 50 | field.Write(c, "") 51 | c.Putln("") 52 | c.Putln("// Create the Union type") 53 | c.Putln("v := %s{}", u.SrcName()) 54 | c.Putln("") 55 | c.Putln("// Now copy buf into all fields") 56 | c.Putln("") 57 | for _, field2 := range u.Fields { 58 | c.Putln("b = 0 // always read the same bytes") 59 | field2.Read(c, "v.") 60 | c.Putln("") 61 | } 62 | c.Putln("return v") 63 | c.Putln("}") 64 | c.Putln("") 65 | } 66 | } 67 | 68 | func (u *Union) Read(c *Context) { 69 | c.Putln("// %sRead reads a byte slice into a %s value.", 70 | u.SrcName(), u.SrcName()) 71 | c.Putln("func %sRead(buf []byte, v *%s) int {", u.SrcName(), u.SrcName()) 72 | c.Putln("var b int") 73 | c.Putln("") 74 | for _, field := range u.Fields { 75 | c.Putln("b = 0 // re-read the same bytes") 76 | field.Read(c, "v.") 77 | c.Putln("") 78 | } 79 | c.Putln("return %s", u.Size()) 80 | c.Putln("}") 81 | c.Putln("") 82 | } 83 | 84 | func (u *Union) ReadList(c *Context) { 85 | c.Putln("// %sReadList reads a byte slice into a list of %s values.", 86 | u.SrcName(), u.SrcName()) 87 | c.Putln("func %sReadList(buf []byte, dest []%s) int {", 88 | u.SrcName(), u.SrcName()) 89 | c.Putln("b := 0") 90 | c.Putln("for i := 0; i < len(dest); i++ {") 91 | c.Putln("dest[i] = %s{}", u.SrcName()) 92 | c.Putln("b += %sRead(buf[b:], &dest[i])", u.SrcName()) 93 | c.Putln("}") 94 | c.Putln("return xgb.Pad(b)") 95 | c.Putln("}") 96 | c.Putln("") 97 | } 98 | 99 | // This is a bit tricky since writing from a Union implies that only 100 | // the data inside ONE of the elements is actually written. 101 | // However, we only currently support unions where every field has the 102 | // *same* *fixed* size. Thus, we make sure to always read bytes into 103 | // every field which allows us to simply pick the first field and write it. 104 | func (u *Union) Write(c *Context) { 105 | c.Putln("// Bytes writes a %s value to a byte slice.", u.SrcName()) 106 | c.Putln("// Each field in a union must contain the same data.") 107 | c.Putln("// So simply pick the first field and write that to the wire.") 108 | c.Putln("func (v %s) Bytes() []byte {", u.SrcName()) 109 | c.Putln("buf := make([]byte, %s)", u.Size().Reduce("v.")) 110 | c.Putln("b := 0") 111 | c.Putln("") 112 | u.Fields[0].Write(c, "v.") 113 | c.Putln("return buf") 114 | c.Putln("}") 115 | c.Putln("") 116 | } 117 | 118 | func (u *Union) WriteList(c *Context) { 119 | c.Putln("// %sListBytes writes a list of %s values to a byte slice.", 120 | u.SrcName(), u.SrcName()) 121 | c.Putln("func %sListBytes(buf []byte, list []%s) int {", 122 | u.SrcName(), u.SrcName()) 123 | c.Putln("b := 0") 124 | c.Putln("var unionBytes []byte") 125 | c.Putln("for _, item := range list {") 126 | c.Putln("unionBytes = item.Bytes()") 127 | c.Putln("copy(buf[b:], unionBytes)") 128 | c.Putln("b += xgb.Pad(len(unionBytes))") 129 | c.Putln("}") 130 | c.Putln("return b") 131 | c.Putln("}") 132 | c.Putln("") 133 | } 134 | 135 | func (u *Union) WriteListSize(c *Context) { 136 | c.Putln("// Union list size %s", u.SrcName()) 137 | c.Putln("// %sListSize computes the size (bytes) of a list of %s values.", 138 | u.SrcName()) 139 | c.Putln("func %sListSize(list []%s) int {", u.SrcName(), u.SrcName()) 140 | c.Putln("size := 0") 141 | c.Putln("for _, item := range list {") 142 | c.Putln("size += %s", u.Size().Reduce("item.")) 143 | c.Putln("}") 144 | c.Putln("return size") 145 | c.Putln("}") 146 | c.Putln("") 147 | } 148 | -------------------------------------------------------------------------------- /xgbgen/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "io/ioutil" 6 | "log" 7 | "os" 8 | "os/exec" 9 | "strings" 10 | ) 11 | 12 | var ( 13 | protoPath = flag.String("proto-path", 14 | "/usr/share/xcb", "path to directory of X protocol XML files") 15 | gofmt = flag.Bool("gofmt", true, 16 | "When disabled, gofmt will not be run before outputting Go code") 17 | ) 18 | 19 | func usage() { 20 | basename := os.Args[0] 21 | if lastSlash := strings.LastIndex(basename, "/"); lastSlash > -1 { 22 | basename = basename[lastSlash+1:] 23 | } 24 | log.Printf("Usage: %s [flags] xml-file", basename) 25 | flag.PrintDefaults() 26 | os.Exit(1) 27 | } 28 | 29 | func init() { 30 | log.SetFlags(0) 31 | } 32 | 33 | func main() { 34 | flag.Usage = usage 35 | flag.Parse() 36 | 37 | if flag.NArg() != 1 { 38 | log.Printf("A single XML protocol file can be processed at once.") 39 | flag.Usage() 40 | } 41 | 42 | // Read the single XML file into []byte 43 | xmlBytes, err := ioutil.ReadFile(flag.Arg(0)) 44 | if err != nil { 45 | log.Fatal(err) 46 | } 47 | 48 | // Initialize the buffer, parse it, and filter it through gofmt. 49 | c := newContext() 50 | c.Morph(xmlBytes) 51 | 52 | if !*gofmt { 53 | c.out.WriteTo(os.Stdout) 54 | } else { 55 | cmdGofmt := exec.Command("gofmt") 56 | cmdGofmt.Stdin = c.out 57 | cmdGofmt.Stdout = os.Stdout 58 | cmdGofmt.Stderr = os.Stderr 59 | err = cmdGofmt.Run() 60 | if err != nil { 61 | log.Fatal(err) 62 | } 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /xgbgen/misc.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "regexp" 5 | "strings" 6 | ) 7 | 8 | // AllCaps is a regex to test if a string identifier is made of 9 | // all upper case letters. 10 | var allCaps = regexp.MustCompile("^[A-Z0-9]+$") 11 | 12 | // popCount counts number of bits 'set' in mask. 13 | func popCount(mask uint) uint { 14 | m := uint32(mask) 15 | n := uint(0) 16 | for i := uint32(0); i < 32; i++ { 17 | if m&(1< 31 { 296 | log.Panicf("A 'bit' literal must be in the range [0, 31], but "+ 297 | " is %d", bit) 298 | } 299 | return &Bit{ 300 | b: bit, 301 | } 302 | case "fieldref": 303 | return &FieldRef{ 304 | Name: x.Data, 305 | } 306 | case "enumref": 307 | return &EnumRef{ 308 | EnumKind: newTranslation(x.Ref), 309 | EnumItem: x.Data, 310 | } 311 | case "sumof": 312 | return &SumOf{ 313 | Name: x.Ref, 314 | } 315 | } 316 | 317 | log.Panicf("Unrecognized tag '%s' in expression context. Expected one of "+ 318 | "op, fieldref, value, bit, enumref, unop, sumof or popcount.", 319 | x.XMLName.Local) 320 | panic("unreachable") 321 | } 322 | 323 | func (x *XMLField) Translate(parent interface{}) Field { 324 | switch x.XMLName.Local { 325 | case "pad": 326 | return &PadField{ 327 | Bytes: x.Bytes, 328 | } 329 | case "field": 330 | return &SingleField{ 331 | xmlName: x.Name, 332 | Type: newTranslation(x.Type), 333 | } 334 | case "list": 335 | return &ListField{ 336 | xmlName: x.Name, 337 | Type: newTranslation(x.Type), 338 | LengthExpr: x.Expr.Translate(), 339 | } 340 | case "localfield": 341 | return &LocalField{&SingleField{ 342 | xmlName: x.Name, 343 | Type: newTranslation(x.Type), 344 | }} 345 | case "exprfield": 346 | return &ExprField{ 347 | xmlName: x.Name, 348 | Type: newTranslation(x.Type), 349 | Expr: x.Expr.Translate(), 350 | } 351 | case "valueparam": 352 | return &ValueField{ 353 | Parent: parent, 354 | MaskType: newTranslation(x.ValueMaskType), 355 | MaskName: x.ValueMaskName, 356 | ListName: x.ValueListName, 357 | } 358 | case "switch": 359 | swtch := &SwitchField{ 360 | Name: x.Name, 361 | Expr: x.Expr.Translate(), 362 | Bitcases: make([]*Bitcase, len(x.Bitcases)), 363 | } 364 | for i, bitcase := range x.Bitcases { 365 | swtch.Bitcases[i] = bitcase.Translate() 366 | } 367 | return swtch 368 | } 369 | 370 | log.Panicf("Unrecognized field element: %s", x.XMLName.Local) 371 | panic("unreachable") 372 | } 373 | 374 | func (x *XMLBitcase) Translate() *Bitcase { 375 | b := &Bitcase{ 376 | Expr: x.Expr().Translate(), 377 | Fields: make([]Field, len(x.Fields)), 378 | } 379 | for i, field := range x.Fields { 380 | b.Fields[i] = field.Translate(b) 381 | } 382 | return b 383 | } 384 | 385 | // SrcName is used to translate any identifier into a Go name. 386 | // Mostly used for fields, but used in a couple other places too (enum items). 387 | func SrcName(p *Protocol, name string) string { 388 | // If it's in the name map, use that translation. 389 | if newn, ok := NameMap[name]; ok { 390 | return newn 391 | } 392 | return splitAndTitle(name) 393 | } 394 | 395 | func TypeSrcName(p *Protocol, typ Type) string { 396 | t := typ.XmlName() 397 | 398 | // If this is a base type, then write the raw Go type. 399 | if baseType, ok := typ.(*Base); ok { 400 | return baseType.SrcName() 401 | } 402 | 403 | // If it's in the type map, use that translation. 404 | if newt, ok := TypeMap[t]; ok { 405 | return newt 406 | } 407 | 408 | // If there's a namespace to this type, just use it and be done. 409 | if colon := strings.Index(t, ":"); colon > -1 { 410 | namespace := t[:colon] 411 | rest := t[colon+1:] 412 | return p.ProtocolFind(namespace).PkgName() + "." + splitAndTitle(rest) 413 | } 414 | 415 | // Since there's no namespace, we're left with the raw type name. 416 | // If the type is part of the source we're generating (i.e., there is 417 | // no parent protocol), then just return that type name. 418 | // Otherwise, we must qualify it with a package name. 419 | if p.Parent == nil { 420 | return splitAndTitle(t) 421 | } 422 | return p.PkgName() + "." + splitAndTitle(t) 423 | } 424 | -------------------------------------------------------------------------------- /xgbgen/type.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | ) 7 | 8 | type Type interface { 9 | Initialize(p *Protocol) 10 | SrcName() string 11 | XmlName() string 12 | Size() Size 13 | 14 | Define(c *Context) 15 | } 16 | 17 | type Types []Type 18 | 19 | func (ts Types) Len() int { return len(ts) } 20 | func (ts Types) Swap(i, j int) { ts[i], ts[j] = ts[j], ts[i] } 21 | func (ts Types) Less(i, j int) bool { 22 | x1, x2 := ts[i].XmlName(), ts[j].XmlName() 23 | s1, s2 := ts[i].SrcName(), ts[j].SrcName() 24 | return (s1 == s2 && x1 < x2) || s1 < s2 25 | } 26 | 27 | // Translation is used *only* when transitioning from XML types to 28 | // our better representation. They are placeholders for the real types (below) 29 | // that will replace them. 30 | type Translation struct { 31 | xmlName string 32 | } 33 | 34 | func newTranslation(name string) *Translation { 35 | return &Translation{xmlName: name} 36 | } 37 | 38 | // RealType takes 'XmlName' and finds its real concrete type in our Protocol. 39 | // It is an error if we can't find such a type. 40 | func (t *Translation) RealType(p *Protocol) Type { 41 | // Check to see if there is a namespace. If so, strip it and use it to 42 | // make sure we only look for a type in that protocol. 43 | namespace, typeName := "", t.XmlName() 44 | if ni := strings.Index(t.XmlName(), ":"); ni > -1 { 45 | namespace, typeName = strings.ToLower(typeName[:ni]), typeName[ni+1:] 46 | } 47 | 48 | if len(namespace) == 0 || namespace == strings.ToLower(p.Name) { 49 | for _, typ := range p.Types { 50 | if typeName == typ.XmlName() { 51 | return typ 52 | } 53 | } 54 | } 55 | for _, imp := range p.Imports { 56 | if len(namespace) == 0 || namespace == strings.ToLower(imp.Name) { 57 | for _, typ := range imp.Types { 58 | if typeName == typ.XmlName() { 59 | return typ 60 | } 61 | } 62 | } 63 | } 64 | panic("Could not find real type for translation type: " + t.XmlName()) 65 | } 66 | 67 | func (t *Translation) SrcName() string { 68 | panic("it is illegal to call SrcName on a translation type") 69 | } 70 | 71 | func (t *Translation) XmlName() string { 72 | return t.xmlName 73 | } 74 | 75 | func (t *Translation) Size() Size { 76 | panic("it is illegal to call Size on a translation type") 77 | } 78 | 79 | func (t *Translation) Define(c *Context) { 80 | panic("it is illegal to call Define on a translation type") 81 | } 82 | 83 | func (t *Translation) Initialize(p *Protocol) { 84 | panic("it is illegal to call Initialize on a translation type") 85 | } 86 | 87 | type Base struct { 88 | srcName string 89 | xmlName string 90 | size Size 91 | } 92 | 93 | func (b *Base) SrcName() string { 94 | return b.srcName 95 | } 96 | 97 | func (b *Base) XmlName() string { 98 | return b.xmlName 99 | } 100 | 101 | func (b *Base) Size() Size { 102 | return b.size 103 | } 104 | 105 | func (b *Base) Initialize(p *Protocol) { 106 | b.srcName = TypeSrcName(p, b) 107 | } 108 | 109 | type Enum struct { 110 | srcName string 111 | xmlName string 112 | Items []*EnumItem 113 | } 114 | 115 | type EnumItem struct { 116 | srcName string 117 | xmlName string 118 | Expr Expression 119 | } 120 | 121 | func (enum *Enum) SrcName() string { 122 | return enum.srcName 123 | } 124 | 125 | func (enum *Enum) XmlName() string { 126 | return enum.xmlName 127 | } 128 | 129 | func (enum *Enum) Size() Size { 130 | panic("Cannot take size of enum") 131 | } 132 | 133 | func (enum *Enum) Initialize(p *Protocol) { 134 | enum.srcName = TypeSrcName(p, enum) 135 | for _, item := range enum.Items { 136 | item.srcName = SrcName(p, item.xmlName) 137 | if item.Expr != nil { 138 | item.Expr.Initialize(p) 139 | } 140 | } 141 | } 142 | 143 | type Resource struct { 144 | srcName string 145 | xmlName string 146 | } 147 | 148 | func (r *Resource) SrcName() string { 149 | return r.srcName 150 | } 151 | 152 | func (r *Resource) XmlName() string { 153 | return r.xmlName 154 | } 155 | 156 | func (r *Resource) Size() Size { 157 | return newFixedSize(BaseTypeSizes["Id"], true) 158 | } 159 | 160 | func (r *Resource) Initialize(p *Protocol) { 161 | r.srcName = TypeSrcName(p, r) 162 | } 163 | 164 | type TypeDef struct { 165 | srcName string 166 | xmlName string 167 | Old Type 168 | } 169 | 170 | func (t *TypeDef) SrcName() string { 171 | return t.srcName 172 | } 173 | 174 | func (t *TypeDef) XmlName() string { 175 | return t.xmlName 176 | } 177 | 178 | func (t *TypeDef) Size() Size { 179 | return t.Old.Size() 180 | } 181 | 182 | func (t *TypeDef) Initialize(p *Protocol) { 183 | t.Old = t.Old.(*Translation).RealType(p) 184 | t.srcName = TypeSrcName(p, t) 185 | } 186 | 187 | type Event struct { 188 | srcName string 189 | xmlName string 190 | Number int 191 | NoSequence bool 192 | Fields []Field 193 | } 194 | 195 | func (e *Event) SrcName() string { 196 | return e.srcName 197 | } 198 | 199 | func (e *Event) XmlName() string { 200 | return e.xmlName 201 | } 202 | 203 | func (e *Event) Size() Size { 204 | return newExpressionSize(&Value{v: 32}, true) 205 | } 206 | 207 | func (e *Event) Initialize(p *Protocol) { 208 | e.srcName = TypeSrcName(p, e) 209 | for _, field := range e.Fields { 210 | field.Initialize(p) 211 | } 212 | } 213 | 214 | func (e *Event) EvType() string { 215 | return fmt.Sprintf("%sEvent", e.srcName) 216 | } 217 | 218 | type EventCopy struct { 219 | srcName string 220 | xmlName string 221 | Old Type 222 | Number int 223 | } 224 | 225 | func (e *EventCopy) SrcName() string { 226 | return e.srcName 227 | } 228 | 229 | func (e *EventCopy) XmlName() string { 230 | return e.xmlName 231 | } 232 | 233 | func (e *EventCopy) Size() Size { 234 | return newExpressionSize(&Value{v: 32}, true) 235 | } 236 | 237 | func (e *EventCopy) Initialize(p *Protocol) { 238 | e.srcName = TypeSrcName(p, e) 239 | e.Old = e.Old.(*Translation).RealType(p) 240 | if _, ok := e.Old.(*Event); !ok { 241 | panic("an EventCopy's old type *must* be *Event") 242 | } 243 | } 244 | 245 | func (e *EventCopy) EvType() string { 246 | return fmt.Sprintf("%sEvent", e.srcName) 247 | } 248 | 249 | type Error struct { 250 | srcName string 251 | xmlName string 252 | Number int 253 | Fields []Field 254 | } 255 | 256 | func (e *Error) SrcName() string { 257 | return e.srcName 258 | } 259 | 260 | func (e *Error) XmlName() string { 261 | return e.xmlName 262 | } 263 | 264 | func (e *Error) Size() Size { 265 | return newExpressionSize(&Value{v: 32}, true) 266 | } 267 | 268 | func (e *Error) Initialize(p *Protocol) { 269 | e.srcName = TypeSrcName(p, e) 270 | for _, field := range e.Fields { 271 | field.Initialize(p) 272 | } 273 | } 274 | 275 | func (e *Error) ErrConst() string { 276 | return fmt.Sprintf("Bad%s", e.srcName) 277 | } 278 | 279 | func (e *Error) ErrType() string { 280 | return fmt.Sprintf("%sError", e.srcName) 281 | } 282 | 283 | type ErrorCopy struct { 284 | srcName string 285 | xmlName string 286 | Old Type 287 | Number int 288 | } 289 | 290 | func (e *ErrorCopy) SrcName() string { 291 | return e.srcName 292 | } 293 | 294 | func (e *ErrorCopy) XmlName() string { 295 | return e.xmlName 296 | } 297 | 298 | func (e *ErrorCopy) Size() Size { 299 | return newExpressionSize(&Value{v: 32}, true) 300 | } 301 | 302 | func (e *ErrorCopy) Initialize(p *Protocol) { 303 | e.srcName = TypeSrcName(p, e) 304 | e.Old = e.Old.(*Translation).RealType(p) 305 | if _, ok := e.Old.(*Error); !ok { 306 | panic("an ErrorCopy's old type *must* be *Event") 307 | } 308 | } 309 | 310 | func (e *ErrorCopy) ErrConst() string { 311 | return fmt.Sprintf("Bad%s", e.srcName) 312 | } 313 | 314 | func (e *ErrorCopy) ErrType() string { 315 | return fmt.Sprintf("%sError", e.srcName) 316 | } 317 | 318 | type Struct struct { 319 | srcName string 320 | xmlName string 321 | Fields []Field 322 | } 323 | 324 | func (s *Struct) SrcName() string { 325 | return s.srcName 326 | } 327 | 328 | func (s *Struct) XmlName() string { 329 | return s.xmlName 330 | } 331 | 332 | func (s *Struct) Size() Size { 333 | size := newFixedSize(0, true) 334 | for _, field := range s.Fields { 335 | size = size.Add(field.Size()) 336 | } 337 | return size 338 | } 339 | 340 | func (s *Struct) Initialize(p *Protocol) { 341 | s.srcName = TypeSrcName(p, s) 342 | for _, field := range s.Fields { 343 | field.Initialize(p) 344 | } 345 | } 346 | 347 | // HasList returns whether there is a field in this struct that is a list. 348 | // When true, a more involved calculation is necessary to compute this struct's 349 | // size. 350 | func (s *Struct) HasList() bool { 351 | for _, field := range s.Fields { 352 | if _, ok := field.(*ListField); ok { 353 | return true 354 | } 355 | } 356 | return false 357 | } 358 | 359 | type Union struct { 360 | srcName string 361 | xmlName string 362 | Fields []Field 363 | } 364 | 365 | func (u *Union) SrcName() string { 366 | return u.srcName 367 | } 368 | 369 | func (u *Union) XmlName() string { 370 | return u.xmlName 371 | } 372 | 373 | // Size for Union is broken. At least, it's broken for XKB. 374 | // It *looks* like the protocol inherently relies on some amount of 375 | // memory unsafety, since some members of unions in XKB are *variable* in 376 | // length! The only thing I can come up with, maybe, is when a union has 377 | // variable size, simply return the raw bytes. Then it's up to the user to 378 | // pass those raw bytes into the appropriate New* constructor. GROSS! 379 | // As of now, just pluck out the first field and return that size. This 380 | // should work for union elements in randr.xml and xproto.xml. 381 | func (u *Union) Size() Size { 382 | return u.Fields[0].Size() 383 | } 384 | 385 | func (u *Union) Initialize(p *Protocol) { 386 | u.srcName = fmt.Sprintf("%sUnion", TypeSrcName(p, u)) 387 | for _, field := range u.Fields { 388 | field.Initialize(p) 389 | } 390 | } 391 | -------------------------------------------------------------------------------- /xgbgen/xml.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/xml" 5 | "io/ioutil" 6 | "log" 7 | ) 8 | 9 | type XML struct { 10 | // Root 'xcb' element properties. 11 | XMLName xml.Name `xml:"xcb"` 12 | Header string `xml:"header,attr"` 13 | ExtensionXName string `xml:"extension-xname,attr"` 14 | ExtensionName string `xml:"extension-name,attr"` 15 | MajorVersion string `xml:"major-version,attr"` 16 | MinorVersion string `xml:"minor-version,attr"` 17 | 18 | // Types for all top-level elements. 19 | // First are the simple ones. 20 | Imports XMLImports `xml:"import"` 21 | Enums []*XMLEnum `xml:"enum"` 22 | Xids []*XMLXid `xml:"xidtype"` 23 | XidUnions []*XMLXid `xml:"xidunion"` 24 | TypeDefs []*XMLTypeDef `xml:"typedef"` 25 | EventCopies []*XMLEventCopy `xml:"eventcopy"` 26 | ErrorCopies []*XMLErrorCopy `xml:"errorcopy"` 27 | 28 | // Here are the complex ones, i.e., anything with "structure contents" 29 | Structs []*XMLStruct `xml:"struct"` 30 | Unions []*XMLUnion `xml:"union"` 31 | Requests []*XMLRequest `xml:"request"` 32 | Events []*XMLEvent `xml:"event"` 33 | Errors []*XMLError `xml:"error"` 34 | } 35 | 36 | type XMLImports []*XMLImport 37 | 38 | func (imports XMLImports) Eval() { 39 | for _, imp := range imports { 40 | xmlBytes, err := ioutil.ReadFile(*protoPath + "/" + imp.Name + ".xml") 41 | if err != nil { 42 | log.Fatalf("Could not read X protocol description for import "+ 43 | "'%s' because: %s", imp.Name, err) 44 | } 45 | 46 | imp.xml = &XML{} 47 | err = xml.Unmarshal(xmlBytes, imp.xml) 48 | if err != nil { 49 | log.Fatal("Could not parse X protocol description for import "+ 50 | "'%s' because: %s", imp.Name, err) 51 | } 52 | 53 | // recursive imports... 54 | imp.xml.Imports.Eval() 55 | } 56 | } 57 | 58 | type XMLImport struct { 59 | Name string `xml:",chardata"` 60 | xml *XML `xml:"-"` 61 | } 62 | 63 | type XMLEnum struct { 64 | Name string `xml:"name,attr"` 65 | Items []*XMLEnumItem `xml:"item"` 66 | } 67 | 68 | type XMLEnumItem struct { 69 | Name string `xml:"name,attr"` 70 | Expr *XMLExpression `xml:",any"` 71 | } 72 | 73 | type XMLXid struct { 74 | XMLName xml.Name 75 | Name string `xml:"name,attr"` 76 | } 77 | 78 | type XMLTypeDef struct { 79 | Old string `xml:"oldname,attr"` 80 | New string `xml:"newname,attr"` 81 | } 82 | 83 | type XMLEventCopy struct { 84 | Name string `xml:"name,attr"` 85 | Number int `xml:"number,attr"` 86 | Ref string `xml:"ref,attr"` 87 | } 88 | 89 | type XMLErrorCopy struct { 90 | Name string `xml:"name,attr"` 91 | Number int `xml:"number,attr"` 92 | Ref string `xml:"ref,attr"` 93 | } 94 | 95 | type XMLStruct struct { 96 | Name string `xml:"name,attr"` 97 | Fields []*XMLField `xml:",any"` 98 | } 99 | 100 | type XMLUnion struct { 101 | Name string `xml:"name,attr"` 102 | Fields []*XMLField `xml:",any"` 103 | } 104 | 105 | type XMLRequest struct { 106 | Name string `xml:"name,attr"` 107 | Opcode int `xml:"opcode,attr"` 108 | Combine bool `xml:"combine-adjacent,attr"` 109 | Fields []*XMLField `xml:",any"` 110 | Reply *XMLReply `xml:"reply"` 111 | } 112 | 113 | type XMLReply struct { 114 | Fields []*XMLField `xml:",any"` 115 | } 116 | 117 | type XMLEvent struct { 118 | Name string `xml:"name,attr"` 119 | Number int `xml:"number,attr"` 120 | NoSequence bool `xml:"no-sequence-number,attr"` 121 | Fields []*XMLField `xml:",any"` 122 | } 123 | 124 | type XMLError struct { 125 | Name string `xml:"name,attr"` 126 | Number int `xml:"number,attr"` 127 | Fields []*XMLField `xml:",any"` 128 | } 129 | 130 | type XMLExpression struct { 131 | XMLName xml.Name 132 | 133 | Exprs []*XMLExpression `xml:",any"` 134 | 135 | Data string `xml:",chardata"` 136 | Op string `xml:"op,attr"` 137 | Ref string `xml:"ref,attr"` 138 | } 139 | -------------------------------------------------------------------------------- /xgbgen/xml_fields.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/xml" 5 | "log" 6 | ) 7 | 8 | type XMLField struct { 9 | XMLName xml.Name 10 | 11 | // For 'pad' element 12 | Bytes uint `xml:"bytes,attr"` 13 | 14 | // For 'field', 'list', 'localfield', 'exprfield' and 'switch' elements. 15 | Name string `xml:"name,attr"` 16 | 17 | // For 'field', 'list', 'localfield', and 'exprfield' elements. 18 | Type string `xml:"type,attr"` 19 | 20 | // For 'list', 'exprfield' and 'switch' elements. 21 | Expr *XMLExpression `xml:",any"` 22 | 23 | // For 'valueparm' element. 24 | ValueMaskType string `xml:"value-mask-type,attr"` 25 | ValueMaskName string `xml:"value-mask-name,attr"` 26 | ValueListName string `xml:"value-list-name,attr"` 27 | 28 | // For 'switch' element. 29 | Bitcases []*XMLBitcase `xml:"bitcase"` 30 | 31 | // I don't know which elements these are for. The documentation is vague. 32 | // They also seem to be completely optional. 33 | OptEnum string `xml:"enum,attr"` 34 | OptMask string `xml:"mask,attr"` 35 | OptAltEnum string `xml:"altenum,attr"` 36 | } 37 | 38 | // Bitcase represents a single expression followed by any number of fields. 39 | // Namely, if the switch's expression (all bitcases are inside a switch), 40 | // and'd with the bitcase's expression is equal to the bitcase expression, 41 | // then the fields should be included in its parent structure. 42 | // Note that since a bitcase is unique in that expressions and fields are 43 | // siblings, we must exhaustively search for one of them. Essentially, 44 | // it's the closest thing to a Union I can get to in Go without interfaces. 45 | // Would an '' tag have been too much to ask? :-( 46 | type XMLBitcase struct { 47 | Fields []*XMLField `xml:",any"` 48 | 49 | // All the different expressions. 50 | // When it comes time to choose one, use the 'Expr' method. 51 | ExprOp *XMLExpression `xml:"op"` 52 | ExprUnOp *XMLExpression `xml:"unop"` 53 | ExprField *XMLExpression `xml:"fieldref"` 54 | ExprValue *XMLExpression `xml:"value"` 55 | ExprBit *XMLExpression `xml:"bit"` 56 | ExprEnum *XMLExpression `xml:"enumref"` 57 | ExprSum *XMLExpression `xml:"sumof"` 58 | ExprPop *XMLExpression `xml:"popcount"` 59 | } 60 | 61 | // Expr chooses the only non-nil Expr* field from Bitcase. 62 | // Panic if there is more than one non-nil expression. 63 | func (b *XMLBitcase) Expr() *XMLExpression { 64 | choices := []*XMLExpression{ 65 | b.ExprOp, b.ExprUnOp, b.ExprField, b.ExprValue, 66 | b.ExprBit, b.ExprEnum, b.ExprSum, b.ExprPop, 67 | } 68 | 69 | var choice *XMLExpression = nil 70 | numNonNil := 0 71 | for _, c := range choices { 72 | if c != nil { 73 | numNonNil++ 74 | choice = c 75 | } 76 | } 77 | 78 | if choice == nil { 79 | log.Panicf("No top level expression found in a bitcase.") 80 | } 81 | if numNonNil > 1 { 82 | log.Panicf("More than one top-level expression was found in a bitcase.") 83 | } 84 | return choice 85 | } 86 | -------------------------------------------------------------------------------- /xproto/xproto_test.go: -------------------------------------------------------------------------------- 1 | package xproto 2 | 3 | /* 4 | Tests for XGB. 5 | 6 | These tests only test the core X protocol at the moment. It isn't even 7 | close to complete coverage (and probably never will be), but it does test 8 | a number of different corners: requests with no replies, requests without 9 | replies, checked (i.e., synchronous) errors, unchecked (i.e., asynchronous) 10 | errors, and sequence number wrapping. 11 | 12 | There are also a couple of benchmarks that show the difference between 13 | correctly issuing lots of requests and gathering replies and 14 | incorrectly doing the same. (This particular difference is one of the 15 | claimed advantages of the XCB, and therefore XGB, family.) 16 | 17 | In sum, these tests are more focused on testing the core xgb package itself, 18 | rather than whether xproto has properly implemented the core X client 19 | protocol. 20 | */ 21 | 22 | import ( 23 | "fmt" 24 | "log" 25 | "math/rand" 26 | "testing" 27 | "time" 28 | 29 | "github.com/BurntSushi/xgb" 30 | ) 31 | 32 | // The X connection used throughout testing. 33 | var X *xgb.Conn 34 | 35 | // init initializes the X connection, seeds the RNG and starts waiting 36 | // for events. 37 | func init() { 38 | var err error 39 | 40 | X, err = xgb.NewConn() 41 | if err != nil { 42 | log.Fatal(err) 43 | } 44 | 45 | rand.Seed(time.Now().UnixNano()) 46 | 47 | go grabEvents() 48 | } 49 | 50 | /******************************************************************************/ 51 | // Tests 52 | /******************************************************************************/ 53 | 54 | // TestSynchronousError purposefully causes a BadWindow error in a 55 | // MapWindow request, and checks it synchronously. 56 | func TestSynchronousError(t *testing.T) { 57 | err := MapWindowChecked(X, 0).Check() // resource 0 is always invalid 58 | if err == nil { 59 | t.Fatalf("MapWindow: A MapWindow request that should return an " + 60 | "error has returned a nil error.") 61 | } 62 | verifyMapWindowError(t, err) 63 | } 64 | 65 | // TestAsynchronousError does the same thing as TestSynchronousError, but 66 | // grabs the error asynchronously instead. 67 | func TestAsynchronousError(t *testing.T) { 68 | MapWindow(X, 0) // resource id 0 is always invalid 69 | 70 | evOrErr := waitForEvent(t, 5) 71 | if evOrErr.ev != nil { 72 | t.Fatalf("After issuing an erroneous MapWindow request, we have "+ 73 | "received an event rather than an error: %s", evOrErr.ev) 74 | } 75 | verifyMapWindowError(t, evOrErr.err) 76 | } 77 | 78 | // TestCookieBuffer issues (2^16) + n requets *without* replies to guarantee 79 | // that the sequence number wraps and that the cookie buffer will have to 80 | // flush itself (since there are no replies coming in to flush it). 81 | // And just like TestSequenceWrap, we issue another request with a reply 82 | // at the end to make sure XGB is still working properly. 83 | func TestCookieBuffer(t *testing.T) { 84 | n := (1 << 16) + 10 85 | for i := 0; i < n; i++ { 86 | NoOperation(X) 87 | } 88 | TestProperty(t) 89 | } 90 | 91 | // TestSequenceWrap issues (2^16) + n requests w/ replies to guarantee that the 92 | // sequence number (which is a 16 bit integer) will wrap. It then issues one 93 | // final request to ensure things still work properly. 94 | func TestSequenceWrap(t *testing.T) { 95 | n := (1 << 16) + 10 96 | for i := 0; i < n; i++ { 97 | _, err := InternAtom(X, false, 5, "RANDO").Reply() 98 | if err != nil { 99 | t.Fatalf("InternAtom: %s", err) 100 | } 101 | } 102 | TestProperty(t) 103 | } 104 | 105 | // TestProperty tests whether a random value can be set and read. 106 | func TestProperty(t *testing.T) { 107 | propName := randString(20) // whatevs 108 | writeVal := randString(20) 109 | readVal, err := changeAndGetProp(propName, writeVal) 110 | if err != nil { 111 | t.Error(err) 112 | } 113 | 114 | if readVal != writeVal { 115 | t.Errorf("The value written, '%s', is not the same as the "+ 116 | "value read '%s'.", writeVal, readVal) 117 | } 118 | } 119 | 120 | // TestWindowEvents creates a window, maps it, listens for configure notify 121 | // events, issues a configure request, and checks for the appropriate 122 | // configure notify event. 123 | // This probably violates the notion of "test one thing and test it well," 124 | // but testing X stuff is unique since it involves so much state. 125 | // Each request is checked to make sure there are no errors returned. If there 126 | // is an error, the test is failed. 127 | // You may see a window appear quickly and then disappear. Do not be alarmed :P 128 | // It's possible that this test will yield a false negative because we cannot 129 | // control our environment. That is, the window manager could override the 130 | // placement set. However, we set override redirect on the window, so the 131 | // window manager *shouldn't* touch our window if it is well-behaved. 132 | func TestWindowEvents(t *testing.T) { 133 | // The geometry to set the window. 134 | gx, gy, gw, gh := 200, 400, 1000, 300 135 | 136 | wid, err := NewWindowId(X) 137 | if err != nil { 138 | t.Fatalf("NewId: %s", err) 139 | } 140 | 141 | screen := Setup(X).DefaultScreen(X) // alias 142 | err = CreateWindowChecked(X, screen.RootDepth, wid, screen.Root, 143 | 0, 0, 500, 500, 0, 144 | WindowClassInputOutput, screen.RootVisual, 145 | CwBackPixel|CwOverrideRedirect, []uint32{0xffffffff, 1}).Check() 146 | if err != nil { 147 | t.Fatalf("CreateWindow: %s", err) 148 | } 149 | 150 | err = MapWindowChecked(X, wid).Check() 151 | if err != nil { 152 | t.Fatalf("MapWindow: %s", err) 153 | } 154 | 155 | // We don't listen in the CreateWindow request so that we don't get 156 | // a MapNotify event. 157 | err = ChangeWindowAttributesChecked(X, wid, 158 | CwEventMask, []uint32{EventMaskStructureNotify}).Check() 159 | if err != nil { 160 | t.Fatalf("ChangeWindowAttributes: %s", err) 161 | } 162 | 163 | err = ConfigureWindowChecked(X, wid, 164 | ConfigWindowX|ConfigWindowY| 165 | ConfigWindowWidth|ConfigWindowHeight, 166 | []uint32{uint32(gx), uint32(gy), uint32(gw), uint32(gh)}).Check() 167 | if err != nil { 168 | t.Fatalf("ConfigureWindow: %s", err) 169 | } 170 | 171 | evOrErr := waitForEvent(t, 5) 172 | switch event := evOrErr.ev.(type) { 173 | case ConfigureNotifyEvent: 174 | if event.X != int16(gx) { 175 | t.Fatalf("x was set to %d but ConfigureNotify reports %d", 176 | gx, event.X) 177 | } 178 | if event.Y != int16(gy) { 179 | t.Fatalf("y was set to %d but ConfigureNotify reports %d", 180 | gy, event.Y) 181 | } 182 | if event.Width != uint16(gw) { 183 | t.Fatalf("width was set to %d but ConfigureNotify reports %d", 184 | gw, event.Width) 185 | } 186 | if event.Height != uint16(gh) { 187 | t.Fatalf("height was set to %d but ConfigureNotify reports %d", 188 | gh, event.Height) 189 | } 190 | default: 191 | t.Fatalf("Expected a ConfigureNotifyEvent but got %T instead.", event) 192 | } 193 | 194 | // Okay, clean up! 195 | err = ChangeWindowAttributesChecked(X, wid, 196 | CwEventMask, []uint32{0}).Check() 197 | if err != nil { 198 | t.Fatalf("ChangeWindowAttributes: %s", err) 199 | } 200 | 201 | err = DestroyWindowChecked(X, wid).Check() 202 | if err != nil { 203 | t.Fatalf("DestroyWindow: %s", err) 204 | } 205 | } 206 | 207 | // Calls GetFontPath function, Issue #12 208 | func TestGetFontPath(t *testing.T) { 209 | fontPathReply, err := GetFontPath(X).Reply() 210 | if err != nil { 211 | t.Fatalf("GetFontPath: %v", err) 212 | } 213 | _ = fontPathReply 214 | } 215 | 216 | func TestListFonts(t *testing.T) { 217 | listFontsReply, err := ListFonts(X, 10, 1, "*").Reply() 218 | if err != nil { 219 | t.Fatalf("ListFonts: %v", err) 220 | } 221 | _ = listFontsReply 222 | } 223 | 224 | /******************************************************************************/ 225 | // Benchmarks 226 | /******************************************************************************/ 227 | 228 | // BenchmarkInternAtomsGood shows how many requests with replies 229 | // *should* be sent and gathered from the server. Namely, send as many 230 | // requests as you can at once, then go back and gather up all the replies. 231 | // More importantly, this approach can exploit parallelism when 232 | // GOMAXPROCS > 1. 233 | // Run with `go test -run 'nomatch' -bench '.*' -cpu 1,2,6` if you have 234 | // multiple cores to see the improvement that parallelism brings. 235 | func BenchmarkInternAtomsGood(b *testing.B) { 236 | b.StopTimer() 237 | names := seqNames(b.N) 238 | 239 | b.StartTimer() 240 | cookies := make([]InternAtomCookie, b.N) 241 | for i := 0; i < b.N; i++ { 242 | cookies[i] = InternAtom(X, false, uint16(len(names[i])), names[i]) 243 | } 244 | for _, cookie := range cookies { 245 | cookie.Reply() 246 | } 247 | } 248 | 249 | // BenchmarkInternAtomsBad shows how *not* to issue a lot of requests with 250 | // replies. Namely, each subsequent request isn't issued *until* the last 251 | // reply is made. This implies a round trip to the X server for every 252 | // iteration. 253 | func BenchmarkInternAtomsPoor(b *testing.B) { 254 | b.StopTimer() 255 | names := seqNames(b.N) 256 | 257 | b.StartTimer() 258 | for i := 0; i < b.N; i++ { 259 | InternAtom(X, false, uint16(len(names[i])), names[i]).Reply() 260 | } 261 | } 262 | 263 | /******************************************************************************/ 264 | // Helper functions 265 | /******************************************************************************/ 266 | 267 | // changeAndGetProp sets property 'prop' with value 'val'. 268 | // It then gets the value of that property and returns it. 269 | // (It's used to check that the 'val' going in is the same 'val' going out.) 270 | // It tests both requests with and without replies (GetProperty and 271 | // ChangeProperty respectively.) 272 | func changeAndGetProp(prop, val string) (string, error) { 273 | setup := Setup(X) 274 | root := setup.DefaultScreen(X).Root 275 | 276 | propAtom, err := InternAtom(X, false, uint16(len(prop)), prop).Reply() 277 | if err != nil { 278 | return "", fmt.Errorf("InternAtom: %s", err) 279 | } 280 | 281 | typName := "UTF8_STRING" 282 | typAtom, err := InternAtom(X, false, uint16(len(typName)), typName).Reply() 283 | if err != nil { 284 | return "", fmt.Errorf("InternAtom: %s", err) 285 | } 286 | 287 | err = ChangePropertyChecked(X, PropModeReplace, root, propAtom.Atom, 288 | typAtom.Atom, 8, uint32(len(val)), []byte(val)).Check() 289 | if err != nil { 290 | return "", fmt.Errorf("ChangeProperty: %s", err) 291 | } 292 | 293 | reply, err := GetProperty(X, false, root, propAtom.Atom, 294 | GetPropertyTypeAny, 0, (1<<32)-1).Reply() 295 | if err != nil { 296 | return "", fmt.Errorf("GetProperty: %s", err) 297 | } 298 | if reply.Format != 8 { 299 | return "", fmt.Errorf("Property reply format is %d but it should be 8.", 300 | reply.Format) 301 | } 302 | 303 | return string(reply.Value), nil 304 | } 305 | 306 | // verifyMapWindowError takes an error that is returned with an invalid 307 | // MapWindow request with a window Id of 0 and makes sure the error is the 308 | // right type and contains the correct values. 309 | func verifyMapWindowError(t *testing.T, err error) { 310 | switch e := err.(type) { 311 | case WindowError: 312 | if e.BadValue != 0 { 313 | t.Fatalf("WindowError should report a bad value of 0 but "+ 314 | "it reports %d instead.", e.BadValue) 315 | } 316 | if e.MajorOpcode != 8 { 317 | t.Fatalf("WindowError should report a major opcode of 8 "+ 318 | "(which is a MapWindow request), but it reports %d instead.", 319 | e.MajorOpcode) 320 | } 321 | default: 322 | t.Fatalf("Expected a WindowError but got %T instead.", e) 323 | } 324 | } 325 | 326 | // randString generates a random string of length n. 327 | func randString(n int) string { 328 | byts := make([]byte, n) 329 | for i := 0; i < n; i++ { 330 | rando := rand.Intn(53) 331 | switch { 332 | case rando <= 25: 333 | byts[i] = byte(65 + rando) 334 | case rando <= 51: 335 | byts[i] = byte(97 + rando - 26) 336 | default: 337 | byts[i] = ' ' 338 | } 339 | } 340 | return string(byts) 341 | } 342 | 343 | // seqNames creates a slice of NAME0, NAME1, ..., NAMEN. 344 | func seqNames(n int) []string { 345 | names := make([]string, n) 346 | for i := range names { 347 | names[i] = fmt.Sprintf("NAME%d", i) 348 | } 349 | return names 350 | } 351 | 352 | // evErr represents a value that is either an event or an error. 353 | type evErr struct { 354 | ev xgb.Event 355 | err xgb.Error 356 | } 357 | 358 | // channel used to pass evErrs. 359 | var evOrErrChan = make(chan evErr, 0) 360 | 361 | // grabEvents is a goroutine that reads events off the wire. 362 | // We used this instead of WaitForEvent directly in our tests so that 363 | // we can timeout and fail a test. 364 | func grabEvents() { 365 | for { 366 | ev, err := X.WaitForEvent() 367 | evOrErrChan <- evErr{ev, err} 368 | } 369 | } 370 | 371 | // waitForEvent asks the evOrErrChan channel for an event. 372 | // If it doesn't get an event in 'n' seconds, the current test is failed. 373 | func waitForEvent(t *testing.T, n int) evErr { 374 | var evOrErr evErr 375 | 376 | select { 377 | case evOrErr = <-evOrErrChan: 378 | case <-time.After(time.Second * 5): 379 | t.Fatalf("After waiting 5 seconds for an event or an error, " + 380 | "we have timed out.") 381 | } 382 | 383 | return evOrErr 384 | } 385 | -------------------------------------------------------------------------------- /xtest/xtest.go: -------------------------------------------------------------------------------- 1 | // Package xtest is the X client API for the XTEST extension. 2 | package xtest 3 | 4 | // This file is automatically generated from xtest.xml. Edit at your peril! 5 | 6 | import ( 7 | "github.com/BurntSushi/xgb" 8 | 9 | "github.com/BurntSushi/xgb/xproto" 10 | ) 11 | 12 | // Init must be called before using the XTEST extension. 13 | func Init(c *xgb.Conn) error { 14 | reply, err := xproto.QueryExtension(c, 5, "XTEST").Reply() 15 | switch { 16 | case err != nil: 17 | return err 18 | case !reply.Present: 19 | return xgb.Errorf("No extension named XTEST could be found on on the server.") 20 | } 21 | 22 | c.ExtLock.Lock() 23 | c.Extensions["XTEST"] = reply.MajorOpcode 24 | c.ExtLock.Unlock() 25 | for evNum, fun := range xgb.NewExtEventFuncs["XTEST"] { 26 | xgb.NewEventFuncs[int(reply.FirstEvent)+evNum] = fun 27 | } 28 | for errNum, fun := range xgb.NewExtErrorFuncs["XTEST"] { 29 | xgb.NewErrorFuncs[int(reply.FirstError)+errNum] = fun 30 | } 31 | return nil 32 | } 33 | 34 | func init() { 35 | xgb.NewExtEventFuncs["XTEST"] = make(map[int]xgb.NewEventFun) 36 | xgb.NewExtErrorFuncs["XTEST"] = make(map[int]xgb.NewErrorFun) 37 | } 38 | 39 | const ( 40 | CursorNone = 0 41 | CursorCurrent = 1 42 | ) 43 | 44 | // Skipping definition for base type 'Bool' 45 | 46 | // Skipping definition for base type 'Byte' 47 | 48 | // Skipping definition for base type 'Card8' 49 | 50 | // Skipping definition for base type 'Char' 51 | 52 | // Skipping definition for base type 'Void' 53 | 54 | // Skipping definition for base type 'Double' 55 | 56 | // Skipping definition for base type 'Float' 57 | 58 | // Skipping definition for base type 'Int16' 59 | 60 | // Skipping definition for base type 'Int32' 61 | 62 | // Skipping definition for base type 'Int8' 63 | 64 | // Skipping definition for base type 'Card16' 65 | 66 | // Skipping definition for base type 'Card32' 67 | 68 | // CompareCursorCookie is a cookie used only for CompareCursor requests. 69 | type CompareCursorCookie struct { 70 | *xgb.Cookie 71 | } 72 | 73 | // CompareCursor sends a checked request. 74 | // If an error occurs, it will be returned with the reply by calling CompareCursorCookie.Reply() 75 | func CompareCursor(c *xgb.Conn, Window xproto.Window, Cursor xproto.Cursor) CompareCursorCookie { 76 | c.ExtLock.RLock() 77 | defer c.ExtLock.RUnlock() 78 | if _, ok := c.Extensions["XTEST"]; !ok { 79 | panic("Cannot issue request 'CompareCursor' using the uninitialized extension 'XTEST'. xtest.Init(connObj) must be called first.") 80 | } 81 | cookie := c.NewCookie(true, true) 82 | c.NewRequest(compareCursorRequest(c, Window, Cursor), cookie) 83 | return CompareCursorCookie{cookie} 84 | } 85 | 86 | // CompareCursorUnchecked sends an unchecked request. 87 | // If an error occurs, it can only be retrieved using xgb.WaitForEvent or xgb.PollForEvent. 88 | func CompareCursorUnchecked(c *xgb.Conn, Window xproto.Window, Cursor xproto.Cursor) CompareCursorCookie { 89 | c.ExtLock.RLock() 90 | defer c.ExtLock.RUnlock() 91 | if _, ok := c.Extensions["XTEST"]; !ok { 92 | panic("Cannot issue request 'CompareCursor' using the uninitialized extension 'XTEST'. xtest.Init(connObj) must be called first.") 93 | } 94 | cookie := c.NewCookie(false, true) 95 | c.NewRequest(compareCursorRequest(c, Window, Cursor), cookie) 96 | return CompareCursorCookie{cookie} 97 | } 98 | 99 | // CompareCursorReply represents the data returned from a CompareCursor request. 100 | type CompareCursorReply struct { 101 | Sequence uint16 // sequence number of the request for this reply 102 | Length uint32 // number of bytes in this reply 103 | Same bool 104 | } 105 | 106 | // Reply blocks and returns the reply data for a CompareCursor request. 107 | func (cook CompareCursorCookie) Reply() (*CompareCursorReply, error) { 108 | buf, err := cook.Cookie.Reply() 109 | if err != nil { 110 | return nil, err 111 | } 112 | if buf == nil { 113 | return nil, nil 114 | } 115 | return compareCursorReply(buf), nil 116 | } 117 | 118 | // compareCursorReply reads a byte slice into a CompareCursorReply value. 119 | func compareCursorReply(buf []byte) *CompareCursorReply { 120 | v := new(CompareCursorReply) 121 | b := 1 // skip reply determinant 122 | 123 | if buf[b] == 1 { 124 | v.Same = true 125 | } else { 126 | v.Same = false 127 | } 128 | b += 1 129 | 130 | v.Sequence = xgb.Get16(buf[b:]) 131 | b += 2 132 | 133 | v.Length = xgb.Get32(buf[b:]) // 4-byte units 134 | b += 4 135 | 136 | return v 137 | } 138 | 139 | // Write request to wire for CompareCursor 140 | // compareCursorRequest writes a CompareCursor request to a byte slice. 141 | func compareCursorRequest(c *xgb.Conn, Window xproto.Window, Cursor xproto.Cursor) []byte { 142 | size := 12 143 | b := 0 144 | buf := make([]byte, size) 145 | 146 | c.ExtLock.RLock() 147 | buf[b] = c.Extensions["XTEST"] 148 | c.ExtLock.RUnlock() 149 | b += 1 150 | 151 | buf[b] = 1 // request opcode 152 | b += 1 153 | 154 | xgb.Put16(buf[b:], uint16(size/4)) // write request size in 4-byte units 155 | b += 2 156 | 157 | xgb.Put32(buf[b:], uint32(Window)) 158 | b += 4 159 | 160 | xgb.Put32(buf[b:], uint32(Cursor)) 161 | b += 4 162 | 163 | return buf 164 | } 165 | 166 | // FakeInputCookie is a cookie used only for FakeInput requests. 167 | type FakeInputCookie struct { 168 | *xgb.Cookie 169 | } 170 | 171 | // FakeInput sends an unchecked request. 172 | // If an error occurs, it can only be retrieved using xgb.WaitForEvent or xgb.PollForEvent. 173 | func FakeInput(c *xgb.Conn, Type byte, Detail byte, Time uint32, Root xproto.Window, RootX int16, RootY int16, Deviceid byte) FakeInputCookie { 174 | c.ExtLock.RLock() 175 | defer c.ExtLock.RUnlock() 176 | if _, ok := c.Extensions["XTEST"]; !ok { 177 | panic("Cannot issue request 'FakeInput' using the uninitialized extension 'XTEST'. xtest.Init(connObj) must be called first.") 178 | } 179 | cookie := c.NewCookie(false, false) 180 | c.NewRequest(fakeInputRequest(c, Type, Detail, Time, Root, RootX, RootY, Deviceid), cookie) 181 | return FakeInputCookie{cookie} 182 | } 183 | 184 | // FakeInputChecked sends a checked request. 185 | // If an error occurs, it can be retrieved using FakeInputCookie.Check() 186 | func FakeInputChecked(c *xgb.Conn, Type byte, Detail byte, Time uint32, Root xproto.Window, RootX int16, RootY int16, Deviceid byte) FakeInputCookie { 187 | c.ExtLock.RLock() 188 | defer c.ExtLock.RUnlock() 189 | if _, ok := c.Extensions["XTEST"]; !ok { 190 | panic("Cannot issue request 'FakeInput' using the uninitialized extension 'XTEST'. xtest.Init(connObj) must be called first.") 191 | } 192 | cookie := c.NewCookie(true, false) 193 | c.NewRequest(fakeInputRequest(c, Type, Detail, Time, Root, RootX, RootY, Deviceid), cookie) 194 | return FakeInputCookie{cookie} 195 | } 196 | 197 | // Check returns an error if one occurred for checked requests that are not expecting a reply. 198 | // This cannot be called for requests expecting a reply, nor for unchecked requests. 199 | func (cook FakeInputCookie) Check() error { 200 | return cook.Cookie.Check() 201 | } 202 | 203 | // Write request to wire for FakeInput 204 | // fakeInputRequest writes a FakeInput request to a byte slice. 205 | func fakeInputRequest(c *xgb.Conn, Type byte, Detail byte, Time uint32, Root xproto.Window, RootX int16, RootY int16, Deviceid byte) []byte { 206 | size := 36 207 | b := 0 208 | buf := make([]byte, size) 209 | 210 | c.ExtLock.RLock() 211 | buf[b] = c.Extensions["XTEST"] 212 | c.ExtLock.RUnlock() 213 | b += 1 214 | 215 | buf[b] = 2 // request opcode 216 | b += 1 217 | 218 | xgb.Put16(buf[b:], uint16(size/4)) // write request size in 4-byte units 219 | b += 2 220 | 221 | buf[b] = Type 222 | b += 1 223 | 224 | buf[b] = Detail 225 | b += 1 226 | 227 | b += 2 // padding 228 | 229 | xgb.Put32(buf[b:], Time) 230 | b += 4 231 | 232 | xgb.Put32(buf[b:], uint32(Root)) 233 | b += 4 234 | 235 | b += 8 // padding 236 | 237 | xgb.Put16(buf[b:], uint16(RootX)) 238 | b += 2 239 | 240 | xgb.Put16(buf[b:], uint16(RootY)) 241 | b += 2 242 | 243 | b += 7 // padding 244 | 245 | buf[b] = Deviceid 246 | b += 1 247 | 248 | return buf 249 | } 250 | 251 | // GetVersionCookie is a cookie used only for GetVersion requests. 252 | type GetVersionCookie struct { 253 | *xgb.Cookie 254 | } 255 | 256 | // GetVersion sends a checked request. 257 | // If an error occurs, it will be returned with the reply by calling GetVersionCookie.Reply() 258 | func GetVersion(c *xgb.Conn, MajorVersion byte, MinorVersion uint16) GetVersionCookie { 259 | c.ExtLock.RLock() 260 | defer c.ExtLock.RUnlock() 261 | if _, ok := c.Extensions["XTEST"]; !ok { 262 | panic("Cannot issue request 'GetVersion' using the uninitialized extension 'XTEST'. xtest.Init(connObj) must be called first.") 263 | } 264 | cookie := c.NewCookie(true, true) 265 | c.NewRequest(getVersionRequest(c, MajorVersion, MinorVersion), cookie) 266 | return GetVersionCookie{cookie} 267 | } 268 | 269 | // GetVersionUnchecked sends an unchecked request. 270 | // If an error occurs, it can only be retrieved using xgb.WaitForEvent or xgb.PollForEvent. 271 | func GetVersionUnchecked(c *xgb.Conn, MajorVersion byte, MinorVersion uint16) GetVersionCookie { 272 | c.ExtLock.RLock() 273 | defer c.ExtLock.RUnlock() 274 | if _, ok := c.Extensions["XTEST"]; !ok { 275 | panic("Cannot issue request 'GetVersion' using the uninitialized extension 'XTEST'. xtest.Init(connObj) must be called first.") 276 | } 277 | cookie := c.NewCookie(false, true) 278 | c.NewRequest(getVersionRequest(c, MajorVersion, MinorVersion), cookie) 279 | return GetVersionCookie{cookie} 280 | } 281 | 282 | // GetVersionReply represents the data returned from a GetVersion request. 283 | type GetVersionReply struct { 284 | Sequence uint16 // sequence number of the request for this reply 285 | Length uint32 // number of bytes in this reply 286 | MajorVersion byte 287 | MinorVersion uint16 288 | } 289 | 290 | // Reply blocks and returns the reply data for a GetVersion request. 291 | func (cook GetVersionCookie) Reply() (*GetVersionReply, error) { 292 | buf, err := cook.Cookie.Reply() 293 | if err != nil { 294 | return nil, err 295 | } 296 | if buf == nil { 297 | return nil, nil 298 | } 299 | return getVersionReply(buf), nil 300 | } 301 | 302 | // getVersionReply reads a byte slice into a GetVersionReply value. 303 | func getVersionReply(buf []byte) *GetVersionReply { 304 | v := new(GetVersionReply) 305 | b := 1 // skip reply determinant 306 | 307 | v.MajorVersion = buf[b] 308 | b += 1 309 | 310 | v.Sequence = xgb.Get16(buf[b:]) 311 | b += 2 312 | 313 | v.Length = xgb.Get32(buf[b:]) // 4-byte units 314 | b += 4 315 | 316 | v.MinorVersion = xgb.Get16(buf[b:]) 317 | b += 2 318 | 319 | return v 320 | } 321 | 322 | // Write request to wire for GetVersion 323 | // getVersionRequest writes a GetVersion request to a byte slice. 324 | func getVersionRequest(c *xgb.Conn, MajorVersion byte, MinorVersion uint16) []byte { 325 | size := 8 326 | b := 0 327 | buf := make([]byte, size) 328 | 329 | c.ExtLock.RLock() 330 | buf[b] = c.Extensions["XTEST"] 331 | c.ExtLock.RUnlock() 332 | b += 1 333 | 334 | buf[b] = 0 // request opcode 335 | b += 1 336 | 337 | xgb.Put16(buf[b:], uint16(size/4)) // write request size in 4-byte units 338 | b += 2 339 | 340 | buf[b] = MajorVersion 341 | b += 1 342 | 343 | b += 1 // padding 344 | 345 | xgb.Put16(buf[b:], MinorVersion) 346 | b += 2 347 | 348 | return buf 349 | } 350 | 351 | // GrabControlCookie is a cookie used only for GrabControl requests. 352 | type GrabControlCookie struct { 353 | *xgb.Cookie 354 | } 355 | 356 | // GrabControl sends an unchecked request. 357 | // If an error occurs, it can only be retrieved using xgb.WaitForEvent or xgb.PollForEvent. 358 | func GrabControl(c *xgb.Conn, Impervious bool) GrabControlCookie { 359 | c.ExtLock.RLock() 360 | defer c.ExtLock.RUnlock() 361 | if _, ok := c.Extensions["XTEST"]; !ok { 362 | panic("Cannot issue request 'GrabControl' using the uninitialized extension 'XTEST'. xtest.Init(connObj) must be called first.") 363 | } 364 | cookie := c.NewCookie(false, false) 365 | c.NewRequest(grabControlRequest(c, Impervious), cookie) 366 | return GrabControlCookie{cookie} 367 | } 368 | 369 | // GrabControlChecked sends a checked request. 370 | // If an error occurs, it can be retrieved using GrabControlCookie.Check() 371 | func GrabControlChecked(c *xgb.Conn, Impervious bool) GrabControlCookie { 372 | c.ExtLock.RLock() 373 | defer c.ExtLock.RUnlock() 374 | if _, ok := c.Extensions["XTEST"]; !ok { 375 | panic("Cannot issue request 'GrabControl' using the uninitialized extension 'XTEST'. xtest.Init(connObj) must be called first.") 376 | } 377 | cookie := c.NewCookie(true, false) 378 | c.NewRequest(grabControlRequest(c, Impervious), cookie) 379 | return GrabControlCookie{cookie} 380 | } 381 | 382 | // Check returns an error if one occurred for checked requests that are not expecting a reply. 383 | // This cannot be called for requests expecting a reply, nor for unchecked requests. 384 | func (cook GrabControlCookie) Check() error { 385 | return cook.Cookie.Check() 386 | } 387 | 388 | // Write request to wire for GrabControl 389 | // grabControlRequest writes a GrabControl request to a byte slice. 390 | func grabControlRequest(c *xgb.Conn, Impervious bool) []byte { 391 | size := 8 392 | b := 0 393 | buf := make([]byte, size) 394 | 395 | c.ExtLock.RLock() 396 | buf[b] = c.Extensions["XTEST"] 397 | c.ExtLock.RUnlock() 398 | b += 1 399 | 400 | buf[b] = 3 // request opcode 401 | b += 1 402 | 403 | xgb.Put16(buf[b:], uint16(size/4)) // write request size in 4-byte units 404 | b += 2 405 | 406 | if Impervious { 407 | buf[b] = 1 408 | } else { 409 | buf[b] = 0 410 | } 411 | b += 1 412 | 413 | b += 3 // padding 414 | 415 | return buf 416 | } 417 | --------------------------------------------------------------------------------