code: purgatorio

ref: a920c765f2b4130590fb5971a50690b21664957a
dir: /appl/lib/wmlib.b/

View raw version
implement Wmlib;

#
# Copyright © 2003 Vita Nuova Holdings Limited
#

# basic window manager functionality, used by
# tkclient and wmclient to create more usable functionality.

include "sys.m";
	sys: Sys;
include "draw.m";
	draw: Draw;
	Display, Image, Screen, Rect, Point, Pointer, Wmcontext, Context: import draw;
include "wmsrv.m";
include "wmlib.m";

Client: adt{
	ptrpid:	int;
	kbdpid:	int;
	ctlpid:	int;
	req:		chan of (array of byte, Sys->Rwrite);
	dir:		string;
	ctlfd:		ref Sys->FD;
	winfd:	ref Sys->FD;
};

DEVWM: con "/mnt/wm";
Ptrsize: con 1+4*12;		# 'm' plus 4 12-byte decimal integers

kbdstarted: int;
ptrstarted: int;
wptr: chan of Point;		# set mouse position (only if we've opened /dev/pointer directly)
cswitch: chan of (string, int, chan of string);	# switch cursor images (as for wptr)

init()
{
	sys = load Sys Sys->PATH;
	draw = load Draw Draw->PATH;
}

#	(_screen, dispi) := ctxt.display.getwindow("/dev/winname", nil, nil, 1); XXX corrupts heap... fix it!

makedrawcontext(): ref Draw->Context
{
	display := Display.allocate(nil);
	if(display == nil){
		sys->fprint(sys->fildes(2), "wmlib: can't allocate Display: %r\n");
		raise "fail:no display";
	}
	return ref Draw->Context(display, nil, nil);
}

importdrawcontext(devdraw, mntwm: string): (ref Draw->Context, string)
{
	if(mntwm == nil)
		mntwm = "/mnt/wm";

	display := Display.allocate(devdraw);
	if(display == nil)
		return (nil, sys->sprint("cannot allocate display: %r"));
	(ok, nil) := sys->stat(mntwm + "/clone");
	if(ok == -1)
		return (nil, "cannot find wm namespace");
	wc := chan of (ref Draw->Context, string);
	spawn wmproxy(display, mntwm, wc);
	return <-wc;
}

# XXX we have no way of knowing when this process should go away...
# perhaps a Draw->Context should hold a file descriptor
# so that we do.
wmproxy(display: ref Display, dir: string, wc: chan of (ref Draw->Context, string))
{
	wmsrv := load Wmsrv Wmsrv->PATH;
	if(wmsrv == nil){
		wc <-= (nil, sys->sprint("cannot load %s: %r", Wmsrv->PATH));
		return;
	}
	sys->pctl(Sys->NEWFD, 1 :: 2 :: nil);

	(wm, join, req) := wmsrv->init();
	if(wm == nil){
		wc <-= (nil, sys->sprint("%r"));
		return;
	}
	wc <-= (ref Draw->Context(display, nil, wm), nil);

	clients: array of ref Client;
	for(;;) alt{
	(sc, rc) := <-join =>
		sync := chan of (ref Client, string);
		spawn clientproc(display, sc, dir, sync);
		(c, err) := <-sync;
		rc <-= err;
		if(c != nil){
			if(sc.id >= len clients)
				clients = (array[sc.id + 1] of ref Client)[0:] = clients;
			clients[sc.id] = c;
		}
	(sc, data, rc) := <-req =>
		clients[sc.id].req <-= (data, rc);
		if(rc == nil)
			clients[sc.id] = nil;
	}
}

zclient: Client;
clientproc(display: ref Display, sc: ref Wmsrv->Client, dir: string, rc: chan of (ref Client, string))
{
	ctlfd := sys->open(dir + "/clone", Sys->ORDWR);
	if(ctlfd == nil){
		rc <-= (nil, sys->sprint("cannot open %s/clone: %r", dir));
		return;
	}
	buf := array[20] of byte;
	n := sys->read(ctlfd, buf, len buf);
	if(n <= 0){
		rc <-= (nil, "cannot read ctl id");
		return;
	}
	sys->fprint(ctlfd, "fixedorigin");
	dir += "/" + string buf[0:n];
	c := ref zclient;
	c.req = chan of (array of byte, Sys->Rwrite);
	c.dir = dir;
	c.ctlfd = ctlfd;
	if ((c.winfd = sys->open(dir + "/winname", Sys->OREAD)) == nil){
		rc <-= (nil, sys->sprint("cannot open %s/winname: %r", dir));
		return;
	}
	rc <-= (c, nil);

	pidc := chan of int;
	spawn ctlproc(pidc, ctlfd, sc.ctl);
	c.ctlpid = <-pidc;
	for(;;) {
		(data, drc) := <-c.req;
		if(drc == nil)
			break;
		err := handlerequest(display, c, sc, data);
		n = len data;
		if(err != nil)
			n = -1;
		alt{
		drc <-= (n, err) =>;
		* =>;
		}
	}
	sc.stop <-= 1;
	kill(c.kbdpid, "kill");
	kill(c.ptrpid, "kill");
	kill(c.ctlpid, "kill");
	c.ctlfd = nil;
	c.winfd = nil;
}

