1
0
mirror of https://github.com/golang/go synced 2024-11-22 05:14:40 -07:00

A first stab at porting the XCB X11 protocol bindings to go.

The python script needs a checkout of xcb/proto to generate
an xproto.go file, which together with xgb.go provide functions
to access all of the core X11 protocol requests. I have included the
generated file.

Extensions and authentication methods are not implemented.

R=r, rsc, nigeltao_golang
https://golang.org/cl/162053
This commit is contained in:
Tor Andersson 2009-11-30 14:25:50 -08:00 committed by Russ Cox
parent 9d50b468a1
commit 598f3e284e
6 changed files with 5344 additions and 0 deletions

View File

@ -99,6 +99,7 @@ DIRS=\
unicode\
utf8\
websocket\
xgb\
xml\
NOTEST=\
@ -113,6 +114,7 @@ NOTEST=\
runtime\
syscall\
testing/iotest\
xgb\
TEST=\
$(filter-out $(NOTEST),$(DIRS))

20
src/pkg/xgb/Makefile Normal file
View File

@ -0,0 +1,20 @@
# 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.
include ../../Make.$(GOARCH)
TARG=xgb
GOFILES=\
xgb.go\
xproto.go\
include ../../Make.pkg
xproto: proto/src/xproto.xml
python go_client.py -p proto/ proto/src/xproto.xml
gofmt -w xproto.go
proto/src/xproto.xml:
git clone git://anongit.freedesktop.org/git/xcb/proto

67
src/pkg/xgb/example.go Normal file
View File

@ -0,0 +1,67 @@
// 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.
package main
import (
"fmt";
"os";
"xgb";
)
func main() {
c, err := xgb.Dial(os.Getenv("DISPLAY"));
if err != nil {
fmt.Printf("cannot connect: %v\n", err);
os.Exit(1);
}
fmt.Printf("vendor = '%s'\n", string(c.Setup.Vendor));
win := c.NewId();
gc := c.NewId();
c.CreateWindow(0, win, c.DefaultScreen().Root, 150, 150, 200, 200, 0, 0, 0, 0, nil);
c.ChangeWindowAttributes(win, xgb.CWEventMask,
[]uint32{xgb.EventMaskExposure | xgb.EventMaskKeyRelease});
c.CreateGC(gc, win, 0, nil);
c.MapWindow(win);
atom, _ := c.InternAtom(0, "HELLO");
fmt.Printf("atom = %d\n", atom.Atom);
points := make([]xgb.Point, 2);
points[1] = xgb.Point{5, 5};
points[1] = xgb.Point{100, 120};
hosts, _ := c.ListHosts();
fmt.Printf("hosts = %+v\n", hosts);
ecookie := c.ListExtensionsRequest();
exts, _ := c.ListExtensionsReply(ecookie);
for _, name := range exts.Names {
fmt.Printf("exts = '%s'\n", name.Name)
}
for {
reply, err := c.WaitForEvent();
if err != nil {
fmt.Printf("error: %v\n", err);
os.Exit(1);
}
fmt.Printf("event %T\n", reply);
switch event := reply.(type) {
case xgb.ExposeEvent:
c.PolyLine(xgb.CoordModeOrigin, win, gc, points)
case xgb.KeyReleaseEvent:
fmt.Printf("key release!\n");
points[0].X = event.EventX;
points[0].Y = event.EventY;
c.PolyLine(xgb.CoordModeOrigin, win, gc, points);
c.Bell(75);
}
}
c.Close();
}

704
src/pkg/xgb/go_client.py Normal file
View File

