reworking xgb. cleaned up connection stuff a little. making new xid generation cleaner and use goroutines for it.

This commit is contained in:
Andrew Gallant (Ocelot) 2012-05-03 22:47:50 -04:00
parent 5cdae5950c
commit a5d4ad6c9d
5 changed files with 316 additions and 174 deletions

View File

@ -44,8 +44,10 @@ func getString(r io.Reader, b []byte) (string, error) {
// readAuthority reads the X authority file for the DISPLAY. // readAuthority reads the X authority file for the DISPLAY.
// If hostname == "" or hostname == "localhost", // If hostname == "" or hostname == "localhost",
// readAuthority uses the system's hostname (as returned by os.Hostname) instead. // then use the system's hostname (as returned by os.Hostname) instead.
func readAuthority(hostname, display string) (name string, data []byte, err error) { 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 // 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). // (i.e. it should be able to hold a hostname).
var b [256]byte var b [256]byte

161
nexgb/conn.go Normal file
View File

@ -0,0 +1,161 @@
package xgb
import (
"errors"
"fmt"
"io"
"net"
"os"
"strconv"
"strings"
)
// connect connects to the X server given in the 'display' string.
// If 'display' is empty it will be taken from os.Getenv("DISPLAY").
// Note that you should read and understand the "Connection Setup" of the
// X Protocol Reference Manual before changing this function:
// http://goo.gl/4zGQg
func (c *Conn) connect(display string) error {
err := c.dial(display)
if err != nil {
return 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 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 err
}
head := make([]byte, 8)
if _, err = io.ReadFull(c.conn, head[0:8]); err != nil {
return 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 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 err
}
if code == 0 {
reason := buf[8 : 8+reasonLen]
return errors.New(fmt.Sprintf("x protocol authentication refused: %s",
string(reason)))
}
ReadSetupInfo(buf, &c.Setup)
if c.defaultScreen >= len(c.Setup.Roots) {
c.defaultScreen = 0
}
return nil
}
func (c *Conn) dial(display string) error {
if len(display) == 0 {
display = os.Getenv("DISPLAY")
}
display0 := display
if len(display) == 0 {
return errors.New("empty display string")
}
colonIdx := strings.LastIndex(display, ":")
if colonIdx < 0 {
return errors.New("bad display string: " + display0)
}
var protocol, socket string
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 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 errors.New("bad display string: " + display0)
}
if len(scr) != 0 {
c.defaultScreen, err = strconv.Atoi(scr)
if err != nil {
return 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 errors.New("cannot connect to " + display0 + ": " + err.Error())
}
return nil
}

27
nexgb/examples/atom.go Normal file
View File

@ -0,0 +1,27 @@
package main
import (
// "fmt"
"log"
"github.com/BurntSushi/xgb"
)
func init() {
log.SetFlags(0)
}
func main() {
X, err := xgb.NewConn()
if err != nil {
log.Fatal(err)
}
aname := "_NET_ACTIVE_WINDOW"
atom, err := X.InternAtom(true, uint16(len(aname)), aname)
if err != nil {
log.Fatal(err)
}
log.Printf("%d", atom.Atom)
}

View File

@ -0,0 +1,39 @@
package main
import (
"log"
"github.com/BurntSushi/xgb"
)
func init() {
log.SetFlags(0)
}
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
}
func main() {
X, err := xgb.NewConn()
if err != nil {
log.Fatal(err)
}
root := X.DefaultScreen().Root
aname := "_NET_ACTIVE_WINDOW"
atom, err := X.InternAtom(true, uint16(len(aname)), aname)
if err != nil {
log.Fatal(err)
}
reply, err := X.GetProperty(false, root, atom.Atom, xgb.GetPropertyTypeAny,
0, (1<<32)-1)
log.Printf("%X", get32(reply.Value))
}

View File

@ -12,7 +12,6 @@ import (
"io" "io"
"net" "net"
"os" "os"
"strconv"
"strings" "strings"
"sync" "sync"
) )
@ -23,11 +22,9 @@ const (
) )
// A Conn represents a connection to an X server. // A Conn represents a connection to an X server.
// Only one goroutine should use a Conn's methods at a time.
type Conn struct { type Conn struct {
host string host string
conn net.Conn conn net.Conn
nextId Id
nextCookie uint16 nextCookie uint16
cookies map[uint16]*Cookie cookies map[uint16]*Cookie
events queue events queue
@ -44,6 +41,7 @@ type Conn struct {
eventChan chan bool eventChan chan bool
errorChan chan bool errorChan chan bool
xidChan chan xid
newIdLock sync.Mutex newIdLock sync.Mutex
writeLock sync.Mutex writeLock sync.Mutex
dequeueLock sync.Mutex dequeueLock sync.Mutex
@ -51,6 +49,51 @@ type Conn struct {
extLock sync.Mutex 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.xidChan = make(chan xid, 5)
go conn.generateXids()
conn.nextCookie = 1
conn.cookies = make(map[uint16]*Cookie)
conn.events = queue{make([][]byte, 100), 0, 0}
conn.extensions = make(map[string]byte)
conn.newReadChannels()
conn.newRequestChannels()
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. // Id is used for all X identifiers, such as windows, pixmaps, and GCs.
type Id uint32 type Id uint32
@ -111,14 +154,46 @@ type Error interface {
var newErrorFuncs = map[int]func(buf []byte) Error{} var newErrorFuncs = map[int]func(buf []byte) Error{}
// NewID generates a new unused ID for use with requests like CreateWindow. // NewID generates a new unused ID for use with requests like CreateWindow.
func (c *Conn) NewId() Id { // If no new ids can be generated, the id returned is 0 and error is non-nil.
c.newIdLock.Lock() func (c *Conn) NewId() (Id, error) {
defer c.newIdLock.Unlock() xid := <-c.xidChan
if xid.err != nil {
return 0, xid.err
}
return xid.id, nil
}
id := c.nextId // xid encapsulates a resource identifier being sent over the Conn.xidChan
// TODO: handle ID overflow // channel. If no new resource id can be generated, id is set to -1 and a
c.nextId++ // non-nil error is set in xid.err.
return id 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.
func (conn *Conn) generateXids() {
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,
}
}
} }
// RegisterExtension adds the respective extension's major op code to // RegisterExtension adds the respective extension's major op code to
@ -328,165 +403,3 @@ func (c *Conn) PollForEvent() (Event, error) {
return nil, nil 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)))
}
ReadSetupInfo(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
}