blob: 3f6f0b0a68f1145e7df21ad27a93d0bed4ac070a [file] [log] [blame]
// Copyright 2009 The Go 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 (
"fmt"
"io"
"net"
"os"
"strconv"
"strings"
)
// 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 Cookie
replies map[Cookie][]byte
events queue
err os.Error
display string
defaultScreen int
scratch [32]byte
Setup SetupInfo
}
// Id is used for all X identifiers, such as windows, pixmaps, and GCs.
type Id uint32
// Cookies are the sequence numbers used to pair replies up with their requests
type Cookie uint16
type Keysym uint32
type Timestamp 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{}
// Error contains protocol errors returned to us by the X server.
type Error struct {
Detail uint8
Major uint8
Minor uint16
Cookie Cookie
Id Id
}
func (e *Error) String() string {
return fmt.Sprintf("Bad%s (major=%d minor=%d cookie=%d id=0x%x)",
errorNames[e.Detail], e.Major, e.Minor, e.Cookie, e.Id)
}
// NewID generates a new unused ID for use with requests like CreateWindow.
func (c *Conn) NewId() Id {
id := c.nextId
// TODO: handle ID overflow
c.nextId++
return id
}
// Pad a length to align on 4 bytes.
func pad(n int) int { return (n + 3) & ^3 }
func put16(buf []byte, v uint16) {
buf[0] = byte(v)
buf[1] = byte(v >> 8)
}
func put32(buf []byte, v uint32) {
buf[0] = byte(v)
buf[1] = byte(v >> 8)
buf[2] = byte(v >> 16)
buf[3] = byte(v >> 24)
}
func get16(buf []byte) uint16 {
v := uint16(buf[0])
v |= uint16(buf[1]) << 8
return v
}
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
}
// Voodoo to count the number of bits set in a value list mask.
func popCount(mask0 int) int {
mask := uint32(mask0)
n := 0
for i := uint32(0); i < 32; i++ {
if mask&(1<<i) != 0 {
n++
}
}
return n
}
// A simple queue used to stow away events.
type queue struct {
data [][]byte
a, b int
}
func (q *queue) queue(item []byte) {
if q.b == len(q.data) {
if q.a > 0 {
copy(q.data, q.data[q.a:q.b])
q.a, q.b = 0, q.b-q.a
} else {
newData := make([][]byte, (len(q.data)*3)/2)
copy(newData, q.data)
q.data = newData
}
}
q.data[q.b] = item
q.b++
}
func (q *queue) dequeue() []byte {
if q.a < q.b {
item := q.data[q.a]
q.a++
return item
}
return nil
}
// sendRequest sends a request to the server and return its associated sequence number, or cookie.
// It is only used to send the fixed length portion of the request, sendBytes and friends are used
// to send any additional variable length data.
func (c *Conn) sendRequest(buf []byte) Cookie {
if _, err := c.conn.Write(buf); err != nil {
fmt.Fprintf(os.Stderr, "x protocol write error: %s\n", err)
c.err = err
}
cookie := c.nextCookie
c.nextCookie++
return cookie
}
// sendPadding sends enough bytes to align to a 4-byte border.
// It is used to pad the variable length data that is used with some requests.
func (c *Conn) sendPadding(n int) {
x := pad(n) - n
if x > 0 {
_, err := c.conn.Write(c.scratch[0:x])
if err != nil {
fmt.Fprintf(os.Stderr, "x protocol write error: %s\n", err)
c.err = err
}
}
}
// sendBytes sends a byte slice as variable length data after the fixed portion of a request,
// along with any necessary padding.
func (c *Conn) sendBytes(buf []byte) {
if _, err := c.conn.Write(buf); err != nil {
fmt.Fprintf(os.Stderr, "x protocol write error: %s\n", err)
c.err = err
}
c.sendPadding(len(buf))
}
func (c *Conn) sendString(str string) { c.sendBytes(strings.Bytes(str)) }
// sendUInt32s sends a list of 32-bit integers as variable length data.
func (c *Conn) sendUInt32List(list []uint32) {
buf := make([]byte, len(list)*4)
for i := 0; i < len(list); i++ {
put32(buf[i*4:], list[i])
}
c.sendBytes(buf)
}
func (c *Conn) sendIdList(list []Id, length int) {
buf := make([]byte, length*4)
for i := 0; i < length; i++ {
put32(buf[i*4:], uint32(list[i]))
}
c.sendBytes(buf)
}
func (c *Conn) sendKeysymList(list []Keysym, length int) {
buf := make([]byte, length*4)
for i := 0; i < length; i++ {
put32(buf[i*4:], uint32(list[i]))
}
c.sendBytes(buf)
}
// readNextReply reads and processes the next server reply.
// If it is a protocol error then it is returned as an Error.
// Events are pushed onto the event queue and replies to requests
// are stashed away in a map indexed by the sequence number.
func (c *Conn) readNextReply() os.Error {
buf := make([]byte, 32)
if _, err := io.ReadFull(c.conn, buf); err != nil {
fmt.Fprintf(os.Stderr, "x protocol read error: %s\n", err)
return err
}
switch buf[0] {
case 0:
err := &Error{
Detail: buf[1],
Cookie: Cookie(get16(buf[2:])),
Id: Id(get32(buf[4:])),
Minor: get16(buf[8:]),
Major: buf[10],
}
fmt.Fprintf(os.Stderr, "x protocol error: %s\n", err)
return err
case 1:
seq := Cookie(get16(buf[2:]))
size := get32(buf[4:])
if size > 0 {
bigbuf := make([]byte, 32+size*4, 32+size*4)
copy(bigbuf[0:32], buf)
if _, err := io.ReadFull(c.conn, bigbuf[32:]); err != nil {
fmt.Fprintf(os.Stderr, "x protocol read error: %s\n", err)
return err
}
c.replies[seq] = bigbuf
} else {
c.replies[seq] = buf
}
default:
c.events.queue(buf)
}
return nil
}
// waitForReply looks for a reply in the map indexed by sequence number.
// If the reply is not in the map it will block while reading replies from the server
// until the reply is found or an error occurs.
func (c *Conn) waitForReply(cookie Cookie) ([]byte, os.Error) {
for {
if reply, ok := c.replies[cookie]; ok {
c.replies[cookie] = reply, false
return reply, nil
}
if err := c.readNextReply(); err != nil {
return nil, err
}
}
panic("unreachable")
}
// WaitForEvent returns the next event from the server.
// It will block until an event is available.
func (c *Conn) WaitForEvent() (Event, os.Error) {
for {
if reply := c.events.dequeue(); reply != nil {
return parseEvent(reply)
}
if err := c.readNextReply(); err != nil {
return nil, err
}
}
panic("unreachable")
}
// PollForEvent returns the next event from the server if one is available in the internal queue.
// It will not read from the connection, so you must call WaitForEvent to receive new events.
// Only use this function to empty the queue without blocking.
func (c *Conn) PollForEvent() (Event, os.Error) {
if reply := c.events.dequeue(); reply != nil {
return parseEvent(reply)
}
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, os.Error) {
c, err := connect(display)
if err != nil {
return nil, err
}
// Get authentication data
authName, authData, err := readAuthority(c.host, c.display)
if err != nil {
return nil, err
}
// Assume that the authentication protocol is "MIT-MAGIC-COOKIE-1".
if authName != "MIT-MAGIC-COOKIE-1" || len(authData) != 16 {
return nil, os.NewError("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:], strings.Bytes(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, os.NewError(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, os.NewError(fmt.Sprintf("x protocol authentication refused: %s", string(reason)))
}
getSetupInfo(buf, &c.Setup)
if c.defaultScreen >= len(c.Setup.Roots) {
c.defaultScreen = 0
}
c.nextId = Id(c.Setup.ResourceIdBase)
c.nextCookie = 1
c.replies = make(map[Cookie][]byte)
c.events = queue{make([][]byte, 100), 0, 0}
return c, nil
}
// Close closes the connection to the X server.
func (c *Conn) Close() { c.conn.Close() }
// DefaultScreen returns the Screen info for the default screen, which is
// 0 or the one given in the display argument to Dial.
func (c *Conn) DefaultScreen() *ScreenInfo { return &c.Setup.Roots[c.defaultScreen] }
// ClientMessageData holds the data from a client message,
// duplicated in three forms because Go doesn't have unions.
type ClientMessageData struct {
Data8 [20]byte
Data16 [10]uint16
Data32 [5]uint32
}
func getClientMessageData(b []byte, v *ClientMessageData) int {
copy(&v.Data8, b)
for i := 0; i < 10; i++ {
v.Data16[i] = get16(b[i*2:])
}
for i := 0; i < 5; i++ {
v.Data32[i] = get32(b[i*4:])
}
return 20
}
func connect(display string) (*Conn, os.Error) {
if len(display) == 0 {
display = os.Getenv("DISPLAY")
}
display0 := display
if len(display) == 0 {
return nil, os.NewError("empty display string")
}
colonIdx := strings.LastIndex(display, ":")
if colonIdx < 0 {
return nil, os.NewError("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, os.NewError("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.Atoui(c.display)
if err != nil {
return nil, os.NewError("bad display string: " + display0)
}
if len(scr) != 0 {
c.defaultScreen, err = strconv.Atoi(scr)
if err != nil {
return nil, os.NewError("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.Uitoa(6000+dispnum))
} else {
c.conn, err = net.Dial("unix", "", "/tmp/.X11-unix/X"+c.display)
}
if err != nil {
return nil, os.NewError("cannot connect to " + display0 + ": " + err.String())
}
return c, nil
}