plan9fox/sys/src/cmd/vnc/vncs.c
2018-08-21 02:21:21 +02:00

1164 lines
22 KiB
C

#define Image IMAGE
#include "vnc.h"
#include "vncs.h"
#include "compat.h"
#include <cursor.h>
#include "screen.h"
#include "kbd.h"
#include <mp.h>
#include <libsec.h>
extern Dev drawdevtab;
extern Dev mousedevtab;
extern Dev consdevtab;
Dev *devtab[] =
{
&drawdevtab,
&mousedevtab,
&consdevtab,
nil
};
/*
* list head. used to hold the list, the lock, dim, and pixelfmt
*/
struct {
QLock;
Vncs *head;
} clients;
int shared;
int sleeptime = 5;
int verbose = 0;
int noauth = 0;
int kbdin = -1;
char *cert;
char *pixchan = "r5g6b5";
static int cmdpid;
static int srvfd;
static int exportfd;
static Vncs **vncpriv;
static int parsedisplay(char*);
static void vnckill(char*, int, int);
static int vncannounce(char *net, int display, char *adir, int base);
static void noteshutdown(void*, char*);
static void vncaccept(Vncs*);
static int vncsfmt(Fmt*);
static void getremote(char*, char*);
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] [-A] [cmd [args]...]\n");
fprint(2, "\tto kill a server: vncs [-v] -k :display\n");
exits("usage");
}
void
main(int argc, char **argv)
{
int baseport, cfd, display, exnum, fd, pid, h, killing, w;
char adir[NETPATHLEN], ldir[NETPATHLEN];
char net[NETPATHLEN], *p;
char *kbdfs[] = { "/bin/aux/kbdfs", "-dq", nil };
char *rc[] = { "/bin/rc", "-i", nil };
Vncs *v;
fmtinstall('V', vncsfmt);
display = -1;
killing = 0;
w = 1024;
h = 768;
baseport = 5900;
setnetmtpt(net, sizeof net, nil);
ARGBEGIN{
default:
usage();
case 'c':
cert = EARGF(usage());
baseport = 35729;
break;
case 'd':
if(display != -1)
usage();
display = parsedisplay(EARGF(usage()));
break;
case 'g':
p = EARGF(usage());
w = strtol(p, &p, 10);
if(*p != 'x' && *p != 'X' && *p != ' ' && *p != ' ')
usage();
h = strtol(p+1, &p, 10);
if(*p != 0)
usage();
break;
case 'k':
if(display != -1)
usage();
display = parsedisplay(EARGF(usage()));
killing = 1;
break;
case 'p':
pixchan = EARGF(usage());
break;
/* DEBUGGING
case 's':
sleeptime = atoi(EARGF(usage()));
break;
*/
case 'v':
verbose++;
break;
case 'x':
p = EARGF(usage());
setnetmtpt(net, sizeof net, p);
break;
case 'A':
noauth = 1;
break;
}ARGEND
if(killing){
vnckill(net, display, baseport);
exits(nil);
}
if(argc == 0)
argv = rc;
/* easy exit */
if(access(argv[0], AEXEC) < 0)
sysfatal("access %s for exec: %r", argv[0]);
/* background ourselves */
switch(rfork(RFPROC|RFNAMEG|RFFDG|RFNOTEG)){
case -1:
sysfatal("rfork: %r");
default:
exits(nil);
case 0:
break;
}
vncpriv = privalloc();
if(vncpriv == nil)
sysfatal("privalloc: %r");
/* start screen */
initcompat();
if(waserror())
sysfatal("screeninit %dx%d %s: %s", w, h, pixchan, up->error);
if(verbose)
fprint(2, "geometry is %dx%d\n", w, h);
screeninit(w, h, pixchan);
poperror();
/* start file system device slaves */
exnum = exporter(devtab, &fd, &exportfd);
/* rebuild /dev because the underlying connection might go away (ick) */
unmount(nil, "/dev");
bind("#c", "/dev", MREPL);
/* mount exporter */
if(mounter("/dev", MBEFORE, fd, exnum) < 0)
sysfatal("mounter: %r");
close(fd);
pid = rfork(RFPROC|RFMEM|RFFDG|RFNOTEG);
switch(pid){
case -1:
sysfatal("rfork: %r");
break;
case 0:
close(exportfd);
close(1);
open("/dev/cons", OWRITE);
close(2);
open("/dev/cons", OWRITE);
/* start and mount kbdfs */
pid = rfork(RFPROC|RFMEM|RFFDG|RFREND);
switch(pid){
case -1:
sysfatal("rfork: %r");
break;
case 0:
exec(kbdfs[0], kbdfs);
fprint(2, "exec %s: %r\n", kbdfs[0]);
_exits("kbdfs");
}
if(waitpid() != pid){
rendezvous(&kbdin, nil);
sysfatal("waitpid: %s: %r", kbdfs[0]);
}
rendezvous(&kbdin, nil);
rfork(RFNAMEG|RFREND);
close(0);
open("/dev/cons", OREAD);
close(1);
open("/dev/cons", OWRITE);
close(2);
open("/dev/cons", OWRITE);
exec(argv[0], argv);
fprint(2, "exec %s: %r\n", argv[0]);
_exits(nil);
}
cmdpid = pid;
/* wait for kbdfs to get mounted */
rendezvous(&kbdin, nil);
if((kbdin = open("/dev/kbdin", OWRITE)) < 0)
sysfatal("open /dev/kbdin: %r");
/* run the service */
srvfd = vncannounce(net, display, adir, baseport);
if(srvfd < 0)
sysfatal("announce failed");
if(verbose)
fprint(2, "announced in %s\n", adir);
atexit(shutdown);
notify(noteshutdown);
for(;;){
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;
close(kbdin);
kbdin = -1;
postnote(PNGROUP, getpid(), killkin);
}
void
shutdown(void)
{
if(verbose)
fprint(2, "vnc server shutdown\n");
killall();
}
static void
noteshutdown(void*, char *msg)
{
if(strcmp(msg, killkin) == 0) /* already shutting down */
noted(NDFLT);
killall();
noted(NDFLT);
}
/*
* Kill a specific instance of a server.
*/
static void
vnckill(char *net, int display, int baseport)
{
int fd, i, n, port;
char buf[NETPATHLEN], *p;
for(i=0;; i++){
snprint(buf, sizeof buf, "%s/tcp/%d/local", net, i);
if((fd = open(buf, OREAD)) < 0)
sysfatal("did not find display");
n = read(fd, buf, sizeof buf-1);
close(fd);
if(n <= 0)
continue;
buf[n] = 0;
p = strchr(buf, '!');
if(p == 0)
continue;
port = atoi(p+1);
if(port != display+baseport)
continue;
snprint(buf, sizeof buf, "%s/tcp/%d/ctl", net, i);
fd = open(buf, OWRITE);
if(fd < 0)
sysfatal("cannot open %s: %r", buf);
if(write(fd, "hangup", 6) != 6)
sysfatal("cannot hangup %s: %r", buf);
close(fd);
break;
}
}
/*
* Look for a port on which to announce.
* If display != -1, we only try that one.
* Otherwise we hunt.
*
* Returns the announce fd.
*/
static int
vncannounce(char *net, int display, char *adir, int base)
{
int port, eport, fd;
char addr[NETPATHLEN];
if(display == -1){
port = base;
eport = base+50;
}else{
port = base+display;
eport = port;
}
for(; port<=eport; port++){
snprint(addr, sizeof addr, "%s/tcp!*!%d", net, port);
if((fd = announce(addr, adir)) >= 0){
fprint(2, "server started on display :%d\n", port-base);
return fd;
}
}
if(display == -1)
fprint(2, "could not find any ports to announce\n");
else
fprint(2, "announce: %r\n");
return -1;
}
/*
* Handle a new connection.
*/
static void clientreadproc(Vncs*);
static void clientwriteproc(Vncs*);
static void chan2fmt(Pixfmt*, ulong);
static ulong fmt2chan(Pixfmt*);
static void
vncaccept(Vncs *v)
{
char buf[32];
int fd;
/* caller returns to listen */
switch(rfork(RFPROC|RFMEM|RFNAMEG)){
case -1:
fprint(2, "%V: fork failed: %r\n", v);
vncclose(v);
return;
default:
return;
case 0:
break;
}
*vncpriv = v;
if(!atexit(exiting)){
fprint(2, "%V: could not register atexit handler: %r; hanging up\n", v);
exiting();
exits(nil);
}
if(cert != nil){
TLSconn conn;
memset(&conn, 0, sizeof conn);
conn.cert = readcert(cert, &conn.certlen);
if(conn.cert == nil){
fprint(2, "%V: could not read cert %s: %r; hanging up\n", v, cert);
exits(nil);
}
fd = tlsServer(v->datafd, &conn);
if(fd < 0){
fprint(2, "%V: tlsServer: %r; hanging up\n", v);
free(conn.cert);
free(conn.sessionID);
exits(nil);
}
v->datafd = fd;
free(conn.cert);
free(conn.sessionID);
}
vncinit(v->datafd, v->ctlfd, v);
if(verbose)
fprint(2, "%V: handshake\n", v);
if(vncsrvhandshake(v) < 0){
fprint(2, "%V: handshake failed; hanging up\n", v);
exits(0);
}
if(noauth){
if(verbose)
fprint(2, "%V: noauth\n", v);
vncwrlong(v, ANoAuth);
vncflush(v);
} else {
if(verbose)
fprint(2, "%V: auth\n", v);
if(vncsrvauth(v) < 0){
fprint(2, "%V: auth failed; hanging up\n", v);
exits(0);
}
}
shared = vncrdchar(v);
if(verbose)
fprint(2, "%V: %sshared\n", v, shared ? "" : "not ");
if(!shared)
killclients(v);
v->dim = rectsubpt(gscreen->clipr, gscreen->clipr.min);
vncwrpoint(v, v->dim.max);
if(verbose)
fprint(2, "%V: send screen size %R\n", v, v->dim);
v->bpp = gscreen->depth;
v->depth = gscreen->depth;
v->truecolor = 1;
v->bigendian = 0;
chan2fmt(v, gscreen->chan);
if(verbose)
fprint(2, "%V: bpp=%d, depth=%d, chan=%s\n", v,
v->bpp, v->depth, chantostr(buf, gscreen->chan));
vncwrpixfmt(v, v);
vncwrlong(v, 14);
vncwrbytes(v, "Plan9 Desktop", 14);
vncflush(v);
if(verbose)
fprint(2, "%V: handshaking done\n", v);
v->updatereq = 0;
switch(rfork(RFPROC|RFMEM)){
case -1:
fprint(2, "%V: cannot fork: %r; hanging up\n", v);
vnchungup(v);
default:
clientreadproc(v);
exits(nil);
case 0:
*vncpriv = v;
v->nproc++;
if(atexit(exiting) == 0){
exiting();
fprint(2, "%V: could not register atexit handler: %r; hanging up\n", v);
exits(nil);
}
clientwriteproc(v);
exits(nil);
}
}
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;
case EncDesktopSize:
v->canresize |= 1;
continue;
case EncXDesktopSize:
v->canresize |= 2;
continue;
}
if(v->countrect != nil)
continue;
switch(x){
case EncRaw:
v->encname = "raw";
v->countrect = countraw;
v->sendrect = sendraw;
break;
case EncRre:
v->encname = "rre";
v->countrect = countrre;
v->sendrect = sendrre;
break;
case EncCorre:
v->encname = "corre";
v->countrect = countcorre;
v->sendrect = sendcorre;
break;
case EncHextile:
v->encname = "hextile";
v->countrect = counthextile;
v->sendrect = sendhextile;
break;
}
}
if(v->countrect == nil){
v->encname = "raw";
v->countrect = countraw;
v->sendrect = sendraw;
}
if(verbose)
fprint(2, "Encoding with %s%s%s%s\n", v->encname,
v->copyrect ? ", copyrect" : "",
v->canwarp ? ", canwarp" : "",
v->canresize ? ", resize" : "");
}
/*
* Continually read updates from one client.
*/
static void
clientreadproc(Vncs *v)
{
int incremental, key, keydown, buttons, type, x, y, n;
char *buf;
Rectangle r;
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){
qlock(&drawlock); /* protects rlist */
addtorlist(&v->rlist, r);
qunlock(&drawlock);
}
v->updatereq++;
break;
case MSetDesktopSize:
vncrdchar(v);
vncrdpoint(v); // desktop size
n = vncrdchar(v);
vncrdchar(v);
if(n == 0)
break;
vncrdlong(v); // id
r = vncrdrect(v);
vncrdlong(v); // flags
while(--n > 0){
vncrdlong(v);
vncrdrect(v);
vncrdlong(v);
}
qlock(&drawlock);
if(!rectclip(&r, gscreen->r)){
qunlock(&drawlock);
break;
}
gscreen->clipr = r;
qunlock(&drawlock);
screenwin();
deletescreenimage();
resetscreenimage();
break;
/* send keystroke */
case MKey:
keydown = vncrdchar(v);
vncgobble(v, 2);
key = vncrdlong(v);
vncputc(!keydown, key);
break;
/* send mouse event */
case MMouse:
buttons = vncrdchar(v);
x = vncrdshort(v);
y = vncrdshort(v);
absmousetrack(x, y, buttons, nsec()/(1000*1000LL));
break;
/* send cut text */
case MCCut:
vncgobble(v, 3);
n = vncrdlong(v);
buf = malloc(n+1);
if(buf){
vncrdbytes(v, buf, n);
buf[n] = 0;
vnclock(v); /* for snarfvers */
setsnarf(buf, n, &v->snarfvers);
vncunlock(v);
}else
vncgobble(v, n);
break;
}
}
}
static int
nbits(ulong mask)
{
int n;
n = 0;
for(; mask; mask>>=1)
n += mask&1;
return n;
}
typedef struct Col Col;
struct Col {
int type;
int nbits;
int shift;
};
static ulong
fmt2chan(Pixfmt *fmt)
{
Col c[4], t;
int i, j, depth, n, nc;
ulong mask, u;
/* unpack the Pixfmt channels */
c[0] = (Col){CRed, nbits(fmt->red.max), fmt->red.shift};
c[1] = (Col){CGreen, nbits(fmt->green.max), fmt->green.shift};
c[2] = (Col){CBlue, nbits(fmt->blue.max), fmt->blue.shift};
nc = 3;
/* add an ignore channel if necessary */
depth = c[0].nbits+c[1].nbits+c[2].nbits;
if(fmt->bpp != depth){
/* BUG: assumes only one run of ignored bits */
if(fmt->bpp == 32)
mask = ~0;
else
mask = (1<<fmt->bpp)-1;
mask ^= fmt->red.max << fmt->red.shift;
mask ^= fmt->green.max << fmt->green.shift;
mask ^= fmt->blue.max << fmt->blue.shift;
if(mask == 0)
abort();
n = 0;
for(; !(mask&1); mask>>=1)
n++;
c[3] = (Col){CIgnore, nbits(mask), n};
nc++;
}
/* sort the channels, largest shift (leftmost bits) first */
for(i=1; i<nc; i++)
for(j=i; j>0; j--)
if(c[j].shift > c[j-1].shift){
t = c[j];
c[j] = c[j-1];
c[j-1] = t;
}
/* build the channel descriptor */
u = 0;
for(i=0; i<nc; i++){
u <<= 8;
u |= CHAN1(c[i].type, c[i].nbits);
}
return u;
}
static void
chan2fmt(Pixfmt *fmt, ulong chan)
{
ulong c, rc, shift;
shift = 0;
for(rc = chan; rc; rc >>=8){
c = rc & 0xFF;
switch(TYPE(c)){
case CRed:
fmt->red = (Colorfmt){(1<<NBITS(c))-1, shift};
break;
case CBlue:
fmt->blue = (Colorfmt){(1<<NBITS(c))-1, shift};
break;
case CGreen:
fmt->green = (Colorfmt){(1<<NBITS(c))-1, shift};
break;
}
shift += NBITS(c);
}
}
/*
* Note that r has changed on the screen.
* Updating the rlists is okay because they are protected by drawlock.
*/
void
flushmemscreen(Rectangle r)
{
Vncs *v;
if(!rectclip(&r, gscreen->clipr))
return;
qlock(&clients);
for(v=clients.head; v; v=v->next)
addtorlist(&v->rlist, r);
qunlock(&clients);
}
/*
* Queue a mouse warp note for the next update to each client.
*/
void
mousewarpnote(Point p)
{
Vncs *v;
qlock(&clients);
for(v=clients.head; v; v=v->next){
if(v->canwarp){
vnclock(v);
v->dowarp = 1;
v->warppt = p;
vncunlock(v);
}
}
qunlock(&clients);
}
/*
* Send a client his changed screen image.
* v is locked on entrance, locked on exit, but released during.
*/
static int
updateimage(Vncs *v)
{
int i, j, ncount, nsend, docursor, dowarp, doresize;
vlong ooffset = 0, t1 = 0;
Point warppt;
Rectangle cr;
Rlist rlist;
int (*count)(Vncs*, Rectangle);
int (*send)(Vncs*, Rectangle);
vnclock(v);
dowarp = v->canwarp && v->dowarp;
warppt = v->warppt;
v->dowarp = 0;
vncunlock(v);
/* copy the screen bits and then unlock the screen so updates can proceed */
qlock(&drawlock);
rlist = v->rlist;
memset(&v->rlist, 0, sizeof v->rlist);
if(v->canresize && !eqrect(v->screen[0].rect, gscreen->clipr)){
v->screen[0].rect = gscreen->clipr;
v->dim = rectsubpt(gscreen->clipr, gscreen->clipr.min);
doresize = 1;
} else
doresize = 0;
if(doresize
|| (v->image == nil && v->imagechan != 0)
|| (v->image != nil && v->image->chan != v->imagechan)){
if(v->image)
freememimage(v->image);
v->image = allocmemimage(v->dim, v->imagechan);
if(v->image == nil){
fprint(2, "%V: allocmemimage: %r; hanging up\n", v);
qlock(&drawlock);
vnchungup(v);
}
}
/* if the cursor has moved or changed shape, we need to redraw its square */
lock(&cursor);
if(v->cursorver != cursorver || !eqpt(v->cursorpos, cursorpos)){
docursor = 1;
v->cursorver = cursorver;
v->cursorpos = cursorpos;
cr = cursorrect();
}else{
docursor = 0;
cr = v->cursorr;
}
unlock(&cursor);
if(docursor){
addtorlist(&rlist, v->cursorr);
if(!rectclip(&cr, gscreen->clipr))
cr.max = cr.min;
addtorlist(&rlist, cr);
}
/* copy changed screen parts, also check for parts overlapping cursor location */
for(i=0; i<rlist.nrect; i++){
if(!docursor)
docursor = rectXrect(v->cursorr, rlist.rect[i]);
memimagedraw(v->image, rlist.rect[i], gscreen, rlist.rect[i].min, memopaque, ZP, S);
}
if(docursor){
cursordraw(v->image, cr);
addtorlist(&rlist, v->cursorr);
v->cursorr = cr;
}
qunlock(&drawlock);
count = v->countrect;
send = v->sendrect;
if(count == nil || send == nil){
count = countraw;
send = sendraw;
}
ncount = 0;
for(i=j=0; i<rlist.nrect; i++){
if(j < i)
rlist.rect[j] = rlist.rect[i];
if(rectclip(&rlist.rect[j], v->dim))
ncount += (*count)(v, rlist.rect[j++]);
}
rlist.nrect = j;
if(doresize == 0 && ncount == 0 && dowarp == 0)
return 0;
if(verbose > 1){
fprint(2, "sendupdate: rlist.nrect=%d, ncount=%d\n", rlist.nrect, ncount);
t1 = nsec();
ooffset = Boffset(&v->out);
}
if(doresize && v->canresize == 1){
doresize = 0;
vncwrchar(v, MFrameUpdate);
vncwrchar(v, 0);
vncwrshort(v, 1);
vncwrrect(v, v->dim);
vncwrlong(v, EncDesktopSize);
}
vncwrchar(v, MFrameUpdate);
vncwrchar(v, 0);
vncwrshort(v, doresize+ncount+dowarp);
if(doresize){
vncwrrect(v, gscreen->r);
vncwrlong(v, EncXDesktopSize);
vncwrlong(v, 1<<24);
vncwrlong(v, v->screen[0].id);
vncwrrect(v, v->screen[0].rect);
vncwrlong(v, v->screen[0].flags);
}
nsend = 0;
for(i=0; i<rlist.nrect; i++)
nsend += (*send)(v, rlist.rect[i]);
if(ncount != nsend){
fprint(2, "%V: ncount=%d, nsend=%d; hanging up\n", v, ncount, nsend);
vnchungup(v);
}
if(dowarp){
vncwrrect(v, Rect(warppt.x, warppt.y, warppt.x+1, warppt.y+1));
vncwrlong(v, EncMouseWarp);
}
if(verbose > 1){
t1 = nsec() - t1;
fprint(2, " in %lldms, %lld bytes\n", t1/1000000, Boffset(&v->out) - ooffset);
}
freerlist(&rlist);
return 1;
}
/*
* Update the snarf buffer if it has changed.
*/
static void
updatesnarf(Vncs *v)
{
char *buf;
int len;
if(v->snarfvers == snarf.vers)
return;
qlock(&snarf);
len = snarf.n;
buf = malloc(len);
if(buf == nil){
qunlock(&snarf);
return;
}
memmove(buf, snarf.buf, len);
v->snarfvers = snarf.vers;
qunlock(&snarf);
vncwrchar(v, MSCut);
vncwrbytes(v, "pad", 3);
vncwrlong(v, len);
vncwrbytes(v, buf, len);
free(buf);
}
/*
* Continually update one client.
*/
static void
clientwriteproc(Vncs *v)
{
ulong last = 0;
vncname("write %V", v);
while(!v->ndead){
sleep(sleeptime);
updatesnarf(v);
if(v->updatereq != last && updateimage(v))
last++;
vncflush(v);
}
vnchungup(v);
}