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