From 7d160ecc2dc688f032ef9e53a8a529821f445df9 Mon Sep 17 00:00:00 2001 From: "Andrew Gallant (Ocelot)" Date: Sat, 28 Apr 2012 23:25:57 -0400 Subject: initial commit. not currently in a working state. --- AUTHORS | 18 ++ CONTRIBUTORS | 39 ++++ LICENSE | 42 ++++ Makefile | 19 ++ README | 33 ++++ auth.go | 111 +++++++++++ xgb.go | 484 +++++++++++++++++++++++++++++++++++++++++++++++ xgb_help.go | 103 ++++++++++ xgbgen/context.go | 89 +++++++++ xgbgen/go.go | 255 +++++++++++++++++++++++++ xgbgen/main.go | 64 +++++++ xgbgen/misc.go | 44 +++++ xgbgen/xgbgen | Bin 0 -> 2318165 bytes xgbgen/xml.go | 298 +++++++++++++++++++++++++++++ xgbgen/xml_expression.go | 160 ++++++++++++++++ xgbgen/xml_fields.go | 147 ++++++++++++++ 16 files changed, 1906 insertions(+) create mode 100644 AUTHORS create mode 100644 CONTRIBUTORS create mode 100644 LICENSE create mode 100644 Makefile create mode 100644 README create mode 100644 auth.go create mode 100644 xgb.go create mode 100644 xgb_help.go create mode 100644 xgbgen/context.go create mode 100644 xgbgen/go.go create mode 100644 xgbgen/main.go create mode 100644 xgbgen/misc.go create mode 100755 xgbgen/xgbgen create mode 100644 xgbgen/xml.go create mode 100644 xgbgen/xml_expression.go create mode 100644 xgbgen/xml_fields.go diff --git a/AUTHORS b/AUTHORS new file mode 100644 index 0000000..08fc0cd --- /dev/null +++ b/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 +# The email address is not required for organizations. + +# Please keep the list sorted. + +Anthony Martin +Firmansyah Adiputra +Google Inc. +Scott Lawrence +Tor Andersson diff --git a/CONTRIBUTORS b/CONTRIBUTORS new file mode 100644 index 0000000..46dc4b0 --- /dev/null +++ b/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 + +# Please keep the list sorted. + +Anthony Martin +Firmansyah Adiputra +Ian Lance Taylor +Nigel Tao +Robert Griesemer +Russ Cox +Scott Lawrence +Tor Andersson diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..d99cd90 --- /dev/null +++ b/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/Makefile b/Makefile new file mode 100644 index 0000000..041d20c --- /dev/null +++ b/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/README b/README new file mode 100644 index 0000000..f659e32 --- /dev/null +++ b/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/auth.go b/auth.go new file mode 100644 index 0000000..355afeb --- /dev/null +++ b/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/xgb.go b/xgb.go new file mode 100644 index 0000000..7e209a7 --- /dev/null +++ b/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/xgb_help.go b/xgb_help.go new file mode 100644 index 0000000..adb97e0 --- /dev/null +++ b/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< -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/xgbgen/main.go b/xgbgen/main.go new file mode 100644 index 0000000..69579a4 --- /dev/null +++ b/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/xgbgen/misc.go b/xgbgen/misc.go new file mode 100644 index 0000000..9adcf5d --- /dev/null +++ b/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< 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 "&": + return wrap(operand1.Eval() & operand2.Eval()) + case "<<": + 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/xgbgen/xml_fields.go b/xgbgen/xml_fields.go new file mode 100644 index 0000000..18be6e3 --- /dev/null +++ b/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 '' 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 +} -- cgit v1.2.3