shithub: fork

Download patch

ref: 04c22e3803910733329fc61859aabb47bdc2a31b
parent: 4baf1f15bd5dc66b815863eed931a9132a70ee7a
author: qwx <[email protected]>
date: Sun Aug 13 23:18:21 EDT 2023

vnc: color regression workaround

diff: cannot open b/sys/src/cmd/vnc//null: file does not exist: 'b/sys/src/cmd/vnc//null'
--- /dev/null
+++ b/sys/src/cmd/vnc/auth.c
@@ -1,0 +1,171 @@
+#include "vnc.h"
+#include <libsec.h>
+#include <auth.h>
+
+int
+vncsrvhandshake(Vnc *v)
+{
+	char msg[VerLen+1];
+
+	vncwrbytes(v, "RFB 003.003\n", VerLen);
+	vncflush(v);
+
+	vncrdbytes(v, msg, VerLen);
+	if(verbose)
+		fprint(2, "client version: %s\n", msg);
+	return 0;
+}
+
+int
+vnchandshake(Vnc *v)
+{
+	char msg[VerLen + 1];
+
+	msg[VerLen] = 0;
+	vncrdbytes(v, msg, VerLen);
+
+	if(verbose)
+		fprint(2, "server version: %s\n", msg);
+
+	if(strncmp(msg, "RFB 003.003\n", VerLen) == 0)
+		v->vers = 33;
+	else if(strncmp(msg, "RFB 003.007\n", VerLen) == 0)
+		v->vers = 37;
+	else if(strncmp(msg, "RFB 003.008\n", VerLen) == 0)
+		v->vers = 38;
+	else if(strncmp(msg, "RFB 003.889\n", VerLen) == 0)
+		v->vers = 38;  /* Darwin */
+	else if(strncmp(msg, "RFB 004.000\n", VerLen) == 0)
+		v->vers = 38;
+	else /* RFC6143: Any other should be treated as 3.3. */
+		v->vers = 33;
+
+	strcpy(msg, "RFB 003.008\n");
+	vncwrbytes(v, msg, VerLen);
+	vncflush(v);
+	return 0;
+}
+
+int
+vncauth(Vnc *v, char *keypattern)
+{
+	uchar chal[VncChalLen];
+	ulong auth, type;
+	int i, ntypes;
+	char *err;
+
+	if(keypattern == nil)
+		keypattern = "";
+
+	auth = AFailed;
+	if(v->vers == 33)
+		auth = vncrdlong(v);
+	else{
+		ntypes = vncrdchar(v);
+		for(i = 0; i < ntypes; i++){
+			type = vncrdchar(v);
+			if(verbose)
+				fprint(2, "auth type %uld\n", type);
+			if(type > auth && type <= AVncAuth)
+				auth = type;
+		}
+		if(auth == AFailed){
+			werrstr("no supported auth types");
+			return -1;
+		}
+	}
+
+	switch(auth){
+	default:
+		werrstr("unknown auth type 0x%lux", auth);
+		if(verbose)
+			fprint(2, "unknown auth type 0x%lux\n", auth);
+		return -1;
+
+	case AFailed:
+		err = vncrdstring(v);
+		werrstr("%s", err);
+		if(verbose)
+			fprint(2, "auth failed: %s\n", err);
+		return -1;
+
+	case ANoAuth:
+		if(v->vers == 38){
+			vncwrchar(v, auth);
+			vncflush(v);
+		}
+		if(verbose)
+			fprint(2, "no auth needed\n");
+		break;
+
+	case AVncAuth:
+		if(v->vers == 38){
+			vncwrchar(v, auth);
+			vncflush(v);
+		}
+
+		vncrdbytes(v, chal, VncChalLen);
+		if(auth_respond(chal, VncChalLen, nil, 0, chal, VncChalLen, auth_getkey,
+			"proto=vnc role=client server=%s %s", v->srvaddr, keypattern) != VncChalLen){
+			return -1;
+		}
+		vncwrbytes(v, chal, VncChalLen);
+		vncflush(v);
+		break;
+	}
+
+	/* in version 3.8 the auth status is always sent, in 3.3 and 3.7, only in AVncAuth */
+	if(v->vers == 38 || auth == AVncAuth){
+		auth = vncrdlong(v); /* auth status */
+		switch(auth){
+		default:
+			werrstr("unknown server response 0x%lux", auth);
+			return -1;
+		case VncAuthFailed:
+			err = (v->vers == 38) ? vncrdstring(v) : "rejected";
+			werrstr("%s", err);
+			if(verbose)
+				fprint(2, "auth failed: %s\n", err);
+			return -1;
+		case VncAuthTooMany:
+			werrstr("server says too many tries");
+			return -1;
+		case VncAuthOK:
+			break;
+		}
+	}
+	return 0;
+}
+
+int
+vncsrvauth(Vnc *v)
+{
+	Chalstate *c;
+	AuthInfo *ai;
+
+	if((c = auth_challenge("proto=vnc role=server user=%q", getuser()))==nil)
+		sysfatal("vncchal: %r");
+	if(c->nchal != VncChalLen)
+		sysfatal("vncchal got %d bytes wanted %d", c->nchal, VncChalLen);
+	vncwrlong(v, AVncAuth);
+	vncwrbytes(v, c->chal, VncChalLen);
+	vncflush(v);
+
+	vncrdbytes(v, c->chal, VncChalLen);
+	c->resp = c->chal;
+	c->nresp = VncChalLen;
+	ai = auth_response(c);
+	auth_freechal(c);
+	if(ai == nil){
+		fprint(2, "vnc auth failed: server factotum: %r\n");
+		vncwrlong(v, VncAuthFailed);
+		vncflush(v);
+		return -1;
+	}
+	auth_freeAI(ai);
+	vncwrlong(v, VncAuthOK);
+	vncflush(v);
+
+	return 0;
+}
+
--- /dev/null
+++ b/sys/src/cmd/vnc/chan.c
@@ -1,0 +1,203 @@
+#include	<u.h>
+#include	<libc.h>
+#include	"compat.h"
+#include	"error.h"
+
+Chan*
+newchan(void)
+{
+	Chan *c;
+
+	c = smalloc(sizeof(Chan));
+
+	/* if you get an error before associating with a dev,
+	   close calls rootclose, a nop */
+	c->type = 0;
+	c->flag = 0;
+	c->ref = 1;
+	c->dev = 0;
+	c->offset = 0;
+	c->iounit = 0;
+	c->aux = 0;
+	c->name = 0;
+	return c;
+}
+
+void
+chanfree(Chan *c)
+{
+	c->flag = CFREE;
+
+	cnameclose(c->name);
+	free(c);
+}
+
+void
+cclose(Chan *c)
+{
+	if(c->flag&CFREE)
+		panic("cclose %#p", getcallerpc(&c));
+	if(decref(c))
+		return;
+
+	if(!waserror()){
+		devtab[c->type]->close(c);
+		poperror();
+	}
+
+	chanfree(c);
+}
+
+Chan*
+cclone(Chan *c)
+{
+	Chan *nc;
+	Walkqid *wq;
+
+	wq = devtab[c->type]->walk(c, nil, nil, 0);
+	if(wq == nil)
+		error("clone failed");
+	nc = wq->clone;
+	free(wq);
+	nc->name = c->name;
+	if(c->name)
+		incref(c->name);
+	return nc;
+}
+
+enum
+{
+	CNAMESLOP	= 20
+};
+
+static Ref ncname;
+
+void cleancname(Cname*);
+
+int
+isdotdot(char *p)
+{
+	return p[0]=='.' && p[1]=='.' && p[2]=='\0';
+}
+
+int
+incref(Ref *r)
+{
+	int x;
+
+	lock(r);
+	x = ++r->ref;
+	unlock(r);
+	return x;
+}
+
+int
+decref(Ref *r)
+{
+	int x;
+
+	lock(r);
+	x = --r->ref;
+	unlock(r);
+	if(x < 0)
+		panic("decref");
+
+	return x;
+}
+
+Cname*
+newcname(char *s)
+{
+	Cname *n;
+	int i;
+
+	n = smalloc(sizeof(Cname));
+	i = strlen(s);
+	n->len = i;
+	n->alen = i+CNAMESLOP;
+	n->s = smalloc(n->alen);
+	memmove(n->s, s, i+1);
+	n->ref = 1;
+	incref(&ncname);
+	return n;
+}
+
+void
+cnameclose(Cname *n)
+{
+	if(n == nil)
+		return;
+	if(decref(n))
+		return;
+	decref(&ncname);
+	free(n->s);
+	free(n);
+}
+
+Cname*
+addelem(Cname *n, char *s)
+{
+	int i, a;
+	char *t;
+	Cname *new;
+
+	if(s[0]=='.' && s[1]=='\0')
+		return n;
+
+	if(n->ref > 1){
+		/* copy on write */
+		new = newcname(n->s);
+		cnameclose(n);
+		n = new;
+	}
+
+	i = strlen(s);
+	if(n->len+1+i+1 > n->alen){
+		a = n->len+1+i+1 + CNAMESLOP;
+		t = smalloc(a);
+		memmove(t, n->s, n->len+1);
+		free(n->s);
+		n->s = t;
+		n->alen = a;
+	}
+	if(n->len>0 && n->s[n->len-1]!='/' && s[0]!='/')	/* don't insert extra slash if one is present */
+		n->s[n->len++] = '/';
+	memmove(n->s+n->len, s, i+1);
+	n->len += i;
+	if(isdotdot(s))
+		cleancname(n);
+	return n;
+}
+
+/*
+ * In place, rewrite name to compress multiple /, eliminate ., and process ..
+ */
+void
+cleancname(Cname *n)
+{
+	char *p;
+
+	if(n->s[0] == '#'){
+		p = strchr(n->s, '/');
+		if(p == nil)
+			return;
+		cleanname(p);
+
+		/*
+		 * The correct name is #i rather than #i/,
+		 * but the correct name of #/ is #/.
+		 */
+		if(strcmp(p, "/")==0 && n->s[1] != '/')
+			*p = '\0';
+	}else
+		cleanname(n->s);
+	n->len = strlen(n->s);
+}
+
+void
+isdir(Chan *c)
+{
+	if(c->qid.type & QTDIR)
+		return;
+	error(Enotdir);
+}
--- /dev/null
+++ b/sys/src/cmd/vnc/color.c
@@ -1,0 +1,194 @@
+#include "vnc.h"
+#include "vncv.h"
+
+enum {
+	RGB12 = CHAN4(CIgnore, 4, CRed, 4, CGreen, 4, CBlue, 4),
+	BGR12 = CHAN4(CIgnore, 4, CBlue, 4, CGreen, 4, CRed, 4),
+	BGR8 = CHAN3(CBlue, 2, CGreen, 3, CRed, 3),
+};
+
+void (*cvtpixels)(uchar*, uchar*, int);
+
+static void
+chan2fmt(Pixfmt *fmt, ulong chan)
+{
+	ulong c, rc, shift;
+
+	shift = 0;
+	for(rc = chan; rc; rc >>=8){
+		c = rc & 0xFF;
+		switch(TYPE(c)){
+		case CRed:
+			fmt->red = (Colorfmt){(1<<NBITS(c))-1, shift};
+			break;
+		case CBlue:
+			fmt->blue = (Colorfmt){(1<<NBITS(c))-1, shift};
+			break;
+		case CGreen:
+			fmt->green = (Colorfmt){(1<<NBITS(c))-1, shift};
+			break;
+		}
+		shift += NBITS(c);
+	}
+}
+
+/*
+ * convert 32-bit data to 24-bit data by skipping
+ * the last of every four bytes.  we skip the last
+ * because we keep the server in little endian mode.
+ */
+static void
+cvt32to24(uchar *dst, uchar *src, int npixel)
+{
+	int i;
+
+	for(i=0; i<npixel; i++){
+		*dst++ = *src++;
+		*dst++ = *src++;
+		*dst++ = *src++;
+		src++;
+	}
+}
+
+/*
+ * convert RGB12 (x4r4g4b4) into CMAP8
+ */
+static uchar rgb12[16*16*16];
+static void
+mkrgbtab(void)
+{
+	int r, g, b;
+
+	for(r=0; r<16; r++)
+	for(g=0; g<16; g++)
+	for(b=0; b<16; b++)
+		rgb12[r*256+g*16+b] = rgb2cmap(r*0x11, g*0x11, b*0x11);
+}
+
+static void
+cvtrgb12tocmap8(uchar *dst, uchar *src, int npixel)
+{
+	int i, s;
+
+	for(i=0; i<npixel; i++){
+		s = (src[0] | (src[1]<<8)) & 0xFFF;
+		*dst++ = rgb12[s];
+		src += 2;
+	}
+}
+
+/*
+ * convert BGR8 (b2g3r3, default VNC format) to CMAP8 
+ * some bits are lost.
+ */
+static uchar bgr8[256];
+static void
+mkbgrtab(void)
+{
+	int i, r, g, b;
+
+	for(i=0; i<256; i++){
+		b = i>>6;
+		b = (b<<6)|(b<<4)|(b<<2)|b;
+		g = (i>>3) & 7;
+		g = (g<<5)|(g<<2)|(g>>1);
+		r = i & 7;
+		r = (r<<5)|(r<<2)|(r>>1);
+		bgr8[i] = rgb2cmap(r, g, b);
+	}
+}
+
+static void
+cvtbgr332tocmap8(uchar *dst, uchar *src, int npixel)
+{
+	uchar *ed;
+
+	ed = dst+npixel;
+	while(dst < ed)
+		*dst++ = bgr8[*src++];
+}
+
+static void
+cvt16to32(uchar *dst, uchar *src, int npixel)
+{
+	uchar *ed;
+	int w, r, g, b;
+
+	ed = dst+npixel*4;
+	while(dst < ed){
+		w = src[1]<<8 | src[0];
+		b = (w >> 11) & 0x1F;
+		g = (w >> 5) & 0x3F;
+		r = (w >> 0) & 0x1F;
+		dst[0] = b<<(8-5);
+		dst[1] = g<<(8-6);
+		dst[2] = r<<(8-5);
+		dst += 4;
+		src += 2;
+	}
+}
+
+void
+choosecolor(Vnc *v)
+{
+	int bpp, depth;
+	ulong chan;
+
+	chan = screen->chan;
+	depth = screen->depth;
+	bpp = depth;
+	if((bpp / 8) * 8 != bpp)
+		sysfatal("screen not supported");
+/*
+	if(bpp == 32 && v->Pixfmt.bpp == 16){
+		cvtpixels = cvt16to32;
+		goto Done;
+	}
+*/
+	if(bpp == 24){
+		if(verbose)
+			fprint(2, "24bit emulation using 32bpp\n");
+		bpp = 32;
+		cvtpixels = cvt32to24;
+	}
+
+	if(chan == CMAP8){
+		if(bpp12){
+			if(verbose)
+				fprint(2, "8bit emulation using 12bpp\n");
+			bpp = 16;
+			depth = 12;
+			chan = RGB12;
+			cvtpixels = cvtrgb12tocmap8;
+			mkrgbtab();
+		}else{
+			if(verbose)
+				fprint(2, "8bit emulation using 6bpp\n");	/* 6: we throw away 1 r, g bit */
+			bpp = 8;
+			depth = 8;
+			chan = BGR8;
+			cvtpixels = cvtbgr332tocmap8;
+			mkbgrtab();
+		}
+	}
+
+	v->bpp = bpp;
+	v->depth = depth;
+	v->truecolor = 1;
+	v->bigendian = 0;
+	chan2fmt(v, chan);
+	if(v->red.max == 0 || v->green.max == 0 || v->blue.max == 0)
+		sysfatal("screen not supported");
+
+Done:
+	if(verbose)
+		fprint(2, "%d bpp, %d depth, 0x%lx chan, %d truecolor, %d bigendian\n",
+			v->bpp, v->depth, screen->chan, v->truecolor, v->bigendian);
+
+	/* send information to server */
+	vncwrchar(v, MPixFmt);
+	vncwrchar(v, 0);	/* padding */
+	vncwrshort(v, 0);
+	vncwrpixfmt(v, &v->Pixfmt);
+	vncflush(v);
+}
--- /dev/null
+++ b/sys/src/cmd/vnc/compat.c
@@ -1,0 +1,250 @@
+#include	<u.h>
+#include	<libc.h>
+#include	"compat.h"
+#include	"error.h"
+
+#include	"errstr.h"
+
+ulong	kerndate;
+Proc	**privup;
+char	*eve;
+extern void *mainmem;
+
+void
+_assert(char *fmt)
+{
+	panic("assert failed: %s", fmt);
+}
+
+int
+errdepth(int ed)
+{
+	if(ed >= 0 && up->nerrlab != ed)
+		panic("unbalanced error depth: expected %d got %d\n", ed, up->nerrlab);
+	return up->nerrlab;
+}
+
+void
+newup(char *name)
+{
+	up = smalloc(sizeof(Proc));
+	up->user = eve;
+	strncpy(up->name, name, KNAMELEN-1);
+	up->name[KNAMELEN-1] = '\0';
+}
+
+void
+kproc(char *name, void (*f)(void *), void *a)
+{
+	int pid;
+
+	pid = rfork(RFPROC|RFMEM|RFNOWAIT);
+	switch(pid){
+	case -1:
+		panic("can't make new thread: %r");
+	case 0:
+		break;
+	default:
+		return;
+	}
+
+	newup(name);
+	if(!waserror())
+		(*f)(a);
+	_exits(nil);
+}
+
+void
+kexit(void)
+{
+	_exits(nil);
+}
+
+void
+initcompat(void)
+{
+	rfork(RFREND);
+	privup = privalloc();
+	kerndate = seconds();
+	eve = getuser();
+	newup("main");
+}
+
+int
+openmode(ulong o)
+{
+	o &= ~(OTRUNC|OCEXEC|ORCLOSE);
+	if(o > OEXEC)
+		error(Ebadarg);
+	if(o == OEXEC)
+		return OREAD;
+	return o;
+}
+
+void
+panic(char *fmt, ...)
+{
+	char buf[512];
+	char buf2[512];
+	va_list va;
+
+	va_start(va, fmt);
+	vseprint(buf, buf+sizeof(buf), fmt, va);
+	va_end(va);
+	sprint(buf2, "panic: %s\n", buf);
+	write(2, buf2, strlen(buf2));
+
+	exits("error");
+}
+
+void*
+smalloc(ulong n)
+{
+	void *p;
+
+	p = mallocz(n, 1);
+	if(p == nil)
+		panic("out of memory");
+	setmalloctag(p, getcallerpc(&n));
+	return p;
+}
+
+long
+seconds(void)
+{
+	return time(nil);
+}
+
+void
+error(char *err)
+{
+	strncpy(up->error, err, ERRMAX);
+	nexterror();
+}
+
+void
+nexterror(void)
+{
+	longjmp(up->errlab[--up->nerrlab], 1);
+}
+
+int
+readstr(ulong off, char *buf, ulong n, char *str)
+{
+	int size;
+
+	size = strlen(str);
+	if(off >= size)
+		return 0;
+	if(off+n > size)
+		n = size-off;
+	memmove(buf, str+off, n);
+	return n;
+}
+
+void
+_rendsleep(void* tag)
+{
+	void *value;
+
+	for(;;){
+		value = rendezvous(tag, (void*)0x22a891b8);
+		if(value == (void*)0x7f7713f9)
+			break;
+		if(tag != (void*)~0)
+			panic("_rendsleep: rendezvous mismatch");
+	}
+}
+
+void
+_rendwakeup(void* tag)
+{
+	void *value;
+
+	for(;;){
+		value = rendezvous(tag, (void*)0x7f7713f9);
+		if(value == (void*)0x22a891b8)
+			break;
+		if(tag != (void*)~0)
+			panic("_rendwakeup: rendezvous mismatch");
+	}
+}
+
+void
+rendsleep(Rendez *r, int (*f)(void*), void *arg)
+{
+	lock(&up->rlock);
+	up->r = r;
+	unlock(&up->rlock);
+
+	lock(r);
+
+	/*
+	 * if condition happened, never mind
+	 */
+	if(up->intr || f(arg)){
+		unlock(r);
+		goto Done;
+	}
+
+	/*
+	 * now we are committed to
+	 * change state and call scheduler
+	 */
+	if(r->p)
+		panic("double sleep");
+	r->p = up;
+	unlock(r);
+
+	_rendsleep(r);
+
+Done:
+	lock(&up->rlock);
+	up->r = 0;
+	if(up->intr){
+		up->intr = 0;
+		unlock(&up->rlock);
+		error(Eintr);
+	}
+	unlock(&up->rlock);
+}
+
+int
+rendwakeup(Rendez *r)
+{
+	Proc *p;
+	int rv;
+
+	lock(r);
+	p = r->p;
+	rv = 0;
+	if(p){
+		r->p = nil;
+		_rendwakeup(r);
+		rv = 1;
+	}
+	unlock(r);
+	return rv;
+}
+
+void
+rendintr(void *v)
+{
+	Proc *p;
+
+	p = v;
+	lock(&p->rlock);
+	p->intr = 1;
+	if(p->r)
+		rendwakeup(p->r);
+	unlock(&p->rlock);
+}
+
+void
+rendclearintr(void)
+{
+	lock(&up->rlock);
+	up->intr = 0;
+	unlock(&up->rlock);
+}
+
--- /dev/null
+++ b/sys/src/cmd/vnc/compat.h
@@ -1,0 +1,168 @@
+#define Rendez KRendez
+
+typedef struct Block	Block;
+typedef struct Chan	Chan;
+typedef struct Cname	Cname;
+typedef struct Dev	Dev;
+typedef struct Dirtab	Dirtab;
+typedef struct Proc	Proc;
+typedef struct Ref	Ref;
+typedef struct Rendez	Rendez;
+typedef struct Walkqid Walkqid;
+typedef int    Devgen(Chan*, Dirtab*, int, int, Dir*);
+
+enum
+{
+	KNAMELEN	= 28,
+	NERR		= 15,
+
+	COPEN		= 0x0001,		/* for i/o */
+	CFREE		= 0x0010,		/* not in use */
+};
+
+struct Ref
+{
+	Lock;
+	int	ref;
+};
+
+struct Rendez
+{
+	Lock;
+	Proc	*p;
+};
+
+struct Chan
+{
+	Ref;
+	Chan*	next;			/* allocation */
+	Chan*	link;
+	vlong	offset;			/* in file */
+	ushort	type;
+	ulong	dev;
+	ushort	mode;			/* read/write */
+	ushort	flag;
+	Qid	qid;
+	int	fid;			/* for devmnt */
+	ulong	iounit;			/* chunk size for i/o; 0==default */
+	void*	aux;
+	Cname	*name;
+};
+
+struct Cname
+{
+	Ref;
+	int	alen;			/* allocated length */
+	int	len;			/* strlen(s) */
+	char	*s;
+};
+
+struct Dev
+{
+	int	dc;
+	char*	name;
+
+	void	(*reset)(void);
+	void	(*init)(void);
+	Chan*	(*attach)(char*);
+	Walkqid*	(*walk)(Chan*, Chan*, char**, int);
+	int	(*stat)(Chan*, uchar*, int);
+	Chan*	(*open)(Chan*, int);
+	void	(*create)(Chan*, char*, int, ulong);
+	void	(*close)(Chan*);
+	long	(*read)(Chan*, void*, long, vlong);
+	Block*	(*bread)(Chan*, long, ulong);
+	long	(*write)(Chan*, void*, long, vlong);
+	long	(*bwrite)(Chan*, Block*, ulong);
+	void	(*remove)(Chan*);
+	int	(*wstat)(Chan*, uchar*, int);
+};
+
+struct Dirtab
+{
+	char	name[KNAMELEN];
+	Qid	qid;
+	vlong length;
+	long	perm;
+};
+
+struct Walkqid
+{
+	Chan	*clone;
+	int	nqid;
+	Qid	qid[1];
+};
+
+struct Proc
+{
+	Lock	rlock;		/* for rendsleep, rendwakeup, intr */
+	Rendez	*r;
+	int	intr;
+
+	char	name[KNAMELEN];
+	char	*user;
+	char	error[ERRMAX];
+	int	nerrlab;
+	jmp_buf	errlab[NERR];
+	char	genbuf[128];	/* buffer used e.g. for last name element from namec */
+};
+
+#define DEVDOTDOT -1
+
+extern	Proc	**privup;
+#define	up	(*privup)
+extern  char	*eve;
+extern	Dev*	devtab[];
+
+Chan*		cclone(Chan*);
+void		cclose(Chan*);
+void		cnameclose(Cname*);
+int		decref(Ref*);
+Chan*		devattach(int, char*);
+Block*		devbread(Chan*, long, ulong);
+long		devbwrite(Chan*, Block*, ulong);
+void		devcreate(Chan*, char*, int, ulong);
+void		devdir(Chan*, Qid, char*, vlong, char*, long, Dir*);
+long		devdirread(Chan*, char*, long, Dirtab*, int, Devgen*);
+Devgen		devgen;
+void		devinit(void);
+Chan*		devopen(Chan*, int, Dirtab*, int, Devgen*);
+void		devremove(Chan*);
+void		devreset(void);
+int		devstat(Chan*, uchar*, int, Dirtab*, int, Devgen*);
+Walkqid*		devwalk(Chan*, Chan*, char**, int, Dirtab*, int, Devgen*);
+int		devwstat(Chan*, uchar*, int);
+void		error(char*);
+int		incref(Ref*);
+void		isdir(Chan*);
+void		kproc(char*, void(*)(void*), void*);
+void		mkqid(Qid*, vlong, ulong, int);
+void		nexterror(void);
+Chan*		newchan(void);
+Cname*		newcname(char*);
+int		openmode(ulong);
+void		panic(char*, ...);
+int		readstr(ulong, char*, ulong, char*);
+long		seconds(void);
+void*		smalloc(ulong);
+
+#define		poperror()	up->nerrlab--
+#define		waserror()	(up->nerrlab++, setjmp(up->errlab[up->nerrlab-1]))
+
+void		initcompat(void);
+void		rendintr(void *v);
+void		rendclearintr(void);
+void		rendsleep(Rendez*, int(*)(void*), void*);
+int		rendwakeup(Rendez*);
+void		kexit(void);
+int		sysexport(int fd, Chan **roots, int nroots);
+int		errdepth(int ed);
+void		newup(char *name);
+
+int		exporter(Dev**, int*, int*);
+int		mounter(char *mntpt, int how, int fds, int n);
+void		shutdown(void);
+
+void		screeninit(int, int, char*);
+
+#pragma	varargck	argpos	panic		1
--- /dev/null
+++ b/sys/src/cmd/vnc/dev.c
@@ -1,0 +1,339 @@
+#include	<u.h>
+#include	<libc.h>
+#include	<fcall.h>
+#include	"compat.h"
+#include	"error.h"
+
+extern ulong	kerndate;
+
+void
+mkqid(Qid *q, vlong path, ulong vers, int type)
+{
+	q->type = type;
+	q->vers = vers;
+	q->path = path;
+}
+
+int
+devno(int c, int user)
+{
+	int i;
+
+	for(i = 0; devtab[i] != nil; i++){
+		if(devtab[i]->dc == c)
+			return i;
+	}
+	if(user == 0)
+		panic("devno %C 0x%ux", c, c);
+
+	return -1;
+}
+
+void
+devdir(Chan *c, Qid qid, char *n, vlong length, char *user, long perm, Dir *db)
+{
+	db->name = n;
+	db->qid = qid;
+	db->type = devtab[c->type]->dc;
+	db->dev = c->dev;
+	db->mode = (qid.type << 24) | perm;
+	db->atime = seconds();
+	db->mtime = kerndate;
+	db->length = length;
+	db->uid = user;
+	db->gid = eve;
+	db->muid = user;
+}
+
+/*
+ * the zeroth element of the table MUST be the directory itself for ..
+*/
+int
+devgen(Chan *c, Dirtab *tab, int ntab, int i, Dir *dp)
+{
+	if(tab == 0)
+		return -1;
+	if(i != DEVDOTDOT){
+		i++; /* skip first element for . itself */
+		if(i >= ntab)
+			return -1;
+		tab += i;
+	}
+	devdir(c, tab->qid, tab->name, tab->length, eve, tab->perm, dp);
+	return 1;
+}
+
+void
+devreset(void)
+{
+}
+
+void
+devinit(void)
+{
+}
+
+Chan*
+devattach(int tc, char *spec)
+{
+	Chan *c;
+	char *buf;
+
+	c = newchan();
+	mkqid(&c->qid, 0, 0, QTDIR);
+	c->type = devno(tc, 0);
+	if(spec == nil)
+		spec = "";
+	buf = smalloc(4+strlen(spec)+1);
+	sprint(buf, "#%C%s", tc, spec);
+	c->name = newcname(buf);
+	free(buf);
+	return c;
+}
+
+
+Chan*
+devclone(Chan *c)
+{
+	Chan *nc;
+
+	if(c->flag & COPEN)
+		panic("clone of open file type %C\n", devtab[c->type]->dc);
+
+	nc = newchan();
+
+	nc->type = c->type;
+	nc->dev = c->dev;
+	nc->mode = c->mode;
+	nc->qid = c->qid;
+	nc->offset = c->offset;
+	nc->aux = c->aux;
+	return nc;
+}
+
+Walkqid*
+devwalk(Chan *c, Chan *nc, char **name, int nname, Dirtab *tab, int ntab, Devgen *gen)
+{
+	int i, j, alloc;
+	Walkqid *wq;
+	char *n;
+	Dir dir;
+
+	isdir(c);
+
+	alloc = 0;
+	wq = smalloc(sizeof(Walkqid)+(nname-1)*sizeof(Qid));
+	if(waserror()){
+		if(alloc && wq->clone!=nil)
+			cclose(wq->clone);
+		free(wq);
+		return nil;
+	}
+	if(nc == nil){
+		nc = devclone(c);
+		nc->type = 0;	/* device doesn't know about this channel yet */
+		alloc = 1;
+	}
+	wq->clone = nc;
+
+	for(j=0; j<nname; j++){
+		isdir(nc);
+		n = name[j];
+		if(strcmp(n, ".") == 0){
+    Accept:
+			wq->qid[wq->nqid++] = nc->qid;
+			continue;
+		}
+		if(strcmp(n, "..") == 0){
+			(*gen)(nc, tab, ntab, DEVDOTDOT, &dir);
+			nc->qid = dir.qid;
+			goto Accept;
+		}
+		for(i=0;; i++){
+			switch((*gen)(nc, tab, ntab, i, &dir)){
+			case -1:
+				if(j == 0)
+					error(Enonexist);
+				strncpy(up->error, Enonexist, ERRMAX);
+				goto Done;
+			case 0:
+				continue;
+			case 1:
+				if(strcmp(n, dir.name) == 0){
+					nc->qid = dir.qid;
+					goto Accept;
+				}
+				continue;
+			}
+		}
+	}
+	/*
+	 * We processed at least one name, so will return some data.
+	 * If we didn't process all nname entries succesfully, we drop
+	 * the cloned channel and return just the Qids of the walks.
+	 */
+Done:
+	poperror();
+	if(wq->nqid < nname){
+		if(alloc)
+			cclose(wq->clone);
+		wq->clone = nil;
+	}else if(wq->clone){
+		/* attach cloned channel to same device */
+		wq->clone->type = c->type;
+	}
+	return wq;
+}
+
+int
+devstat(Chan *c, uchar *db, int n, Dirtab *tab, int ntab, Devgen *gen)
+{
+	int i;
+	Dir dir;
+	char *p, *elem;
+
+	for(i=0;; i++)
+		switch((*gen)(c, tab, ntab, i, &dir)){
+		case -1:
+			if(c->qid.type & QTDIR){
+				if(c->name == nil)
+					elem = "???";
+				else if(strcmp(c->name->s, "/") == 0)
+					elem = "/";
+				else
+					for(elem=p=c->name->s; *p; p++)
+						if(*p == '/')
+							elem = p+1;
+				devdir(c, c->qid, elem, 0, eve, DMDIR|0555, &dir);
+				return convD2M(&dir, db, n);
+			}
+
+			error(Enonexist);
+		case 0:
+			break;
+		case 1:
+			if(c->qid.path == dir.qid.path){
+				return convD2M(&dir, db, n);
+			}
+			break;
+		}
+}
+
+long
+devdirread(Chan *c, char *d, long n, Dirtab *tab, int ntab, Devgen *gen)
+{
+	long k, m, dsz;
+	struct{
+		Dir;
+		char slop[100];
+	}dir;
+
+	k = c->offset;
+	for(m=0; m<n; k++){
+		switch((*gen)(c, tab, ntab, k, &dir)){
+		case -1:
+			return m;
+
+		case 0:
+			c->offset++;	/* BUG??? (was DIRLEN: skip entry) */
+			break;
+
+		case 1:
+			dsz = convD2M(&dir, (uchar*)d, n-m);
+			if(dsz <= BIT16SZ){	/* <= not < because this isn't stat; read is stuck */
+				if(m == 0)
+					return -1;
+				return m;
+			}
+			m += dsz;
+			d += dsz;
+			break;
+		}
+	}
+
+	return m;
+}
+
+/*
+ * error(Eperm) if open permission not granted for up->user.
+ */
+void
+devpermcheck(char *fileuid, ulong perm, int omode)
+{
+	ulong t;
+	static int access[] = { 0400, 0200, 0600, 0100 };
+
+	if(strcmp(up->user, fileuid) == 0)
+		perm <<= 0;
+	else
+	if(strcmp(up->user, eve) == 0)
+		perm <<= 3;
+	else
+		perm <<= 6;
+
+	t = access[omode&3];
+	if((t&perm) != t)
+		error(Eperm);
+}
+
+Chan*
+devopen(Chan *c, int omode, Dirtab *tab, int ntab, Devgen *gen)
+{
+	int i;
+	Dir dir;
+
+	for(i=0;; i++){
+		switch((*gen)(c, tab, ntab, i, &dir)){
+		case -1:
+			goto Return;
+		case 0:
+			break;
+		case 1:
+			if(c->qid.path == dir.qid.path){
+				devpermcheck(dir.uid, dir.mode, omode);
+				goto Return;
+			}
+			break;
+		}
+	}
+Return:
+	c->offset = 0;
+	if((c->qid.type&QTDIR) && omode!=OREAD)
+		error(Eperm);
+	c->mode = openmode(omode);
+	c->flag |= COPEN;
+	return c;
+}
+
+void
+devcreate(Chan*, char*, int, ulong)
+{
+	error(Eperm);
+}
+
+Block*
+devbread(Chan *, long, ulong)
+{
+	panic("no block read");
+	return nil;
+}
+
+long
+devbwrite(Chan *, Block *, ulong)
+{
+	panic("no block write");
+	return 0;
+}
+
+void
+devremove(Chan*)
+{
+	error(Eperm);
+}
+
+int
+devwstat(Chan*, uchar*, int)
+{
+	error(Eperm);
+	return 0;
+}
--- /dev/null
+++ b/sys/src/cmd/vnc/devcons.c
@@ -1,0 +1,186 @@
+#include	<u.h>
+#include	<libc.h>
+#include	"compat.h"
+#include	"kbd.h"
+#include	"error.h"
+
+Snarf	snarf = {
+	.vers =	1
+};
+
+enum{
+	Qdir,
+	Qcons,
+	Qconsctl,
+	Qsnarf,
+};
+
+static Dirtab consdir[]={
+	".",		{Qdir, 0, QTDIR},	0,		DMDIR|0555,
+	"cons",		{Qcons},	0,		0660,
+	"consctl",	{Qconsctl},	0,		0220,
+	"snarf",	{Qsnarf},	0,		0600,
+};
+
+static Chan*
+consattach(char *spec)
+{
+	return devattach('c', spec);
+}
+
+static Walkqid*
+conswalk(Chan *c, Chan *nc, char **name, int nname)
+{
+	return devwalk(c, nc, name,nname, consdir, nelem(consdir), devgen);
+}
+
+static int
+consstat(Chan *c, uchar *dp, int n)
+{
+	return devstat(c, dp, n, consdir, nelem(consdir), devgen);
+}
+
+static Chan*
+consopen(Chan *c, int omode)
+{
+	c->aux = nil;
+	c = devopen(c, omode, consdir, nelem(consdir), devgen);
+	switch((ulong)c->qid.path){
+	case Qconsctl:
+		break;
+	case Qsnarf:
+		if((c->mode&3) == OWRITE || (c->mode&3) == ORDWR)
+			c->aux = smalloc(sizeof(Snarf));
+		break;
+	}
+	return c;
+}
+
+void
+setsnarf(char *buf, int n, int *vers)
+{
+	int i;
+
+	qlock(&snarf);
+	snarf.vers++;
+	if(vers)
+		*vers = snarf.vers;	
+	for(i = 0; i < nelem(consdir); i++){
+		if(consdir[i].qid.type == Qsnarf){
+			consdir[i].qid.vers = snarf.vers;
+			break;
+		}
+	}
+	free(snarf.buf);
+	snarf.n = n;
+	snarf.buf = buf;
+	qunlock(&snarf);
+}
+
+static void
+consclose(Chan *c)
+{
+	Snarf *t;
+
+	switch((ulong)c->qid.path){
+	/* last close of control file turns off raw */
+	case Qconsctl:
+		break;
+	/* odd behavior but really ok: replace snarf buffer when /dev/snarf is closed */
+	case Qsnarf:
+		t = c->aux;
+		if(t == nil)
+			break;
+		setsnarf(t->buf, t->n, 0);
+		t->buf = nil;	/* setsnarf took it */
+		free(t);
+		c->aux = nil;
+		break;
+	}
+}
+
+static long
+consread(Chan *c, void *buf, long n, vlong off)
+{
+	if(n <= 0)
+		return n;
+	switch((ulong)c->qid.path){
+	case Qsnarf:
+		qlock(&snarf);
+		if(off < snarf.n){
+			if(off + n > snarf.n)
+				n = snarf.n - off;
+			memmove(buf, snarf.buf+off, n);
+		}else
+			n = 0;
+		qunlock(&snarf);
+		return n;
+
+	case Qdir:
+		return devdirread(c, buf, n, consdir, nelem(consdir), devgen);
+
+	case Qcons:
+		error(Egreg);
+		return -1;
+
+	default:
+		print("consread 0x%llux\n", c->qid.path);
+		error(Egreg);
+	}
+	return -1;		/* never reached */
+}
+
+static long
+conswrite(Chan *c, void *va, long n, vlong)
+{
+	Snarf *t;
+	char *a;
+
+	switch((ulong)c->qid.path){
+	case Qcons:
+		screenputs(va, n);
+		break;
+
+	case Qconsctl:
+		error(Egreg);
+		break;
+
+	case Qsnarf:
+		t = c->aux;
+		/* always append only */
+		if(t->n > MAXSNARF)	/* avoid thrashing when people cut huge text */
+			error("snarf buffer too big");
+		a = realloc(t->buf, t->n + n + 1);
+		if(a == nil)
+			error("snarf buffer too big");
+		t->buf = a;
+		memmove(t->buf+t->n, va, n);
+		t->n += n;
+		t->buf[t->n] = '\0';
+		break;
+	default:
+		print("conswrite: 0x%llux\n", c->qid.path);
+		error(Egreg);
+	}
+	return n;
+}
+
+Dev consdevtab = {
+	'c',
+	"cons",
+
+	devreset,
+	devinit,
+	consattach,
+	conswalk,
+	consstat,
+	consopen,
+	devcreate,
+	consclose,
+	consread,
+	devbread,
+	conswrite,
+	devbwrite,
+	devremove,
+	devwstat,
+};
--- /dev/null
+++ b/sys/src/cmd/vnc/devdraw.c
@@ -1,0 +1,2134 @@
+#include	<u.h>
+#include	<libc.h>
+#include	"compat.h"
+#include	"error.h"
+
+#define	Image	IMAGE
+#include	<draw.h>
+#include	<memdraw.h>
+#include	<memlayer.h>
+#include	<cursor.h>
+#include	"screen.h"
+
+enum
+{
+	Qtopdir		= 0,
+	Qnew,
+	Qwinname,
+	Q3rd,
+	Q2nd,
+	Qcolormap,
+	Qctl,
+	Qdata,
+	Qrefresh,
+};
+
+/*
+ * Qid path is:
+ *	 4 bits of file type (qids above)
+ *	24 bits of mux slot number +1; 0 means not attached to client
+ */
+#define	QSHIFT	4	/* location in qid of client # */
+
+#define	QID(q)		((((ulong)(q).path)&0x0000000F)>>0)
+#define	CLIENTPATH(q)	((((ulong)q)&0x7FFFFFF0)>>QSHIFT)
+#define	CLIENT(q)	CLIENTPATH((q).path)
+
+#define	NHASH		(1<<5)
+#define	HASHMASK	(NHASH-1)
+
+typedef struct Client Client;
+typedef struct Draw Draw;
+typedef struct DImage DImage;
+typedef struct DScreen DScreen;
+typedef struct CScreen CScreen;
+typedef struct FChar FChar;
+typedef struct Refresh Refresh;
+typedef struct Refx Refx;
+typedef struct DName DName;
+
+struct Draw
+{
+	int		clientid;
+	int		nclient;
+	Client**	client;
+	int		nname;
+	DName*		name;
+	int		vers;
+	int		softscreen;
+};
+
+struct Client
+{
+	Ref		r;
+	DImage*		dimage[NHASH];
+	CScreen*	cscreen;
+	Refresh*	refresh;
+	Rendez		refrend;
+	QLock		refq;
+	uchar*		readdata;
+	int		nreaddata;
+	int		busy;
+	int		clientid;
+	int		slot;
+	int		refreshme;
+	int		infoid;
+	int		op;
+};
+
+struct Refresh
+{
+	DImage*		dimage;
+	Rectangle	r;
+	Refresh*	next;
+};
+
+struct Refx
+{
+	Client*		client;
+	DImage*		dimage;
+};
+
+struct DName
+{
+	char		*name;
+	Client		*client;
+	DImage*		dimage;
+	int		vers;
+};
+
+struct FChar
+{
+	int		minx;	/* left edge of bits */
+	int		maxx;	/* right edge of bits */
+	uchar		miny;	/* first non-zero scan-line */
+	uchar		maxy;	/* last non-zero scan-line + 1 */
+	schar		left;	/* offset of baseline */
+	uchar		width;	/* width of baseline */
+};
+
+/*
+ * Reference counts in DImages:
+ *	one per open by original client
+ *	one per screen image or fill
+ * 	one per image derived from this one by name
+ */
+struct DImage
+{
+	int		id;
+	int		ref;
+	char		*name;
+	int		vers;
+	Memimage*	image;
+	int		ascent;
+	int		nfchar;
+	FChar*		fchar;
+	DScreen*	dscreen;	/* 0 if not a window */
+	DImage*		fromname;	/* image this one is derived from, by name */
+	DImage*		next;
+};
+
+struct CScreen
+{
+	DScreen*	dscreen;
+	CScreen*	next;
+};
+
+struct DScreen
+{
+	int		id;
+	int		public;
+	int		ref;
+	DImage		*dimage;
+	DImage		*dfill;
+	Memscreen*	screen;
+	Client*		owner;
+	DScreen*	next;
+};
+
+static	Draw		sdraw;
+	QLock	drawlock;
+
+static	Memimage	*screenimage;
+static	DImage*	screendimage;
+static	char	screenname[40];
+static	int	screennameid;
+
+static	Rectangle	flushrect;
+static	int		waste;
+static	DScreen*	dscreen;
+extern	void		flushmemscreen(Rectangle);
+	void		drawmesg(Client*, void*, int);
+	void		drawuninstall(Client*, int);
+	void		drawfreedimage(DImage*);
+	Client*		drawclientofpath(ulong);
+	DImage*	allocdimage(Memimage*);
+
+static	char Enodrawimage[] =	"unknown id for draw image";
+static	char Enodrawscreen[] =	"unknown id for draw screen";
+static	char Eshortdraw[] =	"short draw message";
+static	char Eshortread[] =	"draw read too short";
+static	char Eimageexists[] =	"image id in use";
+static	char Escreenexists[] =	"screen id in use";
+static	char Edrawmem[] =	"image memory allocation failed";
+static	char Ereadoutside[] =	"readimage outside image";
+static	char Ewriteoutside[] =	"writeimage outside image";
+static	char Enotfont[] =	"image not a font";
+static	char Eindex[] =		"character index out of range";
+static	char Enoclient[] =	"no such draw client";
+static	char Enameused[] =	"image name in use";
+static	char Enoname[] =	"no image with that name";
+static	char Eoldname[] =	"named image no longer valid";
+static	char Enamed[] = 	"image already has name";
+static	char Ewrongname[] = 	"wrong name for image";
+
+static void
+dlock(void)
+{
+	qlock(&drawlock);
+}
+
+static int
+candlock(void)
+{
+	return canqlock(&drawlock);
+}
+
+static void
+dunlock(void)
+{
+	qunlock(&drawlock);
+}
+
+static int
+drawgen(Chan *c, Dirtab*, int, int s, Dir *dp)
+{
+	int t;
+	Qid q;
+	ulong path;
+	Client *cl;
+
+	q.vers = 0;
+
+	if(s == DEVDOTDOT){
+		switch(QID(c->qid)){
+		case Qtopdir:
+		case Q2nd:
+			mkqid(&q, Qtopdir, 0, QTDIR);
+			devdir(c, q, "#i", 0, eve, 0500, dp);
+			break;
+		case Q3rd:
+			cl = drawclientofpath(c->qid.path);
+			if(cl == nil)
+				strcpy(up->genbuf, "??");
+			else
+				sprint(up->genbuf, "%d", cl->clientid);
+			mkqid(&q, Q2nd, 0, QTDIR);
+			devdir(c, q, up->genbuf, 0, eve, 0500, dp);
+			break;
+		default:
+			panic("drawwalk %llux", c->qid.path);
+		}
+		return 1;
+	}
+
+	/*
+	 * Top level directory contains the name of the device.
+	 */
+	t = QID(c->qid);
+	switch(t){
+	case Qtopdir:
+		if(s == 0){
+			mkqid(&q, Q2nd, 0, QTDIR);
+			devdir(c, q, "draw", 0, eve, 0555, dp);
+			return 1;
+		}
+		if(s == 1){
+	case Qwinname:
+			mkqid(&q, Qwinname, 0, QTFILE);
+			devdir(c, q, "winname", 0, eve, 0444, dp);
+			return 1;
+		}
+		return -1;
+	}
+
+	/*
+	 * Second level contains "new" plus all the clients.
+	 */
+	switch(t){
+	case Q2nd:
+		if(s == 0){
+	case Qnew:
+			mkqid(&q, Qnew, 0, QTFILE);
+			devdir(c, q, "new", 0, eve, 0666, dp);
+			return 1;
+		}
+		if(s <= sdraw.nclient){
+			cl = sdraw.client[s-1];
+			if(cl == nil)
+				return 0;
+			sprint(up->genbuf, "%d", cl->clientid);
+			mkqid(&q, (s<<QSHIFT)|Q3rd, 0, QTDIR);
+			devdir(c, q, up->genbuf, 0, eve, 0555, dp);
+			return 1;
+		}
+		return -1;
+	}
+
+	/*
+	 * Third level.
+	 */
+	path = c->qid.path&~((1<<QSHIFT)-1);	/* slot component */
+	q.vers = c->qid.vers;
+	q.type = QTFILE;
+	switch(s){
+	case 0:
+		q.path = path|Qcolormap;
+		devdir(c, q, "colormap", 0, eve, 0600, dp);
+		break;
+	case 1:
+		q.path = path|Qctl;
+		devdir(c, q, "ctl", 0, eve, 0600, dp);
+		break;
+	case 2:
+		q.path = path|Qdata;
+		devdir(c, q, "data", 0, eve, 0600, dp);
+		break;
+	case 3:
+		q.path = path|Qrefresh;
+		devdir(c, q, "refresh", 0, eve, 0400, dp);
+		break;
+	default:
+		return -1;
+	}
+	return 1;
+}
+
+static
+int
+drawrefactive(void *a)
+{
+	Client *c;
+
+	c = a;
+	return c->refreshme || c->refresh!=0;
+}
+
+static
+void
+drawrefreshscreen(DImage *l, Client *client)
+{
+	while(l != nil && l->dscreen == nil)
+		l = l->fromname;
+	if(l != nil && l->dscreen->owner != client)
+		l->dscreen->owner->refreshme = 1;
+}
+
+static
+void
+drawrefresh(Memimage*, Rectangle r, void *v)
+{
+	Refx *x;
+	DImage *d;
+	Client *c;
+	Refresh *ref;
+
+	if(v == 0)
+		return;
+	x = v;
+	c = x->client;
+	d = x->dimage;
+	for(ref=c->refresh; ref; ref=ref->next)
+		if(ref->dimage == d){
+			combinerect(&ref->r, r);
+			return;
+		}
+	ref = malloc(sizeof(Refresh));
+	if(ref){
+		ref->dimage = d;
+		ref->r = r;
+		ref->next = c->refresh;
+		c->refresh = ref;
+	}
+}
+
+static void
+addflush(Rectangle r)
+{
+	int abb, ar, anbb;
+	Rectangle nbb;
+
+	if(sdraw.softscreen==0 || screenimage == nil || !rectclip(&r, screenimage->r))
+		return;
+	if(flushrect.min.x >= flushrect.max.x){
+		flushrect = r;
+		waste = 0;
+		return;
+	}
+	/* VNC uses a region to compute the minimum bounding area.
+	 * The waste is far less than that of a bounding box. see region.c
+	 */
+	if(1){
+		flushmemscreen(flushrect);
+		flushrect = r;
+		return;
+	}
+	nbb = flushrect;
+	combinerect(&nbb, r);
+	ar = Dx(r)*Dy(r);
+	abb = Dx(flushrect)*Dy(flushrect);
+	anbb = Dx(nbb)*Dy(nbb);
+	/*
+	 * Area of new waste is area of new bb minus area of old bb,
+	 * less the area of the new segment, which we assume is not waste.
+	 * This could be negative, but that's OK.
+	 */
+	waste += anbb-abb - ar;
+	if(waste < 0)
+		waste = 0;
+	/*
+	 * absorb if:
+	 *	total area is small
+	 *	waste is less than half total area
+	 * 	rectangles touch
+	 */
+	if(anbb<=1024 || waste*2<anbb || rectXrect(flushrect, r)){
+		flushrect = nbb;
+		return;
+	}
+	/* emit current state */
+	if(flushrect.min.x < flushrect.max.x)
+		flushmemscreen(flushrect);
+	flushrect = r;
+	waste = 0;
+}
+
+static
+void
+dstflush(int dstid, Memimage *dst, Rectangle r)
+{
+	Memlayer *l;
+
+	if(dstid == 0){
+		//combinerect(&flushrect, r);
+		addflush(r); // for VNC, see comments in addflush
+		return;
+	}
+	if(screenimage == nil || dst == nil || (l = dst->layer) == nil)
+		return;
+	do{
+		if(l->screen->image->data != screenimage->data)
+			return;
+		r = rectaddpt(r, l->delta);
+		l = l->screen->image->layer;
+	}while(l);
+	addflush(r);
+}
+
+void
+drawflush(void)
+{
+	if(screenimage && flushrect.min.x < flushrect.max.x)
+		flushmemscreen(flushrect);
+	flushrect = Rect(10000, 10000, -10000, -10000);
+}
+
+static
+int
+drawcmp(char *a, char *b, int n)
+{
+	if(strlen(a) != n)
+		return 1;
+	return memcmp(a, b, n);
+}
+
+DName*
+drawlookupname(int n, char *str)
+{
+	DName *name, *ename;
+
+	name = sdraw.name;
+	ename = &name[sdraw.nname];
+	for(; name<ename; name++)
+		if(drawcmp(name->name, str, n) == 0)
+			return name;
+	return 0;
+}
+
+int
+drawgoodname(DImage *d)
+{
+	DName *n;
+
+	/* if window, validate the screen's own images */
+	if(d->dscreen)
+		if(drawgoodname(d->dscreen->dimage) == 0
+		|| drawgoodname(d->dscreen->dfill) == 0)
+			return 0;
+	if(d->name == nil)
+		return 1;
+	n = drawlookupname(strlen(d->name), d->name);
+	if(n==nil || n->vers!=d->vers)
+		return 0;
+	return 1;
+}
+
+DImage*
+drawlookup(Client *client, int id, int checkname)
+{
+	DImage *d;
+
+	d = client->dimage[id&HASHMASK];
+	while(d){
+		if(d->id == id){
+			if(checkname && !drawgoodname(d))
+				error(Eoldname);
+			return d;
+		}
+		d = d->next;
+	}
+	return 0;
+}
+
+DScreen*
+drawlookupdscreen(int id)
+{
+	DScreen *s;
+
+	s = dscreen;
+	while(s){
+		if(s->id == id)
+			return s;
+		s = s->next;
+	}
+	return 0;
+}
+
+DScreen*
+drawlookupscreen(Client *client, int id, CScreen **cs)
+{
+	CScreen *s;
+
+	s = client->cscreen;
+	while(s){
+		if(s->dscreen->id == id){
+			*cs = s;
+			return s->dscreen;
+		}
+		s = s->next;
+	}
+	error(Enodrawscreen);
+	return 0;
+}
+
+DImage*
+allocdimage(Memimage *i)
+{
+	DImage *d;
+
+	d = malloc(sizeof(DImage));
+	if(d == 0)
+		return 0;
+	d->ref = 1;
+	d->name = 0;
+	d->vers = 0;
+	d->image = i;
+	d->dscreen = 0;
+	d->nfchar = 0;
+	d->fchar = 0;
+	d->fromname = 0;
+	return d;
+}
+
+Memimage*
+drawinstall(Client *client, int id, Memimage *i, DScreen *dscreen)
+{
+	DImage *d;
+
+	d = allocdimage(i);
+	if(d == 0)
+		return 0;
+	d->id = id;
+	d->dscreen = dscreen;
+	d->next = client->dimage[id&HASHMASK];
+	client->dimage[id&HASHMASK] = d;
+	return i;
+}
+
+Memscreen*
+drawinstallscreen(Client *client, DScreen *d, int id, DImage *dimage, DImage *dfill, int public)
+{
+	Memscreen *s;
+	CScreen *c;
+
+	c = malloc(sizeof(CScreen));
+	if(dimage && dimage->image && dimage->image->chan == 0)
+		panic("bad image %p in drawinstallscreen", dimage->image);
+
+	if(c == 0)
+		return 0;
+	if(d == 0){
+		d = malloc(sizeof(DScreen));
+		if(d == 0){
+			free(c);
+			return 0;
+		}
+		s = malloc(sizeof(Memscreen));
+		if(s == 0){
+			free(c);
+			free(d);
+			return 0;
+		}
+		s->frontmost = 0;
+		s->rearmost = 0;
+		d->dimage = dimage;
+		if(dimage){
+			s->image = dimage->image;
+			dimage->ref++;
+		}
+		d->dfill = dfill;
+		if(dfill){
+			s->fill = dfill->image;
+			dfill->ref++;
+		}
+		d->ref = 0;
+		d->id = id;
+		d->screen = s;
+		d->public = public;
+		d->next = dscreen;
+		d->owner = client;
+		dscreen = d;
+	}
+	c->dscreen = d;
+	d->ref++;
+	c->next = client->cscreen;
+	client->cscreen = c;
+	return d->screen;
+}
+
+void
+drawdelname(DName *name)
+{
+	int i;
+
+	free(name->name);
+	i = name-sdraw.name;
+	memmove(name, name+1, (sdraw.nname-(i+1))*sizeof(DName));
+	sdraw.nname--;
+}
+
+void
+drawfreedscreen(DScreen *this)
+{
+	DScreen *ds, *next;
+
+	this->ref--;
+	if(this->ref < 0)
+		print("negative ref in drawfreedscreen\n");
+	if(this->ref > 0)
+		return;
+	ds = dscreen;
+	if(ds == this){
+		dscreen = this->next;
+		goto Found;
+	}
+	while(next = ds->next){	/* assign = */
+		if(next == this){
+			ds->next = this->next;
+			goto Found;
+		}
+		ds = next;
+	}
+	error(Enodrawimage);
+
+    Found:
+	if(this->dimage)
+		drawfreedimage(this->dimage);
+	if(this->dfill)
+		drawfreedimage(this->dfill);
+	free(this->screen);
+	free(this);
+}
+
+void
+drawfreedimage(DImage *dimage)
+{
+	int i;
+	Memimage *l;
+	DScreen *ds;
+
+	dimage->ref--;
+	if(dimage->ref < 0)
+		print("negative ref in drawfreedimage\n");
+	if(dimage->ref > 0)
+		return;
+
+	/* any names? */
+	for(i=0; i<sdraw.nname; )
+		if(sdraw.name[i].dimage == dimage)
+			drawdelname(sdraw.name+i);
+		else
+			i++;
+	if(dimage->fromname){	/* acquired by name; owned by someone else*/
+		drawfreedimage(dimage->fromname);
+		goto Return;
+	}
+	ds = dimage->dscreen;
+	if(ds){
+		l = dimage->image;
+		if(screenimage && l->data == screenimage->data)
+			addflush(l->layer->screenr);
+		if(l->layer->refreshfn == drawrefresh)	/* else true owner will clean up */
+			free(l->layer->refreshptr);
+		l->layer->refreshptr = nil;
+		if(drawgoodname(dimage))
+			memldelete(l);
+		else
+			memlfree(l);
+		drawfreedscreen(ds);
+	}else
+		freememimage(dimage->image);
+    Return:
+	free(dimage->fchar);
+	free(dimage);
+}
+
+void
+drawuninstallscreen(Client *client, CScreen *this)
+{
+	CScreen *cs, *next;
+
+	cs = client->cscreen;
+	if(cs == this){
+		client->cscreen = this->next;
+		drawfreedscreen(this->dscreen);
+		free(this);
+		return;
+	}
+	while(next = cs->next){	/* assign = */
+		if(next == this){
+			cs->next = this->next;
+			drawfreedscreen(this->dscreen);
+			free(this);
+			return;
+		}
+		cs = next;
+	}
+}
+
+void
+drawuninstall(Client *client, int id)
+{
+	DImage *d, *next;
+
+	d = client->dimage[id&HASHMASK];
+	if(d == 0)
+		error(Enodrawimage);
+	if(d->id == id){
+		client->dimage[id&HASHMASK] = d->next;
+		drawfreedimage(d);
+		return;
+	}
+	while(next = d->next){	/* assign = */
+		if(next->id == id){
+			d->next = next->next;
+			drawfreedimage(next);
+			return;
+		}
+		d = next;
+	}
+	error(Enodrawimage);
+}
+
+void
+drawaddname(Client *client, DImage *di, int n, char *str)
+{
+	DName *name, *ename, *new, *t;
+
+	name = sdraw.name;
+	ename = &name[sdraw.nname];
+	for(; name<ename; name++)
+		if(drawcmp(name->name, str, n) == 0)
+			error(Enameused);
+	t = smalloc((sdraw.nname+1)*sizeof(DName));
+	memmove(t, sdraw.name, sdraw.nname*sizeof(DName));
+	free(sdraw.name);
+	sdraw.name = t;
+	new = &sdraw.name[sdraw.nname++];
+	new->name = smalloc(n+1);
+	memmove(new->name, str, n);
+	new->name[n] = 0;
+	new->dimage = di;
+	new->client = client;
+	new->vers = ++sdraw.vers;
+}
+
+Client*
+drawnewclient(void)
+{
+	Client *cl, **cp;
+	int i;
+
+	for(i=0; i<sdraw.nclient; i++){
+		cl = sdraw.client[i];
+		if(cl == 0)
+			break;
+	}
+	if(i == sdraw.nclient){
+		cp = malloc((sdraw.nclient+1)*sizeof(Client*));
+		if(cp == 0)
+			return 0;
+		memmove(cp, sdraw.client, sdraw.nclient*sizeof(Client*));
+		free(sdraw.client);
+		sdraw.client = cp;
+		sdraw.nclient++;
+		cp[i] = 0;
+	}
+	cl = malloc(sizeof(Client));
+	if(cl == 0)
+		return 0;
+	memset(cl, 0, sizeof(Client));
+	cl->slot = i;
+	cl->clientid = ++sdraw.clientid;
+	cl->op = SoverD;
+	sdraw.client[i] = cl;
+	return cl;
+}
+
+static int
+drawclientop(Client *cl)
+{
+	int op;
+
+	op = cl->op;
+	cl->op = SoverD;
+	return op;
+}
+
+int
+drawhasclients(void)
+{
+	/*
+	 * if draw has ever been used, we can't resize the frame buffer,
+	 * even if all clients have exited (nclients is cumulative); it's too
+	 * hard to make work.
+	 */
+	return sdraw.nclient != 0;
+}
+
+Client*
+drawclientofpath(ulong path)
+{
+	Client *cl;
+	int slot;
+
+	slot = CLIENTPATH(path);
+	if(slot == 0)
+		return nil;
+	cl = sdraw.client[slot-1];
+	if(cl==0 || cl->clientid==0)
+		return nil;
+	return cl;
+}
+
+
+Client*
+drawclient(Chan *c)
+{
+	Client *client;
+
+	client = drawclientofpath(c->qid.path);
+	if(client == nil)
+		error(Enoclient);
+	return client;
+}
+
+Memimage*
+drawimage(Client *client, uchar *a)
+{
+	DImage *d;
+
+	d = drawlookup(client, BGLONG(a), 1);
+	if(d == nil)
+		error(Enodrawimage);
+	return d->image;
+}
+
+void
+drawrectangle(Rectangle *r, uchar *a)
+{
+	r->min.x = BGLONG(a+0*4);
+	r->min.y = BGLONG(a+1*4);
+	r->max.x = BGLONG(a+2*4);
+	r->max.y = BGLONG(a+3*4);
+}
+
+void
+drawpoint(Point *p, uchar *a)
+{
+	p->x = BGLONG(a+0*4);
+	p->y = BGLONG(a+1*4);
+}
+
+Point
+drawchar(Memimage *dst, Memimage *rdst, Point p, Memimage *src, Point *sp, DImage *font, int index, int op)
+{
+	FChar *fc;
+	Rectangle r;
+	Point sp1;
+	static Memimage *tmp;
+
+	fc = &font->fchar[index];
+	r.min.x = p.x+fc->left;
+	r.min.y = p.y-(font->ascent-fc->miny);
+	r.max.x = r.min.x+(fc->maxx-fc->minx);
+	r.max.y = r.min.y+(fc->maxy-fc->miny);
+	sp1.x = sp->x+fc->left;
+	sp1.y = sp->y+fc->miny;
+
+	/*
+	 * If we're drawing greyscale fonts onto a VGA screen,
+	 * it's very costly to read the screen memory to do the
+	 * alpha blending inside memdraw.  If this is really a stringbg,
+	 * then rdst is the bg image (in main memory) which we can
+	 * refer to for the underlying dst pixels instead of reading dst
+	 * directly.
+	 */
+	if(ishwimage(dst) && !ishwimage(rdst) && font->image->depth > 1){
+		if(tmp == nil || tmp->chan != dst->chan || Dx(tmp->r) < Dx(r) || Dy(tmp->r) < Dy(r)){
+			if(tmp)
+				freememimage(tmp);
+			tmp = allocmemimage(Rect(0,0,Dx(r),Dy(r)), dst->chan);
+			if(tmp == nil)
+				goto fallback;
+		}
+		memdraw(tmp, Rect(0,0,Dx(r),Dy(r)), rdst, r.min, memopaque, ZP, S);
+		memdraw(tmp, Rect(0,0,Dx(r),Dy(r)), src, sp1, font->image, Pt(fc->minx, fc->miny), op);
+		memdraw(dst, r, tmp, ZP, memopaque, ZP, S);
+	}else{
+	fallback:
+		memdraw(dst, r, src, sp1, font->image, Pt(fc->minx, fc->miny), op);
+	}
+
+	p.x += fc->width;
+	sp->x += fc->width;
+	return p;
+}
+
+static DImage*
+makescreenimage(void)
+{
+	int width, depth;
+	ulong chan;
+	DImage *di;
+	Memdata *md;
+	Memimage *i;
+	Rectangle r;
+
+	if((md = attachscreen(&r, &chan, &depth, &width, &sdraw.softscreen)) == nil)
+		return nil;
+	assert(md->ref > 0);
+	if((i = allocmemimaged(r, chan, md)) == nil){
+		if(--md->ref == 0 && md->allocd)
+			free(md);
+		return nil;
+	}
+	i->width = width;
+	i->clipr = r;
+	di = allocdimage(i);
+	if(di == nil){
+		freememimage(i);	/* frees md */
+		return nil;
+	}
+	if(!waserror()){
+		snprint(screenname, sizeof screenname, "noborder.screen.%d", ++screennameid);
+		drawaddname(nil, di, strlen(screenname), screenname);
+		poperror();
+	}
+	return di;
+}
+
+static int
+initscreenimage(void)
+{
+	if(screenimage != nil)
+		return 1;
+
+	screendimage = makescreenimage();
+	if(screendimage == nil)
+		return 0;
+	screenimage = screendimage->image;
+// iprint("initscreenimage %p %p\n", screendimage, screenimage);
+	mouseresize();
+	return 1;
+}
+
+void
+deletescreenimage(void)
+{
+	dlock();
+	if(screenimage){
+		/* will be freed via screendimage; disable */
+		screenimage->clipr = ZR;
+		screenimage = nil;
+	}
+	if(screendimage){
+		drawfreedimage(screendimage);
+		screendimage = nil;
+	}
+	dunlock();
+}
+
+void
+resetscreenimage(void)
+{
+	dlock();
+	initscreenimage();
+	dunlock();
+}
+
+static Chan*
+drawattach(char *spec)
+{
+	dlock();
+	if(!initscreenimage()){
+		dunlock();
+		error("no frame buffer");
+	}
+	dunlock();
+	return devattach('i', spec);
+}
+
+static Walkqid*
+drawwalk(Chan *c, Chan *nc, char **name, int nname)
+{
+	if(screenimage == nil)
+		error("no frame buffer");
+	return devwalk(c, nc, name, nname, 0, 0, drawgen);
+}
+
+static int
+drawstat(Chan *c, uchar *db, int n)
+{
+	return devstat(c, db, n, 0, 0, drawgen);
+}
+
+static Chan*
+drawopen(Chan *c, int omode)
+{
+	Client *cl;
+	DName *dn;
+	DImage *di;
+
+	if(c->qid.type & QTDIR){
+		c = devopen(c, omode, 0, 0, drawgen);
+		c->iounit = IOUNIT;
+	}
+
+	dlock();
+	if(waserror()){
+		dunlock();
+		nexterror();
+	}
+
+	if(QID(c->qid) == Qnew){
+		cl = drawnewclient();
+		if(cl == 0)
+			error(Enodev);
+		c->qid.path = Qctl|((cl->slot+1)<<QSHIFT);
+	}
+
+	switch(QID(c->qid)){
+	case Qwinname:
+		break;
+
+	case Qnew:
+		break;
+
+	case Qctl:
+		cl = drawclient(c);
+		if(cl->busy)
+			error(Einuse);
+		cl->busy = 1;
+		flushrect = Rect(10000, 10000, -10000, -10000);
+		dn = drawlookupname(strlen(screenname), screenname);
+		if(dn == 0)
+			error("draw: cannot happen 2");
+		if(drawinstall(cl, 0, dn->dimage->image, 0) == 0)
+			error(Edrawmem);
+		di = drawlookup(cl, 0, 0);
+		if(di == 0)
+			error("draw: cannot happen 1");
+		di->vers = dn->vers;
+		di->name = smalloc(strlen(screenname)+1);
+		strcpy(di->name, screenname);
+		di->fromname = dn->dimage;
+		di->fromname->ref++;
+		incref(&cl->r);
+		break;
+
+	case Qcolormap:
+	case Qdata:
+	case Qrefresh:
+		cl = drawclient(c);
+		incref(&cl->r);
+		break;
+	}
+	dunlock();
+	poperror();
+	c->mode = openmode(omode);
+	c->flag |= COPEN;
+	c->offset = 0;
+	c->iounit = IOUNIT;
+	return c;
+}
+
+static void
+drawclose(Chan *c)
+{
+	int i;
+	DImage *d, **dp;
+	Client *cl;
+	Refresh *r;
+
+	if(QID(c->qid) < Qcolormap)	/* Qtopdir, Qnew, Q3rd, Q2nd have no client */
+		return;
+	dlock();
+	if(waserror()){
+		dunlock();
+		nexterror();
+	}
+
+	cl = drawclient(c);
+	if(QID(c->qid) == Qctl)
+		cl->busy = 0;
+	if((c->flag&COPEN) && (decref(&cl->r)==0)){
+		while(r = cl->refresh){	/* assign = */
+			cl->refresh = r->next;
+			free(r);
+		}
+		/* free names */
+		for(i=0; i<sdraw.nname; )
+			if(sdraw.name[i].client == cl)
+				drawdelname(sdraw.name+i);
+			else
+				i++;
+		while(cl->cscreen)
+			drawuninstallscreen(cl, cl->cscreen);
+		/* all screens are freed, so now we can free images */
+		dp = cl->dimage;
+		for(i=0; i<NHASH; i++){
+			while((d = *dp) != nil){
+				*dp = d->next;
+				drawfreedimage(d);
+			}
+			dp++;
+		}
+		sdraw.client[cl->slot] = 0;
+		drawflush();	/* to erase visible, now dead windows */
+		free(cl);
+	}
+	dunlock();
+	poperror();
+}
+
+long
+drawread(Chan *c, void *a, long n, vlong off)
+{
+	int index, m;
+	ulong red, green, blue;
+	Client *cl;
+	uchar *p;
+	Refresh *r;
+	DImage *di;
+	Memimage *i;
+	ulong offset = off;
+	char buf[16];
+
+	if(c->qid.type & QTDIR)
+		return devdirread(c, a, n, 0, 0, drawgen);
+	if(QID(c->qid) == Qwinname)
+		return readstr(off, a, n, screenname);
+
+	cl = drawclient(c);
+	dlock();
+	if(waserror()){
+		dunlock();
+		nexterror();
+	}
+	switch(QID(c->qid)){
+	case Qctl:
+		if(n < 12*12)
+			error(Eshortread);
+		if(cl->infoid < 0)
+			error(Enodrawimage);
+		if(cl->infoid == 0){
+			i = screenimage;
+			if(i == nil)
+				error(Enodrawimage);
+		}else{
+			di = drawlookup(cl, cl->infoid, 1);
+			if(di == nil)
+				error(Enodrawimage);
+			i = di->image;
+		}
+		n = sprint(a, "%11d %11d %11s %11d %11d %11d %11d %11d %11d %11d %11d %11d",
+			cl->clientid, cl->infoid, chantostr(buf, i->chan), (i->flags&Frepl)==Frepl,
+			i->r.min.x, i->r.min.y, i->r.max.x, i->r.max.y,
+			i->clipr.min.x, i->clipr.min.y, i->clipr.max.x, i->clipr.max.y);
+		((char*)a)[n++] = ' ';
+		cl->infoid = -1;
+		break;
+
+	case Qcolormap:
+		p = malloc(4*12*256+1);
+		if(p == 0)
+			error(Enomem);
+		m = 0;
+		for(index = 0; index < 256; index++){
+			getcolor(index, &red, &green, &blue);
+			m += sprint((char*)p+m, "%11d %11lud %11lud %11lud\n", index, red>>24, green>>24, blue>>24);
+		}
+		n = readstr(offset, a, n, (char*)p);
+		free(p);
+		break;
+
+	case Qdata:
+		if(cl->readdata == nil)
+			error("no draw data");
+		if(n < cl->nreaddata)
+			error(Eshortread);
+		n = cl->nreaddata;
+		memmove(a, cl->readdata, cl->nreaddata);
+		free(cl->readdata);
+		cl->readdata = nil;
+		break;
+
+	case Qrefresh:
+		if(n < 5*4)
+			error(Ebadarg);
+		for(;;){
+			if(cl->refreshme || cl->refresh)
+				break;
+			dunlock();
+			if(waserror()){
+				dlock();
+				nexterror();
+			}
+			qlock(&cl->refq);
+			if(waserror()){
+				qunlock(&cl->refq);
+				nexterror();
+			}
+			rendsleep(&cl->refrend, drawrefactive, cl);
+			poperror();
+			qunlock(&cl->refq);
+			poperror();
+			dlock();
+		}
+		p = a;
+		while(cl->refresh && n>=5*4){
+			r = cl->refresh;
+			BPLONG(p+0*4, r->dimage->id);
+			BPLONG(p+1*4, r->r.min.x);
+			BPLONG(p+2*4, r->r.min.y);
+			BPLONG(p+3*4, r->r.max.x);
+			BPLONG(p+4*4, r->r.max.y);
+			cl->refresh = r->next;
+			free(r);
+			p += 5*4;
+			n -= 5*4;
+		}
+		cl->refreshme = 0;
+		n = p-(uchar*)a;
+		break;
+	}
+	dunlock();
+	poperror();
+	return n;
+}
+
+void
+drawwakeall(void)
+{
+	Client *cl;
+	int i;
+
+	for(i=0; i<sdraw.nclient; i++){
+		cl = sdraw.client[i];
+		if(cl && (cl->refreshme || cl->refresh))
+			rendwakeup(&cl->refrend);
+	}
+}
+
+static long
+drawwrite(Chan *c, void *a, long n, vlong)
+{
+	char buf[128], *fields[4], *q;
+	Client *cl;
+	int i, m, red, green, blue, x;
+
+	if(c->qid.type & QTDIR)
+		error(Eisdir);
+	cl = drawclient(c);
+	dlock();
+	if(waserror()){
+		drawwakeall();
+		dunlock();
+		nexterror();
+	}
+	switch(QID(c->qid)){
+	case Qctl:
+		if(n != 4)
+			error("unknown draw control request");
+		cl->infoid = BGLONG((uchar*)a);
+		break;
+
+	case Qcolormap:
+		m = n;
+		n = 0;
+		while(m > 0){
+			x = m;
+			if(x > sizeof(buf)-1)
+				x = sizeof(buf)-1;
+			q = memccpy(buf, a, '\n', x);
+			if(q == 0)
+				break;
+			i = q-buf;
+			n += i;
+			a = (char*)a + i;
+			m -= i;
+			*q = 0;
+			if(tokenize(buf, fields, nelem(fields)) != 4)
+				error(Ebadarg);
+			i = strtoul(fields[0], 0, 0);
+			red = strtoul(fields[1], 0, 0);
+			green = strtoul(fields[2], 0, 0);
+			blue = strtoul(fields[3], &q, 0);
+			if(fields[3] == q)
+				error(Ebadarg);
+			if(red>255 || green>255 || blue>255 || i<0 || i>255)
+				error(Ebadarg);
+			red |= red<<8;
+			red |= red<<16;
+			green |= green<<8;
+			green |= green<<16;
+			blue |= blue<<8;
+			blue |= blue<<16;
+			setcolor(i, red, green, blue);
+		}
+		break;
+
+	case Qdata:
+		drawmesg(cl, a, n);
+		drawwakeall();
+		break;
+
+	default:
+		error(Ebadusefd);
+	}
+	dunlock();
+	poperror();
+	return n;
+}
+
+uchar*
+drawcoord(uchar *p, uchar *maxp, int oldx, int *newx)
+{
+	int b, x;
+
+	if(p >= maxp)
+		error(Eshortdraw);
+	b = *p++;
+	x = b & 0x7F;
+	if(b & 0x80){
+		if(p+1 >= maxp)
+			error(Eshortdraw);
+		x |= *p++ << 7;
+		x |= *p++ << 15;
+		if(x & (1<<22))
+			x |= ~0<<23;
+	}else{
+		if(b & 0x40)
+			x |= ~0<<7;
+		x += oldx;
+	}
+	*newx = x;
+	return p;
+}
+
+static void
+printmesg(char *fmt, uchar *a, int plsprnt)
+{
+	char buf[256];
+	char *p, *q;
+	int s;
+
+	if(1|| plsprnt==0){
+		SET(s,q,p);
+		USED(fmt, a, buf, p, q, s);
+		return;
+	}
+	q = buf;
+	*q++ = *a++;
+	for(p=fmt; *p; p++){
+		switch(*p){
+		case 'l':
+			q += sprint(q, " %ld", (long)BGLONG(a));
+			a += 4;
+			break;
+		case 'L':
+			q += sprint(q, " %.8lux", (ulong)BGLONG(a));
+			a += 4;
+			break;
+		case 'R':
+			q += sprint(q, " [%d %d %d %d]", BGLONG(a), BGLONG(a+4), BGLONG(a+8), BGLONG(a+12));
+			a += 16;
+			break;
+		case 'P':
+			q += sprint(q, " [%d %d]", BGLONG(a), BGLONG(a+4));
+			a += 8;
+			break;
+		case 'b':
+			q += sprint(q, " %d", *a++);
+			break;
+		case 's':
+			q += sprint(q, " %d", BGSHORT(a));
+			a += 2;
+			break;
+		case 'S':
+			q += sprint(q, " %.4ux", BGSHORT(a));
+			a += 2;
+			break;
+		}
+	}
+	*q++ = '\n';
+	*q = 0;
+	// iprint("%.*s", (int)(q-buf), buf);
+}
+
+void
+drawmesg(Client *client, void *av, int n)
+{
+	int c, repl, m, y, dstid, scrnid, ni, ci, j, nw, e0, e1, op, ox, oy, oesize, esize, doflush;
+	uchar *u, *a, refresh;
+	char *fmt;
+	ulong value, chan;
+	Rectangle r, clipr;
+	Point p, q, *pp, sp;
+	Memimage *i, *bg, *dst, *src, *mask;
+	Memimage *l, **lp;
+	Memscreen *scrn;
+	DImage *font, *ll, *di, *ddst, *dsrc;
+	DName *dn;
+	DScreen *dscrn;
+	FChar *fc;
+	Refx *refx;
+	CScreen *cs;
+	Refreshfn reffn;
+
+	a = av;
+	m = 0;
+	fmt = nil;
+	if(waserror()){
+		if(fmt) printmesg(fmt, a, 1);
+	/*	iprint("error: %s\n", up->errstr);	*/
+		nexterror();
+	}
+	while((n-=m) > 0){
+		USED(fmt);
+		a += m;
+		switch(*a){
+		default:
+			error("bad draw command");
+		/* new allocate: 'b' id[4] screenid[4] refresh[1] chan[4] repl[1] R[4*4] clipR[4*4] rrggbbaa[4] */
+		case 'b':
+			printmesg(fmt="LLbLbRRL", a, 0);
+			m = 1+4+4+1+4+1+4*4+4*4+4;
+			if(n < m)
+				error(Eshortdraw);
+			dstid = BGLONG(a+1);
+			scrnid = BGLONG(a+5);
+			refresh = a[9];
+			chan = BGLONG(a+10);
+			repl = a[14];
+			drawrectangle(&r, a+15);
+			drawrectangle(&clipr, a+31);
+			value = BGLONG(a+47);
+			if(drawlookup(client, dstid, 0))
+				error(Eimageexists);
+			if(scrnid){
+				dscrn = drawlookupscreen(client, scrnid, &cs);
+				scrn = dscrn->screen;
+				if(repl || chan!=scrn->image->chan)
+					error("image parameters incompatible with screen");
+				reffn = nil;
+				switch(refresh){
+				case Refbackup:
+					break;
+				case Refnone:
+					reffn = memlnorefresh;
+					break;
+				case Refmesg:
+					reffn = drawrefresh;
+					break;
+				default:
+					error("unknown refresh method");
+				}
+				l = memlalloc(scrn, r, reffn, 0, value);
+				if(l == 0)
+					error(Edrawmem);
+				addflush(l->layer->screenr);
+				l->clipr = clipr;
+				rectclip(&l->clipr, r);
+				if(drawinstall(client, dstid, l, dscrn) == 0){
+					memldelete(l);
+					error(Edrawmem);
+				}
+				dscrn->ref++;
+				if(reffn){
+					refx = nil;
+					if(reffn == drawrefresh){
+						refx = malloc(sizeof(Refx));
+						if(refx == 0){
+							drawuninstall(client, dstid);
+							error(Edrawmem);
+						}
+						refx->client = client;
+						refx->dimage = drawlookup(client, dstid, 1);
+					}
+					memlsetrefresh(l, reffn, refx);
+				}
+				continue;
+			}
+			i = allocmemimage(r, chan);
+			if(i == 0)
+				error(Edrawmem);
+			if(repl)
+				i->flags |= Frepl;
+			i->clipr = clipr;
+			if(!repl)
+				rectclip(&i->clipr, r);
+			if(drawinstall(client, dstid, i, 0) == 0){
+				freememimage(i);
+				error(Edrawmem);
+			}
+			memfillcolor(i, value);
+			continue;
+
+		/* allocate screen: 'A' id[4] imageid[4] fillid[4] public[1] */
+		case 'A':
+			printmesg(fmt="LLLb", a, 1);
+			m = 1+4+4+4+1;
+			if(n < m)
+				error(Eshortdraw);
+			dstid = BGLONG(a+1);
+			if(dstid == 0)
+				error(Ebadarg);
+			if(drawlookupdscreen(dstid))
+				error(Escreenexists);
+			ddst = drawlookup(client, BGLONG(a+5), 1);
+			dsrc = drawlookup(client, BGLONG(a+9), 1);
+			if(ddst==0 || dsrc==0)
+				error(Enodrawimage);
+			if(drawinstallscreen(client, 0, dstid, ddst, dsrc, a[13]) == 0)
+				error(Edrawmem);
+			continue;
+
+		/* set repl and clip: 'c' dstid[4] repl[1] clipR[4*4] */
+		case 'c':
+			printmesg(fmt="LbR", a, 0);
+			m = 1+4+1+4*4;
+			if(n < m)
+				error(Eshortdraw);
+			ddst = drawlookup(client, BGLONG(a+1), 1);
+			if(ddst == nil)
+				error(Enodrawimage);
+			if(ddst->name)
+				error("cannot change repl/clipr of shared image");
+			dst = ddst->image;
+			if(a[5])
+				dst->flags |= Frepl;
+			drawrectangle(&dst->clipr, a+6);
+			continue;
+
+		/* draw: 'd' dstid[4] srcid[4] maskid[4] R[4*4] P[2*4] P[2*4] */
+		case 'd':
+			printmesg(fmt="LLLRPP", a, 0);
+			m = 1+4+4+4+4*4+2*4+2*4;
+			if(n < m)
+				error(Eshortdraw);
+			dst = drawimage(client, a+1);
+			dstid = BGLONG(a+1);
+			src = drawimage(client, a+5);
+			mask = drawimage(client, a+9);
+			drawrectangle(&r, a+13);
+			drawpoint(&p, a+29);
+			drawpoint(&q, a+37);
+			op = drawclientop(client);
+			memdraw(dst, r, src, p, mask, q, op);
+			dstflush(dstid, dst, r);
+			continue;
+
+		/* toggle debugging: 'D' val[1] */
+		case 'D':
+			printmesg(fmt="b", a, 0);
+			m = 1+1;
+			if(n < m)
+				error(Eshortdraw);
+			continue;
+
+		/* ellipse: 'e' dstid[4] srcid[4] center[2*4] a[4] b[4] thick[4] sp[2*4] alpha[4] phi[4]*/
+		case 'e':
+		case 'E':
+			printmesg(fmt="LLPlllPll", a, 0);
+			m = 1+4+4+2*4+4+4+4+2*4+2*4;
+			if(n < m)
+				error(Eshortdraw);
+			dst = drawimage(client, a+1);
+			dstid = BGLONG(a+1);
+			src = drawimage(client, a+5);
+			drawpoint(&p, a+9);
+			e0 = BGLONG(a+17);
+			e1 = BGLONG(a+21);
+			if(e0<0 || e1<0)
+				error("invalid ellipse semidiameter");
+			j = BGLONG(a+25);
+			if(j < 0)
+				error("negative ellipse thickness");
+			drawpoint(&sp, a+29);
+			c = j;
+			if(*a == 'E')
+				c = -1;
+			ox = BGLONG(a+37);
+			oy = BGLONG(a+41);
+			op = drawclientop(client);
+			/* high bit indicates arc angles are present */
+			if(ox & (1<<31)){
+				if((ox & (1<<30)) == 0)
+					ox &= ~(1<<31);
+				memarc(dst, p, e0, e1, c, src, sp, ox, oy, op);
+			}else
+				memellipse(dst, p, e0, e1, c, src, sp, op);
+			dstflush(dstid, dst, Rect(p.x-e0-j, p.y-e1-j, p.x+e0+j+1, p.y+e1+j+1));
+			continue;
+
+		/* free: 'f' id[4] */
+		case 'f':
+			printmesg(fmt="L", a, 1);
+			m = 1+4;
+			if(n < m)
+				error(Eshortdraw);
+			ll = drawlookup(client, BGLONG(a+1), 0);
+			if(ll && ll->dscreen && ll->dscreen->owner != client)
+				ll->dscreen->owner->refreshme = 1;
+			drawuninstall(client, BGLONG(a+1));
+			continue;
+
+		/* free screen: 'F' id[4] */
+		case 'F':
+			printmesg(fmt="L", a, 1);
+			m = 1+4;
+			if(n < m)
+				error(Eshortdraw);
+			drawlookupscreen(client, BGLONG(a+1), &cs);
+			drawuninstallscreen(client, cs);
+			continue;
+
+		/* initialize font: 'i' fontid[4] nchars[4] ascent[1] */
+		case 'i':
+			printmesg(fmt="Llb", a, 1);
+			m = 1+4+4+1;
+			if(n < m)
+				error(Eshortdraw);
+			dstid = BGLONG(a+1);
+			if(dstid == 0)
+				error("cannot use display as font");
+			font = drawlookup(client, dstid, 1);
+			if(font == 0)
+				error(Enodrawimage);
+			if(font->image->layer)
+				error("cannot use window as font");
+			ni = BGLONG(a+5);
+			if(ni<=0 || ni>4096)
+				error("bad font size (4096 chars max)");
+			free(font->fchar);	/* should we complain if non-zero? */
+			font->fchar = malloc(ni*sizeof(FChar));
+			if(font->fchar == 0)
+				error("no memory for font");
+			memset(font->fchar, 0, ni*sizeof(FChar));
+			font->nfchar = ni;
+			font->ascent = a[9];
+			continue;
+
+		/* load character: 'l' fontid[4] srcid[4] index[2] R[4*4] P[2*4] left[1] width[1] */
+		case 'l':
+			printmesg(fmt="LLSRPbb", a, 0);
+			m = 1+4+4+2+4*4+2*4+1+1;
+			if(n < m)
+				error(Eshortdraw);
+			font = drawlookup(client, BGLONG(a+1), 1);
+			if(font == 0)
+				error(Enodrawimage);
+			if(font->nfchar == 0)
+				error(Enotfont);
+			src = drawimage(client, a+5);
+			ci = BGSHORT(a+9);
+			if(ci >= font->nfchar)
+				error(Eindex);
+			drawrectangle(&r, a+11);
+			drawpoint(&p, a+27);
+			memdraw(font->image, r, src, p, memopaque, p, S);
+			fc = &font->fchar[ci];
+			fc->minx = r.min.x;
+			fc->maxx = r.max.x;
+			fc->miny = r.min.y;
+			fc->maxy = r.max.y;
+			fc->left = a[35];
+			fc->width = a[36];
+			continue;
+
+		/* draw line: 'L' dstid[4] p0[2*4] p1[2*4] end0[4] end1[4] radius[4] srcid[4] sp[2*4] */
+		case 'L':
+			printmesg(fmt="LPPlllLP", a, 0);
+			m = 1+4+2*4+2*4+4+4+4+4+2*4;
+			if(n < m)
+				error(Eshortdraw);
+			dst = drawimage(client, a+1);
+			dstid = BGLONG(a+1);
+			drawpoint(&p, a+5);
+			drawpoint(&q, a+13);
+			e0 = BGLONG(a+21);
+			e1 = BGLONG(a+25);
+			j = BGLONG(a+29);
+			if(j < 0)
+				error("negative line width");
+			src = drawimage(client, a+33);
+			drawpoint(&sp, a+37);
+			op = drawclientop(client);
+			memline(dst, p, q, e0, e1, j, src, sp, op);
+			/* avoid memlinebbox if possible */
+			if(dstid==0 || dst->layer!=nil){
+				/* BUG: this is terribly inefficient: update maximal containing rect*/
+				r = memlinebbox(p, q, e0, e1, j);
+				dstflush(dstid, dst, insetrect(r, -(1+1+j)));
+			}
+			continue;
+
+		/* create image mask: 'm' newid[4] id[4] */
+/*
+ *
+		case 'm':
+			printmesg("LL", a, 0);
+			m = 4+4;
+			if(n < m)
+				error(Eshortdraw);
+			break;
+ *
+ */
+
+		/* attach to a named image: 'n' dstid[4] j[1] name[j] */
+		case 'n':
+			printmesg(fmt="Lz", a, 0);
+			m = 1+4+1;
+			if(n < m)
+				error(Eshortdraw);
+			j = a[5];
+			if(j == 0)	/* give me a non-empty name please */
+				error(Eshortdraw);
+			m += j;
+			if(n < m)
+				error(Eshortdraw);
+			dstid = BGLONG(a+1);
+			if(drawlookup(client, dstid, 0))
+				error(Eimageexists);
+			dn = drawlookupname(j, (char*)a+6);
+			if(dn == nil)
+				error(Enoname);
+			if(drawinstall(client, dstid, dn->dimage->image, 0) == 0)
+				error(Edrawmem);
+			di = drawlookup(client, dstid, 0);
+			if(di == 0)
+				error("draw: cannot happen");
+			di->vers = dn->vers;
+			di->name = smalloc(j+1);
+			di->fromname = dn->dimage;
+			di->fromname->ref++;
+			memmove(di->name, a+6, j);
+			di->name[j] = 0;
+			client->infoid = dstid;
+			continue;
+
+		/* name an image: 'N' dstid[4] in[1] j[1] name[j] */
+		case 'N':
+			printmesg(fmt="Lbz", a, 0);
+			m = 1+4+1+1;
+			if(n < m)
+				error(Eshortdraw);
+			c = a[5];
+			j = a[6];
+			if(j == 0)	/* give me a non-empty name please */
+				error(Eshortdraw);
+			m += j;
+			if(n < m)
+				error(Eshortdraw);
+			di = drawlookup(client, BGLONG(a+1), 0);
+			if(di == 0)
+				error(Enodrawimage);
+			if(di->name)
+				error(Enamed);
+			if(c)
+				drawaddname(client, di, j, (char*)a+7);
+			else{
+				dn = drawlookupname(j, (char*)a+7);
+				if(dn == nil)
+					error(Enoname);
+				if(dn->dimage != di)
+					error(Ewrongname);
+				drawdelname(dn);
+			}
+			continue;
+
+		/* position window: 'o' id[4] r.min [2*4] screenr.min [2*4] */
+		case 'o':
+			printmesg(fmt="LPP", a, 0);
+			m = 1+4+2*4+2*4;
+			if(n < m)
+				error(Eshortdraw);
+			dst = drawimage(client, a+1);
+			if(dst->layer){
+				drawpoint(&p, a+5);
+				drawpoint(&q, a+13);
+				r = dst->layer->screenr;
+				ni = memlorigin(dst, p, q);
+				if(ni < 0)
+					error("image origin failed");
+				if(ni > 0){
+					addflush(r);
+					addflush(dst->layer->screenr);
+					ll = drawlookup(client, BGLONG(a+1), 1);
+					drawrefreshscreen(ll, client);
+				}
+			}
+			continue;
+
+		/* set compositing operator for next draw operation: 'O' op */
+		case 'O':
+			printmesg(fmt="b", a, 0);
+			m = 1+1;
+			if(n < m)
+				error(Eshortdraw);
+			client->op = a[1];
+			continue;
+
+		/* filled polygon: 'P' dstid[4] n[2] wind[4] ignore[2*4] srcid[4] sp[2*4] p0[2*4] dp[2*2*n] */
+		/* polygon: 'p' dstid[4] n[2] end0[4] end1[4] radius[4] srcid[4] sp[2*4] p0[2*4] dp[2*2*n] */
+		case 'p':
+		case 'P':
+			printmesg(fmt="LslllLPP", a, 0);
+			m = 1+4+2+4+4+4+4+2*4;
+			if(n < m)
+				error(Eshortdraw);
+			dstid = BGLONG(a+1);
+			dst = drawimage(client, a+1);
+			ni = BGSHORT(a+5);
+			if(ni < 0)
+				error("negative count in polygon");
+			e0 = BGLONG(a+7);
+			e1 = BGLONG(a+11);
+			j = 0;
+			if(*a == 'p'){
+				j = BGLONG(a+15);
+				if(j < 0)
+					error("negative polygon line width");
+			}
+			src = drawimage(client, a+19);
+			drawpoint(&sp, a+23);
+			drawpoint(&p, a+31);
+			ni++;
+			pp = malloc(ni*sizeof(Point));
+			if(pp == nil)
+				error(Enomem);
+			doflush = 0;
+			if(dstid==0 || (screenimage && dst->layer && dst->layer->screen->image->data == screenimage->data))
+				doflush = 1;	/* simplify test in loop */
+			ox = oy = 0;
+			esize = 0;
+			u = a+m;
+			for(y=0; y<ni; y++){
+				q = p;
+				oesize = esize;
+				u = drawcoord(u, a+n, ox, &p.x);
+				u = drawcoord(u, a+n, oy, &p.y);
+				ox = p.x;
+				oy = p.y;
+				if(doflush){
+					esize = j;
+					if(*a == 'p'){
+						if(y == 0){
+							c = memlineendsize(e0);
+							if(c > esize)
+								esize = c;
+						}
+						if(y == ni-1){
+							c = memlineendsize(e1);
+							if(c > esize)
+								esize = c;
+						}
+					}
+					if(*a=='P' && e0!=1 && e0 !=~0)
+						r = dst->clipr;
+					else if(y > 0){
+						r = Rect(q.x-oesize, q.y-oesize, q.x+oesize+1, q.y+oesize+1);
+						combinerect(&r, Rect(p.x-esize, p.y-esize, p.x+esize+1, p.y+esize+1));
+					}
+					if(rectclip(&r, dst->clipr))		/* should perhaps be an arg to dstflush */
+						dstflush(dstid, dst, r);
+				}
+				pp[y] = p;
+			}
+			if(y == 1)
+				dstflush(dstid, dst, Rect(p.x-esize, p.y-esize, p.x+esize+1, p.y+esize+1));
+			op = drawclientop(client);
+			if(*a == 'p')
+				mempoly(dst, pp, ni, e0, e1, j, src, sp, op);
+			else
+				memfillpoly(dst, pp, ni, e0, src, sp, op);
+			free(pp);
+			m = u-a;
+			continue;
+
+		/* read: 'r' id[4] R[4*4] */
+		case 'r':
+			printmesg(fmt="LR", a, 0);
+			m = 1+4+4*4;
+			if(n < m)
+				error(Eshortdraw);
+			i = drawimage(client, a+1);
+			drawrectangle(&r, a+5);
+			if(!rectinrect(r, i->r))
+				error(Ereadoutside);
+			c = bytesperline(r, i->depth);
+			c *= Dy(r);
+			free(client->readdata);
+			client->readdata = mallocz(c, 0);
+			if(client->readdata == nil)
+				error("readimage malloc failed");
+			client->nreaddata = memunload(i, r, client->readdata, c);
+			if(client->nreaddata < 0){
+				free(client->readdata);
+				client->readdata = nil;
+				error("bad readimage call");
+			}
+			continue;
+
+		/* string: 's' dstid[4] srcid[4] fontid[4] P[2*4] clipr[4*4] sp[2*4] ni[2] ni*(index[2]) */
+		/* stringbg: 'x' dstid[4] srcid[4] fontid[4] P[2*4] clipr[4*4] sp[2*4] ni[2] bgid[4] bgpt[2*4] ni*(index[2]) */
+		case 's':
+		case 'x':
+			printmesg(fmt="LLLPRPs", a, 0);
+			m = 1+4+4+4+2*4+4*4+2*4+2;
+			if(*a == 'x')
+				m += 4+2*4;
+			if(n < m)
+				error(Eshortdraw);
+
+			dst = drawimage(client, a+1);
+			dstid = BGLONG(a+1);
+			src = drawimage(client, a+5);
+			font = drawlookup(client, BGLONG(a+9), 1);
+			if(font == 0)
+				error(Enodrawimage);
+			if(font->nfchar == 0)
+				error(Enotfont);
+			drawpoint(&p, a+13);
+			drawrectangle(&r, a+21);
+			drawpoint(&sp, a+37);
+			ni = BGSHORT(a+45);
+			u = a+m;
+			m += ni*2;
+			if(n < m)
+				error(Eshortdraw);
+			clipr = dst->clipr;
+			dst->clipr = r;
+			op = drawclientop(client);
+			bg = dst;
+			if(*a == 'x'){
+				/* paint background */
+				bg = drawimage(client, a+47);
+				drawpoint(&q, a+51);
+				r.min.x = p.x;
+				r.min.y = p.y-font->ascent;
+				r.max.x = p.x;
+				r.max.y = r.min.y+Dy(font->image->r);
+				j = ni;
+				while(--j >= 0){
+					ci = BGSHORT(u);
+					if(ci<0 || ci>=font->nfchar){
+						dst->clipr = clipr;
+						error(Eindex);
+					}
+					r.max.x += font->fchar[ci].width;
+					u += 2;
+				}
+				memdraw(dst, r, bg, q, memopaque, ZP, op);
+				u -= 2*ni;
+			}
+			q = p;
+			while(--ni >= 0){
+				ci = BGSHORT(u);
+				if(ci<0 || ci>=font->nfchar){
+					dst->clipr = clipr;
+					error(Eindex);
+				}
+				q = drawchar(dst, bg, q, src, &sp, font, ci, op);
+				u += 2;
+			}
+			dst->clipr = clipr;
+			p.y -= font->ascent;
+			dstflush(dstid, dst, Rect(p.x, p.y, q.x, p.y+Dy(font->image->r)));
+			continue;
+
+		/* use public screen: 'S' id[4] chan[4] */
+		case 'S':
+			printmesg(fmt="Ll", a, 0);
+			m = 1+4+4;
+			if(n < m)
+				error(Eshortdraw);
+			dstid = BGLONG(a+1);
+			if(dstid == 0)
+				error(Ebadarg);
+			dscrn = drawlookupdscreen(dstid);
+			if(dscrn==0 || (dscrn->public==0 && dscrn->owner!=client))
+				error(Enodrawscreen);
+			if(dscrn->screen->image->chan != BGLONG(a+5))
+				error("inconsistent chan");
+			if(drawinstallscreen(client, dscrn, 0, 0, 0, 0) == 0)
+				error(Edrawmem);
+			continue;
+
+		/* top or bottom windows: 't' top[1] nw[2] n*id[4] */
+		case 't':
+			printmesg(fmt="bsL", a, 0);
+			m = 1+1+2;
+			if(n < m)
+				error(Eshortdraw);
+			nw = BGSHORT(a+2);
+			if(nw < 0)
+				error(Ebadarg);
+			if(nw == 0)
+				continue;
+			m += nw*4;
+			if(n < m)
+				error(Eshortdraw);
+			lp = malloc(nw*sizeof(Memimage*));
+			if(lp == 0)
+				error(Enomem);
+			if(waserror()){
+				free(lp);
+				nexterror();
+			}
+			for(j=0; j<nw; j++){
+				lp[j] = drawimage(client, a+1+1+2+j*4);
+				if(lp[j]->layer == 0)
+					error("images are not windows");
+				if(lp[j]->layer->screen != lp[0]->layer->screen)
+					error("images not on same screen");
+			}
+			if(a[1])
+				memltofrontn(lp, nw);
+			else
+				memltorearn(lp, nw);
+			if(screenimage && lp[0]->layer->screen->image->data == screenimage->data)
+				for(j=0; j<nw; j++)
+					addflush(lp[j]->layer->screenr);
+			ll = drawlookup(client, BGLONG(a+1+1+2), 1);
+			drawrefreshscreen(ll, client);
+			poperror();
+			free(lp);
+			continue;
+
+		/* visible: 'v' */
+		case 'v':
+			printmesg(fmt="", a, 0);
+			m = 1;
+			drawflush();
+			continue;
+
+		/* write: 'y' id[4] R[4*4] data[x*1] */
+		/* write from compressed data: 'Y' id[4] R[4*4] data[x*1] */
+		case 'y':
+		case 'Y':
+			printmesg(fmt="LR", a, 0);
+		//	iprint("load %c\n", *a);
+			m = 1+4+4*4;
+			if(n < m)
+				error(Eshortdraw);
+			dstid = BGLONG(a+1);
+			dst = drawimage(client, a+1);
+			drawrectangle(&r, a+5);
+			if(!rectinrect(r, dst->r))
+				error(Ewriteoutside);
+			y = memload(dst, r, a+m, n-m, *a=='Y');
+			if(y < 0)
+				error("bad writeimage call");
+			dstflush(dstid, dst, r);
+			m += y;
+			continue;
+		}
+	}
+	poperror();
+}
+
+Dev drawdevtab = {
+	'i',
+	"draw",
+
+	devreset,
+	devinit,
+	drawattach,
+	drawwalk,
+	drawstat,
+	drawopen,
+	devcreate,
+	drawclose,
+	drawread,
+	devbread,
+	drawwrite,
+	devbwrite,
+	devremove,
+	devwstat,
+};
+
+/*
+ * On 8 bit displays, load the default color map
+ */
+void
+drawcmap(void)
+{
+	int r, g, b, cr, cg, cb, v;
+	int num, den;
+	int i, j;
+
+	for(r=0,i=0; r!=4; r++)
+	    for(v=0; v!=4; v++,i+=16){
+		for(g=0,j=v-r; g!=4; g++)
+		    for(b=0;b!=4;b++,j++){
+			den = r;
+			if(g > den)
+				den = g;
+			if(b > den)
+				den = b;
+			if(den == 0)	/* divide check -- pick grey shades */
+				cr = cg = cb = v*17;
+			else{
+				num = 17*(4*den+v);
+				cr = r*num/den;
+				cg = g*num/den;
+				cb = b*num/den;
+			}
+			setcolor(i+(j&15),
+				cr*0x01010101, cg*0x01010101, cb*0x01010101);
+		    }
+	}
+}
--- /dev/null
+++ b/sys/src/cmd/vnc/devmouse.c
@@ -1,0 +1,346 @@
+#include	<u.h>
+#include	<libc.h>
+#include	"compat.h"
+#include	"error.h"
+
+#define	Image	IMAGE
+#include	<draw.h>
+#include	<memdraw.h>
+#include	<cursor.h>
+#include	"screen.h"
+
+typedef struct Mouseinfo	Mouseinfo;
+typedef struct Mousestate	Mousestate;
+
+struct Mousestate
+{
+	Point	xy;		/* mouse.xy */
+	int	buttons;	/* mouse.buttons */
+	ulong	counter;	/* increments every update */
+	ulong	msec;		/* time of last event */
+};
+
+struct Mouseinfo
+{
+	Lock;
+	Mousestate;
+	ulong	lastcounter;	/* value when /dev/mouse read */
+	Rendez	r;
+	Ref;
+	int	resize;
+	int	open;
+	Mousestate	queue[16];	/* circular buffer of click events */
+	ulong	ri;		/* read index into queue */
+	ulong	wi;		/* write index into queue */
+};
+
+Mouseinfo	mouse;
+Cursorinfo	cursor;
+Cursor		curs;
+
+void	Cursortocursor(Cursor*);
+int	mousechanged(void*);
+
+enum{
+	Qdir,
+	Qcursor,
+	Qmouse,
+	Qmousein,
+	Qmousectl,
+};
+
+static Dirtab mousedir[]={
+	".",	{Qdir, 0, QTDIR},	0,			DMDIR|0555,
+	"cursor",	{Qcursor},	0,			0666,
+	"mouse",	{Qmouse},	0,			0666,
+	"mousein",	{Qmousein},	0,			0222,
+	"mousectl",	{Qmousectl},	0,			0222,
+};
+
+Cursor	arrow = {
+	{ -1, -1 },
+	{ 0xFF, 0xFF, 0x80, 0x01, 0x80, 0x02, 0x80, 0x0C,
+	  0x80, 0x10, 0x80, 0x10, 0x80, 0x08, 0x80, 0x04,
+	  0x80, 0x02, 0x80, 0x01, 0x80, 0x02, 0x8C, 0x04,
+	  0x92, 0x08, 0x91, 0x10, 0xA0, 0xA0, 0xC0, 0x40,
+	},
+	{ 0x00, 0x00, 0x7F, 0xFE, 0x7F, 0xFC, 0x7F, 0xF0,
+	  0x7F, 0xE0, 0x7F, 0xE0, 0x7F, 0xF0, 0x7F, 0xF8,
+	  0x7F, 0xFC, 0x7F, 0xFE, 0x7F, 0xFC, 0x73, 0xF8,
+	  0x61, 0xF0, 0x60, 0xE0, 0x40, 0x40, 0x00, 0x00,
+	},
+};
+
+extern Memimage* gscreen;
+extern void mousewarpnote(Point);
+
+static void
+mousereset(void)
+{
+	curs = arrow;
+	Cursortocursor(&arrow);
+}
+
+static void
+mouseinit(void)
+{
+	curs = arrow;
+	Cursortocursor(&arrow);
+	cursoron();
+}
+
+static Chan*
+mouseattach(char *spec)
+{
+	return devattach('m', spec);
+}
+
+static Walkqid*
+mousewalk(Chan *c, Chan *nc, char **name, int nname)
+{
+	return devwalk(c, nc, name, nname, mousedir, nelem(mousedir), devgen);
+}
+
+static int
+mousestat(Chan *c, uchar *db, int n)
+{
+	return devstat(c, db, n, mousedir, nelem(mousedir), devgen);
+}
+
+static Chan*
+mouseopen(Chan *c, int omode)
+{
+	int mode;
+
+	mode = openmode(omode);
+	switch((ulong)c->qid.path){
+	case Qdir:
+		if(omode != OREAD)
+			error(Eperm);
+		break;
+	case Qmousein:
+	case Qmousectl:
+		error(Egreg);
+		break;
+	case Qmouse:
+		if(_tas(&mouse.open) != 0)
+			error(Einuse);
+		mouse.lastcounter = mouse.counter;
+		/* wet floor */
+	case Qcursor:
+		incref(&mouse);
+	}
+	c->mode = mode;
+	c->flag |= COPEN;
+	c->offset = 0;
+	return c;
+}
+
+static void
+mouseclose(Chan *c)
+{
+	if((c->qid.type&QTDIR)!=0 || (c->flag&COPEN)==0)
+		return;
+	switch((ulong)c->qid.path){
+	case Qmouse:
+		mouse.open = 0;
+		/* wet floor */
+	case Qcursor:
+		if(decref(&mouse) != 0)
+			return;
+		cursoroff();
+		curs = arrow;
+		Cursortocursor(&arrow);
+		cursoron();
+	}
+}
+
+
+static long
+mouseread(Chan *c, void *va, long n, vlong off)
+{
+	char buf[1+4*12+1];
+	uchar *p;
+	ulong offset = off;
+	Mousestate m;
+
+	p = va;
+	switch((ulong)c->qid.path){
+	case Qdir:
+		return devdirread(c, va, n, mousedir, nelem(mousedir), devgen);
+
+	case Qcursor:
+		if(offset != 0)
+			return 0;
+		if(n < 2*4+2*2*16)
+			error(Eshort);
+		n = 2*4+2*2*16;
+		BPLONG(p+0, curs.offset.x);
+		BPLONG(p+4, curs.offset.y);
+		memmove(p+8, curs.clr, 2*16);
+		memmove(p+40, curs.set, 2*16);
+		return n;
+
+	case Qmouse:
+		while(mousechanged(0) == 0)
+			rendsleep(&mouse.r, mousechanged, 0);
+
+		lock(&mouse);
+		if(mouse.ri != mouse.wi)
+			m = mouse.queue[mouse.ri++ % nelem(mouse.queue)];
+		else
+			m = mouse.Mousestate;
+		unlock(&mouse);
+
+		sprint(buf, "m%11d %11d %11d %11ld ",
+			m.xy.x, m.xy.y, m.buttons, m.msec);
+
+		mouse.lastcounter = m.counter;
+		if(mouse.resize){
+			mouse.resize = 0;
+			buf[0] = 'r';
+		}
+
+		if(n > 1+4*12)
+			n = 1+4*12;
+		memmove(va, buf, n);
+		return n;
+	}
+	return 0;
+}
+
+static long
+mousewrite(Chan *c, void *va, long n, vlong)
+{
+	char *p;
+	Point pt;
+	char buf[64];
+
+	p = va;
+	switch((ulong)c->qid.path){
+	case Qdir:
+		error(Eisdir);
+
+	case Qcursor:
+		cursoroff();
+		if(n < 2*4+2*2*16){
+			curs = arrow;
+			Cursortocursor(&arrow);
+		}else{
+			n = 2*4+2*2*16;
+			curs.offset.x = BGLONG(p+0);
+			curs.offset.y = BGLONG(p+4);
+			memmove(curs.clr, p+8, 2*16);
+			memmove(curs.set, p+40, 2*16);
+			Cursortocursor(&curs);
+		}
+		cursoron();
+		return n;
+
+	case Qmouse:
+		if(n > sizeof buf-1)
+			n = sizeof buf -1;
+		memmove(buf, va, n);
+		buf[n] = 0;
+
+		pt.x = strtol(buf+1, &p, 0);
+		if(*p == 0)
+			error(Eshort);
+		pt.y = strtol(p, 0, 0);
+		absmousetrack(pt.x, pt.y, mouse.buttons, nsec()/(1000*1000LL));
+		mousewarpnote(pt);
+		return n;
+	}
+
+	error(Egreg);
+	return -1;
+}
+
+Dev mousedevtab = {
+	'm',
+	"mouse",
+
+	mousereset,
+	mouseinit,
+	mouseattach,
+	mousewalk,
+	mousestat,
+	mouseopen,
+	devcreate,
+	mouseclose,
+	mouseread,
+	devbread,
+	mousewrite,
+	devbwrite,
+	devremove,
+	devwstat,
+};
+
+void
+Cursortocursor(Cursor *c)
+{
+	lock(&cursor);
+	memmove(&cursor.Cursor, c, sizeof(Cursor));
+	setcursor(c);
+	unlock(&cursor);
+}
+
+void
+absmousetrack(int x, int y, int b, ulong msec)
+{
+	int lastb;
+
+	if(gscreen==nil)
+		return;
+
+	if(x < gscreen->clipr.min.x)
+		x = gscreen->clipr.min.x;
+	if(x >= gscreen->clipr.max.x)
+		x = gscreen->clipr.max.x-1;
+	if(y < gscreen->clipr.min.y)
+		y = gscreen->clipr.min.y;
+	if(y >= gscreen->clipr.max.y)
+		y = gscreen->clipr.max.y-1;
+
+
+	lock(&mouse);
+	mouse.xy = Pt(x, y);
+	lastb = mouse.buttons;
+	mouse.buttons = b;
+	mouse.msec = msec;
+	mouse.counter++;
+
+	/*
+	 * if the queue fills, don't queue any more events until a
+	 * reader polls the mouse.
+	 */
+	if(b != lastb && (mouse.wi-mouse.ri) < nelem(mouse.queue))
+		mouse.queue[mouse.wi++ % nelem(mouse.queue)] = mouse.Mousestate;
+	unlock(&mouse);
+
+	rendwakeup(&mouse.r);
+
+	cursoron();
+}
+
+int
+mousechanged(void*)
+{
+	return mouse.lastcounter != mouse.counter || mouse.resize != 0;
+}
+
+Point
+mousexy(void)
+{
+	return mouse.xy;
+}
+
+/*
+ * notify reader that screen has been resized
+ */
+void
+mouseresize(void)
+{
+	mouse.resize = 1;
+	rendwakeup(&mouse.r);
+}
--- /dev/null
+++ b/sys/src/cmd/vnc/draw.c
@@ -1,0 +1,428 @@
+#include "vnc.h"
+#include "vncv.h"
+
+static struct {
+	char	*name;
+	int	num;
+} enctab[] = {
+	"copyrect",	EncCopyRect,
+	"corre",	EncCorre,
+	"hextile",	EncHextile,
+	"raw",		EncRaw,
+	"rre",		EncRre,
+	"mousewarp",	EncMouseWarp,
+	"desktopsize",	EncDesktopSize,
+	"xdesktopsize",	EncXDesktopSize,
+};
+
+static	uchar	*pixbuf;
+static	uchar	*linebuf;
+static	int	vpixb;
+static	int	pixb;
+static	void	(*pixcp)(uchar*, uchar*);
+
+static void
+vncsetdim(Vnc *v, Rectangle dim)
+{
+	v->dim = rectsubpt(dim, dim.min);
+	linebuf = realloc(linebuf, v->dim.max.x * vpixb);
+	pixbuf = realloc(pixbuf, v->dim.max.x * pixb * v->dim.max.y);
+	if(linebuf == nil || pixbuf == nil)
+		sysfatal("can't allocate pix decompression storage");
+	lockdisplay(display);
+	adjustwin(v, 0);
+	unlockdisplay(display);
+}
+
+static void
+vncrdcolor(Vnc *v, uchar *color)
+{
+	vncrdbytes(v, color, vpixb);
+
+	if(cvtpixels)
+		(*cvtpixels)(color, color, 1);
+}
+
+void
+sendencodings(Vnc *v)
+{
+	char *f[16];
+	int enc[16], nenc, i, j, nf;
+
+	nf = tokenize(encodings, f, nelem(f));
+	nenc = 0;
+	for(i=0; i<nf; i++){
+		for(j=0; j<nelem(enctab); j++)
+			if(strcmp(f[i], enctab[j].name) == 0)
+				break;
+		if(j == nelem(enctab)){
+			print("warning: unknown encoding %s\n", f[i]);
+			continue;
+		}
+		enc[nenc++] = enctab[j].num;
+	}
+
+	vnclock(v);
+	vncwrchar(v, MSetEnc);
+	vncwrchar(v, 0);
+	vncwrshort(v, nenc);
+	for(i=0; i<nenc; i++)
+		vncwrlong(v, enc[i]);
+	vncflush(v);
+	vncunlock(v);
+}
+
+void
+requestupdate(Vnc *v, int incremental)
+{
+	Rectangle r;
+
+	lockdisplay(display);
+	flushimage(display, 1);
+	r = rectsubpt(screen->r, screen->r.min);
+	unlockdisplay(display);
+	vnclock(v);
+	if(incremental == 0 && (v->canresize&2)!=0 && !eqrect(r, v->dim)){
+		vncwrchar(v, MSetDesktopSize);
+		vncwrchar(v, 0);
+		vncwrpoint(v, r.max);
+		vncwrchar(v, 1);
+		vncwrchar(v, 0);
+		vncwrlong(v, v->screen[0].id);
+		vncwrrect(v, r);
+		vncwrlong(v, v->screen[0].flags);
+	} else 
+		rectclip(&r, v->dim);
+	vncwrchar(v, MFrameReq);
+	vncwrchar(v, incremental);
+	vncwrrect(v, r);
+	vncflush(v);
+	vncunlock(v);
+}
+
+static Rectangle
+clippixbuf(Rectangle r, int maxx, int maxy)
+{
+	int y, h, stride1, stride2;
+
+	if(r.min.x > maxx || r.min.y > maxy){
+		r.max.x = 0;
+		return r;
+	}
+	if(r.max.y > maxy)
+		r.max.y = maxy;
+	if(r.max.x <= maxx)
+		return r;
+
+	stride2 = Dx(r) * pixb;
+	r.max.x = maxx;
+	stride1 = Dx(r) * pixb;
+	h = Dy(r);
+	for(y = 0; y < h; y++)
+		memmove(&pixbuf[y * stride1], &pixbuf[y * stride2], stride1);
+
+	return r;
+}
+
+/* must be called with display locked */
+static void
+updatescreen(Rectangle r)
+{
+	int b, bb;
+
+	lockdisplay(display);
+	if(r.max.x > Dx(screen->r) || r.max.y > Dy(screen->r)){
+		r = clippixbuf(r, Dx(screen->r), Dy(screen->r));
+		if(r.max.x == 0){
+			unlockdisplay(display);
+			return;
+		}
+	}
+
+	/*
+	 * assume load image fails only because of resize
+	 */
+	b = Dx(r) * pixb * Dy(r);
+	bb = loadimage(screen, rectaddpt(r, screen->r.min), pixbuf, b);
+	if(bb != b && verbose)
+		fprint(2, "loadimage %d on %R for %R returned %d: %r\n", b, rectaddpt(r, screen->r.min), screen->r, bb);
+	unlockdisplay(display);
+}
+
+static void
+fillrect(Rectangle r, int stride, uchar *color)
+{
+	int x, xe, y, off;
+
+	y = r.min.y;
+	off = y * stride;
+	for(; y < r.max.y; y++){
+		xe = off + r.max.x * pixb;
+		for(x = off + r.min.x * pixb; x < xe; x += pixb)
+			(*pixcp)(&pixbuf[x], color);
+		off += stride;
+	}
+}
+
+static void
+loadbuf(Vnc *v, Rectangle r, int stride)
+{
+	int off, y;
+
+	if(cvtpixels){
+		y = r.min.y;
+		off = y * stride + r.min.x * pixb;
+		for(; y < r.max.y; y++){
+			vncrdbytes(v, linebuf, Dx(r) * vpixb);
+			(*cvtpixels)(&pixbuf[off], linebuf, Dx(r));
+			off += stride;
+		}
+	}else{
+		y = r.min.y;
+		off = y * stride + r.min.x * pixb;
+		for(; y < r.max.y; y++){
+			vncrdbytes(v, &pixbuf[off], Dx(r) * pixb);
+			off += stride;
+		}
+	}
+}
+
+static Rectangle
+hexrect(ushort u)
+{
+	int x, y, w, h;
+
+	x = u>>12;
+	y = (u>>8)&15;
+	w = ((u>>4)&15)+1;
+	h = (u&15)+1;
+
+	return Rect(x, y, x+w, y+h);
+}
+
+
+static void
+dohextile(Vnc *v, Rectangle r, int stride)
+{
+	ulong bg, fg, c;
+	int enc, nsub, sx, sy, w, h, th, tw;
+	Rectangle sr, ssr;
+
+	fg = bg = 0;
+	h = Dy(r);
+	w = Dx(r);
+	for(sy = 0; sy < h; sy += HextileDim){
+		th = h - sy;
+		if(th > HextileDim)
+			th = HextileDim;
+		for(sx = 0; sx < w; sx += HextileDim){
+			tw = w - sx;
+			if(tw > HextileDim)
+				tw = HextileDim;
+
+			sr = Rect(sx, sy, sx + tw, sy + th);
+			enc = vncrdchar(v);
+			if(enc & HextileRaw){
+				loadbuf(v, sr, stride);
+				continue;
+			}
+
+			if(enc & HextileBack)
+				vncrdcolor(v, (uchar*)&bg);
+			fillrect(sr, stride, (uchar*)&bg);
+
+			if(enc & HextileFore)
+				vncrdcolor(v, (uchar*)&fg);
+
+			if(enc & HextileRects){
+				nsub = vncrdchar(v);
+				(*pixcp)((uchar*)&c, (uchar*)&fg);
+				while(nsub-- > 0){
+					if(enc & HextileCols)
+						vncrdcolor(v, (uchar*)&c);
+					ssr = rectaddpt(hexrect(vncrdshort(v)), sr.min);
+					fillrect(ssr, stride, (uchar*)&c);
+				}
+			}
+		}
+	}
+}
+
+static void
+dorectangle(Vnc *v)
+{
+	ulong type;
+	long n, stride;
+	ulong color;
+	Point p;
+	Rectangle r, subr, maxr;
+
+	r = vncrdrect(v);
+	type = vncrdlong(v);
+	switch(type){
+	case EncMouseWarp:
+		mousewarp(r.min);
+		return;
+	case EncDesktopSize:
+		v->canresize |= 1;
+		vncsetdim(v, r);
+		return;
+	case EncXDesktopSize:
+		v->canresize |= 2;
+		n = vncrdlong(v)>>24;
+		if(n <= 0)
+			break;
+		v->screen[0].id = vncrdlong(v);
+		v->screen[0].rect = vncrdrect(v);
+		v->screen[0].flags = vncrdlong(v);
+		while(--n > 0){
+			vncrdlong(v);
+			vncrdrect(v);
+			vncrdlong(v);
+		}
+		vncsetdim(v, v->screen[0].rect);
+		return;
+	}
+
+	if(!rectinrect(r, v->dim))
+		sysfatal("bad rectangle from server: %R not in %R", r, v->dim);
+	maxr = rectsubpt(r, r.min);
+	stride = maxr.max.x * pixb;
+
+	switch(type){
+	default:
+		sysfatal("bad rectangle encoding from server");
+		break;
+	case EncRaw:
+		loadbuf(v, maxr, stride);
+		updatescreen(r);
+		break;
+
+	case EncCopyRect:
+		p = vncrdpoint(v);
+		lockdisplay(display);
+		p = addpt(p, screen->r.min);
+		r = rectaddpt(r, screen->r.min);
+		draw(screen, r, screen, nil, p);
+		unlockdisplay(display);
+		break;
+
+	case EncRre:
+	case EncCorre:
+		n = vncrdlong(v);
+		vncrdcolor(v, (uchar*)&color);
+		fillrect(maxr, stride, (uchar*)&color);
+		while(n-- > 0){
+			vncrdcolor(v, (uchar*)&color);
+			if(type == EncRre)
+				subr = vncrdrect(v);
+			else
+				subr = vncrdcorect(v);
+			if(!rectinrect(subr, maxr))
+				sysfatal("bad encoding from server");
+			fillrect(subr, stride, (uchar*)&color);
+		}
+		updatescreen(r);
+		break;
+
+	case EncHextile:
+		dohextile(v, r, stride);
+		updatescreen(r);
+		break;
+	}
+}
+
+static void
+pixcp8(uchar *dst, uchar *src)
+{
+	*dst = *src;
+}
+
+static void
+pixcp16(uchar *dst, uchar *src)
+{
+	*(ushort*)dst = *(ushort*)src;
+}
+
+static void
+pixcp32(uchar *dst, uchar *src)
+{
+	*(ulong*)dst = *(ulong*)src;
+}
+
+static void
+pixcp24(uchar *dst, uchar *src)
+{
+	dst[0] = src[0];
+	dst[1] = src[1];
+	dst[2] = src[2];
+}
+
+static int
+calcpixb(int bpp)
+{
+	if(bpp / 8 * 8 != bpp)
+		sysfatal("can't handle your screen");
+	return bpp / 8;
+}
+
+void
+readfromserver(Vnc *v)
+{
+	uchar type;
+	uchar junk[100];
+	long n;
+
+	vpixb = calcpixb(v->bpp);
+	pixb = calcpixb(screen->depth);
+	switch(pixb){
+	case 1:
+		pixcp = pixcp8;
+		break;
+	case 2:
+		pixcp = pixcp16;
+		break;
+	case 3:
+		pixcp = pixcp24;
+		break;
+	case 4:
+		pixcp = pixcp32;
+		break;
+	default:
+		sysfatal("can't handle your screen: bad depth %d", pixb);
+	}
+	vncsetdim(v, v->dim);
+	for(;;){
+		type = vncrdchar(v);
+		switch(type){
+		default:
+			sysfatal("bad message from server: %x", type);
+			break;
+		case MFrameUpdate:
+			vncrdchar(v);
+			n = vncrdshort(v);
+			while(n-- > 0)
+				dorectangle(v);
+			requestupdate(v, 1);
+			break;
+
+		case MSetCmap:
+			vncrdbytes(v, junk, 3);
+			n = vncrdshort(v);
+			vncgobble(v, n*3*2);
+			break;
+
+		case MBell:
+			break;
+
+		case MSAck:
+			break;
+
+		case MSCut:
+			vncrdbytes(v, junk, 3);
+			n = vncrdlong(v);
+			writesnarf(v, n);
+			break;
+		}
+	}
+}
--- /dev/null
+++ b/sys/src/cmd/vnc/error.h
@@ -1,0 +1,48 @@
+extern char Enoerror[];		/* no error */
+extern char Emount[];		/* inconsistent mount */
+extern char Eunmount[];		/* not mounted */
+extern char Eunion[];		/* not in union */
+extern char Emountrpc[];	/* mount rpc error */
+extern char Eshutdown[];	/* mounted device shut down */
+extern char Enocreate[];	/* mounted directory forbids creation */
+extern char Enonexist[];	/* file does not exist */
+extern char Eexist[];		/* file already exists */
+extern char Ebadsharp[];	/* unknown device in # filename */
+extern char Enotdir[];		/* not a directory */
+extern char Eisdir[];		/* file is a directory */
+extern char Ebadchar[];		/* bad character in file name */
+extern char Efilename[];	/* file name syntax */
+extern char Eperm[];		/* permission denied */
+extern char Ebadusefd[];	/* inappropriate use of fd */
+extern char Ebadarg[];		/* bad arg in system call */
+extern char Einuse[];		/* device or object already in use */
+extern char Eio[];		/* i/o error */
+extern char Etoobig[];		/* read or write too large */
+extern char Etoosmall[];	/* read or write too small */
+extern char Enoport[];		/* network port not available */
+extern char Ehungup[];		/* write to hungup channel */
+extern char Ebadctl[];		/* bad process or channel control request */
+extern char Enodev[];		/* no free devices */
+extern char Eprocdied[];	/* process exited */
+extern char Enochild[];		/* no living children */
+extern char Eioload[];		/* i/o error in demand load */
+extern char Enovmem[];		/* virtual memory allocation failed */
+extern char Ebadfd[];		/* fd out of range or not open */
+extern char Enofd[];			/* no free file descriptors */
+extern char Eisstream[];	/* seek on a stream */
+extern char Ebadexec[];		/* exec header invalid */
+extern char Etimedout[];	/* connection timed out */
+extern char Econrefused[];	/* connection refused */
+extern char Econinuse[];	/* connection in use */
+extern char Eintr[];		/* interrupted */
+extern char Enomem[];		/* kernel allocate failed */
+extern char Enoswap[];		/* swap space full */
+extern char Esoverlap[];	/* segments overlap */
+extern char Emouseset[];	/* mouse type already set */
+extern char Eshort[];		/* i/o count too small */
+extern char Egreg[];		/* ken has left the building */
+extern char Ebadspec[];		/* bad attach specifier */
+extern char Enoreg[];		/* process has no saved registers */
+extern char Enoattach[];	/* mount/attach disallowed */
+extern char Eshortstat[];	/* stat buffer too small */
+extern char Ebadstat[];		/* malformed stat buffer */
--- /dev/null
+++ b/sys/src/cmd/vnc/errstr.h
@@ -1,0 +1,48 @@
+char Enoerror[] = "no error";
+char Emount[] = "inconsistent mount";
+char Eunmount[] = "not mounted";
+char Eunion[] = "not in union";
+char Emountrpc[] = "mount rpc error";
+char Eshutdown[] = "mounted device shut down";
+char Enocreate[] = "mounted directory forbids creation";
+char Enonexist[] = "file does not exist";
+char Eexist[] = "file already exists";
+char Ebadsharp[] = "unknown device in # filename";
+char Enotdir[] = "not a directory";
+char Eisdir[] = "file is a directory";
+char Ebadchar[] = "bad character in file name";
+char Efilename[] = "file name syntax";
+char Eperm[] = "permission denied";
+char Ebadusefd[] = "inappropriate use of fd";
+char Ebadarg[] = "bad arg in system call";
+char Einuse[] = "device or object already in use";
+char Eio[] = "i/o error";
+char Etoobig[] = "read or write too large";
+char Etoosmall[] = "read or write too small";
+char Enoport[] = "network port not available";
+char Ehungup[] = "write to hungup channel";
+char Ebadctl[] = "bad process or channel control request";
+char Enodev[] = "no free devices";
+char Eprocdied[] = "process exited";
+char Enochild[] = "no living children";
+char Eioload[] = "i/o error in demand load";
+char Enovmem[] = "virtual memory allocation failed";
+char Ebadfd[] = "fd out of range or not open";
+char Enofd[] = "no free file descriptors";
+char Eisstream[] = "seek on a stream";
+char Ebadexec[] = "exec header invalid";
+char Etimedout[] = "connection timed out";
+char Econrefused[] = "connection refused";
+char Econinuse[] = "connection in use";
+char Eintr[] = "interrupted";
+char Enomem[] = "kernel allocate failed";
+char Enoswap[] = "swap space full";
+char Esoverlap[] = "segments overlap";
+char Emouseset[] = "mouse type already set";
+char Eshort[] = "i/o count too small";
+char Egreg[] = "ken has left the building";
+char Ebadspec[] = "bad attach specifier";
+char Enoreg[] = "process has no saved registers";
+char Enoattach[] = "mount/attach disallowed";
+char Eshortstat[] = "stat buffer too small";
+char Ebadstat[] = "malformed stat buffer";
--- /dev/null
+++ b/sys/src/cmd/vnc/exporter.c
@@ -1,0 +1,94 @@
+#include <u.h>
+#include <libc.h>
+#include "compat.h"
+
+typedef struct Exporter	Exporter;
+struct Exporter
+{
+	int	fd;
+	Chan	**roots;
+	int	nroots;
+};
+
+int
+mounter(char *mntpt, int how, int fd, int n)
+{
+	char buf[32];
+	int i, ok, mfd;
+
+	ok = 1;
+	for(i = 0; i < n; i++){
+		snprint(buf, sizeof buf, "%d", i);
+		mfd = dup(fd, -1);
+		if(mount(mfd, -1, mntpt, how, buf) == -1){
+			close(mfd);
+			fprint(2, "can't mount on %s: %r\n", mntpt);
+			ok = 0;
+			break;
+		}
+		close(mfd);
+		if(how == MREPL)
+			how = MAFTER;
+	}
+
+	close(fd);
+
+	return ok;
+}
+
+static void
+extramp(void *v)
+{
+	Exporter *ex;
+
+	rfork(RFNAMEG);
+	ex = v;
+	sysexport(ex->fd, ex->roots, ex->nroots);
+	shutdown();
+	exits(nil);
+}
+
+int
+exporter(Dev **dt, int *fd, int *sfd)
+{
+	Chan **roots;
+	Exporter ex;
+	int p[2], i, n, ed;
+
+	for(n = 0; dt[n] != nil; n++)
+		;
+	if(!n){
+		werrstr("no devices specified");
+		return 0;
+	}
+
+	ed = errdepth(-1);
+	if(waserror()){
+		werrstr(up->error);
+		return 0;
+	}
+
+	roots = smalloc(n * sizeof *roots);
+	for(i = 0; i < n; i++){
+		(*dt[i]->reset)();
+		(*dt[i]->init)();
+		roots[i] = (*dt[i]->attach)("");
+	}
+	poperror();
+	errdepth(ed);
+
+	if(pipe(p) < 0){
+		werrstr("can't make pipe: %r");
+		return 0;
+	}
+
+	*sfd = p[0];
+	*fd = p[1];
+
+	ex.fd = *sfd;
+	ex.roots = roots;
+	ex.nroots = n;
+	kproc("exporter", extramp, &ex);
+
+	return n;
+}
--- /dev/null
+++ b/sys/src/cmd/vnc/exportfs.c
@@ -1,0 +1,804 @@
+#include	<u.h>
+#include	<libc.h>
+#include	<fcall.h>
+#include	"compat.h"
+#include	"error.h"
+
+typedef	struct Fid	Fid;
+typedef	struct Export	Export;
+typedef	struct Exq	Exq;
+typedef	struct Exwork	Exwork;
+
+enum
+{
+	Nfidhash	= 32,
+	Maxfdata	= 8192,
+	Maxrpc		= IOHDRSZ + Maxfdata,
+};
+
+struct Export
+{
+	Ref	r;
+	Exq*	work;
+	Lock	fidlock;
+	Fid*	fid[Nfidhash];
+	int	io;		/* fd to read/write */
+	int	iounit;
+	int	nroots;
+	Chan	**roots;
+};
+
+struct Fid
+{
+	Fid*	next;
+	Fid**	last;
+	Chan*	chan;
+	long	offset;
+	int	fid;
+	int	ref;		/* fcalls using the fid; locked by Export.Lock */
+	int	attached;	/* fid attached or cloned but not clunked */
+};
+
+struct Exq
+{
+	Lock	lk;
+	int	responding;	/* writing out reply message */
+	int	noresponse;	/* don't respond to this one */
+	Exq*	next;
+	int	shut;		/* has been noted for shutdown */
+	Export*	export;
+	void*	slave;
+	Fcall	rpc;
+	uchar	buf[Maxrpc];
+};
+
+struct Exwork
+{
+	Lock	l;
+
+	int	ref;
+
+	int	nwaiters;	/* queue of slaves waiting for work */
+	QLock	qwait;
+	Rendez	rwait;
+
+	Exq	*head;		/* work waiting for a slave */
+	Exq	*tail;
+};
+
+Exwork exq;
+
+static void	exshutdown(Export*);
+static void	exflush(Export*, int, int);
+static void	exslave(void*);
+static void	exfree(Export*);
+static void	exportproc(Export*);
+
+static char*	Exattach(Export*, Fcall*, uchar*);
+static char*	Exauth(Export*, Fcall*, uchar*);
+static char*	Exclunk(Export*, Fcall*, uchar*);
+static char*	Excreate(Export*, Fcall*, uchar*);
+static char*	Exversion(Export*, Fcall*, uchar*);
+static char*	Exopen(Export*, Fcall*, uchar*);
+static char*	Exread(Export*, Fcall*, uchar*);
+static char*	Exremove(Export*, Fcall*, uchar*);
+static char*	Exsession(Export*, Fcall*, uchar*);
+static char*	Exstat(Export*, Fcall*, uchar*);
+static char*	Exwalk(Export*, Fcall*, uchar*);
+static char*	Exwrite(Export*, Fcall*, uchar*);
+static char*	Exwstat(Export*, Fcall*, uchar*);
+
+static char	*(*fcalls[Tmax])(Export*, Fcall*, uchar*);
+
+static char	Enofid[]   = "no such fid";
+static char	Eseekdir[] = "can't seek on a directory";
+static char	Ereaddir[] = "unaligned read of a directory";
+static int	exdebug = 0;
+
+int
+sysexport(int fd, Chan **roots, int nroots)
+{
+	Export *fs;
+
+	fs = smalloc(sizeof(Export));
+	fs->r.ref = 1;
+	fs->io = fd;
+	fs->roots = roots;
+	fs->nroots = nroots;
+
+	exportproc(fs);
+
+	return 0;
+}
+
+static void
+exportinit(void)
+{
+	lock(&exq.l);
+	exq.ref++;
+	if(fcalls[Tversion] != nil){
+		unlock(&exq.l);
+		return;
+	}
+
+	fmtinstall('F', fcallfmt);
+	fcalls[Tversion] = Exversion;
+	fcalls[Tauth] = Exauth;
+	fcalls[Tattach] = Exattach;
+	fcalls[Twalk] = Exwalk;
+	fcalls[Topen] = Exopen;
+	fcalls[Tcreate] = Excreate;
+	fcalls[Tread] = Exread;
+	fcalls[Twrite] = Exwrite;
+	fcalls[Tclunk] = Exclunk;
+	fcalls[Tremove] = Exremove;
+	fcalls[Tstat] = Exstat;
+	fcalls[Twstat] = Exwstat;
+	unlock(&exq.l);
+}
+
+static void
+exportproc(Export *fs)
+{
+	Exq *q;
+	int n, ed;
+
+	exportinit();
+	ed = errdepth(-1);
+	for(;;){
+		errdepth(ed);
+		q = smalloc(sizeof(Exq));
+
+		n = read9pmsg(fs->io, q->buf, Maxrpc);
+		if(n <= 0 || convM2S(q->buf, n, &q->rpc) != n)
+			break;
+
+		if(exdebug)
+			print("export %d <- %F\n", getpid(), &q->rpc);
+
+		if(q->rpc.type == Tflush){
+			exflush(fs, q->rpc.tag, q->rpc.oldtag);
+			free(q);
+			continue;
+		}
+
+		q->export = fs;
+		incref(&fs->r);
+
+		lock(&exq.l);
+		if(exq.head == nil)
+			exq.head = q;
+		else
+			exq.tail->next = q;
+		q->next = nil;
+		exq.tail = q;
+		n = exq.nwaiters;
+		if(n)
+			exq.nwaiters = n - 1;
+		unlock(&exq.l);
+		if(!n)
+			kproc("exportfs", exslave, nil);
+		rendwakeup(&exq.rwait);
+	}
+	free(q);
+	if(exdebug)
+		fprint(2, "export proc shutting down: %r\n");
+	exshutdown(fs);
+	exfree(fs);
+}
+
+static void
+exflush(Export *fs, int flushtag, int tag)
+{
+	Exq *q, **last;
+	Fcall fc;
+	uchar buf[Maxrpc];
+	int n;
+
+	/* hasn't been started? */
+	lock(&exq.l);
+	last = &exq.head;
+	for(q = exq.head; q != nil; q = q->next){
+		if(q->export == fs && q->rpc.tag == tag){
+			*last = q->next;
+			unlock(&exq.l);
+			exfree(fs);
+			free(q);
+			goto Respond;
+		}
+		last = &q->next;
+	}
+	unlock(&exq.l);
+
+	/* in progress? */
+	lock(&fs->r);
+	for(q = fs->work; q != nil; q = q->next){
+		if(q->rpc.tag == tag){
+			lock(&q->lk);
+			q->noresponse = 1;
+			if(!q->responding)
+				rendintr(q->slave);
+			unlock(&q->lk);
+			break;
+		}
+	}
+	unlock(&fs->r);
+
+Respond:
+	fc.type = Rflush;
+	fc.tag = flushtag;
+
+	n = convS2M(&fc, buf, Maxrpc);
+	if(n == 0)
+		panic("convS2M error on write");
+	if(write(fs->io, buf, n) != n)
+		panic("mount write");
+}
+
+static void
+exshutdown(Export *fs)
+{
+	Exq *q, **last;
+
+	lock(&exq.l);
+	last = &exq.head;
+	for(q = exq.head; q != nil; q = *last){
+		if(q->export == fs){
+			*last = q->next;
+			exfree(fs);
+			free(q);
+			continue;
+		}
+		last = &q->next;
+	}
+
+	/*
+	 * cleanly shut down the slaves if this is the last fs around
+	 */
+	exq.ref--;
+	if(!exq.ref)
+		rendwakeup(&exq.rwait);
+	unlock(&exq.l);
+
+	/*
+	 * kick any sleepers
+	 */
+	lock(&fs->r);
+	for(q = fs->work; q != nil; q = q->next){
+		lock(&q->lk);
+		q->noresponse = 1;
+		if(!q->responding)
+			rendintr(q->slave);
+		unlock(&q->lk);
+	}
+	unlock(&fs->r);
+}
+
+static void
+exfree(Export *fs)
+{
+	Fid *f, *n;
+	int i;
+
+	if(decref(&fs->r) != 0)
+		return;
+	for(i = 0; i < Nfidhash; i++){
+		for(f = fs->fid[i]; f != nil; f = n){
+			if(f->chan != nil)
+				cclose(f->chan);
+			n = f->next;
+			free(f);
+		}
+	}
+	free(fs);
+}
+
+static int
+exwork(void *)
+{
+	int work;
+
+	lock(&exq.l);
+	work = exq.head != nil || !exq.ref;
+	unlock(&exq.l);
+	return work;
+}
+
+static void
+exslave(void *)
+{
+	Export *fs;
+	Exq *q, *t, **last;
+	char *volatile err;
+	int n, ed;
+
+	while(waserror())
+		fprint(2, "exslave %d errored out of loop -- heading back in!\n", getpid());
+	ed = errdepth(-1);
+	for(;;){
+		errdepth(ed);
+		qlock(&exq.qwait);
+		if(waserror()){
+			qunlock(&exq.qwait);
+			nexterror();
+		}
+		rendsleep(&exq.rwait, exwork, nil);
+
+		lock(&exq.l);
+		if(!exq.ref){
+			unlock(&exq.l);
+			poperror();
+			qunlock(&exq.qwait);
+			break;
+		}
+		q = exq.head;
+		if(q == nil){
+			unlock(&exq.l);
+			poperror();
+			qunlock(&exq.qwait);
+			continue;
+		}
+		exq.head = q->next;
+		if(exq.head == nil)
+			exq.tail = nil;
+		poperror();
+		qunlock(&exq.qwait);
+
+		/*
+		 * put the job on the work queue before it's
+		 * visible as off of the head queue, so it's always
+		 * findable for flushes and shutdown
+		 */
+		q->slave = up;
+		q->noresponse = 0;
+		q->responding = 0;
+		rendclearintr();
+		fs = q->export;
+		lock(&fs->r);
+		q->next = fs->work;
+		fs->work = q;
+		unlock(&fs->r);
+
+		unlock(&exq.l);
+
+		if(exdebug > 1)
+			print("exslave dispatch %d %F\n", getpid(), &q->rpc);
+
+		if(waserror()){
+			print("exslave err %r\n");
+			err = up->error;
+		}else{
+			if(q->rpc.type >= Tmax || !fcalls[q->rpc.type])
+				err = "bad fcall type";
+			else
+				err = (*fcalls[q->rpc.type])(fs, &q->rpc, &q->buf[IOHDRSZ]);
+			poperror();
+		}
+
+		q->rpc.type++;
+		if(err){
+			q->rpc.type = Rerror;
+			q->rpc.ename = err;
+		}
+		n = convS2M(&q->rpc, q->buf, Maxrpc);
+
+		if(exdebug)
+			print("exslave %d -> %F\n", getpid(), &q->rpc);
+
+		lock(&q->lk);
+		if(!q->noresponse){
+			q->responding = 1;
+			unlock(&q->lk);
+			write(fs->io, q->buf, n);
+		}else
+			unlock(&q->lk);
+
+		/*
+		 * exflush might set noresponse at this point, but
+		 * setting noresponse means don't send a response now;
+		 * it's okay that we sent a response already.
+		 */
+		if(exdebug > 1)
+			print("exslave %d written %d\n", getpid(), q->rpc.tag);
+
+		lock(&fs->r);
+		last = &fs->work;
+		for(t = fs->work; t != nil; t = t->next){
+			if(t == q){
+				*last = q->next;
+				break;
+			}
+			last = &t->next;
+		}
+		unlock(&fs->r);
+
+		exfree(q->export);
+		free(q);
+
+		rendclearintr();
+		lock(&exq.l);
+		exq.nwaiters++;
+		unlock(&exq.l);
+	}
+	if(exdebug)
+		fprint(2, "export slaveshutting down\n");
+	kexit();
+}
+
+Fid*
+Exmkfid(Export *fs, int fid)
+{
+	ulong h;
+	Fid *f, *nf;
+
+	nf = mallocz(sizeof(Fid), 1);
+	if(nf == nil)
+		return nil;
+	lock(&fs->fidlock);
+	h = fid % Nfidhash;
+	for(f = fs->fid[h]; f != nil; f = f->next){
+		if(f->fid == fid){
+			unlock(&fs->fidlock);
+			free(nf);
+			return nil;
+		}
+	}
+
+	nf->next = fs->fid[h];
+	if(nf->next != nil)
+		nf->next->last = &nf->next;
+	nf->last = &fs->fid[h];
+	fs->fid[h] = nf;
+
+	nf->fid = fid;
+	nf->ref = 1;
+	nf->attached = 1;
+	nf->offset = 0;
+	nf->chan = nil;
+	unlock(&fs->fidlock);
+	return nf;
+}
+
+Fid*
+Exgetfid(Export *fs, int fid)
+{
+	Fid *f;
+	ulong h;
+
+	lock(&fs->fidlock);
+	h = fid % Nfidhash;
+	for(f = fs->fid[h]; f; f = f->next){
+		if(f->fid == fid){
+			if(f->attached == 0)
+				break;
+			f->ref++;
+			unlock(&fs->fidlock);
+			return f;
+		}
+	}
+	unlock(&fs->fidlock);
+	return nil;
+}
+
+void
+Exputfid(Export *fs, Fid *f)
+{
+	lock(&fs->fidlock);
+	f->ref--;
+	if(f->ref == 0 && f->attached == 0){
+		if(f->chan != nil)
+			cclose(f->chan);
+		f->chan = nil;
+		*f->last = f->next;
+		if(f->next != nil)
+			f->next->last = f->last;
+		unlock(&fs->fidlock);
+		free(f);
+		return;
+	}
+	unlock(&fs->fidlock);
+}
+
+static char*
+Exversion(Export *fs, Fcall *rpc, uchar *)
+{
+	if(rpc->msize < 256)
+		return "version: message size too small";
+	if(rpc->msize > Maxrpc)
+		rpc->msize = Maxrpc;
+	if(strncmp(rpc->version, "9P", 2) != 0){
+		rpc->version = "unknown";
+		return nil;
+	}
+
+	fs->iounit = rpc->msize - IOHDRSZ;
+	rpc->version = "9P2000";
+	return nil;
+}
+
+static char*
+Exauth(Export *, Fcall *, uchar *)
+{
+	return "vnc: authentication not required";
+}
+
+static char*
+Exattach(Export *fs, Fcall *rpc, uchar *)
+{
+	Fid *f;
+	int w;
+
+	w = 0;
+	if(rpc->aname != nil)
+		w = strtol(rpc->aname, nil, 10);
+	if(w < 0 || w > fs->nroots)
+		error(Ebadspec);
+	f = Exmkfid(fs, rpc->fid);
+	if(f == nil)
+		return Einuse;
+	if(waserror()){
+		f->attached = 0;
+		Exputfid(fs, f);
+		return up->error;
+	}
+	f->chan = cclone(fs->roots[w]);
+	poperror();
+	rpc->qid = f->chan->qid;
+	Exputfid(fs, f);
+	return nil;
+}
+
+static char*
+Exclunk(Export *fs, Fcall *rpc, uchar *)
+{
+	Fid *f;
+
+	f = Exgetfid(fs, rpc->fid);
+	if(f != nil){
+		f->attached = 0;
+		Exputfid(fs, f);
+	}
+	return nil;
+}
+
+static char*
+Exwalk(Export *fs, Fcall *rpc, uchar *)
+{
+	Fid *volatile f, *volatile nf;
+	Walkqid *wq;
+	Chan *c;
+	int i, nwname;
+	int volatile isnew;
+
+	f = Exgetfid(fs, rpc->fid);
+	if(f == nil)
+		return Enofid;
+	nf = nil;
+	if(waserror()){
+		Exputfid(fs, f);
+		if(nf != nil)
+			Exputfid(fs, nf);
+		return up->error;
+	}
+
+	/*
+	 * optional clone, but don't attach it until the walk succeeds.
+	 */
+	if(rpc->fid != rpc->newfid){
+		nf = Exmkfid(fs, rpc->newfid);
+		if(nf == nil)
+			error(Einuse);
+		nf->attached = 0;
+		isnew = 1;
+	}else{
+		nf = Exgetfid(fs, rpc->fid);
+		isnew = 0;
+	}
+
+	/*
+	 * let the device do the work
+	 */
+	c = f->chan;
+	nwname = rpc->nwname;
+	wq = (*devtab[c->type]->walk)(c, nf->chan, rpc->wname, nwname);
+	if(wq == nil)
+		error(Enonexist);
+
+	poperror();
+
+	/*
+	 * copy qid array
+	 */
+	for(i = 0; i < wq->nqid; i++)
+		rpc->wqid[i] = wq->qid[i];
+	rpc->nwqid = wq->nqid;
+
+	/*
+	 * update the channel if everything walked correctly.
+	 */
+	if(isnew && wq->nqid == nwname){
+		nf->chan = wq->clone;
+		nf->attached = 1;
+	}
+
+	free(wq);
+	Exputfid(fs, f);
+	Exputfid(fs, nf);
+	return nil;
+}
+
+static char*
+Exopen(Export *fs, Fcall *rpc, uchar *)
+{
+	Fid *volatile f;
+	Chan *c;
+	int iou;
+
+	f = Exgetfid(fs, rpc->fid);
+	if(f == nil)
+		return Enofid;
+	if(waserror()){
+		Exputfid(fs, f);
+		return up->error;
+	}
+	c = f->chan;
+	c = (*devtab[c->type]->open)(c, rpc->mode);
+	poperror();
+
+	f->chan = c;
+	f->offset = 0;
+	rpc->qid = f->chan->qid;
+	iou = f->chan->iounit;
+	if(iou > fs->iounit)
+		iou = fs->iounit;
+	rpc->iounit = iou;
+	Exputfid(fs, f);
+	return nil;
+}
+
+static char*
+Excreate(Export *fs, Fcall *rpc, uchar *)
+{
+	Fid *f;
+	Chan *c;
+	int iou;
+
+	f = Exgetfid(fs, rpc->fid);
+	if(f == nil)
+		return Enofid;
+	if(waserror()){
+		Exputfid(fs, f);
+		return up->error;
+	}
+	c = f->chan;
+	(*devtab[c->type]->create)(c, rpc->name, rpc->mode, rpc->perm);
+	poperror();
+
+	f->chan = c;
+	rpc->qid = f->chan->qid;
+	iou = f->chan->iounit;
+	if(iou > fs->iounit)
+		iou = fs->iounit;
+	rpc->iounit = iou;
+	Exputfid(fs, f);
+	return nil;
+}
+
+static char*
+Exread(Export *fs, Fcall *rpc, uchar *buf)
+{
+	Fid *f;
+	Chan *c;
+	long off;
+
+	f = Exgetfid(fs, rpc->fid);
+	if(f == nil)
+		return Enofid;
+
+	c = f->chan;
+
+	if(waserror()){
+		Exputfid(fs, f);
+		return up->error;
+	}
+
+	rpc->data = (char*)buf;
+	off = rpc->offset;
+	c->offset = off;
+	rpc->count = (*devtab[c->type]->read)(c, rpc->data, rpc->count, off);
+	poperror();
+	Exputfid(fs, f);
+	return nil;
+}
+
+static char*
+Exwrite(Export *fs, Fcall *rpc, uchar *)
+{
+	Fid *f;
+	Chan *c;
+
+	f = Exgetfid(fs, rpc->fid);
+	if(f == nil)
+		return Enofid;
+	if(waserror()){
+		Exputfid(fs, f);
+		return up->error;
+	}
+	c = f->chan;
+	if(c->qid.type & QTDIR)
+		error(Eisdir);
+	rpc->count = (*devtab[c->type]->write)(c, rpc->data, rpc->count, rpc->offset);
+	poperror();
+	Exputfid(fs, f);
+	return nil;
+}
+
+static char*
+Exstat(Export *fs, Fcall *rpc, uchar *buf)
+{
+	Fid *f;
+	Chan *c;
+
+	f = Exgetfid(fs, rpc->fid);
+	if(f == nil)
+		return Enofid;
+	if(waserror()){
+		Exputfid(fs, f);
+		return up->error;
+	}
+	c = f->chan;
+	rpc->stat = buf;
+	rpc->nstat = (*devtab[c->type]->stat)(c, rpc->stat, Maxrpc);
+	poperror();
+	Exputfid(fs, f);
+	return nil;
+}
+
+static char*
+Exwstat(Export *fs, Fcall *rpc, uchar *)
+{
+	Fid *f;
+	Chan *c;
+
+	f = Exgetfid(fs, rpc->fid);
+	if(f == nil)
+		return Enofid;
+	if(waserror()){
+		Exputfid(fs, f);
+		return up->error;
+	}
+	c = f->chan;
+	(*devtab[c->type]->wstat)(c, rpc->stat, rpc->nstat);
+	poperror();
+	Exputfid(fs, f);
+	return nil;
+}
+
+static char*
+Exremove(Export *fs, Fcall *rpc, uchar *)
+{
+	Fid *f;
+	Chan *c;
+
+	f = Exgetfid(fs, rpc->fid);
+	if(f == nil)
+		return Enofid;
+	if(waserror()){
+		Exputfid(fs, f);
+		return up->error;
+	}
+	c = f->chan;
+	(*devtab[c->type]->remove)(c);
+	poperror();
+
+	/*
+	 * chan is already clunked by remove.
+	 * however, we need to recover the chan,
+	 * and follow sysremove's lead in making to point to root.
+	 */
+	c->type = 0;
+
+	f->attached = 0;
+	Exputfid(fs, f);
+	return nil;
+}
--- /dev/null
+++ b/sys/src/cmd/vnc/kbd.h
@@ -1,0 +1,21 @@
+typedef struct	Snarf	Snarf;
+
+struct Snarf
+{
+	QLock;
+	int		vers;
+	int		n;
+	char		*buf;
+};
+
+enum
+{
+	MAXSNARF	= 100*1024
+};
+
+extern	Snarf		snarf;
+extern	int		kbdin;
+
+void			screenputs(char*, int);
+void			vncputc(int, int);
+void			setsnarf(char *buf, int n, int *vers);
--- /dev/null
+++ b/sys/src/cmd/vnc/kbds.c
@@ -1,0 +1,74 @@
+#include	<u.h>
+#include	<libc.h>
+#include	<keyboard.h>
+#include	"compat.h"
+#include	"kbd.h"
+#include   "ksym2utf.h"
+
+enum {
+	VKSpecial = 0xff00,
+};
+
+static Rune vnckeys[] =
+{
+[0x00]	0,	0,	0,	0,	0,	0,	0,	0,
+[0x08]	'\b',	'\t',	'\r',	0,	0,	'\n',	0,	0,
+[0x10]	0,	0,	0,	0,	Kscroll,0,	0,	0,
+[0x18]	0,	0,	0,	Kesc,	0,	0,	0,	0,
+[0x20]	0,	0,	0,	0,	0,	0,	0,	0,
+[0x28]	0,	0,	0,	0,	0,	0,	0,	0,
+[0x30]	0,	0,	0,	0,	0,	0,	0,	0,
+[0x38]	0,	0,	0,	0,	0,	0,	0,	0,
+[0x40]	0,	0,	0,	0,	0,	0,	0,	0,
+[0x48]	0,	0,	0,	0,	0,	0,	0,	0,
+[0x50]	Khome,	Kleft,	Kup,	Kright,	Kdown,	Kpgup,	Kpgdown,Kend,
+[0x58]	0,	0,	0,	0,	0,	0,	0,	0,
+[0x60]	0,	Kprint,	0,	Kins,	0,	0,	0,	0,
+[0x68]	0,	0,	0,	Kbreak,	0,	0,	0,	0,
+[0x70]	0,	0,	0,	0,	0,	0,	0,	0,
+[0x78]	0,	0,	0,	0,	0,	0,	0,	Knum,
+[0x80]	0,	0,	0,	0,	0,	0,	0,	0,
+[0x88]	0,	0,	0,	0,	0,	0,	0,	0,
+[0x90]	0,	0,	0,	0,	0,	0,	0,	0,
+[0x98]	0,	0,	0,	0,	0,	0,	0,	0,
+[0xa0]	0,	0,	0,	0,	0,	0,	0,	0,
+[0xa8]	0,	0,	'*',	'+',	0,	'-',	'.',	'/',
+[0xb0]	'0',	'1',	'2',	'3',	'4',	'5',	'6',	'7',
+[0xb8]	'8',	'9',	0,	0,	0,	'=',	0,	0,
+[0xc0]	0,	0,	0,	0,	0,	0,	0,	0,
+[0xc8]	0,	0,	0,	0,	0,	0,	0,	0,
+[0xd0]	0,	0,	0,	0,	0,	0,	0,	0,
+[0xd8]	0,	0,	0,	0,	0,	0,	0,	0,
+[0xe0]	0,	Kshift,	Kshift,	Kctl,	Kctl,	Kcaps,	Kcaps,	0,
+[0xe8]	0,	Kalt,	Kalt,	0,	0,	0,	0,	0,
+[0xf0]	0,	0,	0,	0,	0,	0,	0,	0,
+[0xf8]	0,	0,	0,	0,	0,	0,	0,	Kdel,
+};
+
+/*
+ *  keyboard interrupt
+ */
+void
+vncputc(int keyup, int c)
+{
+	char buf[16];
+
+	/*
+ 	 *  character mapping
+	 */
+	if((c & VKSpecial) == VKSpecial){
+		c = vnckeys[c & 0xff];
+		if(c == 0)
+			return;
+	}
+	/*
+	 * map an xkeysym onto a utf-8 char
+	 */
+	if((c & 0xff00) && c < nelem(ksym2utf) && ksym2utf[c] != 0)
+		c = ksym2utf[c];
+	snprint(buf, sizeof(buf), "r%C", c);
+	if(keyup)
+		buf[0] = 'R';
+	if(kbdin >= 0)
+		write(kbdin, buf, strlen(buf)+1);
+}
--- /dev/null
+++ b/sys/src/cmd/vnc/kbdv.c
@@ -1,0 +1,270 @@
+#include "vnc.h"
+#include <keyboard.h>
+#include "utf2ksym.h"
+
+enum {
+	Xshift = 0xFFE1,
+	Xctl = 0xFFE3,
+	Xmeta = 0xFFE7,
+	Xalt = 0xFFE9,
+	Xsuper = 0xFFEB,
+};
+
+static struct {
+	Rune kbdc;
+	ulong keysym;
+} ktab[] = {
+	{'\b',		0xff08},
+	{'\t',		0xff09},
+	{'\n',		0xff0d},
+	/* {0x0b, 0xff0b}, */
+	{'\r',		0xff0d},
+	{Kesc,	0xff1b},
+	{Kins,	0xff63},
+	{Kdel,	0xffff},
+	{Khome,	0xff50},
+	{Kend,	0xff57},
+	{Kpgup,	0xff55},
+	{Kpgdown,	0xff56},
+	{Kleft,	0xff51},
+	{Kup,	0xff52},
+	{Kright,	0xff53},
+	{Kdown,	0xff54},
+	{KF|1,	0xffbe},
+	{KF|2,	0xffbf},
+	{KF|3,	0xffc0},
+	{KF|4,	0xffc1},
+	{KF|5,	0xffc2},
+	{KF|6,	0xffc3},
+	{KF|7,	0xffc4},
+	{KF|8,	0xffc5},
+	{KF|9,	0xffc6},
+	{KF|10,	0xffc7},
+	{KF|11,	0xffc8},
+	{KF|12,	0xffc9},
+
+	{Kshift, Xshift},
+	{Kalt, Xalt},
+	{Kaltgr, Xmeta},
+	{Kmod4, Xsuper},
+	{Kctl, Xctl},
+};
+
+static char shiftkey[128] = {
+	0, 0, 0, 0, 0, 0, 0, 0, /* nul soh stx etx eot enq ack bel */
+	0, 0, 0, 0, 0, 0, 0, 0, /* bs ht nl vt np cr so si */
+	0, 0, 0, 0, 0, 0, 0, 0, /* dle dc1 dc2 dc3 dc4 nak syn etb */
+	0, 0, 0, 0, 0, 0, 0, 0, /* can em sub esc fs gs rs us */
+	0, 1, 1, 1, 1, 1, 1, 0, /* sp ! " # $ % & ' */
+	1, 1, 1, 1, 0, 0, 0, 0, /* ( ) * + , - . / */
+	0, 0, 0, 0, 0, 0, 0, 0, /* 0 1 2 3 4 5 6 7 */
+	0, 0, 1, 0, 1, 0, 1, 1, /* 8 9 : ; < = > ? */
+	1, 1, 1, 1, 1, 1, 1, 1, /* @ A B C D E F G */
+	1, 1, 1, 1, 1, 1, 1, 1, /* H I J K L M N O */
+	1, 1, 1, 1, 1, 1, 1, 1, /* P Q R S T U V W */
+	1, 1, 1, 0, 0, 0, 1, 1, /* X Y Z [ \ ] ^ _ */
+	0, 0, 0, 0, 0, 0, 0, 0, /* ` a b c d e f g */
+	0, 0, 0, 0, 0, 0, 0, 0, /* h i j k l m n o */
+	0, 0, 0, 0, 0, 0, 0, 0, /* p q r s t u v w */
+	0, 0, 0, 1, 1, 1, 1, 0, /* x y z { | } ~ del  */
+};
+
+ulong
+runetoksym(Rune r)
+{
+	int i;
+
+	for(i=0; i<nelem(ktab); i++)
+		if(ktab[i].kbdc == r)
+			return ktab[i].keysym;
+	return r;
+}
+
+static void
+keyevent(Vnc *v, ulong ksym, int down)
+{
+	vnclock(v);
+	vncwrchar(v, MKey);
+	vncwrchar(v, down);
+	vncwrshort(v, 0);
+	vncwrlong(v, ksym);
+	vncflush(v);
+	vncunlock(v);
+}
+
+static void
+readcons(Vnc *v)
+{
+	char buf[256], k[10];
+	ulong ks;
+	int ctlfd, fd, kr, kn, w, shift, ctl, alt, super;
+	Rune r;
+
+	snprint(buf, sizeof buf, "%s/cons", display->devdir);
+	if((fd = open(buf, OREAD)) < 0)
+		sysfatal("open %s: %r", buf);
+
+	snprint(buf, sizeof buf, "%s/consctl", display->devdir);
+	if((ctlfd = open(buf, OWRITE)) < 0)
+		sysfatal("open %s: %r", buf);
+	write(ctlfd, "rawon", 5);
+
+	kn = 0;
+	shift = alt = ctl = super = 0;
+	for(;;){
+		while(!fullrune(k, kn)){
+			kr = read(fd, k+kn, sizeof k - kn);
+			if(kr <= 0)
+				sysfatal("bad read from kbd");
+			kn += kr;
+		}
+		w = chartorune(&r, k);
+		kn -= w;
+		memmove(k, &k[w], kn);
+		ks = runetoksym(r);
+
+		switch(r){
+		case Kalt:
+			alt = !alt;
+			keyevent(v, Xalt, alt);
+			break;
+		case Kctl:
+			ctl = !ctl;
+			keyevent(v, Xctl, ctl);
+			break;
+		case Kshift:
+			shift = !shift;
+			keyevent(v, Xshift, shift);
+			break;
+		case Kmod4:
+			super = !super;
+			keyevent(v, Xsuper, super);
+			break;
+		default:
+			if(r == ks && r < 0x1A){	/* control key */
+				keyevent(v, Xctl, 1);
+				keyevent(v, r+0x60, 1);	/* 0x60: make capital letter */
+				keyevent(v, r+0x60, 0);
+				keyevent(v, Xctl, 0);
+			}else{
+				/*
+				 * to send an upper case letter or shifted
+				 * punctuation, mac os x vnc server,
+				 * at least, needs a `shift' sent first.
+				 */
+				if(!shift && r == ks && r < sizeof shiftkey && shiftkey[r]){
+					shift = 1;
+					keyevent(v, Xshift, 1);
+				}
+				/*
+				 * map an xkeysym onto a utf-8 char.
+				 * allows Xvnc to read us, see utf2ksym.h
+				 */
+				if((ks & 0xff00) && ks < nelem(utf2ksym) && utf2ksym[ks] != 0)
+					ks = utf2ksym[ks];
+				keyevent(v, ks, 1);
+				/*
+				 * up event needed by vmware inside linux vnc server,
+				 * perhaps others.
+				 */
+				keyevent(v, ks, 0);
+			}
+
+			if(alt){
+				keyevent(v, Xalt, 0);
+				alt = 0;
+			}
+			if(ctl){
+				keyevent(v, Xctl, 0);
+				ctl = 0;
+			}
+			if(shift){
+				keyevent(v, Xshift, 0);
+				shift = 0;
+			}
+			if(super){
+				keyevent(v, Xsuper, 0);
+				super = 0;
+			}
+			break;
+		}
+	}
+}
+
+ulong
+runetovnc(Rune r)
+{
+	ulong k;
+
+	k = runetoksym(r);
+	if((k & 0xff00) && k < nelem(utf2ksym) && utf2ksym[k] != 0)
+		k = utf2ksym[k];
+	return k;
+}
+
+void
+readkbd(Vnc *v)
+{
+	char buf[128], buf2[128], *s;
+	int fd, n;
+	Rune r;
+
+	if((fd = open("/dev/kbd", OREAD)) < 0){
+		readcons(v);
+		return;
+	}
+
+	buf2[0] = 0;
+	buf2[1] = 0;
+	buf[0] = 0;
+	for(;;){
+		if(buf[0] != 0){
+			n = strlen(buf)+1;
+			memmove(buf, buf+n, sizeof(buf)-n);
+		}
+		if(buf[0] == 0){
+			n = read(fd, buf, sizeof(buf)-1);
+			if(n <= 0)
+				break;
+			buf[n-1] = 0;
+			buf[n] = 0;
+		}
+		switch(buf[0]){
+		case 'k':
+			s = buf+1;
+			while(*s){
+				s += chartorune(&r, s);
+				if(utfrune(buf2+1, r) == nil)
+					if((r == Kshift) ||
+					   utfrune(buf+1, Kctl) || 
+					   utfrune(buf+1, Kalt) ||
+					   utfrune(buf+1, Kmod4) ||
+					   utfrune(buf+1, Kaltgr))
+						keyevent(v, runetovnc(r), 1);
+			}
+			break;
+		case 'K':
+			s = buf2+1;
+			while(*s){
+				s += chartorune(&r, s);
+				if(utfrune(buf+1, r) == nil)
+					keyevent(v, runetovnc(r), 0);
+			}
+			break;
+		case 'c':
+			if(utfrune(buf2+1, Kctl) ||
+			   utfrune(buf2+1, Kalt) ||
+			   utfrune(buf2+1, Kmod4) ||
+			   utfrune(buf2+1, Kaltgr))
+				continue;
+			chartorune(&r, buf+1);
+			keyevent(v, runetovnc(r), 1);
+			if(utfrune(buf2+1, r) == nil)
+				keyevent(v, runetovnc(r), 0);
+		default:
+			continue;
+		}
+		strcpy(buf2, buf);
+	}
+}
+
--- /dev/null
+++ b/sys/src/cmd/vnc/ksym2utf.h
@@ -1,0 +1,1097 @@
+/*
+ * VNC uses X11's keysyms defined in X11/keysym.h, this is a converter
+ * to unicode characters
+ */
+static ulong
+ksym2utf [] = {
+	[0x1a1]	L'Ą',
+	[0x1a2]	L'˘',
+	[0x1a3]	L'Ł',
+	[0x1a5]	L'Ľ',
+	[0x1a6]	L'Ś',
+	[0x1a9]	L'Š',
+	[0x1aa]	L'Ş',
+	[0x1ab]	L'Ť',
+	[0x1ac]	L'Ź',
+	[0x1ae]	L'Ž',
+	[0x1af]	L'Ż',
+	[0x1b1]	L'ą',
+	[0x1b2]	L'˛',
+	[0x1b3]	L'ł',
+	[0x1b5]	L'ľ',
+	[0x1b6]	L'ś',
+	[0x1b7]	L'ˇ',
+	[0x1b9]	L'š',
+	[0x1ba]	L'ş',
+	[0x1bb]	L'ť',
+	[0x1bc]	L'ź',
+	[0x1bd]	L'˝',
+	[0x1be]	L'ž',
+	[0x1bf]	L'ż',
+	[0x1c0]	L'Ŕ',
+	[0x1c3]	L'Ă',
+	[0x1c5]	L'Ĺ',
+	[0x1c6]	L'Ć',
+	[0x1c8]	L'Č',
+	[0x1ca]	L'Ę',
+	[0x1cc]	L'Ě',
+	[0x1cf]	L'Ď',
+	[0x1d0]	L'Đ',
+	[0x1d1]	L'Ń',
+	[0x1d2]	L'Ň',
+	[0x1d5]	L'Ő',
+	[0x1d8]	L'Ř',
+	[0x1d9]	L'Ů',
+	[0x1db]	L'Ű',
+	[0x1de]	L'Ţ',
+	[0x1e0]	L'ŕ',
+	[0x1e3]	L'ă',
+	[0x1e5]	L'ĺ',
+	[0x1e6]	L'ć',
+	[0x1e8]	L'č',
+	[0x1ea]	L'ę',
+	[0x1ec]	L'ě',
+	[0x1ef]	L'ď',
+	[0x1f0]	L'đ',
+	[0x1f1]	L'ń',
+	[0x1f2]	L'ň',
+	[0x1f5]	L'ő',
+	[0x1f8]	L'ř',
+	[0x1f9]	L'ů',
+	[0x1fb]	L'ű',
+	[0x1fe]	L'ţ',
+	[0x1ff]	L'˙',
+	[0x2a1]	L'Ħ',
+	[0x2a6]	L'Ĥ',
+	[0x2a9]	L'İ',
+	[0x2ab]	L'Ğ',
+	[0x2ac]	L'Ĵ',
+	[0x2b1]	L'ħ',
+	[0x2b6]	L'ĥ',
+	[0x2b9]	L'ı',
+	[0x2bb]	L'ğ',
+	[0x2bc]	L'ĵ',
+	[0x2c5]	L'Ċ',
+	[0x2c6]	L'Ĉ',
+	[0x2d5]	L'Ġ',
+	[0x2d8]	L'Ĝ',
+	[0x2dd]	L'Ŭ',
+	[0x2de]	L'Ŝ',
+	[0x2e5]	L'ċ',
+	[0x2e6]	L'ĉ',
+	[0x2f5]	L'ġ',
+	[0x2f8]	L'ĝ',
+	[0x2fd]	L'ŭ',
+	[0x2fe]	L'ŝ',
+	[0x3a2]	L'ĸ',
+	[0x3a3]	L'Ŗ',
+	[0x3a5]	L'Ĩ',
+	[0x3a6]	L'Ļ',
+	[0x3aa]	L'Ē',
+	[0x3ab]	L'Ģ',
+	[0x3ac]	L'Ŧ',
+	[0x3b3]	L'ŗ',
+	[0x3b5]	L'ĩ',
+	[0x3b6]	L'ļ',
+	[0x3ba]	L'ē',
+	[0x3bb]	L'ģ',
+	[0x3bc]	L'ŧ',
+	[0x3bd]	L'Ŋ',
+	[0x3bf]	L'ŋ',
+	[0x3c0]	L'Ā',
+	[0x3c7]	L'Į',
+	[0x3cc]	L'Ė',
+	[0x3cf]	L'Ī',
+	[0x3d1]	L'Ņ',
+	[0x3d2]	L'Ō',
+	[0x3d3]	L'Ķ',
+	[0x3d9]	L'Ų',
+	[0x3dd]	L'Ũ',
+	[0x3de]	L'Ū',
+	[0x3e0]	L'ā',
+	[0x3e7]	L'į',
+	[0x3ec]	L'ė',
+	[0x3ef]	L'ī',
+	[0x3f1]	L'ņ',
+	[0x3f2]	L'ō',
+	[0x3f3]	L'ķ',
+	[0x3f9]	L'ų',
+	[0x3fd]	L'ũ',
+	[0x3fe]	L'ū',
+	[0x4a1]	L'。',
+	[0x4a2]	L'〈',
+	[0x4a3]	L'〉',
+	[0x4a4]	L'、',
+	[0x4a5]	L'・',
+	[0x4a6]	L'ヲ',
+	[0x4a7]	L'ァ',
+	[0x4a8]	L'ィ',
+	[0x4a9]	L'ゥ',
+	[0x4aa]	L'ェ',
+	[0x4ab]	L'ォ',
+	[0x4ac]	L'ャ',
+	[0x4ad]	L'ュ',
+	[0x4ae]	L'ョ',
+	[0x4af]	L'ッ',
+	[0x4b0]	L'ー',
+	[0x4b1]	L'ア',
+	[0x4b2]	L'イ',
+	[0x4b3]	L'ウ',
+	[0x4b4]	L'エ',
+	[0x4b5]	L'オ',
+	[0x4b6]	L'カ',
+	[0x4b7]	L'キ',
+	[0x4b8]	L'ク',
+	[0x4b9]	L'ケ',
+	[0x4ba]	L'コ',
+	[0x4bb]	L'サ',
+	[0x4bc]	L'シ',
+	[0x4bd]	L'ス',
+	[0x4be]	L'セ',
+	[0x4bf]	L'ソ',
+	[0x4c0]	L'タ',
+	[0x4c1]	L'チ',
+	[0x4c2]	L'ツ',
+	[0x4c3]	L'テ',
+	[0x4c4]	L'ト',
+	[0x4c5]	L'ナ',
+	[0x4c6]	L'ニ',
+	[0x4c7]	L'ヌ',
+	[0x4c8]	L'ネ',
+	[0x4c9]	L'ノ',
+	[0x4ca]	L'ハ',
+	[0x4cb]	L'ヒ',
+	[0x4cc]	L'フ',
+	[0x4cd]	L'ヘ',
+	[0x4ce]	L'ホ',
+	[0x4cf]	L'マ',
+	[0x4d0]	L'ミ',
+	[0x4d1]	L'ム',
+	[0x4d2]	L'メ',
+	[0x4d3]	L'モ',
+	[0x4d4]	L'ヤ',
+	[0x4d5]	L'ユ',
+	[0x4d6]	L'ヨ',
+	[0x4d7]	L'ラ',
+	[0x4d8]	L'リ',
+	[0x4d9]	L'ル',
+	[0x4da]	L'レ',
+	[0x4db]	L'ロ',
+	[0x4dc]	L'ワ',
+	[0x4dd]	L'ン',
+	[0x4de]	L'゛',
+	[0x4df]	L'゜',
+	[0x58a]	L'ロ',
+	[0x58b]	L'ワ',
+	[0x58c]	L'ン',
+	[0x58d]	L'゛',
+	[0x58e]	L'゜',
+	[0x590]	L'۰',
+	[0x591]	L'۱',
+	[0x592]	L'۲',
+	[0x593]	L'۳',
+	[0x594]	L'۴',
+	[0x595]	L'۵',
+	[0x596]	L'۶',
+	[0x597]	L'۷',
+	[0x598]	L'۸',
+	[0x599]	L'۹',
+	[0x5a5]	L'٪',
+	[0x5a6]	L'ٰ',
+	[0x5a7]	L'ٹ',
+	[0x5a8]	L'پ',
+	[0x5a9]	L'چ',
+	[0x5aa]	L'ڈ',
+	[0x5ab]	L'ڑ',
+	[0x5ac]	L'،',
+	[0x5ae]	L'۔',
+	[0x5b0]	L'٠',
+	[0x5b1]	L'١',
+	[0x5b2]	L'٢',
+	[0x5b3]	L'٣',
+	[0x5b4]	L'٤',
+	[0x5b5]	L'٥',
+	[0x5b6]	L'٦',
+	[0x5b7]	L'٧',
+	[0x5b8]	L'٨',
+	[0x5b9]	L'٩',
+	[0x5bb]	L'؛',
+	[0x5bf]	L'؟',
+	[0x5c1]	L'ء',
+	[0x5c2]	L'آ',
+	[0x5c3]	L'أ',
+	[0x5c4]	L'ؤ',
+	[0x5c5]	L'إ',
+	[0x5c6]	L'ئ',
+	[0x5c7]	L'ا',
+	[0x5c8]	L'ب',
+	[0x5c9]	L'ة',
+	[0x5ca]	L'ت',
+	[0x5cb]	L'ث',
+	[0x5cc]	L'ج',
+	[0x5cd]	L'ح',
+	[0x5ce]	L'خ',
+	[0x5cf]	L'د',
+	[0x5d0]	L'ذ',
+	[0x5d1]	L'ر',
+	[0x5d2]	L'ز',
+	[0x5d3]	L'س',
+	[0x5d4]	L'ش',
+	[0x5d5]	L'ص',
+	[0x5d6]	L'ض',
+	[0x5d7]	L'ط',
+	[0x5d8]	L'ظ',
+	[0x5d9]	L'ع',
+	[0x5da]	L'غ',
+	[0x5e0]	L'ـ',
+	[0x5e1]	L'ف',
+	[0x5e2]	L'ق',
+	[0x5e3]	L'ك',
+	[0x5e4]	L'ل',
+	[0x5e5]	L'م',
+	[0x5e6]	L'ن',
+	[0x5e7]	L'ه',
+	[0x5e8]	L'و',
+	[0x5e9]	L'ى',
+	[0x5ea]	L'ي',
+	[0x5eb]	L'ً',
+	[0x5ec]	L'ٌ',
+	[0x5ed]	L'ٍ',
+	[0x5ee]	L'َ',
+	[0x5ef]	L'ُ',
+	[0x5f0]	L'ِ',
+	[0x5f1]	L'ّ',
+	[0x5f2]	L'ْ',
+	[0x5f3]	L'ٓ',
+	[0x5f4]	L'ٔ',
+	[0x5f5]	L'ٕ',
+	[0x5f6]	L'ژ',
+	[0x5f7]	L'ڤ',
+	[0x5f8]	L'ک',
+	[0x5f9]	L'گ',
+	[0x5fa]	L'ں',
+	[0x5fb]	L'ھ',
+	[0x5fc]	L'ی',
+	[0x5fd]	L'ے',
+	[0x5fe]	L'ہ',
+	[0x680]	L'Ғ',
+	[0x681]	L'Җ',
+	[0x682]	L'Қ',
+	[0x683]	L'Ҝ',
+	[0x684]	L'Ң',
+	[0x685]	L'Ү',
+	[0x686]	L'Ұ',
+	[0x687]	L'Ҳ',
+	[0x688]	L'Ҷ',
+	[0x689]	L'Ҹ',
+	[0x68a]	L'Һ',
+	[0x68c]	L'Ә',
+	[0x68d]	L'Ӣ',
+	[0x68e]	L'Ө',
+	[0x68f]	L'Ӯ',
+	[0x690]	L'ғ',
+	[0x691]	L'җ',
+	[0x692]	L'қ',
+	[0x693]	L'ҝ',
+	[0x694]	L'ң',
+	[0x695]	L'ү',
+	[0x696]	L'ұ',
+	[0x697]	L'ҳ',
+	[0x698]	L'ҷ',
+	[0x699]	L'ҹ',
+	[0x69a]	L'һ',
+	[0x69c]	L'ә',
+	[0x69d]	L'ӣ',
+	[0x69e]	L'ө',
+	[0x69f]	L'ӯ',
+	[0x6a1]	L'ђ',
+	[0x6a2]	L'ѓ',
+	[0x6a3]	L'ё',
+	[0x6a4]	L'є',
+	[0x6a5]	L'ѕ',
+	[0x6a6]	L'і',
+	[0x6a7]	L'ї',
+	[0x6a8]	L'ј',
+	[0x6a9]	L'љ',
+	[0x6aa]	L'њ',
+	[0x6ab]	L'ћ',
+	[0x6ac]	L'ќ',
+	[0x6ad]	L'ґ',
+	[0x6ae]	L'ў',
+	[0x6af]	L'џ',
+	[0x6b0]	L'№',
+	[0x6b1]	L'Ђ',
+	[0x6b2]	L'Ѓ',
+	[0x6b3]	L'Ё',
+	[0x6b4]	L'Є',
+	[0x6b5]	L'Ѕ',
+	[0x6b6]	L'І',
+	[0x6b7]	L'Ї',
+	[0x6b8]	L'Ј',
+	[0x6b9]	L'Љ',
+	[0x6ba]	L'Њ',
+	[0x6bb]	L'Ћ',
+	[0x6bc]	L'Ќ',
+	[0x6bd]	L'Ґ',
+	[0x6be]	L'Ў',
+	[0x6bf]	L'Џ',
+	[0x6c0]	L'ю',
+	[0x6c1]	L'а',
+	[0x6c2]	L'б',
+	[0x6c3]	L'ц',
+	[0x6c4]	L'д',
+	[0x6c5]	L'е',
+	[0x6c6]	L'ф',
+	[0x6c7]	L'г',
+	[0x6c8]	L'х',
+	[0x6c9]	L'и',
+	[0x6ca]	L'й',
+	[0x6cb]	L'к',
+	[0x6cc]	L'л',
+	[0x6cd]	L'м',
+	[0x6ce]	L'н',
+	[0x6cf]	L'о',
+	[0x6d0]	L'п',
+	[0x6d1]	L'я',
+	[0x6d2]	L'р',
+	[0x6d3]	L'с',
+	[0x6d4]	L'т',
+	[0x6d5]	L'у',
+	[0x6d6]	L'ж',
+	[0x6d7]	L'в',
+	[0x6d8]	L'ь',
+	[0x6d9]	L'ы',
+	[0x6da]	L'з',
+	[0x6db]	L'ш',
+	[0x6dc]	L'э',
+	[0x6dd]	L'щ',
+	[0x6de]	L'ч',
+	[0x6df]	L'ъ',
+	[0x6e0]	L'Ю',
+	[0x6e1]	L'А',
+	[0x6e2]	L'Б',
+	[0x6e3]	L'Ц',
+	[0x6e4]	L'Д',
+	[0x6e5]	L'Е',
+	[0x6e6]	L'Ф',
+	[0x6e7]	L'Г',
+	[0x6e8]	L'Х',
+	[0x6e9]	L'И',
+	[0x6ea]	L'Й',
+	[0x6eb]	L'К',
+	[0x6ec]	L'Л',
+	[0x6ed]	L'М',
+	[0x6ee]	L'Н',
+	[0x6ef]	L'О',
+	[0x6f0]	L'П',
+	[0x6f1]	L'Я',
+	[0x6f2]	L'Р',
+	[0x6f3]	L'С',
+	[0x6f4]	L'Т',
+	[0x6f5]	L'У',
+	[0x6f6]	L'Ж',
+	[0x6f7]	L'В',
+	[0x6f8]	L'Ь',
+	[0x6f9]	L'Ы',
+	[0x6fa]	L'З',
+	[0x6fb]	L'Ш',
+	[0x6fc]	L'Э',
+	[0x6fd]	L'Щ',
+	[0x6fe]	L'Ч',
+	[0x6ff]	L'Ъ',
+	[0x7a1]	L'Ά',
+	[0x7a2]	L'Έ',
+	[0x7a3]	L'Ή',
+	[0x7a4]	L'Ί',
+	[0x7a5]	L'Ϊ',
+	[0x7a7]	L'Ό',
+	[0x7a8]	L'Ύ',
+	[0x7a9]	L'Ϋ',
+	[0x7ab]	L'Ώ',
+	[0x7ae]	L'΅',
+	[0x7af]	L'―',
+	[0x7b1]	L'ά',
+	[0x7b2]	L'έ',
+	[0x7b3]	L'ή',
+	[0x7b4]	L'ί',
+	[0x7b5]	L'ϊ',
+	[0x7b6]	L'ΐ',
+	[0x7b7]	L'ό',
+	[0x7b8]	L'ύ',
+	[0x7b9]	L'ϋ',
+	[0x7ba]	L'ΰ',
+	[0x7bb]	L'ώ',
+	[0x7c1]	L'Α',
+	[0x7c2]	L'Β',
+	[0x7c3]	L'Γ',
+	[0x7c4]	L'Δ',
+	[0x7c5]	L'Ε',
+	[0x7c6]	L'Ζ',
+	[0x7c7]	L'Η',
+	[0x7c8]	L'Θ',
+	[0x7c9]	L'Ι',
+	[0x7ca]	L'Κ',
+	[0x7cb]	L'Λ',
+	[0x7cc]	L'Μ',
+	[0x7cd]	L'Ν',
+	[0x7ce]	L'Ξ',
+	[0x7cf]	L'Ο',
+	[0x7d0]	L'Π',
+	[0x7d1]	L'Ρ',
+	[0x7d2]	L'Σ',
+	[0x7d4]	L'Τ',
+	[0x7d5]	L'Υ',
+	[0x7d6]	L'Φ',
+	[0x7d7]	L'Χ',
+	[0x7d8]	L'Ψ',
+	[0x7d9]	L'Ω',
+	[0x7e1]	L'α',
+	[0x7e2]	L'β',
+	[0x7e3]	L'γ',
+	[0x7e4]	L'δ',
+	[0x7e5]	L'ε',
+	[0x7e6]	L'ζ',
+	[0x7e7]	L'η',
+	[0x7e8]	L'θ',
+	[0x7e9]	L'ι',
+	[0x7ea]	L'κ',
+	[0x7eb]	L'λ',
+	[0x7ec]	L'μ',
+	[0x7ed]	L'ν',
+	[0x7ee]	L'ξ',
+	[0x7ef]	L'ο',
+	[0x7f0]	L'π',
+	[0x7f1]	L'ρ',
+	[0x7f2]	L'σ',
+	[0x7f3]	L'ς',
+	[0x7f4]	L'τ',
+	[0x7f5]	L'υ',
+	[0x7f6]	L'φ',
+	[0x7f7]	L'χ',
+	[0x7f8]	L'ψ',
+	[0x7f9]	L'ω',
+	[0x8a4]	L'⌠',
+	[0x8a5]	L'⌡',
+	[0x8a7]	L'⌜',
+	[0x8a8]	L'⌝',
+	[0x8a9]	L'⌞',
+	[0x8aa]	L'⌟',
+	[0x8bc]	L'≤',
+	[0x8bd]	L'≠',
+	[0x8be]	L'≥',
+	[0x8bf]	L'∫',
+	[0x8c0]	L'∴',
+	[0x8c2]	L'∞',
+	[0x8c5]	L'∇',
+	[0x8c8]	L'≅',
+	[0x8c9]	L'≆',
+	[0x8ce]	L'⊢',
+	[0x8d6]	L'√',
+	[0x8da]	L'⊂',
+	[0x8db]	L'⊃',
+	[0x8dc]	L'∩',
+	[0x8dd]	L'∪',
+	[0x8de]	L'∧',
+	[0x8df]	L'∨',
+	[0x8f6]	L'ƒ',
+	[0x8fb]	L'←',
+	[0x8fc]	L'↑',
+	[0x8fd]	L'→',
+	[0x8fe]	L'↓',
+	[0x9df]	L'␢',
+	[0x9e0]	L'♦',
+	[0x9e1]	L'▦',
+	[0x9e2]	L'␉',
+	[0x9e3]	L'␌',
+	[0x9e4]	L'␍',
+	[0x9e5]	L'␊',
+	[0x9e8]	L'␊',
+	[0x9e9]	L'␋',
+	[0x9ea]	L'┘',
+	[0x9eb]	L'┐',
+	[0x9ec]	L'┌',
+	[0x9ed]	L'└',
+	[0x9ee]	L'┼',
+	[0x9ef]	L'─',
+	[0x9f4]	L'├',
+	[0x9f5]	L'┤',
+	[0x9f6]	L'┴',
+	[0x9f7]	L'┬',
+	[0x9f8]	L'│',
+	[0xaa1]	L' ',
+	[0xaa2]	L' ',
+	[0xaa3]	L' ',
+	[0xaa4]	L' ',
+	[0xaa5]	L' ',
+	[0xaa6]	L' ',
+	[0xaa7]	L' ',
+	[0xaa8]	L' ',
+	[0xaa9]	L'—',
+	[0xaaa]	L'–',
+	[0xaae]	L'…',
+	[0xaaf]	L'‥',
+	[0xab0]	L'⅓',
+	[0xab1]	L'⅔',
+	[0xab2]	L'⅕',
+	[0xab3]	L'⅖',
+	[0xab4]	L'⅗',
+	[0xab5]	L'⅘',
+	[0xab6]	L'⅙',
+	[0xab7]	L'⅚',
+	[0xab8]	L'℅',
+	[0xabb]	L'‒',
+	[0xabc]	L'‹',
+	[0xabd]	L'․',
+	[0xabe]	L'›',
+	[0xac3]	L'⅛',
+	[0xac4]	L'⅜',
+	[0xac5]	L'⅝',
+	[0xac6]	L'⅞',
+	[0xac9]	L'™',
+	[0xaca]	L'℠',
+	[0xacc]	L'◁',
+	[0xacd]	L'▷',
+	[0xace]	L'○',
+	[0xacf]	L'▭',
+	[0xad0]	L'‘',
+	[0xad1]	L'’',
+	[0xad2]	L'“',
+	[0xad3]	L'”',
+	[0xad4]	L'℞',
+	[0xad6]	L'′',
+	[0xad7]	L'″',
+	[0xad9]	L'✝',
+	[0xadb]	L'∎',
+	[0xadc]	L'◂',
+	[0xadd]	L'‣',
+	[0xade]	L'●',
+	[0xadf]	L'▬',
+	[0xae0]	L'◦',
+	[0xae1]	L'▫',
+	[0xae2]	L'▮',
+	[0xae3]	L'▵',
+	[0xae4]	L'▿',
+	[0xae5]	L'☆',
+	[0xae6]	L'•',
+	[0xae7]	L'▪',
+	[0xae8]	L'▴',
+	[0xae9]	L'▾',
+	[0xaea]	L'☚',
+	[0xaeb]	L'☛',
+	[0xaec]	L'♣',
+	[0xaed]	L'♦',
+	[0xaee]	L'♥',
+	[0xaf0]	L'✠',
+	[0xaf1]	L'†',
+	[0xaf2]	L'‡',
+	[0xaf3]	L'✓',
+	[0xaf4]	L'☒',
+	[0xaf5]	L'♯',
+	[0xaf6]	L'♭',
+	[0xaf7]	L'♂',
+	[0xaf8]	L'♀',
+	[0xaf9]	L'℡',
+	[0xafa]	L'⌕',
+	[0xafb]	L'℗',
+	[0xafc]	L'‸',
+	[0xafd]	L'‚',
+	[0xafe]	L'„',
+	[0xcdf]	L'‗',
+	[0xce0]	L'א',
+	[0xce1]	L'ב',
+	[0xce2]	L'ג',
+	[0xce3]	L'ד',
+	[0xce4]	L'ה',
+	[0xce5]	L'ו',
+	[0xce6]	L'ז',
+	[0xce7]	L'ח',
+	[0xce8]	L'ט',
+	[0xce9]	L'י',
+	[0xcea]	L'ך',
+	[0xceb]	L'כ',
+	[0xcec]	L'ל',
+	[0xced]	L'ם',
+	[0xcee]	L'מ',
+	[0xcef]	L'ן',
+	[0xcf0]	L'נ',
+	[0xcf1]	L'ס',
+	[0xcf2]	L'ע',
+	[0xcf3]	L'ף',
+	[0xcf4]	L'פ',
+	[0xcf5]	L'ץ',
+	[0xcf6]	L'צ',
+	[0xcf7]	L'ק',
+	[0xcf8]	L'ר',
+	[0xcf9]	L'ש',
+	[0xcfa]	L'ת',
+	[0xda1]	L'ก',
+	[0xda2]	L'ข',
+	[0xda3]	L'ฃ',
+	[0xda4]	L'ค',
+	[0xda5]	L'ฅ',
+	[0xda6]	L'ฆ',
+	[0xda7]	L'ง',
+	[0xda8]	L'จ',
+	[0xda9]	L'ฉ',
+	[0xdaa]	L'ช',
+	[0xdab]	L'ซ',
+	[0xdac]	L'ฌ',
+	[0xdad]	L'ญ',
+	[0xdae]	L'ฎ',
+	[0xdaf]	L'ฏ',
+	[0xdb0]	L'ฐ',
+	[0xdb1]	L'ฑ',
+	[0xdb2]	L'ฒ',
+	[0xdb3]	L'ณ',
+	[0xdb4]	L'ด',
+	[0xdb5]	L'ต',
+	[0xdb6]	L'ถ',
+	[0xdb7]	L'ท',
+	[0xdb8]	L'ธ',
+	[0xdb9]	L'น',
+	[0xdba]	L'บ',
+	[0xdbb]	L'ป',
+	[0xdbc]	L'ผ',
+	[0xdbd]	L'ฝ',
+	[0xdbe]	L'พ',
+	[0xdbf]	L'ฟ',
+	[0xdc0]	L'ภ',
+	[0xdc1]	L'ม',
+	[0xdc2]	L'ย',
+	[0xdc3]	L'ร',
+	[0xdc4]	L'ฤ',
+	[0xdc5]	L'ล',
+	[0xdc6]	L'ฦ',
+	[0xdc7]	L'ว',
+	[0xdc8]	L'ศ',
+	[0xdc9]	L'ษ',
+	[0xdca]	L'ส',
+	[0xdcb]	L'ห',
+	[0xdcc]	L'ฬ',
+	[0xdcd]	L'อ',
+	[0xdce]	L'ฮ',
+	[0xdcf]	L'ฯ',
+	[0xdd0]	L'ะ',
+	[0xdd1]	L'ั',
+	[0xdd2]	L'า',
+	[0xdd3]	L'ำ',
+	[0xdd4]	L'ิ',
+	[0xdd5]	L'ี',
+	[0xdd6]	L'ึ',
+	[0xdd7]	L'ื',
+	[0xdd8]	L'ุ',
+	[0xdd9]	L'ู',
+	[0xdda]	L'ฺ',
+	[0xdde]	L'฾',
+	[0xddf]	L'฿',
+	[0xde0]	L'เ',
+	[0xde1]	L'แ',
+	[0xde2]	L'โ',
+	[0xde3]	L'ใ',
+	[0xde4]	L'ไ',
+	[0xde5]	L'ๅ',
+	[0xde6]	L'ๆ',
+	[0xde7]	L'็',
+	[0xde8]	L'่',
+	[0xde9]	L'้',
+	[0xdea]	L'๊',
+	[0xdeb]	L'๋',
+	[0xdec]	L'์',
+	[0xded]	L'ํ',
+	[0xdf0]	L'๐',
+	[0xdf1]	L'๑',
+	[0xdf2]	L'๒',
+	[0xdf3]	L'๓',
+	[0xdf4]	L'๔',
+	[0xdf5]	L'๕',
+	[0xdf6]	L'๖',
+	[0xdf7]	L'๗',
+	[0xdf8]	L'๘',
+	[0xdf9]	L'๙',
+	[0xea1]	L'ᄁ',
+	[0xea2]	L'ᄁ',
+	[0xea3]	L'ᆪ',
+	[0xea4]	L'ᄂ',
+	[0xea5]	L'ᆬ',
+	[0xea6]	L'ᆭ',
+	[0xea7]	L'ᄃ',
+	[0xea8]	L'ᄄ',
+	[0xea9]	L'ᄅ',
+	[0xeaa]	L'ᆰ',
+	[0xeab]	L'ᆱ',
+	[0xeac]	L'ᆲ',
+	[0xead]	L'ᆳ',
+	[0xeae]	L'ᆴ',
+	[0xeaf]	L'ᆵ',
+	[0xeb0]	L'ᆶ',
+	[0xeb1]	L'ᄆ',
+	[0xeb2]	L'ᄇ',
+	[0xeb3]	L'ᄈ',
+	[0xeb4]	L'ᆹ',
+	[0xeb5]	L'ᄉ',
+	[0xeb6]	L'ᄊ',
+	[0xeb7]	L'ᄋ',
+	[0xeb8]	L'ᄌ',
+	[0xeb9]	L'ᄍ',
+	[0xeba]	L'ᄎ',
+	[0xebb]	L'ᄏ',
+	[0xebc]	L'ᄐ',
+	[0xebd]	L'ᄑ',
+	[0xebe]	L'ᄒ',
+	[0xebf]	L'ᅡ',
+	[0xec0]	L'ᅢ',
+	[0xec1]	L'ᅣ',
+	[0xec2]	L'ᅤ',
+	[0xec3]	L'ᅥ',
+	[0xec4]	L'ᅦ',
+	[0xec5]	L'ᅧ',
+	[0xec6]	L'ᅨ',
+	[0xec7]	L'ᅩ',
+	[0xec8]	L'ᅪ',
+	[0xec9]	L'ᅫ',
+	[0xeca]	L'ᅬ',
+	[0xecb]	L'ᅭ',
+	[0xecc]	L'ᅮ',
+	[0xecd]	L'ᅯ',
+	[0xece]	L'ᅰ',
+	[0xecf]	L'ᅱ',
+	[0xed0]	L'ᅲ',
+	[0xed1]	L'ᅳ',
+	[0xed2]	L'ᅴ',
+	[0xed3]	L'ᅵ',
+	[0xed4]	L'ᆨ',
+	[0xed5]	L'ᆩ',
+	[0xed6]	L'ᆪ',
+	[0xed7]	L'ᆫ',
+	[0xed8]	L'ᆬ',
+	[0xed9]	L'ᆭ',
+	[0xeda]	L'ᆮ',
+	[0xedb]	L'ᆯ',
+	[0xedc]	L'ᆰ',
+	[0xedd]	L'ᆱ',
+	[0xede]	L'ᆲ',
+	[0xedf]	L'ᆳ',
+	[0xee0]	L'ᆴ',
+	[0xee1]	L'ᆵ',
+	[0xee2]	L'ᆶ',
+	[0xee3]	L'ᆷ',
+	[0xee4]	L'ᆸ',
+	[0xee5]	L'ᆹ',
+	[0xee6]	L'ᆺ',
+	[0xee7]	L'ᆻ',
+	[0xee8]	L'ᆼ',
+	[0xee9]	L'ᆽ',
+	[0xeea]	L'ᆾ',
+	[0xeeb]	L'ᆿ',
+	[0xeec]	L'ᇀ',
+	[0xeed]	L'ᇁ',
+	[0xeee]	L'ᇂ',
+	[0xef2]	L'ᅀ',
+	[0xef5]	L'ᅙ',
+	[0xef6]	L'ᆞ',
+	[0xef8]	L'ᇫ',
+	[0xefa]	L'ᇹ',
+	[0xeff]	L'₩',
+	[0x12a1]	L'Ḃ',
+	[0x12a2]	L'ḃ',
+	[0x12a6]	L'Ḋ',
+	[0x12a8]	L'Ẁ',
+	[0x12aa]	L'Ẃ',
+	[0x12ab]	L'ḋ',
+	[0x12ac]	L'Ỳ',
+	[0x12b0]	L'Ḟ',
+	[0x12b1]	L'ḟ',
+	[0x12b4]	L'Ṁ',
+	[0x12b5]	L'ṁ',
+	[0x12b7]	L'Ṗ',
+	[0x12b8]	L'ẁ',
+	[0x12b9]	L'ṗ',
+	[0x12ba]	L'ẃ',
+	[0x12bb]	L'Ṡ',
+	[0x12bc]	L'ỳ',
+	[0x12bd]	L'Ẅ',
+	[0x12be]	L'ẅ',
+	[0x12bf]	L'ṡ',
+	[0x12d0]	L'Ŵ',
+	[0x12d7]	L'Ṫ',
+	[0x12de]	L'Ŷ',
+	[0x12f0]	L'ŵ',
+	[0x12f7]	L'ṫ',
+	[0x12fe]	L'ŷ',
+	[0x13bc]	L'Œ',
+	[0x13bd]	L'œ',
+	[0x13be]	L'Ÿ',
+	[0x14a1]	L'❁',
+	[0x14a2]	L'§',
+	[0x14a3]	L'։',
+	[0x14a4]	L')',
+	[0x14a5]	L'(',
+	[0x14a6]	L'»',
+	[0x14a7]	L'«',
+	[0x14a8]	L'—',
+	[0x14a9]	L'.',
+	[0x14aa]	L'՝',
+	[0x14ab]	L',',
+	[0x14ac]	L'–',
+	[0x14ad]	L'֊',
+	[0x14ae]	L'…',
+	[0x14af]	L'՜',
+	[0x14b0]	L'՛',
+	[0x14b1]	L'՞',
+	[0x14b2]	L'Ա',
+	[0x14b3]	L'ա',
+	[0x14b4]	L'Բ',
+	[0x14b5]	L'բ',
+	[0x14b6]	L'Գ',
+	[0x14b7]	L'գ',
+	[0x14b8]	L'Դ',
+	[0x14b9]	L'դ',
+	[0x14ba]	L'Ե',
+	[0x14bb]	L'ե',
+	[0x14bc]	L'Զ',
+	[0x14bd]	L'զ',
+	[0x14be]	L'Է',
+	[0x14bf]	L'է',
+	[0x14c0]	L'Ը',
+	[0x14c1]	L'ը',
+	[0x14c2]	L'Թ',
+	[0x14c3]	L'թ',
+	[0x14c4]	L'Ժ',
+	[0x14c5]	L'ժ',
+	[0x14c6]	L'Ի',
+	[0x14c7]	L'ի',
+	[0x14c8]	L'Լ',
+	[0x14c9]	L'լ',
+	[0x14ca]	L'Խ',
+	[0x14cb]	L'խ',
+	[0x14cc]	L'Ծ',
+	[0x14cd]	L'ծ',
+	[0x14ce]	L'Կ',
+	[0x14cf]	L'կ',
+	[0x14d0]	L'Հ',
+	[0x14d1]	L'հ',
+	[0x14d2]	L'Ձ',
+	[0x14d3]	L'ձ',
+	[0x14d4]	L'Ղ',
+	[0x14d5]	L'ղ',
+	[0x14d6]	L'Ճ',
+	[0x14d7]	L'ճ',
+	[0x14d8]	L'Մ',
+	[0x14d9]	L'մ',
+	[0x14da]	L'Յ',
+	[0x14db]	L'յ',
+	[0x14dc]	L'Ն',
+	[0x14dd]	L'ն',
+	[0x14de]	L'Շ',
+	[0x14df]	L'շ',
+	[0x14e0]	L'Ո',
+	[0x14e1]	L'ո',
+	[0x14e2]	L'Չ',
+	[0x14e3]	L'չ',
+	[0x14e4]	L'Պ',
+	[0x14e5]	L'պ',
+	[0x14e6]	L'Ջ',
+	[0x14e7]	L'ջ',
+	[0x14e8]	L'Ռ',
+	[0x14e9]	L'ռ',
+	[0x14ea]	L'Ս',
+	[0x14eb]	L'ս',
+	[0x14ec]	L'Վ',
+	[0x14ed]	L'վ',
+	[0x14ee]	L'Տ',
+	[0x14ef]	L'տ',
+	[0x14f0]	L'Ր',
+	[0x14f1]	L'ր',
+	[0x14f2]	L'Ց',
+	[0x14f3]	L'ց',
+	[0x14f4]	L'Ւ',
+	[0x14f5]	L'ւ',
+	[0x14f6]	L'Փ',
+	[0x14f7]	L'փ',
+	[0x14f8]	L'Ք',
+	[0x14f9]	L'ք',
+	[0x14fa]	L'Օ',
+	[0x14fb]	L'օ',
+	[0x14fc]	L'Ֆ',
+	[0x14fd]	L'ֆ',
+	[0x14fe]	L'’',
+	[0x14ff]	L''',
+	[0x15d0]	L'ა',
+	[0x15d1]	L'ბ',
+	[0x15d2]	L'გ',
+	[0x15d3]	L'დ',
+	[0x15d4]	L'ე',
+	[0x15d5]	L'ვ',
+	[0x15d6]	L'ზ',
+	[0x15d7]	L'თ',
+	[0x15d8]	L'ი',
+	[0x15d9]	L'კ',
+	[0x15da]	L'ლ',
+	[0x15db]	L'მ',
+	[0x15dc]	L'ნ',
+	[0x15dd]	L'ო',
+	[0x15de]	L'პ',
+	[0x15df]	L'ჟ',
+	[0x15e0]	L'რ',
+	[0x15e1]	L'ს',
+	[0x15e2]	L'ტ',
+	[0x15e3]	L'უ',
+	[0x15e4]	L'ფ',
+	[0x15e5]	L'ქ',
+	[0x15e6]	L'ღ',
+	[0x15e7]	L'ყ',
+	[0x15e8]	L'შ',
+	[0x15e9]	L'ჩ',
+	[0x15ea]	L'ც',
+	[0x15eb]	L'ძ',
+	[0x15ec]	L'წ',
+	[0x15ed]	L'ჭ',
+	[0x15ee]	L'ხ',
+	[0x15ef]	L'ჯ',
+	[0x15f0]	L'ჰ',
+	[0x15f1]	L'ჱ',
+	[0x15f2]	L'ჲ',
+	[0x15f3]	L'ჳ',
+	[0x15f4]	L'ჴ',
+	[0x15f5]	L'ჵ',
+	[0x15f6]	L'ჶ',
+	[0x16a2]	L'',
+	[0x16a3]	L'Ẋ',
+	[0x16a5]	L'',
+	[0x16a6]	L'Ĭ',
+	[0x16a7]	L'',
+	[0x16a8]	L'',
+	[0x16a9]	L'Ƶ',
+	[0x16aa]	L'Ǧ',
+	[0x16af]	L'Ɵ',
+	[0x16b2]	L'',
+	[0x16b3]	L'ẋ',
+	[0x16b4]	L'Ǒ',
+	[0x16b5]	L'',
+	[0x16b6]	L'ĭ',
+	[0x16b7]	L'',
+	[0x16b8]	L'',
+	[0x16b9]	L'ƶ',
+	[0x16ba]	L'ǧ',
+	[0x16bd]	L'ǒ',
+	[0x16bf]	L'ɵ',
+	[0x16c6]	L'Ə',
+	[0x16d1]	L'Ḷ',
+	[0x16d2]	L'',
+	[0x16d3]	L'',
+	[0x16e1]	L'ḷ',
+	[0x16e2]	L'',
+	[0x16e3]	L'',
+	[0x16f6]	L'ə',
+	[0x1e9f]	L'̃',
+	[0x1ea0]	L'Ạ',
+	[0x1ea1]	L'ạ',
+	[0x1ea2]	L'Ả',
+	[0x1ea3]	L'ả',
+	[0x1ea4]	L'Ấ',
+	[0x1ea5]	L'ấ',
+	[0x1ea6]	L'Ầ',
+	[0x1ea7]	L'ầ',
+	[0x1ea8]	L'Ẩ',
+	[0x1ea9]	L'ẩ',
+	[0x1eaa]	L'Ẫ',
+	[0x1eab]	L'ẫ',
+	[0x1eac]	L'Ậ',
+	[0x1ead]	L'ậ',
+	[0x1eae]	L'Ắ',
+	[0x1eaf]	L'ắ',
+	[0x1eb0]	L'Ằ',
+	[0x1eb1]	L'ằ',
+	[0x1eb2]	L'Ẳ',
+	[0x1eb3]	L'ẳ',
+	[0x1eb4]	L'Ẵ',
+	[0x1eb5]	L'ẵ',
+	[0x1eb6]	L'Ặ',
+	[0x1eb7]	L'ặ',
+	[0x1eb8]	L'Ẹ',
+	[0x1eb9]	L'ẹ',
+	[0x1eba]	L'Ẻ',
+	[0x1ebb]	L'ẻ',
+	[0x1ebc]	L'Ẽ',
+	[0x1ebd]	L'ẽ',
+	[0x1ebe]	L'Ế',
+	[0x1ebf]	L'ế',
+	[0x1ec0]	L'Ề',
+	[0x1ec1]	L'ề',
+	[0x1ec2]	L'Ể',
+	[0x1ec3]	L'ể',
+	[0x1ec4]	L'Ễ',
+	[0x1ec5]	L'ễ',
+	[0x1ec6]	L'Ệ',
+	[0x1ec7]	L'ệ',
+	[0x1ec8]	L'Ỉ',
+	[0x1ec9]	L'ỉ',
+	[0x1eca]	L'Ị',
+	[0x1ecb]	L'ị',
+	[0x1ecc]	L'Ọ',
+	[0x1ecd]	L'ọ',
+	[0x1ece]	L'Ỏ',
+	[0x1ecf]	L'ỏ',
+	[0x1ed0]	L'Ố',
+	[0x1ed1]	L'ố',
+	[0x1ed2]	L'Ồ',
+	[0x1ed3]	L'ồ',
+	[0x1ed4]	L'Ổ',
+	[0x1ed5]	L'ổ',
+	[0x1ed6]	L'Ỗ',
+	[0x1ed7]	L'ỗ',
+	[0x1ed8]	L'Ộ',
+	[0x1ed9]	L'ộ',
+	[0x1eda]	L'Ớ',
+	[0x1edb]	L'ớ',
+	[0x1edc]	L'Ờ',
+	[0x1edd]	L'ờ',
+	[0x1ede]	L'Ở',
+	[0x1edf]	L'ở',
+	[0x1ee0]	L'Ỡ',
+	[0x1ee1]	L'ỡ',
+	[0x1ee2]	L'Ợ',
+	[0x1ee3]	L'ợ',
+	[0x1ee4]	L'Ụ',
+	[0x1ee5]	L'ụ',
+	[0x1ee6]	L'Ủ',
+	[0x1ee7]	L'ủ',
+	[0x1ee8]	L'Ứ',
+	[0x1ee9]	L'ứ',
+	[0x1eea]	L'Ừ',
+	[0x1eeb]	L'ừ',
+	[0x1eec]	L'Ử',
+	[0x1eed]	L'ử',
+	[0x1eee]	L'Ữ',
+	[0x1eef]	L'ữ',
+	[0x1ef0]	L'Ự',
+	[0x1ef1]	L'ự',
+	[0x1ef2]	L'̀',
+	[0x1ef3]	L'́',
+	[0x1ef4]	L'Ỵ',
+	[0x1ef5]	L'ỵ',
+	[0x1ef6]	L'Ỷ',
+	[0x1ef7]	L'ỷ',
+	[0x1ef8]	L'Ỹ',
+	[0x1ef9]	L'ỹ',
+	[0x1efa]	L'Ơ',
+	[0x1efb]	L'ơ',
+	[0x1efc]	L'Ư',
+	[0x1efd]	L'ư',
+	[0x1efe]	L'̉',
+	[0x1eff]	L'̣',
+	[0x20a0]	L'₠',
+	[0x20a1]	L'₡',
+	[0x20a2]	L'₢',
+	[0x20a3]	L'₣',
+	[0x20a4]	L'₤',
+	[0x20a5]	L'₥',
+	[0x20a6]	L'₦',
+	[0x20a7]	L'₧',
+	[0x20a8]	L'₨',
+	[0x20a9]	L'₩',
+	[0x20aa]	L'₪',
+	[0x20ab]	L'₫',
+	[0x20ac]	L'€',
+
+};
\ No newline at end of file
--- /dev/null
+++ b/sys/src/cmd/vnc/mkfile
@@ -1,0 +1,60 @@
+</$objtype/mkfile
+
+TARG=vncs vncv
+BIN=/$objtype/bin
+
+OFILES=\
+	proto.$O\
+	auth.$O\
+
+SOFILES=\
+	devdraw.$O\
+	devmouse.$O\
+	devcons.$O\
+	screen.$O\
+	exporter.$O\
+	dev.$O\
+	chan.$O\
+	compat.$O\
+	exportfs.$O\
+	kbds.$O\
+	rre.$O\
+	rlist.$O\
+
+COFILES=\
+	draw.$O\
+	kbdv.$O\
+	color.$O\
+	wsys.$O\
+
+HFILES=\
+	vnc.h\
+	screen.h\
+	compat.h\
+	errstr.h\
+	kbd.h\
+	vncv.h\
+	vncs.h\
+
+UPDATE=\
+	mkfile\
+	$HFILES\
+	${OFILES:%.$O=%.c}\
+	${SOFILES:%.$O=%.c}\
+	${COFILES:%.$O=%.c}\
+	${TARG:%=%.c}\
+
+
+default:V:	all
+
+</sys/src/cmd/mkmany
+
+$O.vncs:	$SOFILES
+
+$O.vncv:	$COFILES
+
+errstr.h:	error.h
+	sed 's/extern //;s,;.*/\* (.*) \*/, = "\1";,' < error.h > errstr.h
+
+kbds.$O:	ksym2utf.h
+kbdv.$O:	utf2ksym.h
--- /dev/null
+++ b/sys/src/cmd/vnc/proto.c
@@ -1,0 +1,294 @@
+#include "vnc.h"
+
+#define SHORT(p) (((p)[0]<<8)|((p)[1]))
+#define LONG(p) ((SHORT(p)<<16)|SHORT(p+2))
+
+uchar zero[64];
+
+Vnc*
+vncinit(int fd, int cfd, Vnc *v)
+{
+        if(v == nil)
+		v = mallocz(sizeof(*v), 1);
+	Binit(&v->in, fd, OREAD);
+	Binit(&v->out, fd, OWRITE);
+	v->datafd = fd;
+	v->ctlfd = cfd;
+	return v;
+}
+
+void
+vncterm(Vnc *v)
+{
+	Bterm(&v->out);
+	Bterm(&v->in);
+}
+
+void
+vncflush(Vnc *v)
+{
+	if(Bflush(&v->out) < 0){
+		if(verbose > 1)
+			fprint(2, "hungup while sending flush: %r\n");
+		vnchungup(v);
+	}
+}
+
+uchar
+vncrdchar(Vnc *v)
+{
+	uchar buf[1];
+
+	vncrdbytes(v, buf, 1);
+	return buf[0];
+}
+
+ushort
+vncrdshort(Vnc *v)
+{
+	uchar buf[2];
+
+	vncrdbytes(v, buf, 2);
+	return SHORT(buf);
+}
+
+ulong
+vncrdlong(Vnc *v)
+{
+	uchar buf[4];
+
+	vncrdbytes(v, buf, 4);
+	return LONG(buf);
+}
+
+Point
+vncrdpoint(Vnc *v)
+{
+	Point p;
+
+	p.x = vncrdshort(v);
+	p.y = vncrdshort(v);
+	return p;
+}
+
+Rectangle
+vncrdrect(Vnc *v)
+{
+	Rectangle r;
+
+	r.min.x = vncrdshort(v);
+	r.min.y = vncrdshort(v);
+	r.max.x = r.min.x + vncrdshort(v);
+	r.max.y = r.min.y + vncrdshort(v);
+	return r;
+}
+
+Rectangle
+vncrdcorect(Vnc *v)
+{
+	Rectangle r;
+
+	r.min.x = vncrdchar(v);
+	r.min.y = vncrdchar(v);
+	r.max.x = r.min.x + vncrdchar(v);
+	r.max.y = r.min.y + vncrdchar(v);
+	return r;
+}
+
+void
+vncrdbytes(Vnc *v, void *a, int n)
+{
+	if(Bread(&v->in, a, n) != n){
+		if(verbose > 1)
+			fprint(2, "hungup while reading\n");
+		vnchungup(v);
+	}
+}
+
+Pixfmt
+vncrdpixfmt(Vnc *v)
+{
+	Pixfmt fmt;
+	uchar pad[3];
+
+	fmt.bpp = vncrdchar(v);
+	fmt.depth = vncrdchar(v);
+	fmt.bigendian = vncrdchar(v);
+	fmt.truecolor = vncrdchar(v);
+	fmt.red.max = vncrdshort(v);
+	fmt.green.max = vncrdshort(v);
+	fmt.blue.max = vncrdshort(v);
+	fmt.red.shift = vncrdchar(v);
+	fmt.green.shift = vncrdchar(v);
+	fmt.blue.shift = vncrdchar(v);
+	vncrdbytes(v, pad, 3);
+	return fmt;
+}
+
+char*
+vncrdstring(Vnc *v)
+{
+	ulong len;
+	char *s;
+
+	len = vncrdlong(v);
+	s = malloc(len+1);
+	assert(s != nil);
+
+	vncrdbytes(v, s, len);
+	s[len] = '\0';
+	return s;
+}
+
+/*
+ * on the server side of the negotiation protocol, we read
+ * the client response and then run the negotiated function.
+ * in some cases (e.g., TLS) the negotiated function needs to
+ * use v->datafd directly and be sure that no data has been
+ * buffered away in the Bio.  since we know the client is waiting
+ * for our response, it won't have sent any until we respond.
+ * thus we read the response with vncrdstringx, which goes
+ * behind bio's back.
+ */
+char*
+vncrdstringx(Vnc *v)
+{
+	char tmp[4];
+	char *s;
+	ulong len;
+
+	assert(Bbuffered(&v->in) == 0);
+	if(readn(v->datafd, tmp, 4) != 4){
+		fprint(2, "cannot rdstringx: %r");
+		vnchungup(v);
+	}
+	len = LONG(tmp);
+	s = malloc(len+1);
+	assert(s != nil);
+	if(readn(v->datafd, s, len) != len){
+		fprint(2, "cannot rdstringx len %lud: %r", len);
+		vnchungup(v);
+	}
+	s[len] = '\0';
+	return s;
+}
+
+void
+vncwrstring(Vnc *v, char *s)
+{
+	ulong len;
+
+	len = strlen(s);
+	vncwrlong(v, len);
+	vncwrbytes(v, s, len);
+}
+
+void
+vncwrbytes(Vnc *v, void *a, int n)
+{
+	if(Bwrite(&v->out, a, n) < 0){
+		if(verbose > 1) 
+			fprint(2, "hungup while writing bytes\n");
+		vnchungup(v);
+	}
+}
+
+void
+vncwrlong(Vnc *v, ulong u)
+{
+	uchar buf[4];
+
+	buf[0] = u>>24;
+	buf[1] = u>>16;
+	buf[2] = u>>8;
+	buf[3] = u;
+	vncwrbytes(v, buf, 4);
+}
+
+void
+vncwrshort(Vnc *v, ushort u)
+{
+	uchar buf[2];
+
+	buf[0] = u>>8;
+	buf[1] = u;
+	vncwrbytes(v, buf, 2);
+}
+
+void
+vncwrchar(Vnc *v, uchar c)
+{
+	vncwrbytes(v, &c, 1);
+}
+
+void
+vncwrpixfmt(Vnc *v, Pixfmt *fmt)
+{
+	vncwrchar(v, fmt->bpp);
+	vncwrchar(v, fmt->depth);
+	vncwrchar(v, fmt->bigendian);
+	vncwrchar(v, fmt->truecolor);
+	vncwrshort(v, fmt->red.max);
+	vncwrshort(v, fmt->green.max);
+	vncwrshort(v, fmt->blue.max);
+	vncwrchar(v, fmt->red.shift);
+	vncwrchar(v, fmt->green.shift);
+	vncwrchar(v, fmt->blue.shift);
+	vncwrbytes(v, zero, 3);
+}
+
+void
+vncwrrect(Vnc *v, Rectangle r)
+{
+	vncwrshort(v, r.min.x);
+	vncwrshort(v, r.min.y);
+	vncwrshort(v, r.max.x-r.min.x);
+	vncwrshort(v, r.max.y-r.min.y);
+}
+
+void
+vncwrpoint(Vnc *v, Point p)
+{
+	vncwrshort(v, p.x);
+	vncwrshort(v, p.y);
+}
+
+void
+vnclock(Vnc *v)
+{
+	qlock(v);
+}
+
+void
+vncunlock(Vnc *v)
+{
+	qunlock(v);
+}
+
+void
+hexdump(void *a, int n)
+{
+	uchar *p, *ep;
+
+	p = a;
+	ep = p+n;
+
+	for(; p<ep; p++) 
+		print("%.2ux ", *p);
+	print("\n");
+}
+
+void
+vncgobble(Vnc *v, long n)
+{
+	uchar buf[8192];
+	long m;
+
+	while(n > 0){
+		m = n;
+		if(m > sizeof(buf))
+			m = sizeof(buf);
+		vncrdbytes(v, buf, m);
+		n -= m;
+	}
+}
--- /dev/null
+++ b/sys/src/cmd/vnc/rlist.c
@@ -1,0 +1,286 @@
+#include "vnc.h"
+#include "vncs.h"
+
+static int tot;
+static void rprint(Rlist*);
+
+static void
+growrlist(Rlist *rlist, int n)
+{
+	int old;
+
+	if(rlist->nrect+n <= rlist->maxrect)
+		return;
+
+	old = rlist->maxrect;
+	while(rlist->nrect+n > rlist->maxrect){
+		if(rlist->maxrect == 0)
+			rlist->maxrect = 16;
+		else
+			rlist->maxrect *= 2;
+	}
+
+	tot += rlist->maxrect - old;
+	if(tot > 10000)
+		sysfatal("too many rectangles");
+
+	rlist->rect = realloc(rlist->rect, rlist->maxrect*sizeof(rlist->rect[0]));
+	if(rlist->rect == nil)
+		sysfatal("realloc failed in growrlist");
+}
+
+static void
+rappend(Rlist *rl, Rectangle r)
+{
+	growrlist(rl, 1);
+	rl->rect[rl->nrect++] = r;
+}
+
+/* remove rectangle i from the list */
+static int
+rtrim(Rlist *r, int i)
+{
+	if(i < 0 || i >= r->nrect)
+		return 0;
+	if(i == r->nrect-1){
+		r->nrect--;
+		return 1;
+	}
+	r->rect[i] = r->rect[--r->nrect];
+	return 1;
+}
+
+static int
+rectadjacent(Rectangle r, Rectangle s)
+{
+	return r.min.x<=s.max.x && s.min.x<=r.max.x &&
+	       r.min.y<=s.max.y && s.min.y<=r.max.y;
+}
+
+/*
+ * If s shares three edges with r, compute the
+ * rectangle r - s and return 1.
+ * Else return 0.
+ */
+static int
+rectubr(Rectangle *r, Rectangle s)
+{
+	if(r->min.y==s.min.y && r->max.y==s.max.y){
+		if(r->min.x == s.min.x){
+			r->min.x = s.max.x;
+			assert(r->max.x > r->min.x);
+			return 1;
+		}
+		if(r->max.x == s.max.x){
+			r->max.x = s.min.x;
+			assert(r->max.x > r->min.x);
+			return 1;
+		}
+	}
+	if(r->min.x==s.min.x && r->max.x==s.max.x){
+		if(r->min.y == s.min.y){
+			r->min.y = s.max.y;
+			assert(r->max.y > r->min.y);
+			return 1;
+		}
+		if(r->max.y == s.max.y){
+			r->max.y = s.min.y;
+			assert(r->max.y > r->min.y);
+			return 1;
+		}
+	}
+	return 0;
+}
+
+/*
+ * If s is a corner of r, remove s from r, yielding
+ * two rectangles r and rr.  R holds the part with
+ * smaller coordinates.
+ */
+static int
+rectcornersubr(Rectangle *r, Rectangle s, Rectangle *rr)
+{
+#	define UPRIGHT(r) Pt((r).max.x, (r).min.y)
+#	define LOWLEFT(r) Pt((r).min.x, (r).max.y)
+
+	*rr = *r;
+
+	if(s.min.x == r->min.x){  
+		if(s.min.y == r->min.y){  // upper left
+			*rr = Rpt(UPRIGHT(s), r->max);
+			*r = Rpt(LOWLEFT(s), LOWLEFT(*rr));
+			return 1;
+		}
+		if(s.max.y == r->max.y){ // lower left
+			*rr = Rpt(Pt(s.max.x, r->min.y), r->max);
+			*r = Rpt(r->min, UPRIGHT(s));
+			return 1;
+		}
+	}
+	if(s.max.x == r->max.x){
+		if(s.max.y == r->max.y){ // lower right
+			*rr = Rpt(Pt(s.min.x, r->min.y), UPRIGHT(s));
+			*r = Rpt(r->min, LOWLEFT(s));
+			return 1;
+		}
+		if(s.min.y == r->min.y){ // upper right
+			*rr = Rpt(LOWLEFT(s), r->max);
+			*r = Rpt(r->min, LOWLEFT(*rr));
+			return 1;
+		}
+	}
+	return 0;
+}
+
+/*
+ * If s is a band cutting r into two pieces, set r to one piece
+ * and rr to the other.
+ */
+static int
+recttridesubr(Rectangle *nr, Rectangle s, Rectangle *rr)
+{
+	*rr = *nr;
+	if((nr->min.x == s.min.x && nr->max.x == s.max.x) &&
+	    (nr->min.y < s.min.y && s.max.y < nr->max.y)){
+		nr->max.y = s.min.y;
+		rr->min.y = s.max.y;
+		return 1;
+	}
+
+	if((nr->min.y == s.min.y && nr->max.y == s.max.y) &&
+	    (nr->min.x < s.min.x && s.max.x < nr->max.x)){
+		nr->max.x = s.min.x;
+		rr->min.x = s.max.x;
+		return 1;
+	}
+	return 0;
+}
+
+void
+addtorlist(Rlist *rlist, Rectangle r)
+{
+	int i, j;
+	Rectangle ir, cr, rr;
+	Rlist tmp;
+
+	if(r.min.x >= r.max.x || r.min.y >= r.max.y)
+		return;
+
+	memset(&tmp, 0, sizeof tmp);
+	rappend(&tmp, r);
+	
+	if(verbose > 5)
+		fprint(2, "region union add %R:\n", r);
+
+	combinerect(&rlist->bbox, r); // must do this first
+	for(j = 0; j < tmp.nrect; j++){
+		r = tmp.rect[j];
+
+		for(i=0; i < rlist->nrect; i++){
+			ir = rlist->rect[i];
+
+			if(verbose > 5)
+				fprint(2, "checking %R against %R\n", r, ir);
+			if(!rectadjacent(ir, r))
+				continue;
+
+			/* r is covered by ir? */
+			if(rectinrect(r, ir))
+				break;
+
+			/* r covers ir? */
+ 			if(rectinrect(ir, r)){
+				rtrim(rlist, i);
+				i--;
+				continue;
+			}
+
+			/* aligned and overlapping? */
+			if((ir.min.y == r.min.y && ir.max.y == r.max.y) ||
+		    	(ir.min.x == r.min.x && ir.max.x == r.max.x)){
+				combinerect(&r, ir);
+				rtrim(rlist, i);
+				i--;
+				continue;
+			}
+
+			/* not aligned */ 
+			if(verbose > 5)
+				fprint(2, "break up rect %R and %R\n", ir, r);
+			/* 2->2 breakup */
+			cr = ir;
+			if (!rectclip(&cr, r))	/* share only one point */
+				continue;
+
+			if(rectubr(&r, cr))
+				continue;
+
+			if(rectubr(&rlist->rect[i], cr))
+				continue;
+
+			/* 2 -> 3 breakup */
+			/* stride across */
+			if(recttridesubr(&r, cr, &rr)){
+				rappend(&tmp, rr);
+				continue;
+			}
+
+			/* corner overlap */
+			if(rectcornersubr(&r, cr, &rr)){
+				rappend(&tmp, rr);
+				continue;
+			}
+			abort();
+		}
+		if(i == rlist->nrect)
+			rappend(rlist, r);
+	}
+	freerlist(&tmp);
+	if(verbose > 5)
+		rprint(rlist);
+}
+
+void
+freerlist(Rlist *r)
+{
+	free(r->rect);
+	tot -= r->maxrect;
+	r->nrect = 0;
+	r->maxrect = 0;
+	r->rect = nil;
+}
+
+static void
+rprint(Rlist *r)
+{
+	int i;
+
+	fprint(2, "rlist %p:", r);
+	for(i=0; i<r->nrect; i++)
+		fprint(2, " %R", r->rect[i]);
+	fprint(2, "\n");
+}
+
+
+#ifdef REGION_DEBUG
+
+int verbose = 10;
+
+void main(int argc, char * argv[])
+{
+	Rectangle r1 = Rect(0, 0, 300, 200);
+	Rectangle r2 = Rect(100, 100, 400, 300);
+	Rectangle r3 = Rect(200, 100, 500, 300);
+	Region reg;
+
+	if(initdraw(0, 0, "vncviewer") < 0){
+		fprint(2, "%s: initdraw failed: %r\n", argv[0]);
+		exits("initdraw");
+	}
+	region_init(&reg);
+	region_union(&reg, r1, r1);
+	region_union(&reg, r2, r2);
+	region_union(&reg, r3, r3);
+}
+
+#endif
--- /dev/null
+++ b/sys/src/cmd/vnc/rre.c
@@ -1,0 +1,549 @@
+#include "vnc.h"
+#include "vncs.h"
+
+/*
+ * rise and run length encoding, aka rre.
+ *
+ * the pixel contained in r are subdivided into
+ * rectangles of uniform color, each of which
+ * is encoded by <color, x, y, w, h>.
+ *
+ * use raw encoding if it's shorter.
+ *
+ * for compact rre, use limited size rectangles,
+ * which are shorter to encode and therefor give better compression.
+ *
+ * hextile encoding uses rre encoding on at most 16x16 rectangles tiled
+ * across and then down the screen.
+ */
+static int	encrre(uchar *raw, int stride, int w, int h, int back, int pixb, uchar *buf, int maxr, uchar *done, int (*eqpix)(uchar*, int, int), uchar *(putr)(uchar*, uchar*, int, int, int, int, int, int));
+static int	eqpix16(uchar *raw, int p1, int p2);
+static int	eqpix32(uchar *raw, int p1, int p2);
+static int	eqpix8(uchar *raw, int p1, int p2);
+static int	findback(uchar *raw, int stride, int w, int h, int (*eqpix)(uchar*, int, int));
+static uchar*	putcorre(uchar *buf, uchar *raw, int p, int pixb, int x, int y, int w, int h);
+static uchar*	putrre(uchar *buf, uchar *raw, int p, int pixb, int x, int y, int w, int h);
+static void	putpix(Vnc *v, uchar *raw, int p, int pixb);
+static int	hexcolors(uchar *raw, int stride, int w, int h, int (*eqpix)(uchar*, int, int), int back, int *fore);
+static uchar	*puthexfore(uchar *buf, uchar*, int, int, int x, int y, int w, int h);
+static uchar	*puthexcol(uchar *buf, uchar*, int, int, int x, int y, int w, int h);
+static void	sendtraw(Vnc *v, uchar *raw, int pixb, int stride, int w, int h);
+
+/*
+ * default routine, no compression, just the pixels
+ */
+int
+sendraw(Vncs *v, Rectangle r)
+{
+	int pixb, stride;
+	uchar *raw;
+
+	if(!rectinrect(r, v->image->r))
+		sysfatal("sending bad rectangle");
+
+	pixb = v->bpp >> 3;
+	if((pixb << 3) != v->bpp)
+		sysfatal("bad pixel math in sendraw");
+	stride = v->image->width*sizeof(ulong);
+	if(((stride / pixb) * pixb) != stride)
+		sysfatal("bad pixel math in sendraw");
+	stride /= pixb;
+
+	raw = byteaddr(v->image, r.min);
+
+	vncwrrect(v, r);
+	vncwrlong(v, EncRaw);
+	sendtraw(v, raw, pixb, stride, Dx(r), Dy(r));
+	return 1;
+}
+
+int
+countraw(Vncs*, Rectangle)
+{
+	return 1;
+}
+
+/*
+ * grab the image for the entire rectangle,
+ * then encode each tile
+ */
+int
+sendhextile(Vncs *v, Rectangle r)
+{
+	uchar *(*putr)(uchar*, uchar*, int, int, int, int, int, int);
+	int (*eq)(uchar*, int, int);
+	uchar *raw, *buf, *done, *traw;
+	int w, h, stride, pixb, pixlg, nr, bpr, back, fore;
+	int sy, sx, th, tw, oback, ofore, k, nc;
+
+	h = Dy(r);
+	w = Dx(r);
+	if(h == 0 || w == 0 || !rectinrect(r, v->image->r))
+		sysfatal("bad rectangle %R in sendhextile %R", r, v->image->r);
+
+	switch(v->bpp){
+	case  8:	pixlg = 0;	eq = eqpix8;	break;
+	case 16:	pixlg = 1;	eq = eqpix16;	break;
+	case 32:	pixlg = 2;	eq = eqpix32;	break;
+	default:
+		sendraw(v, r);
+		return 1;
+	}
+	pixb = 1 << pixlg;
+	stride = v->image->width*sizeof(ulong);
+	if(((stride >> pixlg) << pixlg) != stride){
+		sendraw(v, r);
+		return 1;
+	}
+	stride >>= pixlg;
+
+	buf = malloc(HextileDim * HextileDim * pixb);
+	done = malloc(HextileDim * HextileDim);
+	if(buf == nil || done == nil){
+		free(buf);
+		free(done);
+		sendraw(v, r);
+		return 1;
+	}
+	raw = byteaddr(v->image, r.min);
+
+	vncwrrect(v, r);
+	vncwrlong(v, EncHextile);
+	oback = -1;
+	ofore = -1;
+	for(sy = 0; sy < h; sy += HextileDim){
+		th = h - sy;
+		if(th > HextileDim)
+			th = HextileDim;
+		for(sx = 0; sx < w; sx += HextileDim){
+			tw = w - sx;
+			if(tw > HextileDim)
+				tw = HextileDim;
+
+			traw = raw + ((sy * stride + sx) << pixlg);
+
+			back = findback(traw, stride, tw, th, eq);
+			nc = hexcolors(traw, stride, tw, th, eq, back, &fore);
+			k = 0;
+			if(oback < 0 || !(*eq)(raw, back + ((traw - raw) >> pixlg), oback))
+				k |= HextileBack;
+			if(nc == 1){
+				vncwrchar(v, k);
+				if(k & HextileBack){
+					oback = back + ((traw - raw) >> pixlg);
+					putpix(v, raw, oback, pixb);
+				}
+				continue;
+			}
+			k |= HextileRects;
+			if(nc == 2){
+				putr = puthexfore;
+				bpr = 2;
+				if(ofore < 0 || !(*eq)(raw, fore + ((traw - raw) >> pixlg), ofore))
+					k |= HextileFore;
+			}else{
+				putr = puthexcol;
+				bpr = 2 + pixb;
+				k |= HextileCols;
+				/* stupid vnc clients smash foreground in this case */
+				ofore = -1;
+			}
+
+			nr = th * tw << pixlg;
+			if(k & HextileBack)
+				nr -= pixb;
+			if(k & HextileFore)
+				nr -= pixb;
+			nr /= bpr;
+			memset(done, 0, HextileDim * HextileDim);
+			nr = encrre(traw, stride, tw, th, back, pixb, buf, nr, done, eq, putr);
+			if(nr < 0){
+				vncwrchar(v, HextileRaw);
+				sendtraw(v, traw, pixb, stride, tw, th);
+				/* stupid vnc clients smash colors in this case */
+				ofore = -1;
+				oback = -1;
+			}else{
+				vncwrchar(v, k);
+				if(k & HextileBack){
+					oback = back + ((traw - raw) >> pixlg);
+					putpix(v, raw, oback, pixb);
+				}
+				if(k & HextileFore){
+					ofore = fore + ((traw - raw) >> pixlg);
+					putpix(v, raw, ofore, pixb);
+				}
+				vncwrchar(v, nr);
+				vncwrbytes(v, buf, nr * bpr);
+			}
+		}
+	}
+	free(buf);
+	free(done);
+	return 1;
+}
+
+int
+counthextile(Vncs*, Rectangle)
+{
+	return 1;
+}
+
+static int
+hexcolors(uchar *raw, int stride, int w, int h, int (*eqpix)(uchar*, int, int), int back, int *rfore)
+{
+	int s, es, sx, esx, fore;
+
+	*rfore = -1;
+	fore = -1;
+	es = stride * h;
+	for(s = 0; s < es; s += stride){
+		esx = s + w;
+		for(sx = s; sx < esx; sx++){
+			if((*eqpix)(raw, back, sx))
+				continue;
+			if(fore < 0){
+				fore = sx;
+				*rfore = fore;
+			}else if(!(*eqpix)(raw, fore, sx))
+				return 3;
+		}
+	}
+
+	if(fore < 0)
+		return 1;
+	return 2;
+}
+
+static uchar*
+puthexcol(uchar *buf, uchar *raw, int p, int pixb, int x, int y, int w, int h)
+{
+	raw += p * pixb;
+	while(pixb--)
+		*buf++ = *raw++;
+	*buf++ = (x << 4) | y;
+	*buf++ = (w - 1) << 4 | (h - 1);
+	return buf;
+}
+
+static uchar*
+puthexfore(uchar *buf, uchar*, int, int, int x, int y, int w, int h)
+{
+	*buf++ = (x << 4) | y;
+	*buf++ = (w - 1) << 4 | (h - 1);
+	return buf;
+}
+
+static void
+sendtraw(Vnc *v, uchar *raw, int pixb, int stride, int w, int h)
+{
+	int y;
+
+	for(y = 0; y < h; y++)
+		vncwrbytes(v, &raw[y * stride * pixb], w * pixb);
+}
+
+static int
+rrerects(Rectangle r, int split)
+{
+	return ((Dy(r) + split - 1) / split) * ((Dx(r) + split - 1) / split);
+}
+
+enum
+{
+	MaxCorreDim	= 48,
+	MaxRreDim	= 64,
+};
+
+int
+countrre(Vncs*, Rectangle r)
+{
+	return rrerects(r, MaxRreDim);
+}
+
+int
+countcorre(Vncs*, Rectangle r)
+{
+	return rrerects(r, MaxCorreDim);
+}
+
+static int
+_sendrre(Vncs *v, Rectangle r, int split, int compact)
+{
+	uchar *raw, *buf, *done;
+	int w, h, stride, pixb, pixlg, nraw, nr, bpr, back, totr;
+	int (*eq)(uchar*, int, int);
+
+	totr = 0;
+	h = Dy(r);
+	while(h > split){
+		h = r.max.y;
+		r.max.y = r.min.y + split;
+		totr += _sendrre(v, r, split, compact);
+		r.min.y = r.max.y;
+		r.max.y = h;
+		h = Dy(r);
+	}
+	w = Dx(r);
+	while(w > split){
+		w = r.max.x;
+		r.max.x = r.min.x + split;
+		totr += _sendrre(v, r, split, compact);
+		r.min.x = r.max.x;
+		r.max.x = w;
+		w = Dx(r);
+	}
+	if(h == 0 || w == 0 || !rectinrect(r, v->image->r))
+		sysfatal("bad rectangle in sendrre");
+
+	switch(v->bpp){
+	case  8:	pixlg = 0;	eq = eqpix8;	break;
+	case 16:	pixlg = 1;	eq = eqpix16;	break;
+	case 32:	pixlg = 2;	eq = eqpix32;	break;
+	default:
+		sendraw(v, r);
+		return totr + 1;
+	}
+	pixb = 1 << pixlg;
+	stride = v->image->width*sizeof(ulong);
+	if(((stride >> pixlg) << pixlg) != stride){
+		sendraw(v, r);
+		return totr + 1;
+	}
+	stride >>= pixlg;
+
+	nraw = w * pixb * h;
+	buf = malloc(nraw);
+	done = malloc(w * h);
+	if(buf == nil || done == nil){
+		free(buf);
+		free(done);
+		sendraw(v, r);
+		return totr + 1;
+	}
+	memset(done, 0, w * h);
+
+	raw = byteaddr(v->image, r.min);
+
+	if(compact)
+		bpr = 4 * 1 + pixb;
+	else
+		bpr = 4 * 2 + pixb;
+	nr = (nraw - 4 - pixb) / bpr;
+	back = findback(raw, stride, w, h, eq);
+	if(compact)
+		nr = encrre(raw, stride, w, h, back, pixb, buf, nr, done, eq, putcorre);
+	else
+		nr = encrre(raw, stride, w, h, back, pixb, buf, nr, done, eq, putrre);
+	if(nr < 0){
+		vncwrrect(v, r);
+		vncwrlong(v, EncRaw);
+		sendtraw(v, raw, pixb, stride, w, h);
+	}else{
+		vncwrrect(v, r);
+		if(compact)
+			vncwrlong(v, EncCorre);
+		else
+			vncwrlong(v, EncRre);
+		vncwrlong(v, nr);
+		putpix(v, raw, back, pixb);
+		vncwrbytes(v, buf, nr * bpr);
+	}
+	free(buf);
+	free(done);
+
+	return totr + 1;
+}
+
+int
+sendrre(Vncs *v, Rectangle r)
+{
+	return _sendrre(v, r, MaxRreDim, 0);
+}
+
+int
+sendcorre(Vncs *v, Rectangle r)
+{
+	return _sendrre(v, r, MaxCorreDim, 1);
+}
+
+static int
+encrre(uchar *raw, int stride, int w, int h, int back, int pixb, uchar *buf,
+	int maxr, uchar *done, int (*eqpix)(uchar*, int, int),
+	uchar *(*putr)(uchar*, uchar*, int, int, int, int, int, int))
+{
+	int s, es, sx, esx, sy, syx, esyx, rh, rw, y, nr, dsy, dp;
+
+	es = stride * h;
+	y = 0;
+	nr = 0;
+	dp = 0;
+	for(s = 0; s < es; s += stride){
+		esx = s + w;
+		for(sx = s; sx < esx; ){
+			rw = done[dp];
+			if(rw){
+				sx += rw;
+				dp += rw;
+				continue;
+			}
+			if((*eqpix)(raw, back, sx)){
+				sx++;
+				dp++;
+				continue;
+			}
+
+			if(nr >= maxr)
+				return -1;
+
+			/*
+			 * find the tallest maximally wide uniform colored rectangle
+			 * with p at the upper left.
+			 * this isn't an optimal parse, but it's pretty good for text
+			 */
+			rw = esx - sx;
+			rh = 0;
+			for(sy = sx; sy < es; sy += stride){
+				if(!(*eqpix)(raw, sx, sy))
+					break;
+				esyx = sy + rw;
+				for(syx = sy + 1; syx < esyx; syx++){
+					if(!(*eqpix)(raw, sx, syx)){
+						if(sy == sx)
+							break;
+						goto breakout;
+					}
+				}
+				if(sy == sx)
+					rw = syx - sy;
+				rh++;
+			}
+		breakout:;
+
+			nr++;
+			buf = (*putr)(buf, raw, sx, pixb, sx - s, y, rw, rh);
+
+			/*
+			 * mark all pixels done
+			 */
+			dsy = dp;
+			while(rh--){
+				esyx = dsy + rw;
+				for(syx = dsy; syx < esyx; syx++)
+					done[syx] = esyx - syx;
+				dsy += w;
+			}
+
+			sx += rw;
+			dp += rw;
+		}
+		y++;
+	}
+	return nr;
+}
+
+/*
+ * estimate the background color
+ * by finding the most frequent character in a small sample
+ */
+static int
+findback(uchar *raw, int stride, int w, int h, int (*eqpix)(uchar*, int, int))
+{
+	enum{
+		NCol = 6,
+		NExamine = 4
+	};
+	int ccount[NCol], col[NCol], i, wstep, hstep, x, y, pix, c, max, maxc;
+
+	wstep = w / NExamine;
+	if(wstep < 1)
+		wstep = 1;
+	hstep = h / NExamine;
+	if(hstep < 1)
+		hstep = 1;
+
+	for(i = 0; i< NCol; i++)
+		ccount[i] = 0;
+	for(y = 0; y < h; y += hstep){
+		for(x = 0; x < w; x += wstep){
+			pix = y * stride + x;
+			for(i = 0; i < NCol; i++){
+				if(ccount[i] == 0){
+					ccount[i] = 1;
+					col[i] = pix;
+					break;
+				}
+				if((*eqpix)(raw, pix, col[i])){
+					ccount[i]++;
+					break;
+				}
+			}
+		}
+	}
+	maxc = ccount[0];
+	max = 0;
+	for(i = 1; i < NCol; i++){
+		c = ccount[i];
+		if(!c)
+			break;
+		if(c > maxc){
+			max = i;
+			maxc = c;
+		}
+	}
+	return col[max];
+}
+
+static uchar*
+putrre(uchar *buf, uchar *raw, int p, int pixb, int x, int y, int w, int h)
+{
+	raw += p * pixb;
+	while(pixb--)
+		*buf++ = *raw++;
+	*buf++ = x >> 8;
+	*buf++ = x;
+	*buf++ = y >> 8;
+	*buf++ = y;
+	*buf++ = w >> 8;
+	*buf++ = w;
+	*buf++ = h >> 8;
+	*buf++ = h;
+	return buf;
+}
+
+static uchar*
+putcorre(uchar *buf, uchar *raw, int p, int pixb, int x, int y, int w, int h)
+{
+	raw += p * pixb;
+	while(pixb--)
+		*buf++ = *raw++;
+	*buf++ = x;
+	*buf++ = y;
+	*buf++ = w;
+	*buf++ = h;
+	return buf;
+}
+
+static int
+eqpix8(uchar *raw, int p1, int p2)
+{
+	return raw[p1] == raw[p2];
+}
+
+static int
+eqpix16(uchar *raw, int p1, int p2)
+{
+	return ((ushort*)raw)[p1] == ((ushort*)raw)[p2];
+}
+
+static int
+eqpix32(uchar *raw, int p1, int p2)
+{
+	return ((ulong*)raw)[p1] == ((ulong*)raw)[p2];
+}
+
+static void
+putpix(Vnc *v, uchar *raw, int p, int pixb)
+{
+	vncwrbytes(v, raw + p * pixb, pixb);
+}
--- /dev/null
+++ b/sys/src/cmd/vnc/screen.c
@@ -1,0 +1,347 @@
+#include	<u.h>
+#include	<libc.h>
+#include	"compat.h"
+#include	"kbd.h"
+#include	"error.h"
+
+#define	Image	IMAGE
+#include	<draw.h>
+#include	<memdraw.h>
+#include	<cursor.h>
+#include	"screen.h"
+
+enum
+{
+	CURSORDIM = 16
+};
+
+Memimage	*gscreen;
+Point		ZP;
+int		cursorver;
+Point		cursorpos;
+
+static Memimage		*back;
+static Memimage		*conscol;
+static Memimage		*curscol;
+static Point		curpos;
+static Memsubfont	*memdefont;
+static Rectangle	flushr;
+static Rectangle	window;
+static int		h;
+static int		w;
+
+static Rectangle	cursorr;
+static Point		offscreen;
+static uchar		cursset[CURSORDIM*CURSORDIM/8];
+static uchar		cursclr[CURSORDIM*CURSORDIM/8];
+static int		cursdrawvers = -1;
+static Memimage		*cursorset;
+static Memimage		*cursorclear;
+static Cursor		screencursor;
+
+void
+screeninit(int x, int y, char *chanstr)
+{
+	char buf[128];
+	Rectangle r;
+	int chan;
+
+	cursorver = 0;
+
+	memimageinit();
+	chan = strtochan(chanstr);
+	if(chan == 0)
+		error("bad screen channel string");
+
+	r = Rect(0, 0, x, y);
+	gscreen = allocmemimage(r, chan);
+	if(gscreen == nil){
+		snprint(buf, sizeof buf, "can't allocate screen image: %r");
+		error(buf);
+	}
+
+	offscreen = Pt(x + 100, y + 100);
+	cursorr = Rect(0, 0, CURSORDIM, CURSORDIM);
+	cursorset = allocmemimage(cursorr, GREY8);
+	cursorclear = allocmemimage(cursorr, GREY1);
+	if(cursorset == nil || cursorclear == nil){
+		freememimage(gscreen);
+		freememimage(cursorset);
+		freememimage(cursorclear);
+		gscreen = nil;
+		cursorset = nil;
+		cursorclear = nil;
+		snprint(buf, sizeof buf, "can't allocate cursor images: %r");
+		error(buf);
+	}
+
+	/* a lot of work to get a grey color */
+	curscol = allocmemimage(Rect(0,0,1,1), RGBA32);
+	curscol->flags |= Frepl;
+	curscol->clipr = gscreen->r;
+	memfillcolor(curscol, 0xff0000ff);
+
+	screenwin();
+
+	setcursor(&arrow);
+}
+
+void
+screenwin(void)
+{
+	Point p;
+	char *greet;
+	Memimage *grey;
+
+	qlock(&drawlock);
+	back = memwhite;
+	conscol = memblack;
+	memfillcolor(gscreen, 0x888844FF);
+	
+	memdefont = getmemdefont();
+	h = memdefont->height;
+
+	window = insetrect(gscreen->clipr, 20);
+	memimagedraw(gscreen, window, memblack, ZP, memopaque, ZP, S);
+	window = insetrect(window, 4);
+	memimagedraw(gscreen, window, memwhite, ZP, memopaque, ZP, S);
+
+	/* a lot of work to get a grey color */
+	grey = allocmemimage(Rect(0,0,1,1), CMAP8);
+	grey->flags |= Frepl;
+	grey->clipr = gscreen->r;
+	memfillcolor(grey, 0xAAAAAAFF);
+	memimagedraw(gscreen, Rect(window.min.x, window.min.y,
+			window.max.x, window.min.y+h+5+6), grey, ZP, nil, ZP, S);
+	freememimage(grey);
+	window = insetrect(window, 5);
+
+	greet = " Plan 9 Console ";
+	p = addpt(window.min, Pt(10, 0));
+	memimagestring(gscreen, p, conscol, ZP, memdefont, greet);
+	window.min.y += h+6;
+	curpos = window.min;
+	window.max.y = window.min.y+((window.max.y-window.min.y)/h)*h;
+	flushmemscreen(gscreen->r);
+	qunlock(&drawlock);
+}
+
+Memdata*
+attachscreen(Rectangle* r, ulong* chan, int* d, int* width, int *softscreen)
+{
+	*r = gscreen->clipr;
+	*d = gscreen->depth;
+	*chan = gscreen->chan;
+	*width = gscreen->width;
+	*softscreen = 1;
+
+	gscreen->data->ref++;
+	return gscreen->data;
+}
+
+void
+getcolor(ulong , ulong* pr, ulong* pg, ulong* pb)
+{
+	*pr = 0;
+	*pg = 0;
+	*pb = 0;
+}
+
+int
+setcolor(ulong , ulong , ulong , ulong )
+{
+	return 0;
+}
+
+/*
+ * called with cursor unlocked, drawlock locked
+ */
+void
+cursordraw(Memimage *dst, Rectangle r)
+{
+	static uchar set[CURSORDIM*CURSORDIM], clr[CURSORDIM*CURSORDIM/8];
+	static int ver = -1;
+	int i, j, n;
+
+	lock(&cursor);
+	if(ver != cursorver){
+		n = 0;
+		for(i = 0; i < CURSORDIM*CURSORDIM/8; i += CURSORDIM/8){
+			for(j = 0; j < CURSORDIM; j++){
+				if(cursset[i + (j >> 3)] & (1 << (7 - (j & 7))))
+					set[n] = 0xaa;
+				else
+					set[n] = 0;
+				n++;
+			}
+		}
+		memmove(clr, cursclr, CURSORDIM*CURSORDIM/8);
+		ver = cursorver;
+		unlock(&cursor);
+		loadmemimage(cursorset, cursorr, set, CURSORDIM*CURSORDIM);
+		loadmemimage(cursorclear, cursorr, clr, CURSORDIM*CURSORDIM/8);
+	}else
+		unlock(&cursor);
+	memimagedraw(dst, r, memwhite, ZP, cursorclear, ZP, SoverD);
+	memimagedraw(dst, r, curscol, ZP, cursorset, ZP, SoverD);
+}
+
+/*
+ * called with cursor locked, drawlock possibly unlocked
+ */
+Rectangle
+cursorrect(void)
+{
+	Rectangle r;
+
+	r.min.x = cursorpos.x + cursor.offset.x;
+	r.min.y = cursorpos.y + cursor.offset.y;
+	r.max.x = r.min.x + CURSORDIM;
+	r.max.y = r.min.y + CURSORDIM;
+	return r;
+}
+
+/*
+ * called with cursor locked, drawlock possibly unlocked
+ */
+void
+setcursor(Cursor* curs)
+{
+	cursorver++;
+	memmove(cursset, curs->set, CURSORDIM*CURSORDIM/8);
+	memmove(cursclr, curs->clr, CURSORDIM*CURSORDIM/8);
+}
+
+void
+cursoron(void)
+{
+	cursorpos = mousexy();
+}
+
+void
+cursoroff(void)
+{
+	cursorpos = offscreen;
+}
+
+void
+blankscreen(int blank)
+{
+	USED(blank);
+}
+
+static void
+screenflush(void)
+{
+	flushmemscreen(flushr);
+	flushr = Rect(10000, 10000, -10000, -10000);
+}
+
+static void
+addflush(Rectangle r)
+{
+	if(flushr.min.x >= flushr.max.x)
+		flushr = r;
+	else
+		combinerect(&flushr, r);
+}
+
+static void
+scroll(void)
+{
+	int o;
+	Point p;
+	Rectangle r;
+
+	o = 8*h;
+	r = Rpt(window.min, Pt(window.max.x, window.max.y-o));
+	p = Pt(window.min.x, window.min.y+o);
+	memimagedraw(gscreen, r, gscreen, p, nil, p, S);
+	r = Rpt(Pt(window.min.x, window.max.y-o), window.max);
+	memimagedraw(gscreen, r, back, ZP, nil, ZP, S);
+	flushmemscreen(gscreen->clipr);
+
+	curpos.y -= o;
+}
+
+static void
+screenputc(char *buf)
+{
+	Point p;
+	int w, pos;
+	Rectangle r;
+	static int *xp;
+	static int xbuf[256];
+
+	if(xp < xbuf || xp >= &xbuf[sizeof(xbuf)])
+		xp = xbuf;
+
+	switch(buf[0]){
+	case '\n':
+		if(curpos.y+h >= window.max.y)
+			scroll();
+		curpos.y += h;
+		screenputc("\r");
+		break;
+	case '\r':
+		xp = xbuf;
+		curpos.x = window.min.x;
+		break;
+	case '\t':
+		p = memsubfontwidth(memdefont, " ");
+		w = p.x;
+		*xp++ = curpos.x;
+		pos = (curpos.x-window.min.x)/w;
+		pos = 8-(pos%8);
+		r = Rect(curpos.x, curpos.y, curpos.x+pos*w, curpos.y + h);
+		memimagedraw(gscreen, r, back, back->r.min, memopaque, ZP, S);
+		addflush(r);
+		curpos.x += pos*w;
+		break;
+	case '\b':
+		if(xp <= xbuf)
+			break;
+		xp--;
+		r = Rect(*xp, curpos.y, curpos.x, curpos.y + h);
+		memimagedraw(gscreen, r, back, back->r.min, memopaque, ZP, S);
+		addflush(r);
+		curpos.x = *xp;
+		break;
+	case '\0':
+		break;
+	default:
+		p = memsubfontwidth(memdefont, buf);
+		w = p.x;
+
+		if(curpos.x >= window.max.x-w)
+			screenputc("\n");
+
+		*xp++ = curpos.x;
+		r = Rect(curpos.x, curpos.y, curpos.x+w, curpos.y + h);
+		memimagedraw(gscreen, r, back, back->r.min, memopaque, ZP, S);
+		memimagestring(gscreen, curpos, conscol, ZP, memdefont, buf);
+		addflush(r);
+		curpos.x += w;
+	}
+}
+
+void
+screenputs(char *s, int n)
+{
+	static char rb[UTFmax+1];
+	static int nrb;
+	char *e;
+
+	qlock(&drawlock);
+	e = s + n;
+	while(s < e){
+		rb[nrb++] = *s++;
+		if(nrb >= UTFmax || fullrune(rb, nrb)){
+			rb[nrb] = 0;
+			screenputc(rb);
+			nrb = 0;
+		}
+	}
+	screenflush();
+	qunlock(&drawlock);
+}
--- /dev/null
+++ b/sys/src/cmd/vnc/screen.h
@@ -1,0 +1,37 @@
+typedef struct Cursor Cursor;
+typedef struct Cursorinfo Cursorinfo;
+struct Cursorinfo {
+	Cursor;
+	Lock;
+};
+
+extern Cursorinfo	cursor;
+extern Cursor		arrow;
+extern Memimage		*gscreen;
+extern int		cursorver;
+extern Point		cursorpos;
+
+void		mouseresize(void);
+Point 		mousexy(void);
+void		cursoron(void);
+void		cursoroff(void);
+void		setcursor(Cursor*);
+void		flushmemscreen(Rectangle r);
+Rectangle	cursorrect(void);
+void		cursordraw(Memimage *dst, Rectangle r);
+
+extern QLock	drawlock;
+void		drawactive(int);
+void		getcolor(ulong, ulong*, ulong*, ulong*);
+int		setcolor(ulong, ulong, ulong, ulong);
+#define		TK2SEC(x)	0
+extern void	blankscreen(int);
+void		screeninit(int x, int y, char *chanstr);
+void		screenwin(void);
+void		absmousetrack(int x, int y, int b, ulong msec);
+Memdata*	attachscreen(Rectangle*, ulong*, int*, int*, int*);
+void		deletescreenimage(void);
+void		resetscreenimage(void);
+
+void		fsinit(char *mntpt, int x, int y, char *chanstr);
+#define		ishwimage(i)	0
--- /dev/null
+++ b/sys/src/cmd/vnc/utf2ksym.h
@@ -1,0 +1,1073 @@
+/*
+ * VNC uses X11's keysyms defined in X11/keysym.h, this is a converter
+ * from unicode characters to ksyms that other servers use.
+ */
+static ulong
+utf2ksym [] = {
+	[L'Ą']	0x1a1,
+	[L'˘']	0x1a2,
+	[L'Ł']	0x1a3,
+	[L'Ľ']	0x1a5,
+	[L'Ś']	0x1a6,
+	[L'Š']	0x1a9,
+	[L'Ş']	0x1aa,
+	[L'Ť']	0x1ab,
+	[L'Ź']	0x1ac,
+	[L'Ž']	0x1ae,
+	[L'Ż']	0x1af,
+	[L'ą']	0x1b1,
+	[L'˛']	0x1b2,
+	[L'ł']	0x1b3,
+	[L'ľ']	0x1b5,
+	[L'ś']	0x1b6,
+	[L'ˇ']	0x1b7,
+	[L'š']	0x1b9,
+	[L'ş']	0x1ba,
+	[L'ť']	0x1bb,
+	[L'ź']	0x1bc,
+	[L'˝']	0x1bd,
+	[L'ž']	0x1be,
+	[L'ż']	0x1bf,
+	[L'Ŕ']	0x1c0,
+	[L'Ă']	0x1c3,
+	[L'Ĺ']	0x1c5,
+	[L'Ć']	0x1c6,
+	[L'Č']	0x1c8,
+	[L'Ę']	0x1ca,
+	[L'Ě']	0x1cc,
+	[L'Ď']	0x1cf,
+	[L'Đ']	0x1d0,
+	[L'Ń']	0x1d1,
+	[L'Ň']	0x1d2,
+	[L'Ő']	0x1d5,
+	[L'Ř']	0x1d8,
+	[L'Ů']	0x1d9,
+	[L'Ű']	0x1db,
+	[L'Ţ']	0x1de,
+	[L'ŕ']	0x1e0,
+	[L'ă']	0x1e3,
+	[L'ĺ']	0x1e5,
+	[L'ć']	0x1e6,
+	[L'č']	0x1e8,
+	[L'ę']	0x1ea,
+	[L'ě']	0x1ec,
+	[L'ď']	0x1ef,
+	[L'đ']	0x1f0,
+	[L'ń']	0x1f1,
+	[L'ň']	0x1f2,
+	[L'ő']	0x1f5,
+	[L'ř']	0x1f8,
+	[L'ů']	0x1f9,
+	[L'ű']	0x1fb,
+	[L'ţ']	0x1fe,
+	[L'˙']	0x1ff,
+	[L'Ħ']	0x2a1,
+	[L'Ĥ']	0x2a6,
+	[L'İ']	0x2a9,
+	[L'Ğ']	0x2ab,
+	[L'Ĵ']	0x2ac,
+	[L'ħ']	0x2b1,
+	[L'ĥ']	0x2b6,
+	[L'ı']	0x2b9,
+	[L'ğ']	0x2bb,
+	[L'ĵ']	0x2bc,
+	[L'Ċ']	0x2c5,
+	[L'Ĉ']	0x2c6,
+	[L'Ġ']	0x2d5,
+	[L'Ĝ']	0x2d8,
+	[L'Ŭ']	0x2dd,
+	[L'Ŝ']	0x2de,
+	[L'ċ']	0x2e5,
+	[L'ĉ']	0x2e6,
+	[L'ġ']	0x2f5,
+	[L'ĝ']	0x2f8,
+	[L'ŭ']	0x2fd,
+	[L'ŝ']	0x2fe,
+	[L'ĸ']	0x3a2,
+	[L'Ŗ']	0x3a3,
+	[L'Ĩ']	0x3a5,
+	[L'Ļ']	0x3a6,
+	[L'Ē']	0x3aa,
+	[L'Ģ']	0x3ab,
+	[L'Ŧ']	0x3ac,
+	[L'ŗ']	0x3b3,
+	[L'ĩ']	0x3b5,
+	[L'ļ']	0x3b6,
+	[L'ē']	0x3ba,
+	[L'ģ']	0x3bb,
+	[L'ŧ']	0x3bc,
+	[L'Ŋ']	0x3bd,
+	[L'ŋ']	0x3bf,
+	[L'Ā']	0x3c0,
+	[L'Į']	0x3c7,
+	[L'Ė']	0x3cc,
+	[L'Ī']	0x3cf,
+	[L'Ņ']	0x3d1,
+	[L'Ō']	0x3d2,
+	[L'Ķ']	0x3d3,
+	[L'Ų']	0x3d9,
+	[L'Ũ']	0x3dd,
+	[L'Ū']	0x3de,
+	[L'ā']	0x3e0,
+	[L'į']	0x3e7,
+	[L'ė']	0x3ec,
+	[L'ī']	0x3ef,
+	[L'ņ']	0x3f1,
+	[L'ō']	0x3f2,
+	[L'ķ']	0x3f3,
+	[L'ų']	0x3f9,
+	[L'ũ']	0x3fd,
+	[L'ū']	0x3fe,
+	[L'。']	0x4a1,
+	[L'〈']	0x4a2,
+	[L'〉']	0x4a3,
+	[L'、']	0x4a4,
+	[L'・']	0x4a5,
+	[L'ヲ']	0x4a6,
+	[L'ァ']	0x4a7,
+	[L'ィ']	0x4a8,
+	[L'ゥ']	0x4a9,
+	[L'ェ']	0x4aa,
+	[L'ォ']	0x4ab,
+	[L'ャ']	0x4ac,
+	[L'ュ']	0x4ad,
+	[L'ョ']	0x4ae,
+	[L'ッ']	0x4af,
+	[L'ー']	0x4b0,
+	[L'ア']	0x4b1,
+	[L'イ']	0x4b2,
+	[L'ウ']	0x4b3,
+	[L'エ']	0x4b4,
+	[L'オ']	0x4b5,
+	[L'カ']	0x4b6,
+	[L'キ']	0x4b7,
+	[L'ク']	0x4b8,
+	[L'ケ']	0x4b9,
+	[L'コ']	0x4ba,
+	[L'サ']	0x4bb,
+	[L'シ']	0x4bc,
+	[L'ス']	0x4bd,
+	[L'セ']	0x4be,
+	[L'ソ']	0x4bf,
+	[L'タ']	0x4c0,
+	[L'チ']	0x4c1,
+	[L'ツ']	0x4c2,
+	[L'テ']	0x4c3,
+	[L'ト']	0x4c4,
+	[L'ナ']	0x4c5,
+	[L'ニ']	0x4c6,
+	[L'ヌ']	0x4c7,
+	[L'ネ']	0x4c8,
+	[L'ノ']	0x4c9,
+	[L'ハ']	0x4ca,
+	[L'ヒ']	0x4cb,
+	[L'フ']	0x4cc,
+	[L'ヘ']	0x4cd,
+	[L'ホ']	0x4ce,
+	[L'マ']	0x4cf,
+	[L'ミ']	0x4d0,
+	[L'ム']	0x4d1,
+	[L'メ']	0x4d2,
+	[L'モ']	0x4d3,
+	[L'ヤ']	0x4d4,
+	[L'ユ']	0x4d5,
+	[L'ヨ']	0x4d6,
+	[L'ラ']	0x4d7,
+	[L'リ']	0x4d8,
+	[L'ル']	0x4d9,
+	[L'レ']	0x4da,
+	[L'ロ']	0x4db,
+	[L'ワ']	0x4dc,
+	[L'ン']	0x4dd,
+	[L'゛']	0x4de,
+	[L'゜']	0x4df,
+	[L'۰']	0x10006f0,
+	[L'۱']	0x10006f1,
+	[L'۲']	0x10006f2,
+	[L'۳']	0x10006f3,
+	[L'۴']	0x10006f4,
+	[L'۵']	0x10006f5,
+	[L'۶']	0x10006f6,
+	[L'۷']	0x10006f7,
+	[L'۸']	0x10006f8,
+	[L'۹']	0x10006f9,
+	[L'٪']	0x5a5,
+	[L'ٰ']	0x5a6,
+	[L'ٹ']	0x5a7,
+	[L'پ']	0x100067e,
+	[L'چ']	0x1000686,
+	[L'ڈ']	0x5aa,
+	[L'ڑ']	0x5ab,
+	[L'،']	0x5ac,
+	[L'۔']	0x5ae,
+	[L'٠']	0x5b0,
+	[L'١']	0x5b1,
+	[L'٢']	0x5b2,
+	[L'٣']	0x5b3,
+	[L'٤']	0x5b4,
+	[L'٥']	0x5b5,
+	[L'٦']	0x5b6,
+	[L'٧']	0x5b7,
+	[L'٨']	0x5b8,
+	[L'٩']	0x5b9,
+	[L'؛']	0x5bb,
+	[L'؟']	0x5bf,
+	[L'ء']	0x5c1,
+	[L'آ']	0x5c2,
+	[L'أ']	0x5c3,
+	[L'ؤ']	0x5c4,
+	[L'إ']	0x5c5,
+	[L'ئ']	0x5c6,
+	[L'ا']	0x5c7,
+	[L'ب']	0x5c8,
+	[L'ة']	0x5c9,
+	[L'ت']	0x5ca,
+	[L'ث']	0x5cb,
+	[L'ج']	0x5cc,
+	[L'ح']	0x5cd,
+	[L'خ']	0x5ce,
+	[L'د']	0x5cf,
+	[L'ذ']	0x5d0,
+	[L'ر']	0x5d1,
+	[L'ز']	0x5d2,
+	[L'س']	0x5d3,
+	[L'ش']	0x5d4,
+	[L'ص']	0x5d5,
+	[L'ض']	0x5d6,
+	[L'ط']	0x5d7,
+	[L'ظ']	0x5d8,
+	[L'ع']	0x5d9,
+	[L'غ']	0x5da,
+	[L'ـ']	0x5e0,
+	[L'ف']	0x5e1,
+	[L'ق']	0x5e2,
+	[L'ك']	0x5e3,
+	[L'ل']	0x5e4,
+	[L'م']	0x5e5,
+	[L'ن']	0x5e6,
+	[L'ه']	0x5e7,
+	[L'و']	0x5e8,
+	[L'ى']	0x5e9,
+	[L'ي']	0x5ea,
+	[L'ً']	0x5eb,
+	[L'ٌ']	0x5ec,
+	[L'ٍ']	0x5ed,
+	[L'َ']	0x5ee,
+	[L'ُ']	0x5ef,
+	[L'ِ']	0x5f0,
+	[L'ّ']	0x5f1,
+	[L'ْ']	0x5f2,
+	[L'ٓ']	0x5f3,
+	[L'ٔ']	0x5f4,
+	[L'ٕ']	0x5f5,
+	[L'ژ']	0x1000698,
+	[L'ڤ']	0x5f7,
+	[L'ک']	0x10006a9,
+	[L'گ']	0x10006af,
+	[L'ں']	0x5fa,
+	[L'ھ']	0x5fb,
+	[L'ی']	0x10006cc,
+	[L'ے']	0x5fd,
+	[L'ہ']	0x5fe,
+	[L'Ғ']	0x680,
+	[L'Җ']	0x681,
+	[L'Қ']	0x682,
+	[L'Ҝ']	0x683,
+	[L'Ң']	0x684,
+	[L'Ү']	0x685,
+	[L'Ұ']	0x686,
+	[L'Ҳ']	0x687,
+	[L'Ҷ']	0x688,
+	[L'Ҹ']	0x689,
+	[L'Һ']	0x68a,
+	[L'Ә']	0x68c,
+	[L'Ӣ']	0x68d,
+	[L'Ө']	0x68e,
+	[L'Ӯ']	0x68f,
+	[L'ғ']	0x690,
+	[L'җ']	0x691,
+	[L'қ']	0x692,
+	[L'ҝ']	0x693,
+	[L'ң']	0x694,
+	[L'ү']	0x695,
+	[L'ұ']	0x696,
+	[L'ҳ']	0x697,
+	[L'ҷ']	0x698,
+	[L'ҹ']	0x699,
+	[L'һ']	0x69a,
+	[L'ә']	0x69c,
+	[L'ӣ']	0x69d,
+	[L'ө']	0x69e,
+	[L'ӯ']	0x69f,
+	[L'ђ']	0x6a1,
+	[L'ѓ']	0x6a2,
+	[L'ё']	0x6a3,
+	[L'є']	0x6a4,
+	[L'ѕ']	0x6a5,
+	[L'і']	0x6a6,
+	[L'ї']	0x6a7,
+	[L'ј']	0x6a8,
+	[L'љ']	0x6a9,
+	[L'њ']	0x6aa,
+	[L'ћ']	0x6ab,
+	[L'ќ']	0x6ac,
+	[L'ґ']	0x6ad,
+	[L'ў']	0x6ae,
+	[L'џ']	0x6af,
+	[L'№']	0x6b0,
+	[L'Ђ']	0x6b1,
+	[L'Ѓ']	0x6b2,
+	[L'Ё']	0x6b3,
+	[L'Є']	0x6b4,
+	[L'Ѕ']	0x6b5,
+	[L'І']	0x6b6,
+	[L'Ї']	0x6b7,
+	[L'Ј']	0x6b8,
+	[L'Љ']	0x6b9,
+	[L'Њ']	0x6ba,
+	[L'Ћ']	0x6bb,
+	[L'Ќ']	0x6bc,
+	[L'Ґ']	0x6bd,
+	[L'Ў']	0x6be,
+	[L'Џ']	0x6bf,
+	[L'ю']	0x6c0,
+	[L'а']	0x6c1,
+	[L'б']	0x6c2,
+	[L'ц']	0x6c3,
+	[L'д']	0x6c4,
+	[L'е']	0x6c5,
+	[L'ф']	0x6c6,
+	[L'г']	0x6c7,
+	[L'х']	0x6c8,
+	[L'и']	0x6c9,
+	[L'й']	0x6ca,
+	[L'к']	0x6cb,
+	[L'л']	0x6cc,
+	[L'м']	0x6cd,
+	[L'н']	0x6ce,
+	[L'о']	0x6cf,
+	[L'п']	0x6d0,
+	[L'я']	0x6d1,
+	[L'р']	0x6d2,
+	[L'с']	0x6d3,
+	[L'т']	0x6d4,
+	[L'у']	0x6d5,
+	[L'ж']	0x6d6,
+	[L'в']	0x6d7,
+	[L'ь']	0x6d8,
+	[L'ы']	0x6d9,
+	[L'з']	0x6da,
+	[L'ш']	0x6db,
+	[L'э']	0x6dc,
+	[L'щ']	0x6dd,
+	[L'ч']	0x6de,
+	[L'ъ']	0x6df,
+	[L'Ю']	0x6e0,
+	[L'А']	0x6e1,
+	[L'Б']	0x6e2,
+	[L'Ц']	0x6e3,
+	[L'Д']	0x6e4,
+	[L'Е']	0x6e5,
+	[L'Ф']	0x6e6,
+	[L'Г']	0x6e7,
+	[L'Х']	0x6e8,
+	[L'И']	0x6e9,
+	[L'Й']	0x6ea,
+	[L'К']	0x6eb,
+	[L'Л']	0x6ec,
+	[L'М']	0x6ed,
+	[L'Н']	0x6ee,
+	[L'О']	0x6ef,
+	[L'П']	0x6f0,
+	[L'Я']	0x6f1,
+	[L'Р']	0x6f2,
+	[L'С']	0x6f3,
+	[L'Т']	0x6f4,
+	[L'У']	0x6f5,
+	[L'Ж']	0x6f6,
+	[L'В']	0x6f7,
+	[L'Ь']	0x6f8,
+	[L'Ы']	0x6f9,
+	[L'З']	0x6fa,
+	[L'Ш']	0x6fb,
+	[L'Э']	0x6fc,
+	[L'Щ']	0x6fd,
+	[L'Ч']	0x6fe,
+	[L'Ъ']	0x6ff,
+	[L'Ά']	0x7a1,
+	[L'Έ']	0x7a2,
+	[L'Ή']	0x7a3,
+	[L'Ί']	0x7a4,
+	[L'Ϊ']	0x7a5,
+	[L'Ό']	0x7a7,
+	[L'Ύ']	0x7a8,
+	[L'Ϋ']	0x7a9,
+	[L'Ώ']	0x7ab,
+	[L'΅']	0x7ae,
+	[L'―']	0x7af,
+	[L'ά']	0x7b1,
+	[L'έ']	0x7b2,
+	[L'ή']	0x7b3,
+	[L'ί']	0x7b4,
+	[L'ϊ']	0x7b5,
+	[L'ΐ']	0x7b6,
+	[L'ό']	0x7b7,
+	[L'ύ']	0x7b8,
+	[L'ϋ']	0x7b9,
+	[L'ΰ']	0x7ba,
+	[L'ώ']	0x7bb,
+	[L'Α']	0x7c1,
+	[L'Β']	0x7c2,
+	[L'Γ']	0x7c3,
+	[L'Δ']	0x7c4,
+	[L'Ε']	0x7c5,
+	[L'Ζ']	0x7c6,
+	[L'Η']	0x7c7,
+	[L'Θ']	0x7c8,
+	[L'Ι']	0x7c9,
+	[L'Κ']	0x7ca,
+	[L'Λ']	0x7cb,
+	[L'Μ']	0x7cc,
+	[L'Ν']	0x7cd,
+	[L'Ξ']	0x7ce,
+	[L'Ο']	0x7cf,
+	[L'Π']	0x7d0,
+	[L'Ρ']	0x7d1,
+	[L'Σ']	0x7d2,
+	[L'Τ']	0x7d4,
+	[L'Υ']	0x7d5,
+	[L'Φ']	0x7d6,
+	[L'Χ']	0x7d7,
+	[L'Ψ']	0x7d8,
+	[L'Ω']	0x7d9,
+	[L'α']	0x7e1,
+	[L'β']	0x7e2,
+	[L'γ']	0x7e3,
+	[L'δ']	0x7e4,
+	[L'ε']	0x7e5,
+	[L'ζ']	0x7e6,
+	[L'η']	0x7e7,
+	[L'θ']	0x7e8,
+	[L'ι']	0x7e9,
+	[L'κ']	0x7ea,
+	[L'λ']	0x7eb,
+	[L'μ']	0x7ec,
+	[L'ν']	0x7ed,
+	[L'ξ']	0x7ee,
+	[L'ο']	0x7ef,
+	[L'π']	0x7f0,
+	[L'ρ']	0x7f1,
+	[L'σ']	0x7f2,
+	[L'ς']	0x7f3,
+	[L'τ']	0x7f4,
+	[L'υ']	0x7f5,
+	[L'φ']	0x7f6,
+	[L'χ']	0x7f7,
+	[L'ψ']	0x7f8,
+	[L'ω']	0x7f9,
+	[L'⌠']	0x8a4,
+	[L'⌡']	0x8a5,
+	[L'⌜']	0x8a7,
+	[L'⌝']	0x8a8,
+	[L'⌞']	0x8a9,
+	[L'⌟']	0x8aa,
+	[L'≤']	0x8bc,
+	[L'≠']	0x8bd,
+	[L'≥']	0x8be,
+	[L'∫']	0x8bf,
+	[L'∴']	0x8c0,
+	[L'∞']	0x8c2,
+	[L'∇']	0x8c5,
+	[L'≅']	0x8c8,
+	[L'≆']	0x8c9,
+	[L'⊢']	0x8ce,
+	[L'√']	0x8d6,
+	[L'⊂']	0x8da,
+	[L'⊃']	0x8db,
+	[L'∩']	0x8dc,
+	[L'∪']	0x8dd,
+	[L'∧']	0x8de,
+	[L'∨']	0x8df,
+	[L'ƒ']	0x8f6,
+	[L'←']	0x8fb,
+	[L'↑']	0x8fc,
+	[L'→']	0x8fd,
+	[L'↓']	0x8fe,
+	[L'␢']	0x9df,
+	[L'♦']	0x9e0,
+	[L'▦']	0x9e1,
+	[L'␉']	0x9e2,
+	[L'␌']	0x9e3,
+	[L'␍']	0x9e4,
+	[L'␊']	0x9e5,
+	[L'␋']	0x9e9,
+	[L'┘']	0x9ea,
+	[L'┐']	0x9eb,
+	[L'┌']	0x9ec,
+	[L'└']	0x9ed,
+	[L'┼']	0x9ee,
+	[L'─']	0x9ef,
+	[L'├']	0x9f4,
+	[L'┤']	0x9f5,
+	[L'┴']	0x9f6,
+	[L'┬']	0x9f7,
+	[L'│']	0x9f8,
+	[L' ']	0xaa1,
+	[L' ']	0xaa2,
+	[L' ']	0xaa3,
+	[L' ']	0xaa4,
+	[L' ']	0xaa5,
+	[L' ']	0xaa6,
+	[L' ']	0xaa7,
+	[L' ']	0xaa8,
+	[L'—']	0xaa9,
+	[L'–']	0xaaa,
+	[L'…']	0xaae,
+	[L'‥']	0xaaf,
+	[L'⅓']	0xab0,
+	[L'⅔']	0xab1,
+	[L'⅕']	0xab2,
+	[L'⅖']	0xab3,
+	[L'⅗']	0xab4,
+	[L'⅘']	0xab5,
+	[L'⅙']	0xab6,
+	[L'⅚']	0xab7,
+	[L'℅']	0xab8,
+	[L'‒']	0xabb,
+	[L'‹']	0xabc,
+	[L'․']	0xabd,
+	[L'›']	0xabe,
+	[L'⅛']	0xac3,
+	[L'⅜']	0xac4,
+	[L'⅝']	0xac5,
+	[L'⅞']	0xac6,
+	[L'™']	0xac9,
+	[L'℠']	0xaca,
+	[L'◁']	0xacc,
+	[L'▷']	0xacd,
+	[L'○']	0xace,
+	[L'▭']	0xacf,
+	[L'‘']	0xad0,
+	[L'’']	0xad1,
+	[L'“']	0xad2,
+	[L'”']	0xad3,
+	[L'℞']	0xad4,
+	[L'′']	0xad6,
+	[L'″']	0xad7,
+	[L'✝']	0xad9,
+	[L'∎']	0xadb,
+	[L'◂']	0xadc,
+	[L'‣']	0xadd,
+	[L'●']	0xade,
+	[L'▬']	0xadf,
+	[L'◦']	0xae0,
+	[L'▫']	0xae1,
+	[L'▮']	0xae2,
+	[L'▵']	0xae3,
+	[L'▿']	0xae4,
+	[L'☆']	0xae5,
+	[L'•']	0xae6,
+	[L'▪']	0xae7,
+	[L'▴']	0xae8,
+	[L'▾']	0xae9,
+	[L'☚']	0xaea,
+	[L'☛']	0xaeb,
+	[L'♣']	0xaec,
+	[L'♥']	0xaee,
+	[L'✠']	0xaf0,
+	[L'†']	0xaf1,
+	[L'‡']	0xaf2,
+	[L'✓']	0xaf3,
+	[L'☒']	0xaf4,
+	[L'♯']	0xaf5,
+	[L'♭']	0xaf6,
+	[L'♂']	0xaf7,
+	[L'♀']	0xaf8,
+	[L'℡']	0xaf9,
+	[L'⌕']	0xafa,
+	[L'℗']	0xafb,
+	[L'‸']	0xafc,
+	[L'‚']	0xafd,
+	[L'„']	0xafe,
+	[L'‗']	0xcdf,
+	[L'א']	0xce0,
+	[L'ב']	0xce1,
+	[L'ג']	0xce2,
+	[L'ד']	0xce3,
+	[L'ה']	0xce4,
+	[L'ו']	0xce5,
+	[L'ז']	0xce6,
+	[L'ח']	0xce7,
+	[L'ט']	0xce8,
+	[L'י']	0xce9,
+	[L'ך']	0xcea,
+	[L'כ']	0xceb,
+	[L'ל']	0xcec,
+	[L'ם']	0xced,
+	[L'מ']	0xcee,
+	[L'ן']	0xcef,
+	[L'נ']	0xcf0,
+	[L'ס']	0xcf1,
+	[L'ע']	0xcf2,
+	[L'ף']	0xcf3,
+	[L'פ']	0xcf4,
+	[L'ץ']	0xcf5,
+	[L'צ']	0xcf6,
+	[L'ק']	0xcf7,
+	[L'ר']	0xcf8,
+	[L'ש']	0xcf9,
+	[L'ת']	0xcfa,
+	[L'ก']	0xda1,
+	[L'ข']	0xda2,
+	[L'ฃ']	0xda3,
+	[L'ค']	0xda4,
+	[L'ฅ']	0xda5,
+	[L'ฆ']	0xda6,
+	[L'ง']	0xda7,
+	[L'จ']	0xda8,
+	[L'ฉ']	0xda9,
+	[L'ช']	0xdaa,
+	[L'ซ']	0xdab,
+	[L'ฌ']	0xdac,
+	[L'ญ']	0xdad,
+	[L'ฎ']	0xdae,
+	[L'ฏ']	0xdaf,
+	[L'ฐ']	0xdb0,
+	[L'ฑ']	0xdb1,
+	[L'ฒ']	0xdb2,
+	[L'ณ']	0xdb3,
+	[L'ด']	0xdb4,
+	[L'ต']	0xdb5,
+	[L'ถ']	0xdb6,
+	[L'ท']	0xdb7,
+	[L'ธ']	0xdb8,
+	[L'น']	0xdb9,
+	[L'บ']	0xdba,
+	[L'ป']	0xdbb,
+	[L'ผ']	0xdbc,
+	[L'ฝ']	0xdbd,
+	[L'พ']	0xdbe,
+	[L'ฟ']	0xdbf,
+	[L'ภ']	0xdc0,
+	[L'ม']	0xdc1,
+	[L'ย']	0xdc2,
+	[L'ร']	0xdc3,
+	[L'ฤ']	0xdc4,
+	[L'ล']	0xdc5,
+	[L'ฦ']	0xdc6,
+	[L'ว']	0xdc7,
+	[L'ศ']	0xdc8,
+	[L'ษ']	0xdc9,
+	[L'ส']	0xdca,
+	[L'ห']	0xdcb,
+	[L'ฬ']	0xdcc,
+	[L'อ']	0xdcd,
+	[L'ฮ']	0xdce,
+	[L'ฯ']	0xdcf,
+	[L'ะ']	0xdd0,
+	[L'ั']	0xdd1,
+	[L'า']	0xdd2,
+	[L'ำ']	0xdd3,
+	[L'ิ']	0xdd4,
+	[L'ี']	0xdd5,
+	[L'ึ']	0xdd6,
+	[L'ื']	0xdd7,
+	[L'ุ']	0xdd8,
+	[L'ู']	0xdd9,
+	[L'ฺ']	0xdda,
+	[L'฾']	0xdde,
+	[L'฿']	0xddf,
+	[L'เ']	0xde0,
+	[L'แ']	0xde1,
+	[L'โ']	0xde2,
+	[L'ใ']	0xde3,
+	[L'ไ']	0xde4,
+	[L'ๅ']	0xde5,
+	[L'ๆ']	0xde6,
+	[L'็']	0xde7,
+	[L'่']	0xde8,
+	[L'้']	0xde9,
+	[L'๊']	0xdea,
+	[L'๋']	0xdeb,
+	[L'์']	0xdec,
+	[L'ํ']	0xded,
+	[L'๐']	0xdf0,
+	[L'๑']	0xdf1,
+	[L'๒']	0xdf2,
+	[L'๓']	0xdf3,
+	[L'๔']	0xdf4,
+	[L'๕']	0xdf5,
+	[L'๖']	0xdf6,
+	[L'๗']	0xdf7,
+	[L'๘']	0xdf8,
+	[L'๙']	0xdf9,
+	[L'ᄁ']	0xea2,
+	[L'ᄂ']	0xea4,
+	[L'ᄃ']	0xea7,
+	[L'ᄄ']	0xea8,
+	[L'ᄅ']	0xea9,
+	[L'ᄆ']	0xeb1,
+	[L'ᄇ']	0xeb2,
+	[L'ᄈ']	0xeb3,
+	[L'ᄉ']	0xeb5,
+	[L'ᄊ']	0xeb6,
+	[L'ᄋ']	0xeb7,
+	[L'ᄌ']	0xeb8,
+	[L'ᄍ']	0xeb9,
+	[L'ᄎ']	0xeba,
+	[L'ᄏ']	0xebb,
+	[L'ᄐ']	0xebc,
+	[L'ᄑ']	0xebd,
+	[L'ᄒ']	0xebe,
+	[L'ᅡ']	0xebf,
+	[L'ᅢ']	0xec0,
+	[L'ᅣ']	0xec1,
+	[L'ᅤ']	0xec2,
+	[L'ᅥ']	0xec3,
+	[L'ᅦ']	0xec4,
+	[L'ᅧ']	0xec5,
+	[L'ᅨ']	0xec6,
+	[L'ᅩ']	0xec7,
+	[L'ᅪ']	0xec8,
+	[L'ᅫ']	0xec9,
+	[L'ᅬ']	0xeca,
+	[L'ᅭ']	0xecb,
+	[L'ᅮ']	0xecc,
+	[L'ᅯ']	0xecd,
+	[L'ᅰ']	0xece,
+	[L'ᅱ']	0xecf,
+	[L'ᅲ']	0xed0,
+	[L'ᅳ']	0xed1,
+	[L'ᅴ']	0xed2,
+	[L'ᅵ']	0xed3,
+	[L'ᆨ']	0xed4,
+	[L'ᆩ']	0xed5,
+	[L'ᆪ']	0xed6,
+	[L'ᆫ']	0xed7,
+	[L'ᆬ']	0xed8,
+	[L'ᆭ']	0xed9,
+	[L'ᆮ']	0xeda,
+	[L'ᆯ']	0xedb,
+	[L'ᆰ']	0xedc,
+	[L'ᆱ']	0xedd,
+	[L'ᆲ']	0xede,
+	[L'ᆳ']	0xedf,
+	[L'ᆴ']	0xee0,
+	[L'ᆵ']	0xee1,
+	[L'ᆶ']	0xee2,
+	[L'ᆷ']	0xee3,
+	[L'ᆸ']	0xee4,
+	[L'ᆹ']	0xee5,
+	[L'ᆺ']	0xee6,
+	[L'ᆻ']	0xee7,
+	[L'ᆼ']	0xee8,
+	[L'ᆽ']	0xee9,
+	[L'ᆾ']	0xeea,
+	[L'ᆿ']	0xeeb,
+	[L'ᇀ']	0xeec,
+	[L'ᇁ']	0xeed,
+	[L'ᇂ']	0xeee,
+	[L'ᅀ']	0xef2,
+	[L'ᅙ']	0xef5,
+	[L'ᆞ']	0xef6,
+	[L'ᇫ']	0xef8,
+	[L'ᇹ']	0xefa,
+	[L'₩']	0xeff,
+	[L'Ḃ']	0x12a1,
+	[L'ḃ']	0x12a2,
+	[L'Ḋ']	0x12a6,
+	[L'Ẁ']	0x12a8,
+	[L'Ẃ']	0x12aa,
+	[L'ḋ']	0x12ab,
+	[L'Ỳ']	0x12ac,
+	[L'Ḟ']	0x12b0,
+	[L'ḟ']	0x12b1,
+	[L'Ṁ']	0x12b4,
+	[L'ṁ']	0x12b5,
+	[L'Ṗ']	0x12b7,
+	[L'ẁ']	0x12b8,
+	[L'ṗ']	0x12b9,
+	[L'ẃ']	0x12ba,
+	[L'Ṡ']	0x12bb,
+	[L'ỳ']	0x12bc,
+	[L'Ẅ']	0x12bd,
+	[L'ẅ']	0x12be,
+	[L'ṡ']	0x12bf,
+	[L'Ŵ']	0x12d0,
+	[L'Ṫ']	0x12d7,
+	[L'Ŷ']	0x12de,
+	[L'ŵ']	0x12f0,
+	[L'ṫ']	0x12f7,
+	[L'ŷ']	0x12fe,
+	[L'Œ']	0x13bc,
+	[L'œ']	0x13bd,
+	[L'Ÿ']	0x13be,
+	[L'❁']	0x14a1,
+	[L'§']	0x14a2,
+	[L'։']	0x14a3,
+	[L')']	0x14a4,
+	[L'(']	0x14a5,
+	[L'»']	0x14a6,
+	[L'«']	0x14a7,
+	[L'.']	0x14a9,
+	[L'՝']	0x14aa,
+	[L',']	0x14ab,
+	[L'֊']	0x14ad,
+	[L'՜']	0x14af,
+	[L'՛']	0x14b0,
+	[L'՞']	0x14b1,
+	[L'Ա']	0x14b2,
+	[L'ա']	0x14b3,
+	[L'Բ']	0x14b4,
+	[L'բ']	0x14b5,
+	[L'Գ']	0x14b6,
+	[L'գ']	0x14b7,
+	[L'Դ']	0x14b8,
+	[L'դ']	0x14b9,
+	[L'Ե']	0x14ba,
+	[L'ե']	0x14bb,
+	[L'Զ']	0x14bc,
+	[L'զ']	0x14bd,
+	[L'Է']	0x14be,
+	[L'է']	0x14bf,
+	[L'Ը']	0x14c0,
+	[L'ը']	0x14c1,
+	[L'Թ']	0x14c2,
+	[L'թ']	0x14c3,
+	[L'Ժ']	0x14c4,
+	[L'ժ']	0x14c5,
+	[L'Ի']	0x14c6,
+	[L'ի']	0x14c7,
+	[L'Լ']	0x14c8,
+	[L'լ']	0x14c9,
+	[L'Խ']	0x14ca,
+	[L'խ']	0x14cb,
+	[L'Ծ']	0x14cc,
+	[L'ծ']	0x14cd,
+	[L'Կ']	0x14ce,
+	[L'կ']	0x14cf,
+	[L'Հ']	0x14d0,
+	[L'հ']	0x14d1,
+	[L'Ձ']	0x14d2,
+	[L'ձ']	0x14d3,
+	[L'Ղ']	0x14d4,
+	[L'ղ']	0x14d5,
+	[L'Ճ']	0x14d6,
+	[L'ճ']	0x14d7,
+	[L'Մ']	0x14d8,
+	[L'մ']	0x14d9,
+	[L'Յ']	0x14da,
+	[L'յ']	0x14db,
+	[L'Ն']	0x14dc,
+	[L'ն']	0x14dd,
+	[L'Շ']	0x14de,
+	[L'շ']	0x14df,
+	[L'Ո']	0x14e0,
+	[L'ո']	0x14e1,
+	[L'Չ']	0x14e2,
+	[L'չ']	0x14e3,
+	[L'Պ']	0x14e4,
+	[L'պ']	0x14e5,
+	[L'Ջ']	0x14e6,
+	[L'ջ']	0x14e7,
+	[L'Ռ']	0x14e8,
+	[L'ռ']	0x14e9,
+	[L'Ս']	0x14ea,
+	[L'ս']	0x14eb,
+	[L'Վ']	0x14ec,
+	[L'վ']	0x14ed,
+	[L'Տ']	0x14ee,
+	[L'տ']	0x14ef,
+	[L'Ր']	0x14f0,
+	[L'ր']	0x14f1,
+	[L'Ց']	0x14f2,
+	[L'ց']	0x14f3,
+	[L'Ւ']	0x14f4,
+	[L'ւ']	0x14f5,
+	[L'Փ']	0x14f6,
+	[L'փ']	0x14f7,
+	[L'Ք']	0x14f8,
+	[L'ք']	0x14f9,
+	[L'Օ']	0x14fa,
+	[L'օ']	0x14fb,
+	[L'Ֆ']	0x14fc,
+	[L'ֆ']	0x14fd,
+	[L''']	0x14ff,
+	[L'ა']	0x15d0,
+	[L'ბ']	0x15d1,
+	[L'გ']	0x15d2,
+	[L'დ']	0x15d3,
+	[L'ე']	0x15d4,
+	[L'ვ']	0x15d5,
+	[L'ზ']	0x15d6,
+	[L'თ']	0x15d7,
+	[L'ი']	0x15d8,
+	[L'კ']	0x15d9,
+	[L'ლ']	0x15da,
+	[L'მ']	0x15db,
+	[L'ნ']	0x15dc,
+	[L'ო']	0x15dd,
+	[L'პ']	0x15de,
+	[L'ჟ']	0x15df,
+	[L'რ']	0x15e0,
+	[L'ს']	0x15e1,
+	[L'ტ']	0x15e2,
+	[L'უ']	0x15e3,
+	[L'ფ']	0x15e4,
+	[L'ქ']	0x15e5,
+	[L'ღ']	0x15e6,
+	[L'ყ']	0x15e7,
+	[L'შ']	0x15e8,
+	[L'ჩ']	0x15e9,
+	[L'ც']	0x15ea,
+	[L'ძ']	0x15eb,
+	[L'წ']	0x15ec,
+	[L'ჭ']	0x15ed,
+	[L'ხ']	0x15ee,
+	[L'ჯ']	0x15ef,
+	[L'ჰ']	0x15f0,
+	[L'ჱ']	0x15f1,
+	[L'ჲ']	0x15f2,
+	[L'ჳ']	0x15f3,
+	[L'ჴ']	0x15f4,
+	[L'ჵ']	0x15f5,
+	[L'ჶ']	0x15f6,
+	[L'']	0x16a2,
+	[L'Ẋ']	0x16a3,
+	[L'']	0x16a5,
+	[L'Ĭ']	0x16a6,
+	[L'']	0x16a7,
+	[L'']	0x16a8,
+	[L'Ƶ']	0x16a9,
+	[L'Ǧ']	0x16aa,
+	[L'Ɵ']	0x16af,
+	[L'']	0x16b2,
+	[L'ẋ']	0x16b3,
+	[L'Ǒ']	0x16b4,
+	[L'']	0x16b5,
+	[L'ĭ']	0x16b6,
+	[L'']	0x16b7,
+	[L'']	0x16b8,
+	[L'ƶ']	0x16b9,
+	[L'ǧ']	0x16ba,
+	[L'ǒ']	0x16bd,
+	[L'ɵ']	0x16bf,
+	[L'Ə']	0x16c6,
+	[L'Ḷ']	0x16d1,
+	[L'']	0x16d2,
+	[L'']	0x16d3,
+	[L'ḷ']	0x16e1,
+	[L'']	0x16e2,
+	[L'']	0x16e3,
+	[L'ə']	0x16f6,
+	[L'̃']	0x1e9f,
+	[L'Ạ']	0x1ea0,
+	[L'ạ']	0x1ea1,
+	[L'Ả']	0x1ea2,
+	[L'ả']	0x1ea3,
+	[L'Ấ']	0x1ea4,
+	[L'ấ']	0x1ea5,
+	[L'Ầ']	0x1ea6,
+	[L'ầ']	0x1ea7,
+	[L'Ẩ']	0x1ea8,
+	[L'ẩ']	0x1ea9,
+	[L'Ẫ']	0x1eaa,
+	[L'ẫ']	0x1eab,
+	[L'Ậ']	0x1eac,
+	[L'ậ']	0x1ead,
+	[L'Ắ']	0x1eae,
+	[L'ắ']	0x1eaf,
+	[L'Ằ']	0x1eb0,
+	[L'ằ']	0x1eb1,
+	[L'Ẳ']	0x1eb2,
+	[L'ẳ']	0x1eb3,
+	[L'Ẵ']	0x1eb4,
+	[L'ẵ']	0x1eb5,
+	[L'Ặ']	0x1eb6,
+	[L'ặ']	0x1eb7,
+	[L'Ẹ']	0x1eb8,
+	[L'ẹ']	0x1eb9,
+	[L'Ẻ']	0x1eba,
+	[L'ẻ']	0x1ebb,
+	[L'Ẽ']	0x1ebc,
+	[L'ẽ']	0x1ebd,
+	[L'Ế']	0x1ebe,
+	[L'ế']	0x1ebf,
+	[L'Ề']	0x1ec0,
+	[L'ề']	0x1ec1,
+	[L'Ể']	0x1ec2,
+	[L'ể']	0x1ec3,
+	[L'Ễ']	0x1ec4,
+	[L'ễ']	0x1ec5,
+	[L'Ệ']	0x1ec6,
+	[L'ệ']	0x1ec7,
+	[L'Ỉ']	0x1ec8,
+	[L'ỉ']	0x1ec9,
+	[L'Ị']	0x1eca,
+	[L'ị']	0x1ecb,
+	[L'Ọ']	0x1ecc,
+	[L'ọ']	0x1ecd,
+	[L'Ỏ']	0x1ece,
+	[L'ỏ']	0x1ecf,
+	[L'Ố']	0x1ed0,
+	[L'ố']	0x1ed1,
+	[L'Ồ']	0x1ed2,
+	[L'ồ']	0x1ed3,
+	[L'Ổ']	0x1ed4,
+	[L'ổ']	0x1ed5,
+	[L'Ỗ']	0x1ed6,
+	[L'ỗ']	0x1ed7,
+	[L'Ộ']	0x1ed8,
+	[L'ộ']	0x1ed9,
+	[L'Ớ']	0x1eda,
+	[L'ớ']	0x1edb,
+	[L'Ờ']	0x1edc,
+	[L'ờ']	0x1edd,
+	[L'Ở']	0x1ede,
+	[L'ở']	0x1edf,
+	[L'Ỡ']	0x1ee0,
+	[L'ỡ']	0x1ee1,
+	[L'Ợ']	0x1ee2,
+	[L'ợ']	0x1ee3,
+	[L'Ụ']	0x1ee4,
+	[L'ụ']	0x1ee5,
+	[L'Ủ']	0x1ee6,
+	[L'ủ']	0x1ee7,
+	[L'Ứ']	0x1ee8,
+	[L'ứ']	0x1ee9,
+	[L'Ừ']	0x1eea,
+	[L'ừ']	0x1eeb,
+	[L'Ử']	0x1eec,
+	[L'ử']	0x1eed,
+	[L'Ữ']	0x1eee,
+	[L'ữ']	0x1eef,
+	[L'Ự']	0x1ef0,
+	[L'ự']	0x1ef1,
+	[L'̀']	0x1ef2,
+	[L'́']	0x1ef3,
+	[L'Ỵ']	0x1ef4,
+	[L'ỵ']	0x1ef5,
+	[L'Ỷ']	0x1ef6,
+	[L'ỷ']	0x1ef7,
+	[L'Ỹ']	0x1ef8,
+	[L'ỹ']	0x1ef9,
+	[L'Ơ']	0x1efa,
+	[L'ơ']	0x1efb,
+	[L'Ư']	0x1efc,
+	[L'ư']	0x1efd,
+	[L'̉']	0x1efe,
+	[L'̣']	0x1eff,
+	[L'₠']	0x20a0,
+	[L'₡']	0x20a1,
+	[L'₢']	0x20a2,
+	[L'₣']	0x20a3,
+	[L'₤']	0x20a4,
+	[L'₥']	0x20a5,
+	[L'₦']	0x20a6,
+	[L'₧']	0x20a7,
+	[L'₨']	0x20a8,
+	[L'₪']	0x20aa,
+	[L'₫']	0x20ab,
+	[L'€']	0x20ac,
+
+};
\ No newline at end of file
--- /dev/null
+++ b/sys/src/cmd/vnc/vnc.h
@@ -1,0 +1,149 @@
+#include <u.h>
+#include <libc.h>
+#include <bio.h>
+#include <draw.h>
+#include <memdraw.h>
+
+typedef struct Pixfmt	Pixfmt;
+typedef struct Colorfmt	Colorfmt;
+typedef struct Vnc	Vnc;
+
+struct Colorfmt {
+	int		max;
+	int		shift;
+};
+
+struct Pixfmt {
+	int		bpp;
+	int		depth;
+	int		bigendian;
+	int		truecolor;
+	Colorfmt	red;
+	Colorfmt	green;
+	Colorfmt	blue;
+};
+
+struct Vnc {
+	QLock;
+	int		datafd;			/* for network connection */
+	int		ctlfd;			/* control for network connection */
+
+	Biobuf		in;
+	Biobuf		out;
+
+	Rectangle	dim;
+	Pixfmt;
+
+	/* client only */
+	char		*name;
+	char		*srvaddr;
+	int		vers;
+
+	int		canresize;
+	struct {
+		ulong		id;
+		Rectangle	rect;
+		ulong		flags;
+	}		screen[1];
+};
+
+enum {
+	VerLen		= 12,
+	/* authentication negotiation */
+	AFailed		= 0,
+	ANoAuth,
+	AVncAuth,
+
+	/* vnc auth negotiation */
+	VncAuthOK	= 0,
+	VncAuthFailed,
+	VncAuthTooMany,
+	VncChalLen	= 16,
+
+	/* server to client */
+	MFrameUpdate	= 0,
+	MSetCmap,
+	MBell,
+	MSCut,
+	MSAck,
+
+	/* client to server */
+	MPixFmt		= 0,
+	MFixCmap,
+	MSetEnc,
+	MFrameReq,
+	MKey,
+	MMouse,
+	MCCut,
+	MSetDesktopSize = 251,
+
+	/* image encoding methods */
+	EncRaw		= 0,
+	EncCopyRect	= 1,
+	EncRre		= 2,
+	EncCorre	= 4,
+	EncHextile	= 5,
+	EncZlib		= 6,  /* 6,7,8 have been used by others */
+	EncTight	= 7,
+	EncZHextile	= 8,
+	EncMouseWarp	= 9,
+
+	EncDesktopSize	= -223,
+	EncXDesktopSize	= -308,
+
+	/* paramaters for hextile encoding */
+	HextileDim	= 16,
+	HextileRaw	= 1,
+	HextileBack	= 2,
+	HextileFore	= 4,
+	HextileRects	= 8,
+	HextileCols	= 16
+};
+
+/*
+ * we're only using the ulong as a place to store bytes,
+ * and as something to compare against.
+ * the bytes are stored in little-endian format.
+ */
+typedef ulong Color;
+
+/* auth.c */
+extern	int		vncauth(Vnc*, char*);
+extern	int		vnchandshake(Vnc*);
+extern	int		vncsrvauth(Vnc*);
+extern	int		vncsrvhandshake(Vnc*);
+
+/* proto.c */
+extern	Vnc*		vncinit(int, int, Vnc*);
+extern	uchar		vncrdchar(Vnc*);
+extern	ushort		vncrdshort(Vnc*);
+extern	ulong		vncrdlong(Vnc*);
+extern	Point		vncrdpoint(Vnc*);
+extern	Rectangle	vncrdrect(Vnc*);
+extern	Rectangle	vncrdcorect(Vnc*);
+extern	Pixfmt		vncrdpixfmt(Vnc*);
+extern	void		vncrdbytes(Vnc*, void*, int);
+extern	char*		vncrdstring(Vnc*);
+extern	char*	vncrdstringx(Vnc*);
+extern	void		vncwrstring(Vnc*, char*);
+extern  void    	vncgobble(Vnc*, long);
+
+extern	void		vncflush(Vnc*);
+extern	void		vncterm(Vnc*);
+extern	void		vncwrbytes(Vnc*, void*, int);
+extern	void		vncwrlong(Vnc*, ulong);
+extern	void		vncwrshort(Vnc*, ushort);
+extern	void		vncwrchar(Vnc*, uchar);
+extern	void		vncwrpixfmt(Vnc*, Pixfmt*);
+extern	void		vncwrrect(Vnc*, Rectangle);
+extern	void		vncwrpoint(Vnc*, Point);
+
+extern	void		vnclock(Vnc*);		/* for writing */
+extern	void		vncunlock(Vnc*);
+
+extern	void		hexdump(void*, int);
+
+/* implemented by clients of the io library */
+extern	void		vnchungup(Vnc*);
+
+extern	int		verbose;
--- /dev/null
+++ b/sys/src/cmd/vnc/vncs.c
@@ -1,0 +1,1143 @@
+#define	Image	IMAGE
+#include "vnc.h"
+#include "vncs.h"
+#include "compat.h"
+#include <cursor.h>
+#include "screen.h"
+#include "kbd.h"
+
+#include <mp.h>
+#include <libsec.h>
+
+extern	Dev	drawdevtab;
+extern	Dev	mousedevtab;
+extern	Dev	consdevtab;
+
+Dev	*devtab[] =
+{
+	&drawdevtab,
+	&mousedevtab,
+	&consdevtab,
+	nil
+};
+
+/* 
+ * list head. used to hold the list, the lock, dim, and pixelfmt
+ */
+struct {
+	QLock;
+	Vncs *head;
+} clients;
+
+int	shared;
+int	sleeptime = 5;
+int	verbose = 0;
+int	noauth = 0;
+int	kbdin = -1;
+
+char *cert;
+char *pixchan = "r5g6b5";
+static int	cmdpid;
+static int	srvfd;
+static int	exportfd;
+static Vncs	**vncpriv;
+
+static int parsedisplay(char*);
+static void vnckill(char*, int, int);
+static int vncannounce(char *net, int display, char *adir, int base);
+static void noteshutdown(void*, char*);
+static void vncaccept(Vncs*);
+static int vncsfmt(Fmt*);
+static void getremote(char*, char*);
+#pragma varargck type "V" Vncs*
+
+void
+usage(void)
+{
+	fprint(2, "usage: vncs [-v] [-c cert] [-d :display] [-g widthXheight] [-p pixelfmt] [-A] [cmd [args]...]\n");
+	fprint(2, "\tto kill a server: vncs [-v] -k :display\n");
+	exits("usage");
+}
+
+void
+main(int argc, char **argv)
+{
+	int baseport, cfd, display, exnum, fd, pid, h, killing, w;
+	char adir[NETPATHLEN], ldir[NETPATHLEN];
+	char net[NETPATHLEN], *p;
+	char *kbdfs[] = { "/bin/aux/kbdfs", "-dq", nil };
+	char *rc[] = { "/bin/rc", "-i", nil };
+	Vncs *v;
+
+	fmtinstall('V', vncsfmt);
+	display = -1;
+	killing = 0;
+	w = 1024;
+	h = 768;
+	baseport = 5900;
+	setnetmtpt(net, sizeof net, nil);
+	ARGBEGIN{
+	default:
+		usage();
+	case 'c':
+		cert = EARGF(usage());
+		baseport = 35729;
+		break;
+	case 'd':
+		if(display != -1)
+			usage();
+		display = parsedisplay(EARGF(usage()));
+		break;
+	case 'g':
+		p = EARGF(usage());
+		w = strtol(p, &p, 10);
+		if(*p != 'x' && *p != 'X' && *p != ' ' && *p != '	')
+			usage();
+		h = strtol(p+1, &p, 10);
+		if(*p != 0)
+			usage();
+		break;
+	case 'k':
+		if(display != -1)
+			usage();
+		display = parsedisplay(EARGF(usage()));
+		killing = 1;
+		break;
+	case 'p':
+		pixchan = EARGF(usage());
+		break;
+/* DEBUGGING
+	case 's':
+		sleeptime = atoi(EARGF(usage()));
+		break;
+*/
+	case 'v':
+		verbose++;
+		break;
+	case 'x':
+		p = EARGF(usage());
+		setnetmtpt(net, sizeof net, p);
+		break;
+	case 'A':
+		noauth = 1;
+		break;
+	}ARGEND
+
+	if(killing){
+		vnckill(net, display, baseport);
+		exits(nil);
+	}
+
+	if(argc == 0)
+		argv = rc;
+
+	/* easy exit */
+	if(access(argv[0], AEXEC) < 0)
+		sysfatal("access %s for exec: %r", argv[0]);
+
+	/* background ourselves */
+	switch(rfork(RFPROC|RFNAMEG|RFFDG|RFNOTEG)){
+	case -1:
+		sysfatal("rfork: %r");
+	default:
+		exits(nil);
+	case 0:
+		break;
+	}
+
+	vncpriv = privalloc();
+	if(vncpriv == nil)
+		sysfatal("privalloc: %r");
+
+	/* start screen */
+	initcompat();
+	if(waserror())
+		sysfatal("screeninit %dx%d %s: %s", w, h, pixchan, up->error);
+	if(verbose)
+		fprint(2, "geometry is %dx%d\n", w, h);
+	screeninit(w, h, pixchan);
+	poperror();
+
+	/* start file system device slaves */
+	exnum = exporter(devtab, &fd, &exportfd);
+
+	/* rebuild /dev because the underlying connection might go away (ick) */
+	unmount(nil, "/dev");
+	bind("#c", "/dev", MREPL);
+
+	/* mount exporter */
+	if(mounter("/dev", MBEFORE, fd, exnum) < 0)
+		sysfatal("mounter: %r");
+	close(fd);
+
+	pid = rfork(RFPROC|RFMEM|RFFDG|RFNOTEG);
+	switch(pid){
+	case -1:
+		sysfatal("rfork: %r");
+		break;
+	case 0:
+		close(exportfd);
+
+		close(1);
+		open("/dev/cons", OWRITE);
+		close(2);
+		open("/dev/cons", OWRITE);
+
+		/* start and mount kbdfs */
+		pid = rfork(RFPROC|RFMEM|RFFDG|RFREND);
+		switch(pid){
+		case -1:
+			sysfatal("rfork: %r");
+			break;
+		case 0:
+			exec(kbdfs[0], kbdfs);
+			fprint(2, "exec %s: %r\n", kbdfs[0]);
+			_exits("kbdfs");
+		}
+		if(waitpid() != pid){
+			rendezvous(&kbdin, nil);
+			sysfatal("waitpid: %s: %r", kbdfs[0]);
+		}
+		rendezvous(&kbdin, nil);
+
+		rfork(RFNAMEG|RFREND);
+
+		close(0);
+		open("/dev/cons", OREAD);
+		close(1);
+		open("/dev/cons", OWRITE);
+		close(2);
+		open("/dev/cons", OWRITE);
+
+		exec(argv[0], argv);
+		fprint(2, "exec %s: %r\n", argv[0]);
+		_exits(nil);
+	}
+	cmdpid = pid;
+
+	/* wait for kbdfs to get mounted */
+	rendezvous(&kbdin, nil);
+	if((kbdin = open("/dev/kbdin", OWRITE)) < 0)
+		sysfatal("open /dev/kbdin: %r");
+
+	/* run the service */
+	srvfd = vncannounce(net, display, adir, baseport);
+	if(srvfd < 0)
+		sysfatal("announce failed");
+	if(verbose)
+		fprint(2, "announced in %s\n", adir);
+
+	atexit(shutdown);
+	notify(noteshutdown);
+	for(;;){
+		procsetname("listener");
+		cfd = listen(adir, ldir);
+		if(cfd < 0)
+			break;
+		if(verbose)
+			fprint(2, "call in %s\n", ldir);
+		fd = accept(cfd, ldir);
+		if(fd < 0){
+			close(cfd);
+			continue;
+		}
+		v = mallocz(sizeof(Vncs), 1);
+		if(v == nil){
+			close(cfd);
+			close(fd);
+			continue;
+		}
+		v->ctlfd = cfd;
+		v->datafd = fd;
+		v->nproc = 1;
+		v->ndead = 0;
+		getremote(ldir, v->remote);
+		strcpy(v->netpath, ldir);
+		qlock(&clients);
+		v->next = clients.head;
+		clients.head = v;
+		qunlock(&clients);
+		vncaccept(v);
+	}
+	exits(0);
+}
+
+static int
+parsedisplay(char *p)
+{
+	int n;
+
+	if(*p != ':')
+		usage();
+	if(*p == 0)
+		usage();
+	n = strtol(p+1, &p, 10);
+	if(*p != 0)
+		usage();
+	return n;
+}
+
+static void
+getremote(char *ldir, char *remote)
+{
+	char buf[NETPATHLEN];
+	int fd, n;
+
+	snprint(buf, sizeof buf, "%s/remote", ldir);
+	strcpy(remote, "<none>");
+	if((fd = open(buf, OREAD)) < 0)
+		return;
+	n = readn(fd, remote, NETPATHLEN-1);
+	close(fd);
+	if(n < 0)
+		return;
+	remote[n] = 0;
+	if(n>0 && remote[n-1] == '\n')
+		remote[n-1] = 0;
+}
+
+static int
+vncsfmt(Fmt *fmt)
+{
+	Vncs *v;
+
+	v = va_arg(fmt->args, Vncs*);
+	return fmtprint(fmt, "[%d] %s %s", getpid(), v->remote, v->netpath);
+}
+
+/*
+ * We register exiting as an atexit handler in each proc, so that 
+ * client procs need merely exit when something goes wrong.
+ */
+static void
+vncclose(Vncs *v)
+{
+	Vncs **l;
+
+	/* remove self from client list if there */
+	qlock(&clients);
+	for(l=&clients.head; *l; l=&(*l)->next)
+		if(*l == v){
+			*l = v->next;
+			break;
+		}
+	qunlock(&clients);
+
+	/* if last proc, free v */
+	vnclock(v);
+	if(++v->ndead < v->nproc){
+		vncunlock(v);
+		return;
+	}
+
+	freerlist(&v->rlist);
+	vncterm(v);
+	if(v->ctlfd >= 0)
+		close(v->ctlfd);
+	if(v->datafd >= 0)
+		close(v->datafd);
+	if(v->image)
+		freememimage(v->image);
+	free(v);
+}
+
+static void
+exiting(void)
+{
+	vncclose(*vncpriv);
+}
+
+void
+vnchungup(Vnc *v)
+{
+	if(verbose)
+		fprint(2, "%V: hangup\n", (Vncs*)v);
+	exits(0);	/* atexit and exiting() will take care of everything */
+}
+
+/*
+ * Kill all clients except safe.
+ * Used to start a non-shared client and at shutdown. 
+ */
+static void
+killclients(Vncs *safe)
+{
+	Vncs *v;
+
+	qlock(&clients);
+	for(v=clients.head; v; v=v->next){
+		if(v == safe)
+			continue;
+		if(v->ctlfd >= 0){
+			hangup(v->ctlfd);
+			close(v->ctlfd);
+			v->ctlfd = -1;
+			close(v->datafd);
+			v->datafd = -1;
+		}
+	}
+	qunlock(&clients);
+}
+
+/*
+ * Kill the executing command and then kill everyone else.
+ * Called to close up shop at the end of the day
+ * and also if we get an unexpected note.
+ */
+static char killkin[] = "die vnc kin";
+static void
+killall(void)
+{
+	postnote(PNGROUP, cmdpid, "hangup");
+	close(srvfd);
+	srvfd = -1;
+	close(exportfd);
+	exportfd = -1;
+	close(kbdin);
+	kbdin = -1;
+	postnote(PNGROUP, getpid(), killkin);
+}
+
+void
+shutdown(void)
+{
+	if(verbose)
+		fprint(2, "vnc server shutdown\n");
+	killall();
+}
+
+static void
+noteshutdown(void*, char *msg)
+{
+	if(strcmp(msg, killkin) == 0)	/* already shutting down */
+		noted(NDFLT);
+	killall();
+	noted(NDFLT);
+}
+
+/*
+ * Kill a specific instance of a server.
+ */
+static void
+vnckill(char *net, int display, int baseport)
+{
+	int fd, i, n, port;
+	char buf[NETPATHLEN], *p;
+
+	for(i=0;; i++){
+		snprint(buf, sizeof buf, "%s/tcp/%d/local", net, i);
+		if((fd = open(buf, OREAD)) < 0)
+			sysfatal("did not find display");
+		n = read(fd, buf, sizeof buf-1);
+		close(fd);
+		if(n <= 0)
+			continue;
+		buf[n] = 0;
+		p = strchr(buf, '!');
+		if(p == 0)
+			continue;
+		port = atoi(p+1);
+		if(port != display+baseport)
+			continue;
+		snprint(buf, sizeof buf, "%s/tcp/%d/ctl", net, i);
+		fd = open(buf, OWRITE);
+		if(fd < 0)
+			sysfatal("cannot open %s: %r", buf);
+		if(write(fd, "hangup", 6) != 6)
+			sysfatal("cannot hangup %s: %r", buf);
+		close(fd);
+		break;
+	}
+}
+
+/*
+ * Look for a port on which to announce.
+ * If display != -1, we only try that one.
+ * Otherwise we hunt.
+ *
+ * Returns the announce fd.
+ */
+static int
+vncannounce(char *net, int display, char *adir, int base)
+{
+	int port, eport, fd;
+	char addr[NETPATHLEN];
+
+	if(display == -1){
+		port = base;
+		eport = base+50;
+	}else{
+		port = base+display;
+		eport = port;
+	}
+
+	for(; port<=eport; port++){
+		snprint(addr, sizeof addr, "%s/tcp!*!%d", net, port);
+		if((fd = announce(addr, adir)) >= 0){
+			fprint(2, "server started on display :%d\n", port-base);
+			return fd;
+		}
+	}
+	if(display == -1)
+		fprint(2, "could not find any ports to announce\n");
+	else
+		fprint(2, "announce: %r\n");
+	return -1;
+}
+
+/*
+ * Handle a new connection.
+ */
+static void clientreadproc(Vncs*);
+static void clientwriteproc(Vncs*);
+static void chan2fmt(Pixfmt*, ulong);
+static ulong fmt2chan(Pixfmt*);
+
+static void
+vncaccept(Vncs *v)
+{
+	char buf[32];
+	int fd;
+
+	/* caller returns to listen */
+	switch(rfork(RFPROC|RFMEM|RFNAMEG)){
+	case -1:
+		fprint(2, "%V: fork failed: %r\n", v);
+		vncclose(v);
+		return;
+	default:
+		return;
+	case 0:
+		break;
+	}
+	*vncpriv = v;
+
+	if(!atexit(exiting)){
+		fprint(2, "%V: could not register atexit handler: %r; hanging up\n", v);
+		exiting();
+		exits(nil);
+	}
+
+	if(cert != nil){
+		TLSconn conn;
+
+		memset(&conn, 0, sizeof conn);
+		conn.cert = readcert(cert, &conn.certlen);
+		if(conn.cert == nil){
+			fprint(2, "%V: could not read cert %s: %r; hanging up\n", v, cert);
+			exits(nil);
+		}
+		fd = tlsServer(v->datafd, &conn);
+		if(fd < 0){
+			fprint(2, "%V: tlsServer: %r; hanging up\n", v);
+			free(conn.cert);
+			free(conn.sessionID);
+			exits(nil);
+		}
+		v->datafd = fd;
+		free(conn.cert);
+		free(conn.sessionID);
+	}
+	vncinit(v->datafd, v->ctlfd, v);
+
+	if(verbose)
+		fprint(2, "%V: handshake\n", v);
+	if(vncsrvhandshake(v) < 0){
+		fprint(2, "%V: handshake failed; hanging up\n", v);
+		exits(0);
+	}
+
+	if(noauth){
+		if(verbose)
+			fprint(2, "%V: noauth\n", v);
+		vncwrlong(v, ANoAuth);
+		vncflush(v);
+	} else {
+		if(verbose)
+			fprint(2, "%V: auth\n", v);
+		if(vncsrvauth(v) < 0){
+			fprint(2, "%V: auth failed; hanging up\n", v);
+			exits(0);
+		}
+	}
+
+	shared = vncrdchar(v);
+
+	if(verbose)
+		fprint(2, "%V: %sshared\n", v, shared ? "" : "not ");
+	if(!shared)
+		killclients(v);
+
+	v->dim = rectsubpt(gscreen->clipr, gscreen->clipr.min);
+	vncwrpoint(v, v->dim.max);
+	if(verbose)
+		fprint(2, "%V: send screen size %R\n", v, v->dim);
+
+	v->bpp = gscreen->depth;
+	v->depth = gscreen->depth;
+	v->truecolor = 1;
+	v->bigendian = 0;
+	chan2fmt(v, gscreen->chan);
+	if(verbose)
+		fprint(2, "%V: bpp=%d, depth=%d, chan=%s\n", v,
+			v->bpp, v->depth, chantostr(buf, gscreen->chan));
+	vncwrpixfmt(v, v);
+	vncwrlong(v, 14);
+	vncwrbytes(v, "Plan9 Desktop", 14);
+	vncflush(v);
+
+	if(verbose)
+		fprint(2, "%V: handshaking done\n", v);
+
+	v->updatereq = 0;
+
+	switch(rfork(RFPROC|RFMEM)){
+	case -1:
+		fprint(2, "%V: cannot fork: %r; hanging up\n", v);
+		vnchungup(v);
+	default:
+		clientreadproc(v);
+		exits(nil);
+	case 0:
+		*vncpriv = v;
+		v->nproc++;
+		if(atexit(exiting) == 0){
+			exiting();
+			fprint(2, "%V: could not register atexit handler: %r; hanging up\n", v);
+			exits(nil);
+		}
+		clientwriteproc(v);
+		exits(nil);
+	}
+}
+
+
+/*
+ * Set the pixel format being sent.  Can only happen once.
+ * (Maybe a client would send this again if the screen changed
+ * underneath it?  If we want to support this we need a way to
+ * make sure the current image is no longer in use, so we can free it. 
+ */
+static void
+setpixelfmt(Vncs *v)
+{
+	ulong chan;
+
+	vncgobble(v, 3);
+	v->Pixfmt = vncrdpixfmt(v);
+	chan = fmt2chan(v);
+	if(chan == 0){
+		fprint(2, "%V: bad pixel format; hanging up\n", v);
+		vnchungup(v);
+	}
+	v->imagechan = chan;
+}
+
+/*
+ * Set the preferred encoding list.  Can only happen once.
+ * If we want to support changing this more than once then
+ * we need to put a lock around the encoding functions
+ * so as not to conflict with updateimage.
+ */
+static void
+setencoding(Vncs *v)
+{
+	int n, x;
+
+	vncrdchar(v);
+	n = vncrdshort(v);
+	while(n-- > 0){
+		x = vncrdlong(v);
+		switch(x){
+		case EncCopyRect:
+			v->copyrect = 1;
+			continue;
+		case EncMouseWarp:
+			v->canwarp = 1;
+			continue;
+		case EncDesktopSize:
+			v->canresize |= 1;
+			continue;
+		case EncXDesktopSize:
+			v->canresize |= 2;
+			continue;
+		}
+		if(v->countrect != nil)
+			continue;
+		switch(x){
+		case EncRaw:
+			v->encname = "raw";
+			v->countrect = countraw;
+			v->sendrect = sendraw;
+			break;
+		case EncRre:
+			v->encname = "rre";
+			v->countrect = countrre;
+			v->sendrect = sendrre;
+			break;
+		case EncCorre:
+			v->encname = "corre";
+			v->countrect = countcorre;
+			v->sendrect = sendcorre;
+			break;
+		case EncHextile:
+			v->encname = "hextile";
+			v->countrect = counthextile;
+			v->sendrect = sendhextile;
+			break;
+		}
+	}
+
+	if(v->countrect == nil){
+		v->encname = "raw";
+		v->countrect = countraw;
+		v->sendrect = sendraw;
+	}
+
+	if(verbose)
+		fprint(2, "Encoding with %s%s%s%s\n", v->encname,
+			v->copyrect ? ", copyrect" : "",
+			v->canwarp ? ", canwarp" : "",
+			v->canresize ? ", resize" : "");
+}
+
+/*
+ * Continually read updates from one client.
+ */
+static void
+clientreadproc(Vncs *v)
+{
+	int incremental, key, keydown, buttons, type, x, y, n;
+	char *buf;
+	Rectangle r;
+
+	procsetname("read %V", v);
+
+	for(;;){
+		type = vncrdchar(v);
+		switch(type){
+		default:
+			fprint(2, "%V: unknown vnc message type %d; hanging up\n", v, type);
+			vnchungup(v);
+
+		/* set pixel format */
+		case MPixFmt:
+			setpixelfmt(v);
+			break;
+
+		/* ignore color map changes */
+		case MFixCmap:
+			vncgobble(v, 3);
+			n = vncrdshort(v);
+			vncgobble(v, n*6);
+			break;
+
+		/* set encoding list */
+		case MSetEnc:
+			setencoding(v);
+			break;
+
+		/* request image update in rectangle */
+		case MFrameReq:
+			incremental = vncrdchar(v);
+			r = vncrdrect(v);
+			if(!incremental){
+				qlock(&drawlock);	/* protects rlist */
+				addtorlist(&v->rlist, r);
+				qunlock(&drawlock);
+			}
+			v->updatereq++;
+			break;
+
+		case MSetDesktopSize:
+			vncrdchar(v);
+			vncrdpoint(v);	// desktop size
+			n = vncrdchar(v);
+			vncrdchar(v);
+			if(n == 0)
+				break;
+			vncrdlong(v);	// id
+			r = vncrdrect(v);
+			vncrdlong(v);	// flags
+			while(--n > 0){
+				vncrdlong(v);
+				vncrdrect(v);
+				vncrdlong(v);
+			}
+			qlock(&drawlock);
+			if(!rectclip(&r, gscreen->r)){
+				qunlock(&drawlock);
+				break;
+			}
+			gscreen->clipr = r;
+			qunlock(&drawlock);
+
+			screenwin();
+			deletescreenimage();
+			resetscreenimage();
+			break;
+
+		/* send keystroke */
+		case MKey:
+			keydown = vncrdchar(v);
+			vncgobble(v, 2);
+			key = vncrdlong(v);
+			vncputc(!keydown, key);
+			break;
+
+		/* send mouse event */
+		case MMouse:
+			buttons = vncrdchar(v);
+			x = vncrdshort(v);
+			y = vncrdshort(v);
+			absmousetrack(x, y, buttons, nsec()/(1000*1000LL));
+			break;
+
+		/* send cut text */
+		case MCCut:
+			vncgobble(v, 3);
+			n = vncrdlong(v);
+			buf = malloc(n+1);
+			if(buf){
+				vncrdbytes(v, buf, n);
+				buf[n] = 0;
+				vnclock(v);	/* for snarfvers */
+				setsnarf(buf, n, &v->snarfvers);
+				vncunlock(v);
+			}else
+				vncgobble(v, n);
+			break;
+		}
+	}
+}
+
+static int
+nbits(ulong mask)
+{
+	int n;
+
+	n = 0;
+	for(; mask; mask>>=1)
+		n += mask&1;
+	return n;
+}
+
+typedef struct Col Col;
+struct Col {
+	int type;
+	int nbits;
+	int shift;
+};
+
+static ulong
+fmt2chan(Pixfmt *fmt)
+{
+	Col c[4], t;
+	int i, j, depth, n, nc;
+	ulong mask, u;
+
+	/* unpack the Pixfmt channels */
+	c[0] = (Col){CRed, nbits(fmt->red.max), fmt->red.shift};
+	c[1] = (Col){CGreen, nbits(fmt->green.max), fmt->green.shift};
+	c[2] = (Col){CBlue, nbits(fmt->blue.max), fmt->blue.shift};
+	nc = 3;
+
+	/* add an ignore channel if necessary */
+	depth = c[0].nbits+c[1].nbits+c[2].nbits;
+	if(fmt->bpp != depth){
+		/* BUG: assumes only one run of ignored bits */
+		if(fmt->bpp == 32)
+			mask = ~0;
+		else
+			mask = (1<<fmt->bpp)-1;
+		mask ^= fmt->red.max << fmt->red.shift;
+		mask ^= fmt->green.max << fmt->green.shift;
+		mask ^= fmt->blue.max << fmt->blue.shift;
+		if(mask == 0)
+			abort();
+		n = 0;
+		for(; !(mask&1); mask>>=1)
+			n++;
+		c[3] = (Col){CIgnore, nbits(mask), n};
+		nc++;
+	}
+
+	/* sort the channels, largest shift (leftmost bits) first */
+	for(i=1; i<nc; i++)
+		for(j=i; j>0; j--)
+			if(c[j].shift > c[j-1].shift){
+				t = c[j];
+				c[j] = c[j-1];
+				c[j-1] = t;
+			}
+
+	/* build the channel descriptor */
+	u = 0;
+	for(i=0; i<nc; i++){
+		u <<= 8;
+		u |= CHAN1(c[i].type, c[i].nbits);
+	}
+
+	return u;
+}
+
+static void
+chan2fmt(Pixfmt *fmt, ulong chan)
+{
+	ulong c, rc, shift;
+
+	shift = 0;
+	for(rc = chan; rc; rc >>=8){
+		c = rc & 0xFF;
+		switch(TYPE(c)){
+		case CRed:
+			fmt->red = (Colorfmt){(1<<NBITS(c))-1, shift};
+			break;
+		case CBlue:
+			fmt->blue = (Colorfmt){(1<<NBITS(c))-1, shift};
+			break;
+		case CGreen:
+			fmt->green = (Colorfmt){(1<<NBITS(c))-1, shift};
+			break;
+		}
+		shift += NBITS(c);
+	}
+}
+
+/*
+ * Note that r has changed on the screen.
+ * Updating the rlists is okay because they are protected by drawlock.
+ */
+void
+flushmemscreen(Rectangle r)
+{
+	Vncs *v;
+
+	if(!rectclip(&r, gscreen->clipr))
+		return;
+	qlock(&clients);
+	for(v=clients.head; v; v=v->next)
+		addtorlist(&v->rlist, r);
+	qunlock(&clients);
+}
+
+/*
+ * Queue a mouse warp note for the next update to each client.
+ */
+void
+mousewarpnote(Point p)
+{
+	Vncs *v;
+
+	qlock(&clients);
+	for(v=clients.head; v; v=v->next){
+		if(v->canwarp){
+			vnclock(v);
+			v->dowarp = 1;
+			v->warppt = p;
+			vncunlock(v);
+		}
+	}
+	qunlock(&clients);
+}
+
+/*
+ * Send a client his changed screen image.
+ * v is locked on entrance, locked on exit, but released during.
+ */
+static int
+updateimage(Vncs *v)
+{
+	int i, j, ncount, nsend, docursor, dowarp, doresize;
+	vlong ooffset = 0, t1 = 0;
+	Point warppt;
+	Rectangle cr;
+	Rlist rlist;
+	int (*count)(Vncs*, Rectangle);
+	int (*send)(Vncs*, Rectangle);
+
+	vnclock(v);
+	dowarp = v->canwarp && v->dowarp;
+	warppt = v->warppt;
+	v->dowarp = 0;
+	vncunlock(v);
+
+	/* copy the screen bits and then unlock the screen so updates can proceed */
+	qlock(&drawlock);
+	rlist = v->rlist;
+	memset(&v->rlist, 0, sizeof v->rlist);
+
+	if(v->canresize && !eqrect(v->screen[0].rect, gscreen->clipr)){
+		v->screen[0].rect = gscreen->clipr;
+		v->dim = rectsubpt(gscreen->clipr, gscreen->clipr.min);
+		doresize = 1;
+	} else
+		doresize = 0;
+
+	if(doresize
+	|| (v->image == nil && v->imagechan != 0)
+	|| (v->image != nil && v->image->chan != v->imagechan)){
+		if(v->image)
+			freememimage(v->image);
+		v->image = allocmemimage(v->dim, v->imagechan);
+		if(v->image == nil){
+			fprint(2, "%V: allocmemimage: %r; hanging up\n", v);
+			qlock(&drawlock);
+			vnchungup(v);
+		}
+	}
+
+	/* if the cursor has moved or changed shape, we need to redraw its square */
+	lock(&cursor);
+	if(v->cursorver != cursorver || !eqpt(v->cursorpos, cursorpos)){
+		docursor = 1;
+		v->cursorver = cursorver;
+		v->cursorpos = cursorpos;
+		cr = cursorrect();
+	}else{
+		docursor = 0;
+		cr = v->cursorr;
+	}
+	unlock(&cursor);
+
+	if(docursor){
+		addtorlist(&rlist, v->cursorr);
+		if(!rectclip(&cr, gscreen->clipr))
+			cr.max = cr.min;
+		addtorlist(&rlist, cr);
+	}
+
+	/* copy changed screen parts, also check for parts overlapping cursor location */
+	for(i=0; i<rlist.nrect; i++){
+		if(!docursor)
+			docursor = rectXrect(v->cursorr, rlist.rect[i]);
+		memimagedraw(v->image, rlist.rect[i], gscreen, rlist.rect[i].min, memopaque, ZP, S);
+	}
+
+	if(docursor){
+		cursordraw(v->image, cr);
+		addtorlist(&rlist, v->cursorr);
+		v->cursorr = cr;
+	}
+
+	qunlock(&drawlock);
+
+	count = v->countrect;
+	send = v->sendrect;
+	if(count == nil || send == nil){
+		count = countraw;
+		send = sendraw;
+	}
+
+	ncount = 0;
+	for(i=j=0; i<rlist.nrect; i++){
+		if(j < i)
+			rlist.rect[j] = rlist.rect[i];
+		if(rectclip(&rlist.rect[j], v->dim))
+			ncount += (*count)(v, rlist.rect[j++]);
+	}
+	rlist.nrect = j;
+
+	if(doresize == 0 && ncount == 0 && dowarp == 0)
+		return 0;
+
+	if(verbose > 1){
+		fprint(2, "sendupdate: rlist.nrect=%d, ncount=%d\n", rlist.nrect, ncount);
+		t1 = nsec();
+		ooffset = Boffset(&v->out);
+	}
+
+	if(doresize && v->canresize == 1){
+		doresize = 0;
+
+		vncwrchar(v, MFrameUpdate);
+		vncwrchar(v, 0);
+		vncwrshort(v, 1);
+		vncwrrect(v, v->dim);
+		vncwrlong(v, EncDesktopSize);
+	}
+
+	vncwrchar(v, MFrameUpdate);
+	vncwrchar(v, 0);
+	vncwrshort(v, doresize+ncount+dowarp);
+
+	if(doresize){
+		vncwrrect(v, gscreen->r);
+		vncwrlong(v, EncXDesktopSize);
+		vncwrlong(v, 1<<24);
+		vncwrlong(v, v->screen[0].id);
+		vncwrrect(v, v->screen[0].rect);
+		vncwrlong(v, v->screen[0].flags);
+	}
+
+	nsend = 0;
+	for(i=0; i<rlist.nrect; i++)
+		nsend += (*send)(v, rlist.rect[i]);
+
+	if(ncount != nsend){
+		fprint(2, "%V: ncount=%d, nsend=%d; hanging up\n", v, ncount, nsend);
+		vnchungup(v);
+	}
+
+	if(dowarp){
+		vncwrrect(v, Rect(warppt.x, warppt.y, warppt.x+1, warppt.y+1));
+		vncwrlong(v, EncMouseWarp);
+	}
+
+	if(verbose > 1){
+		t1 = nsec() - t1;
+		fprint(2, " in %lldms, %lld bytes\n", t1/1000000, Boffset(&v->out) - ooffset);
+	}
+
+	freerlist(&rlist);
+	return 1;
+}
+
+/*
+ * Update the snarf buffer if it has changed.
+ */
+static void
+updatesnarf(Vncs *v)
+{
+	char *buf;
+	int len;
+
+	if(v->snarfvers == snarf.vers)
+		return;
+	qlock(&snarf);
+	len = snarf.n;
+	buf = malloc(len);
+	if(buf == nil){
+		qunlock(&snarf);
+		return;
+	}
+	memmove(buf, snarf.buf, len);
+	v->snarfvers = snarf.vers;
+	qunlock(&snarf);
+
+	vncwrchar(v, MSCut);
+	vncwrbytes(v, "pad", 3);
+	vncwrlong(v, len);
+	vncwrbytes(v, buf, len);
+	free(buf);
+}
+
+/*
+ * Continually update one client.
+ */
+static void
+clientwriteproc(Vncs *v)
+{
+	ulong last = 0;
+
+	procsetname("write %V", v);
+	while(!v->ndead){
+		sleep(sleeptime);
+		updatesnarf(v);
+		if(v->updatereq != last && updateimage(v))
+			last++;
+		vncflush(v);
+	}
+	vnchungup(v);
+}
+
--- /dev/null
+++ b/sys/src/cmd/vnc/vncs.h
@@ -1,0 +1,54 @@
+typedef struct	Rlist Rlist;
+typedef struct	Vncs	Vncs;
+
+struct Rlist
+{
+	Rectangle	bbox;
+	int	maxrect;
+	int	nrect;
+	Rectangle *rect;
+};
+
+struct Vncs
+{
+	Vnc;
+
+	Vncs	*next;
+	char		remote[NETPATHLEN];
+	char		netpath[NETPATHLEN];
+
+	char		*encname;
+	int		(*countrect)(Vncs*, Rectangle);
+	int		(*sendrect)(Vncs*, Rectangle);
+	int		copyrect;
+	int		canwarp;
+	int		dowarp;
+	Point		warppt;
+
+	ulong		updatereq;
+
+	Rlist		rlist;
+	int		ndead;
+	int		nproc;
+	int		cursorver;
+	Point		cursorpos;
+	Rectangle	cursorr;
+	int		snarfvers;
+
+	Memimage	*image;
+	ulong	imagechan;
+};
+
+/* rre.c */
+int	countcorre(Vncs*, Rectangle);
+int	counthextile(Vncs*, Rectangle);
+int	countraw(Vncs*, Rectangle);
+int	countrre(Vncs*, Rectangle);
+int	sendcorre(Vncs*, Rectangle);
+int	sendhextile(Vncs*, Rectangle);
+int	sendraw(Vncs*, Rectangle);
+int	sendrre(Vncs*, Rectangle);
+
+/* rlist.c */
+void addtorlist(Rlist*, Rectangle);
+void freerlist(Rlist*);
--- /dev/null
+++ b/sys/src/cmd/vnc/vncv.c
@@ -1,0 +1,205 @@
+#include "vnc.h"
+#include "vncv.h"
+#include <libsec.h>
+
+char*		charset = "utf-8";
+char*		encodings = "copyrect hextile corre rre raw mousewarp desktopsize xdesktopsize";
+int		bpp12;
+int		shared;
+int		verbose;
+Vnc*		vnc;
+int		mousefd;
+int		tls;
+
+
+static int	vncstart(Vnc*, int);
+
+enum
+{
+	NProcs	= 4
+};
+
+static int pids[NProcs];
+static char killkin[] = "die vnc kin";
+
+/*
+ * called by any proc when exiting to tear everything down.
+ */
+static void
+shutdown(void)
+{
+	int i, pid;
+
+	hangup(vnc->ctlfd);
+	close(vnc->ctlfd);
+	vnc->ctlfd = -1;
+	close(vnc->datafd);
+	vnc->datafd = -1;
+
+	pid = getpid();
+	for(i = 0; i < NProcs; i++)
+		if(pids[i] != 0 && pids[i] != pid)
+			postnote(PNPROC, pids[i], killkin);
+}
+
+char*
+netmkvncaddr(char *server)
+{
+	char *p, portstr[NETPATHLEN];
+	int port;
+
+	/* leave execnet dial strings alone */
+	if(strncmp(server, "exec!", 5) == 0)
+		return server;
+
+	port = 5900;
+	if(tls)
+		port = 35729;
+	if((p = strchr(server, ']')) == nil)
+		p = server;
+	if((p = strchr(p, ':')) != nil) {
+		*p++ = '\0';
+		port += atoi(p);
+	}
+	snprint(portstr, sizeof portstr, "%d", port);
+	return netmkaddr(server, "tcp", portstr);
+}
+
+void
+vnchungup(Vnc*)
+{
+	sysfatal("connection closed");
+}
+
+void
+usage(void)
+{
+	fprint(2, "usage: vncv [-e encodings] [-k keypattern] [-l charset] [-csv] host[:n]\n");
+	exits("usage");
+}
+
+void
+main(int argc, char **argv)
+{
+	int p, dfd, cfd, shared;
+	char *keypattern, *label;
+
+	keypattern = nil;
+	shared = 0;
+	ARGBEGIN{
+	case 'c':
+		bpp12 = 1;
+		break;
+	case 'e':
+		encodings = EARGF(usage());
+		break;
+	case 's':
+		shared = 1;
+		break;
+	case 't':
+		tls = 1;
+		break;
+	case 'v':
+		verbose++;
+		break;
+	case 'k':
+		keypattern = EARGF(usage());
+		break;
+	case 'l':
+		charset = EARGF(usage());
+		break;
+	default:
+		usage();
+	}ARGEND;
+
+	if(argc != 1)
+		usage();
+
+	dfd = dial(netmkvncaddr(argv[0]), nil, nil, &cfd);
+	if(dfd < 0)
+		sysfatal("cannot dial %s: %r", argv[0]);
+	if(tls){
+		TLSconn conn;
+
+		memset(&conn, 0, sizeof(conn));
+		if((dfd = tlsClient(dfd, &conn)) < 0)
+			sysfatal("tlsClient: %r");
+		/* XXX check thumbprint */
+		free(conn.cert);
+		free(conn.sessionID);
+	}
+	vnc = vncinit(dfd, cfd, nil);
+	vnc->srvaddr = strdup(argv[0]);
+
+	if(vnchandshake(vnc) < 0)
+		sysfatal("handshake failure: %r");
+	if(vncauth(vnc, keypattern) < 0)
+		sysfatal("authentication failure: %r");
+	if(vncstart(vnc, shared) < 0)
+		sysfatal("init failure: %r");
+
+	label = smprint("vnc %s", argv[0]);
+	if(initdraw(0, 0, label) < 0)
+		sysfatal("initdraw: %r");
+	free(label);
+	display->locking = 1;
+	unlockdisplay(display);
+
+	choosecolor(vnc);
+	sendencodings(vnc);
+	initmouse();
+
+	rfork(RFREND);
+	atexit(shutdown);
+	pids[0] = getpid();
+
+	switch(p = rfork(RFPROC|RFMEM)){
+	case -1:
+		sysfatal("rfork: %r");
+	default:
+		break;
+	case 0:
+		atexit(shutdown);
+		readfromserver(vnc);
+		exits(nil);
+	}
+	pids[1] = p;
+
+	switch(p = rfork(RFPROC|RFMEM)){
+	case -1:
+		sysfatal("rfork: %r");
+	default:
+		break;
+	case 0:
+		atexit(shutdown);
+		checksnarf(vnc);
+		exits(nil);
+	}
+	pids[2] = p;
+
+	switch(p = rfork(RFPROC|RFMEM)){
+	case -1:
+		sysfatal("rfork: %r");
+	default:
+		break;
+	case 0:
+		atexit(shutdown);
+		readkbd(vnc);
+		exits(nil);
+	}
+	pids[3] = p;
+
+	readmouse(vnc);
+	exits(nil);
+}
+
+static int
+vncstart(Vnc *v, int shared)
+{
+	vncwrchar(v, shared);
+	vncflush(v);
+	v->dim = Rpt(ZP, vncrdpoint(v));
+	v->Pixfmt = vncrdpixfmt(v);
+	v->name = vncrdstring(v);
+	return 0;
+}
--- /dev/null
+++ b/sys/src/cmd/vnc/vncv.h
@@ -1,0 +1,28 @@
+/* color.c */
+extern	void		choosecolor(Vnc*);
+extern	void		(*cvtpixels)(uchar*, uchar*, int);
+extern  void            settranslation(Vnc*);
+
+/* draw.c */
+extern	void		sendencodings(Vnc*);
+extern	void		requestupdate(Vnc*, int);
+extern	void		readfromserver(Vnc*);
+
+extern	uchar	zero[];
+
+/* vncv.c */
+extern	char		*charset;
+extern	char		*encodings;
+extern	int		bpp12;
+extern	Vnc*		vnc;
+extern	int		mousefd;
+
+/* wsys.c */
+extern	void		adjustwin(Vnc*, int);
+extern	void		readkbd(Vnc*);
+extern	void		initmouse(void);
+extern	void		mousewarp(Point);
+extern	void		readmouse(Vnc*);
+extern  void            senddim(Vnc*);
+extern  void            writesnarf(Vnc*, long);
+extern  void            checksnarf(Vnc*);
--- /dev/null
+++ b/sys/src/cmd/vnc/wsys.c
@@ -1,0 +1,323 @@
+#include "vnc.h"
+#include "vncv.h"
+#include <cursor.h>
+
+typedef struct Cursor Cursor;
+
+typedef struct Mouse Mouse;
+struct Mouse {
+	int buttons;
+	Point xy;
+};
+
+void
+adjustwin(Vnc *v, int force)
+{
+	int fd;
+	Point d;
+
+	if(force)
+		d = v->dim.max;
+	else {
+		/*
+		 * limit the window to at most the vnc server's size
+		 */
+		d = subpt(screen->r.max, screen->r.min);
+		if(d.x > v->dim.max.x){
+			d.x = v->dim.max.x;
+			force = 1;
+		}
+		if(d.y > v->dim.max.y){
+			d.y = v->dim.max.y;
+			force = 1;
+		}
+	}
+	if(force) {
+		fd = open("/dev/wctl", OWRITE);
+		if(fd >= 0){
+			fprint(fd, "resize -dx %d -dy %d", d.x+2*Borderwidth, d.y+2*Borderwidth);
+			close(fd);
+		}
+	}
+}
+
+static void
+resized(int first)
+{
+	lockdisplay(display);
+	if(getwindow(display, Refnone) < 0)
+		sysfatal("internal error: can't get the window image");
+	if((vnc->canresize&2) == 0)
+		adjustwin(vnc, first);
+	unlockdisplay(display);
+	requestupdate(vnc, 0);
+}
+
+static Cursor dotcursor = {
+	{-7, -7},
+	{0x00, 0x00,
+	 0x00, 0x00,
+	 0x00, 0x00,
+	 0x00, 0x00, 
+	 0x03, 0xc0,
+	 0x07, 0xe0,
+	 0x0f, 0xf0, 
+	 0x0f, 0xf0,
+	 0x0f, 0xf0,
+	 0x07, 0xe0,
+	 0x03, 0xc0,
+	 0x00, 0x00, 
+	 0x00, 0x00,
+	 0x00, 0x00,
+	 0x00, 0x00,
+	 0x00, 0x00, },
+	{0x00, 0x00,
+	 0x00, 0x00,
+	 0x00, 0x00,
+	 0x00, 0x00, 
+	 0x00, 0x00,
+	 0x03, 0xc0,
+	 0x07, 0xe0, 
+	 0x07, 0xe0,
+	 0x07, 0xe0,
+	 0x03, 0xc0,
+	 0x00, 0x00,
+	 0x00, 0x00, 
+	 0x00, 0x00,
+	 0x00, 0x00,
+	 0x00, 0x00,
+	 0x00, 0x00, }
+};
+
+static void
+mouseevent(Vnc *v, Mouse m)
+{
+	vnclock(v);
+	vncwrchar(v, MMouse);
+	vncwrchar(v, m.buttons);
+	vncwrpoint(v, m.xy);
+	vncflush(v);
+	vncunlock(v);
+}
+
+void
+mousewarp(Point pt)
+{
+	pt = addpt(pt, screen->r.min);
+	if(fprint(mousefd, "m%d %d", pt.x, pt.y) < 0)
+		fprint(2, "mousefd write: %r\n");
+}
+
+void
+initmouse(void)
+{
+	char buf[1024];
+
+	snprint(buf, sizeof buf, "%s/mouse", display->devdir);
+	if((mousefd = open(buf, ORDWR)) < 0)
+		sysfatal("open %s: %r", buf);
+}
+
+enum {
+	EventSize = 1+4*12
+};
+void
+readmouse(Vnc *v)
+{
+	int cursorfd, len, n;
+	char buf[10*EventSize], *start, *end;
+	uchar curs[2*4+2*2*16];
+	Cursor *cs;
+	Mouse m;
+
+	cs = &dotcursor;
+
+	snprint(buf, sizeof buf, "%s/cursor", display->devdir);
+	if((cursorfd = open(buf, OWRITE)) < 0)
+		sysfatal("open %s: %r", buf);
+
+	BPLONG(curs+0*4, cs->offset.x);
+	BPLONG(curs+1*4, cs->offset.y);
+	memmove(curs+2*4, cs->clr, 2*2*16);
+	write(cursorfd, curs, sizeof curs);
+
+	resized(1);
+	start = end = buf;
+	len = 0;
+	for(;;){
+		if((n = read(mousefd, end, sizeof(buf) - (end - buf))) < 0)
+			sysfatal("read mouse failed");
+
+		len += n;
+		end += n;
+		while(len >= EventSize){
+			if(*start == 'm'){
+				m.xy.x = atoi(start+1);
+				m.xy.y = atoi(start+1+12);
+				m.buttons = atoi(start+1+2*12) & 0x1F;
+				m.xy = subpt(m.xy, screen->r.min);
+				if(ptinrect(m.xy, v->dim)){
+					mouseevent(v, m);
+					/* send wheel button *release* */ 
+					if ((m.buttons & 0x7) != m.buttons) {
+						m.buttons &= 0x7;
+						mouseevent(v, m);
+					}
+				}
+			} else
+				resized(0);
+			start += EventSize;
+			len -= EventSize;
+		}
+		if(start - buf > sizeof(buf) - EventSize){
+			memmove(buf, start, len);
+			start = buf;
+			end = start+len;
+		}
+	}
+}
+
+static int
+tcs(int fd0, int fd1)
+{
+	int pfd[2];
+
+	if(strcmp(charset, "utf-8") == 0)
+		goto Dup;
+	if(pipe(pfd) < 0)
+		goto Dup;
+	switch(rfork(RFPROC|RFFDG|RFMEM)){
+	case -1:
+		close(pfd[0]);
+		close(pfd[1]);
+		goto Dup;
+	case 0:
+		if(fd0 < 0){
+			dup(pfd[0], 0);
+			dup(fd1, 1);
+			close(fd1);	
+		} else {
+			dup(pfd[0], 1);
+			dup(fd0, 0);
+			close(fd0);	
+		}
+		close(pfd[0]);
+		close(pfd[1]);
+		execl("/bin/tcs", "tcs", fd0 < 0 ? "-f" : "-t", charset, nil);
+		execl("/bin/cat", "cat", nil);
+		_exits(0);
+	}
+	close(pfd[0]);
+	return pfd[1];
+Dup:
+	return dup(fd0 < 0 ? fd1 : fd0, -1);
+}
+
+static int snarffd = -1;
+static ulong snarfvers;
+
+static int
+gotsnarf(void)
+{
+	Dir *dir;
+	int ret;
+
+	if(snarffd < 0 || (dir = dirfstat(snarffd)) == nil)
+		return 0;
+
+	ret = dir->qid.vers != snarfvers;
+	snarfvers = dir->qid.vers;
+	free(dir);
+
+	return ret;
+}
+
+void 
+writesnarf(Vnc *v, long n)
+{
+	uchar buf[8192];
+	int fd, sfd;
+	long m;
+
+	vnclock(v);
+	fd = -1;
+	if((sfd = create("/dev/snarf", OWRITE, 0666)) >= 0){
+		fd = tcs(-1, sfd);
+		close(sfd);
+	}
+	if(fd < 0)
+		vncgobble(v, n);
+	else {
+		while(n > 0){
+			m = n;
+			if(m > sizeof(buf))
+				m = sizeof(buf);
+			vncrdbytes(v, buf, m);
+			n -= m;
+			write(fd, buf, m);
+		}
+		close(fd);
+		waitpid();
+	}
+	gotsnarf();
+	vncunlock(v);
+}
+
+char *
+getsnarf(int *sz)
+{
+	char *snarf, *p;
+	int fd, n, c;
+
+	*sz =0;
+	n = 8192;
+	p = snarf = malloc(n);
+
+	seek(snarffd, 0, 0);
+	if((fd = tcs(snarffd, -1)) >= 0){
+		while((c = read(fd, p, n)) > 0){
+			p += c;
+			n -= c;
+			*sz += c;
+			if (n == 0){
+				snarf = realloc(snarf, *sz + 8192);
+				p = snarf + *sz;
+				n = 8192;
+			}
+		}
+		close(fd);
+		waitpid();
+	}
+	return snarf;
+}
+
+void
+checksnarf(Vnc *v)
+{
+	char *snarf;
+	int len;
+
+	if(snarffd < 0){
+		snarffd = open("/dev/snarf", OREAD);
+		if(snarffd < 0)
+			sysfatal("can't open /dev/snarf: %r");
+	}
+
+	for(;;){
+		sleep(1000);
+
+		vnclock(v);
+		if(gotsnarf()){
+			snarf = getsnarf(&len);
+
+			vncwrchar(v, MCCut);
+			vncwrbytes(v, "pad", 3);
+			vncwrlong(v, len);
+			vncwrbytes(v, snarf, len);
+			vncflush(v);
+
+			free(snarf);
+		}
+		vncunlock(v);
+	}
+}