]> git.lizzy.rs Git - plan9front.git/blob - sys/src/cmd/page.c
page: use less aggressive read ahead and keep track of image memory allocation
[plan9front.git] / sys / src / cmd / page.c
1 #include <u.h>
2 #include <libc.h>
3 #include <draw.h>
4 #include <event.h>
5 #include <cursor.h>
6 #include <keyboard.h>
7 #include <plumb.h>
8
9 typedef struct Page Page;
10 struct Page {
11         char    *label;
12
13         QLock;
14         char    *ext;
15         void    *data;
16         int     (*open)(Page *);
17
18         Image   *image;
19         int     fd;
20
21         Page    *up;
22         Page    *next;
23         Page    *down;
24         Page    *tail;
25 };
26
27 int zoom = 1;
28 int ppi = 100;
29 int imode;
30 int newwin;
31 int rotate;
32 int viewgen;
33 Point resize, pos;
34 Page *root, *current;
35 QLock pagelock;
36 int nullfd;
37
38 enum {
39         MiB     = 1024*1024,
40 };
41
42 ulong imemlimit = 8*MiB;
43 ulong imemsize;
44
45 Image *frame, *paper, *ground;
46
47 char pagespool[] = "/tmp/pagespool.";
48
49 enum {
50         NPROC = 4,
51         NBUF = 8*1024,
52         NPATH = 1024,
53 };
54
55 enum {
56         Corigsize,
57         Czoomin,
58         Czoomout,
59         Cfitwidth,
60         Cfitheight,
61         Crotate90,
62         Cupsidedown,
63         Cdummy1,
64         Cnext,
65         Cprev,
66         Czerox,
67         Cwrite,
68         Cext,
69         Cdummy2,
70         Cquit,
71 };
72
73 struct {
74         char    *m;
75         Rune    k1;
76         Rune    k2;
77         Rune    k3;
78 } cmds[] = {
79         [Corigsize]     "orig size",    'o', Kesc, 0,
80         [Czoomin]       "zoom in",      '+', 0, 0,
81         [Czoomout]      "zoom out",     '-', 0, 0,
82         [Cfitwidth]     "fit width",    'f', 0, 0,
83         [Cfitheight]    "fit height",   'h', 0, 0,
84         [Crotate90]     "rotate 90",    'r', 0, 0,
85         [Cupsidedown]   "upside down",  'u', 0, 0,
86         [Cdummy1]       "",             0, 0, 0,
87         [Cnext]         "next",         Kright, ' ', '\n', 
88         [Cprev]         "prev",         Kleft, Kbs, 0,
89         [Czerox]        "zerox",        'z', 0, 0,
90         [Cwrite]        "write",        'w', 0, 0,
91         [Cext]          "ext",          'x', 0, 0,
92         [Cdummy2]       "",             0, 0, 0,
93         [Cquit]         "quit",         'q', Kdel, Keof,
94 };
95
96 char *pagemenugen(int i);
97 char *cmdmenugen(int i);
98
99 Menu pagemenu = {
100         nil,
101         pagemenugen,
102         -1,
103 };
104
105 Menu cmdmenu = {
106         nil,
107         cmdmenugen,
108         -1,
109 };
110
111 Cursor reading = {
112         {-1, -1},
113         {0xff, 0x80, 0xff, 0x80, 0xff, 0x00, 0xfe, 0x00, 
114          0xff, 0x00, 0xff, 0x80, 0xff, 0xc0, 0xef, 0xe0, 
115          0xc7, 0xf0, 0x03, 0xf0, 0x01, 0xe0, 0x00, 0xc0, 
116          0x03, 0xff, 0x03, 0xff, 0x03, 0xff, 0x03, 0xff, },
117         {0x00, 0x00, 0x7f, 0x00, 0x7e, 0x00, 0x7c, 0x00, 
118          0x7e, 0x00, 0x7f, 0x00, 0x6f, 0x80, 0x47, 0xc0, 
119          0x03, 0xe0, 0x01, 0xf0, 0x00, 0xe0, 0x00, 0x40, 
120          0x00, 0x00, 0x01, 0xb6, 0x01, 0xb6, 0x00, 0x00, }
121 };
122
123 void showpage1(Page *);
124 void showpage(Page *);
125 void drawpage(Page *);
126 Point pagesize(Page *);
127
128 Page*
129 addpage(Page *up, char *label, int (*popen)(Page *), void *pdata, int fd)
130 {
131         Page *p;
132
133         p = mallocz(sizeof(*p), 1);
134         p->label = strdup(label);
135         p->image = nil;
136         p->data = pdata;
137         p->open = popen;
138         p->fd = fd;
139
140         p->down = nil;
141         p->tail = nil;
142         p->next = nil;
143
144         qlock(&pagelock);
145         if(p->up = up){
146                 if(up->tail == nil)
147                         up->down = up->tail = p;
148                 else {
149                         up->tail->next = p;
150                         up->tail = p;
151                 }
152         }
153         qunlock(&pagelock);
154
155         if(up && current == up)
156                 showpage1(p);
157         return p;
158 }
159
160 void
161 resizewin(Point size)
162 {
163         int wctl;
164
165         if((wctl = open("/dev/wctl", OWRITE)) < 0)
166                 return;
167         /* add rio border */
168         size = addpt(size, Pt(Borderwidth*2, Borderwidth*2));
169         if(display->image){
170                 Point dsize = subpt(display->image->r.max, display->image->r.min);
171                 if(size.x > dsize.x)
172                         size.x = dsize.x;
173                 if(size.y > dsize.y)
174                         size.y = dsize.y;
175                 /* can't just conver whole display */
176                 if(eqpt(size, dsize))
177                         size.y--;
178         }
179         fprint(wctl, "resize -dx %d -dy %d\n", size.x, size.y);
180         close(wctl);
181 }
182
183 int
184 createtmp(char *pfx)
185 {
186         static ulong id = 1;
187         char nam[64];
188         snprint(nam, sizeof nam, "%s%s%.12d%.8lux", pagespool, pfx, getpid(), id++);
189         return create(nam, OEXCL|ORCLOSE|ORDWR, 0600);
190 }
191
192 int
193 catchnote(void *, char *msg)
194 {
195         if(strstr(msg, "sys: write on closed pipe"))
196                 return 1;
197         if(strstr(msg, "hangup"))
198                 return 1;
199         if(strstr(msg, "alarm"))
200                 return 1;
201         if(strstr(msg, "interrupt"))
202                 return 1;
203         if(strstr(msg, "kill"))
204                 exits("killed");
205         return 0;
206 }
207
208 void
209 dupfds(int fd, ...)
210 {
211         int mfd, n, i;
212         va_list arg;
213         Dir *dir;
214
215         va_start(arg, fd);
216         for(mfd = 0; fd >= 0; fd = va_arg(arg, int), mfd++)
217                 if(fd != mfd)
218                         if(dup(fd, mfd) < 0)
219                                 sysfatal("dup: %r");
220         va_end(arg);
221         if((fd = open("/fd", OREAD)) < 0)
222                 sysfatal("open: %r");
223         n = dirreadall(fd, &dir);
224         for(i=0; i<n; i++){
225                 if(strstr(dir[i].name, "ctl"))
226                         continue;
227                 fd = atoi(dir[i].name);
228                 if(fd >= mfd)
229                         close(fd);
230         }
231         free(dir);
232 }
233
234 void
235 pipeline(int fd, char *fmt, ...)
236 {
237         char buf[NPATH], *argv[4];
238         va_list arg;
239         int pfd[2];
240
241         if(pipe(pfd) < 0){
242         Err:
243                 dup(nullfd, fd);
244                 return;
245         }
246         va_start(arg, fmt);
247         vsnprint(buf, sizeof buf, fmt, arg);
248         va_end(arg);
249         switch(rfork(RFPROC|RFMEM|RFFDG|RFREND|RFNOWAIT)){
250         case -1:
251                 close(pfd[0]);
252                 close(pfd[1]);
253                 goto Err;
254         case 0:
255                 dupfds(fd, pfd[1], 2, -1);
256                 argv[0] = "rc";
257                 argv[1] = "-c";
258                 argv[2] = buf;
259                 argv[3] = nil;
260                 exec("/bin/rc", argv);
261                 sysfatal("exec: %r");
262         }
263         close(pfd[1]);
264         dup(pfd[0], fd);
265         close(pfd[0]);
266 }
267
268 char*
269 shortname(char *s)
270 {
271         char *x;
272
273         while(strlen(s) > 20){
274                 if((x = strchr(s, '/')) == nil)
275                         break;
276                 if(x[1] == 0)
277                         break;
278                 s = x+1;
279         }
280         return s;
281 }
282
283 int
284 popenfile(Page*);
285
286 int
287 popenimg(Page *p)
288 {
289         char nam[NPATH];
290         int fd;
291
292         if((fd = dup(p->fd, -1)) < 0){
293                 close(p->fd);
294                 p->fd = -1;
295                 return -1;
296         }
297
298         seek(fd, 0, 0);
299         if(p->data){
300                 p->ext = p->data;
301                 if(strcmp(p->ext, "ico") == 0)
302                         snprint(nam, sizeof(nam), "%s -c", p->ext);
303                 else
304                         snprint(nam, sizeof(nam), "%s -t9", p->ext);
305                 pipeline(fd, "%s", nam);
306         }
307
308         /*
309          * dont keep the file descriptor arround if it can simply
310          * be reopened.
311          */
312         fd2path(p->fd, nam, sizeof(nam));
313         if(strncmp(nam, pagespool, strlen(pagespool))){
314                 close(p->fd);
315                 p->fd = -1;
316                 p->data = strdup(nam);
317                 p->open = popenfile;
318         }
319
320         return fd;
321 }
322
323 int
324 popenfilter(Page *p)
325 {
326         seek(p->fd, 0, 0);
327         if(p->data){
328                 pipeline(p->fd, "%s", (char*)p->data);
329                 p->data = nil;
330         }
331         p->open = popenfile;
332         return p->open(p);
333 }
334
335 int
336 popentape(Page *p)
337 {
338         char mnt[32], cmd[64], *argv[4];
339
340         seek(p->fd, 0, 0);
341         snprint(mnt, sizeof(mnt), "/n/tapefs.%.12d%.8lux", getpid(), (ulong)p);
342         snprint(cmd, sizeof(cmd), "%s -m %s /fd/0", (char*)p->data, mnt);
343         switch(rfork(RFPROC|RFMEM|RFFDG|RFREND)){
344         case -1:
345                 close(p->fd);
346                 p->fd = -1;
347                 return -1;
348         case 0:
349                 dupfds(p->fd, 1, 2, -1);
350                 argv[0] = "rc";
351                 argv[1] = "-c";
352                 argv[2] = cmd;
353                 argv[3] = nil;
354                 exec("/bin/rc", argv);
355                 sysfatal("exec: %r");
356         }
357         close(p->fd);
358         waitpid();
359         p->fd = -1;
360         p->data = strdup(mnt);
361         p->open = popenfile;
362         return p->open(p);
363 }
364
365 int
366 popenepub(Page *p)
367 {
368         char buf[NPATH], *s, *e;
369         int n, fd;
370
371         fd = p->fd;
372         p->fd = -1;
373         s = buf;
374         e = buf+sizeof(buf)-1;
375         s += snprint(s, e-s, "%s/", (char*)p->data);
376         free(p->data);
377         p->data = nil;
378         pipeline(fd, "awk '/\\<rootfile/{"
379                 "if(match($0, /full\\-path\\=\\\"([^\\\"]+)\\\"/)){"
380                 "print substr($0, RSTART+11,RLENGTH-12);exit}}'");
381         n = read(fd, s, e - s);
382         close(fd);
383         if(n <= 0)
384                 return -1;
385         while(n > 0 && s[n-1] == '\n')
386                 n--;
387         s += n;
388         *s = 0;
389         if((fd = open(buf, OREAD)) < 0)
390                 return -1;
391         pipeline(fd, "awk '/\\<item/{"
392                 "if(match($0, /id\\=\\\"([^\\\"]+)\\\"/)){"
393                 "id=substr($0, RSTART+4, RLENGTH-5);"
394                 "if(match($0, /href\\=\\\"([^\\\"]+)\\\"/)){"
395                 "item[id]=substr($0, RSTART+6, RLENGTH-7)}}};"
396                 "/\\<itemref/{"
397                 "if(match($0, /idref\\=\\\"([^\\\"]+)\\\"/)){"
398                 "ref=substr($0, RSTART+7, RLENGTH-8);"
399                 "print item[ref]; fflush}}'");
400         s = strrchr(buf, '/')+1;
401         while((n = read(fd, s, e-s)) > 0){
402                 while(n > 0 && s[n-1] == '\n')
403                         n--;
404                 s[n] = 0;
405                 addpage(p, shortname(buf), popenfile, strdup(buf), -1);
406         }
407         close(fd);
408         return -1;
409 }
410
411 typedef struct Ghost Ghost;
412 struct Ghost
413 {
414         QLock;
415
416         int     pin;
417         int     pout;
418         int     pdat;
419 };
420
421 int
422 popenpdf(Page *p)
423 {
424         char buf[NBUF];
425         int n, pfd[2];
426         Ghost *gs;
427
428         if(pipe(pfd) < 0)
429                 return -1;
430         switch(rfork(RFPROC|RFMEM|RFFDG|RFNOWAIT)){
431         case -1:
432                 close(pfd[0]);
433                 close(pfd[1]);
434                 return -1;
435         case 0:
436                 gs = p->data;
437                 qlock(gs);
438                 dupfds(gs->pdat, gs->pin, pfd[1], -1);
439                 fprint(1, "%s DoPDFPage\n"
440                         "(/fd/3) (w) file "
441                         "dup flushfile "
442                         "dup (THIS IS NOT AN INFERNO BITMAP\\n) writestring "
443                         "flushfile\n", p->label);
444                 while((n = read(0, buf, sizeof buf)) > 0){
445                         if(memcmp(buf, "THIS IS NOT AN INFERNO BITMAP\n", 30) == 0)
446                                 break;
447                         write(2, buf, n);
448                 }
449                 qunlock(gs);
450                 exits(nil);
451         }
452         close(pfd[1]);
453         return pfd[0];
454 }
455
456 int
457 infernobithdr(char *buf, int n)
458 {
459         if(n >= 11){
460                 if(memcmp(buf, "compressed\n", 11) == 0)
461                         return 1;
462                 if(strtochan((char*)buf))
463                         return 1;
464                 if(memcmp(buf, "          ", 10) == 0 && 
465                         '0' <= buf[10] && buf[10] <= '9' &&
466                         buf[11] == ' ')
467                         return 1;
468         }
469         return 0;
470 }
471
472 int
473 popengs(Page *p)
474 {
475         int n, i, pdf, ifd, ofd, pin[2], pout[2], pdat[2];
476         char buf[NBUF], nam[32], *argv[16];
477
478         pdf = 0;
479         ifd = p->fd;
480         p->fd = -1;
481         seek(ifd, 0, 0);
482         if(read(ifd, buf, 5) != 5)
483                 goto Err0;
484         seek(ifd, 0, 0);
485         if(memcmp(buf, "%PDF-", 5) == 0)
486                 pdf = 1;
487         if(pipe(pin) < 0){
488         Err0:
489                 close(ifd);
490                 return -1;
491         }
492         if(pipe(pout) < 0){
493         Err1:
494                 close(pin[0]);
495                 close(pin[1]);
496                 goto Err0;
497         }
498         if(pipe(pdat) < 0){
499         Err2:
500                 close(pdat[0]);
501                 close(pdat[1]);
502                 goto Err1;
503         }
504
505         argv[0] = (char*)p->data;
506         switch(rfork(RFPROC|RFMEM|RFFDG|RFREND|RFNOWAIT)){
507         case -1:
508                 goto Err2;
509         case 0:
510                 if(pdf)
511                         dupfds(pin[1], pout[1], 2, pdat[1], ifd, -1);
512                 else
513                         dupfds(nullfd, nullfd, 2, pdat[1], ifd, -1);
514                 if(argv[0])
515                         pipeline(4, "%s", argv[0]);
516                 argv[0] = "gs";
517                 argv[1] = "-q";
518                 argv[2] = "-sDEVICE=plan9";
519                 argv[3] = "-sOutputFile=/fd/3";
520                 argv[4] = "-dBATCH";
521                 argv[5] = pdf ? "-dDELAYSAFER" : "-dSAFER";
522                 argv[6] = "-dQUIET";
523                 argv[7] = "-dTextAlphaBits=4";
524                 argv[8] = "-dGraphicsAlphaBits=4";
525                 snprint(buf, sizeof buf, "-r%d", ppi);
526                 argv[9] = buf;
527                 argv[10] = "-dDOINTERPOLATE";
528                 argv[11] = pdf ? "-" : "/fd/4";
529                 argv[12] = nil;
530                 exec("/bin/gs", argv);
531                 sysfatal("exec: %r");
532         }
533
534         close(pin[1]);
535         close(pout[1]);
536         close(pdat[1]);
537         close(ifd);
538
539         if(pdf){
540                 Ghost *gs;
541                 char *prolog =
542                         "/PAGEOUT (/fd/1) (w) file def\n"
543                         "/PAGE== { PAGEOUT exch write==only PAGEOUT (\\n) writestring PAGEOUT flushfile } def\n"
544                         "\n"
545                         "/Page null def\n"
546                         "/Page# 0 def\n"
547                         "/PDFSave null def\n"
548                         "/DSCPageCount 0 def\n"
549                         "/DoPDFPage {dup /Page# exch store pdfgetpage pdfshowpage } def\n"
550                         "\n"
551                         "GS_PDF_ProcSet begin\n"
552                         "pdfdict begin\n"
553                         "(/fd/4) (r) file { DELAYSAFER { .setsafe } if } stopped pop pdfopen begin\n"
554                         "\n"
555                         "pdfpagecount PAGE==\n";
556
557                 n = strlen(prolog);
558                 if(write(pin[0], prolog, n) != n)
559                         goto Out;
560                 if((n = read(pout[0], buf, sizeof(buf)-1)) < 0)
561                         goto Out;
562                 buf[n] = 0;
563                 n = atoi(buf);
564                 if(n <= 0){
565                         werrstr("no pages");
566                         goto Out;
567                 }
568                 gs = mallocz(sizeof(*gs), 1);
569                 gs->pin = pin[0];
570                 gs->pout = pout[0];
571                 gs->pdat = pdat[0];
572                 for(i=1; i<=n; i++){
573                         snprint(nam, sizeof nam, "%d", i);
574                         addpage(p, nam, popenpdf, gs, -1);
575                 }
576
577                 /* keep ghostscript arround */
578                 return -1;
579         } else {
580                 i = 0;
581                 ofd = -1;
582                 while((n = read(pdat[0], buf, sizeof(buf))) >= 0){
583                         if(ofd >= 0 && (n <= 0 || infernobithdr(buf, n))){
584                                 snprint(nam, sizeof nam, "%d", i);
585                                 addpage(p, nam, popenimg, nil, ofd);
586                                 ofd = -1;
587                         }
588                         if(n <= 0)
589                                 break;
590                         if(ofd < 0){
591                                 snprint(nam, sizeof nam, "%.4d", ++i);
592                                 if((ofd = createtmp(nam)) < 0)
593                                         ofd = dup(nullfd, -1);
594                         }
595                         if(write(ofd, buf, n) != n)
596                                 break;
597                 }
598                 if(ofd >= 0)
599                         close(ofd);
600         }
601 Out:
602         close(pin[0]);
603         close(pout[0]);
604         close(pdat[0]);
605         return -1;
606 }
607
608 int
609 filetype(char *buf, int nbuf, char *typ, int ntyp)
610 {
611         int n, ifd[2], ofd[2];
612         char *argv[3];
613
614         if(infernobithdr(buf, nbuf)){
615                 strncpy(typ, "image/p9bit", ntyp);
616                 return 0;
617         }
618
619         typ[0] = 0;
620         if(pipe(ifd) < 0)
621                 return -1;
622         if(pipe(ofd) < 0){
623                 close(ifd[0]);
624                 close(ifd[1]);
625                 return -1;
626         }
627         if(rfork(RFPROC|RFMEM|RFFDG|RFREND|RFNOWAIT) == 0){
628                 dupfds(ifd[1], ofd[1], 2, -1);
629                 argv[0] = "file";
630                 argv[1] = "-m";
631                 argv[2] = 0;
632                 exec("/bin/file", argv);
633         }
634         close(ifd[1]);
635         close(ofd[1]);
636         if(rfork(RFPROC|RFMEM|RFFDG|RFNOWAIT) == 0){
637                 dupfds(ifd[0], -1);
638                 write(0, buf, nbuf);
639                 exits(nil);
640         }
641         close(ifd[0]);
642         if((n = readn(ofd[0], typ, ntyp-1)) < 0)
643                 n = 0;
644         close(ofd[0]);
645         while(n > 0 && typ[n-1] == '\n')
646                 n--;
647         typ[n] = 0;
648         return 0;
649 }
650
651 int
652 dircmp(void *p1, void *p2)
653 {
654         Dir *d1, *d2;
655
656         d1 = p1;
657         d2 = p2;
658
659         return strcmp(d1->name, d2->name);
660 }
661
662 int
663 popenfile(Page *p)
664 {
665         static struct {
666                 char    *typ;
667                 void    *open;
668                 void    *data;
669         } tab[] = {
670         "application/pdf",              popengs,        nil,
671         "application/postscript",       popengs,        nil,
672         "application/troff",            popengs,        "lp -dstdout",
673         "text/plain",                   popengs,        "lp -dstdout",
674         "text/html",                    popengs,        "uhtml | html2ms | tbl | troff -ms | lp -dstdout",
675         "application/dvi",              popengs,        "dvips -Pps -r0 -q1 -f1",
676         "application/doc",              popengs,        "doc2ps",
677         "application/zip",              popentape,      "fs/zipfs",
678         "application/x-tar",            popentape,      "fs/tarfs",
679         "application/x-ustar",          popentape,      "fs/tarfs",
680         "application/x-compress",       popenfilter,    "uncompress",
681         "application/x-gzip",           popenfilter,    "gunzip",
682         "application/x-bzip2",          popenfilter,    "bunzip2",
683         "image/gif",                    popenimg,       "gif",
684         "image/jpeg",                   popenimg,       "jpg",
685         "image/png",                    popenimg,       "png",
686         "image/tiff",                   popenimg,       "tif",
687         "image/ppm",                    popenimg,       "ppm",
688         "image/bmp",                    popenimg,       "bmp",
689         "image/tga",                    popenimg,       "tga",
690         "image/x-icon",                 popenimg,       "ico",
691         "image/p9bit",                  popenimg,       nil,
692         };
693
694         char buf[NBUF], typ[128], *file;
695         int i, n, fd, tfd;
696         Dir *d;
697
698         fd = p->fd;
699         p->fd = -1;
700         p->ext = nil;
701         file = p->data;
702         p->data = nil;
703         if(fd < 0){
704                 if((fd = open(file, OREAD)) < 0){
705                 Err0:
706                         free(file);
707                         return -1;
708                 }
709         }
710         seek(fd, 0, 0);
711         if((d = dirfstat(fd)) == nil){
712         Err1:
713                 close(fd);
714                 goto Err0;
715         }
716         if(d->mode & DMDIR){
717                 free(d);
718                 d = nil;
719
720                 snprint(buf, sizeof(buf), "%s/META-INF/container.xml", file);
721                 if((tfd = open(buf, OREAD)) >= 0){
722                         close(fd);
723                         p->fd = tfd;
724                         p->data = file;
725                         p->open = popenepub;
726                         return p->open(p);
727                 }
728
729                 if((n = dirreadall(fd, &d)) < 0)
730                         goto Err1;
731                 qsort(d, n, sizeof d[0], dircmp);
732                 for(i = 0; i<n; i++)
733                         addpage(p, d[i].name, popenfile, smprint("%s/%s", file, d[i].name), -1);
734                 free(d);
735                 goto Err1;
736         }
737         free(d);
738
739         memset(buf, 0, NBUF/2);
740         if((n = readn(fd, buf, NBUF/2)) <= 0)
741                 goto Err1;
742         filetype(buf, n, typ, sizeof(typ));
743         for(i=0; i<nelem(tab); i++)
744                 if(strncmp(typ, tab[i].typ, strlen(tab[i].typ)) == 0)
745                         break;
746         if(i == nelem(tab)){
747                 werrstr("unknown image format: %s", typ);
748                 goto Err1;
749         }
750         p->fd = fd;
751         p->data = tab[i].data;
752         p->open = tab[i].open;
753         if(seek(fd, 0, 0) < 0)
754                 goto Noseek;
755         if((i = readn(fd, buf+n, n)) < 0)
756                 goto Err1;
757         if(i != n || memcmp(buf, buf+n, i)){
758                 n += i;
759         Noseek:
760                 if((tfd = createtmp("file")) < 0)
761                         goto Err1;
762                 while(n > 0){
763                         if(write(tfd, buf, n) != n)
764                                 goto Err2;
765                         if((n = read(fd, buf, sizeof(buf))) < 0)
766                                 goto Err2;
767                 }
768                 if(dup(tfd, fd) < 0){
769                 Err2:
770                         close(tfd);
771                         goto Err1;
772                 }
773                 close(tfd);
774         }
775         free(file);
776         return p->open(p);
777 }
778
779 Page*
780 nextpage(Page *p)
781 {
782         if(p && p->down)
783                 return p->down;
784         while(p){
785                 if(p->next)
786                         return p->next;
787                 p = p->up;
788         }
789         return nil;
790 }
791
792 Page*
793 prevpage(Page *x)
794 {
795         Page *p, *t;
796
797         if(x){
798                 for(p = root->down; p; p = t)
799                         if((t = nextpage(p)) == x)
800                                 return p;
801         }
802         return nil;
803 }
804
805 int
806 openpage(Page *p)
807 {
808         int fd;
809
810         fd = -1;
811         if(p->open == nil || (fd = p->open(p)) < 0)
812                 p->open = nil;
813         else {
814                 if(rotate)
815                         pipeline(fd, "rotate -r %d", rotate);
816                 if(resize.x)
817                         pipeline(fd, "resize -x %d", resize.x);
818                 else if(resize.y)
819                         pipeline(fd, "resize -y %d", resize.y);
820         }
821         return fd;
822 }
823
824 static ulong
825 imagesize(Image *i)
826 {
827         if(i == nil)
828                 return 0;
829         return Dy(i->r)*bytesperline(i->r, i->depth);
830 }
831
832 void
833 loadpage(Page *p)
834 {
835         int fd;
836
837         if(p->open && p->image == nil){
838                 fd = openpage(p);
839                 if(fd >= 0){
840                         if((p->image = readimage(display, fd, 1)) == nil)
841                                 fprint(2, "readimage: %r\n");
842                         close(fd);
843                 }
844                 if(p->image == nil)
845                         p->open = nil;
846                 else {
847                         lockdisplay(display);
848                         imemsize += imagesize(p->image);
849                         unlockdisplay(display);
850                 }
851         }
852 }
853
854 void
855 unloadpage(Page *p)
856 {
857         if(p->open == nil || p->image == nil)
858                 return;
859         lockdisplay(display);
860         imemsize -= imagesize(p->image);
861         freeimage(p->image);
862         unlockdisplay(display);
863         p->image = nil;
864 }
865
866 void
867 unloadpages(ulong limit)
868 {
869         Page *p;
870
871         for(p = root->down; p && imemsize >= limit; p = nextpage(p)){
872                 qlock(p);
873                 unloadpage(p);
874                 qunlock(p);
875         }
876 }
877
878 void
879 loadpages(Page *p, int oviewgen)
880 {
881         while(p && viewgen == oviewgen){
882                 qlock(p);
883                 loadpage(p);
884                 if(viewgen != oviewgen){
885                         unloadpage(p);
886                         qunlock(p);
887                         break;
888                 }
889                 if(p == current){
890                         Point size;
891
892                         esetcursor(nil);
893                         size = pagesize(p);
894                         if(size.x && size.y && newwin){
895                                 newwin = 0;
896                                 resizewin(size);
897                         }
898                         lockdisplay(display);
899                         drawpage(p);
900                         unlockdisplay(display);
901                 }
902                 qunlock(p);
903                 if(p != current || imemsize >= imemlimit)
904                         break;
905                 p = nextpage(p);
906         }
907 }
908
909 /*
910  * A draw operation that touches only the area contained in bot but not in top.
911  * mp and sp get aligned with bot.min.
912  */
913 static void
914 gendrawdiff(Image *dst, Rectangle bot, Rectangle top, 
915         Image *src, Point sp, Image *mask, Point mp, int op)
916 {
917         Rectangle r;
918         Point origin;
919         Point delta;
920
921         if(Dx(bot)*Dy(bot) == 0)
922                 return;
923
924         /* no points in bot - top */
925         if(rectinrect(bot, top))
926                 return;
927
928         /* bot - top â‰¡ bot */
929         if(Dx(top)*Dy(top)==0 || rectXrect(bot, top)==0){
930                 gendrawop(dst, bot, src, sp, mask, mp, op);
931                 return;
932         }
933
934         origin = bot.min;
935         /* split bot into rectangles that don't intersect top */
936         /* left side */
937         if(bot.min.x < top.min.x){
938                 r = Rect(bot.min.x, bot.min.y, top.min.x, bot.max.y);
939                 delta = subpt(r.min, origin);
940                 gendrawop(dst, r, src, addpt(sp, delta), mask, addpt(mp, delta), op);
941                 bot.min.x = top.min.x;
942         }
943
944         /* right side */
945         if(bot.max.x > top.max.x){
946                 r = Rect(top.max.x, bot.min.y, bot.max.x, bot.max.y);
947                 delta = subpt(r.min, origin);
948                 gendrawop(dst, r, src, addpt(sp, delta), mask, addpt(mp, delta), op);
949                 bot.max.x = top.max.x;
950         }
951
952         /* top */
953         if(bot.min.y < top.min.y){
954                 r = Rect(bot.min.x, bot.min.y, bot.max.x, top.min.y);
955                 delta = subpt(r.min, origin);
956                 gendrawop(dst, r, src, addpt(sp, delta), mask, addpt(mp, delta), op);
957                 bot.min.y = top.min.y;
958         }
959
960         /* bottom */
961         if(bot.max.y > top.max.y){
962                 r = Rect(bot.min.x, top.max.y, bot.max.x, bot.max.y);
963                 delta = subpt(r.min, origin);
964                 gendrawop(dst, r, src, addpt(sp, delta), mask, addpt(mp, delta), op);
965                 bot.max.y = top.max.y;
966         }
967 }
968
969 int
970 alphachan(ulong chan)
971 {
972         for(; chan; chan >>= 8)
973                 if(TYPE(chan) == CAlpha)
974                         return 1;
975         return 0;
976 }
977
978 void
979 zoomdraw(Image *d, Rectangle r, Rectangle top, Image *b, Image *s, Point sp, int f)
980 {
981         Rectangle dr;
982         Image *t;
983         Point a;
984         int w;
985
986         a = ZP;
987         if(r.min.x < d->r.min.x){
988                 sp.x += (d->r.min.x - r.min.x)/f;
989                 a.x = (d->r.min.x - r.min.x)%f;
990                 r.min.x = d->r.min.x;
991         }
992         if(r.min.y < d->r.min.y){
993                 sp.y += (d->r.min.y - r.min.y)/f;
994                 a.y = (d->r.min.y - r.min.y)%f;
995                 r.min.y = d->r.min.y;
996         }
997         rectclip(&r, d->r);
998         w = s->r.max.x - sp.x;
999         if(w > Dx(r))
1000                 w = Dx(r);
1001         dr = r;
1002         dr.max.x = dr.min.x+w;
1003         if(!alphachan(s->chan))
1004                 b = nil;
1005         if(f <= 1){
1006                 if(b) gendrawdiff(d, dr, top, b, sp, nil, ZP, SoverD);
1007                 gendrawdiff(d, dr, top, s, sp, nil, ZP, SoverD);
1008                 return;
1009         }
1010         if((t = allocimage(display, dr, s->chan, 0, 0)) == nil)
1011                 return;
1012         for(; dr.min.y < r.max.y; dr.min.y++){
1013                 dr.max.y = dr.min.y+1;
1014                 draw(t, dr, s, nil, sp);
1015                 if(++a.y == f){
1016                         a.y = 0;
1017                         sp.y++;
1018                 }
1019         }
1020         dr = r;
1021         for(sp=dr.min; dr.min.x < r.max.x; sp.x++){
1022                 dr.max.x = dr.min.x+1;
1023                 if(b) gendrawdiff(d, dr, top, b, sp, nil, ZP, SoverD);
1024                 gendrawdiff(d, dr, top, t, sp, nil, ZP, SoverD);
1025                 for(dr.min.x++; ++a.x < f && dr.min.x < r.max.x; dr.min.x++){
1026                         dr.max.x = dr.min.x+1;
1027                         gendrawdiff(d, dr, top, d, Pt(dr.min.x-1, dr.min.y), nil, ZP, SoverD);
1028                 }
1029                 a.x = 0;
1030         }
1031         freeimage(t);
1032 }
1033
1034 Point
1035 pagesize(Page *p)
1036 {
1037         return p->image ? mulpt(subpt(p->image->r.max, p->image->r.min), zoom) : ZP;
1038 }
1039
1040 void
1041 drawframe(Rectangle r)
1042 {
1043         border(screen, r, -Borderwidth, frame, ZP);
1044         gendrawdiff(screen, screen->r, insetrect(r, -Borderwidth), ground, ZP, nil, ZP, SoverD);
1045         flushimage(display, 1);
1046 }
1047
1048 void
1049 drawpage(Page *p)
1050 {
1051         Rectangle r;
1052         Image *i;
1053
1054         if(i = p->image){
1055                 r = rectaddpt(Rpt(ZP, pagesize(p)), addpt(pos, screen->r.min));
1056                 zoomdraw(screen, r, ZR, paper, i, i->r.min, zoom);
1057         } else {
1058                 r = Rpt(ZP, stringsize(font, p->label));
1059                 r = rectaddpt(r, addpt(subpt(divpt(subpt(screen->r.max, screen->r.min), 2),
1060                         divpt(r.max, 2)), screen->r.min));
1061                 draw(screen, r, paper, nil, ZP);
1062                 string(screen, r.min, display->black, ZP, font, p->label);
1063         }
1064         drawframe(r);
1065 }
1066
1067 void
1068 translate(Page *p, Point d)
1069 {
1070         Rectangle r, nr;
1071         Image *i;
1072
1073         i = p->image;
1074         if(i==0 || d.x==0 && d.y==0)
1075                 return;
1076         r = rectaddpt(Rpt(ZP, pagesize(p)), addpt(pos, screen->r.min));
1077         pos = addpt(pos, d);
1078         nr = rectaddpt(r, d);
1079         rectclip(&r, screen->r);
1080         draw(screen, rectaddpt(r, d), screen, nil, r.min);
1081         zoomdraw(screen, nr, rectaddpt(r, d), paper, i, i->r.min, zoom);
1082         drawframe(nr);
1083 }
1084
1085 Page*
1086 findpage(char *name)
1087 {
1088         Page *p;
1089         int n;
1090
1091         n = strlen(name);
1092         /* look in current document first */
1093         if(current && current->up){
1094                 for(p = current->up->down; p; p = p->next)
1095                         if(cistrncmp(p->label, name, n) == 0)
1096                                 return p;
1097         }
1098         /* look everywhere */
1099         for(p = root->down; p; p = nextpage(p))
1100                 if(cistrncmp(p->label, name, n) == 0)
1101                         return p;
1102         return nil;
1103 }
1104
1105 Page*
1106 pageat(int i)
1107 {
1108         Page *p;
1109
1110         for(p = root->down; i > 0 && p; p = nextpage(p))
1111                 i--;
1112         return i ? nil : p;
1113 }
1114
1115 int
1116 pageindex(Page *x)
1117 {
1118         Page *p;
1119         int i;
1120
1121         for(i = 0, p = root->down; p && p != x; p = nextpage(p))
1122                 i++;
1123         return (p == x) ? i : -1;
1124 }
1125
1126 char*
1127 pagemenugen(int i)
1128 {
1129         Page *p;
1130         if(p = pageat(i))
1131                 return p->label;
1132         return nil;
1133 }
1134
1135 char*
1136 cmdmenugen(int i)
1137 {
1138         if(i < 0 || i >= nelem(cmds))
1139                 return nil;
1140         return cmds[i].m;
1141 }
1142
1143 /*
1144  * spawn new proc to load a run of pages starting with p
1145  * the display should *not* be locked as it gets called
1146  * from recursive page load.
1147  */
1148 void
1149 showpage1(Page *p)
1150 {
1151         static int nproc;
1152         int oviewgen;
1153
1154         if(p == nil)
1155                 return;
1156         esetcursor(&reading);
1157         current = p;
1158         oviewgen = viewgen;
1159         switch(rfork(RFPROC|RFMEM)){
1160         case -1:
1161                 sysfatal("rfork: %r");
1162         case 0:
1163                 loadpages(p, oviewgen);
1164                 exits(nil);
1165         }
1166         if(++nproc >= NPROC)
1167                 if(waitpid() > 0)
1168                         nproc--;
1169 }
1170
1171 /* recursive display lock, called from main proc only */
1172 void
1173 drawlock(int dolock){
1174         static int ref = 0;
1175         if(dolock){
1176                 if(ref++ == 0)
1177                         lockdisplay(display);
1178         } else {
1179                 if(--ref == 0)
1180                         unlockdisplay(display);
1181         }
1182 }
1183
1184
1185 void
1186 showpage(Page *p)
1187 {
1188         if(p == nil)
1189                 return;
1190         drawlock(0);
1191         if(p->image == nil)
1192                 unloadpages(imemlimit/2);
1193         showpage1(p);
1194         drawlock(1);
1195 }
1196
1197 void
1198 shownext(void)
1199 {
1200         Page *p;
1201
1202         for(p = nextpage(current); p; p = nextpage(p))
1203                 if(p->image || p->open)
1204                         break;
1205         showpage(p);
1206 }
1207
1208 void
1209 showprev(void)
1210 {
1211         Page *p;
1212
1213         for(p = prevpage(current); p; p = prevpage(p))
1214                 if(p->image || p->open)
1215                         break;
1216         showpage(p);
1217 }
1218
1219 void
1220 zerox(Page *p)
1221 {
1222         char nam[64], *argv[4];
1223         int fd;
1224
1225         if(p == nil)
1226                 return;
1227         drawlock(0);
1228         qlock(p);
1229         if((fd = openpage(p)) < 0)
1230                 goto Out;
1231         if(rfork(RFPROC|RFMEM|RFFDG|RFENVG|RFNOTEG|RFNOWAIT) == 0){
1232                 dupfds(fd, 1, 2, -1);
1233                 snprint(nam, sizeof nam, "/bin/%s", argv0);
1234                 argv[0] = argv0;
1235                 argv[1] = "-w";
1236                 argv[2] = nil;
1237                 exec(nam, argv);
1238                 sysfatal("exec: %r");
1239         }
1240         close(fd);
1241 Out:
1242         qunlock(p);
1243         drawlock(1);
1244 }
1245
1246 void
1247 showext(Page *p)
1248 {
1249         char buf[128], label[64], *argv[4];
1250         Point ps;
1251         int fd;
1252
1253         if(p->ext == nil)
1254                 return;
1255         snprint(label, sizeof(label), "%s %s", p->ext, p->label);
1256         ps = Pt(0, 0);
1257         if(p->image)
1258                 ps = addpt(subpt(p->image->r.max, p->image->r.min), Pt(24, 24));
1259         drawlock(0);
1260         if((fd = p->fd) < 0){
1261                 if(p->open != popenfile)
1262                         return;
1263                 fd = open((char*)p->data, OREAD);
1264         } else {
1265                 fd = dup(fd, -1);
1266                 seek(fd, 0, 0);
1267         }
1268         if(rfork(RFPROC|RFMEM|RFFDG|RFNOTEG|RFNAMEG|RFNOWAIT) == 0){
1269                 snprint(buf, sizeof(buf), "-pid %d", getpid());
1270                 if(newwindow(buf) != -1){
1271                         dupfds(fd, 1, 2, -1);
1272                         if((fd = open("/dev/label", OWRITE)) >= 0){
1273                                 write(fd, label, strlen(label));
1274                                 close(fd);
1275                         }
1276                         if(ps.x && ps.y)
1277                                 resizewin(ps);
1278                         argv[0] = "rc";
1279                         argv[1] = "-c";
1280                         argv[2] = p->ext;
1281                         argv[3] = nil;
1282                         exec("/bin/rc", argv);
1283                 }
1284                 exits(0);
1285         }
1286         close(fd);
1287         drawlock(1);
1288 }
1289
1290
1291 void
1292 eresized(int new)
1293 {
1294         Page *p;
1295
1296         drawlock(1);
1297         if(new && getwindow(display, Refnone) == -1)
1298                 sysfatal("getwindow: %r");
1299         if(p = current){
1300                 if(canqlock(p)){
1301                         drawpage(p);
1302                         qunlock(p);
1303                 }
1304         }
1305         drawlock(0);
1306 }
1307
1308 int cohort = -1;
1309 void killcohort(void)
1310 {
1311         int i;
1312         for(i=0;i!=3;i++){      /* It's a long way to the kitchen */
1313                 postnote(PNGROUP, cohort, "kill");
1314                 sleep(1);
1315         }
1316 }
1317
1318 void drawerr(Display *, char *msg)
1319 {
1320         sysfatal("draw: %s", msg);
1321 }
1322
1323 void
1324 usage(void)
1325 {
1326         fprint(2, "usage: %s [ -iRw ] [ -m mb ] [ -p ppi ] [ file ... ]\n", argv0);
1327         exits("usage");
1328 }
1329
1330 void
1331 docmd(int i, Mouse *m)
1332 {
1333         char buf[NPATH], *s;
1334         Point o;
1335         int fd;
1336
1337         switch(i){
1338         case Corigsize:
1339                 pos = ZP;
1340                 zoom = 1;
1341                 resize = ZP;
1342                 rotate = 0;
1343         Unload:
1344                 viewgen++;
1345                 drawlock(0);
1346                 unloadpages(0);
1347                 showpage1(current);
1348                 drawlock(1);
1349                 break;
1350         case Cupsidedown:
1351                 rotate += 90;
1352         case Crotate90:
1353                 rotate += 90;
1354                 rotate %= 360;
1355                 goto Unload;
1356         case Cfitwidth:
1357                 pos = ZP;
1358                 zoom = 1;
1359                 resize = subpt(screen->r.max, screen->r.min);
1360                 resize.y = 0;
1361                 goto Unload;
1362         case Cfitheight:
1363                 pos = ZP;
1364                 zoom = 1;
1365                 resize = subpt(screen->r.max, screen->r.min);
1366                 resize.x = 0;
1367                 goto Unload;
1368         case Czoomin:
1369         case Czoomout:
1370                 if(current == nil || !canqlock(current))
1371                         break;
1372                 o = subpt(m->xy, screen->r.min);
1373                 if(i == Czoomin){
1374                         if(zoom < 0x1000){
1375                                 zoom *= 2;
1376                                 pos =  addpt(mulpt(subpt(pos, o), 2), o);
1377                         }
1378                 }else{
1379                         if(zoom > 1){
1380                                 zoom /= 2;
1381                                 pos =  addpt(divpt(subpt(pos, o), 2), o);
1382                         }
1383                 }
1384                 drawpage(current);
1385                 qunlock(current);
1386                 break;
1387         case Cwrite:
1388                 if(current == nil || !canqlock(current))
1389                         break;
1390                 if(current->image){
1391                         s = nil;
1392                         if(current->up && current->up != root)
1393                                 s = current->up->label;
1394                         snprint(buf, sizeof(buf), "%s%s%s.bit",
1395                                 s ? s : "",
1396                                 s ? "." : "",
1397                                 current->label);
1398                         if(eenter("Write", buf, sizeof(buf), m) > 0){
1399                                 if((fd = create(buf, OWRITE, 0666)) < 0){
1400                                         errstr(buf, sizeof(buf));
1401                                         eenter(buf, 0, 0, m);
1402                                 } else {
1403                                         esetcursor(&reading);
1404                                         writeimage(fd, current->image, 0);
1405                                         close(fd);
1406                                         esetcursor(nil);
1407                                 }
1408                         }
1409                 }
1410                 qunlock(current);
1411                 break;
1412         case Cext:
1413                 if(current == nil || !canqlock(current))
1414                         break;
1415                 showext(current);
1416                 qunlock(current);
1417                 break;
1418         case Cnext:
1419                 shownext();
1420                 break;
1421         case Cprev:
1422                 showprev();
1423                 break;
1424         case Czerox:
1425                 zerox(current);
1426                 break;
1427         case Cquit:
1428                 exits(0);
1429         }
1430 }
1431
1432 void
1433 main(int argc, char *argv[])
1434 {
1435         enum { Eplumb = 4 };
1436         char buf[NPATH];
1437         Plumbmsg *pm;
1438         Point o;
1439         Mouse m;
1440         Event e;
1441         char *s;
1442         int i;
1443
1444         ARGBEGIN {
1445         case 'a':
1446         case 'v':
1447         case 'V':
1448         case 'P':
1449                 break;
1450         case 'R':
1451                 if(newwin == 0)
1452                         newwin = -1;
1453                 break;
1454         case 'w':
1455                 newwin = 1;
1456                 break;
1457         case 'i':
1458                 imode = 1;
1459                 break;
1460         case 'm':
1461                 imemlimit = atol(EARGF(usage()))*MiB;
1462                 break;
1463         case 'p':
1464                 ppi = atoi(EARGF(usage()));
1465                 break;
1466         default:
1467                 usage();
1468         } ARGEND;
1469
1470         /*
1471          * so that we can stop all subprocesses with a note,
1472          * and to isolate rendezvous from other processes
1473          */
1474         atnotify(catchnote, 1);
1475         if(cohort = rfork(RFPROC|RFNOTEG|RFNAMEG|RFREND)){
1476                 atexit(killcohort);
1477                 waitpid();
1478                 exits(0);
1479         }
1480         cohort = getpid();
1481         atexit(killcohort);
1482
1483         if(newwin > 0){
1484                 s = smprint("-pid %d", getpid());
1485                 if(newwindow(s) < 0)
1486                         sysfatal("newwindow: %r");
1487                 free(s);
1488         }
1489         if(initdraw(drawerr, nil, argv0) < 0)
1490                 sysfatal("initdraw: %r");
1491         paper = display->white;
1492         frame = display->black;
1493         ground = allocimage(display, Rect(0,0,1,1), screen->chan, 1, 0x777777FF);
1494         display->locking = 1;
1495         unlockdisplay(display);
1496         drawlock(1);
1497
1498         einit(Ekeyboard|Emouse);
1499         eplumb(Eplumb, "image");
1500         memset(&m, 0, sizeof(m));
1501         if((nullfd = open("/dev/null", ORDWR)) < 0)
1502                 sysfatal("open: %r");
1503         current = root = addpage(nil, "", nil, nil, -1);
1504         if(*argv == nil && !imode)
1505                 addpage(root, "stdin", popenfile, strdup("/fd/0"), -1);
1506         for(; *argv; argv++)
1507                 addpage(root, shortname(*argv), popenfile, strdup(*argv), -1);
1508
1509         for(;;){
1510                 drawlock(0);
1511                 i=event(&e);
1512                 drawlock(1);
1513
1514                 switch(i){
1515                 case Emouse:
1516                         m = e.mouse;
1517                         if(m.buttons & 1){
1518                                 if(current &&  canqlock(current)){
1519                                         for(;;) {
1520                                                 o = m.xy;
1521                                                 m = emouse();
1522                                                 if((m.buttons & 1) == 0)
1523                                                         break;
1524                                                 translate(current, subpt(m.xy, o));
1525                                         }
1526                                         qunlock(current);
1527                                 }
1528                         } else if(m.buttons & 2){
1529                                 o = m.xy;
1530                                 i = emenuhit(2, &m, &cmdmenu);
1531                                 m.xy = o;
1532                                 docmd(i, &m);
1533                         } else if(m.buttons & 4){
1534                                 if(root->down){
1535                                         Page *x;
1536
1537                                         qlock(&pagelock);
1538                                         pagemenu.lasthit = pageindex(current);
1539                                         x = pageat(emenuhit(3, &m, &pagemenu));
1540                                         qunlock(&pagelock);
1541                                         showpage(x);
1542                                 }
1543                         }
1544                         break;
1545                 case Ekeyboard:
1546                         switch(e.kbdc){
1547                         case Kup:
1548                                 if(current == nil || !canqlock(current))
1549                                         break;
1550                                 if(pos.y < 0){
1551                                         translate(current, Pt(0, Dy(screen->r)/2));
1552                                         qunlock(current);
1553                                         break;
1554                                 }
1555                                 if(prevpage(current))
1556                                         pos.y = 0;
1557                                 qunlock(current);
1558                                 docmd(Cprev, &m);
1559                                 break;
1560                         case Kdown:
1561                                 if(current == nil || !canqlock(current))
1562                                         break;
1563                                 o = addpt(pos, pagesize(current));
1564                                 if(o.y > Dy(screen->r)){
1565                                         translate(current, Pt(0, -Dy(screen->r)/2));
1566                                         qunlock(current);
1567                                         break;
1568                                 }
1569                                 if(nextpage(current))
1570                                         pos.y = 0;
1571                                 qunlock(current);
1572                                 docmd(Cnext, &m);
1573                                 break;
1574                         default:
1575                                 for(i = 0; i<nelem(cmds); i++)
1576                                         if((cmds[i].k1 == e.kbdc) ||
1577                                            (cmds[i].k2 == e.kbdc) ||
1578                                            (cmds[i].k3 == e.kbdc))
1579                                                 break;
1580                                 if(i < nelem(cmds)){
1581                                         docmd(i, &m);
1582                                         break;
1583                                 }
1584                                 if((e.kbdc < 0x20) || 
1585                                    (e.kbdc & 0xFF00) == KF || 
1586                                    (e.kbdc & 0xFF00) == Spec)
1587                                         break;
1588                                 snprint(buf, sizeof(buf), "%C", (Rune)e.kbdc);
1589                                 if(eenter("Go to", buf, sizeof(buf), &m) > 0)
1590                                         showpage(findpage(buf));
1591                         }
1592                         break;
1593                 case Eplumb:
1594                         pm = e.v;
1595                         if(pm && pm->ndata > 0){
1596                                 int fd;
1597
1598                                 fd = -1;
1599                                 s = plumblookup(pm->attr, "action");
1600                                 if(s && strcmp(s, "quit")==0)
1601                                         exits(0);
1602                                 if(s && strcmp(s, "showdata")==0){
1603                                         if((fd = createtmp("plumb")) < 0){
1604                                                 fprint(2, "plumb: createtmp: %r\n");
1605                                                 goto Plumbfree;
1606                                         }
1607                                         s = malloc(NPATH);
1608                                         if(fd2path(fd, s, NPATH) < 0){
1609                                                 close(fd);
1610                                                 goto Plumbfree;
1611                                         }
1612                                         write(fd, pm->data, pm->ndata);
1613                                 }else if(pm->data[0] == '/'){
1614                                         s = strdup(pm->data);
1615                                 }else{
1616                                         s = malloc(strlen(pm->wdir)+1+pm->ndata+1);
1617                                         sprint(s, "%s/%s", pm->wdir, pm->data);
1618                                         cleanname(s);
1619                                 }
1620                                 showpage(addpage(root, shortname(s), popenfile, s, fd));
1621                         }
1622                 Plumbfree:
1623                         plumbfree(pm);
1624                         break;
1625                 }
1626         }
1627 }