summaryrefslogtreecommitdiff
path: root/sys/src/cmd/vnc/vncs.c
diff options
context:
space:
mode:
authorTaru Karttunen <taruti@taruti.net>2011-03-30 15:46:40 +0300
committerTaru Karttunen <taruti@taruti.net>2011-03-30 15:46:40 +0300
commite5888a1ffdae813d7575f5fb02275c6bb07e5199 (patch)
treed8d51eac403f07814b9e936eed0c9a79195e2450 /sys/src/cmd/vnc/vncs.c
Import sources from 2011-03-30 iso image
Diffstat (limited to 'sys/src/cmd/vnc/vncs.c')
-rwxr-xr-xsys/src/cmd/vnc/vncs.c1103
1 files changed, 1103 insertions, 0 deletions
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);
+}
+