diff options
author | Taru Karttunen <taruti@taruti.net> | 2011-03-30 15:46:40 +0300 |
---|---|---|
committer | Taru Karttunen <taruti@taruti.net> | 2011-03-30 15:46:40 +0300 |
commit | e5888a1ffdae813d7575f5fb02275c6bb07e5199 (patch) | |
tree | d8d51eac403f07814b9e936eed0c9a79195e2450 /sys/src/cmd/vnc |
Import sources from 2011-03-30 iso image
Diffstat (limited to 'sys/src/cmd/vnc')
33 files changed, 11818 insertions, 0 deletions
diff --git a/sys/src/cmd/vnc/auth.c b/sys/src/cmd/vnc/auth.c new file mode 100755 index 000000000..508c65366 --- /dev/null +++ b/sys/src/cmd/vnc/auth.c @@ -0,0 +1,242 @@ +#include "vnc.h" +#include <libsec.h> +#include <auth.h> + +char *serveraddr; + +/* + * Encrypt n bytes using the password + * as key, padded with zeros to 8 bytes. + */ +enum +{ + VerLen = 12 +}; + +static char version[VerLen+1] = "RFB 003.003\n"; + +static uchar tab[256]; + +/* VNC reverses the bits of each byte before using as a des key */ +static void +mktab(void) +{ + int i, j, k; + static int once; + + if(once) + return; + once = 1; + + for(i=0; i<256; i++){ + j=i; + tab[i] = 0; + for(k=0; k<8; k++){ + tab[i] = (tab[i]<<1) | (j&1); + j >>= 1; + } + } +} + +static void +vncencrypt(uchar *buf, int n, char *pw) +{ + uchar *p; + uchar key[9]; + DESstate s; + + mktab(); + memset(key, 0, sizeof key); + strncpy((char*)key, pw, 8); + for(p=key; *p; p++) + *p = tab[*p]; + + setupDESstate(&s, key, nil); + desECBencrypt(buf, n, &s); +} + +static int +readln(char *prompt, char *line, int len) +{ + char *p; + int fd, ctl, n, nr; + + fd = open("/dev/cons", ORDWR); + if(fd < 0) + sysfatal("couldn't open cons"); + ctl = open("/dev/consctl", OWRITE); + if(ctl < 0) + sysfatal("couldn't open consctl"); + write(ctl, "rawon", 5); + fprint(fd, "%s", prompt); + nr = 0; + p = line; + for(;;){ + n = read(fd, p, 1); + if(n < 0){ + close(fd); + close(ctl); + return -1; + } + if(n == 0 || *p == '\n' || *p == '\r'){ + *p = '\0'; + write(fd, "\n", 1); + close(fd); + close(ctl); + return nr; + } + if(*p == '\b'){ + if(nr > 0){ + nr--; + p--; + } + }else if(*p == 21){ /* cntrl-u */ + fprint(fd, "\n%s", prompt); + nr = 0; + p = line; + }else{ + nr++; + p++; + } + if(nr == len){ + fprint(fd, "line too long; try again\n%s", prompt); + nr = 0; + p = line; + } + } +} + +int +vncsrvhandshake(Vnc *v) +{ + char msg[VerLen+1]; + + strecpy(msg, msg+sizeof msg, version); + if(verbose) + fprint(2, "server version: %s", msg); + vncwrbytes(v, msg, VerLen); + vncflush(v); + + vncrdbytes(v, msg, VerLen); + if(verbose) + fprint(2, "client version: %s", msg); + return 0; +} + +int +vnchandshake(Vnc *v) +{ + char msg[VerLen+1]; + + msg[VerLen] = 0; + vncrdbytes(v, msg, VerLen); + if(strncmp(msg, "RFB ", 4) != 0){ + werrstr("bad rfb version \"%s\"", msg); + return -1; + } + if(verbose) + fprint(2, "server version: %s", msg); + strcpy(msg, version); + vncwrbytes(v, msg, VerLen); + vncflush(v); + return 0; +} + +int +vncauth(Vnc *v, char *keypattern) +{ + char pw[128], *reason; + uchar chal[VncChalLen]; + ulong auth; + char *p, *server; + + if(keypattern == nil) + keypattern = ""; + auth = vncrdlong(v); + switch(auth){ + default: + werrstr("unknown auth type 0x%lux", auth); + if(verbose) + fprint(2, "unknown auth type 0x%lux", auth); + return -1; + + case AFailed: + reason = vncrdstring(v); + werrstr("%s", reason); + if(verbose) + fprint(2, "auth failed: %s\n", reason); + return -1; + + case ANoAuth: + if(verbose) + fprint(2, "no auth needed"); + break; + + case AVncAuth: + vncrdbytes(v, chal, VncChalLen); + server = strdup(serveraddr); + p = strrchr(server, ':'); + if(p) + *p = 0; + if(auth_respond(chal, VncChalLen, nil, 0, chal, VncChalLen, auth_getkey, + "proto=vnc role=client server=%s %s", server, keypattern) != VncChalLen){ + /* BUG This is for drawterm users who don't start their own factotums */ + readln("password: ", pw, sizeof(pw)); + vncencrypt(chal, VncChalLen, pw); + memset(pw, 0, sizeof pw); + } + free(server); + vncwrbytes(v, chal, VncChalLen); + vncflush(v); + + auth = vncrdlong(v); + switch(auth){ + default: + werrstr("unknown server response 0x%lux", auth); + return -1; + case VncAuthFailed: + werrstr("server says authentication failed"); + return -1; + case VncAuthTooMany: + werrstr("server says too many tries"); + return -1; + case VncAuthOK: + break; + } + 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; +} + diff --git a/sys/src/cmd/vnc/chan.c b/sys/src/cmd/vnc/chan.c new file mode 100755 index 000000000..14bcdf390 --- /dev/null +++ b/sys/src/cmd/vnc/chan.c @@ -0,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); +} diff --git a/sys/src/cmd/vnc/color.c b/sys/src/cmd/vnc/color.c new file mode 100755 index 000000000..485ad4c65 --- /dev/null +++ b/sys/src/cmd/vnc/color.c @@ -0,0 +1,169 @@ +#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++]; +} + +void +choosecolor(Vnc *v) +{ + int bpp, depth; + ulong chan; + + bpp = screen->depth; + if((bpp / 8) * 8 != bpp) + sysfatal("screen not supported"); + + depth = screen->depth; + chan = screen->chan; + + 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"); + + 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); +} diff --git a/sys/src/cmd/vnc/compat.c b/sys/src/cmd/vnc/compat.c new file mode 100755 index 000000000..2b408a546 --- /dev/null +++ b/sys/src/cmd/vnc/compat.c @@ -0,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); +} + diff --git a/sys/src/cmd/vnc/compat.h b/sys/src/cmd/vnc/compat.h new file mode 100755 index 000000000..6c3acfe6c --- /dev/null +++ b/sys/src/cmd/vnc/compat.h @@ -0,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 diff --git a/sys/src/cmd/vnc/dev.c b/sys/src/cmd/vnc/dev.c new file mode 100755 index 000000000..5305e498d --- /dev/null +++ b/sys/src/cmd/vnc/dev.c @@ -0,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; +} diff --git a/sys/src/cmd/vnc/devcons.c b/sys/src/cmd/vnc/devcons.c new file mode 100755 index 000000000..16804fce4 --- /dev/null +++ b/sys/src/cmd/vnc/devcons.c @@ -0,0 +1,432 @@ +#include <u.h> +#include <libc.h> +#include "compat.h" +#include "kbd.h" +#include "error.h" + +typedef struct Queue Queue; +struct Queue +{ + QLock qwait; + Rendez rwait; + + Lock lock; + int notempty; + char buf[1024]; + char *w; + char *r; + char *e; +}; + +Queue* kbdq; /* unprocessed console input */ +Queue* lineq; /* processed console input */ +Snarf snarf = { + .vers = 1 +}; + +static struct +{ + QLock; + int raw; /* true if we shouldn't process input */ + int ctl; /* number of opens to the control file */ + int x; /* index into line */ + char line[1024]; /* current input line */ +} kbd; + +/* + * cheapo fixed-length queues + */ +static void +qwrite(Queue *q, void *v, int n) +{ + char *buf, *next; + int i; + + buf = v; + lock(&q->lock); + for(i = 0; i < n; i++){ + next = q->w+1; + if(next >= q->e) + next = q->buf; + if(next == q->r) + break; + *q->w = buf[i]; + q->w = next; + } + q->notempty = 1; + unlock(&q->lock); + rendwakeup(&q->rwait); +} + +static int +qcanread(void *vq) +{ + Queue *q; + int ne; + + q = vq; + lock(&q->lock); + ne = q->notempty; + unlock(&q->lock); + return ne; +} + +static int +qread(Queue *q, void *v, int n) +{ + char *a; + int nn, notempty; + + if(n == 0) + return 0; + a = v; + nn = 0; + for(;;){ + lock(&q->lock); + + while(nn < n && q->r != q->w){ + a[nn++] = *q->r++; + if(q->r >= q->e) + q->r = q->buf; + } + + notempty = q->notempty; + q->notempty = q->r != q->w; + unlock(&q->lock); + if(notempty) + break; + + /* + * wait for something to show up in the kbd buffer. + */ + qlock(&q->qwait); + if(waserror()){ + qunlock(&q->qwait); + nexterror(); + } + rendsleep(&q->rwait, qcanread, q); + qunlock(&q->qwait); + poperror(); + } + return nn; +} + +static Queue * +mkqueue(void) +{ + Queue *q; + + q = smalloc(sizeof(Queue)); + q->r = q->buf; + q->w = q->r; + q->e = &q->buf[sizeof q->buf]; + q->notempty = 0; + return q; +} + +static void +echoscreen(char *buf, int n) +{ + char *e, *p; + char ebuf[128]; + int x; + + p = ebuf; + e = ebuf + sizeof(ebuf) - 4; + while(n-- > 0){ + if(p >= e){ + screenputs(ebuf, p - ebuf); + p = ebuf; + } + x = *buf++; + if(x == 0x15){ + *p++ = '^'; + *p++ = 'U'; + *p++ = '\n'; + } else + *p++ = x; + } + if(p != ebuf) + screenputs(ebuf, p - ebuf); +} + +/* + * Put character, possibly a rune, into read queue at interrupt time. + * Called at interrupt time to process a character. + */ +void +kbdputc(int ch) +{ + int n; + char buf[3]; + Rune r; + + r = ch; + n = runetochar(buf, &r); + qwrite(kbdq, buf, n); + if(!kbd.raw) + echoscreen(buf, n); +} + +static void +kbdputcinit(void) +{ + kbdq = mkqueue(); + lineq = mkqueue(); + kbd.raw = 0; + kbd.ctl = 0; + kbd.x = 0; +} + +enum{ + Qdir, + Qcons, + Qconsctl, + Qsnarf, + Qwinname, +}; + +static Dirtab consdir[]={ + ".", {Qdir, 0, QTDIR}, 0, DMDIR|0555, + "cons", {Qcons}, 0, 0660, + "consctl", {Qconsctl}, 0, 0220, + "snarf", {Qsnarf}, 0, 0600, + "winname", {Qwinname}, 0, 0000, +}; + +static void +consinit(void) +{ + kbdputcinit(); +} + +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: + qlock(&kbd); + kbd.ctl++; + qunlock(&kbd); + 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: + if(c->flag&COPEN){ + qlock(&kbd); + if(--kbd.ctl == 0) + kbd.raw = 0; + qunlock(&kbd); + } + 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) +{ + char ch; + int send; + + 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: + qlock(&kbd); + if(waserror()){ + qunlock(&kbd); + nexterror(); + } + while(!qcanread(lineq)){ + qread(kbdq, &ch, 1); + send = 0; + if(ch == 0){ + /* flush output on rawoff -> rawon */ + if(kbd.x > 0) + send = !qcanread(kbdq); + }else if(kbd.raw){ + kbd.line[kbd.x++] = ch; + send = !qcanread(kbdq); + }else{ + switch(ch){ + case '\b': + if(kbd.x > 0) + kbd.x--; + break; + case 0x15: /* ^U */ + kbd.x = 0; + break; + case '\n': + case 0x04: /* ^D */ + send = 1; + default: + if(ch != 0x04) + kbd.line[kbd.x++] = ch; + break; + } + } + if(send || kbd.x == sizeof kbd.line){ + qwrite(lineq, kbd.line, kbd.x); + kbd.x = 0; + } + } + n = qread(lineq, buf, n); + qunlock(&kbd); + poperror(); + return n; + + 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 buf[256], *a; + char ch; + + switch((ulong)c->qid.path){ + case Qcons: + screenputs(va, n); + break; + + case Qconsctl: + if(n >= sizeof(buf)) + n = sizeof(buf)-1; + strncpy(buf, va, n); + buf[n] = 0; + for(a = buf; a;){ + if(strncmp(a, "rawon", 5) == 0){ + kbd.raw = 1; + /* clumsy hack - wake up reader */ + ch = 0; + qwrite(kbdq, &ch, 1); + } else if(strncmp(a, "rawoff", 6) == 0){ + kbd.raw = 0; + } + if(a = strchr(a, ' ')) + a++; + } + 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, + consinit, + consattach, + conswalk, + consstat, + consopen, + devcreate, + consclose, + consread, + devbread, + conswrite, + devbwrite, + devremove, + devwstat, +}; diff --git a/sys/src/cmd/vnc/devdraw.c b/sys/src/cmd/vnc/devdraw.c new file mode 100755 index 000000000..4c8f25ae5 --- /dev/null +++ b/sys/src/cmd/vnc/devdraw.c @@ -0,0 +1,2086 @@ +#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, + 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; + +ulong blanktime = 30; /* in minutes; a half hour */ + +struct Draw +{ + QLock; + int clientid; + int nclient; + Client** client; + int nname; + DName* name; + int vers; + int softscreen; + int blanked; /* screen turned off */ + ulong blanktime; /* time of last operation */ + ulong savemap[3*256]; +}; + +struct Client +{ + Ref r; + DImage* dimage[NHASH]; + CScreen* cscreen; + Refresh* refresh; + Rendez refrend; + 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; +static Memimage *screenimage; +static Memdata screendata; +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); + +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 Edepth[] = "image has bad depth"; +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"; + +void +drawlock(void) +{ + qlock(&sdraw); +} + +void +drawunlock(void) +{ + qunlock(&sdraw); +} + +int +candrawlock(void) +{ + return canqlock(&sdraw); +} + +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); + if(t == Qtopdir){ + switch(s){ + case 0: + mkqid(&q, Q2nd, 0, QTDIR); + devdir(c, q, "draw", 0, eve, 0555, dp); + break; + default: + return -1; + } + return 1; + } + + /* + * Second level contains "new" plus all the clients. + */ + if(t == Q2nd || t == Qnew){ + if(s == 0){ + mkqid(&q, Qnew, 0, QTFILE); + devdir(c, q, "new", 0, eve, 0666, dp); + } + else if(s <= sdraw.nclient){ + cl = sdraw.client[s-1]; + if(cl == 0) + 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; + } + else + 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 *l, Rectangle r, void *v) +{ + Refx *x; + DImage *d; + Client *c; + Refresh *ref; + + USED(l); + 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 || !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 */ + 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; + } + l = dst->layer; + if(l == 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); +} + +static +void +drawflush(void) +{ + 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; +} + +Memimage* +drawinstall(Client *client, int id, Memimage *i, DScreen *dscreen) +{ + DImage *d; + + d = malloc(sizeof(DImage)); + if(d == 0) + return 0; + d->id = id; + d->ref = 1; + d->name = 0; + d->vers = 0; + d->image = i; + d->nfchar = 0; + d->fchar = 0; + d->fromname = 0; + 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; + + 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; + } + if(dimage->image == screenimage) /* don't free the display */ + goto Return; + ds = dimage->dscreen; + if(ds){ + l = dimage->image; + if(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, Point p, Memimage *src, Point *sp, DImage *font, int index, int op) +{ + FChar *fc; + Rectangle r; + Point sp1; + + 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; + memdraw(dst, r, src, sp1, font->image, Pt(fc->minx, fc->miny), op); + p.x += fc->width; + sp->x += fc->width; + return p; +} + +static int +initscreenimage(void) +{ + int width, depth; + ulong chan; + Rectangle r; + + if(screenimage != nil) + return 1; + + screendata.base = nil; + screendata.bdata = attachscreen(&r, &chan, &depth, &width, &sdraw.softscreen); + if(screendata.bdata == nil) +{fprint(2, "bad bdata\n"); + return 0; +} + screendata.ref = 1; + + screenimage = allocmemimaged(r, chan, &screendata); + if(screenimage == nil){ +fprint(2, "bad memimaged: %r\n"); + /* RSC: BUG: detach screen */ + return 0; + } + + screenimage->width = width; + screenimage->clipr = screenimage->r; + return 1; +} + +void +deletescreenimage(void) +{ + qlock(&sdraw); + /* RSC: BUG: detach screen */ + if(screenimage) + freememimage(screenimage); + screenimage = nil; + qunlock(&sdraw); +} + +Chan* +drawattach(char *spec) +{ + qlock(&sdraw); + if(!initscreenimage()){ + qunlock(&sdraw); + error("no frame buffer"); + } + qunlock(&sdraw); + return devattach('i', spec); +} + +Walkqid* +drawwalk(Chan *c, Chan *nc, char **name, int nname) +{ + if(screendata.bdata == 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; + + if(c->qid.type & QTDIR) + return devopen(c, omode, 0, 0, drawgen); + + qlock(&sdraw); + if(waserror()){ + qunlock(&sdraw); + 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 Qnew: + break; + + case Qctl: + cl = drawclient(c); + if(cl->busy) + error(Einuse); + cl->busy = 1; + flushrect = Rect(10000, 10000, -10000, -10000); + drawinstall(cl, 0, screenimage, 0); + incref(&cl->r); + break; + case Qcolormap: + case Qdata: + case Qrefresh: + cl = drawclient(c); + incref(&cl->r); + break; + } + qunlock(&sdraw); + poperror(); + c->mode = openmode(omode); + c->flag |= COPEN; + c->offset = 0; + return c; +} + +static void +drawclose(Chan *c) +{ + int i; + DImage *d, **dp; + Client *cl; + Refresh *r; + + if(c->qid.type & QTDIR) + return; + qlock(&sdraw); + if(waserror()){ + qunlock(&sdraw); + 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); + } + qunlock(&sdraw); + 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]; + + USED(offset); + if(c->qid.type & QTDIR) + return devdirread(c, a, n, 0, 0, drawgen); + cl = drawclient(c); + qlock(&sdraw); + if(waserror()){ + qunlock(&sdraw); + 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); + cl->infoid = -1; + break; + + case Qcolormap: + drawactive(1); /* to restore map from backup */ + 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; + qunlock(&sdraw); + if(waserror()){ + qlock(&sdraw); /* restore lock for waserror() above */ + nexterror(); + } + rendsleep(&cl->refrend, drawrefactive, cl); + poperror(); + qlock(&sdraw); + } + 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; + } + qunlock(&sdraw); + 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 off) +{ + char buf[128], *fields[4], *q; + Client *cl; + int i, m, red, green, blue, x; + ulong offset = off; + + USED(offset); + if(c->qid.type & QTDIR) + error(Eisdir); + cl = drawclient(c); + qlock(&sdraw); + if(waserror()){ + drawwakeall(); + qunlock(&sdraw); + nexterror(); + } + switch(QID(c->qid)){ + case Qctl: + if(n != 4) + error("unknown draw control request"); + cl->infoid = BGLONG((uchar*)a); + break; + + case Qcolormap: + drawactive(1); /* to restore map from backup */ + 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(getfields(buf, fields, nelem(fields), 1, " ") != 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); + } + qunlock(&sdraw); + 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, *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->error); */ + 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 = BGSHORT(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("can't 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); + drawdebug = a[1]; + 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("can't use display as font"); + font = drawlookup(client, dstid, 1); + if(font == 0) + error(Enodrawimage); + if(font->image->layer) + error("can't use window as font"); + free(font->fchar); /* should we complain if non-zero? */ + ni = BGLONG(a+5); + 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: can't 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 || (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); + if(0 && i->layer) + error("readimage from window unimplemented"); + 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 = unloadmemimage(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); + if(*a == 'x'){ + /* paint background */ + l = 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, l, 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, 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[0]->layer == 0) + error("images are not windows"); + for(j=1; j<nw; j++) + 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(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; + + drawactive(1); /* to restore map from backup */ + 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); + } + } +} + +void +drawblankscreen(int blank) +{ + int i, nc; + ulong *p; + + if(blank == sdraw.blanked) + return; + if(!canqlock(&sdraw)) + return; + if(!initscreenimage()){ + qunlock(&sdraw); + return; + } + p = sdraw.savemap; + nc = screenimage->depth > 8 ? 256 : 1<<screenimage->depth; + + /* + * blankscreen uses the hardware to blank the screen + * when possible. to help in cases when it is not possible, + * we set the color map to be all black. + */ + if(blank == 0){ /* turn screen on */ + for(i=0; i<nc; i++, p+=3) + setcolor(i, p[0], p[1], p[2]); + blankscreen(0); + }else{ /* turn screen off */ + blankscreen(1); + for(i=0; i<nc; i++, p+=3){ + getcolor(i, &p[0], &p[1], &p[2]); + setcolor(i, 0, 0, 0); + } + } + sdraw.blanked = blank; + qunlock(&sdraw); +} + +/* + * record activity on screen, changing blanking as appropriate + */ +void +drawactive(int active) +{ + if(active){ + drawblankscreen(0); + sdraw.blanktime = 0; + }else + sdraw.blanktime++; +} diff --git a/sys/src/cmd/vnc/devmouse.c b/sys/src/cmd/vnc/devmouse.c new file mode 100755 index 000000000..232808aed --- /dev/null +++ b/sys/src/cmd/vnc/devmouse.c @@ -0,0 +1,447 @@ +#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 +{ + Mousestate; + int dx; + int dy; + int track; /* dx & dy updated */ + int redraw; /* update cursor on screen */ + ulong lastcounter; /* value when /dev/mouse read */ + Rendez r; + Ref; + QLock; + int open; + int acceleration; + int maxacc; + Mousestate queue[16]; /* circular buffer of click events */ + int ri; /* read index into queue */ + int wi; /* write index into queue */ + uchar qfull; /* queue is full */ +}; + +Mouseinfo mouse; +Cursorinfo cursor; +int mouseshifted; +Cursor curs; + +void Cursortocursor(Cursor*); +int mousechanged(void*); +static void mouseclock(void); + +enum{ + Qdir, + Qcursor, + Qmouse, +}; + +static Dirtab mousedir[]={ + ".", {Qdir, 0, QTDIR}, 0, DMDIR|0555, + "cursor", {Qcursor}, 0, 0666, + "mouse", {Qmouse}, 0, 0666, +}; + +static uchar buttonmap[8] = { + 0, 1, 2, 3, 4, 5, 6, 7, +}; +static int mouseswap; + +extern Memimage* gscreen; +extern void mousewarpnote(Point); + +static void +mousereset(void) +{ + curs = arrow; + Cursortocursor(&arrow); +} + +static void +mouseinit(void) +{ + cursoron(1); +} + +static Chan* +mouseattach(char *spec) +{ + return devattach('m', spec); +} + +static Walkqid* +mousewalk(Chan *c, Chan *nc, char **name, int nname) +{ + Walkqid *wq; + + wq = devwalk(c, nc, name, nname, mousedir, nelem(mousedir), devgen); + if(wq != nil && wq->clone != c && (wq->clone->qid.type&QTDIR)==0) + incref(&mouse); + return wq; +} + +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) +{ + switch((ulong)c->qid.path){ + case Qdir: + if(omode != OREAD) + error(Eperm); + break; + case Qmouse: + lock(&mouse); + if(mouse.open){ + unlock(&mouse); + error(Einuse); + } + mouse.open = 1; + mouse.ref++; + unlock(&mouse); + break; + default: + incref(&mouse); + } + c->mode = openmode(omode); + c->flag |= COPEN; + c->offset = 0; + return c; +} + +static void +mousecreate(Chan*, char*, int, ulong) +{ + error(Eperm); +} + +static void +mouseclose(Chan *c) +{ + if((c->qid.type&QTDIR)==0 && (c->flag&COPEN)){ + lock(&mouse); + if(c->qid.path == Qmouse) + mouse.open = 0; + if(--mouse.ref == 0){ + cursoroff(1); + curs = arrow; + Cursortocursor(&arrow); + cursoron(1); + } + unlock(&mouse); + } +} + + +static long +mouseread(Chan *c, void *va, long n, vlong off) +{ + char buf[4*12+1]; + uchar *p; + static int map[8] = {0, 4, 2, 6, 1, 5, 3, 7 }; + ulong offset = off; + Mousestate m; + int b; + + 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; + lock(&cursor); + 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); + unlock(&cursor); + return n; + + case Qmouse: + while(mousechanged(0) == 0) + rendsleep(&mouse.r, mousechanged, 0); + + mouse.qfull = 0; + + /* + * No lock of the indicies is necessary here, because ri is only + * updated by us, and there is only one mouse reader + * at a time. I suppose that more than one process + * could try to read the fd at one time, but such behavior + * is degenerate and already violates the calling + * conventions for sleep above. + */ + if(mouse.ri != mouse.wi){ + m = mouse.queue[mouse.ri]; + if(++mouse.ri == nelem(mouse.queue)) + mouse.ri = 0; + } else { + lock(&cursor); + + m = mouse.Mousestate; + unlock(&cursor); + } + + b = buttonmap[m.buttons&7]; + /* put buttons 4 and 5 back in */ + b |= m.buttons & (3<<3); + sprint(buf, "m%11d %11d %11d %11lud", + m.xy.x, m.xy.y, + b, + m.msec); + mouse.lastcounter = m.counter; + if(n > 1+4*12) + n = 1+4*12; + memmove(va, buf, n); + return n; + } + return 0; +} + +static void +setbuttonmap(char* map) +{ + int i, x, one, two, three; + + one = two = three = 0; + for(i = 0; i < 3; i++){ + if(map[i] == 0) + error(Ebadarg); + if(map[i] == '1'){ + if(one) + error(Ebadarg); + one = 1<<i; + } + else if(map[i] == '2'){ + if(two) + error(Ebadarg); + two = 1<<i; + } + else if(map[i] == '3'){ + if(three) + error(Ebadarg); + three = 1<<i; + } + else + error(Ebadarg); + } + if(map[i]) + error(Ebadarg); + + memset(buttonmap, 0, 8); + for(i = 0; i < 8; i++){ + x = 0; + if(i & 1) + x |= one; + if(i & 2) + x |= two; + if(i & 4) + x |= three; + buttonmap[x] = i; + } +} + +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(1); + 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); + } + qlock(&mouse); + mouse.redraw = 1; + mouseclock(); + qunlock(&mouse); + cursoron(1); + return n; + + case Qmouse: + if(n > sizeof buf-1) + n = sizeof buf -1; + memmove(buf, va, n); + buf[n] = 0; + p = 0; + pt.x = strtoul(buf+1, &p, 0); + if(p == 0) + error(Eshort); + pt.y = strtoul(p, 0, 0); + qlock(&mouse); + if(ptinrect(pt, gscreen->r)){ + mousetrack(pt.x, pt.y, mouse.buttons, nsec()/(1000*1000LL)); + mousewarpnote(pt); + } + qunlock(&mouse); + return n; + } + + error(Egreg); + return -1; +} + +Dev mousedevtab = { + 'm', + "mouse", + + mousereset, + mouseinit, + mouseattach, + mousewalk, + mousestat, + mouseopen, + mousecreate, + mouseclose, + mouseread, + devbread, + mousewrite, + devbwrite, + devremove, + devwstat, +}; + +void +Cursortocursor(Cursor *c) +{ + lock(&cursor); + memmove(&cursor.Cursor, c, sizeof(Cursor)); + setcursor(c); + unlock(&cursor); +} + +static int +scale(int x) +{ + int sign = 1; + + if(x < 0){ + sign = -1; + x = -x; + } + switch(x){ + case 0: + case 1: + case 2: + case 3: + break; + case 4: + x = 6 + (mouse.acceleration>>2); + break; + case 5: + x = 9 + (mouse.acceleration>>1); + break; + default: + x *= mouse.maxacc; + break; + } + return sign*x; +} + +static void +mouseclock(void) +{ + lock(&cursor); + if(mouse.redraw){ + mouse.redraw = 0; + cursoroff(0); + mouse.redraw = cursoron(0); + } + unlock(&cursor); +} + +/* + * called at interrupt level to update the structure and + * awaken any waiting procs. + */ +void +mousetrack(int x, int y, int b, int msec) +{ + int lastb; + + lastb = mouse.buttons; + mouse.xy = Pt(x, y); + mouse.buttons = b; + mouse.redraw = 1; + mouse.counter++; + mouse.msec = msec; + + /* + * if the queue fills, we discard the entire queue and don't + * queue any more events until a reader polls the mouse. + */ + if(!mouse.qfull && lastb != b){ /* add to ring */ + mouse.queue[mouse.wi] = mouse.Mousestate; + if(++mouse.wi == nelem(mouse.queue)) + mouse.wi = 0; + if(mouse.wi == mouse.ri) + mouse.qfull = 1; + } + rendwakeup(&mouse.r); + mouseclock(); +} + +int +mousechanged(void*) +{ + return mouse.lastcounter != mouse.counter; +} + +Point +mousexy(void) +{ + return mouse.xy; +} + +void +mouseaccelerate(int x) +{ + mouse.acceleration = x; + if(mouse.acceleration < 3) + mouse.maxacc = 2; + else + mouse.maxacc = mouse.acceleration; +} diff --git a/sys/src/cmd/vnc/draw.c b/sys/src/cmd/vnc/draw.c new file mode 100755 index 000000000..5a37bb5ab --- /dev/null +++ b/sys/src/cmd/vnc/draw.c @@ -0,0 +1,390 @@ +#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, +}; + +static uchar *pixbuf; +static uchar *linebuf; +static int vpixb; +static int pixb; +static void (*pixcp)(uchar*, uchar*); + +static void +vncrdcolor(Vnc *v, uchar *color) +{ + vncrdbytes(v, color, vpixb); + + if(cvtpixels) + (*cvtpixels)(color, color, 1); +} + +void +sendencodings(Vnc *v) +{ + char *f[10]; + int enc[10], 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) +{ + int x, y; + + lockdisplay(display); + x = Dx(screen->r); + y = Dy(screen->r); + unlockdisplay(display); + if(x > v->dim.x) + x = v->dim.x; + if(y > v->dim.y) + y = v->dim.y; + vnclock(v); + vncwrchar(v, MFrameReq); + vncwrchar(v, incremental); + vncwrrect(v, Rpt(ZP, Pt(x, y))); + 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; + for(; y < r.max.y; y++){ + vncrdbytes(v, linebuf, Dx(r) * vpixb); + (*cvtpixels)(&pixbuf[off + r.min.x * pixb], linebuf, Dx(r)); + off += stride; + } + }else{ + y = r.min.y; + off = y * stride; + for(; y < r.max.y; y++){ + vncrdbytes(v, &pixbuf[off + r.min.x * pixb], 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); + if(r.min.x == r.max.x || r.min.y == r.max.y) + return; + if(!rectinrect(r, Rpt(ZP, v->dim))) + sysfatal("bad rectangle from server: %R not in %R", r, Rpt(ZP, v->dim)); + stride = Dx(r) * pixb; + type = vncrdlong(v); + switch(type){ + default: + sysfatal("bad rectangle encoding from server"); + break; + case EncRaw: + loadbuf(v, Rpt(ZP, Pt(Dx(r), Dy(r))), 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: + maxr = Rpt(ZP, Pt(Dx(r), Dy(r))); + 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; + + case EncMouseWarp: + mousewarp(r.min); + 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); + } + linebuf = malloc(v->dim.x * vpixb); + pixbuf = malloc(v->dim.x * pixb * v->dim.y); + if(linebuf == nil || pixbuf == nil) + sysfatal("can't allocate pix decompression storage"); + for(;;){ + type = vncrdchar(v); + switch(type){ + default: + sysfatal("bad message from server"); + break; + case MFrameUpdate: + vncrdchar(v); + n = vncrdshort(v); + while(n-- > 0) + dorectangle(v); + flushimage(display, 1); + 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; + } + } +} diff --git a/sys/src/cmd/vnc/error.h b/sys/src/cmd/vnc/error.h new file mode 100755 index 000000000..877fdfc39 --- /dev/null +++ b/sys/src/cmd/vnc/error.h @@ -0,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 */ diff --git a/sys/src/cmd/vnc/errstr.h b/sys/src/cmd/vnc/errstr.h new file mode 100755 index 000000000..9c56befa7 --- /dev/null +++ b/sys/src/cmd/vnc/errstr.h @@ -0,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"; diff --git a/sys/src/cmd/vnc/exporter.c b/sys/src/cmd/vnc/exporter.c new file mode 100755 index 000000000..bf5198bc3 --- /dev/null +++ b/sys/src/cmd/vnc/exporter.c @@ -0,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; +} diff --git a/sys/src/cmd/vnc/exportfs.c b/sys/src/cmd/vnc/exportfs.c new file mode 100755 index 000000000..4948016d4 --- /dev/null +++ b/sys/src/cmd/vnc/exportfs.c @@ -0,0 +1,803 @@ +#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) + goto bad; + + 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); + } +bad: + 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 > 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; +} diff --git a/sys/src/cmd/vnc/kbd.h b/sys/src/cmd/vnc/kbd.h new file mode 100755 index 000000000..f8ee6136e --- /dev/null +++ b/sys/src/cmd/vnc/kbd.h @@ -0,0 +1,22 @@ +typedef struct Snarf Snarf; + +struct Snarf +{ + QLock; + int vers; + int n; + char *buf; +}; + +enum +{ + MAXSNARF = 100*1024 +}; + +extern Snarf snarf; + +long latin1(Rune *k, int n); +void kbdputc(int c); +void screenputs(char*, int); +void vncputc(int, int); +void setsnarf(char *buf, int n, int *vers); diff --git a/sys/src/cmd/vnc/kbds.c b/sys/src/cmd/vnc/kbds.c new file mode 100755 index 000000000..89f7c80aa --- /dev/null +++ b/sys/src/cmd/vnc/kbds.c @@ -0,0 +1,173 @@ +#include <u.h> +#include <libc.h> +#include "compat.h" +#include "kbd.h" +#include "ksym2utf.h" + +enum +{ + VKSpecial = 0xff00, + + /* + * plan 9 key mappings + */ + Spec= 0xF800, + + PF= Spec|0x20, /* num pad function key */ + View= Spec|0x00, /* view (shift window up) */ + KF= 0xF000, /* function key (begin Unicode private space) */ + Shift= Spec|0x60, + Break= Spec|0x61, + Ctrl= Spec|0x62, + Latin= Spec|0x63, + Caps= Spec|0x64, + Num= Spec|0x65, + Middle= Spec|0x66, + No= 0x00, /* peter */ + + Home= KF|13, + Up= KF|14, + Pgup= KF|15, + Print= KF|16, + Left= KF|17, + Right= KF|18, + End= '\r', + Down= View, + Pgdown= KF|19, + Ins= KF|20, + Del= 0x7F, + Scroll= KF|21, + + Esc = 0x1b, + Delete = 0x7f, +}; + +static Rune vnckeys[] = +{ +[0x00] No, No, No, No, No, No, No, No, +[0x08] '\b', '\t', '\r', No, No, '\n', No, No, +[0x10] No, No, No, No, Scroll, No, No, No, +[0x18] No, No, No, Esc, No, No, No, No, +[0x20] No, No, No, No, No, No, No, No, +[0x28] No, No, No, No, No, No, No, No, +[0x30] No, No, No, No, No, No, No, No, +[0x38] No, No, No, No, No, No, No, No, +[0x40] No, No, No, No, No, No, No, No, +[0x48] No, No, No, No, No, No, No, No, +[0x50] Home, Left, Up, Right, Down, Pgup, Pgdown, No, +[0x58] No, No, No, No, No, No, No, No, +[0x60] No, Print, No, Ins, No, No, No, No, +[0x68] No, No, No, Break, No, No, No, No, +[0x70] No, No, No, No, No, No, No, No, +[0x78] No, No, No, No, No, No, No, Num, +[0x80] No, No, No, No, No, No, No, No, +[0x88] No, No, No, No, No, No, No, No, +[0x90] No, No, No, No, No, No, No, No, +[0x98] No, No, No, No, No, No, No, No, +[0xa0] No, No, No, No, No, No, No, No, +[0xa8] No, No, '*', '+', No, '-', '.', '/', +[0xb0] '0', '1', '2', '3', '4', '5', '6', '7', +[0xb8] '8', '9', No, No, No, '=', No, No, +[0xc0] No, No, No, No, No, No, No, No, +[0xc8] No, No, No, No, No, No, No, No, +[0xd0] No, No, No, No, No, No, No, No, +[0xd8] No, No, No, No, No, No, No, No, +[0xe0] No, Shift, Shift, Ctrl, Ctrl, Caps, Caps, No, +[0xe8] No, Latin, Latin, No, No, No, No, No, +[0xf0] No, No, No, No, No, No, No, No, +[0xf8] No, No, No, No, No, No, No, Delete, +}; + +/* + * keyboard interrupt + */ +void +vncputc(int keyup, int c) +{ + int i; + static int esc1, esc2; + static int alt, caps, ctl, num, shift; + static int collecting, nk; + static Rune kc[5]; + + if(caps && c<='z' && c>='a') + c += 'A' - 'a'; + + /* + * character mapping + */ + if((c & VKSpecial) == VKSpecial){ + c = vnckeys[c & 0xff]; + if(c == No) + return; + } + /* + * map an xkeysym onto a utf-8 char + */ + if((c & 0xff00) && c < nelem(ksym2utf) && ksym2utf[c] != 0) + c = ksym2utf[c]; + + /* + * keyup only important for shifts + */ + if(keyup){ + switch(c){ + case Latin: + alt = 0; + break; + case Shift: + shift = 0; + break; + case Ctrl: + ctl = 0; + break; + } + return; + } + + /* + * normal character + */ + if(!(c & (Spec|KF))){ + if(ctl){ + c &= 0x1f; + } + if(!collecting){ + kbdputc(c); + return; + } + kc[nk++] = c; + c = latin1(kc, nk); + if(c < -1) /* need more keystrokes */ + return; + if(c != -1) /* valid sequence */ + kbdputc(c); + else /* dump characters */ + for(i=0; i<nk; i++) + kbdputc(kc[i]); + nk = 0; + collecting = 0; + return; + }else{ + switch(c){ + case Caps: + caps ^= 1; + return; + case Num: + num ^= 1; + return; + case Shift: + shift = 1; + return; + case Latin: + alt = 1; + collecting = 1; + nk = 0; + return; + case Ctrl: + ctl = 1; + return; + } + } + kbdputc(c); +} diff --git a/sys/src/cmd/vnc/kbdv.c b/sys/src/cmd/vnc/kbdv.c new file mode 100755 index 000000000..329567ed7 --- /dev/null +++ b/sys/src/cmd/vnc/kbdv.c @@ -0,0 +1,178 @@ +#include "vnc.h" +#include <keyboard.h> +#include "utf2ksym.h" + +enum { + Xshift = 0xFFE1, + Xctl = 0xFFE3, + Xmeta = 0xFFE7, + Xalt = 0xFFE9 +}; + +static struct { + Rune kbdc; + ulong keysym; +} ktab[] = { + {'\b', 0xff08}, + {'\t', 0xff09}, + {'\n', 0xff0d}, + /* {0x0b, 0xff0b}, */ + {'\r', 0xff0d}, + {0x1b, 0xff1b}, /* escape */ + {Kins, 0xff63}, + {0x7F, 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}, +}; + +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); +} + +void +readkbd(Vnc *v) +{ + char buf[256], k[10]; + ulong ks; + int ctlfd, fd, kr, kn, w, shift, ctl, alt; + 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 = 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; + 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; + } + break; + } + } +} + diff --git a/sys/src/cmd/vnc/ksym2utf.h b/sys/src/cmd/vnc/ksym2utf.h new file mode 100755 index 000000000..536cd8b32 --- /dev/null +++ b/sys/src/cmd/vnc/ksym2utf.h @@ -0,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 diff --git a/sys/src/cmd/vnc/latin1.c b/sys/src/cmd/vnc/latin1.c new file mode 100755 index 000000000..8650f7d82 --- /dev/null +++ b/sys/src/cmd/vnc/latin1.c @@ -0,0 +1,76 @@ +#include <u.h> + +/* + * The code makes two assumptions: strlen(ld) is 1 or 2; latintab[i].ld can be a + * prefix of latintab[j].ld only when j<i. + */ +struct cvlist +{ + char *ld; /* must be seen before using this conversion */ + char *si; /* options for last input characters */ + Rune *so; /* the corresponding Rune for each si entry */ +} latintab[] = { +#include "latin1.h" + 0, 0, 0 +}; + +/* + * Given 5 characters k[0]..k[4], find the rune or return -1 for failure. + */ +long +unicode(Rune *k) +{ + long i, c; + + k++; /* skip 'X' */ + c = 0; + for(i=0; i<4; i++,k++){ + c <<= 4; + if('0'<=*k && *k<='9') + c += *k-'0'; + else if('a'<=*k && *k<='f') + c += 10 + *k-'a'; + else if('A'<=*k && *k<='F') + c += 10 + *k-'A'; + else + return -1; + } + return c; +} + +/* + * Given n characters k[0]..k[n-1], find the corresponding rune or return -1 for + * failure, or something < -1 if n is too small. In the latter case, the result + * is minus the required n. + */ +long +latin1(Rune *k, int n) +{ + struct cvlist *l; + int c; + char* p; + + if(k[0] == 'X') + if(n>=5) + return unicode(k); + else + return -5; + for(l=latintab; l->ld!=0; l++) + if(k[0] == l->ld[0]){ + if(n == 1) + return -2; + if(l->ld[1] == 0) + c = k[1]; + else if(l->ld[1] != k[1]) + continue; + else if(n == 2) + return -3; + else + c = k[2]; + for(p=l->si; *p!=0; p++) + if(*p == c) + return l->so[p - l->si]; + return -1; + } + return -1; +} diff --git a/sys/src/cmd/vnc/latin1.h b/sys/src/cmd/vnc/latin1.h new file mode 100755 index 000000000..fe3c6d2fd --- /dev/null +++ b/sys/src/cmd/vnc/latin1.h @@ -0,0 +1,99 @@ + " ", " i", L"␣ı", + "!~", "-=~", L"≄≇≉", + "!", "!<=>?bmp", L"¡≮≠≯‽⊄∉⊅", + "\"*", "IUiu", L"ΪΫϊϋ", + "\"", "\"AEIOUYaeiouy", L"¨ÄËÏÖÜŸäëïöüÿ", + "$*", "fhk", L"ϕϑϰ", + "$", "BEFHILMRVaefglopv", L"ℬℰℱℋℐℒℳℛƲɑℯƒℊℓℴ℘ʋ", + "\'\"", "Uu", L"Ǘǘ", + "\'", "\'ACEILNORSUYZacegilnorsuyz", L"´ÁĆÉÍĹŃÓŔŚÚÝŹáćéģíĺńóŕśúýź", + "*", "*ABCDEFGHIKLMNOPQRSTUWXYZabcdefghiklmnopqrstuwxyz", L"∗ΑΒΞΔΕΦΓΘΙΚΛΜΝΟΠΨΡΣΤΥΩΧΗΖαβξδεφγθικλμνοπψρστυωχηζ", + "+", "-O", L"±⊕", + ",", ",ACEGIKLNORSTUacegiklnorstu", L"¸ĄÇĘĢĮĶĻŅǪŖŞŢŲąçęģįķļņǫŗşţų", + "-*", "l", L"ƛ", + "-", "+-2:>DGHILOTZbdghiltuz~", L"∓ƻ÷→ÐǤĦƗŁ⊖ŦƵƀðǥℏɨłŧʉƶ≂", + ".", ".CEGILOZceglz", L"·ĊĖĠİĿ⊙Żċėġŀż", + "/", "Oo", L"Øø", + "1", "234568", L"½⅓¼⅕⅙⅛", + "2", "-35", L"ƻ⅔⅖", + "3", "458", L"¾⅗⅜", + "4", "5", L"⅘", + "5", "68", L"⅚⅝", + "7", "8", L"⅞", + ":", ")-=", L"☺÷≔", + "<!", "=~", L"≨⋦", + "<", "-<=>~", L"←«≤≶≲", + "=", ":<=>OV", L"≕⋜≡⋝⊜⇒", + ">!", "=~", L"≩⋧", + ">", "<=>~", L"≷≥»≳", + "?", "!?", L"‽¿", + "@\'", "\'", L"ъ", + "@@", "\'EKSTYZekstyz", L"ьЕКСТЫЗекстыз", + "@C", "Hh", L"ЧЧ", + "@E", "Hh", L"ЭЭ", + "@K", "Hh", L"ХХ", + "@S", "CHch", L"ЩШЩШ", + "@T", "Ss", L"ЦЦ", + "@Y", "AEOUaeou", L"ЯЕЁЮЯЕЁЮ", + "@Z", "Hh", L"ЖЖ", + "@c", "h", L"ч", + "@e", "h", L"э", + "@k", "h", L"х", + "@s", "ch", L"щш", + "@t", "s", L"ц", + "@y", "aeou", L"яеёю", + "@z", "h", L"ж", + "@", "ABDFGIJLMNOPRUVXabdfgijlmnopruvx", L"АБДФГИЙЛМНОПРУВХабдфгийлмнопрувх", + "A", "E", L"Æ", + "C", "ACU", L"⋂ℂ⋃", + "Dv", "Zz", L"DŽDž", + "D", "-e", L"Ð∆", + "G", "-", L"Ǥ", + "H", "-H", L"Ħℍ", + "I", "-J", L"ƗIJ", + "L", "&-Jj|", L"⋀ŁLJLj⋁", + "N", "JNj", L"NJℕNj", + "O", "*+-./=EIcoprx", L"⊛⊕⊖⊙⊘⊜ŒƢ©⊚℗®⊗", + "P", "P", L"ℙ", + "Q", "Q", L"ℚ", + "R", "R", L"ℝ", + "S", "123S", L"¹²³§", + "T", "-u", L"Ŧ⊨", + "V", "=", L"⇐", + "Y", "R", L"Ʀ", + "Z", "-ACSZ", L"Ƶℤ", + "^", "ACEGHIJOSUWYaceghijosuwy", L"ÂĈÊĜĤÎĴÔŜÛŴŶâĉêĝĥîĵôŝûŵŷ", + "_\"", "AUau", L"ǞǕǟǖ", + "_,", "Oo", L"Ǭǭ", + "_.", "Aa", L"Ǡǡ", + "_", "AEIOU_aeiou", L"ĀĒĪŌŪ¯āēīōū", + "`\"", "Uu", L"Ǜǜ", + "`", "AEIOUaeiou", L"ÀÈÌÒÙàèìòù", + "a", "ben", L"↔æ∠", + "b", "()+-0123456789=bknpqru", L"₍₎₊₋₀₁₂₃₄₅₆₇₈₉₌♝♚♞♟♛♜•", + "c", "$Oagu", L"¢©∩≅∪", + "dv", "z", L"dž", + "d", "-adegz", L"ð↓‡°†ʣ", + "e", "$lmns", L"€⋯—–∅", + "f", "a", L"∀", + "g", "$-r", L"¤ǥ∇", + "h", "-v", L"ℏƕ", + "i", "-bfjps", L"ɨ⊆∞ij⊇∫", + "l", "\"$&\'-jz|", L"“£∧‘łlj⋄∨", + "m", "iou", L"µ∈×", + "n", "jo", L"nj¬", + "o", "AOUaeiu", L"Å⊚Ůåœƣů", + "p", "Odgrt", L"℗∂¶∏∝", + "r", "\"\'O", L"”’®", + "s", "()+-0123456789=abnoprstu", L"⁽⁾⁺⁻⁰ⁱ⁴⁵⁶⁷⁸⁹⁼ª⊂ⁿº⊃√ß∍∑", + "t", "-efmsu", L"ŧ∃∴™ς⊢", + "u", "-AEGIOUaegiou", L"ʉĂĔĞĬŎŬ↑ĕğĭŏŭ", + "v\"", "Uu", L"Ǚǚ", + "v", "ACDEGIKLNORSTUZacdegijklnorstuz", L"ǍČĎĚǦǏǨĽŇǑŘŠŤǓŽǎčďěǧǐǰǩľňǒřšťǔž", + "w", "bknpqr", L"♗♔♘♙♕♖", + "x", "O", L"⊗", + "y", "$", L"¥", + "z", "-", L"ƶ", + "|", "Pp|", L"Þþ¦", + "~!", "=", L"≆", + "~", "-=AINOUainou~", L"≃≅ÃĨÑÕŨãĩñõũ≈", diff --git a/sys/src/cmd/vnc/mkfile b/sys/src/cmd/vnc/mkfile new file mode 100755 index 000000000..8b1c84946 --- /dev/null +++ b/sys/src/cmd/vnc/mkfile @@ -0,0 +1,62 @@ +</$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\ + latin1.$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\ + latin1.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 diff --git a/sys/src/cmd/vnc/proto.c b/sys/src/cmd/vnc/proto.c new file mode 100755 index 000000000..277525034 --- /dev/null +++ b/sys/src/cmd/vnc/proto.c @@ -0,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; + } +} diff --git a/sys/src/cmd/vnc/rlist.c b/sys/src/cmd/vnc/rlist.c new file mode 100755 index 000000000..dddad64fc --- /dev/null +++ b/sys/src/cmd/vnc/rlist.c @@ -0,0 +1,283 @@ +#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; + + initdraw(0, 0, "vncviewer"); + region_init(®); + region_union(®, r1, r1); + region_union(®, r2, r2); + region_union(®, r3, r3); +} + +#endif diff --git a/sys/src/cmd/vnc/rre.c b/sys/src/cmd/vnc/rre.c new file mode 100755 index 000000000..327d758bc --- /dev/null +++ b/sys/src/cmd/vnc/rre.c @@ -0,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); +} diff --git a/sys/src/cmd/vnc/screen.c b/sys/src/cmd/vnc/screen.c new file mode 100755 index 000000000..e11155ac9 --- /dev/null +++ b/sys/src/cmd/vnc/screen.c @@ -0,0 +1,377 @@ +#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; + +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, + }, +}; + +void +screeninit(int x, int y, char *chanstr) +{ + Point p, q; + char *greet; + char buf[128]; + Memimage *grey; + 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); + } + + drawlock(); + + /* + * set up goo for screenputs + */ + memdefont = getmemdefont(); + + back = memwhite; + conscol = memblack; + + /* 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); + + memfillcolor(gscreen, 0x444488FF); + + w = memdefont->info[' '].width; + h = memdefont->height; + + window.min = addpt(gscreen->r.min, Pt(20,20)); + window.max.x = window.min.x + Dx(gscreen->r)*3/4-40; + window.max.y = window.min.y + Dy(gscreen->r)*3/4-100; + + 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)); + q = memsubfontwidth(memdefont, greet); + 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); + + drawunlock(); + + setcursor(&arrow); +} + +uchar* +attachscreen(Rectangle* r, ulong* chan, int* d, int* width, int *softscreen) +{ + *r = gscreen->r; + *d = gscreen->depth; + *chan = gscreen->chan; + *width = gscreen->width; + *softscreen = 1; + + return gscreen->data->bdata; +} + +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); +} + +int +cursoron(int dolock) +{ + if(dolock) + lock(&cursor); + cursorpos = mousexy(); + if(dolock) + unlock(&cursor); + + return 0; +} + +void +cursoroff(int dolock) +{ + if(dolock) + lock(&cursor); + cursorpos = offscreen; + if(dolock) + unlock(&cursor); +} + +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->r); + + 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; + 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) +{ + int i; + Rune r; + char buf[4]; + + drawlock(); + while(n > 0){ + i = chartorune(&r, s); + if(i == 0){ + s++; + --n; + continue; + } + memmove(buf, s, i); + buf[i] = 0; + n -= i; + s += i; + screenputc(buf); + } + screenflush(); + drawunlock(); +} diff --git a/sys/src/cmd/vnc/screen.h b/sys/src/cmd/vnc/screen.h new file mode 100755 index 000000000..64b8d144e --- /dev/null +++ b/sys/src/cmd/vnc/screen.h @@ -0,0 +1,34 @@ +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; + +Point mousexy(void); +int cursoron(int); +void cursoroff(int); +void setcursor(Cursor*); +void flushmemscreen(Rectangle r); +Rectangle cursorrect(void); +void cursordraw(Memimage *dst, Rectangle r); + +void drawactive(int); +void drawlock(void); +void drawunlock(void); +int candrawlock(void); +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 mousetrack(int x, int y, int b, int msec); +uchar *attachscreen(Rectangle*, ulong*, int*, int*, int*); + +void fsinit(char *mntpt, int x, int y, char *chanstr); diff --git a/sys/src/cmd/vnc/utf2ksym.h b/sys/src/cmd/vnc/utf2ksym.h new file mode 100755 index 000000000..ea24d818b --- /dev/null +++ b/sys/src/cmd/vnc/utf2ksym.h @@ -0,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'۰'] 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'␋'] 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 diff --git a/sys/src/cmd/vnc/vnc.h b/sys/src/cmd/vnc/vnc.h new file mode 100755 index 000000000..afa020624 --- /dev/null +++ b/sys/src/cmd/vnc/vnc.h @@ -0,0 +1,134 @@ +#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; + + Point dim; + Pixfmt; + char *name; /* client only */ +}; + +enum { + /* 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, + + /* 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, + + /* 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; +extern char* serveraddr;
\ No newline at end of file diff --git a/sys/src/cmd/vnc/vncs.c b/sys/src/cmd/vnc/vncs.c new file mode 100755 index 000000000..cb4bbd66f --- /dev/null +++ b/sys/src/cmd/vnc/vncs.c @@ -0,0 +1,1103 @@ +#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 +}; + +static char *msgname[] = { + [MPixFmt] = "MPixFmt", + [MFixCmap] = "MFixCmap", + [MSetEnc] = "MSetEnc", + [MFrameReq] = "MFrameReq", + [MKey] = "MKey", + [MMouse] = "MMouse", + [MCCut] = "MCCut", +}; + +static char *encname[] = { + [EncRaw] = "raw", + [EncCopyRect] = "copy rect", + [EncRre] = "rre", + [EncCorre] = "corre", + [EncHextile] = "hextile", + [EncZlib] = "zlib", + [EncTight] = "zlibtight", + [EncZHextile] = "zhextile", + [EncMouseWarp] = "mousewarp", + +}; + +/* + * 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; +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*); +static void vncname(char*, ...); +#pragma varargck argpos vncname 1 + +#pragma varargck type "V" Vncs* + +void +usage(void) +{ + fprint(2, "usage: vncs [-v] [-c cert] [-d :display] [-g widthXheight] [-p pixelfmt] [cmd [args]...]\n"); + fprint(2, "\tto kill a server: vncs [-v] -k :display\n"); + exits("usage"); +} + +void +main(int argc, char **argv) +{ + int altnet, baseport, cfd, display, exnum, fd, h, killing, w; + char adir[NETPATHLEN], ldir[NETPATHLEN]; + char net[NETPATHLEN], *p; + char *rc[] = { "/bin/rc", "-i", nil }; + Vncs *v; + + fmtinstall('V', vncsfmt); + display = -1; + killing = 0; + altnet = 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); + altnet = 1; + break; + }ARGEND + + if(killing){ + vnckill(net, display, baseport); + exits(nil); + } + + if(altnet && !cert) + sysfatal("announcing on alternate network requires TLS (-c)"); + + 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); + + /* run the command */ + switch(cmdpid = rfork(RFPROC|RFFDG|RFNOTEG|RFNAMEG|RFREND)){ + case -1: + sysfatal("rfork: %r"); + break; + case 0: + if(mounter("/dev", MBEFORE, fd, exnum) < 0) + sysfatal("mounter: %r"); + close(exportfd); + close(0); + close(1); + close(2); + open("/dev/cons", OREAD); + open("/dev/cons", OWRITE); + open("/dev/cons", OWRITE); + exec(argv[0], argv); + fprint(2, "exec %s: %r\n", argv[0]); + _exits(nil); + default: + close(fd); + break; + } + + /* 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(;;){ + vncname("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; + 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; + TLSconn conn; + + /* 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){ + 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); + if(conn.sessionID) + free(conn.sessionID); + exits(nil); + } + close(v->datafd); + 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(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 = (Point){Dx(gscreen->r), Dy(gscreen->r)}; + vncwrpoint(v, v->dim); + if(verbose) + fprint(2, "%V: send screen size %P (rect %R)\n", v, v->dim, gscreen->r); + + 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); + + 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); + } +} + +static void +vncname(char *fmt, ...) +{ + int fd; + char name[64], buf[32]; + va_list arg; + + va_start(arg, fmt); + vsnprint(name, sizeof name, fmt, arg); + va_end(arg); + + sprint(buf, "/proc/%d/args", getpid()); + if((fd = open(buf, OWRITE)) >= 0){ + write(fd, name, strlen(name)); + close(fd); + } +} + +/* + * 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; + } + 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\n", v->encname, + v->copyrect ? ", copyrect" : "", + v->canwarp ? ", canwarp" : ""); +} + +/* + * Continually read updates from one client. + */ +static void +clientreadproc(Vncs *v) +{ + int incremental, key, keydown, buttons, type, x, y, n; + char *buf; + Rectangle r; + + vncname("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){ + vnclock(v); + v->updaterequest = 1; + vncunlock(v); + }else{ + drawlock(); /* protects rlist */ + vnclock(v); /* protects updaterequest */ + v->updaterequest = 1; + addtorlist(&v->rlist, r); + vncunlock(v); + drawunlock(); + } + 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); + mousetrack(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->r)) + 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->needwarp = 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, ncount, nsend, docursor, needwarp; + vlong ooffset; + Point warppt; + Rectangle cr; + Rlist rlist; + vlong t1; + int (*count)(Vncs*, Rectangle); + int (*send)(Vncs*, Rectangle); + + if(v->image == nil) + return 0; + + /* warping info and unlock v so that updates can proceed */ + needwarp = v->canwarp && v->needwarp; + warppt = v->warppt; + v->needwarp = 0; + vncunlock(v); + + /* copy the screen bits and then unlock the screen so updates can proceed */ + drawlock(); + rlist = v->rlist; + memset(&v->rlist, 0, sizeof v->rlist); + + /* 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->r)) + 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; + } + + drawunlock(); + + ooffset = Boffset(&v->out); + /* no more locks are held; talk to the client */ + + if(rlist.nrect == 0 && needwarp == 0){ + vnclock(v); + return 0; + } + + count = v->countrect; + send = v->sendrect; + if(count == nil || send == nil){ + count = countraw; + send = sendraw; + } + + ncount = 0; + for(i=0; i<rlist.nrect; i++) + ncount += (*count)(v, rlist.rect[i]); + + if(verbose > 1) + fprint(2, "sendupdate: rlist.nrect=%d, ncount=%d", rlist.nrect, ncount); + + t1 = nsec(); + vncwrchar(v, MFrameUpdate); + vncwrchar(v, 0); + vncwrshort(v, ncount+needwarp); + + 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(needwarp){ + vncwrrect(v, Rect(warppt.x, warppt.y, warppt.x+1, warppt.y+1)); + vncwrlong(v, EncMouseWarp); + } + + t1 = nsec() - t1; + if(verbose > 1) + fprint(2, " in %lldms, %lld bytes\n", t1/1000000, Boffset(&v->out) - ooffset); + + freerlist(&rlist); + vnclock(v); + 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; + vncunlock(v); + qlock(&snarf); + len = snarf.n; + buf = malloc(len); + if(buf == nil){ + qunlock(&snarf); + vnclock(v); + 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); + vnclock(v); +} + +/* + * Continually update one client. + */ +static void +clientwriteproc(Vncs *v) +{ + char buf[32], buf2[32]; + int sent; + + vncname("write %V", v); + for(;;){ + vnclock(v); + if(v->ndead) + break; + if((v->image == nil && v->imagechan!=0) + || (v->image && v->image->chan != v->imagechan)){ + if(v->image) + freememimage(v->image); + v->image = allocmemimage(Rpt(ZP, v->dim), v->imagechan); + if(v->image == nil){ + fprint(2, "%V: allocmemimage: %r; hanging up\n", v); + vnchungup(v); + } + if(verbose) + fprint(2, "%V: translating image from chan=%s to chan=%s\n", + v, chantostr(buf, gscreen->chan), chantostr(buf2, v->imagechan)); + } + sent = 0; + if(v->updaterequest){ + v->updaterequest = 0; + updatesnarf(v); + sent = updateimage(v); + if(!sent) + v->updaterequest = 1; + } + vncunlock(v); + vncflush(v); + if(!sent) + sleep(sleeptime); + } + vncunlock(v); + vnchungup(v); +} + diff --git a/sys/src/cmd/vnc/vncs.h b/sys/src/cmd/vnc/vncs.h new file mode 100755 index 000000000..64095c512 --- /dev/null +++ b/sys/src/cmd/vnc/vncs.h @@ -0,0 +1,53 @@ +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 needwarp; + Point warppt; + + int updaterequest; + 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*); diff --git a/sys/src/cmd/vnc/vncv.c b/sys/src/cmd/vnc/vncv.c new file mode 100755 index 000000000..5de6786f0 --- /dev/null +++ b/sys/src/cmd/vnc/vncv.c @@ -0,0 +1,207 @@ +#include "vnc.h" +#include "vncv.h" +#include <libsec.h> + +char* encodings = "copyrect hextile corre rre raw mousewarp"; +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] != pid) + postnote(PNPROC, pids[i], killkin); +} + +char* +netmkvncaddr(char *inserver) +{ + char *p, portstr[NETPATHLEN], *server; + int port; + + server = strdup(inserver); + assert(server != nil); + + port = 5900; + if(tls) + port = 35729; + if(p = strchr(server, ':')) { + *p++ = '\0'; + port += atoi(p); + } + + snprint(portstr, sizeof portstr, "%d", port); + p = netmkaddr(server, "tcp", portstr); + free(server); + return p; +} + +void +vnchungup(Vnc*) +{ + sysfatal("connection closed"); +} + +void +usage(void) +{ + fprint(2, "usage: vncv [-e encodings] [-k keypattern] [-csv] host[:n]\n"); + exits("usage"); +} + +void +main(int argc, char **argv) +{ + int p, fd, dfd, cfd, shared; + char *keypattern, *addr; + Point d; + TLSconn conn; + + 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 = 1; + break; + case 'k': + keypattern = EARGF(usage()); + break; + default: + usage(); + }ARGEND; + + if(argc != 1) + usage(); + + addr = netmkvncaddr(argv[0]); + serveraddr = argv[0]; + dfd = dial(addr, nil, nil, &cfd); + if(dfd < 0) + sysfatal("cannot dial %s: %r", addr); + if(tls){ + dfd = tlsClient(dfd, &conn); + if(dfd < 0) + sysfatal("tlsClient: %r"); + /* XXX check thumbprint */ + } + vnc = vncinit(dfd, cfd, nil); + + 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"); + + initdraw(0, 0, "vncv"); + display->locking = 1; + unlockdisplay(display); + + d = addpt(vnc->dim, Pt(2*Borderwidth, 2*Borderwidth)); + if(verbose) + fprint(2, "screen size %P, desktop size %P\n", display->image->r.max, d); + + 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; + + fd = open("/dev/label", OWRITE); + if(fd >= 0){ + fprint(fd, "vnc %s", serveraddr); + close(fd); + } + if(access("/dev/snarf", AEXIST) >= 0){ + 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 = vncrdpoint(v); + v->Pixfmt = vncrdpixfmt(v); + v->name = vncrdstring(v); + return 0; +} diff --git a/sys/src/cmd/vnc/vncv.h b/sys/src/cmd/vnc/vncv.h new file mode 100755 index 000000000..7b213d0df --- /dev/null +++ b/sys/src/cmd/vnc/vncv.h @@ -0,0 +1,26 @@ +/* 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 *encodings; +extern int bpp12; +extern Vnc* vnc; +extern int mousefd; + +/* wsys.c */ +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*); diff --git a/sys/src/cmd/vnc/wsys.c b/sys/src/cmd/vnc/wsys.c new file mode 100755 index 000000000..bc088782c --- /dev/null +++ b/sys/src/cmd/vnc/wsys.c @@ -0,0 +1,259 @@ +#include "vnc.h" +#include "vncv.h" +#include <cursor.h> + +typedef struct Cursor Cursor; + +typedef struct Mouse Mouse; +struct Mouse { + int buttons; + Point xy; +}; + +static void +resize(Vnc *v, int first) +{ + int fd; + Point d; + + d = addpt(v->dim, Pt(2*Borderwidth, 2*Borderwidth)); + lockdisplay(display); + + if(getwindow(display, Refnone) < 0) + sysfatal("internal error: can't get the window image"); + + /* + * limit the window to at most the vnc server's size + */ + if(first || d.x < Dx(screen->r) || d.y < Dy(screen->r)){ + fd = open("/dev/wctl", OWRITE); + if(fd >= 0){ + fprint(fd, "resize -dx %d -dy %d", d.x, d.y); + close(fd); + } + } + unlockdisplay(display); +} + +static void +eresized(void) +{ + resize(vnc, 0); + + 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); + + resize(v, 1); + requestupdate(vnc, 0); + 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, Rpt(ZP, v->dim))){ + mouseevent(v, m); + /* send wheel button *release* */ + if ((m.buttons & 0x7) != m.buttons) { + m.buttons &= 0x7; + mouseevent(v, m); + } + } + } else + eresized(); + + start += EventSize; + len -= EventSize; + } + if(start - buf > sizeof(buf) - EventSize){ + memmove(buf, start, len); + start = buf; + end = start+len; + } + } +} + +static int snarffd = -1; +static ulong snarfvers; + +void +writesnarf(Vnc *v, long n) +{ + uchar buf[8192]; + long m; + Biobuf *b; + + if((b = Bopen("/dev/snarf", OWRITE)) == nil){ + vncgobble(v, n); + return; + } + + while(n > 0){ + m = n; + if(m > sizeof(buf)) + m = sizeof(buf); + vncrdbytes(v, buf, m); + n -= m; + + Bwrite(b, buf, m); + } + Bterm(b); + snarfvers++; +} + +char * +getsnarf(int *sz) +{ + char *snarf, *p; + int n, c; + + *sz =0; + n = 8192; + p = snarf = malloc(n); + + seek(snarffd, 0, 0); + while ((c = read(snarffd, p, n)) > 0){ + p += c; + n -= c; + *sz += c; + if (n == 0){ + snarf = realloc(snarf, *sz + 8192); + n = 8192; + } + } + return snarf; +} + +void +checksnarf(Vnc *v) +{ + Dir *dir; + 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); + + dir = dirstat("/dev/snarf"); + if(dir == nil) /* this happens under old drawterm */ + continue; + if(dir->qid.vers > snarfvers){ + snarf = getsnarf(&len); + + vnclock(v); + vncwrchar(v, MCCut); + vncwrbytes(v, "pad", 3); + vncwrlong(v, len); + vncwrbytes(v, snarf, len); + vncflush(v); + vncunlock(v); + + free(snarf); + + snarfvers = dir->qid.vers; + } + free(dir); + } +} |