aboutsummaryrefslogtreecommitdiff
path: root/nexgb
diff options
context:
space:
mode:
Diffstat (limited to 'nexgb')
-rw-r--r--nexgb/AUTHORS18
-rw-r--r--nexgb/CONTRIBUTORS39
-rw-r--r--nexgb/LICENSE42
-rw-r--r--nexgb/Makefile19
-rw-r--r--nexgb/README33
-rw-r--r--nexgb/auth.go111
-rw-r--r--nexgb/xgb.go484
-rw-r--r--nexgb/xgb_help.go103
-rw-r--r--nexgb/xgbgen/context.go89
-rw-r--r--nexgb/xgbgen/go.go255
-rw-r--r--nexgb/xgbgen/main.go64
-rw-r--r--nexgb/xgbgen/misc.go44
-rwxr-xr-xnexgb/xgbgen/xgbgenbin0 -> 2318165 bytes
-rw-r--r--nexgb/xgbgen/xml.go298
-rw-r--r--nexgb/xgbgen/xml_expression.go160
-rw-r--r--nexgb/xgbgen/xml_fields.go147
16 files changed, 1906 insertions, 0 deletions
diff --git a/nexgb/AUTHORS b/nexgb/AUTHORS
new file mode 100644
index 0000000..08fc0cd
--- /dev/null
+++ b/nexgb/AUTHORS
@@ -0,0 +1,18 @@
+Andrew Gallant is the maintainer of this fork. What follows is the original
+list of authors for the x-go-binding.
+
+# This is the official list of XGB authors for copyright purposes.
+# This file is distinct from the CONTRIBUTORS files.
+# See the latter for an explanation.
+
+# Names should be added to this file as
+# Name or Organization <email address>
+# The email address is not required for organizations.
+
+# Please keep the list sorted.
+
+Anthony Martin <ality@pbrane.org>
+Firmansyah Adiputra <frm.adiputra@gmail.com>
+Google Inc.
+Scott Lawrence <bytbox@gmail.com>
+Tor Andersson <tor.andersson@gmail.com>
diff --git a/nexgb/CONTRIBUTORS b/nexgb/CONTRIBUTORS
new file mode 100644
index 0000000..46dc4b0
--- /dev/null
+++ b/nexgb/CONTRIBUTORS
@@ -0,0 +1,39 @@
+Andrew Gallant is the maintainer of this fork. What follows is the original
+list of contributors for the x-go-binding.
+
+# This is the official list of people who can contribute
+# (and typically have contributed) code to the XGB repository.
+# The AUTHORS file lists the copyright holders; this file
+# lists people. For example, Google employees are listed here
+# but not in AUTHORS, because Google holds the copyright.
+#
+# The submission process automatically checks to make sure
+# that people submitting code are listed in this file (by email address).
+#
+# Names should be added to this file only after verifying that
+# the individual or the individual's organization has agreed to
+# the appropriate Contributor License Agreement, found here:
+#
+# http://code.google.com/legal/individual-cla-v1.0.html
+# http://code.google.com/legal/corporate-cla-v1.0.html
+#
+# The agreement for individuals can be filled out on the web.
+#
+# When adding J Random Contributor's name to this file,
+# either J's name or J's organization's name should be
+# added to the AUTHORS file, depending on whether the
+# individual or corporate CLA was used.
+
+# Names should be added to this file like so:
+# Name <email address>
+
+# Please keep the list sorted.
+
+Anthony Martin <ality@pbrane.org>
+Firmansyah Adiputra <frm.adiputra@gmail.com>
+Ian Lance Taylor <iant@golang.org>
+Nigel Tao <nigeltao@golang.org>
+Robert Griesemer <gri@golang.org>
+Russ Cox <rsc@golang.org>
+Scott Lawrence <bytbox@gmail.com>
+Tor Andersson <tor.andersson@gmail.com>
diff --git a/nexgb/LICENSE b/nexgb/LICENSE
new file mode 100644
index 0000000..d99cd90
--- /dev/null
+++ b/nexgb/LICENSE
@@ -0,0 +1,42 @@
+// Copyright (c) 2009 The XGB Authors. All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+//
+// Subject to the terms and conditions of this License, Google hereby
+// grants to You a perpetual, worldwide, non-exclusive, no-charge,
+// royalty-free, irrevocable (except as stated in this section) patent
+// license to make, have made, use, offer to sell, sell, import, and
+// otherwise transfer this implementation of XGB, where such license
+// applies only to those patent claims licensable by Google that are
+// necessarily infringed by use of this implementation of XGB. If You
+// institute patent litigation against any entity (including a
+// cross-claim or counterclaim in a lawsuit) alleging that this
+// implementation of XGB or a Contribution incorporated within this
+// implementation of XGB constitutes direct or contributory patent
+// infringement, then any patent licenses granted to You under this
+// License for this implementation of XGB shall terminate as of the date
+// such litigation is filed.
diff --git a/nexgb/Makefile b/nexgb/Makefile
new file mode 100644
index 0000000..041d20c
--- /dev/null
+++ b/nexgb/Makefile
@@ -0,0 +1,19 @@
+XPROTO=/usr/share/xcb
+all: xproto xinerama
+
+xproto:
+ python2 go_client.py $(XPROTO)/xproto.xml
+ gofmt -w xproto.go
+
+xinerama:
+ python2 go_client.py $(XPROTO)/xinerama.xml
+ gofmt -w xinerama.go
+
+randr:
+ python2 go_client.py $(XPROTO)/randr.xml
+ gofmt -w randr.go
+
+render:
+ python2 go_client.py $(XPROTO)/render.xml
+ gofmt -w render.go
+
diff --git a/nexgb/README b/nexgb/README
new file mode 100644
index 0000000..f659e32
--- /dev/null
+++ b/nexgb/README
@@ -0,0 +1,33 @@
+BurntSushi's Fork
+=================
+I've forked the XGB repository from Google Code due to inactivty upstream.
+
+Much of the code has been rewritten in an effort to support thread safety
+and multiple extensions. Namely, go_client.py has been thrown away in favor
+of an xgbgen package.
+
+The biggest parts that *haven't* been rewritten by me are the connection and
+authentication handshakes. They're inherently messy, and there's really no
+reason to re-work them.
+
+I like to release my code under the WTFPL, but since I'm starting with someone
+else's work, I'm leaving the original license/contributor/author information
+in tact.
+
+I suppose I can legitimately release xgbgen under the WTFPL.
+
+What follows is the original README:
+
+XGB README
+==========
+XGB is the X protocol Go language Binding.
+
+It is the Go equivalent of XCB, the X protocol C-language Binding
+(http://xcb.freedesktop.org/).
+
+Unless otherwise noted, the XGB source files are distributed
+under the BSD-style license found in the LICENSE file.
+
+Contributions should follow the same procedure as for the Go project:
+http://golang.org/doc/contribute.html
+
diff --git a/nexgb/auth.go b/nexgb/auth.go
new file mode 100644
index 0000000..355afeb
--- /dev/null
+++ b/nexgb/auth.go
@@ -0,0 +1,111 @@
+// Copyright 2009 The XGB Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package xgb
+
+import (
+ "bufio"
+ "errors"
+ "io"
+ "os"
+)
+
+func getU16BE(r io.Reader, b []byte) (uint16, error) {
+ _, err := io.ReadFull(r, b[0:2])
+ if err != nil {
+ return 0, err
+ }
+ return uint16(b[0])<<8 + uint16(b[1]), nil
+}
+
+func getBytes(r io.Reader, b []byte) ([]byte, error) {
+ n, err := getU16BE(r, b)
+ if err != nil {
+ return nil, err
+ }
+ if int(n) > len(b) {
+ return nil, errors.New("bytes too long for buffer")
+ }
+ _, err = io.ReadFull(r, b[0:n])
+ if err != nil {
+ return nil, err
+ }
+ return b[0:n], nil
+}
+
+func getString(r io.Reader, b []byte) (string, error) {
+ b, err := getBytes(r, b)
+ if err != nil {
+ return "", err
+ }
+ return string(b), nil
+}
+
+// readAuthority reads the X authority file for the DISPLAY.
+// If hostname == "" or hostname == "localhost",
+// readAuthority uses the system's hostname (as returned by os.Hostname) instead.
+func readAuthority(hostname, display string) (name string, data []byte, err error) {
+ // b is a scratch buffer to use and should be at least 256 bytes long
+ // (i.e. it should be able to hold a hostname).
+ var b [256]byte
+
+ // As per /usr/include/X11/Xauth.h.
+ const familyLocal = 256
+
+ if len(hostname) == 0 || hostname == "localhost" {
+ hostname, err = os.Hostname()
+ if err != nil {
+ return "", nil, err
+ }
+ }
+
+ fname := os.Getenv("XAUTHORITY")
+ if len(fname) == 0 {
+ home := os.Getenv("HOME")
+ if len(home) == 0 {
+ err = errors.New("Xauthority not found: $XAUTHORITY, $HOME not set")
+ return "", nil, err
+ }
+ fname = home + "/.Xauthority"
+ }
+
+ r, err := os.Open(fname)
+ if err != nil {
+ return "", nil, err
+ }
+ defer r.Close()
+
+ br := bufio.NewReader(r)
+ for {
+ family, err := getU16BE(br, b[0:2])
+ if err != nil {
+ return "", nil, err
+ }
+
+ addr, err := getString(br, b[0:])
+ if err != nil {
+ return "", nil, err
+ }
+
+ disp, err := getString(br, b[0:])
+ if err != nil {
+ return "", nil, err
+ }
+
+ name0, err := getString(br, b[0:])
+ if err != nil {
+ return "", nil, err
+ }
+
+ data0, err := getBytes(br, b[0:])
+ if err != nil {
+ return "", nil, err
+ }
+
+ if family == familyLocal && addr == hostname && disp == display {
+ return name0, data0, nil
+ }
+ }
+ panic("unreachable")
+}
diff --git a/nexgb/xgb.go b/nexgb/xgb.go
new file mode 100644
index 0000000..7e209a7
--- /dev/null
+++ b/nexgb/xgb.go
@@ -0,0 +1,484 @@
+// Copyright 2009 The XGB Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// The XGB package implements the X11 core protocol.
+// It is based on XCB: http://xcb.freedesktop.org/
+package xgb
+
+import (
+ "errors"
+ "fmt"
+ "io"
+ "net"
+ "os"
+ "strconv"
+ "strings"
+ "sync"
+)
+
+const (
+ readBuffer = 100
+ writeBuffer = 100
+)
+
+// A Conn represents a connection to an X server.
+// Only one goroutine should use a Conn's methods at a time.
+type Conn struct {
+ host string
+ conn net.Conn
+ nextId Id
+ nextCookie uint16
+ cookies map[uint16]*Cookie
+ events queue
+ err error
+ display string
+ defaultScreen int
+ scratch [32]byte
+ Setup SetupInfo
+ extensions map[string]byte
+
+ requestChan chan *Request
+ requestCookieChan chan *Cookie
+ replyChan chan bool
+ eventChan chan bool
+ errorChan chan bool
+
+ newIdLock sync.Mutex
+ writeLock sync.Mutex
+ dequeueLock sync.Mutex
+ cookieLock sync.Mutex
+ extLock sync.Mutex
+}
+
+// Id is used for all X identifiers, such as windows, pixmaps, and GCs.
+type Id uint32
+
+// Request is used to abstract the difference between a request
+// that expects a reply and a request that doesn't expect a reply.
+type Request struct {
+ buf []byte
+ cookieChan chan *Cookie
+}
+
+func newRequest(buf []byte, needsReply bool) *Request {
+ req := &Request{
+ buf: buf,
+ cookieChan: nil,
+ }
+ if needsReply {
+ req.cookieChan = make(chan *Cookie)
+ }
+ return req
+}
+
+// Cookies are the sequence numbers used to pair replies up with their requests
+type Cookie struct {
+ id uint16
+ replyChan chan []byte
+ errorChan chan error
+}
+
+func newCookie(id uint16) *Cookie {
+ return &Cookie{
+ id: id,
+ replyChan: make(chan []byte, 1),
+ errorChan: make(chan error, 1),
+ }
+}
+
+// Event is an interface that can contain any of the events returned by the server.
+// Use a type assertion switch to extract the Event structs.
+type Event interface{}
+
+// Error contains protocol errors returned to us by the X server.
+type Error struct {
+ Detail uint8
+ Major uint8
+ Minor uint16
+ Cookie uint16
+ Id Id
+}
+
+func (e *Error) Error() string {
+ return fmt.Sprintf("Bad%s (major=%d minor=%d cookie=%d id=0x%x)",
+ errorNames[e.Detail], e.Major, e.Minor, e.Cookie, e.Id)
+}
+
+// NewID generates a new unused ID for use with requests like CreateWindow.
+func (c *Conn) NewId() Id {
+ c.newIdLock.Lock()
+ defer c.newIdLock.Unlock()
+
+ id := c.nextId
+ // TODO: handle ID overflow
+ c.nextId++
+ return id
+}
+
+// RegisterExtension adds the respective extension's major op code to
+// the extensions map.
+func (c *Conn) RegisterExtension(name string) error {
+ nameUpper := strings.ToUpper(name)
+ reply, err := c.QueryExtension(nameUpper)
+
+ switch {
+ case err != nil:
+ return err
+ case !reply.Present:
+ return errors.New(fmt.Sprintf("No extension named '%s' is present.",
+ nameUpper))
+ }
+
+ c.extLock.Lock()
+ c.extensions[nameUpper] = reply.MajorOpcode
+ c.extLock.Unlock()
+
+ return nil
+}
+
+// A simple queue used to stow away events.
+type queue struct {
+ data [][]byte
+ a, b int
+}
+
+func (q *queue) queue(item []byte) {
+ if q.b == len(q.data) {
+ if q.a > 0 {
+ copy(q.data, q.data[q.a:q.b])
+ q.a, q.b = 0, q.b-q.a
+ } else {
+ newData := make([][]byte, (len(q.data)*3)/2)
+ copy(newData, q.data)
+ q.data = newData
+ }
+ }
+ q.data[q.b] = item
+ q.b++
+}
+
+func (q *queue) dequeue(c *Conn) []byte {
+ c.dequeueLock.Lock()
+ defer c.dequeueLock.Unlock()
+
+ if q.a < q.b {
+ item := q.data[q.a]
+ q.a++
+ return item
+ }
+ return nil
+}
+
+// newWriteChan creates the channel required for writing to the net.Conn.
+func (c *Conn) newRequestChannels() {
+ c.requestChan = make(chan *Request, writeBuffer)
+ c.requestCookieChan = make(chan *Cookie, 1)
+
+ go func() {
+ for request := range c.requestChan {
+ cookieNum := c.nextCookie
+ c.nextCookie++
+
+ if request.cookieChan != nil {
+ cookie := newCookie(cookieNum)
+ c.cookies[cookieNum] = cookie
+ request.cookieChan <- cookie
+ }
+ if _, err := c.conn.Write(request.buf); err != nil {
+ fmt.Fprintf(os.Stderr, "x protocol write error: %s\n", err)
+ close(c.requestChan)
+ return
+ }
+ }
+ }()
+}
+
+// request is a buffered write to net.Conn.
+func (c *Conn) request(buf []byte, needsReply bool) *Cookie {
+ req := newRequest(buf, needsReply)
+ c.requestChan <- req
+
+ if req.cookieChan != nil {
+ cookie := <-req.cookieChan
+ close(req.cookieChan)
+ return cookie
+ }
+ return nil
+}
+
+func (c *Conn) sendRequest(needsReply bool, bufs ...[]byte) *Cookie {
+ if len(bufs) == 1 {
+ return c.request(bufs[0], needsReply)
+ }
+
+ total := make([]byte, 0)
+ for _, buf := range bufs {
+ total = append(total, buf...)
+ }
+ return c.request(total, needsReply)
+}
+
+func (c *Conn) newReadChannels() {
+ c.eventChan = make(chan bool, readBuffer)
+
+ onError := func() {
+ panic("read error")
+ }
+
+ go func() {
+ for {
+ buf := make([]byte, 32)
+ if _, err := io.ReadFull(c.conn, buf); err != nil {
+ fmt.Fprintf(os.Stderr, "x protocol read error: %s\n", err)
+ onError()
+ return
+ }
+
+ switch buf[0] {
+ case 0:
+ err := &Error{
+ Detail: buf[1],
+ Cookie: uint16(get16(buf[2:])),
+ Id: Id(get32(buf[4:])),
+ Minor: get16(buf[8:]),
+ Major: buf[10],
+ }
+ if cookie, ok := c.cookies[err.Cookie]; ok {
+ cookie.errorChan <- err
+ } else {
+ fmt.Fprintf(os.Stderr, "x protocol error: %s\n", err)
+ }
+ case 1:
+ seq := uint16(get16(buf[2:]))
+ if _, ok := c.cookies[seq]; !ok {
+ continue
+ }
+
+ size := get32(buf[4:])
+ if size > 0 {
+ bigbuf := make([]byte, 32+size*4, 32+size*4)
+ copy(bigbuf[0:32], buf)
+ if _, err := io.ReadFull(c.conn, bigbuf[32:]); err != nil {
+ fmt.Fprintf(os.Stderr,
+ "x protocol read error: %s\n", err)
+ onError()
+ return
+ }
+ c.cookies[seq].replyChan <- bigbuf
+ } else {
+ c.cookies[seq].replyChan <- buf
+ }
+ default:
+ c.events.queue(buf)
+ select {
+ case c.eventChan <- true:
+ default:
+ }
+ }
+ }
+ }()
+}
+
+func (c *Conn) waitForReply(cookie *Cookie) ([]byte, error) {
+ if cookie == nil {
+ panic("nil cookie")
+ }
+ if _, ok := c.cookies[cookie.id]; !ok {
+ panic("waiting for a cookie that will never come")
+ }
+ select {
+ case reply := <-cookie.replyChan:
+ return reply, nil
+ case err := <-cookie.errorChan:
+ return nil, err
+ }
+ panic("unreachable")
+}
+
+// WaitForEvent returns the next event from the server.
+// It will block until an event is available.
+func (c *Conn) WaitForEvent() (Event, error) {
+ for {
+ if reply := c.events.dequeue(c); reply != nil {
+ return parseEvent(reply)
+ }
+ if !<-c.eventChan {
+ return nil, errors.New("Event channel has been closed.")
+ }
+ }
+ panic("unreachable")
+}
+
+// PollForEvent returns the next event from the server if one is available in the internal queue.
+// It will not read from the connection, so you must call WaitForEvent to receive new events.
+// Only use this function to empty the queue without blocking.
+func (c *Conn) PollForEvent() (Event, error) {
+ if reply := c.events.dequeue(c); reply != nil {
+ return parseEvent(reply)
+ }
+ return nil, nil
+}
+
+// Dial connects to the X server given in the 'display' string.
+// If 'display' is empty it will be taken from os.Getenv("DISPLAY").
+//
+// Examples:
+// Dial(":1") // connect to net.Dial("unix", "", "/tmp/.X11-unix/X1")
+// Dial("/tmp/launch-123/:0") // connect to net.Dial("unix", "", "/tmp/launch-123/:0")
+// Dial("hostname:2.1") // connect to net.Dial("tcp", "", "hostname:6002")
+// Dial("tcp/hostname:1.0") // connect to net.Dial("tcp", "", "hostname:6001")
+func Dial(display string) (*Conn, error) {
+ c, err := connect(display)
+ if err != nil {
+ return nil, err
+ }
+
+ // Get authentication data
+ authName, authData, err := readAuthority(c.host, c.display)
+ noauth := false
+ if err != nil {
+ fmt.Fprintf(os.Stderr, "Could not get authority info: %v\n", err)
+ fmt.Fprintf(os.Stderr, "Trying connection without authority info...\n")
+ authName = ""
+ authData = []byte{}
+ noauth = true
+ }
+
+ // Assume that the authentication protocol is "MIT-MAGIC-COOKIE-1".
+ if !noauth && (authName != "MIT-MAGIC-COOKIE-1" || len(authData) != 16) {
+ return nil, errors.New("unsupported auth protocol " + authName)
+ }
+
+ buf := make([]byte, 12+pad(len(authName))+pad(len(authData)))
+ buf[0] = 0x6c
+ buf[1] = 0
+ put16(buf[2:], 11)
+ put16(buf[4:], 0)
+ put16(buf[6:], uint16(len(authName)))
+ put16(buf[8:], uint16(len(authData)))
+ put16(buf[10:], 0)
+ copy(buf[12:], []byte(authName))
+ copy(buf[12+pad(len(authName)):], authData)
+ if _, err = c.conn.Write(buf); err != nil {
+ return nil, err
+ }
+
+ head := make([]byte, 8)
+ if _, err = io.ReadFull(c.conn, head[0:8]); err != nil {
+ return nil, err
+ }
+ code := head[0]
+ reasonLen := head[1]
+ major := get16(head[2:])
+ minor := get16(head[4:])
+ dataLen := get16(head[6:])
+
+ if major != 11 || minor != 0 {
+ return nil, errors.New(fmt.Sprintf("x protocol version mismatch: %d.%d", major, minor))
+ }
+
+ buf = make([]byte, int(dataLen)*4+8, int(dataLen)*4+8)
+ copy(buf, head)
+ if _, err = io.ReadFull(c.conn, buf[8:]); err != nil {
+ return nil, err
+ }
+
+ if code == 0 {
+ reason := buf[8 : 8+reasonLen]
+ return nil, errors.New(fmt.Sprintf("x protocol authentication refused: %s", string(reason)))
+ }
+
+ getSetupInfo(buf, &c.Setup)
+
+ if c.defaultScreen >= len(c.Setup.Roots) {
+ c.defaultScreen = 0
+ }
+
+ c.nextId = Id(c.Setup.ResourceIdBase)
+ c.nextCookie = 1
+ c.cookies = make(map[uint16]*Cookie)
+ c.events = queue{make([][]byte, 100), 0, 0}
+ c.extensions = make(map[string]byte)
+
+ c.newReadChannels()
+ c.newRequestChannels()
+ return c, nil
+}
+
+// Close closes the connection to the X server.
+func (c *Conn) Close() { c.conn.Close() }
+
+func connect(display string) (*Conn, error) {
+ if len(display) == 0 {
+ display = os.Getenv("DISPLAY")
+ }
+
+ display0 := display
+ if len(display) == 0 {
+ return nil, errors.New("empty display string")
+ }
+
+ colonIdx := strings.LastIndex(display, ":")
+ if colonIdx < 0 {
+ return nil, errors.New("bad display string: " + display0)
+ }
+
+ var protocol, socket string
+ c := new(Conn)
+
+ if display[0] == '/' {
+ socket = display[0:colonIdx]
+ } else {
+ slashIdx := strings.LastIndex(display, "/")
+ if slashIdx >= 0 {
+ protocol = display[0:slashIdx]
+ c.host = display[slashIdx+1 : colonIdx]
+ } else {
+ c.host = display[0:colonIdx]
+ }
+ }
+
+ display = display[colonIdx+1 : len(display)]
+ if len(display) == 0 {
+ return nil, errors.New("bad display string: " + display0)
+ }
+
+ var scr string
+ dotIdx := strings.LastIndex(display, ".")
+ if dotIdx < 0 {
+ c.display = display[0:]
+ } else {
+ c.display = display[0:dotIdx]
+ scr = display[dotIdx+1:]
+ }
+
+ dispnum, err := strconv.Atoi(c.display)
+ if err != nil || dispnum < 0 {
+ return nil, errors.New("bad display string: " + display0)
+ }
+
+ if len(scr) != 0 {
+ c.defaultScreen, err = strconv.Atoi(scr)
+ if err != nil {
+ return nil, errors.New("bad display string: " + display0)
+ }
+ }
+
+ // Connect to server
+ if len(socket) != 0 {
+ c.conn, err = net.Dial("unix", socket+":"+c.display)
+ } else if len(c.host) != 0 {
+ if protocol == "" {
+ protocol = "tcp"
+ }
+ c.conn, err = net.Dial(protocol, c.host+":"+strconv.Itoa(6000+dispnum))
+ } else {
+ c.conn, err = net.Dial("unix", "/tmp/.X11-unix/X"+c.display)
+ }
+
+ if err != nil {
+ return nil, errors.New("cannot connect to " + display0 + ": " + err.Error())
+ }
+ return c, nil
+}
diff --git a/nexgb/xgb_help.go b/nexgb/xgb_help.go
new file mode 100644
index 0000000..adb97e0
--- /dev/null
+++ b/nexgb/xgb_help.go
@@ -0,0 +1,103 @@
+package xgb
+
+// getExtensionOpcode retrieves the extension opcode from the extensions map.
+// If one doesn't exist, just return 0. An X error will likely result.
+func (c *Conn) getExtensionOpcode(name string) byte {
+ return c.extensions[name]
+}
+
+func (c *Conn) bytesPadding(buf []byte) []byte {
+ return append(buf, make([]byte, pad(len(buf))-len(buf))...)
+}
+
+func (c *Conn) bytesString(str string) []byte {
+ return c.bytesPadding([]byte(str))
+}
+
+func (c *Conn) bytesStrList(list []Str, length int) []byte {
+ buf := make([]byte, 0)
+ for _, str := range list {
+ buf = append(buf, []byte(str.Name)...)
+ }
+ return c.bytesPadding(buf)
+}
+
+func (c *Conn) bytesUInt32List(list []uint32) []byte {
+ buf := make([]byte, len(list)*4)
+ for i, item := range list {
+ put32(buf[i*4:], item)
+ }
+ return c.bytesPadding(buf)
+}
+
+func (c *Conn) bytesIdList(list []Id, length int) []byte {
+ buf := make([]byte, length*4)
+ for i, item := range list {
+ put32(buf[i*4:], uint32(item))
+ }
+ return c.bytesPadding(buf)
+}
+
+// Pad a length to align on 4 bytes.
+func pad(n int) int { return (n + 3) & ^3 }
+
+func put16(buf []byte, v uint16) {
+ buf[0] = byte(v)
+ buf[1] = byte(v >> 8)
+}
+
+func put32(buf []byte, v uint32) {
+ buf[0] = byte(v)
+ buf[1] = byte(v >> 8)
+ buf[2] = byte(v >> 16)
+ buf[3] = byte(v >> 24)
+}
+
+func get16(buf []byte) uint16 {
+ v := uint16(buf[0])
+ v |= uint16(buf[1]) << 8
+ return v
+}
+
+func get32(buf []byte) uint32 {
+ v := uint32(buf[0])
+ v |= uint32(buf[1]) << 8
+ v |= uint32(buf[2]) << 16
+ v |= uint32(buf[3]) << 24
+ return v
+}
+
+// Voodoo to count the number of bits set in a value list mask.
+func popCount(mask0 int) int {
+ mask := uint32(mask0)
+ n := 0
+ for i := uint32(0); i < 32; i++ {
+ if mask&(1<<i) != 0 {
+ n++
+ }
+ }
+ return n
+}
+
+// DefaultScreen returns the Screen info for the default screen, which is
+// 0 or the one given in the display argument to Dial.
+func (c *Conn) DefaultScreen() *ScreenInfo { return &c.Setup.Roots[c.defaultScreen] }
+
+// ClientMessageData holds the data from a client message,
+// duplicated in three forms because Go doesn't have unions.
+type ClientMessageData struct {
+ Data8 [20]byte
+ Data16 [10]uint16
+ Data32 [5]uint32
+}
+
+func getClientMessageData(b []byte, v *ClientMessageData) int {
+ copy(v.Data8[:], b)
+ for i := 0; i < 10; i++ {
+ v.Data16[i] = get16(b[i*2:])
+ }
+ for i := 0; i < 5; i++ {
+ v.Data32[i] = get32(b[i*4:])
+ }
+ return 20
+}
diff --git a/nexgb/xgbgen/context.go b/nexgb/xgbgen/context.go
new file mode 100644
index 0000000..e5acb12
--- /dev/null
+++ b/nexgb/xgbgen/context.go
@@ -0,0 +1,89 @@
+package main
+
+import (
+ "bytes"
+ "encoding/xml"
+ "fmt"
+ "log"
+ "strings"
+)
+
+type Context struct {
+ xml *XML
+ out *bytes.Buffer
+}
+
+func newContext() *Context {
+ return &Context{
+ xml: &XML{},
+ out: bytes.NewBuffer([]byte{}),
+ }
+}
+
+// Putln calls put and adds a new line to the end of 'format'.
+func (c *Context) Putln(format string, v ...interface{}) {
+ c.Put(format + "\n", v...)
+}
+
+// Put is a short alias to write to 'out'.
+func (c *Context) Put(format string, v ...interface{}) {
+ _, err := c.out.WriteString(fmt.Sprintf(format, v...))
+ if err != nil {
+ log.Fatalf("There was an error writing to context buffer: %s", err)
+ }
+}
+
+// TypePrefix searches the parsed XML for a type matching 'needle'.
+// It then returns the appropriate prefix to be used in source code.
+// Note that the core X protocol *is* a namespace, but does not have a prefix.
+// Also note that you should probably check the BaseTypeMap and TypeMap
+// before calling this function.
+func (c *Context) TypePrefix(needle Type) string {
+ // If this is xproto, quit. No prefixes needed.
+ if c.xml.Header == "xproto" {
+ return ""
+ }
+
+ // First check for the type in the current namespace.
+ if c.xml.HasType(needle) {
+ return strings.Title(c.xml.Header)
+ }
+
+ // Now check each of the imports...
+ for _, imp := range c.xml.Imports {
+ if imp.xml.Header != "xproto" && imp.xml.HasType(needle) {
+ return strings.Title(imp.xml.Header)
+ }
+ }
+
+ return ""
+}
+
+// Translate is the big daddy of them all. It takes in an XML byte slice
+// and writes Go code to the 'out' buffer.
+func (c *Context) Translate(xmlBytes []byte) {
+ err := xml.Unmarshal(xmlBytes, c.xml)
+ if err != nil {
+ log.Fatal(err)
+ }
+
+ // Parse all imports
+ c.xml.Imports.Eval()
+
+ // Make sure all top level enumerations have expressions
+ // (For when there are empty items.)
+ c.xml.Enums.Eval()
+
+ // It's Morphin' Time!
+ c.xml.Morph(c)
+
+ // for _, req := range c.xml.Requests {
+ // if req.Name != "CreateContext" && req.Name != "MakeCurrent" {
+ // continue
+ // }
+ // log.Println(req.Name)
+ // for _, field := range req.Fields {
+ // log.Println("\t", field.XMLName.Local, field.Type.Morph(c))
+ // }
+ // }
+}
diff --git a/nexgb/xgbgen/go.go b/nexgb/xgbgen/go.go
new file mode 100644
index 0000000..eb3f0fb
--- /dev/null
+++ b/nexgb/xgbgen/go.go
@@ -0,0 +1,255 @@
+package main
+/*
+ To the best of my ability, these are all of the Go specific formatting
+ functions. If I've designed xgbgen correctly, this should be only the
+ place that you change things to generate code for a new language.
+
+ This file is organized as follows:
+
+ * Imports and helper variables.
+ * Manual type and name override maps.
+ * Helper morphing functions.
+ * Morphing functions for each "unit".
+ * Morphing functions for collections of "units".
+*/
+
+import (
+ "strings"
+)
+
+/******************************************************************************/
+// Manual type and name overrides.
+/******************************************************************************/
+
+// BaseTypeMap is a map from X base types to Go types.
+// X base types should correspond to the smallest set of X types
+// that can be used to rewrite ALL X types in terms of Go types.
+// That is, if you remove any of the following types, at least one
+// XML protocol description will produce an invalid Go program.
+// The types on the left *never* show themselves in the source.
+var BaseTypeMap = map[string]string{
+ "CARD8": "byte",
+ "CARD16": "uint16",
+ "CARD32": "uint32",
+ "INT8": "int8",
+ "INT16": "int16",
+ "INT32": "int32",
+ "BYTE": "byte",
+ "BOOL": "bool",
+ "float": "float64",
+ "double": "float64",
+}
+
+// TypeMap is a map from types in the XML to type names that is used
+// in the functions that follow. Basically, every occurrence of the key
+// type is replaced with the value type.
+var TypeMap = map[string]string{
+ "VISUALTYPE": "VisualInfo",
+ "DEPTH": "DepthInfo",
+ "SCREEN": "ScreenInfo",
+ "Setup": "SetupInfo",
+}
+
+// NameMap is the same as TypeMap, but for names.
+var NameMap = map[string]string{ }
+
+/******************************************************************************/
+// Helper functions that aide in morphing repetive constructs.
+// i.e., "structure contents", expressions, type and identifier names, etc.
+/******************************************************************************/
+
+// Morph changes every TYPE (not names) into something suitable
+// for your language.
+func (typ Type) Morph(c *Context) string {
+ t := string(typ)
+
+ // If this is a base type, then write the raw Go type.
+ if newt, ok := BaseTypeMap[t]; ok {
+ return newt
+ }
+
+ // If it's in the type map, use that translation.
+ if newt, ok := TypeMap[t]; ok {
+ return newt
+ }
+
+ // If it's a resource type, just use 'Id'.
+ if c.xml.IsResource(typ) {
+ return "Id"
+ }
+
+ // If there's a namespace to this type, just use it and be done.
+ if colon := strings.Index(t, ":"); colon > -1 {
+ namespace := t[:colon]
+ rest := t[colon+1:]
+ return splitAndTitle(namespace) + splitAndTitle(rest)
+ }
+
+ // Since there is no namespace, we need to look for a namespace
+ // in the current context.
+ return c.TypePrefix(typ) + splitAndTitle(t)
+}
+
+// Morph changes every identifier (NOT type) into something suitable
+// for your language.
+func (name Name) Morph(c *Context) string {
+ n := string(name)
+
+ // If it's in the name map, use that translation.
+ if newn, ok := NameMap[n]; ok {
+ return newn
+ }
+
+ return splitAndTitle(n)
+}
+
+/******************************************************************************/
+// Per element morphing.
+// Below are functions that morph a single unit.
+/******************************************************************************/
+
+// Import morphing.
+func (imp *Import) Morph(c *Context) {
+ c.Putln("// import \"%s\"", imp.Name)
+}
+
+// Enum morphing.
+func (enum *Enum) Morph(c *Context) {
+ c.Putln("const (")
+ for _, item := range enum.Items {
+ c.Putln("%s%s = %d", enum.Name.Morph(c), item.Name.Morph(c),
+ item.Expr.Eval())
+ }
+ c.Putln(")\n")
+}
+
+// Xid morphing.
+func (xid *Xid) Morph(c *Context) {
+ // Don't emit anything for xid types for now.
+ // We're going to force them all to simply be 'Id'
+ // to avoid excessive type converting.
+ // c.Putln("type %s Id", xid.Name.Morph(c))
+}
+
+// TypeDef morphing.
+func (typedef *TypeDef) Morph(c *Context) {
+ c.Putln("type %s %s", typedef.Old.Morph(c), typedef.New.Morph(c))
+}
+
+// Struct morphing.
+func (strct *Struct) Morph(c *Context) {
+}
+
+// Union morphing.
+func (union *Union) Morph(c *Context) {
+}
+
+// Request morphing.
+func (request *Request) Morph(c *Context) {
+}
+
+// Event morphing.
+func (ev *Event) Morph(c *Context) {
+}
+
+// EventCopy morphing.
+func (evcopy *EventCopy) Morph(c *Context) {
+}
+
+// Error morphing.
+func (err *Error) Morph(c *Context) {
+}
+
+// ErrorCopy morphing.
+func (errcopy *ErrorCopy) Morph(c *Context) {
+}
+
+/******************************************************************************/
+// Collection morphing.
+// Below are functions that morph a collections of units.
+// Most of these can probably remain unchanged, but they are useful if you
+// need to group all of some "unit" in a single block or something.
+/******************************************************************************/
+func (imports Imports) Morph(c *Context) {
+ if len(imports) == 0 {
+ return
+ }
+
+ c.Putln("// Imports are not required for XGB since everything is in")
+ c.Putln("// a single package. Still these may be useful for ")
+ c.Putln("// reference purposes.")
+ for _, imp := range imports {
+ imp.Morph(c)
+ }
+}
+
+func (enums Enums) Morph(c *Context) {
+ c.Putln("// Enums\n")
+ for _, enum := range enums {
+ enum.Morph(c)
+ }
+}
+
+func (xids Xids) Morph(c *Context) {
+ c.Putln("// Xids\n")
+ for _, xid := range xids {
+ xid.Morph(c)
+ }
+}
+
+func (typedefs TypeDefs) Morph(c *Context) {
+ c.Putln("// TypeDefs\n")
+ for _, typedef := range typedefs {
+ typedef.Morph(c)
+ }
+}
+
+func (strct Structs) Morph(c *Context) {
+ c.Putln("// Structs\n")
+ for _, typedef := range strct {
+ typedef.Morph(c)
+ }
+}
+
+func (union Unions) Morph(c *Context) {
+ c.Putln("// Unions\n")
+ for _, typedef := range union {
+ typedef.Morph(c)
+ }
+}
+
+func (request Requests) Morph(c *Context) {
+ c.Putln("// Requests\n")
+ for _, typedef := range request {
+ typedef.Morph(c)
+ }
+}
+
+func (event Events) Morph(c *Context) {
+ c.Putln("// Events\n")
+ for _, typedef := range event {
+ typedef.Morph(c)
+ }
+}
+
+func (evcopy EventCopies) Morph(c *Context) {
+ c.Putln("// Event Copies\n")
+ for _, typedef := range evcopy {
+ typedef.Morph(c)
+ }
+}
+
+func (err Errors) Morph(c *Context) {
+ c.Putln("// Errors\n")
+ for _, typedef := range err {
+ typedef.Morph(c)
+ }
+}
+
+func (errcopy ErrorCopies) Morph(c *Context) {
+ c.Putln("// Error copies\n")
+ for _, typedef := range errcopy {
+ typedef.Morph(c)
+ }
+}
+
diff --git a/nexgb/xgbgen/main.go b/nexgb/xgbgen/main.go
new file mode 100644
index 0000000..69579a4
--- /dev/null
+++ b/nexgb/xgbgen/main.go
@@ -0,0 +1,64 @@
+package main
+
+import (
+ "flag"
+ "io/ioutil"
+ "log"
+ "os"
+ "os/exec"
+ "strings"
+)
+
+var (
+ protoPath = flag.String("proto-path",
+ "/usr/share/xcb", "path to directory of X protocol XML files")
+ gofmt = flag.Bool("gofmt", true,
+ "When disabled, gofmt will not be run before outputting Go code")
+)
+
+func usage() {
+ basename := os.Args[0]
+ if lastSlash := strings.LastIndex(basename, "/"); lastSlash > -1 {
+ basename = basename[lastSlash+1:]
+ }
+ log.Printf("Usage: %s [flags] xml-file", basename)
+ flag.PrintDefaults()
+ os.Exit(1)
+}
+
+func init() {
+ log.SetFlags(0)
+}
+
+func main() {
+ flag.Usage = usage
+ flag.Parse()
+
+ if flag.NArg() != 1 {
+ log.Printf("A single XML protocol file can be processed at once.")
+ flag.Usage()
+ }
+
+ // Read the single XML file into []byte
+ xmlBytes, err := ioutil.ReadFile(flag.Arg(0))
+ if err != nil {
+ log.Fatal(err)
+ }
+
+ // Initialize the buffer, parse it, and filter it through gofmt.
+ c := newContext()
+ c.Translate(xmlBytes)
+
+ if !*gofmt {
+ c.out.WriteTo(os.Stdout)
+ } else {
+ cmdGofmt := exec.Command("gofmt")
+ cmdGofmt.Stdin = c.out
+ cmdGofmt.Stdout = os.Stdout
+ err = cmdGofmt.Run()
+ if err != nil {
+ log.Fatal(err)
+ }
+ }
+}
+
diff --git a/nexgb/xgbgen/misc.go b/nexgb/xgbgen/misc.go
new file mode 100644
index 0000000..9adcf5d
--- /dev/null
+++ b/nexgb/xgbgen/misc.go
@@ -0,0 +1,44 @@
+package main
+
+import (
+ "regexp"
+ "strings"
+)
+
+// AllCaps is a regex to test if a string identifier is made of
+// all upper case letters.
+var AllCaps = regexp.MustCompile("^[A-Z0-9]+$")
+
+// popCount counts number of bits 'set' in mask.
+func popCount(mask uint) uint {
+ m := uint32(mask)
+ n := uint(0)
+ for i := uint32(0); i < 32; i++ {
+ if m&(1<<i) != 0 {
+ n++
+ }
+ }
+ return n
+}
+
+// splitAndTitle takes a string, splits it by underscores, capitalizes the
+// first letter of each chunk, and smushes'em back together.
+func splitAndTitle(s string) string {
+ // If the string is all caps, lower it and capitalize first letter.
+ if AllCaps.MatchString(s) {
+ return strings.Title(strings.ToLower(s))
+ }
+
+ // If the string has no underscores, leave it be.
+ if i := strings.Index(s, "_"); i == -1 {
+ return s
+ }
+
+ // Now split the name at underscores, capitalize the first
+ // letter of each chunk, and smush'em back together.
+ chunks := strings.Split(s, "_")
+ for i, chunk := range chunks {
+ chunks[i] = strings.Title(strings.ToLower(chunk))
+ }
+ return strings.Join(chunks, "")
+}
diff --git a/nexgb/xgbgen/xgbgen b/nexgb/xgbgen/xgbgen
new file mode 100755
index 0000000..ef33abc
--- /dev/null
+++ b/nexgb/xgbgen/xgbgen
Binary files differ
diff --git a/nexgb/xgbgen/xml.go b/nexgb/xgbgen/xml.go
new file mode 100644
index 0000000..0f632b4
--- /dev/null
+++ b/nexgb/xgbgen/xml.go
@@ -0,0 +1,298 @@
+package main
+
+import (
+ "encoding/xml"
+ "io/ioutil"
+ "log"
+ "time"
+)
+
+type XML struct {
+ // Root 'xcb' element properties.
+ XMLName xml.Name `xml:"xcb"`
+ Header string `xml:"header,attr"`
+ ExtensionXName string `xml:"extension-xname,attr"`
+ ExtensionName string `xml:"extension-name,attr"`
+ MajorVersion string `xml:"major-version,attr"`
+ MinorVersion string `xml:"minor-version,attr"`
+
+ // Types for all top-level elements.
+ // First are the simple ones.
+ Imports Imports `xml:"import"`
+ Enums Enums `xml:"enum"`
+ Xids Xids `xml:"xidtype"`
+ XidUnions Xids `xml:"xidunion"`
+ TypeDefs TypeDefs `xml:"typedef"`
+ EventCopies EventCopies `xml:"eventcopy"`
+ ErrorCopies ErrorCopies `xml:"errorcopy"`
+
+ // Here are the complex ones, i.e., anything with "structure contents"
+ Structs Structs `xml:"struct"`
+ Unions Unions `xml:"union"`
+ Requests Requests `xml:"request"`
+ Events Events `xml:"event"`
+ Errors Errors `xml:"error"`
+}
+
+// Morph cascades down all of the XML and calls each type's corresponding
+// Morph function with itself as an argument (the context).
+func (x *XML) Morph(c *Context) {
+ // Start the header...
+ c.Putln("package xgb")
+ c.Putln("/*")
+ c.Putln("\tX protocol API for '%s.xml'.", c.xml.Header)
+ c.Putln("\tThis file is automatically generated. Edit at your own peril!")
+ c.Putln("\tGenerated on %s",
+ time.Now().Format("Jan 2, 2006 at 3:04:05pm MST"))
+ c.Putln("*/")
+ c.Putln("")
+
+ x.Imports.Morph(c)
+ c.Putln("")
+
+ x.Enums.Morph(c)
+ c.Putln("")
+
+ x.Xids.Morph(c)
+ c.Putln("")
+
+ x.XidUnions.Morph(c)
+ c.Putln("")
+
+ x.TypeDefs.Morph(c)
+ c.Putln("")
+
+ x.Structs.Morph(c)
+ c.Putln("")
+
+ x.Unions.Morph(c)
+ c.Putln("")
+
+ x.Requests.Morph(c)
+ c.Putln("")
+
+ x.Events.Morph(c)
+ c.Putln("")
+
+ x.Errors.Morph(c)
+ c.Putln("")
+
+ x.EventCopies.Morph(c)
+ c.Putln("")
+
+ x.ErrorCopies.Morph(c)
+ c.Putln("")
+}
+
+// IsResource returns true if the 'needle' type is a resource type.
+// i.e., an "xid"
+func (x *XML) IsResource(needle Type) bool {
+ for _, xid := range x.Xids {
+ if needle == xid.Name {
+ return true
+ }
+ }
+ for _, xidunion := range x.XidUnions {
+ if needle == xidunion.Name {
+ return true
+ }
+ }
+ for _, imp := range x.Imports {
+ if imp.xml.IsResource(needle) {
+ return true
+ }
+ }
+ return false
+}
+
+// HasType returns true if the 'needle' type can be found in the protocol
+// description represented by 'x'.
+func (x *XML) HasType(needle Type) bool {
+ for _, enum := range x.Enums {
+ if needle == enum.Name {
+ return true
+ }
+ }
+ for _, xid := range x.Xids {
+ if needle == xid.Name {
+ return true
+ }
+ }
+ for _, xidunion := range x.XidUnions {
+ if needle == xidunion.Name {
+ return true
+ }
+ }
+ for _, typedef := range x.TypeDefs {
+ if needle == typedef.New {
+ return true
+ }
+ }
+ for _, evcopy := range x.EventCopies {
+ if needle == evcopy.Name {
+ return true
+ }
+ }
+ for _, errcopy := range x.ErrorCopies {
+ if needle == errcopy.Name {
+ return true
+ }
+ }
+ for _, strct := range x.Structs {
+ if needle == strct.Name {
+ return true
+ }
+ }
+ for _, union := range x.Unions {
+ if needle == union.Name {
+ return true
+ }
+ }
+ for _, ev := range x.Events {
+ if needle == ev.Name {
+ return true
+ }
+ }
+ for _, err := range x.Errors {
+ if needle == err.Name {
+ return true
+ }
+ }
+
+ return false
+}
+
+type Name string
+
+type Type string
+
+type Imports []*Import
+
+func (imports Imports) Eval() {
+ for _, imp := range imports {
+ xmlBytes, err := ioutil.ReadFile(*protoPath + "/" + imp.Name + ".xml")
+ if err != nil {
+ log.Fatalf("Could not read X protocol description for import " +
+ "'%s' because: %s", imp.Name, err)
+ }
+
+ imp.xml = &XML{}
+ err = xml.Unmarshal(xmlBytes, imp.xml)
+ if err != nil {
+ log.Fatal("Could not parse X protocol description for import " +
+ "'%s' because: %s", imp.Name, err)
+ }
+ }
+}
+
+type Import struct {
+ Name string `xml:",chardata"`
+ xml *XML `xml:"-"`
+}
+
+type Enums []Enum
+
+// Eval on the list of all enum types goes through and forces every enum
+// item to have a valid expression.
+// This is necessary because when an item is empty, it is defined to have
+// the value of "one more than that of the previous item, or 0 for the first
+// item".
+func (enums Enums) Eval() {
+ for _, enum := range enums {
+ nextValue := uint(0)
+ for _, item := range enum.Items {
+ if item.Expr == nil {
+ item.Expr = newValueExpression(nextValue)
+ nextValue++
+ } else {
+ nextValue = item.Expr.Eval() + 1
+ }
+ }
+ }
+}
+
+type Enum struct {
+ Name Type `xml:"name,attr"`
+ Items []*EnumItem `xml:"item"`
+}
+
+type EnumItem struct {
+ Name Name `xml:"name,attr"`
+ Expr *Expression `xml:",any"`
+}
+
+type Xids []*Xid
+
+type Xid struct {
+ XMLName xml.Name
+ Name Type `xml:"name,attr"`
+}
+
+type TypeDefs []*TypeDef
+
+type TypeDef struct {
+ Old Type `xml:"oldname,attr"`
+ New Type `xml:"newname,attr"`
+}
+
+type EventCopies []*EventCopy
+
+type EventCopy struct {
+ Name Type `xml:"name,attr"`
+ Number string `xml:"number,attr"`
+ Ref Type `xml:"ref,attr"`
+}
+
+type ErrorCopies []*ErrorCopy
+
+type ErrorCopy struct {
+ Name Type `xml:"name,attr"`
+ Number string `xml:"number,attr"`
+ Ref Type `xml:"ref,attr"`
+}
+
+type Structs []*Struct
+
+type Struct struct {
+ Name Type `xml:"name,attr"`
+ Fields []*Field `xml:",any"`
+}
+
+type Unions []*Union
+
+type Union struct {
+ Name Type `xml:"name,attr"`
+ Fields []*Field `xml:",any"`
+}
+
+type Requests []*Request
+
+type Request struct {
+ Name Type `xml:"name,attr"`
+ Opcode int `xml:"opcode,attr"`
+ Combine bool `xml:"combine-adjacent,attr"`
+ Fields []*Field `xml:",any"`
+ Reply *Reply `xml:"reply"`
+}
+
+type Reply struct {
+ Fields []*Field `xml:",any"`
+}
+
+type Events []*Event
+
+type Event struct {
+ Name Type `xml:"name,attr"`
+ Number int `xml:"number,attr"`
+ NoSequence bool `xml:"no-sequence-number,true"`
+ Fields []*Field `xml:",any"`
+}
+
+type Errors []*Error
+
+type Error struct {
+ Name Type `xml:"name,attr"`
+ Number int `xml:"number,attr"`
+ Fields []*Field `xml:",any"`
+}
+
diff --git a/nexgb/xgbgen/xml_expression.go b/nexgb/xgbgen/xml_expression.go
new file mode 100644
index 0000000..dd32512
--- /dev/null
+++ b/nexgb/xgbgen/xml_expression.go
@@ -0,0 +1,160 @@
+package main
+
+import (
+ "encoding/xml"
+ "fmt"
+ "log"
+ "strconv"
+)
+
+type Expression struct {
+ XMLName xml.Name
+
+ Exprs []*Expression `xml:",any"`
+
+ Data string `xml:",chardata"`
+ Op string `xml:"op,attr"`
+ Ref string `xml:"ref,attr"`
+}
+
+func newValueExpression(v uint) *Expression {
+ return &Expression{
+ XMLName: xml.Name{Local: "value"},
+ Data: fmt.Sprintf("%d", v),
+ }
+}
+
+// String is for debugging. For actual use, please use 'Morph'.
+func (e *Expression) String() string {
+ switch e.XMLName.Local {
+ case "op":
+ return fmt.Sprintf("(%s %s %s)", e.Exprs[0], e.Op, e.Exprs[1])
+ case "unop":
+ return fmt.Sprintf("(%s (%s))", e.Op, e.Exprs[0])
+ case "popcount":
+ return fmt.Sprintf("popcount(%s)", e.Exprs[0])
+ case "fieldref":
+ fallthrough
+ case "value":
+ return fmt.Sprintf("%s", e.Data)
+ case "bit":
+ return fmt.Sprintf("(1 << %s)", e.Data)
+ case "enumref":
+ return fmt.Sprintf("%s%s", e.Ref, e.Data)
+ case "sumof":
+ return fmt.Sprintf("sum(%s)", e.Ref)
+ default:
+ log.Panicf("Unrecognized expression element: %s", e.XMLName.Local)
+ }
+
+ panic("unreachable")
+}
+
+// Eval is used to *attempt* to compute a concrete value for a particular
+// expression. This is used in the initial setup to instantiate values for
+// empty items in enums.
+// We can't compute a concrete value for expressions that rely on a context,
+// i.e., some field value.
+func (e *Expression) Eval() uint {
+ switch e.XMLName.Local {
+ case "op":
+ if len(e.Exprs) != 2 {
+ log.Panicf("'op' found %d expressions; expected 2.", len(e.Exprs))
+ }
+ return e.BinaryOp(e.Exprs[0], e.Exprs[1]).Eval()
+ case "unop":
+ if len(e.Exprs) != 1 {
+ log.Panicf("'unop' found %d expressions; expected 1.", len(e.Exprs))
+ }
+ return e.UnaryOp(e.Exprs[0]).Eval()
+ case "popcount":
+ if len(e.Exprs) != 1 {
+ log.Panicf("'popcount' found %d expressions; expected 1.",
+ len(e.Exprs))
+ }
+ return popCount(e.Exprs[0].Eval())
+ case "value":
+ val, err := strconv.Atoi(e.Data)
+ if err != nil {
+ log.Panicf("Could not convert '%s' in 'value' expression to int.",
+ e.Data)
+ }
+ return uint(val)
+ case "bit":
+ bit, err := strconv.Atoi(e.Data)
+ if err != nil {
+ log.Panicf("Could not convert '%s' in 'bit' expression to int.",
+ e.Data)
+ }
+ if bit < 0 || bit > 31 {
+ log.Panicf("A 'bit' literal must be in the range [0, 31], but " +
+ " is %d", bit)
+ }
+ return 1 << uint(bit)
+ case "fieldref":
+ log.Panicf("Cannot compute concrete value of 'fieldref' in " +
+ "expression '%s'.", e)
+ case "enumref":
+ log.Panicf("Cannot compute concrete value of 'enumref' in " +
+ "expression '%s'.", e)
+ case "sumof":
+ log.Panicf("Cannot compute concrete value of 'sumof' in " +
+ "expression '%s'.", e)
+ }
+
+ log.Panicf("Unrecognized tag '%s' in expression context. Expected one of " +
+ "op, fieldref, value, bit, enumref, unop, sumof or popcount.",
+ e.XMLName.Local)
+ panic("unreachable")
+}
+
+func (e *Expression) BinaryOp(operand1, operand2 *Expression) *Expression {
+ if e.XMLName.Local != "op" {
+ log.Panicf("Cannot perform binary operation on non-op expression: %s",
+ e.XMLName.Local)
+ }
+ if len(e.Op) == 0 {
+ log.Panicf("Cannot perform binary operation without operator for: %s",
+ e.XMLName.Local)
+ }
+
+ wrap := newValueExpression
+ switch e.Op {
+ case "+":
+ return wrap(operand1.Eval() + operand2.Eval())
+ case "-":
+ return wrap(operand1.Eval() + operand2.Eval())
+ case "*":
+ return wrap(operand1.Eval() * operand2.Eval())
+ case "/":
+ return wrap(operand1.Eval() / operand2.Eval())
+ case "&amp;":
+ return wrap(operand1.Eval() & operand2.Eval())
+ case "&lt;&lt;":
+ return wrap(operand1.Eval() << operand2.Eval())
+ }
+
+ log.Panicf("Invalid binary operator '%s' for '%s' expression.",
+ e.Op, e.XMLName.Local)
+ panic("unreachable")
+}
+
+func (e *Expression) UnaryOp(operand *Expression) *Expression {
+ if e.XMLName.Local != "unop" {
+ log.Panicf("Cannot perform unary operation on non-unop expression: %s",
+ e.XMLName.Local)
+ }
+ if len(e.Op) == 0 {
+ log.Panicf("Cannot perform unary operation without operator for: %s",
+ e.XMLName.Local)
+ }
+
+ switch e.Op {
+ case "~":
+ return newValueExpression(^operand.Eval())
+ }
+
+ log.Panicf("Invalid unary operator '%s' for '%s' expression.",
+ e.Op, e.XMLName.Local)
+ panic("unreachable")
+}
diff --git a/nexgb/xgbgen/xml_fields.go b/nexgb/xgbgen/xml_fields.go
new file mode 100644
index 0000000..18be6e3
--- /dev/null
+++ b/nexgb/xgbgen/xml_fields.go
@@ -0,0 +1,147 @@
+package main
+/*
+ A series of fields should be taken as "structure contents", and *not*
+ just the single 'field' elements. Namely, 'fields' subsumes 'field'
+ elements.
+
+ More particularly, 'fields' corresponds to list, in order, of any of the
+ follow elements: pad, field, list, localfield, exprfield, valueparm
+ and switch.
+
+ Thus, the 'Field' type must contain the union of information corresponding
+ to all aforementioned fields.
+
+ This would ideally be a better job for interfaces, but I could not figure
+ out how to make them jive with Go's XML package. (And I don't really feel
+ up to type translation.)
+*/
+
+import (
+ "encoding/xml"
+ "fmt"
+ "log"
+ "strings"
+)
+
+type Field struct {
+ XMLName xml.Name
+
+ // For 'pad' element
+ Bytes int `xml:"bytes,attr"`
+
+ // For 'field', 'list', 'localfield', 'exprfield' and 'switch' elements.
+ Name string `xml:"name,attr"`
+
+ // For 'field', 'list', 'localfield', and 'exprfield' elements.
+ Type Type `xml:"type,attr"`
+
+ // For 'list', 'exprfield' and 'switch' elements.
+ Expr *Expression `xml:",any"`
+
+ // For 'valueparm' element.
+ ValueMaskType Type `xml:"value-mask-type,attr"`
+ ValueMaskName string `xml:"value-mask-name,attr"`
+ ValueListName string `xml:"value-list-name,attr"`
+
+ // For 'switch' element.
+ Bitcases []*Bitcase `xml:"bitcase"`
+
+ // I don't know which elements these are for. The documentation is vague.
+ // They also seem to be completely optional.
+ OptEnum Type `xml:"enum,attr"`
+ OptMask Type `xml:"mask,attr"`
+ OptAltEnum Type `xml:"altenum,attr"`
+}
+
+// String is for debugging purposes.
+func (f *Field) String() string {
+ switch f.XMLName.Local {
+ case "pad":
+ return fmt.Sprintf("pad (%d bytes)", f.Bytes)
+ case "field":
+ return fmt.Sprintf("field (type = '%s', name = '%s')", f.Type, f.Name)
+ case "list":
+ return fmt.Sprintf("list (type = '%s', name = '%s', length = '%s')",
+ f.Type, f.Name, f.Expr)
+ case "localfield":
+ return fmt.Sprintf("localfield (type = '%s', name = '%s')",
+ f.Type, f.Name)
+ case "exprfield":
+ return fmt.Sprintf("exprfield (type = '%s', name = '%s', expr = '%s')",
+ f.Type, f.Name, f.Expr)
+ case "valueparam":
+ return fmt.Sprintf("valueparam (type = '%s', name = '%s', list = '%s')",
+ f.ValueMaskType, f.ValueMaskName, f.ValueListName)
+ case "switch":
+ bitcases := make([]string, len(f.Bitcases))
+ for i, bitcase := range f.Bitcases {
+ bitcases[i] = bitcase.StringPrefix("\t")
+ }
+ return fmt.Sprintf("switch (name = '%s', expr = '%s')\n\t%s",
+ f.Name, f.Expr, strings.Join(bitcases, "\n\t"))
+ default:
+ log.Panicf("Unrecognized field element: %s", f.XMLName.Local)
+ }
+
+ panic("unreachable")
+}
+
+// Bitcase represents a single expression followed by any number of fields.
+// Namely, if the switch's expression (all bitcases are inside a switch),
+// and'd with the bitcase's expression is equal to the bitcase expression,
+// then the fields should be included in its parent structure.
+// Note that since a bitcase is unique in that expressions and fields are
+// siblings, we must exhaustively search for one of them. Essentially,
+// it's the closest thing to a Union I can get to in Go without interfaces.
+// Would an '<expression>' tag have been too much to ask? :-(
+type Bitcase struct {
+ Fields []*Field `xml:",any"`
+
+ // All the different expressions.
+ // When it comes time to choose one, use the 'Expr' method.
+ ExprOp *Expression `xml:"op"`
+ ExprUnOp *Expression `xml:"unop"`
+ ExprField *Expression `xml:"fieldref"`
+ ExprValue *Expression `xml:"value"`
+ ExprBit *Expression `xml:"bit"`
+ ExprEnum *Expression `xml:"enumref"`
+ ExprSum *Expression `xml:"sumof"`
+ ExprPop *Expression `xml:"popcount"`
+}
+
+// StringPrefix is for debugging purposes only.
+// StringPrefix takes a string to prefix to every extra line for formatting.
+func (b *Bitcase) StringPrefix(prefix string) string {
+ fields := make([]string, len(b.Fields))
+ for i, field := range b.Fields {
+ fields[i] = fmt.Sprintf("%s%s", prefix, field)
+ }
+ return fmt.Sprintf("%s\n\t%s%s", b.Expr(), prefix,
+ strings.Join(fields, "\n\t"))
+}
+
+// Expr chooses the only non-nil Expr* field from Bitcase.
+// Panic if there is more than one non-nil expression.
+func (b *Bitcase) Expr() *Expression {
+ choices := []*Expression{
+ b.ExprOp, b.ExprUnOp, b.ExprField, b.ExprValue,
+ b.ExprBit, b.ExprEnum, b.ExprSum, b.ExprPop,
+ }
+
+ var choice *Expression = nil
+ numNonNil := 0
+ for _, c := range choices {
+ if c != nil {
+ numNonNil++
+ choice = c
+ }
+ }
+
+ if choice == nil {
+ log.Panicf("No top level expression found in a bitcase.")
+ }
+ if numNonNil > 1 {
+ log.Panicf("More than one top-level expression was found in a bitcase.")
+ }
+ return choice
+}