diff --git a/Makefile b/Makefile index 7a3dae2..229ab11 100644 --- a/Makefile +++ b/Makefile @@ -9,8 +9,10 @@ # necessary, but it's a good idea to make sure each sub-package is a valid # Go package. -# My path to the X protocol XML descriptions. +ifndef XPROTO +# Path to the X protocol XML descriptions. XPROTO=/usr/share/xcb +endif # All of the XML files in my /usr/share/xcb directory EXCEPT XKB. -_- # This is intended to build xgbgen and generate Go code for each supported diff --git a/bigreq/bigreq.go b/bigreq/bigreq.go index 6590376..5545ea1 100644 --- a/bigreq/bigreq.go +++ b/bigreq/bigreq.go @@ -4,9 +4,9 @@ package bigreq // This file is automatically generated from bigreq.xml. Edit at your peril! import ( - "github.com/BurntSushi/xgb" + "github.com/wwfx/xgb" - "github.com/BurntSushi/xgb/xproto" + "github.com/wwfx/xgb/xproto" ) // Init must be called before using the BIG-REQUESTS extension. diff --git a/composite/composite.go b/composite/composite.go index 1373f8b..5150932 100644 --- a/composite/composite.go +++ b/composite/composite.go @@ -4,10 +4,10 @@ package composite // This file is automatically generated from composite.xml. Edit at your peril! import ( - "github.com/BurntSushi/xgb" + "github.com/wwfx/xgb" - "github.com/BurntSushi/xgb/xfixes" - "github.com/BurntSushi/xgb/xproto" + "github.com/wwfx/xgb/xfixes" + "github.com/wwfx/xgb/xproto" ) // Init must be called before using the Composite extension. diff --git a/cookie.go b/cookie.go index d5cdb29..9caa0a9 100644 --- a/cookie.go +++ b/cookie.go @@ -98,6 +98,8 @@ func (c Cookie) replyChecked() ([]byte, error) { return reply, nil case err := <-c.errorChan: return nil, err + case <-c.conn.done: + return nil, errors.New("X connection was closed") } } @@ -121,6 +123,8 @@ func (c Cookie) replyUnchecked() ([]byte, error) { return reply, nil case <-c.pingChan: return nil, nil + case <-c.conn.done: + return nil, errors.New("X connection was closed") } } @@ -161,5 +165,7 @@ func (c Cookie) Check() error { return err case <-c.pingChan: return nil + case <-c.conn.done: + return errors.New("X connection was closed") } } diff --git a/damage/damage.go b/damage/damage.go index 26eca04..9880246 100644 --- a/damage/damage.go +++ b/damage/damage.go @@ -4,10 +4,10 @@ package damage // This file is automatically generated from damage.xml. Edit at your peril! import ( - "github.com/BurntSushi/xgb" + "github.com/wwfx/xgb" - "github.com/BurntSushi/xgb/xfixes" - "github.com/BurntSushi/xgb/xproto" + "github.com/wwfx/xgb/xfixes" + "github.com/wwfx/xgb/xproto" ) // Init must be called before using the DAMAGE extension. diff --git a/dpms/dpms.go b/dpms/dpms.go index 4bf5883..918cb6f 100644 --- a/dpms/dpms.go +++ b/dpms/dpms.go @@ -4,9 +4,9 @@ package dpms // This file is automatically generated from dpms.xml. Edit at your peril! import ( - "github.com/BurntSushi/xgb" + "github.com/wwfx/xgb" - "github.com/BurntSushi/xgb/xproto" + "github.com/wwfx/xgb/xproto" ) // Init must be called before using the DPMS extension. diff --git a/dri2/dri2.go b/dri2/dri2.go index 820cf2b..ff2010c 100644 --- a/dri2/dri2.go +++ b/dri2/dri2.go @@ -4,9 +4,9 @@ package dri2 // This file is automatically generated from dri2.xml. Edit at your peril! import ( - "github.com/BurntSushi/xgb" + "github.com/wwfx/xgb" - "github.com/BurntSushi/xgb/xproto" + "github.com/wwfx/xgb/xproto" ) // Init must be called before using the DRI2 extension. diff --git a/ge/ge.go b/ge/ge.go index f7e1ce4..4a2af88 100644 --- a/ge/ge.go +++ b/ge/ge.go @@ -4,9 +4,9 @@ package ge // This file is automatically generated from ge.xml. Edit at your peril! import ( - "github.com/BurntSushi/xgb" + "github.com/wwfx/xgb" - "github.com/BurntSushi/xgb/xproto" + "github.com/wwfx/xgb/xproto" ) // Init must be called before using the Generic Event Extension extension. diff --git a/glx/glx.go b/glx/glx.go index cf72d9a..3ee19c6 100644 --- a/glx/glx.go +++ b/glx/glx.go @@ -4,9 +4,9 @@ package glx // This file is automatically generated from glx.xml. Edit at your peril! import ( - "github.com/BurntSushi/xgb" + "github.com/wwfx/xgb" - "github.com/BurntSushi/xgb/xproto" + "github.com/wwfx/xgb/xproto" ) // Init must be called before using the GLX extension. diff --git a/randr/randr.go b/randr/randr.go index 021c011..7ff9bad 100644 --- a/randr/randr.go +++ b/randr/randr.go @@ -4,10 +4,10 @@ package randr // This file is automatically generated from randr.xml. Edit at your peril! import ( - "github.com/BurntSushi/xgb" + "github.com/wwfx/xgb" - "github.com/BurntSushi/xgb/render" - "github.com/BurntSushi/xgb/xproto" + "github.com/wwfx/xgb/render" + "github.com/wwfx/xgb/xproto" ) // Init must be called before using the RANDR extension. diff --git a/record/record.go b/record/record.go index 5469170..9f4ec73 100644 --- a/record/record.go +++ b/record/record.go @@ -4,9 +4,9 @@ package record // This file is automatically generated from record.xml. Edit at your peril! import ( - "github.com/BurntSushi/xgb" + "github.com/wwfx/xgb" - "github.com/BurntSushi/xgb/xproto" + "github.com/wwfx/xgb/xproto" ) // Init must be called before using the RECORD extension. diff --git a/render/render.go b/render/render.go index e15bd67..5dad073 100644 --- a/render/render.go +++ b/render/render.go @@ -4,9 +4,9 @@ package render // This file is automatically generated from render.xml. Edit at your peril! import ( - "github.com/BurntSushi/xgb" + "github.com/wwfx/xgb" - "github.com/BurntSushi/xgb/xproto" + "github.com/wwfx/xgb/xproto" ) // Init must be called before using the RENDER extension. diff --git a/res/res.go b/res/res.go index 0ad0389..7d00c53 100644 --- a/res/res.go +++ b/res/res.go @@ -4,9 +4,9 @@ package res // This file is automatically generated from res.xml. Edit at your peril! import ( - "github.com/BurntSushi/xgb" + "github.com/wwfx/xgb" - "github.com/BurntSushi/xgb/xproto" + "github.com/wwfx/xgb/xproto" ) // Init must be called before using the X-Resource extension. diff --git a/screensaver/screensaver.go b/screensaver/screensaver.go index 418576c..ae3a67e 100644 --- a/screensaver/screensaver.go +++ b/screensaver/screensaver.go @@ -4,9 +4,9 @@ package screensaver // This file is automatically generated from screensaver.xml. Edit at your peril! import ( - "github.com/BurntSushi/xgb" + "github.com/wwfx/xgb" - "github.com/BurntSushi/xgb/xproto" + "github.com/wwfx/xgb/xproto" ) // Init must be called before using the MIT-SCREEN-SAVER extension. diff --git a/shape/shape.go b/shape/shape.go index 7069f7e..373a1ad 100644 --- a/shape/shape.go +++ b/shape/shape.go @@ -4,9 +4,9 @@ package shape // This file is automatically generated from shape.xml. Edit at your peril! import ( - "github.com/BurntSushi/xgb" + "github.com/wwfx/xgb" - "github.com/BurntSushi/xgb/xproto" + "github.com/wwfx/xgb/xproto" ) // Init must be called before using the SHAPE extension. diff --git a/shm/shm.go b/shm/shm.go index b310c34..bd81653 100644 --- a/shm/shm.go +++ b/shm/shm.go @@ -4,9 +4,9 @@ package shm // This file is automatically generated from shm.xml. Edit at your peril! import ( - "github.com/BurntSushi/xgb" + "github.com/wwfx/xgb" - "github.com/BurntSushi/xgb/xproto" + "github.com/wwfx/xgb/xproto" ) // Init must be called before using the MIT-SHM extension. diff --git a/xcmisc/xcmisc.go b/xcmisc/xcmisc.go index 1778057..48fdfb7 100644 --- a/xcmisc/xcmisc.go +++ b/xcmisc/xcmisc.go @@ -4,9 +4,9 @@ package xcmisc // This file is automatically generated from xc_misc.xml. Edit at your peril! import ( - "github.com/BurntSushi/xgb" + "github.com/wwfx/xgb" - "github.com/BurntSushi/xgb/xproto" + "github.com/wwfx/xgb/xproto" ) // Init must be called before using the XC-MISC extension. diff --git a/xevie/xevie.go b/xevie/xevie.go index 180312b..4d419e0 100644 --- a/xevie/xevie.go +++ b/xevie/xevie.go @@ -4,9 +4,9 @@ package xevie // This file is automatically generated from xevie.xml. Edit at your peril! import ( - "github.com/BurntSushi/xgb" + "github.com/wwfx/xgb" - "github.com/BurntSushi/xgb/xproto" + "github.com/wwfx/xgb/xproto" ) // Init must be called before using the XEVIE extension. diff --git a/xf86dri/xf86dri.go b/xf86dri/xf86dri.go index 51c9322..34b2fb6 100644 --- a/xf86dri/xf86dri.go +++ b/xf86dri/xf86dri.go @@ -4,9 +4,9 @@ package xf86dri // This file is automatically generated from xf86dri.xml. Edit at your peril! import ( - "github.com/BurntSushi/xgb" + "github.com/wwfx/xgb" - "github.com/BurntSushi/xgb/xproto" + "github.com/wwfx/xgb/xproto" ) // Init must be called before using the XFree86-DRI extension. diff --git a/xf86vidmode/xf86vidmode.go b/xf86vidmode/xf86vidmode.go index 2829fd6..0c17cab 100644 --- a/xf86vidmode/xf86vidmode.go +++ b/xf86vidmode/xf86vidmode.go @@ -4,9 +4,9 @@ package xf86vidmode // This file is automatically generated from xf86vidmode.xml. Edit at your peril! import ( - "github.com/BurntSushi/xgb" + "github.com/wwfx/xgb" - "github.com/BurntSushi/xgb/xproto" + "github.com/wwfx/xgb/xproto" ) // Init must be called before using the XFree86-VidModeExtension extension. diff --git a/xfixes/xfixes.go b/xfixes/xfixes.go index 0f9e4b0..28e1928 100644 --- a/xfixes/xfixes.go +++ b/xfixes/xfixes.go @@ -4,11 +4,11 @@ package xfixes // This file is automatically generated from xfixes.xml. Edit at your peril! import ( - "github.com/BurntSushi/xgb" + "github.com/wwfx/xgb" - "github.com/BurntSushi/xgb/render" - "github.com/BurntSushi/xgb/shape" - "github.com/BurntSushi/xgb/xproto" + "github.com/wwfx/xgb/render" + "github.com/wwfx/xgb/shape" + "github.com/wwfx/xgb/xproto" ) // Init must be called before using the XFIXES extension. diff --git a/xgb.go b/xgb.go index 3d2c61f..683cb00 100644 --- a/xgb.go +++ b/xgb.go @@ -60,7 +60,8 @@ type Conn struct { xidChan chan xid seqChan chan uint16 reqChan chan *request - closing chan chan struct{} + done chan chan struct{} + wg sync.WaitGroup // ExtLock is a lock used whenever new extensions are initialized. // It should not be used. It is exported for use in the extension @@ -101,7 +102,7 @@ func NewConnDisplay(display string) (*Conn, error) { return postNewConn(conn) } -// NewConnDisplay is just like NewConn, but allows a specific net.Conn +// NewConnNet is just like NewConn, but allows a specific net.Conn // to be used. func NewConnNet(netConn net.Conn) (*Conn, error) { conn := &Conn{} @@ -125,8 +126,9 @@ func postNewConn(conn *Conn) (*Conn, error) { conn.seqChan = make(chan uint16, seqBuffer) conn.reqChan = make(chan *request, reqBuffer) conn.eventChan = make(chan eventOrError, eventBuffer) - conn.closing = make(chan chan struct{}, 1) + conn.done = make(chan chan struct{}) + conn.wg.Add(4) go conn.generateXIds() go conn.generateSeqIds() go conn.sendRequests() @@ -137,7 +139,19 @@ func postNewConn(conn *Conn) (*Conn, error) { // Close gracefully closes the connection to the X server. func (c *Conn) Close() { - close(c.reqChan) + c.broadcastDone() + c.wg.Wait() + c.conn.Close() + c.conn = nil +} + +func (c *Conn) broadcastDone() { + select { + case <-c.done: + return + default: + close(c.done) + } } // Event is an interface that can contain any of the events returned by the @@ -217,8 +231,9 @@ type xid struct { // This needs to be updated to use the XC Misc extension once we run out of // new ids. // Thanks to libxcb/src/xcb_xid.c. This code is greatly inspired by it. -func (conn *Conn) generateXIds() { - defer close(conn.xidChan) +func (c *Conn) generateXIds() { + defer c.wg.Done() + defer close(c.xidChan) // This requires some explanation. From the horse's mouth: // "The resource-id-mask contains a single contiguous set of bits (at least @@ -236,23 +251,22 @@ func (conn *Conn) generateXIds() { // 00111000 & 11001000 = 00001000. // And we use that value to increment the last resource id to get a new one. // (And then, of course, we OR it with resource-id-base.) - inc := conn.setupResourceIdMask & -conn.setupResourceIdMask - max := conn.setupResourceIdMask + inc := c.setupResourceIdMask & -c.setupResourceIdMask + max := c.setupResourceIdMask last := uint32(0) for { - // TODO: Use the XC Misc extension to look for released ids. + var id xid if last > 0 && last >= max-inc+1 { - conn.xidChan <- xid{ - id: 0, - err: errors.New("There are no more available resource" + - "identifiers."), - } + // TODO: Use the XC Misc extension to look for released ids. + id.err = errors.New("there are no more available resource identifiers") + } else { + last += inc + id.id = last | c.setupResourceIdBase } - - last += inc - conn.xidChan <- xid{ - id: last | conn.setupResourceIdBase, - err: nil, + select { + case <-c.done: + return + case c.xidChan <- id: } } } @@ -271,15 +285,20 @@ func (c *Conn) newSequenceId() uint16 { // N.B. As long as the cookie buffer is less than 2^16, there are no limitations // on the number (or kind) of requests made in sequence. func (c *Conn) generateSeqIds() { + defer c.wg.Done() defer close(c.seqChan) seqid := uint16(1) for { - c.seqChan <- seqid - if seqid == uint16((1<<16)-1) { - seqid = 0 - } else { - seqid++ + select { + case <-c.done: + return + case c.seqChan <- seqid: + if seqid == uint16((1<<16)-1) { + seqid = 0 + } else { + seqid++ + } } } } @@ -315,8 +334,16 @@ type request struct { // edits the generated code for the request you want to issue. func (c *Conn) NewRequest(buf []byte, cookie *Cookie) { seq := make(chan struct{}) - c.reqChan <- &request{buf: buf, cookie: cookie, seq: seq} - <-seq + select { + case c.reqChan <- &request{buf: buf, cookie: cookie, seq: seq}: + case <-seq: + return + case <-c.done: + // If connection was broken, all goroutines, including `sendRequests`, will be closed. + // This prevents NewRequest from blocking forever in <-seq or in c.reqChan <- &request if reqChan is full. + // We can't close c.reqChan since NewRequest will panic when sending to it. + return + } } // sendRequests is run as a single goroutine that takes requests and writes @@ -324,28 +351,32 @@ func (c *Conn) NewRequest(buf []byte, cookie *Cookie) { // It is meant to be run as its own goroutine. func (c *Conn) sendRequests() { defer close(c.cookieChan) + defer func() { + c.noop() // Flush the response reading goroutine, ignore error. + c.wg.Done() + }() - for req := range c.reqChan { - // ho there! if the cookie channel is nearly full, force a round - // trip to clear out the cookie buffer. - // Note that we circumvent the request channel, because we're *in* - // the request channel. - if len(c.cookieChan) == cookieBuffer-1 { - if err := c.noop(); err != nil { - // Shut everything down. - break + for { + select { + case req := <-c.reqChan: + // ho there! if the cookie channel is nearly full, force a round + // trip to clear out the cookie buffer. + // Note that we circumvent the request channel, because we're *in* + // the request channel. + if len(c.cookieChan) == cookieBuffer-1 { + if err := c.noop(); err != nil { + // Shut everything down. + return + } } + req.cookie.Sequence = c.newSequenceId() + c.cookieChan <- req.cookie + c.writeBuffer(req.buf) + close(req.seq) + case <-c.done: + return } - req.cookie.Sequence = c.newSequenceId() - c.cookieChan <- req.cookie - c.writeBuffer(req.buf) - close(req.seq) } - response := make(chan struct{}) - c.closing <- response - c.noop() // Flush the response reading goroutine, ignore error. - <-response - c.conn.Close() } // noop circumvents the usual request sending goroutines and forces a round @@ -366,9 +397,8 @@ func (c *Conn) writeBuffer(buf []byte) error { if _, err := c.conn.Write(buf); err != nil { Logger.Printf("A write error is unrecoverable: %s", err) return err - } else { - return nil } + return nil } // readResponses is a goroutine that reads events, errors and @@ -381,6 +411,7 @@ func (c *Conn) writeBuffer(buf []byte) error { // channel. (It is an error if no such cookie exists in this case.) // Finally, cookies that came "before" this reply are always cleaned up. func (c *Conn) readResponses() { + defer c.wg.Done() defer close(c.eventChan) var ( @@ -391,18 +422,16 @@ func (c *Conn) readResponses() { for { select { - case respond := <-c.closing: - respond <- struct{}{} + case <-c.done: return default: } - buf := make([]byte, 32) err, seq = nil, 0 if _, err := io.ReadFull(c.conn, buf); err != nil { Logger.Printf("A read error is unrecoverable: %s", err) c.eventChan <- err - c.Close() + c.broadcastDone() continue } switch buf[0] { @@ -432,7 +461,7 @@ func (c *Conn) readResponses() { if _, err := io.ReadFull(c.conn, biggerBuf[32:]); err != nil { Logger.Printf("A read error is unrecoverable: %s", err) c.eventChan <- err - c.Close() + c.broadcastDone() continue } replyBytes = biggerBuf diff --git a/xgbgen/context.go b/xgbgen/context.go index f18fd67..c8ccfec 100644 --- a/xgbgen/context.go +++ b/xgbgen/context.go @@ -64,10 +64,10 @@ func (c *Context) Morph(xmlBytes []byte) { // Write imports. We always need to import at least xgb. // We also need to import xproto if it's an extension. c.Putln("import (") - c.Putln("\"github.com/BurntSushi/xgb\"") + c.Putln("\"github.com/wwfx/xgb\"") c.Putln("") if c.protocol.isExt() { - c.Putln("\"github.com/BurntSushi/xgb/xproto\"") + c.Putln("\"github.com/wwfx/xgb/xproto\"") } sort.Sort(Protocols(c.protocol.Imports)) @@ -76,7 +76,7 @@ func (c *Context) Morph(xmlBytes []byte) { if imp.Name == "xproto" { continue } - c.Putln("\"github.com/BurntSushi/xgb/%s\"", imp.Name) + c.Putln("\"github.com/wwfx/xgb/%s\"", imp.Name) } c.Putln(")") c.Putln("") diff --git a/xinerama/xinerama.go b/xinerama/xinerama.go index ec97406..1e0c49b 100644 --- a/xinerama/xinerama.go +++ b/xinerama/xinerama.go @@ -4,9 +4,9 @@ package xinerama // This file is automatically generated from xinerama.xml. Edit at your peril! import ( - "github.com/BurntSushi/xgb" + "github.com/wwfx/xgb" - "github.com/BurntSushi/xgb/xproto" + "github.com/wwfx/xgb/xproto" ) // Init must be called before using the XINERAMA extension. diff --git a/xprint/xprint.go b/xprint/xprint.go index d692aed..9f28401 100644 --- a/xprint/xprint.go +++ b/xprint/xprint.go @@ -4,9 +4,9 @@ package xprint // This file is automatically generated from xprint.xml. Edit at your peril! import ( - "github.com/BurntSushi/xgb" + "github.com/wwfx/xgb" - "github.com/BurntSushi/xgb/xproto" + "github.com/wwfx/xgb/xproto" ) // Init must be called before using the XpExtension extension. diff --git a/xproto/xproto.go b/xproto/xproto.go index 716c49b..f1090e0 100644 --- a/xproto/xproto.go +++ b/xproto/xproto.go @@ -4,7 +4,7 @@ package xproto // This file is automatically generated from xproto.xml. Edit at your peril! import ( - "github.com/BurntSushi/xgb" + "github.com/wwfx/xgb" ) // Setup parses the setup bytes retrieved when diff --git a/xselinux/xselinux.go b/xselinux/xselinux.go index 1afcc10..c85a23d 100644 --- a/xselinux/xselinux.go +++ b/xselinux/xselinux.go @@ -4,9 +4,9 @@ package xselinux // This file is automatically generated from xselinux.xml. Edit at your peril! import ( - "github.com/BurntSushi/xgb" + "github.com/wwfx/xgb" - "github.com/BurntSushi/xgb/xproto" + "github.com/wwfx/xgb/xproto" ) // Init must be called before using the SELinux extension. diff --git a/xtest/xtest.go b/xtest/xtest.go index 182760e..43b2a16 100644 --- a/xtest/xtest.go +++ b/xtest/xtest.go @@ -4,9 +4,9 @@ package xtest // This file is automatically generated from xtest.xml. Edit at your peril! import ( - "github.com/BurntSushi/xgb" + "github.com/wwfx/xgb" - "github.com/BurntSushi/xgb/xproto" + "github.com/wwfx/xgb/xproto" ) // Init must be called before using the XTEST extension. diff --git a/xv/xv.go b/xv/xv.go index f0d3f3a..ffdd7be 100644 --- a/xv/xv.go +++ b/xv/xv.go @@ -4,10 +4,10 @@ package xv // This file is automatically generated from xv.xml. Edit at your peril! import ( - "github.com/BurntSushi/xgb" + "github.com/wwfx/xgb" - "github.com/BurntSushi/xgb/shm" - "github.com/BurntSushi/xgb/xproto" + "github.com/wwfx/xgb/shm" + "github.com/wwfx/xgb/xproto" ) // Init must be called before using the XVideo extension. diff --git a/xvmc/xvmc.go b/xvmc/xvmc.go index b943fa0..f8e3f15 100644 --- a/xvmc/xvmc.go +++ b/xvmc/xvmc.go @@ -4,10 +4,10 @@ package xvmc // This file is automatically generated from xvmc.xml. Edit at your peril! import ( - "github.com/BurntSushi/xgb" + "github.com/wwfx/xgb" - "github.com/BurntSushi/xgb/xproto" - "github.com/BurntSushi/xgb/xv" + "github.com/wwfx/xgb/xproto" + "github.com/wwfx/xgb/xv" ) // Init must be called before using the XVideo-MotionCompensation extension.