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