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