]> git.lizzy.rs Git - plan9front.git/blob - sys/src/cmd/vnc/vncs.c
vnc/devdraw: fix topnwindows() panic when images are not windows (thanks aiju)
[plan9front.git] / sys / src / cmd / vnc / vncs.c
1 #define Image   IMAGE
2 #include "vnc.h"
3 #include "vncs.h"
4 #include "compat.h"
5 #include <cursor.h>
6 #include "screen.h"
7 #include "kbd.h"
8
9 #include <mp.h>
10 #include <libsec.h>
11
12 extern  Dev     drawdevtab;
13 extern  Dev     mousedevtab;
14 extern  Dev     consdevtab;
15
16 Dev     *devtab[] =
17 {
18         &drawdevtab,
19         &mousedevtab,
20         &consdevtab,
21         nil
22 };
23
24 /* 
25  * list head. used to hold the list, the lock, dim, and pixelfmt
26  */
27 struct {
28         QLock;
29         Vncs *head;
30 } clients;
31
32 int     shared;
33 int     sleeptime = 5;
34 int     verbose = 0;
35 int     noauth = 0;
36 int     kbdin = -1;
37
38 char *cert;
39 char *pixchan = "r5g6b5";
40 static int      cmdpid;
41 static int      srvfd;
42 static int      exportfd;
43 static Vncs     **vncpriv;
44
45 static int parsedisplay(char*);
46 static void vnckill(char*, int, int);
47 static int vncannounce(char *net, int display, char *adir, int base);
48 static void noteshutdown(void*, char*);
49 static void vncaccept(Vncs*);
50 static int vncsfmt(Fmt*);
51 static void getremote(char*, char*);
52 static void vncname(char*, ...);
53 #pragma varargck argpos vncname 1
54
55 #pragma varargck type "V" Vncs*
56
57 void
58 usage(void)
59 {
60         fprint(2, "usage: vncs [-v] [-c cert] [-d :display] [-g widthXheight] [-p pixelfmt] [-A] [cmd [args]...]\n");
61         fprint(2, "\tto kill a server: vncs [-v] -k :display\n");
62         exits("usage");
63 }
64
65 void
66 main(int argc, char **argv)
67 {
68         int baseport, cfd, display, exnum, fd, pid, h, killing, w;
69         char adir[NETPATHLEN], ldir[NETPATHLEN];
70         char net[NETPATHLEN], *p;
71         char *kbdfs[] = { "/bin/aux/kbdfs", "-dq", nil };
72         char *rc[] = { "/bin/rc", "-i", nil };
73         Vncs *v;
74
75         fmtinstall('V', vncsfmt);
76         display = -1;
77         killing = 0;
78         w = 1024;
79         h = 768;
80         baseport = 5900;
81         setnetmtpt(net, sizeof net, nil);
82         ARGBEGIN{
83         default:
84                 usage();
85         case 'c':
86                 cert = EARGF(usage());
87                 baseport = 35729;
88                 break;
89         case 'd':
90                 if(display != -1)
91                         usage();
92                 display = parsedisplay(EARGF(usage()));
93                 break;
94         case 'g':
95                 p = EARGF(usage());
96                 w = strtol(p, &p, 10);
97                 if(*p != 'x' && *p != 'X' && *p != ' ' && *p != '       ')
98                         usage();
99                 h = strtol(p+1, &p, 10);
100                 if(*p != 0)
101                         usage();
102                 break;
103         case 'k':
104                 if(display != -1)
105                         usage();
106                 display = parsedisplay(EARGF(usage()));
107                 killing = 1;
108                 break;
109         case 'p':
110                 pixchan = EARGF(usage());
111                 break;
112 /* DEBUGGING
113         case 's':
114                 sleeptime = atoi(EARGF(usage()));
115                 break;
116 */
117         case 'v':
118                 verbose++;
119                 break;
120         case 'x':
121                 p = EARGF(usage());
122                 setnetmtpt(net, sizeof net, p);
123                 break;
124         case 'A':
125                 noauth = 1;
126                 break;
127         }ARGEND
128
129         if(killing){
130                 vnckill(net, display, baseport);
131                 exits(nil);
132         }
133
134         if(argc == 0)
135                 argv = rc;
136
137         /* easy exit */
138         if(access(argv[0], AEXEC) < 0)
139                 sysfatal("access %s for exec: %r", argv[0]);
140
141         /* background ourselves */
142         switch(rfork(RFPROC|RFNAMEG|RFFDG|RFNOTEG)){
143         case -1:
144                 sysfatal("rfork: %r");
145         default:
146                 exits(nil);
147         case 0:
148                 break;
149         }
150
151         vncpriv = privalloc();
152         if(vncpriv == nil)
153                 sysfatal("privalloc: %r");
154
155         /* start screen */
156         initcompat();
157         if(waserror())
158                 sysfatal("screeninit %dx%d %s: %s", w, h, pixchan, up->error);
159         if(verbose)
160                 fprint(2, "geometry is %dx%d\n", w, h);
161         screeninit(w, h, pixchan);
162         poperror();
163
164         /* start file system device slaves */
165         exnum = exporter(devtab, &fd, &exportfd);
166
167         /* rebuild /dev because the underlying connection might go away (ick) */
168         unmount(nil, "/dev");
169         bind("#c", "/dev", MREPL);
170
171         /* mount exporter */
172         if(mounter("/dev", MBEFORE, fd, exnum) < 0)
173                 sysfatal("mounter: %r");
174         close(fd);
175
176         pid = rfork(RFPROC|RFMEM|RFFDG|RFNOTEG);
177         switch(pid){
178         case -1:
179                 sysfatal("rfork: %r");
180                 break;
181         case 0:
182                 close(exportfd);
183
184                 close(1);
185                 open("/dev/cons", OWRITE);
186                 close(2);
187                 open("/dev/cons", OWRITE);
188
189                 /* start and mount kbdfs */
190                 pid = rfork(RFPROC|RFMEM|RFFDG|RFREND);
191                 switch(pid){
192                 case -1:
193                         sysfatal("rfork: %r");
194                         break;
195                 case 0:
196                         exec(kbdfs[0], kbdfs);
197                         fprint(2, "exec %s: %r\n", kbdfs[0]);
198                         _exits("kbdfs");
199                 }
200                 if(waitpid() != pid){
201                         rendezvous(&kbdin, nil);
202                         sysfatal("waitpid: %s: %r", kbdfs[0]);
203                 }
204                 rendezvous(&kbdin, nil);
205
206                 rfork(RFNAMEG|RFREND);
207
208                 close(0);
209                 open("/dev/cons", OREAD);
210                 close(1);
211                 open("/dev/cons", OWRITE);
212                 close(2);
213                 open("/dev/cons", OWRITE);
214
215                 exec(argv[0], argv);
216                 fprint(2, "exec %s: %r\n", argv[0]);
217                 _exits(nil);
218         }
219         cmdpid = pid;
220
221         /* wait for kbdfs to get mounted */
222         rendezvous(&kbdin, nil);
223         if((kbdin = open("/dev/kbdin", OWRITE)) < 0)
224                 sysfatal("open /dev/kbdin: %r");
225
226         /* run the service */
227         srvfd = vncannounce(net, display, adir, baseport);
228         if(srvfd < 0)
229                 sysfatal("announce failed");
230         if(verbose)
231                 fprint(2, "announced in %s\n", adir);
232
233         atexit(shutdown);
234         notify(noteshutdown);
235         for(;;){
236                 vncname("listener");
237                 cfd = listen(adir, ldir);
238                 if(cfd < 0)
239                         break;
240                 if(verbose)
241                         fprint(2, "call in %s\n", ldir);
242                 fd = accept(cfd, ldir);
243                 if(fd < 0){
244                         close(cfd);
245                         continue;
246                 }
247                 v = mallocz(sizeof(Vncs), 1);
248                 if(v == nil){
249                         close(cfd);
250                         close(fd);
251                         continue;
252                 }
253                 v->ctlfd = cfd;
254                 v->datafd = fd;
255                 v->nproc = 1;
256                 v->ndead = 0;
257                 getremote(ldir, v->remote);
258                 strcpy(v->netpath, ldir);
259                 qlock(&clients);
260                 v->next = clients.head;
261                 clients.head = v;
262                 qunlock(&clients);
263                 vncaccept(v);
264         }
265         exits(0);
266 }
267
268 static int
269 parsedisplay(char *p)
270 {
271         int n;
272
273         if(*p != ':')
274                 usage();
275         if(*p == 0)
276                 usage();
277         n = strtol(p+1, &p, 10);
278         if(*p != 0)
279                 usage();
280         return n;
281 }
282
283 static void
284 getremote(char *ldir, char *remote)
285 {
286         char buf[NETPATHLEN];
287         int fd, n;
288
289         snprint(buf, sizeof buf, "%s/remote", ldir);
290         strcpy(remote, "<none>");
291         if((fd = open(buf, OREAD)) < 0)
292                 return;
293         n = readn(fd, remote, NETPATHLEN-1);
294         close(fd);
295         if(n < 0)
296                 return;
297         remote[n] = 0;
298         if(n>0 && remote[n-1] == '\n')
299                 remote[n-1] = 0;
300 }
301
302 static int
303 vncsfmt(Fmt *fmt)
304 {
305         Vncs *v;
306
307         v = va_arg(fmt->args, Vncs*);
308         return fmtprint(fmt, "[%d] %s %s", getpid(), v->remote, v->netpath);
309 }
310
311 /*
312  * We register exiting as an atexit handler in each proc, so that 
313  * client procs need merely exit when something goes wrong.
314  */
315 static void
316 vncclose(Vncs *v)
317 {
318         Vncs **l;
319
320         /* remove self from client list if there */
321         qlock(&clients);
322         for(l=&clients.head; *l; l=&(*l)->next)
323                 if(*l == v){
324                         *l = v->next;
325                         break;
326                 }
327         qunlock(&clients);
328
329         /* if last proc, free v */
330         vnclock(v);
331         if(++v->ndead < v->nproc){
332                 vncunlock(v);
333                 return;
334         }
335
336         freerlist(&v->rlist);
337         vncterm(v);
338         if(v->ctlfd >= 0)
339                 close(v->ctlfd);
340         if(v->datafd >= 0)
341                 close(v->datafd);
342         if(v->image)
343                 freememimage(v->image);
344         free(v);
345 }
346
347 static void
348 exiting(void)
349 {
350         vncclose(*vncpriv);
351 }
352
353 void
354 vnchungup(Vnc *v)
355 {
356         if(verbose)
357                 fprint(2, "%V: hangup\n", (Vncs*)v);
358         exits(0);       /* atexit and exiting() will take care of everything */
359 }
360
361 /*
362  * Kill all clients except safe.
363  * Used to start a non-shared client and at shutdown. 
364  */
365 static void
366 killclients(Vncs *safe)
367 {
368         Vncs *v;
369
370         qlock(&clients);
371         for(v=clients.head; v; v=v->next){
372                 if(v == safe)
373                         continue;
374                 if(v->ctlfd >= 0){
375                         hangup(v->ctlfd);
376                         close(v->ctlfd);
377                         v->ctlfd = -1;
378                         close(v->datafd);
379                         v->datafd = -1;
380                 }
381         }
382         qunlock(&clients);
383 }
384
385 /*
386  * Kill the executing command and then kill everyone else.
387  * Called to close up shop at the end of the day
388  * and also if we get an unexpected note.
389  */
390 static char killkin[] = "die vnc kin";
391 static void
392 killall(void)
393 {
394         postnote(PNGROUP, cmdpid, "hangup");
395         close(srvfd);
396         srvfd = -1;
397         close(exportfd);
398         exportfd = -1;
399         close(kbdin);
400         kbdin = -1;
401         postnote(PNGROUP, getpid(), killkin);
402 }
403
404 void
405 shutdown(void)
406 {
407         if(verbose)
408                 fprint(2, "vnc server shutdown\n");
409         killall();
410 }
411
412 static void
413 noteshutdown(void*, char *msg)
414 {
415         if(strcmp(msg, killkin) == 0)   /* already shutting down */
416                 noted(NDFLT);
417         killall();
418         noted(NDFLT);
419 }
420
421 /*
422  * Kill a specific instance of a server.
423  */
424 static void
425 vnckill(char *net, int display, int baseport)
426 {
427         int fd, i, n, port;
428         char buf[NETPATHLEN], *p;
429
430         for(i=0;; i++){
431                 snprint(buf, sizeof buf, "%s/tcp/%d/local", net, i);
432                 if((fd = open(buf, OREAD)) < 0)
433                         sysfatal("did not find display");
434                 n = read(fd, buf, sizeof buf-1);
435                 close(fd);
436                 if(n <= 0)
437                         continue;
438                 buf[n] = 0;
439                 p = strchr(buf, '!');
440                 if(p == 0)
441                         continue;
442                 port = atoi(p+1);
443                 if(port != display+baseport)
444                         continue;
445                 snprint(buf, sizeof buf, "%s/tcp/%d/ctl", net, i);
446                 fd = open(buf, OWRITE);
447                 if(fd < 0)
448                         sysfatal("cannot open %s: %r", buf);
449                 if(write(fd, "hangup", 6) != 6)
450                         sysfatal("cannot hangup %s: %r", buf);
451                 close(fd);
452                 break;
453         }
454 }
455
456 /*
457  * Look for a port on which to announce.
458  * If display != -1, we only try that one.
459  * Otherwise we hunt.
460  *
461  * Returns the announce fd.
462  */
463 static int
464 vncannounce(char *net, int display, char *adir, int base)
465 {
466         int port, eport, fd;
467         char addr[NETPATHLEN];
468
469         if(display == -1){
470                 port = base;
471                 eport = base+50;
472         }else{
473                 port = base+display;
474                 eport = port;
475         }
476
477         for(; port<=eport; port++){
478                 snprint(addr, sizeof addr, "%s/tcp!*!%d", net, port);
479                 if((fd = announce(addr, adir)) >= 0){
480                         fprint(2, "server started on display :%d\n", port-base);
481                         return fd;
482                 }
483         }
484         if(display == -1)
485                 fprint(2, "could not find any ports to announce\n");
486         else
487                 fprint(2, "announce: %r\n");
488         return -1;
489 }
490
491 /*
492  * Handle a new connection.
493  */
494 static void clientreadproc(Vncs*);
495 static void clientwriteproc(Vncs*);
496 static void chan2fmt(Pixfmt*, ulong);
497 static ulong fmt2chan(Pixfmt*);
498
499 static void
500 vncaccept(Vncs *v)
501 {
502         char buf[32];
503         int fd;
504
505         /* caller returns to listen */
506         switch(rfork(RFPROC|RFMEM|RFNAMEG)){
507         case -1:
508                 fprint(2, "%V: fork failed: %r\n", v);
509                 vncclose(v);
510                 return;
511         default:
512                 return;
513         case 0:
514                 break;
515         }
516         *vncpriv = v;
517
518         if(!atexit(exiting)){
519                 fprint(2, "%V: could not register atexit handler: %r; hanging up\n", v);
520                 exiting();
521                 exits(nil);
522         }
523
524         if(cert != nil){
525                 TLSconn conn;
526
527                 memset(&conn, 0, sizeof conn);
528                 conn.cert = readcert(cert, &conn.certlen);
529                 if(conn.cert == nil){
530                         fprint(2, "%V: could not read cert %s: %r; hanging up\n", v, cert);
531                         exits(nil);
532                 }
533                 fd = tlsServer(v->datafd, &conn);
534                 if(fd < 0){
535                         fprint(2, "%V: tlsServer: %r; hanging up\n", v);
536                         free(conn.cert);
537                         free(conn.sessionID);
538                         exits(nil);
539                 }
540                 v->datafd = fd;
541                 free(conn.cert);
542                 free(conn.sessionID);
543         }
544         vncinit(v->datafd, v->ctlfd, v);
545
546         if(verbose)
547                 fprint(2, "%V: handshake\n", v);
548         if(vncsrvhandshake(v) < 0){
549                 fprint(2, "%V: handshake failed; hanging up\n", v);
550                 exits(0);
551         }
552
553         if(noauth){
554                 if(verbose)
555                         fprint(2, "%V: noauth\n", v);
556                 vncwrlong(v, ANoAuth);
557                 vncflush(v);
558         } else {
559                 if(verbose)
560                         fprint(2, "%V: auth\n", v);
561                 if(vncsrvauth(v) < 0){
562                         fprint(2, "%V: auth failed; hanging up\n", v);
563                         exits(0);
564                 }
565         }
566
567         shared = vncrdchar(v);
568
569         if(verbose)
570                 fprint(2, "%V: %sshared\n", v, shared ? "" : "not ");
571         if(!shared)
572                 killclients(v);
573
574         v->dim = rectsubpt(gscreen->clipr, gscreen->clipr.min);
575         vncwrpoint(v, v->dim.max);
576         if(verbose)
577                 fprint(2, "%V: send screen size %R\n", v, v->dim);
578
579         v->bpp = gscreen->depth;
580         v->depth = gscreen->depth;
581         v->truecolor = 1;
582         v->bigendian = 0;
583         chan2fmt(v, gscreen->chan);
584         if(verbose)
585                 fprint(2, "%V: bpp=%d, depth=%d, chan=%s\n", v,
586                         v->bpp, v->depth, chantostr(buf, gscreen->chan));
587         vncwrpixfmt(v, v);
588         vncwrlong(v, 14);
589         vncwrbytes(v, "Plan9 Desktop", 14);
590         vncflush(v);
591
592         if(verbose)
593                 fprint(2, "%V: handshaking done\n", v);
594
595         v->updatereq = 0;
596
597         switch(rfork(RFPROC|RFMEM)){
598         case -1:
599                 fprint(2, "%V: cannot fork: %r; hanging up\n", v);
600                 vnchungup(v);
601         default:
602                 clientreadproc(v);
603                 exits(nil);
604         case 0:
605                 *vncpriv = v;
606                 v->nproc++;
607                 if(atexit(exiting) == 0){
608                         exiting();
609                         fprint(2, "%V: could not register atexit handler: %r; hanging up\n", v);
610                         exits(nil);
611                 }
612                 clientwriteproc(v);
613                 exits(nil);
614         }
615 }
616
617 static void
618 vncname(char *fmt, ...)
619 {
620         int fd;
621         char name[64], buf[32];
622         va_list arg;
623
624         va_start(arg, fmt);
625         vsnprint(name, sizeof name, fmt, arg);
626         va_end(arg);
627
628         sprint(buf, "/proc/%d/args", getpid());
629         if((fd = open(buf, OWRITE)) >= 0){
630                 write(fd, name, strlen(name));
631                 close(fd);
632         }
633 }
634
635 /*
636  * Set the pixel format being sent.  Can only happen once.
637  * (Maybe a client would send this again if the screen changed
638  * underneath it?  If we want to support this we need a way to
639  * make sure the current image is no longer in use, so we can free it. 
640  */
641 static void
642 setpixelfmt(Vncs *v)
643 {
644         ulong chan;
645
646         vncgobble(v, 3);
647         v->Pixfmt = vncrdpixfmt(v);
648         chan = fmt2chan(v);
649         if(chan == 0){
650                 fprint(2, "%V: bad pixel format; hanging up\n", v);
651                 vnchungup(v);
652         }
653         v->imagechan = chan;
654 }
655
656 /*
657  * Set the preferred encoding list.  Can only happen once.
658  * If we want to support changing this more than once then
659  * we need to put a lock around the encoding functions
660  * so as not to conflict with updateimage.
661  */
662 static void
663 setencoding(Vncs *v)
664 {
665         int n, x;
666
667         vncrdchar(v);
668         n = vncrdshort(v);
669         while(n-- > 0){
670                 x = vncrdlong(v);
671                 switch(x){
672                 case EncCopyRect:
673                         v->copyrect = 1;
674                         continue;
675                 case EncMouseWarp:
676                         v->canwarp = 1;
677                         continue;
678                 case EncDesktopSize:
679                         v->canresize |= 1;
680                         continue;
681                 case EncXDesktopSize:
682                         v->canresize |= 2;
683                         continue;
684                 }
685                 if(v->countrect != nil)
686                         continue;
687                 switch(x){
688                 case EncRaw:
689                         v->encname = "raw";
690                         v->countrect = countraw;
691                         v->sendrect = sendraw;
692                         break;
693                 case EncRre:
694                         v->encname = "rre";
695                         v->countrect = countrre;
696                         v->sendrect = sendrre;
697                         break;
698                 case EncCorre:
699                         v->encname = "corre";
700                         v->countrect = countcorre;
701                         v->sendrect = sendcorre;
702                         break;
703                 case EncHextile:
704                         v->encname = "hextile";
705                         v->countrect = counthextile;
706                         v->sendrect = sendhextile;
707                         break;
708                 }
709         }
710
711         if(v->countrect == nil){
712                 v->encname = "raw";
713                 v->countrect = countraw;
714                 v->sendrect = sendraw;
715         }
716
717         if(verbose)
718                 fprint(2, "Encoding with %s%s%s%s\n", v->encname,
719                         v->copyrect ? ", copyrect" : "",
720                         v->canwarp ? ", canwarp" : "",
721                         v->canresize ? ", resize" : "");
722 }
723
724 /*
725  * Continually read updates from one client.
726  */
727 static void
728 clientreadproc(Vncs *v)
729 {
730         int incremental, key, keydown, buttons, type, x, y, n;
731         char *buf;
732         Rectangle r;
733
734         vncname("read %V", v);
735
736         for(;;){
737                 type = vncrdchar(v);
738                 switch(type){
739                 default:
740                         fprint(2, "%V: unknown vnc message type %d; hanging up\n", v, type);
741                         vnchungup(v);
742
743                 /* set pixel format */
744                 case MPixFmt:
745                         setpixelfmt(v);
746                         break;
747
748                 /* ignore color map changes */
749                 case MFixCmap:
750                         vncgobble(v, 3);
751                         n = vncrdshort(v);
752                         vncgobble(v, n*6);
753                         break;
754
755                 /* set encoding list */
756                 case MSetEnc:
757                         setencoding(v);
758                         break;
759
760                 /* request image update in rectangle */
761                 case MFrameReq:
762                         incremental = vncrdchar(v);
763                         r = vncrdrect(v);
764                         if(!incremental){
765                                 qlock(&drawlock);       /* protects rlist */
766                                 addtorlist(&v->rlist, r);
767                                 qunlock(&drawlock);
768                         }
769                         v->updatereq++;
770                         break;
771
772                 case MSetDesktopSize:
773                         vncrdchar(v);
774                         vncrdpoint(v);  // desktop size
775                         n = vncrdchar(v);
776                         vncrdchar(v);
777                         if(n == 0)
778                                 break;
779                         vncrdlong(v);   // id
780                         r = vncrdrect(v);
781                         vncrdlong(v);   // flags
782                         while(--n > 0){
783                                 vncrdlong(v);
784                                 vncrdrect(v);
785                                 vncrdlong(v);
786                         }
787                         qlock(&drawlock);
788                         if(!rectclip(&r, gscreen->r)){
789                                 qunlock(&drawlock);
790                                 break;
791                         }
792                         gscreen->clipr = r;
793                         qunlock(&drawlock);
794
795                         screenwin();
796                         deletescreenimage();
797                         resetscreenimage();
798                         break;
799
800                 /* send keystroke */
801                 case MKey:
802                         keydown = vncrdchar(v);
803                         vncgobble(v, 2);
804                         key = vncrdlong(v);
805                         vncputc(!keydown, key);
806                         break;
807
808                 /* send mouse event */
809                 case MMouse:
810                         buttons = vncrdchar(v);
811                         x = vncrdshort(v);
812                         y = vncrdshort(v);
813                         absmousetrack(x, y, buttons, nsec()/(1000*1000LL));
814                         break;
815
816                 /* send cut text */
817                 case MCCut:
818                         vncgobble(v, 3);
819                         n = vncrdlong(v);
820                         buf = malloc(n+1);
821                         if(buf){
822                                 vncrdbytes(v, buf, n);
823                                 buf[n] = 0;
824                                 vnclock(v);     /* for snarfvers */
825                                 setsnarf(buf, n, &v->snarfvers);
826                                 vncunlock(v);
827                         }else
828                                 vncgobble(v, n);
829                         break;
830                 }
831         }
832 }
833
834 static int
835 nbits(ulong mask)
836 {
837         int n;
838
839         n = 0;
840         for(; mask; mask>>=1)
841                 n += mask&1;
842         return n;
843 }
844
845 typedef struct Col Col;
846 struct Col {
847         int type;
848         int nbits;
849         int shift;
850 };
851
852 static ulong
853 fmt2chan(Pixfmt *fmt)
854 {
855         Col c[4], t;
856         int i, j, depth, n, nc;
857         ulong mask, u;
858
859         /* unpack the Pixfmt channels */
860         c[0] = (Col){CRed, nbits(fmt->red.max), fmt->red.shift};
861         c[1] = (Col){CGreen, nbits(fmt->green.max), fmt->green.shift};
862         c[2] = (Col){CBlue, nbits(fmt->blue.max), fmt->blue.shift};
863         nc = 3;
864
865         /* add an ignore channel if necessary */
866         depth = c[0].nbits+c[1].nbits+c[2].nbits;
867         if(fmt->bpp != depth){
868                 /* BUG: assumes only one run of ignored bits */
869                 if(fmt->bpp == 32)
870                         mask = ~0;
871                 else
872                         mask = (1<<fmt->bpp)-1;
873                 mask ^= fmt->red.max << fmt->red.shift;
874                 mask ^= fmt->green.max << fmt->green.shift;
875                 mask ^= fmt->blue.max << fmt->blue.shift;
876                 if(mask == 0)
877                         abort();
878                 n = 0;
879                 for(; !(mask&1); mask>>=1)
880                         n++;
881                 c[3] = (Col){CIgnore, nbits(mask), n};
882                 nc++;
883         }
884
885         /* sort the channels, largest shift (leftmost bits) first */
886         for(i=1; i<nc; i++)
887                 for(j=i; j>0; j--)
888                         if(c[j].shift > c[j-1].shift){
889                                 t = c[j];
890                                 c[j] = c[j-1];
891                                 c[j-1] = t;
892                         }
893
894         /* build the channel descriptor */
895         u = 0;
896         for(i=0; i<nc; i++){
897                 u <<= 8;
898                 u |= CHAN1(c[i].type, c[i].nbits);
899         }
900
901         return u;
902 }
903
904 static void
905 chan2fmt(Pixfmt *fmt, ulong chan)
906 {
907         ulong c, rc, shift;
908
909         shift = 0;
910         for(rc = chan; rc; rc >>=8){
911                 c = rc & 0xFF;
912                 switch(TYPE(c)){
913                 case CRed:
914                         fmt->red = (Colorfmt){(1<<NBITS(c))-1, shift};
915                         break;
916                 case CBlue:
917                         fmt->blue = (Colorfmt){(1<<NBITS(c))-1, shift};
918                         break;
919                 case CGreen:
920                         fmt->green = (Colorfmt){(1<<NBITS(c))-1, shift};
921                         break;
922                 }
923                 shift += NBITS(c);
924         }
925 }
926
927 /*
928  * Note that r has changed on the screen.
929  * Updating the rlists is okay because they are protected by drawlock.
930  */
931 void
932 flushmemscreen(Rectangle r)
933 {
934         Vncs *v;
935
936         if(!rectclip(&r, gscreen->clipr))
937                 return;
938         qlock(&clients);
939         for(v=clients.head; v; v=v->next)
940                 addtorlist(&v->rlist, r);
941         qunlock(&clients);
942 }
943
944 /*
945  * Queue a mouse warp note for the next update to each client.
946  */
947 void
948 mousewarpnote(Point p)
949 {
950         Vncs *v;
951
952         qlock(&clients);
953         for(v=clients.head; v; v=v->next){
954                 if(v->canwarp){
955                         vnclock(v);
956                         v->dowarp = 1;
957                         v->warppt = p;
958                         vncunlock(v);
959                 }
960         }
961         qunlock(&clients);
962 }
963
964 /*
965  * Send a client his changed screen image.
966  * v is locked on entrance, locked on exit, but released during.
967  */
968 static int
969 updateimage(Vncs *v)
970 {
971         int i, j, ncount, nsend, docursor, dowarp, doresize;
972         vlong ooffset = 0, t1 = 0;
973         Point warppt;
974         Rectangle cr;
975         Rlist rlist;
976         int (*count)(Vncs*, Rectangle);
977         int (*send)(Vncs*, Rectangle);
978
979         vnclock(v);
980         dowarp = v->canwarp && v->dowarp;
981         warppt = v->warppt;
982         v->dowarp = 0;
983         vncunlock(v);
984
985         /* copy the screen bits and then unlock the screen so updates can proceed */
986         qlock(&drawlock);
987         rlist = v->rlist;
988         memset(&v->rlist, 0, sizeof v->rlist);
989
990         if(v->canresize && !eqrect(v->screen[0].rect, gscreen->clipr)){
991                 v->screen[0].rect = gscreen->clipr;
992                 v->dim = rectsubpt(gscreen->clipr, gscreen->clipr.min);
993                 doresize = 1;
994         } else
995                 doresize = 0;
996
997         if(doresize
998         || (v->image == nil && v->imagechan != 0)
999         || (v->image != nil && v->image->chan != v->imagechan)){
1000                 if(v->image)
1001                         freememimage(v->image);
1002                 v->image = allocmemimage(v->dim, v->imagechan);
1003                 if(v->image == nil){
1004                         fprint(2, "%V: allocmemimage: %r; hanging up\n", v);
1005                         qlock(&drawlock);
1006                         vnchungup(v);
1007                 }
1008         }
1009
1010         /* if the cursor has moved or changed shape, we need to redraw its square */
1011         lock(&cursor);
1012         if(v->cursorver != cursorver || !eqpt(v->cursorpos, cursorpos)){
1013                 docursor = 1;
1014                 v->cursorver = cursorver;
1015                 v->cursorpos = cursorpos;
1016                 cr = cursorrect();
1017         }else{
1018                 docursor = 0;
1019                 cr = v->cursorr;
1020         }
1021         unlock(&cursor);
1022
1023         if(docursor){
1024                 addtorlist(&rlist, v->cursorr);
1025                 if(!rectclip(&cr, gscreen->clipr))
1026                         cr.max = cr.min;
1027                 addtorlist(&rlist, cr);
1028         }
1029
1030         /* copy changed screen parts, also check for parts overlapping cursor location */
1031         for(i=0; i<rlist.nrect; i++){
1032                 if(!docursor)
1033                         docursor = rectXrect(v->cursorr, rlist.rect[i]);
1034                 memimagedraw(v->image, rlist.rect[i], gscreen, rlist.rect[i].min, memopaque, ZP, S);
1035         }
1036
1037         if(docursor){
1038                 cursordraw(v->image, cr);
1039                 addtorlist(&rlist, v->cursorr);
1040                 v->cursorr = cr;
1041         }
1042
1043         qunlock(&drawlock);
1044
1045         count = v->countrect;
1046         send = v->sendrect;
1047         if(count == nil || send == nil){
1048                 count = countraw;
1049                 send = sendraw;
1050         }
1051
1052         ncount = 0;
1053         for(i=j=0; i<rlist.nrect; i++){
1054                 if(j < i)
1055                         rlist.rect[j] = rlist.rect[i];
1056                 if(rectclip(&rlist.rect[j], v->dim))
1057                         ncount += (*count)(v, rlist.rect[j++]);
1058         }
1059         rlist.nrect = j;
1060
1061         if(doresize == 0 && ncount == 0 && dowarp == 0)
1062                 return 0;
1063
1064         if(verbose > 1){
1065                 fprint(2, "sendupdate: rlist.nrect=%d, ncount=%d\n", rlist.nrect, ncount);
1066                 t1 = nsec();
1067                 ooffset = Boffset(&v->out);
1068         }
1069
1070         if(doresize && v->canresize == 1){
1071                 doresize = 0;
1072
1073                 vncwrchar(v, MFrameUpdate);
1074                 vncwrchar(v, 0);
1075                 vncwrshort(v, 1);
1076                 vncwrrect(v, v->dim);
1077                 vncwrlong(v, EncDesktopSize);
1078         }
1079
1080         vncwrchar(v, MFrameUpdate);
1081         vncwrchar(v, 0);
1082         vncwrshort(v, doresize+ncount+dowarp);
1083
1084         if(doresize){
1085                 vncwrrect(v, gscreen->r);
1086                 vncwrlong(v, EncXDesktopSize);
1087                 vncwrlong(v, 1<<24);
1088                 vncwrlong(v, v->screen[0].id);
1089                 vncwrrect(v, v->screen[0].rect);
1090                 vncwrlong(v, v->screen[0].flags);
1091         }
1092
1093         nsend = 0;
1094         for(i=0; i<rlist.nrect; i++)
1095                 nsend += (*send)(v, rlist.rect[i]);
1096
1097         if(ncount != nsend){
1098                 fprint(2, "%V: ncount=%d, nsend=%d; hanging up\n", v, ncount, nsend);
1099                 vnchungup(v);
1100         }
1101
1102         if(dowarp){
1103                 vncwrrect(v, Rect(warppt.x, warppt.y, warppt.x+1, warppt.y+1));
1104                 vncwrlong(v, EncMouseWarp);
1105         }
1106
1107         if(verbose > 1){
1108                 t1 = nsec() - t1;
1109                 fprint(2, " in %lldms, %lld bytes\n", t1/1000000, Boffset(&v->out) - ooffset);
1110         }
1111
1112         freerlist(&rlist);
1113         return 1;
1114 }
1115
1116 /*
1117  * Update the snarf buffer if it has changed.
1118  */
1119 static void
1120 updatesnarf(Vncs *v)
1121 {
1122         char *buf;
1123         int len;
1124
1125         if(v->snarfvers == snarf.vers)
1126                 return;
1127         qlock(&snarf);
1128         len = snarf.n;
1129         buf = malloc(len);
1130         if(buf == nil){
1131                 qunlock(&snarf);
1132                 return;
1133         }
1134         memmove(buf, snarf.buf, len);
1135         v->snarfvers = snarf.vers;
1136         qunlock(&snarf);
1137
1138         vncwrchar(v, MSCut);
1139         vncwrbytes(v, "pad", 3);
1140         vncwrlong(v, len);
1141         vncwrbytes(v, buf, len);
1142         free(buf);
1143 }
1144
1145 /*
1146  * Continually update one client.
1147  */
1148 static void
1149 clientwriteproc(Vncs *v)
1150 {
1151         ulong last = 0;
1152
1153         vncname("write %V", v);
1154         while(!v->ndead){
1155                 sleep(sleeptime);
1156                 updatesnarf(v);
1157                 if(v->updatereq != last && updateimage(v))
1158                         last++;
1159                 vncflush(v);
1160         }
1161         vnchungup(v);
1162 }
1163