Plan 9 from Bell Labs’s /usr/web/sources/contrib/ericvh/go-plan9/src/pkg/xgb/xgb.go

Copyright © 2021 Plan 9 Foundation.
Distributed under the MIT License.
Download the Plan 9 distribution.


// 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;
}

Bell Labs OSI certified Powered by Plan 9

(Return to Plan 9 Home Page)

Copyright © 2021 Plan 9 Foundation. All Rights Reserved.
Comments to webmaster@9p.io.