handlerequest(display: ref Display, c: ref Client, sc: ref Wmsrv->Client, data: array of byte): string
{
	req := string data;
	if(req == nil)
		return nil;
	(w, e) := qword(req, 0);
	case w {
	"start" =>
		(w, e) = qword(req, e);
		case w {
		"ptr" or
		"mouse" =>
			if(c.ptrpid == -1)
				return "already started";
			fd := sys->open(c.dir + "/pointer", Sys->OREAD);
			if(fd == nil)
				return sys->sprint("cannot open %s: %r", c.dir + "/pointer");
			sync := chan of int;
			spawn ptrproc(sync, fd, sc.ptr);
			c.ptrpid = <-sync;
			return nil;
		"kbd" =>
			if(c.kbdpid == -1)
				return "already started";
			sync := chan of (int, string);
			spawn kbdproc(sync, c.dir + "/keyboard", sc.kbd);
			(pid, err) := <-sync;
			c.kbdpid = pid;
			return err;
		}
	}

	if(sys->write(c.ctlfd, data, len data) == -1)
		return sys->sprint("%r");
	if(req[0] == '!'){
		buf := array[100] of byte;
		n := sys->read(c.winfd, buf, len buf);
		if(n <= 0)
			return sys->sprint("read winname: %r");
		name := string buf[0:n];
		# XXX this is the dodgy bit...
		i := display.namedimage(name);
		if(i == nil)
			return sys->sprint("cannot get image %#q: %r", name);
		s := Screen.allocate(i, display.white, 0);
		i = s.newwindow(i.r, Draw->Refnone, Draw->Nofill);
		rc := chan of int;
		sc.images <-= (nil, i, rc);
		if(<-rc == -1)
			return "image request already in progress";
	}
	return nil;
}

connect(ctxt: ref Context): ref Wmcontext
{
	# don't automatically make a new Draw->Context, 'cos the
	# client should be aware that there's no wm so multiple
	# windows won't work correctly.
	# ... unless there's an exported wm available, of course!
	if(ctxt == nil){
		sys->fprint(sys->fildes(2), "wmlib: no draw context\n");
		raise "fail:error";
	}
	if(ctxt.wm == nil){
		wm := ref Wmcontext(
			chan of int,
			chan of ref Draw->Pointer,
			chan of string,
			nil,	# unused
			chan of ref Image,
			nil,
			ctxt
		);
		return wm;
	}
	fd := sys->open("/chan/wmctl", Sys->ORDWR);
	if(fd == nil){
		sys->fprint(sys->fildes(2), "wmlib: cannot open /chan/wmctl: %r\n");
		raise "fail:error";
	}
	buf := array[32] of byte;
	n := sys->read(fd, buf, len buf);
	if(n < 0){
		sys->fprint(sys->fildes(2), "wmlib: cannot get window token: %r\n");
		raise "fail:error";
	}
	reply := chan of (string, ref Wmcontext);
	ctxt.wm <-= (string buf[0:n], reply);
	(err, wm) := <-reply;
	if(err != nil){
		sys->fprint(sys->fildes(2), "wmlib: cannot connect: %s\n", err);
		raise "fail:" + err;
	}
	wm.connfd = fd;
	wm.ctxt = ctxt;
	return wm;
}

startinput(wm: ref Wmcontext, devs: list of string): string
{
	for(; devs != nil; devs = tl devs)
		wmctl(wm, "start " + hd devs);
	return nil;
}

reshape(wm: ref Wmcontext, name: string, r: Draw->Rect, i: ref Draw->Image, how: string): ref Draw->Image
{
	if(name == nil)
		return nil;
	(nil, ni, err) := wmctl(wm, sys->sprint("!reshape %s -1 %d %d %d %d %s", name, r.min.x, r.min.y, r.max.x, r.max.y, how));
	if(err == nil)
		return ni;
	return i;
}