@ -0,0 +1,704 @@
#!/usr/bin/env python
# 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.
from xml.etree.cElementTree import *
from os.path import basename, exists
import getopt
import sys
import re
_ns = None
outfile = None
golines = []
def go(fmt, *args):
golines.append(fmt % args)
namere = re.compile('([A-Z0-9][a-z]+|[A-Z0-9]+(?![a-z])|[a-z]+)')
allcaps = re.compile('^[A-Z0-9]+$')
sizeoftab = {
"byte": 1,
"int8": 1,
"uint8": 1,
"int16": 2,
"uint16": 2,
"int32": 4,
"uint32": 4,
"float32": 4,
"float64": 8,
"Id": 4,
"Keysym": 4,
"Timestamp": 4,
}
def sizeof(t):
if t in sizeoftab:
return sizeoftab[t]
return 4
symbols = []
def readsymbols(filename):
symbols.append("XXX Dummy XXX")
if exists(filename):
for line in open(filename, 'r').readlines():
symbols.append(line.strip())
#
# Name munging crap for names, enums and types.
#
mangletab = {
"int8_t": "int8",
"uint8_t": "byte",
"uint16_t": "uint16",
"uint32_t": "uint32",
"int16_t": "int16",
"int32_t": "int32",
"float": "float32",
"double": "float64",
"char": "byte",
"void": "byte",
'VISUALTYPE': 'VisualInfo',
'DEPTH': 'DepthInfo',
'SCREEN': 'ScreenInfo',
'Setup': 'SetupInfo',
'WINDOW': 'Id',
}
def mangle(str):
if str in mangletab:
return mangletab[str]
return str
def camel(str):
return str[0].upper() + str[1:]
def uncamel(str):
return str[0].lower() + str[1:]
def nitem(str):
split = namere.finditer(str)
return ''.join([camel(match.group(0)) for match in split])
def titem(str):
str = mangle(str)
if str in sizeoftab:
return str
if allcaps.match(str):
return str.capitalize()
return nitem(str)
def n(list):
"Mangle name (JoinedCamelCase) and chop off 'xcb' prefix."
if len(list) == 1:
parts = [nitem(list[0])]
else:
parts = [nitem(x) for x in list[1:]]
return ''.join(parts)
def t(list):
"Mangle name (JoinedCamelCase) and chop off 'xcb' prefix. Preserve primitive type names."
if len(list) == 1:
return titem(list[0])
else:
parts = [titem(x) for x in list[1:]]
return ''.join(parts)
#
# Various helper functions
#
def go_type_setup(self, name, postfix):
'''
Sets up all the Go-related state by adding additional data fields to
all Field and Type objects. Here is where we figure out most of our
variable and function names.
Recurses into child fields and list member types.
'''
# Do all the various names in advance
self.c_type = t(name + postfix)
self.c_request_name = n(name)
self.c_reply_name = n(name + ('Reply',))
self.c_reply_type = t(name + ('Reply',))
if not self.is_container:
return
offset = 0
for field in self.fields:
go_type_setup(field.type, field.field_type, ())
if field.type.is_list:
go_type_setup(field.type.member, field.field_type, ())
field.c_field_type = t(field.field_type)
field.c_field_name = n((field.field_name,))
field.c_subscript = '[%d]' % field.type.nmemb if (field.type.nmemb > 1) else ''
field.c_pointer = ' ' if field.type.nmemb == 1 else '[]'
field.c_offset = offset
if field.type.fixed_size():
offset += field.type.size * field.type.nmemb
def go_accessor_length(expr, prefix, iswriting):
'''
Figures out what C code is needed to get a length field.
For fields that follow a variable-length field, use the accessor.
Otherwise, just reference the structure field directly.
'''
prefarrow = '' if prefix == '' else prefix + '.'
if expr.lenfield_name != None:
lenstr = prefarrow + n((expr.lenfield_name,))
if iswriting and lenstr.endswith("Len"):
# chop off ...Len and refer to len(array) instead
return "len(" + lenstr[:-3] + ")"
return "int(" + lenstr + ")"
else:
return str(expr.nmemb)
def go_accessor_expr(expr, prefix, iswriting):
'''
Figures out what C code is needed to get the length of a list field.
Recurses for math operations.
Returns bitcount for value-mask fields.
Otherwise, uses the value of the length field.
'''
lenexp = go_accessor_length(expr, prefix, iswriting)
if expr.op != None:
return '(' + go_accessor_expr(expr.lhs, prefix, iswriting) + ' ' + expr.op + ' ' + go_accessor_expr(expr.rhs, prefix, iswriting) + ')'
elif expr.bitfield:
return 'popCount(' + lenexp + ')'
else:
return lenexp
def go_complex(self, fieldlist=None):
'''
Helper function for handling all structure types.
Called for all structs, requests, replies, events, errors.
'''
if self.is_union:
go('type %s struct /*union */ {', self.c_type)
else:
go('type %s struct {', self.c_type)
if not fieldlist:
fieldlist = self.fields
for field in fieldlist:
if field.type.is_pad:
continue
if field.wire and field.type.fixed_size():
go(' %s %s%s;', field.c_field_name, field.c_subscript, field.c_field_type)
if field.wire and not field.type.fixed_size():
go(' %s []%s;', field.c_field_name, field.c_field_type)
go('}')
go('')
def go_get(dst, ofs, typename, typesize):
dst = "v." + dst
if typesize == 1:
if typename == 'byte':
go('%s = b[%s];', dst, ofs)
else:
go('%s = %s(b[%s]);', dst, typename, ofs)
elif typesize == 2:
if typename == 'uint16':
go('%s = get16(b[%s:]);', dst, ofs)
else:
go('%s = %s(get16(b[%s:]));', dst, typename, ofs)
elif typesize == 4:
if typename == 'uint32':
go('%s = get32(b[%s:]);', dst, ofs)
else:
go('%s = %s(get32(b[%s:]));', dst, typename, ofs)
else:
go('get%s(b[%s:], &%s);', typename, ofs, dst)
def go_get_list(dst, ofs, typename, typesize, count):
if typesize == 1 and typename == 'byte':
go('copy(v.%s[0:%s], b[%s:]);', dst, count, ofs)
else:
go('for i := 0; i < %s; i++ {', count)
go_get(dst + "[i]", ofs + "+i*" + str(typesize), typename, typesize)
go('}')
def go_complex_reader_help(self, fieldlist):
firstvar = 1
total = 0
for field in fieldlist:
fieldname = field.c_field_name
fieldtype = field.c_field_type
if field.wire and field.type.fixed_size():
total = field.c_offset + field.type.size * field.type.nmemb
if field.type.is_pad:
continue
if field.type.nmemb == 1:
go_get(fieldname, field.c_offset, fieldtype, field.type.size)
else:
go_get_list(fieldname, field.c_offset, fieldtype, field.type.size, field.type.nmemb)
if field.wire and not field.type.fixed_size():
lenstr = go_accessor_expr(field.type.expr, 'v', False)
if firstvar:
firstvar = 0
go('offset := %d;', field.c_offset);
else:
go('offset = pad(offset);')
go('v.%s = make([]%s, %s);', fieldname, fieldtype, lenstr)
if fieldtype in sizeoftab:
go_get_list(fieldname, "offset", fieldtype, sizeoftab[fieldtype], "len(v."+fieldname+")")
go('offset += len(v.%s) * %d;', fieldname, sizeoftab[fieldtype])
else:
go('for i := 0; i < %s; i++ {', lenstr)
go(' offset += get%s(b[offset:], &v.%s[i]);', fieldtype, fieldname)
go('}')
if not firstvar:
return 'offset'
return str(total)
def go_complex_reader(self):
go('func get%s(b []byte, v *%s) int {', self.c_type, self.c_type)
go(' return %s;', go_complex_reader_help(self, self.fields))
go('}')
go('')
def structsize(fieldlist):
fixedtotal = 0
for field in fieldlist:
if field.wire and field.type.fixed_size():
fixedtotal += field.type.size * field.type.nmemb
return fixedtotal
def go_put(src, ofs, typename, typesize):
if typesize == 1:
if typename == 'byte':
go('b[%s] = %s;', ofs, src)
else:
go('b[%s] = byte(%s);', ofs, src)
elif typesize == 2:
if typename == 'uint16':
go('put16(b[%s:], %s);', ofs, src)
else:
go('put16(b[%s:], uint16(%s));', ofs, src)
elif typesize == 4:
if typename == 'uint32':
go('put32(b[%s:], %s);', ofs, src)
else:
go('put32(b[%s:], uint32(%s));', ofs, src)
else:
go('put%s(b[%s:], %s);', typename, ofs, src)
def go_complex_writer_help(fieldlist, prefix=''):
prefarrow = '' if prefix == '' else prefix + '.'
for field in fieldlist:
fieldname = prefarrow + field.c_field_name
fieldtype = field.c_field_type
if fieldname.endswith("Len"):
fieldname = "len(%s)" % fieldname[:-3]
fieldtype = "(exp)"
if not field.type.fixed_size():
continue
if field.type.is_expr:
expstr = go_accessor_expr(field.type.expr, prefix, True)
go_put(expstr, field.c_offset, "(exp)", field.type.size)
elif not field.type.is_pad:
if field.type.nmemb == 1:
go_put(fieldname, field.c_offset, fieldtype, field.type.size)
else:
go(' copy(b[%d:%d], %s);', field.c_offset, field.c_offset + field.type.nmemb, fieldname)
def go_complex_writer_arguments(param_fields):
out = []
for field in param_fields:
namestr = field.c_field_name
typestr = field.c_pointer + t(field.field_type)
if typestr == '[]byte' and namestr == 'Name':
typestr = 'string'
out.append(namestr + ' ' + typestr)
go(' ' + ', '.join(out))
def go_complex_writer_arguments_names(param_fields):
out = []
for field in param_fields:
out.append(field.c_field_name)
return ', '.join(out)
def go_complex_writer(self, name, void):
func_name = self.c_request_name
param_fields = []
wire_fields = []
for field in self.fields:
if field.visible:
# _len is taken from the list directly
if not field.field_name.endswith("_len"):
# The field should appear as a call parameter
param_fields.append(field)
if field.wire and not field.auto:
# We need to set the field up in the structure
wire_fields.append(field)
if void:
go('func (c *Conn) %s(', func_name)
go_complex_writer_arguments(param_fields)
go(') {')
else:
go('func (c *Conn) %sRequest(', func_name)
go_complex_writer_arguments(param_fields)
go(') Cookie {')
fixedtotal = structsize(self.fields)
if fixedtotal <= 32:
go(' b := c.scratch[0:%d];', fixedtotal)
else:
go(' b := make([]byte, %d);', fixedtotal)
firstvar = 0
for field in wire_fields:
if not field.type.fixed_size():
if not firstvar:
firstvar = 1
go(' n := %d;', fixedtotal)
go(' n += pad(%s * %d);', go_accessor_expr(field.type.expr, '', True), field.type.size)
if not firstvar:
go(' put16(b[2:], %d);', fixedtotal / 4)
else:
go(' put16(b[2:], uint16(n / 4));')
go(' b[0] = %s;', self.opcode)
go_complex_writer_help(wire_fields)
if not void:
if firstvar:
go(' cookie := c.sendRequest(b);')
else:
go(' return c.sendRequest(b);')
else:
go(' c.sendRequest(b);')
# send extra data
for field in param_fields:
if not field.type.fixed_size():
if field.type.is_list:
fieldname = field.c_field_name
lenstr = go_accessor_expr(field.type.expr, '', True)
if t(field.field_type) == 'byte':
if fieldname == 'Name':
go(' c.sendString(%s);', fieldname)
else:
go(' c.sendBytes(%s[0:%s]);', fieldname, lenstr)
elif t(field.field_type) == 'uint32':
go(' c.sendUInt32List(%s[0:%s]);', fieldname, lenstr)
else:
go(' c.send%sList(%s, %s);', t(field.field_type), fieldname, lenstr)
if not void and firstvar:
go(' return cookie;')
go('}')
go('')
if not void:
args = go_complex_writer_arguments_names(param_fields)
go('func (c *Conn) %s(', func_name)
go_complex_writer_arguments(param_fields)
go(') (*%s, os.Error) {', self.c_reply_type)
go(' return c.%sReply(c.%sRequest(%s));', func_name, func_name, args)
go('}')
go('')
#
# Struct definitions, readers and writers
#
def go_struct(self, name):
go_type_setup(self, name, ())
if symbols and t(name) not in symbols:
go('// excluding struct %s\n', t(name))
return
if self.c_type == 'SetupRequest': return
if self.c_type == 'SetupFailed': return
if self.c_type == 'SetupAuthenticate': return
go_complex(self)
go_complex_reader(self)
if self.c_type == 'Format': return
if self.c_type == 'VisualInfo': return
if self.c_type == 'DepthInfo': return
if self.c_type == 'SetupInfo': return
if self.c_type == 'ScreenInfo': return
# omit variable length struct writers, they're never used
if not self.fixed_size():
go('// omitting variable length send%s', self.c_type)
go('')
return
go('func (c *Conn) send%sList(list []%s, count int) {', self.c_type, self.c_type)
go(' b0 := make([]byte, %d * count);', structsize(self.fields))
go(' for k := 0; k < count; k++ {')
go(' b := b0[k * %d:];', structsize(self.fields))
go_complex_writer_help(self.fields, 'list[k]')
go(' }')
go(' c.sendBytes(b0);')
go('}')
go('')
def go_union(self, name):
pass
#
# Request writers with reply structs and readers where needed
#
def replyfields(self):
l = []
for field in self.fields:
if field.type.is_pad or not field.wire: continue
if field.field_name == 'response_type': continue
if field.field_name == 'sequence': continue
if field.field_name == 'length':
if self.c_reply_name != 'GetImageReply' and self.c_reply_name != 'GetKeyboardMappingReply':
continue
l.append(field)
return l
def go_reply(self, name):
'''
Declares the function that returns the reply structure.
'''
fields = replyfields(self.reply)
go_complex(self.reply, fields)
go('func (c *Conn) %s(cookie Cookie) (*%s, os.Error) {', self.c_reply_name, self.c_reply_type)
go(' b, error := c.waitForReply(cookie);')
go(' if error != nil { return nil, error }')
go(' v := new(%s);', self.c_reply_type)
go_complex_reader_help(self.reply, fields)
go(' return v, nil;')
go('}')
go('')
def go_request(self, name):
'''
Exported function that handles request declarations.
'''
go_type_setup(self, name, ('Request',))
if symbols and n(name) not in symbols:
go('// excluding request %s\n', n(name))
return
if self.reply:
go_complex_writer(self, name, False)
go_type_setup(self.reply, name, ('Reply',))
go_reply(self, name)
else:
go_complex_writer(self, name, True)
#
# Event structs and readers
#
def eventfields(self):
l = []
for field in self.fields:
if field.type.is_pad or not field.wire: continue
if field.field_name == 'response_type': continue
if field.field_name == 'sequence': continue
l.append(field)
return l
eventlist = []
def dumpeventlist():
go('func parseEvent(buf []byte) (Event, os.Error) {')
go(' switch buf[0] {')
for event in eventlist:
go(' case %s: return get%sEvent(buf), nil;', event, event)
go(' }')
go(' return nil, os.NewError("unknown event type");')
go('}')
def go_event(self, name):
'''
Exported function that handles event declarations.
'''
go_type_setup(self, name, ('Event',))
if symbols and t(name) not in symbols:
go('// excluding event %s\n', t(name))
return
eventlist.append(n(name))
go('const %s = %s', t(name), self.opcodes[name])
go('')
fields = eventfields(self)
if self.name == name:
# Structure definition
go_complex(self, fields)
go('func get%s(b []byte) %s {', self.c_type, self.c_type)
go(' var v %s;', self.c_type)
go_complex_reader_help(self, fields)
go(' return v;')
go('}')
go('')
else:
# maybe skip this depending on how it interacts with type switching on interfaces
go('type %s %s', n(name + ('Event',)), n(self.name + ('Event',)))
go('')
go('func get%s(b []byte) %s {', self.c_type, self.c_type)
go(' return (%s)(get%s(b));', n(name + ('Event',)), n(self.name + ('Event',)))
go('}')
go('')
#
# Map simple types to primitive types
#
def go_simple(self, name):
'''
Exported function that handles cardinal type declarations.
These are types which are typedef'd to one of the CARDx's, char, float, etc.
We stick them into the mangletab. Lop off xcb prefix.
'''
go_type_setup(self, name, ())
if self.name != name:
if _ns.is_ext:
name = name[2]
else:
name = name[1]
if name == "KEYSYM":
mangletab[name] = "Keysym"
elif name == "TIMESTAMP":
mangletab[name] = "Timestamp"
elif self.size == 4:
mangletab[name] = "Id"
else:
mangletab[name] = t(self.name)
#
# Dump enums as consts, calculate implicit values instead
# of using iota.
#
def go_enum(self, name):
if symbols and t(name) not in symbols:
go('// excluding enum %s\n', t(name))
return
go('const (')
iota = 0
for (enam, eval) in self.values:
if str(eval) == '':
iota = iota + 1
eval = iota
else:
iota = int(eval)
if name[1] == 'Atom':
s = name[1] + "".join([x.capitalize() for x in enam.split("_")])
else:
s = n(name + (enam,))
go(' %s = %s;', s, eval)
go(')')
go('')
errorlist = []
def dumperrorlist():
go('var errorNames = map[byte]string{')
for error in errorlist:
go(' Bad%s: "%s",', error, error)
go('}')
go('')
def go_error(self, name):
'''
Exported function that handles error declarations.
'''
errorlist.append(n(name))
go('const Bad%s = %s', n(name), self.opcodes[name])
go('')
#
# Create the go file
#
def go_open(self):
'''
Exported function that handles module open.
Opens the files and writes out the auto-generated code.
'''
global _ns
_ns = self.namespace
go('// This file was generated automatically from %s.', _ns.file)
go('')
go('package xgb')
go('')
go('import "os"')
go('')
if _ns.is_ext:
go('const %s_MAJOR_VERSION = %s', _ns.ext_name.upper(), _ns.major_version)
go('const %s_MINOR_VERSION = %s', _ns.ext_name.upper(), _ns.minor_version)
go('')
def go_close(self):
'''
Exported function that handles module close.
'''
global outfile
if len(eventlist) > 0:
dumpeventlist()
if len(errorlist) > 0:
dumperrorlist()
if not outfile:
outfile = '%s.go' % _ns.header
gofile = open(outfile, 'w')
for line in golines:
gofile.write(line)
gofile.write('\n')
gofile.close()
# Main routine starts here
# Must create an "output" dictionary before any xcbgen imports.
output = {'open' : go_open,
'close' : go_close,
'simple' : go_simple,
'enum' : go_enum,
'struct' : go_struct,
'union' : go_union,
'request' : go_request,
'event' : go_event,
'error' : go_error
}
# Boilerplate below this point
# Check for the argument that specifies path to the xcbgen python package.
try:
opts, args = getopt.getopt(sys.argv[1:], 'p:s:o:')
except getopt.GetoptError, err:
print str(err)
print 'Usage: go_client.py [-p path] [-s symbol_list_file] [-o output.go] file.xml'
sys.exit(1)
for (opt, arg) in opts:
if opt == '-p':
sys.path.append(arg)
if opt == '-s':
readsymbols(arg)
if opt == '-o':
outfile = arg
# Import the module class
try:
from xcbgen.state import Module
except ImportError:
print 'Failed to load the xcbgen Python package!'
print 'Make sure that xcb/proto installed it on your Python path,'
print 'or pass the path with -p.'
print ''
raise
module = Module(args[0], output)
module.register()
module.resolve()
module.generate()

