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