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