aboutsummaryrefslogtreecommitdiff
path: root/nexgb/xgb.go
diff options
context:
space:
mode:
authorAndrew Gallant (Ocelot) <Andrew.Gallant@tufts.edu>2012-04-28 23:25:57 -0400
committerAndrew Gallant (Ocelot) <Andrew.Gallant@tufts.edu>2012-04-28 23:25:57 -0400
commit52a21b415ad95b2c4649254447388cb329cee1a4 (patch)
tree0897f4033cc6251c7feb85fe9765159a3d0608eb /nexgb/xgb.go
downloadhaven-52a21b415ad95b2c4649254447388cb329cee1a4.tar.gz
haven-52a21b415ad95b2c4649254447388cb329cee1a4.tar.xz
haven-52a21b415ad95b2c4649254447388cb329cee1a4.zip
initial commit. not currently in a working state.
Diffstat (limited to 'nexgb/xgb.go')
-rw-r--r--nexgb/xgb.go484
1 files changed, 484 insertions, 0 deletions
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
+}