]> git.lizzy.rs Git - plan9front.git/blob - sys/src/cmd/page.c
page: 12 zoom levels should be enougth
[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/ppm",                    popenimg,       "ppm",
684         "image/bmp",                    popenimg,       "bmp",
685         "image/tga",                    popenimg,       "tga",
686         "image/x-icon",                 popenimg,       "ico",
687         "image/p9bit",                  popenimg,       nil,
688         };
689
690         char buf[NBUF], typ[128], *file;
691         int i, n, fd, tfd;
692         Dir *d;
693
694         fd = p->fd;
695         p->fd = -1;
696         p->ext = nil;
697         file = p->data;
698         p->data = nil;
699         if(fd < 0){
700                 if((fd = open(file, OREAD)) < 0){
701                 Err0:
702                         free(file);
703                         return -1;
704                 }
705         }
706         seek(fd, 0, 0);
707         if((d = dirfstat(fd)) == nil){
708         Err1:
709                 close(fd);
710                 goto Err0;
711         }
712         if(d->mode & DMDIR){
713                 free(d);
714                 d = nil;
715
716                 snprint(buf, sizeof(buf), "%s/META-INF/container.xml", file);
717                 if((tfd = open(buf, OREAD)) >= 0){
718                         close(fd);
719                         p->fd = tfd;
720                         p->data = file;
721                         p->open = popenepub;
722                         return p->open(p);
723                 }
724
725                 if((n = dirreadall(fd, &d)) < 0)
726                         goto Err1;
727                 qsort(d, n, sizeof d[0], dircmp);
728                 for(i = 0; i<n; i++)
729                         addpage(p, d[i].name, popenfile, smprint("%s/%s", file, d[i].name), -1);
730                 free(d);
731                 goto Err1;
732         }
733         free(d);
734
735         memset(buf, 0, NBUF/2);
736         if((n = readn(fd, buf, NBUF/2)) <= 0)
737                 goto Err1;
738         filetype(buf, n, typ, sizeof(typ));
739         for(i=0; i<nelem(tab); i++)
740                 if(strncmp(typ, tab[i].typ, strlen(tab[i].typ)) == 0)
741                         break;
742         if(i == nelem(tab)){
743                 werrstr("unknown image format: %s", typ);
744                 goto Err1;
745         }
746         p->fd = fd;
747         p->data = tab[i].data;
748         p->open = tab[i].open;
749         if(seek(fd, 0, 0) < 0)
750                 goto Noseek;
751         if((i = readn(fd, buf+n, n)) < 0)
752                 goto Err1;
753         if(i != n || memcmp(buf, buf+n, i)){
754                 n += i;
755         Noseek:
756                 if((tfd = createtmp("file")) < 0)
757                         goto Err1;
758                 while(n > 0){
759                         if(write(tfd, buf, n) != n)
760                                 goto Err2;
761                         if((n = read(fd, buf, sizeof(buf))) < 0)
762                                 goto Err2;
763                 }
764                 if(dup(tfd, fd) < 0){
765                 Err2:
766                         close(tfd);
767                         goto Err1;
768                 }
769                 close(tfd);
770         }
771         free(file);
772         return p->open(p);
773 }
774
775 Page*
776 nextpage(Page *p)
777 {
778         if(p && p->down)
779                 return p->down;
780         while(p){
781                 if(p->next)
782                         return p->next;
783                 p = p->up;
784         }
785         return nil;
786 }
787
788 Page*
789 prevpage(Page *x)
790 {
791         Page *p, *t;
792
793         if(x){
794                 for(p = root->down; p; p = t)
795                         if((t = nextpage(p)) == x)
796                                 return p;
797         }
798         return nil;
799 }
800
801 int
802 openpage(Page *p)
803 {
804         int fd;
805
806         fd = -1;
807         if(p->open == nil || (fd = p->open(p)) < 0)
808                 p->open = nil;
809         else {
810                 if(rotate)
811                         pipeline(fd, "rotate -r %d", rotate);
812                 if(resize.x)
813                         pipeline(fd, "resize -x %d", resize.x);
814                 else if(resize.y)
815                         pipeline(fd, "resize -y %d", resize.y);
816         }
817         return fd;
818 }
819
820 void
821 loadpage(Page *p)
822 {
823         int fd;
824
825         if(p->open && p->image == nil){
826                 fd = openpage(p);
827                 if(fd >= 0){
828                         pagegen++;
829                         if((p->image = readimage(display, fd, 1)) == nil)
830                                 fprint(2, "readimage: %r\n");
831                         close(fd);
832                 }
833                 if(p->image == nil)
834                         p->open = nil;
835         }
836         p->gen = pagegen;
837 }
838
839 void
840 unloadpage(Page *p)
841 {
842         if(p->open == nil || p->image == nil)
843                 return;
844         lockdisplay(display);
845         freeimage(p->image);
846         unlockdisplay(display);
847         p->image = nil;
848 }
849
850 void
851 unloadpages(int age)
852 {
853         Page *p;
854
855         for(p = root->down; p; p = nextpage(p)){
856                 if(age == 0)    /* synchronous flush */
857                         qlock(p);
858                 else if(!canqlock(p))
859                         continue;
860                 if((pagegen - p->gen) >= age)
861                         unloadpage(p);
862                 qunlock(p);
863         }
864 }
865
866 void
867 loadpages(Page *p, int ahead, int oviewgen)
868 {
869         int i;
870
871         ahead++;        /* load at least one */
872         unloadpages(ahead*2);
873         for(i = 0; i < ahead && p; p = nextpage(p), i++){
874                 if(viewgen != oviewgen)
875                         break;
876                 if(canqlock(p)){
877                         loadpage(p);
878                         if(viewgen != oviewgen){
879                                 unloadpage(p);
880                                 qunlock(p);
881                                 break;
882                         }
883                         if(p == current){
884                                 Point size;
885
886                                 esetcursor(nil);
887                                 size = pagesize(p);
888                                 if(size.x && size.y && newwin){
889                                         newwin = 0;
890                                         resizewin(size);
891                                 }
892                                 lockdisplay(display);
893                                 drawpage(p);
894                                 unlockdisplay(display);
895                         }
896                         qunlock(p);
897                 }
898         }
899 }
900
901 /*
902  * A draw operation that touches only the area contained in bot but not in top.
903  * mp and sp get aligned with bot.min.
904  */
905 static void
906 gendrawdiff(Image *dst, Rectangle bot, Rectangle top, 
907         Image *src, Point sp, Image *mask, Point mp, int op)
908 {
909         Rectangle r;
910         Point origin;
911         Point delta;
912
913         if(Dx(bot)*Dy(bot) == 0)
914                 return;
915
916         /* no points in bot - top */
917         if(rectinrect(bot, top))
918                 return;
919
920         /* bot - top â‰¡ bot */
921         if(Dx(top)*Dy(top)==0 || rectXrect(bot, top)==0){
922                 gendrawop(dst, bot, src, sp, mask, mp, op);
923                 return;
924         }
925
926         origin = bot.min;
927         /* split bot into rectangles that don't intersect top */
928         /* left side */
929         if(bot.min.x < top.min.x){
930                 r = Rect(bot.min.x, bot.min.y, top.min.x, bot.max.y);
931                 delta = subpt(r.min, origin);
932                 gendrawop(dst, r, src, addpt(sp, delta), mask, addpt(mp, delta), op);
933                 bot.min.x = top.min.x;
934         }
935
936         /* right side */
937         if(bot.max.x > top.max.x){
938                 r = Rect(top.max.x, bot.min.y, bot.max.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.max.x = top.max.x;
942         }
943
944         /* top */
945         if(bot.min.y < top.min.y){
946                 r = Rect(bot.min.x, bot.min.y, bot.max.x, top.min.y);
947                 delta = subpt(r.min, origin);
948                 gendrawop(dst, r, src, addpt(sp, delta), mask, addpt(mp, delta), op);
949                 bot.min.y = top.min.y;
950         }
951
952         /* bottom */
953         if(bot.max.y > top.max.y){
954                 r = Rect(bot.min.x, top.max.y, bot.max.x, bot.max.y);
955                 delta = subpt(r.min, origin);
956                 gendrawop(dst, r, src, addpt(sp, delta), mask, addpt(mp, delta), op);
957                 bot.max.y = top.max.y;
958         }
959 }
960
961 int
962 alphachan(ulong chan)
963 {
964         for(; chan; chan >>= 8)
965                 if(TYPE(chan) == CAlpha)
966                         return 1;
967         return 0;
968 }
969
970 void
971 zoomdraw(Image *d, Rectangle r, Rectangle top, Image *b, Image *s, Point sp, int f)
972 {
973         Rectangle dr;
974         Image *t;
975         Point a;
976         int w;
977
978         a = ZP;
979         if(r.min.x < d->r.min.x){
980                 sp.x += (d->r.min.x - r.min.x)/f;
981                 a.x = (d->r.min.x - r.min.x)%f;
982                 r.min.x = d->r.min.x;
983         }
984         if(r.min.y < d->r.min.y){
985                 sp.y += (d->r.min.y - r.min.y)/f;
986                 a.y = (d->r.min.y - r.min.y)%f;
987                 r.min.y = d->r.min.y;
988         }
989         rectclip(&r, d->r);
990         w = s->r.max.x - sp.x;
991         if(w > Dx(r))
992                 w = Dx(r);
993         dr = r;
994         dr.max.x = dr.min.x+w;
995         if(!alphachan(s->chan))
996                 b = nil;
997         if(f <= 1){
998                 if(b) gendrawdiff(d, dr, top, b, sp, nil, ZP, SoverD);
999                 gendrawdiff(d, dr, top, s, sp, nil, ZP, SoverD);
1000                 return;
1001         }
1002         if((t = allocimage(display, dr, s->chan, 0, 0)) == nil)
1003                 return;
1004         for(; dr.min.y < r.max.y; dr.min.y++){
1005                 dr.max.y = dr.min.y+1;
1006                 draw(t, dr, s, nil, sp);
1007                 if(++a.y == f){
1008                         a.y = 0;
1009                         sp.y++;
1010                 }
1011         }
1012         dr = r;
1013         for(sp=dr.min; dr.min.x < r.max.x; sp.x++){
1014                 dr.max.x = dr.min.x+1;
1015                 if(b) gendrawdiff(d, dr, top, b, sp, nil, ZP, SoverD);
1016                 gendrawdiff(d, dr, top, t, sp, nil, ZP, SoverD);
1017                 for(dr.min.x++; ++a.x < f && dr.min.x < r.max.x; dr.min.x++){
1018                         dr.max.x = dr.min.x+1;
1019                         gendrawdiff(d, dr, top, d, Pt(dr.min.x-1, dr.min.y), nil, ZP, SoverD);
1020                 }
1021                 a.x = 0;
1022         }
1023         freeimage(t);
1024 }
1025
1026 Point
1027 pagesize(Page *p)
1028 {
1029         return p->image ? mulpt(subpt(p->image->r.max, p->image->r.min), zoom) : ZP;
1030 }
1031
1032 void
1033 drawframe(Rectangle r)
1034 {
1035         border(screen, r, -Borderwidth, frame, ZP);
1036         gendrawdiff(screen, screen->r, insetrect(r, -Borderwidth), ground, ZP, nil, ZP, SoverD);
1037         flushimage(display, 1);
1038 }
1039
1040 void
1041 drawpage(Page *p)
1042 {
1043         Rectangle r;
1044         Image *i;
1045
1046         if(i = p->image){
1047                 r = rectaddpt(Rpt(ZP, pagesize(p)), addpt(pos, screen->r.min));
1048                 zoomdraw(screen, r, ZR, paper, i, i->r.min, zoom);
1049         } else {
1050                 r = Rpt(ZP, stringsize(font, p->label));
1051                 r = rectaddpt(r, addpt(subpt(divpt(subpt(screen->r.max, screen->r.min), 2),
1052                         divpt(r.max, 2)), screen->r.min));
1053                 draw(screen, r, paper, nil, ZP);
1054                 string(screen, r.min, display->black, ZP, font, p->label);
1055         }
1056         drawframe(r);
1057 }
1058
1059 void
1060 translate(Page *p, Point d)
1061 {
1062         Rectangle r, nr;
1063         Image *i;
1064
1065         i = p->image;
1066         if(i==0 || d.x==0 && d.y==0)
1067                 return;
1068         r = rectaddpt(Rpt(ZP, pagesize(p)), addpt(pos, screen->r.min));
1069         pos = addpt(pos, d);
1070         nr = rectaddpt(r, d);
1071         rectclip(&r, screen->r);
1072         draw(screen, rectaddpt(r, d), screen, nil, r.min);
1073         zoomdraw(screen, nr, rectaddpt(r, d), paper, i, i->r.min, zoom);
1074         drawframe(nr);
1075 }
1076
1077 Page*
1078 findpage(char *name)
1079 {
1080         Page *p;
1081         int n;
1082
1083         n = strlen(name);
1084         /* look in current document first */
1085         if(current && current->up){
1086                 for(p = current->up->down; p; p = p->next)
1087                         if(cistrncmp(p->label, name, n) == 0)
1088                                 return p;
1089         }
1090         /* look everywhere */
1091         for(p = root->down; p; p = nextpage(p))
1092                 if(cistrncmp(p->label, name, n) == 0)
1093                         return p;
1094         return nil;
1095 }
1096
1097 Page*
1098 pageat(int i)
1099 {
1100         Page *p;
1101
1102         for(p = root->down; i > 0 && p; p = nextpage(p))
1103                 i--;
1104         return i ? nil : p;
1105 }
1106
1107 int
1108 pageindex(Page *x)
1109 {
1110         Page *p;
1111         int i;
1112
1113         for(i = 0, p = root->down; p && p != x; p = nextpage(p))
1114                 i++;
1115         return (p == x) ? i : -1;
1116 }
1117
1118 char*
1119 pagemenugen(int i)
1120 {
1121         Page *p;
1122         if(p = pageat(i))
1123                 return p->label;
1124         return nil;
1125 }
1126
1127 char*
1128 cmdmenugen(int i)
1129 {
1130         if(i < 0 || i >= nelem(cmds))
1131                 return nil;
1132         return cmds[i].m;
1133 }
1134
1135 /*
1136  * spawn new proc to load a run of pages starting with p
1137  * the display should *not* be locked as it gets called
1138  * from recursive page load.
1139  */
1140 void
1141 showpage1(Page *p)
1142 {
1143         static int nproc;
1144         int oviewgen;
1145
1146         if(p == nil)
1147                 return;
1148         esetcursor(&reading);
1149         current = p;
1150         oviewgen = viewgen;
1151         if(++nproc > NPROC)
1152                 if(waitpid() > 0)
1153                         nproc--;
1154         switch(rfork(RFPROC|RFMEM)){
1155         case -1:
1156                 sysfatal("rfork: %r");
1157         case 0:
1158                 loadpages(p, NAHEAD, oviewgen);
1159                 exits(nil);
1160         }
1161 }
1162
1163 /* recursive display lock, called from main proc only */
1164 void
1165 drawlock(int dolock){
1166         static int ref = 0;
1167         if(dolock){
1168                 if(ref++ == 0)
1169                         lockdisplay(display);
1170         } else {
1171                 if(--ref == 0)
1172                         unlockdisplay(display);
1173         }
1174 }
1175
1176
1177 void
1178 showpage(Page *p)
1179 {
1180         drawlock(0);
1181         showpage1(p);
1182         drawlock(1);
1183 }
1184
1185 void
1186 shownext(void)
1187 {
1188         Page *p;
1189
1190         for(p = nextpage(current); p; p = nextpage(p))
1191                 if(p->image || p->open)
1192                         break;
1193         showpage(p);
1194 }
1195
1196 void
1197 showprev(void)
1198 {
1199         Page *p;
1200
1201         for(p = prevpage(current); p; p = prevpage(p))
1202                 if(p->image || p->open)
1203                         break;
1204         showpage(p);
1205 }
1206
1207 void
1208 zerox(Page *p)
1209 {
1210         char nam[64], *argv[4];
1211         int fd;
1212
1213         if(p == nil)
1214                 return;
1215         drawlock(0);
1216         qlock(p);
1217         if((fd = openpage(p)) < 0)
1218                 goto Out;
1219         if(rfork(RFPROC|RFMEM|RFFDG|RFENVG|RFNOTEG|RFNOWAIT) == 0){
1220                 dupfds(fd, 1, 2, -1);
1221                 snprint(nam, sizeof nam, "/bin/%s", argv0);
1222                 argv[0] = argv0;
1223                 argv[1] = "-w";
1224                 argv[2] = nil;
1225                 exec(nam, argv);
1226                 sysfatal("exec: %r");
1227         }
1228         close(fd);
1229 Out:
1230         qunlock(p);
1231         drawlock(1);
1232 }
1233
1234 void
1235 showext(Page *p)
1236 {
1237         char buf[128], label[64], *argv[4];
1238         Point ps;
1239         int fd;
1240
1241         if(p->ext == nil)
1242                 return;
1243         snprint(label, sizeof(label), "%s %s", p->ext, p->label);
1244         if(p->image){
1245                 ps = subpt(p->image->r.max, p->image->r.min);
1246                 ps.x += 24;
1247                 ps.y += 24;
1248         } else
1249                 ps = subpt(screen->r.max, screen->r.min);
1250         drawlock(0);
1251         if((fd = p->fd) < 0){
1252                 if(p->open != popenfile)
1253                         return;
1254                 fd = open((char*)p->data, OREAD);
1255         } else {
1256                 fd = dup(fd, -1);
1257                 seek(fd, 0, 0);
1258         }
1259         if(rfork(RFPROC|RFMEM|RFFDG|RFNOTEG|RFNAMEG|RFNOWAIT) == 0){
1260                 snprint(buf, sizeof(buf), "-pid %d -dx %d -dy %d", getpid(), ps.x, ps.y);
1261                 if(newwindow(buf) != -1){
1262                         dupfds(fd, 1, 2, -1);
1263                         if((fd = open("/dev/label", OWRITE)) >= 0){
1264                                 write(fd, label, strlen(label));
1265                                 close(fd);
1266                         }
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                 newwin = -1;
1441                 break;
1442         case 'w':
1443                 newwin = 1;
1444                 break;
1445         case 'i':
1446                 imode = 1;
1447                 break;
1448         case 'p':
1449                 ppi = atoi(EARGF(usage()));
1450                 break;
1451         default:
1452                 usage();
1453         } ARGEND;
1454
1455         /*
1456          * so that we can stop all subprocesses with a note,
1457          * and to isolate rendezvous from other processes
1458          */
1459         atnotify(catchnote, 1);
1460         if(cohort = rfork(RFPROC|RFNOTEG|RFNAMEG|RFREND)){
1461                 atexit(killcohort);
1462                 waitpid();
1463                 exits(0);
1464         }
1465         cohort = getpid();
1466         atexit(killcohort);
1467
1468         if(newwin > 0){
1469                 s = smprint("-pid %d", getpid());
1470                 if(newwindow(s) < 0)
1471                         sysfatal("newwindow: %r");
1472                 free(s);
1473         }
1474         if(initdraw(drawerr, nil, argv0) < 0)
1475                 sysfatal("initdraw: %r");
1476         paper = display->white;
1477         frame = display->black;
1478         ground = allocimage(display, Rect(0,0,1,1), screen->chan, 1, 0x777777FF);
1479         display->locking = 1;
1480         unlockdisplay(display);
1481         drawlock(1);
1482
1483         einit(Ekeyboard|Emouse);
1484         eplumb(Eplumb, "image");
1485         memset(&m, 0, sizeof(m));
1486         if((nullfd = open("/dev/null", ORDWR)) < 0)
1487                 sysfatal("open: %r");
1488         current = root = addpage(nil, "", nil, nil, -1);
1489         if(*argv == nil && !imode)
1490                 addpage(root, "stdin", popenfile, strdup("/fd/0"), -1);
1491         for(; *argv; argv++)
1492                 addpage(root, shortname(*argv), popenfile, strdup(*argv), -1);
1493
1494         for(;;){
1495                 drawlock(0);
1496                 i=event(&e);
1497                 drawlock(1);
1498
1499                 switch(i){
1500                 case Emouse:
1501                         m = e.mouse;
1502                         if(m.buttons & 1){
1503                                 if(current &&  canqlock(current)){
1504                                         for(;;) {
1505                                                 o = m.xy;
1506                                                 m = emouse();
1507                                                 if((m.buttons & 1) == 0)
1508                                                         break;
1509                                                 translate(current, subpt(m.xy, o));
1510                                         }
1511                                         qunlock(current);
1512                                 }
1513                         } else if(m.buttons & 2){
1514                                 o = m.xy;
1515                                 i = emenuhit(2, &m, &cmdmenu);
1516                                 m.xy = o;
1517                                 docmd(i, &m);
1518                         } else if(m.buttons & 4){
1519                                 if(root->down){
1520                                         Page *x;
1521
1522                                         qlock(&pagelock);
1523                                         pagemenu.lasthit = pageindex(current);
1524                                         x = pageat(emenuhit(3, &m, &pagemenu));
1525                                         qunlock(&pagelock);
1526                                         showpage(x);
1527                                 }
1528                         }
1529                         break;
1530                 case Ekeyboard:
1531                         switch(e.kbdc){
1532                         case Kup:
1533                                 if(current == nil || !canqlock(current))
1534                                         break;
1535                                 if(pos.y < 0){
1536                                         translate(current, Pt(0, Dy(screen->r)/2));
1537                                         qunlock(current);
1538                                         break;
1539                                 }
1540                                 if(prevpage(current))
1541                                         pos.y = 0;
1542                                 qunlock(current);
1543                                 docmd(Cprev, &m);
1544                                 break;
1545                         case Kdown:
1546                                 if(current == nil || !canqlock(current))
1547                                         break;
1548                                 o = addpt(pos, pagesize(current));
1549                                 if(o.y > Dy(screen->r)){
1550                                         translate(current, Pt(0, -Dy(screen->r)/2));
1551                                         qunlock(current);
1552                                         break;
1553                                 }
1554                                 if(nextpage(current))
1555                                         pos.y = 0;
1556                                 qunlock(current);
1557                                 docmd(Cnext, &m);
1558                                 break;
1559                         default:
1560                                 for(i = 0; i<nelem(cmds); i++)
1561                                         if((cmds[i].k1 == e.kbdc) ||
1562                                            (cmds[i].k2 == e.kbdc) ||
1563                                            (cmds[i].k3 == e.kbdc))
1564                                                 break;
1565                                 if(i < nelem(cmds)){
1566                                         docmd(i, &m);
1567                                         break;
1568                                 }
1569                                 if((e.kbdc < 0x20) || 
1570                                    (e.kbdc & 0xFF00) == KF || 
1571                                    (e.kbdc & 0xFF00) == Spec)
1572                                         break;
1573                                 snprint(buf, sizeof(buf), "%C", (Rune)e.kbdc);
1574                                 if(eenter("Go to", buf, sizeof(buf), &m) > 0)
1575                                         showpage(findpage(buf));
1576                         }
1577                         break;
1578                 case Eplumb:
1579                         pm = e.v;
1580                         if(pm && pm->ndata > 0){
1581                                 int fd;
1582
1583                                 fd = -1;
1584                                 s = plumblookup(pm->attr, "action");
1585                                 if(s && strcmp(s, "quit")==0)
1586                                         exits(0);
1587                                 if(s && strcmp(s, "showdata")==0){
1588                                         if((fd = createtmp("plumb")) < 0){
1589                                                 fprint(2, "plumb: createtmp: %r\n");
1590                                                 goto Plumbfree;
1591                                         }
1592                                         s = malloc(NPATH);
1593                                         if(fd2path(fd, s, NPATH) < 0){
1594                                                 close(fd);
1595                                                 goto Plumbfree;
1596                                         }
1597                                         write(fd, pm->data, pm->ndata);
1598                                 }else if(pm->data[0] == '/'){
1599                                         s = strdup(pm->data);
1600                                 }else{
1601                                         s = malloc(strlen(pm->wdir)+1+pm->ndata+1);
1602                                         sprint(s, "%s/%s", pm->wdir, pm->data);
1603                                         cleanname(s);
1604                                 }
1605                                 showpage(addpage(root, shortname(s), popenfile, s, fd));
1606                         }
1607                 Plumbfree:
1608                         plumbfree(pm);
1609                         break;
1610                 }
1611         }
1612 }