// 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" "strings" "sync" ) const ( readBuffer = 100 writeBuffer = 100 ) // A Conn represents a connection to an X server. type Conn struct { host string conn net.Conn err error display string defaultScreen int Setup SetupInfo extensions map[string]byte eventChan chan eventOrError cookieChan chan cookie xidChan chan xid seqChan chan uint16 reqChan chan *request extLock sync.Mutex } // 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-123/:0") -> net.Dial("unix", "", "/tmp/launch-123/: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 } conn.extensions = make(map[string]byte) conn.cookieChan = make(chan cookie, 100) conn.xidChan = make(chan xid, 5) conn.seqChan = make(chan uint16, 20) conn.reqChan = make(chan *request, 100) conn.eventChan = make(chan eventOrError, 100) go conn.generateXIds() go conn.generateSeqIds() go conn.sendRequests() go conn.readResponses() return conn, nil } // Close closes the connection to the X server. func (c *Conn) Close() { c.conn.Close() } // Id is used for all X identifiers, such as windows, pixmaps, and GCs. type Id uint32 // 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 { ImplementsEvent() Bytes() []byte } // newEventFuncs is a map from event numbers to functions that create // the corresponding event. var newEventFuncs = map[int]func(buf []byte) Event{} // 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 { ImplementsError() SequenceId() uint16 BadId() Id Error() string } // newErrorFuncs is a map from error numbers to functions that create // the corresponding error. var newErrorFuncs = map[int]func(buf []byte) Error{} // 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. func (c *Conn) NewId() (Id, 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 Id err error } // generateXids sends new Ids down the channel for NewId to use. // 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() { // 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.Setup.ResourceIdMask & -conn.Setup.ResourceIdMask max := conn.Setup.ResourceIdMask 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: Id(0), err: errors.New("There are no more available resource" + "identifiers."), } } last += inc conn.xidChan <- xid{ id: Id(last | conn.Setup.ResourceIdBase), 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. // 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. // FIXME: 65,536 requests without replies cannot be made in a single sequence. func (c *Conn) generateSeqIds() { 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 } // newRequest takes the bytes an a cookie, constructs a request type, // and sends it over the Conn.reqChan channel. It then returns the cookie // (for convenience). func (c *Conn) newRequest(buf []byte, cookie cookie) { c.reqChan <- &request{buf: buf, cookie: cookie} } // 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. func (c *Conn) sendRequests() { for req := range c.reqChan { c.cookieChan <- req.cookie if _, err := c.conn.Write(req.buf); err != nil { fmt.Fprintf(os.Stderr, "x protocol write error: %s\n", err) close(c.reqChan) return } } } // 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() { var ( err Error event Event seq uint16 replyBytes []byte ) buf := make([]byte, 32) for { err, event, seq = nil, nil, 0 if _, err := io.ReadFull(c.conn, buf); err != nil { fmt.Fprintf(os.Stderr, "x protocol read error: %s\n", err) close(c.eventChan) break } 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. err = newErrorFuncs[int(buf[1])](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 { fmt.Fprintf(os.Stderr, "x protocol read error: %s\n", err) close(c.eventChan) break } 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). event = newEventFuncs[int(buf[0] & 127)](buf) // seq = event.SequenceId() // 0 for KeymapNotify // Put the event into the queue. c.eventChan <- event // No more processing for events. continue // If this was a KeymapNotify event, then we don't do any more // processing since we don't have any sequence id. // if event != nil { // if _, ok := event.(KeymapNotifyEvent); ok { // 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 } } else { // this is a reply if cookie.replyChan == nil { fmt.Fprintf(os.Stderr, "Reply with sequence id %d does not have a " + "cookie with a valid reply channel.\n", seq) } else { cookie.replyChan <- replyBytes } } break } switch { // Checked requests with replies case cookie.replyChan != nil && cookie.errorChan != nil: fmt.Fprintf(os.Stderr, "Found cookie with sequence id %d that is expecting a " + "reply but will never get it.\n", cookie.Sequence) // Unchecked requests with replies case cookie.replyChan != nil && cookie.pingChan != nil: cookie.pingChan <- true // 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: fmt.Fprintf(os.Stderr, "Invalid event/error type: %T\n", everr) } panic("unreachable") } // WaitForEvent returns the next event from the server. // It will block until an event is available. 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. // It will not block. func (c *Conn) PollForEvent() (Event, Error) { select { case everr := <-c.eventChan: return processEventOrError(everr) default: return nil, nil } panic("unreachable") } // 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(uint16(len(nameUpper)), nameUpper).Reply() 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 }