| package xgb |
| |
| import ( |
| "errors" |
| "io" |
| "log" |
| "net" |
| "os" |
| "sync" |
| ) |
| |
| var ( |
| // Where to log error-messages. Defaults to stderr. |
| // To disable logging, just set this to log.New(ioutil.Discard, "", 0) |
| Logger = log.New(os.Stderr, "XGB: ", log.Lshortfile) |
| |
| // ExtLock is a lock used whenever new extensions are initialized. |
| // It should not be used. It is exported for use in the extension |
| // sub-packages. |
| ExtLock sync.Mutex |
| ) |
| |
| const ( |
| // cookieBuffer represents the queue size of cookies existing at any |
| // point in time. The size of the buffer is really only important when |
| // there are many requests without replies made in sequence. Once the |
| // buffer fills, a round trip request is made to clear the buffer. |
| cookieBuffer = 1000 |
| |
| // xidBuffer represents the queue size of the xid channel. |
| // I don't think this value matters much, since xid generation is not |
| // that expensive. |
| xidBuffer = 5 |
| |
| // seqBuffer represents the queue size of the sequence number channel. |
| // I don't think this value matters much, since sequence number generation |
| // is not that expensive. |
| seqBuffer = 5 |
| |
| // reqBuffer represents the queue size of the number of requests that |
| // can be made until new ones block. This value seems OK. |
| reqBuffer = 100 |
| |
| // eventBuffer represents the queue size of the number of events or errors |
| // that can be loaded off the wire and not grabbed with WaitForEvent |
| // until reading an event blocks. This value should be big enough to handle |
| // bursts of events. |
| eventBuffer = 5000 |
| ) |
| |
| // A Conn represents a connection to an X server. |
| type Conn struct { |
| host string |
| conn net.Conn |
| display string |
| DisplayNumber int |
| DefaultScreen int |
| SetupBytes []byte |
| |
| setupResourceIdBase uint32 |
| setupResourceIdMask uint32 |
| |
| eventChan chan eventOrError |
| cookieChan chan *Cookie |
| xidChan chan xid |
| seqChan chan uint16 |
| reqChan chan *request |
| closing chan chan struct{} |
| |
| // Extensions is a map from extension name to major opcode. It should |
| // not be used. It is exported for use in the extension sub-packages. |
| Extensions map[string]byte |
| } |
| |
| // NewConn creates a new connection instance. It initializes locks, data |
| // structures, and performs the initial handshake. (The code for the handshake |
| // has been relegated to conn.go.) |
| func NewConn() (*Conn, error) { |
| return NewConnDisplay("") |
| } |
| |
| // NewConnDisplay is just like NewConn, but allows a specific DISPLAY |
| // string to be used. |
| // If 'display' is empty it will be taken from os.Getenv("DISPLAY"). |
| // |
| // Examples: |
| // NewConn(":1") -> net.Dial("unix", "", "/tmp/.X11-unix/X1") |
| // NewConn("/tmp/launch-12/:0") -> net.Dial("unix", "", "/tmp/launch-12/:0") |
| // NewConn("hostname:2.1") -> net.Dial("tcp", "", "hostname:6002") |
| // NewConn("tcp/hostname:1.0") -> net.Dial("tcp", "", "hostname:6001") |
| func NewConnDisplay(display string) (*Conn, error) { |
| conn := &Conn{} |
| |
| // First connect. This reads authority, checks DISPLAY environment |
| // variable, and loads the initial Setup info. |
| err := conn.connect(display) |
| if err != nil { |
| return nil, err |
| } |
| |
| return postNewConn(conn) |
| } |
| |
| // NewConnDisplay is just like NewConn, but allows a specific net.Conn |
| // to be used. |
| func NewConnNet(netConn net.Conn) (*Conn, error) { |
| conn := &Conn{} |
| |
| // First connect. This reads authority, checks DISPLAY environment |
| // variable, and loads the initial Setup info. |
| err := conn.connectNet(netConn) |
| |
| if err != nil { |
| return nil, err |
| } |
| |
| return postNewConn(conn) |
| } |
| |
| func postNewConn(conn *Conn) (*Conn, error) { |
| conn.Extensions = make(map[string]byte) |
| |
| conn.cookieChan = make(chan *Cookie, cookieBuffer) |
| conn.xidChan = make(chan xid, xidBuffer) |
| 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) |
| |
| go conn.generateXIds() |
| go conn.generateSeqIds() |
| go conn.sendRequests() |
| go conn.readResponses() |
| |
| return conn, nil |
| } |
| |
| // Close gracefully closes the connection to the X server. |
| func (c *Conn) Close() { |
| close(c.reqChan) |
| } |
| |
| // 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 { |
| Bytes() []byte |
| String() string |
| } |
| |
| // NewEventFun is the type of function use to construct events from raw bytes. |
| // It should not be used. It is exported for use in the extension sub-packages. |
| type NewEventFun func(buf []byte) Event |
| |
| // NewEventFuncs is a map from event numbers to functions that create |
| // the corresponding event. It should not be used. It is exported for use |
| // in the extension sub-packages. |
| var NewEventFuncs = make(map[int]NewEventFun) |
| |
| // NewExtEventFuncs is a temporary map that stores event constructor functions |
| // for each extension. When an extension is initialized, each event for that |
| // extension is added to the 'NewEventFuncs' map. It should not be used. It is |
| // exported for use in the extension sub-packages. |
| var NewExtEventFuncs = make(map[string]map[int]NewEventFun) |
| |
| // Error is an interface that can contain any of the errors returned by |
| // the server. Use a type assertion switch to extract the Error structs. |
| type Error interface { |
| SequenceId() uint16 |
| BadId() uint32 |
| Error() string |
| } |
| |
| // NewErrorFun is the type of function use to construct errors from raw bytes. |
| // It should not be used. It is exported for use in the extension sub-packages. |
| type NewErrorFun func(buf []byte) Error |
| |
| // NewErrorFuncs is a map from error numbers to functions that create |
| // the corresponding error. It should not be used. It is exported for use in |
| // the extension sub-packages. |
| var NewErrorFuncs = make(map[int]NewErrorFun) |
| |
| // NewExtErrorFuncs is a temporary map that stores error constructor functions |
| // for each extension. When an extension is initialized, each error for that |
| // extension is added to the 'NewErrorFuncs' map. It should not be used. It is |
| // exported for use in the extension sub-packages. |
| var NewExtErrorFuncs = make(map[string]map[int]NewErrorFun) |
| |
| // eventOrError corresponds to values that can be either an event or an |
| // error. |
| type eventOrError interface{} |
| |
| // NewId generates a new unused ID for use with requests like CreateWindow. |
| // If no new ids can be generated, the id returned is 0 and error is non-nil. |
| // This shouldn't be used directly, and is exported for use in the extension |
| // sub-packages. |
| // If you need identifiers, use the appropriate constructor. |
| // e.g., For a window id, use xproto.NewWindowId. For |
| // a new pixmap id, use xproto.NewPixmapId. And so on. |
| func (c *Conn) NewId() (uint32, error) { |
| xid := <-c.xidChan |
| if xid.err != nil { |
| return 0, xid.err |
| } |
| return xid.id, nil |
| } |
| |
| // xid encapsulates a resource identifier being sent over the Conn.xidChan |
| // channel. If no new resource id can be generated, id is set to 0 and a |
| // non-nil error is set in xid.err. |
| type xid struct { |
| id uint32 |
| err error |
| } |
| |
| // generateXids sends new Ids down the channel for NewId to use. |
| // generateXids should be run in its own goroutine. |
| // 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) |
| |
| // This requires some explanation. From the horse's mouth: |
| // "The resource-id-mask contains a single contiguous set of bits (at least |
| // 18). The client allocates resource IDs for types WINDOW, PIXMAP, |
| // CURSOR, FONT, GCONTEXT, and COLORMAP by choosing a value with only some |
| // subset of these bits set and ORing it with resource-id-base. Only values |
| // constructed in this way can be used to name newly created resources over |
| // this connection." |
| // So for example (using 8 bit integers), the mask might look like: |
| // 00111000 |
| // So that valid values would be 00101000, 00110000, 00001000, and so on. |
| // Thus, the idea is to increment it by the place of the last least |
| // significant '1'. In this case, that value would be 00001000. To get |
| // that value, we can AND the original mask with its two's complement: |
| // 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 |
| last := uint32(0) |
| for { |
| // TODO: Use the XC Misc extension to look for released ids. |
| if last > 0 && last >= max-inc+1 { |
| conn.xidChan <- xid{ |
| id: 0, |
| err: errors.New("There are no more available resource" + |
| "identifiers."), |
| } |
| } |
| |
| last += inc |
| conn.xidChan <- xid{ |
| id: last | conn.setupResourceIdBase, |
| err: nil, |
| } |
| } |
| } |
| |
| // newSeqId fetches the next sequence id from the Conn.seqChan channel. |
| func (c *Conn) newSequenceId() uint16 { |
| return <-c.seqChan |
| } |
| |
| // generateSeqIds returns new sequence ids. It is meant to be run in its |
| // own goroutine. |
| // A sequence id is generated for *every* request. It's the identifier used |
| // to match up replies with requests. |
| // Since sequence ids can only be 16 bit integers we start over at zero when it |
| // comes time to wrap. |
| // 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 close(c.seqChan) |
| |
| seqid := uint16(1) |
| for { |
| c.seqChan <- seqid |
| if seqid == uint16((1<<16)-1) { |
| seqid = 0 |
| } else { |
| seqid++ |
| } |
| } |
| } |
| |
| // request encapsulates a buffer of raw bytes (containing the request data) |
| // and a cookie, which when combined represents a single request. |
| // The cookie is used to match up the reply/error. |
| type request struct { |
| buf []byte |
| cookie *Cookie |
| |
| // seq is closed when the request (cookie) has been sequenced by the Conn. |
| seq chan struct{} |
| } |
| |
| // NewRequest takes the bytes and a cookie of a particular request, constructs |
| // a request type, and sends it over the Conn.reqChan channel. |
| // Note that the sequence number is added to the cookie after it is sent |
| // over the request channel, but before it is sent to X. |
| // |
| // Note that you may safely use NewRequest to send arbitrary byte requests |
| // to X. The resulting cookie can be used just like any normal cookie and |
| // abides by the same rules, except that for replies, you'll get back the |
| // raw byte data. This may be useful for performance critical sections where |
| // every allocation counts, since all X requests in XGB allocate a new byte |
| // slice. In contrast, NewRequest allocates one small request struct and |
| // nothing else. (Except when the cookie buffer is full and has to be flushed.) |
| // |
| // If you're using NewRequest manually, you'll need to use NewCookie to create |
| // a new cookie. |
| // |
| // In all likelihood, you should be able to copy and paste with some minor |
| // 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 |
| } |
| |
| // sendRequests is run as a single goroutine that takes requests and writes |
| // the bytes to the wire and adds the cookie to the cookie queue. |
| // It is meant to be run as its own goroutine. |
| func (c *Conn) sendRequests() { |
| defer close(c.cookieChan) |
| |
| 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 |
| } |
| } |
| req.cookie.Sequence = c.newSequenceId() |
| close(req.seq) |
| c.cookieChan <- req.cookie |
| c.writeBuffer(req.buf) |
| } |
| 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 |
| // trip request manually. |
| func (c *Conn) noop() error { |
| cookie := c.NewCookie(true, true) |
| cookie.Sequence = c.newSequenceId() |
| c.cookieChan <- cookie |
| if err := c.writeBuffer(c.getInputFocusRequest()); err != nil { |
| return err |
| } |
| cookie.Reply() // wait for the buffer to clear |
| return nil |
| } |
| |
| // writeBuffer is a convenience function for writing a byte slice to the wire. |
| 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 |
| } |
| } |
| |
| // readResponses is a goroutine that reads events, errors and |
| // replies off the wire. |
| // When an event is read, it is always added to the event channel. |
| // When an error is read, if it corresponds to an existing checked cookie, |
| // it is sent to that cookie's error channel. Otherwise it is added to the |
| // event channel. |
| // When a reply is read, it is added to the corresponding cookie's reply |
| // 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 close(c.eventChan) |
| |
| var ( |
| err Error |
| seq uint16 |
| replyBytes []byte |
| ) |
| |
| for { |
| select { |
| case respond := <-c.closing: |
| respond <- struct{}{} |
| 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() |
| continue |
| } |
| switch buf[0] { |
| case 0: // This is an error |
| // Use the constructor function for this error (that is auto |
| // generated) by looking it up by the error number. |
| newErrFun, ok := NewErrorFuncs[int(buf[1])] |
| if !ok { |
| Logger.Printf("BUG: Could not find error constructor function "+ |
| "for error with number %d.", buf[1]) |
| continue |
| } |
| err = newErrFun(buf) |
| seq = err.SequenceId() |
| |
| // This error is either sent to the event channel or a specific |
| // cookie's error channel below. |
| case 1: // This is a reply |
| seq = Get16(buf[2:]) |
| |
| // check to see if this reply has more bytes to be read |
| size := Get32(buf[4:]) |
| if size > 0 { |
| byteCount := 32 + size*4 |
| biggerBuf := make([]byte, byteCount) |
| copy(biggerBuf[:32], buf) |
| if _, err := io.ReadFull(c.conn, biggerBuf[32:]); err != nil { |
| Logger.Printf("A read error is unrecoverable: %s", err) |
| c.eventChan <- err |
| c.Close() |
| continue |
| } |
| replyBytes = biggerBuf |
| } else { |
| replyBytes = buf |
| } |
| |
| // This reply is sent to its corresponding cookie below. |
| default: // This is an event |
| // Use the constructor function for this event (like for errors, |
| // and is also auto generated) by looking it up by the event number. |
| // Note that we AND the event number with 127 so that we ignore |
| // the most significant bit (which is set when it was sent from |
| // a SendEvent request). |
| evNum := int(buf[0] & 127) |
| newEventFun, ok := NewEventFuncs[evNum] |
| if !ok { |
| Logger.Printf("BUG: Could not find event construct function "+ |
| "for event with number %d.", evNum) |
| continue |
| } |
| c.eventChan <- newEventFun(buf) |
| continue |
| } |
| |
| // At this point, we have a sequence number and we're either |
| // processing an error or a reply, which are both responses to |
| // requests. So all we have to do is find the cookie corresponding |
| // to this error/reply, and send the appropriate data to it. |
| // In doing so, we make sure that any cookies that came before it |
| // are marked as successful if they are void and checked. |
| // If there's a cookie that requires a reply that is before this |
| // reply, then something is wrong. |
| for cookie := range c.cookieChan { |
| // This is the cookie we're looking for. Process and break. |
| if cookie.Sequence == seq { |
| if err != nil { // this is an error to a request |
| // synchronous processing |
| if cookie.errorChan != nil { |
| cookie.errorChan <- err |
| } else { // asynchronous processing |
| c.eventChan <- err |
| // if this is an unchecked reply, ping the cookie too |
| if cookie.pingChan != nil { |
| cookie.pingChan <- true |
| } |
| } |
| } else { // this is a reply |
| if cookie.replyChan == nil { |
| Logger.Printf("Reply with sequence id %d does not "+ |
| "have a cookie with a valid reply channel.", seq) |
| continue |
| } else { |
| cookie.replyChan <- replyBytes |
| } |
| } |
| break |
| } |
| |
| switch { |
| // Checked requests with replies |
| case cookie.replyChan != nil && cookie.errorChan != nil: |
| Logger.Printf("Found cookie with sequence id %d that is "+ |
| "expecting a reply but will never get it. Currently "+ |
| "on sequence number %d", cookie.Sequence, seq) |
| // Unchecked requests with replies |
| case cookie.replyChan != nil && cookie.pingChan != nil: |
| Logger.Printf("Found cookie with sequence id %d that is "+ |
| "expecting a reply (and not an error) but will never "+ |
| "get it. Currently on sequence number %d", |
| cookie.Sequence, seq) |
| // Checked requests without replies |
| case cookie.pingChan != nil && cookie.errorChan != nil: |
| cookie.pingChan <- true |
| // Unchecked requests without replies don't have any channels, |
| // so we can't do anything with them except let them pass by. |
| } |
| } |
| } |
| } |
| |
| // processEventOrError takes an eventOrError, type switches on it, |
| // and returns it in Go idiomatic style. |
| func processEventOrError(everr eventOrError) (Event, Error) { |
| switch ee := everr.(type) { |
| case Event: |
| return ee, nil |
| case Error: |
| return nil, ee |
| default: |
| Logger.Printf("Invalid event/error type: %T", everr) |
| return nil, nil |
| } |
| } |
| |
| // WaitForEvent returns the next event from the server. |
| // It will block until an event is available. |
| // WaitForEvent returns either an Event or an Error. (Returning both |
| // is a bug.) Note than an Error here is an X error and not an XGB error. That |
| // is, X errors are sometimes completely expected (and you may want to ignore |
| // them in some cases). |
| // |
| // If both the event and error are nil, then the connection has been closed. |
| func (c *Conn) WaitForEvent() (Event, Error) { |
| return processEventOrError(<-c.eventChan) |
| } |
| |
| // PollForEvent returns the next event from the server if one is available in |
| // the internal queue without blocking. Note that unlike WaitForEvent, both |
| // Event and Error could be nil. Indeed, they are both nil when the event queue |
| // is empty. |
| func (c *Conn) PollForEvent() (Event, Error) { |
| select { |
| case everr := <-c.eventChan: |
| return processEventOrError(everr) |
| default: |
| return nil, nil |
| } |
| } |