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