#
# wmctl implements the default window behaviour
#
wmctl(wm: ref Wmcontext, request: string): (string, ref Image, string)
{
	(w, e) := qword(request, 0);
	case w {
	"exit" =>
		kill(sys->pctl(0, nil), "killgrp");
		exit;
	* =>
		if(wm.connfd != nil){
			# standard form for requests: if request starts with '!',
			# then the next word gives the tag of the window that the
			# request applies to, and a new image is provided.
			if(sys->fprint(wm.connfd, "%s", request) == -1){
				sys->fprint(sys->fildes(2), "wmlib: wm request '%s' failed\n", request);
				return (nil, nil, sys->sprint("%r"));
			}
			if(request[0] == '!'){
				i := <-wm.images;
				if(i == nil)
					i = <-wm.images;
				return (qword(request, e).t0, i, nil);
			}
			return (nil, nil, nil);
		}
		# requests we can handle ourselves, if we have to.
		case w{
		"start" =>
			(w, e) = qword(request, e);
			case w{
			"ptr" or
			"mouse" =>
				if(!ptrstarted){
					fd := sys->open("/dev/pointer", Sys->ORDWR);
					if(fd != nil)
						wptr = chan of Point;
					else
						fd = sys->open("/dev/pointer", Sys->OREAD);
					if(fd == nil)
						return (nil, nil, sys->sprint("cannot open /dev/pointer: %r"));
					cfd := sys->open("/dev/cursor", Sys->OWRITE);
					if(cfd != nil)
						cswitch = chan of (string, int, chan of string);
					spawn wptrproc(fd, cfd);
					sync := chan of int;
					spawn ptrproc(sync, fd, wm.ptr);
					<-sync;
					ptrstarted = 1;
				}
			"kbd" =>
				if(!kbdstarted){
					sync := chan of (int, string);
					spawn kbdproc(sync, "/dev/keyboard", wm.kbd);
					(nil, err) := <-sync;
					if(err != nil)
						return (nil, nil, err);
					spawn sendreq(wm.ctl, "haskbdfocus 1");
					kbdstarted = 1;
				}
			* =>
				return (nil, nil, "unknown input source");
			}
			return (nil, nil, nil);
		"ptr" =>
			if(wptr == nil)
				return (nil, nil, "cannot change mouse position");
			p: Point;
			(w, e) = qword(request, e);
			p.x = int w;
			(w, e) = qword(request, e);
			p.y = int w;
			wptr <-= p;
			return (nil, nil, nil);
		"cursor" =>
			if(cswitch == nil)
				return (nil, nil, "cannot switch cursor");
			cswitch <-= (request, e, reply := chan of string);
			return (nil, nil, <-reply);
		* =>
			return (nil, nil, "unknown wmctl request");
		}
	}
}

sendreq(c: chan of string, s: string)
{
	c <-= s;
}

ctlproc(sync: chan of int, fd: ref Sys->FD, ctl: chan of string)
{
	sync <-= sys->pctl(0, nil);
	buf := array[4096] of byte;
	while((n := sys->read(fd, buf, len buf)) > 0)
		ctl <-= string buf[0:n];
}

kbdproc(sync: chan of (int, string), f: string, keys: chan of int)
{
	sys->pctl(Sys->NEWFD, nil);
	fd := sys->open(f, Sys->OREAD);
	if(fd == nil){
		sync <-= (-1, sys->sprint("cannot open /dev/keyboard: %r"));
		return;
	}
	sync <-= (sys->pctl(0, nil), nil);
	buf := array[12] of byte;
	while((n := sys->read(fd, buf, len buf)) > 0){
		s := string buf[0:n];
		for(j := 0; j < len s; j++)
			keys <-= int s[j];
	}
}

wptrproc(pfd, cfd: ref Sys->FD)
{
	if(wptr == nil && cswitch == nil)
		return;
	if(wptr == nil)
		wptr = chan of Point;
	if(cswitch == nil)
		cswitch = chan of (string, int, chan of string);
	for(;;)alt{
	p := <-wptr =>
		sys->fprint(pfd, "m%11d %11d", p.x, p.y);
	(c, start, reply) := <-cswitch =>
		buf: array of byte;
		if(start == len c){
			buf = array[0] of byte;
		}else{
			hot, size: Point;
			(w, e) := qword(c, start);
			hot.x = int w;
			(w, e) = qword(c, e);
			hot.y = int w;
			(w, e) = qword(c, e);
			size.x = int w;
			(w, e) = qword(c, e);
			size.y = int w;
			((d0, d1), nil) := splitqword(c, e);
			nb := size.x/8*size.y;
			if(d1 - d0 != nb * 2){
				reply <-= "inconsistent cursor image data";
				break;
			}
			buf = array[4*4 + nb] of byte;
			bplong(buf, 0*4, hot.x);
			bplong(buf, 1*4, hot.y);
			bplong(buf, 2*4, size.x);
			bplong(buf, 3*4, size.y);
			j := 4*4;
			for(i := d0; i < d1; i += 2)
				buf[j++] = byte ((hexc(c[i]) << 4) | hexc(c[i+1]));
		}
		if(sys->write(cfd, buf, len buf) != len buf)
			reply <-= sys->sprint("%r");
		else
			reply <-= nil;
	}
}

