reworking xgb. cleaned up connection stuff a little. making new xid generation cleaner and use goroutines for it.
This commit is contained in:
parent
5cdae5950c
commit
a5d4ad6c9d
@ -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
|
||||
|
161
nexgb/conn.go
Normal file
161
nexgb/conn.go
Normal 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
27
nexgb/examples/atom.go
Normal 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)
|
||||
}
|
||||
|
39
nexgb/examples/property.go
Normal file
39
nexgb/examples/property.go
Normal 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))
|
||||
}
|
||||
|
257
nexgb/xgb.go
257
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
|
||||
}
|
||||
|
||||
id := c.nextId
|
||||
// TODO: handle ID overflow
|
||||
c.nextId++
|
||||
return id
|
||||
// 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
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user