398
src/pkg/xgb/xgb.go Normal file
View File

@ -0,0 +1,398 @@
// 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 {
conn net.Conn;
nextId Id;
nextCookie Cookie;
replies map[Cookie][]byte;
events queue;
err os.Error;
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]) << 32;
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.
// The display string is typically taken from os.Getenv("DISPLAY").
func Dial(display string) (*Conn, os.Error) {
var err os.Error;
c := new(Conn);
if display[0] == '/' {
c.conn, err = net.Dial("unix", "", display);
if err != nil {
fmt.Printf("cannot connect: %v\n", err);
return nil, err;
}
} else {
parts := strings.Split(display, ":", 2);
host := parts[0];
port := 0;
if len(parts) > 1 {
parts = strings.Split(parts[1], ".", 2);
port, _ = strconv.Atoi(parts[0]);
if len(parts) > 1 {
c.defaultScreen, _ = strconv.Atoi(parts[1])
}
}
display = fmt.Sprintf("%s:%d", host, port+6000);
c.conn, err = net.Dial("tcp", "", display);
if err != nil {
fmt.Printf("cannot connect: %v\n", err);
return nil, err;
}
}
// TODO: get these from .Xauthority
var authName, authData []byte;
buf := make([]byte, 12+pad(len(authName))+pad(len(authData)));
buf[0] = 'l';
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:], 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;
}

4153
src/pkg/xgb/xproto.go Normal file

File diff suppressed because it is too large Load Diff