hexc(c: int): int
{
	if(c >= '0' && c <= '9')
		return c - '0';
	if(c >= 'a' && c <= 'f')
		return c - 'a' + 10;
	if(c >= 'A' && c <= 'F')
		return c - 'A' + 10;
	return 0;
}

bplong(d: array of byte, o: int, x: int)
{
	d[o] = byte x;
	d[o+1] = byte (x >> 8);
	d[o+2] = byte (x >> 16);
	d[o+3] = byte (x >> 24);
}

ptrproc(sync: chan of int, fd: ref Sys->FD, ptr: chan of ref Draw->Pointer)
{
	sync <-= sys->pctl(0, nil);

	b:= array[Ptrsize] of byte;
	while(sys->read(fd, b, len b) > 0){
		p := bytes2ptr(b);
		if(p != nil)
			ptr <-= p;
	}
}

bytes2ptr(b: array of byte): ref Pointer
{
	if(len b < Ptrsize || int b[0] != 'm')
		return nil;
	x := int string b[1:13];
	y := int string b[13:25];
	but := int string b[25:37];
	msec := int string b[37:49];
	return ref Pointer (but, (x, y), msec);
}

snarfbuf: string;		# at least we get *something* when there's no wm.

snarfget(): string
{
	fd := sys->open("/chan/snarf", sys->OREAD);
	if(fd == nil)
		return snarfbuf;

	buf := array[8192] of byte;
	nr := 0;
	while ((n := sys->read(fd, buf[nr:], len buf - nr)) > 0) {
		nr += n;
		if (nr == len buf) {
			nbuf := array[len buf * 2] of byte;
			nbuf[0:] = buf;
			buf = nbuf;
		}
	}
	return string buf[0:nr];
}

snarfput(buf: string)
{
	fd := sys->open("/chan/snarf", sys->OWRITE);
	if(fd != nil)
		sys->fprint(fd, "%s", buf);
	else
		snarfbuf = buf;
}

# return (qslice, end).
# the slice has a leading quote if the word is quoted; it does not include the terminating quote.
splitqword(s: string, start: int): ((int, int), int)
{
	for(; start < len s; start++)
		if(s[start] != ' ')
			break;
	if(start >= len s)
		return ((start, start), start);
	i := start;
	end := -1;
	if(s[i] == '\''){
		gotq := 0;
		for(i++; i < len s; i++){
			if(s[i] == '\''){
				if(i + 1 >= len s || s[i + 1] != '\''){
					end = i+1;
					break;
				}
				i++;
				gotq = 1;
			}
		}
		if(!gotq && i > start+1)
			start++;
		if(end == -1)
			end = i;
	} else {
		for(; i < len s; i++)
			if(s[i] == ' ')
				break;
		end = i;
	}
	return ((start, i), end);
}

# unquote a string slice as returned by sliceqword.
qslice(s: string, r: (int, int)): string
{
	if(r.t0 == r.t1)
		return nil;
	if(s[r.t0] != '\'')
		return s[r.t0:r.t1];
	t := "";
	for(i := r.t0 + 1; i < r.t1; i++){
		t[len t] = s[i];
		if(s[i] == '\'')
			i++;
	}
	return t;
}

qword(s: string, start: int): (string, int)
{
	(w, next) := splitqword(s, start);
	return (qslice(s, w), next);
}

s2r(s: string, e: int): (Rect, int)
{
	r: Rect;
	w: string;
	(w, e) = qword(s, e);
	r.min.x = int w;
	(w, e) = qword(s, e);
	r.min.y = int w;
	(w, e) = qword(s, e);
	r.max.x = int w;
	(w, e) = qword(s, e);
	r.max.y = int w;
	return (r, e);
}

kill(pid: int, note: string): int
{
	fd := sys->open("#p/"+string pid+"/ctl", Sys->OWRITE);
	if(fd == nil)		# dodgy failover
		fd = sys->open("/prog/"+string pid+"/ctl", Sys->OWRITE);
	if(fd == nil || sys->fprint(fd, "%s", note) < 0)
		return -1;
	return 0;
}