diff options
Diffstat (limited to 'nexgb')
| -rw-r--r-- | nexgb/auth.go | 6 | ||||
| -rw-r--r-- | nexgb/conn.go | 161 | ||||
| -rw-r--r-- | nexgb/examples/atom.go | 27 | ||||
| -rw-r--r-- | nexgb/examples/property.go | 39 | ||||
| -rw-r--r-- | nexgb/xgb.go | 257 | 
5 files changed, 316 insertions, 174 deletions
| diff --git a/nexgb/auth.go b/nexgb/auth.go index 355afeb..ac43e07 100644 --- a/nexgb/auth.go +++ b/nexgb/auth.go @@ -44,8 +44,10 @@ func getString(r io.Reader, b []byte) (string, error) {  // readAuthority reads the X authority file for the DISPLAY.  // If hostname == "" or hostname == "localhost", -// readAuthority uses the system's hostname (as returned by os.Hostname) instead. -func readAuthority(hostname, display string) (name string, data []byte, err error) { +// then use the system's hostname (as returned by os.Hostname) instead. +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  	// (i.e. it should be able to hold a hostname).  	var b [256]byte diff --git a/nexgb/conn.go b/nexgb/conn.go new file mode 100644 index 0000000..235402d --- /dev/null +++ b/nexgb/conn.go @@ -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 +} diff --git a/nexgb/examples/atom.go b/nexgb/examples/atom.go new file mode 100644 index 0000000..c64acee --- /dev/null +++ b/nexgb/examples/atom.go @@ -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) +} + diff --git a/nexgb/examples/property.go b/nexgb/examples/property.go new file mode 100644 index 0000000..2477df4 --- /dev/null +++ b/nexgb/examples/property.go @@ -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)) +} + diff --git a/nexgb/xgb.go b/nexgb/xgb.go index 0dd8163..1a4ada1 100644 --- a/nexgb/xgb.go +++ b/nexgb/xgb.go @@ -12,7 +12,6 @@ import (  	"io"  	"net"  	"os" -	"strconv"  	"strings"  	"sync"  ) @@ -23,11 +22,9 @@ const (  )  // 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 @@ -44,6 +41,7 @@ type Conn struct {  	eventChan         chan bool  	errorChan         chan bool +	xidChan chan xid  	newIdLock   sync.Mutex  	writeLock   sync.Mutex  	dequeueLock sync.Mutex @@ -51,6 +49,51 @@ type Conn struct {  	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.  type Id uint32 @@ -111,14 +154,46 @@ type Error interface {  var newErrorFuncs = map[int]func(buf []byte) Error{}  // NewID generates a new unused ID for use with requests like CreateWindow. -func (c *Conn) NewId() Id { -	c.newIdLock.Lock() -	defer c.newIdLock.Unlock() +// 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 -1 and a +// non-nil error is set in xid.err. +type xid struct { +	id Id +	err error +} -	id := c.nextId -	// TODO: handle ID overflow -	c.nextId++ -	return id +// 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 @@ -328,165 +403,3 @@ func (c *Conn) PollForEvent() (Event, error) {  	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 -} | 
