ref: 04c22e3803910733329fc61859aabb47bdc2a31b
parent: 4baf1f15bd5dc66b815863eed931a9132a70ee7a
author: qwx <[email protected]>
date: Sun Aug 13 23:18:21 EDT 2023
vnc: color regression workaround
--- /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(®);
+ region_union(®, r1, r1);
+ region_union(®, r2, r2);
+ region_union(®, 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);
+ }
+}