Subversion Repositories planix.SVN

Rev

Blame | Last modification | View Log | RSS feed

#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);
}