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