]> git.lizzy.rs Git - plan9front.git/blob - sys/src/cmd/page.c
page: make lookahead fetch work in backwards direction
[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){
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 && p->down)
826                 return p->down;
827         while(p){
828                 if(p->next)
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){
841                 for(p = root->down; p; 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 && 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                 p = forward < 0 ? prevpage(p) : nextpage(p);
978         }
979 }
980
981 /*
982  * A draw operation that touches only the area contained in bot but not in top.
983  * mp and sp get aligned with bot.min.
984  */
985 static void
986 gendrawdiff(Image *dst, Rectangle bot, Rectangle top, 
987         Image *src, Point sp, Image *mask, Point mp, int op)
988 {
989         Rectangle r;
990         Point origin;
991         Point delta;
992
993         if(Dx(bot)*Dy(bot) == 0)
994                 return;
995
996         /* no points in bot - top */
997         if(rectinrect(bot, top))
998                 return;
999
1000         /* bot - top â‰¡ bot */
1001         if(Dx(top)*Dy(top)==0 || rectXrect(bot, top)==0){
1002                 gendrawop(dst, bot, src, sp, mask, mp, op);
1003                 return;
1004         }
1005
1006         origin = bot.min;
1007         /* split bot into rectangles that don't intersect top */
1008         /* left side */
1009         if(bot.min.x < top.min.x){
1010                 r = Rect(bot.min.x, bot.min.y, top.min.x, bot.max.y);
1011                 delta = subpt(r.min, origin);
1012                 gendrawop(dst, r, src, addpt(sp, delta), mask, addpt(mp, delta), op);
1013                 bot.min.x = top.min.x;
1014         }
1015
1016         /* right side */
1017         if(bot.max.x > top.max.x){
1018                 r = Rect(top.max.x, bot.min.y, bot.max.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.max.x = top.max.x;
1022         }
1023
1024         /* top */
1025         if(bot.min.y < top.min.y){
1026                 r = Rect(bot.min.x, bot.min.y, bot.max.x, top.min.y);
1027                 delta = subpt(r.min, origin);
1028                 gendrawop(dst, r, src, addpt(sp, delta), mask, addpt(mp, delta), op);
1029                 bot.min.y = top.min.y;
1030         }
1031
1032         /* bottom */
1033         if(bot.max.y > top.max.y){
1034                 r = Rect(bot.min.x, top.max.y, bot.max.x, bot.max.y);
1035                 delta = subpt(r.min, origin);
1036                 gendrawop(dst, r, src, addpt(sp, delta), mask, addpt(mp, delta), op);
1037                 bot.max.y = top.max.y;
1038         }
1039 }
1040
1041 int
1042 alphachan(ulong chan)
1043 {
1044         for(; chan; chan >>= 8)
1045                 if(TYPE(chan) == CAlpha)
1046                         return 1;
1047         return 0;
1048 }
1049
1050 void
1051 zoomdraw(Image *d, Rectangle r, Rectangle top, Image *b, Image *s, Point sp, int f)
1052 {
1053         Rectangle dr;
1054         Image *t;
1055         Point a;
1056         int w;
1057
1058         a = ZP;
1059         if(r.min.x < d->r.min.x){
1060                 sp.x += (d->r.min.x - r.min.x)/f;
1061                 a.x = (d->r.min.x - r.min.x)%f;
1062                 r.min.x = d->r.min.x;
1063         }
1064         if(r.min.y < d->r.min.y){
1065                 sp.y += (d->r.min.y - r.min.y)/f;
1066                 a.y = (d->r.min.y - r.min.y)%f;
1067                 r.min.y = d->r.min.y;
1068         }
1069         rectclip(&r, d->r);
1070         w = s->r.max.x - sp.x;
1071         if(w > Dx(r))
1072                 w = Dx(r);
1073         dr = r;
1074         dr.max.x = dr.min.x+w;
1075         if(!alphachan(s->chan))
1076                 b = nil;
1077         if(f <= 1){
1078                 if(b) gendrawdiff(d, dr, top, b, sp, nil, ZP, SoverD);
1079                 gendrawdiff(d, dr, top, s, sp, nil, ZP, SoverD);
1080                 return;
1081         }
1082         if((t = allocimage(display, dr, s->chan, 0, 0)) == nil)
1083                 return;
1084         for(; dr.min.y < r.max.y; dr.min.y++){
1085                 dr.max.y = dr.min.y+1;
1086                 draw(t, dr, s, nil, sp);
1087                 if(++a.y == f){
1088                         a.y = 0;
1089                         sp.y++;
1090                 }
1091         }
1092         dr = r;
1093         for(sp=dr.min; dr.min.x < r.max.x; sp.x++){
1094                 dr.max.x = dr.min.x+1;
1095                 if(b) gendrawdiff(d, dr, top, b, sp, nil, ZP, SoverD);
1096                 gendrawdiff(d, dr, top, t, sp, nil, ZP, SoverD);
1097                 for(dr.min.x++; ++a.x < f && dr.min.x < r.max.x; dr.min.x++){
1098                         dr.max.x = dr.min.x+1;
1099                         gendrawdiff(d, dr, top, d, Pt(dr.min.x-1, dr.min.y), nil, ZP, SoverD);
1100                 }
1101                 a.x = 0;
1102         }
1103         freeimage(t);
1104 }
1105
1106 Point
1107 pagesize(Page *p)
1108 {
1109         return p->image ? mulpt(subpt(p->image->r.max, p->image->r.min), zoom) : ZP;
1110 }
1111
1112 void
1113 drawframe(Rectangle r)
1114 {
1115         border(screen, r, -Borderwidth, frame, ZP);
1116         gendrawdiff(screen, screen->r, insetrect(r, -Borderwidth), ground, ZP, nil, ZP, SoverD);
1117         flushimage(display, 1);
1118 }
1119
1120 void
1121 drawpage(Page *p)
1122 {
1123         Rectangle r;
1124         Image *i;
1125
1126         if(i = p->image){
1127                 r = rectaddpt(Rpt(ZP, pagesize(p)), addpt(pos, screen->r.min));
1128                 zoomdraw(screen, r, ZR, paper, i, i->r.min, zoom);
1129         } else {
1130                 r = Rpt(ZP, stringsize(font, p->name));
1131                 r = rectaddpt(r, addpt(subpt(divpt(subpt(screen->r.max, screen->r.min), 2),
1132                         divpt(r.max, 2)), screen->r.min));
1133                 draw(screen, r, paper, nil, ZP);
1134                 string(screen, r.min, display->black, ZP, font, p->name);
1135         }
1136         drawframe(r);
1137 }
1138
1139 void
1140 translate(Page *p, Point d)
1141 {
1142         Rectangle r, nr;
1143         Image *i;
1144
1145         i = p->image;
1146         if(i==0 || d.x==0 && d.y==0)
1147                 return;
1148         r = rectaddpt(Rpt(ZP, pagesize(p)), addpt(pos, screen->r.min));
1149         pos = addpt(pos, d);
1150         nr = rectaddpt(r, d);
1151         if(rectclip(&r, screen->r))
1152                 draw(screen, rectaddpt(r, d), screen, nil, r.min);
1153         else
1154                 r = ZR;
1155         zoomdraw(screen, nr, rectaddpt(r, d), paper, i, i->r.min, zoom);
1156         drawframe(nr);
1157 }
1158
1159 int
1160 pagewalk1(Page *p)
1161 {
1162         char *s;
1163         int n;
1164
1165         if((s = pagewalk) == nil || *s == 0)
1166                 return 1;
1167         n = strlen(p->name);
1168         if(n == 0 || strncmp(s, p->name, n) != 0)
1169                 return 0;
1170         if(s[n] == 0){
1171                 pagewalk = nil;
1172                 return 1;
1173         }
1174         if(s[n] == '/' || s[n] == '!'){
1175                 pagewalk = s + n+1;
1176                 return 1;
1177         }
1178         return 0;
1179 }
1180
1181 Page*
1182 trywalk(char *name, char *addr)
1183 {
1184         static char buf[NPATH];
1185         Page *p, *a;
1186
1187         pagewalk = nil;
1188         memset(buf, 0, sizeof(buf));
1189         snprint(buf, sizeof(buf), "%s%s%s",
1190                 name ? name : "",
1191                 (name && addr) ? "!" : "", 
1192                 addr ? addr : "");
1193         pagewalk = buf;
1194
1195         a = nil;
1196         if(root){
1197                 p = root->down;
1198         Loop:
1199                 for(; p; p = p->next)
1200                         if(pagewalk1(p)){
1201                                 a = p;
1202                                 p = p->down;
1203                                 goto Loop;
1204                         }
1205         }
1206         return a;
1207 }
1208
1209 Page*
1210 findpage(char *name)
1211 {
1212         static char buf[NPATH], *f[32];
1213         Page *p;
1214         int n;
1215
1216         if(name == nil)
1217                 return nil;
1218
1219         n = strlen(name);
1220         /* look in current document */
1221         if(current && current->up){
1222                 for(p = current->up->down; p; p = p->next)
1223                         if(cistrncmp(p->name, name, n) == 0)
1224                                 return p;
1225         }
1226         /* look everywhere */
1227         if(root){
1228                 for(p = root->down; p; p = nextpage(p))
1229                         if(cistrncmp(p->name, name, n) == 0)
1230                                 return p;
1231         }
1232         /* try bookmark */
1233         return trywalk(name, nil);
1234 }
1235
1236 void
1237 writeaddr(Page *p, char *file)
1238 {
1239         char buf[NPATH], *s;
1240         int fd;
1241
1242         s = pageaddr(p, buf, sizeof(buf));
1243         if((fd = open(file, OWRITE)) >= 0){
1244                 write(fd, s, strlen(s));
1245                 close(fd);
1246         }
1247 }
1248
1249 Page*
1250 pageat(int i)
1251 {
1252         Page *p;
1253
1254         for(p = root->down; i > 0 && p; p = nextpage(p))
1255                 i--;
1256         return i ? nil : p;
1257 }
1258
1259 int
1260 pageindex(Page *x)
1261 {
1262         Page *p;
1263         int i;
1264
1265         for(i = 0, p = root->down; p && p != x; p = nextpage(p))
1266                 i++;
1267         return (p == x) ? i : -1;
1268 }
1269
1270 char*
1271 pagemenugen(int i)
1272 {
1273         Page *p;
1274
1275         if(p = pageat(i))
1276                 return shortlabel(p->name);
1277         return nil;
1278 }
1279
1280 char*
1281 cmdmenugen(int i)
1282 {
1283         if(i < 0 || i >= nelem(cmds))
1284                 return nil;
1285         return cmds[i].m;
1286 }
1287
1288 /*
1289  * spawn new proc to load a run of pages starting with p
1290  * the display should *not* be locked as it gets called
1291  * from recursive page load.
1292  */
1293 void
1294 showpage1(Page *p)
1295 {
1296         static int nproc;
1297         int oviewgen;
1298
1299         if(p == nil)
1300                 return;
1301         esetcursor(&reading);
1302         writeaddr(p, "/dev/label");
1303         current = p;
1304         oviewgen = viewgen;
1305         switch(rfork(RFPROC|RFMEM)){
1306         case -1:
1307                 sysfatal("rfork: %r");
1308         case 0:
1309                 loadpages(p, oviewgen);
1310                 exits(nil);
1311         }
1312         if(++nproc >= NPROC)
1313                 if(waitpid() > 0)
1314                         nproc--;
1315 }
1316
1317 /* recursive display lock, called from main proc only */
1318 void
1319 drawlock(int dolock){
1320         static int ref = 0;
1321         if(dolock){
1322                 if(ref++ == 0)
1323                         lockdisplay(display);
1324         } else {
1325                 if(--ref == 0)
1326                         unlockdisplay(display);
1327         }
1328 }
1329
1330
1331 void
1332 showpage(Page *p)
1333 {
1334         if(p == nil)
1335                 return;
1336         drawlock(0);
1337         unloadpages(imemlimit);
1338         showpage1(p);
1339         drawlock(1);
1340 }
1341
1342 void
1343 shownext(void)
1344 {
1345         Page *p;
1346
1347         forward = 1;
1348         for(p = nextpage(current); p; p = nextpage(p))
1349                 if(p->image || p->open)
1350                         break;
1351         showpage(p);
1352 }
1353
1354 void
1355 showprev(void)
1356 {
1357         Page *p;
1358
1359         forward = -1;
1360         for(p = prevpage(current); p; p = prevpage(p))
1361                 if(p->image || p->open)
1362                         break;
1363         showpage(p);
1364 }
1365
1366 void
1367 zerox(Page *p)
1368 {
1369         char nam[64], *argv[4];
1370         int fd;
1371
1372         if(p == nil)
1373                 return;
1374         drawlock(0);
1375         qlock(p);
1376         if((fd = openpage(p)) < 0)
1377                 goto Out;
1378         if(rfork(RFPROC|RFMEM|RFFDG|RFENVG|RFNOTEG|RFNOWAIT) == 0){
1379                 dupfds(fd, 1, 2, -1);
1380                 snprint(nam, sizeof nam, "/bin/%s", argv0);
1381                 argv[0] = argv0;
1382                 argv[1] = "-w";
1383                 argv[2] = nil;
1384                 exec(nam, argv);
1385                 sysfatal("exec: %r");
1386         }
1387         close(fd);
1388 Out:
1389         qunlock(p);
1390         drawlock(1);
1391 }
1392
1393 void
1394 showext(Page *p)
1395 {
1396         char label[64], *argv[4];
1397         Point ps;
1398         int fd;
1399
1400         if(p->ext == nil)
1401                 return;
1402         snprint(label, sizeof(label), "%s %s", p->ext, p->name);
1403         ps = Pt(0, 0);
1404         if(p->image)
1405                 ps = addpt(subpt(p->image->r.max, p->image->r.min), Pt(24, 24));
1406         drawlock(0);
1407         if((fd = p->fd) < 0){
1408                 if(p->open != popenfile)
1409                         return;
1410                 fd = open((char*)p->data, OREAD);
1411         } else {
1412                 fd = dup(fd, -1);
1413                 seek(fd, 0, 0);
1414         }
1415         if(rfork(RFPROC|RFMEM|RFFDG|RFNOTEG|RFREND|RFNOWAIT) == 0){
1416                 if(newwindow(nil) != -1){
1417                         dupfds(fd, open("/dev/cons", OWRITE), open("/dev/cons", OWRITE), -1);
1418                         if((fd = open("/dev/label", OWRITE)) >= 0){
1419                                 write(fd, label, strlen(label));
1420                                 close(fd);
1421                         }
1422                         if(ps.x && ps.y)
1423                                 resizewin(ps);
1424                         argv[0] = "rc";
1425                         argv[1] = "-c";
1426                         argv[2] = p->ext;
1427                         argv[3] = nil;
1428                         exec("/bin/rc", argv);
1429                 }
1430                 exits(0);
1431         }
1432         close(fd);
1433         drawlock(1);
1434 }
1435
1436 void
1437 eresized(int new)
1438 {
1439         Page *p;
1440
1441         drawlock(1);
1442         if(new && getwindow(display, Refnone) == -1)
1443                 sysfatal("getwindow: %r");
1444         if(p = current){
1445                 if(canqlock(p)){
1446                         drawpage(p);
1447                         qunlock(p);
1448                 }
1449         }
1450         drawlock(0);
1451 }
1452
1453 int cohort = -1;
1454 void killcohort(void)
1455 {
1456         int i;
1457         for(i=0;i!=3;i++){      /* It's a long way to the kitchen */
1458                 postnote(PNGROUP, cohort, "kill");
1459                 sleep(1);
1460         }
1461 }
1462
1463 void drawerr(Display *, char *msg)
1464 {
1465         sysfatal("draw: %s", msg);
1466 }
1467
1468 void
1469 usage(void)
1470 {
1471         fprint(2, "usage: %s [ -iRw ] [ -m mb ] [ -p ppi ] [ -j addr ] [ file ... ]\n", argv0);
1472         exits("usage");
1473 }
1474
1475 void
1476 docmd(int i, Mouse *m)
1477 {
1478         char buf[NPATH], *s;
1479         Point o;
1480         int fd;
1481
1482         switch(i){
1483         case Corigsize:
1484                 pos = ZP;
1485                 zoom = 1;
1486                 resize = ZP;
1487                 rotate = 0;
1488         Unload:
1489                 viewgen++;
1490                 drawlock(0);
1491                 unloadpages(0);
1492                 showpage1(current);
1493                 drawlock(1);
1494                 break;
1495         case Cupsidedown:
1496                 rotate += 90;
1497         case Crotate90:
1498                 rotate += 90;
1499                 rotate %= 360;
1500                 goto Unload;
1501         case Cfitwidth:
1502                 pos = ZP;
1503                 zoom = 1;
1504                 resize = subpt(screen->r.max, screen->r.min);
1505                 resize.y = 0;
1506                 goto Unload;
1507         case Cfitheight:
1508                 pos = ZP;
1509                 zoom = 1;
1510                 resize = subpt(screen->r.max, screen->r.min);
1511                 resize.x = 0;
1512                 goto Unload;
1513         case Czoomin:
1514         case Czoomout:
1515                 if(current == nil || !canqlock(current))
1516                         break;
1517                 o = subpt(m->xy, screen->r.min);
1518                 if(i == Czoomin){
1519                         if(zoom < 0x1000){
1520                                 zoom *= 2;
1521                                 pos =  addpt(mulpt(subpt(pos, o), 2), o);
1522                         }
1523                 }else{
1524                         if(zoom > 1){
1525                                 zoom /= 2;
1526                                 pos =  addpt(divpt(subpt(pos, o), 2), o);
1527                         }
1528                 }
1529                 drawpage(current);
1530                 qunlock(current);
1531                 break;
1532         case Cwrite:
1533                 if(current == nil || !canqlock(current))
1534                         break;
1535                 if(current->image){
1536                         s = nil;
1537                         if(current->up && current->up != root)
1538                                 s = current->up->name;
1539                         snprint(buf, sizeof(buf), "%s%s%s.bit",
1540                                 s ? s : "",
1541                                 s ? "." : "",
1542                                 current->name);
1543                         if(eenter("Write", buf, sizeof(buf), m) > 0){
1544                                 if((fd = create(buf, OWRITE, 0666)) < 0){
1545                                         errstr(buf, sizeof(buf));
1546                                         eenter(buf, 0, 0, m);
1547                                 } else {
1548                                         esetcursor(&reading);
1549                                         writeimage(fd, current->image, 0);
1550                                         close(fd);
1551                                         esetcursor(nil);
1552                                 }
1553                         }
1554                 }
1555                 qunlock(current);
1556                 break;
1557         case Cext:
1558                 if(current == nil || !canqlock(current))
1559                         break;
1560                 showext(current);
1561                 qunlock(current);
1562                 break;
1563         case Csnarf:
1564                 writeaddr(current, "/dev/snarf");
1565                 break;
1566         case Cnext:
1567                 shownext();
1568                 break;
1569         case Cprev:
1570                 showprev();
1571                 break;
1572         case Czerox:
1573                 zerox(current);
1574                 break;
1575         case Cquit:
1576                 exits(0);
1577         }
1578 }
1579
1580 void
1581 scroll(int y)
1582 {
1583         Point z;
1584         Page *p;
1585
1586         if(current == nil || !canqlock(current))
1587                 return;
1588         if(y < 0){
1589                 if(pos.y >= 0){
1590                         p = prevpage(current);
1591                         if(p){
1592                                 qunlock(current);
1593                                 z = ZP;
1594                                 if(canqlock(p)){
1595                                         z = pagesize(p);
1596                                         qunlock(p);
1597                                 }
1598                                 if(z.y == 0)
1599                                         z.y = Dy(screen->r);
1600                                 if(pos.y+z.y > Dy(screen->r))
1601                                         pos.y = Dy(screen->r) - z.y;
1602                                 showprev();
1603                                 return;
1604                         }
1605                         y = 0;
1606                 }
1607         } else {
1608                 z = pagesize(current);
1609                 if(pos.y+z.y <= Dy(screen->r)){
1610                         p = nextpage(current);
1611                         if(p){
1612                                 qunlock(current);
1613                                 if(pos.y < 0)
1614                                         pos.y = 0;
1615                                 shownext();
1616                                 return;
1617                         }
1618                         y = 0;
1619                 }
1620         }
1621         translate(current, Pt(0, -y));
1622         qunlock(current);
1623 }
1624
1625 void
1626 main(int argc, char *argv[])
1627 {
1628         enum { Eplumb = 4 };
1629         char buf[NPATH];
1630         Plumbmsg *pm;
1631         Point o;
1632         Mouse m;
1633         Event e;
1634         char *s;
1635         int i;
1636
1637         quotefmtinstall();
1638
1639         ARGBEGIN {
1640         case 'a':
1641         case 'v':
1642         case 'V':
1643         case 'P':
1644                 break;
1645         case 'R':
1646                 if(newwin == 0)
1647                         newwin = -1;
1648                 break;
1649         case 'w':
1650                 newwin = 1;
1651                 break;
1652         case 'i':
1653                 imode = 1;
1654                 break;
1655         case 'j':
1656                 trywalk(EARGF(usage()), nil);
1657                 break;
1658         case 'm':
1659                 imemlimit = atol(EARGF(usage()))*MiB;
1660                 break;
1661         case 'p':
1662                 ppi = atoi(EARGF(usage()));
1663                 break;
1664         default:
1665                 usage();
1666         } ARGEND;
1667
1668         if(newwin > 0){
1669                 if(newwindow(nil) < 0)
1670                         sysfatal("newwindow: %r");
1671         }
1672
1673         /*
1674          * so that we can stop all subprocesses with a note,
1675          * and to isolate rendezvous from other processes
1676          */
1677         atnotify(catchnote, 1);
1678         if(cohort = rfork(RFPROC|RFNOTEG|RFNAMEG|RFREND)){
1679                 atexit(killcohort);
1680                 waitpid();
1681                 exits(0);
1682         }
1683         cohort = getpid();
1684         atexit(killcohort);
1685         if(initdraw(drawerr, nil, argv0) < 0)
1686                 sysfatal("initdraw: %r");
1687         paper = display->white;
1688         frame = display->black;
1689         ground = allocimage(display, Rect(0,0,1,1), screen->chan, 1, 0x777777FF);
1690         display->locking = 1;
1691         unlockdisplay(display);
1692
1693         einit(Ekeyboard|Emouse);
1694         eplumb(Eplumb, "image");
1695         memset(&m, 0, sizeof(m));
1696         if((nullfd = open("/dev/null", ORDWR)) < 0)
1697                 sysfatal("open: %r");
1698         dup(nullfd, 1);
1699         lru.lprev = &lru;
1700         lru.lnext = &lru;
1701         current = root = addpage(nil, "", nil, nil, -1);
1702         root->delim = "";
1703         if(*argv == nil && !imode)
1704                 addpage(root, "stdin", popenfile, strdup("/fd/0"), -1);
1705         for(; *argv; argv++)
1706                 addpage(root, *argv, popenfile, strdup(*argv), -1);
1707
1708         drawlock(1);
1709         for(;;){
1710                 drawlock(0);
1711                 i=event(&e);
1712                 drawlock(1);
1713
1714                 switch(i){
1715                 case Emouse:
1716                         m = e.mouse;
1717                         if(m.buttons & 1){
1718                                 if(current &&  canqlock(current)){
1719                                         for(;;) {
1720                                                 o = m.xy;
1721                                                 m = emouse();
1722                                                 if((m.buttons & 1) == 0)
1723                                                         break;
1724                                                 translate(current, subpt(m.xy, o));
1725                                         }
1726                                         qunlock(current);
1727                                 }
1728                         } else if(m.buttons & 2){
1729                                 o = m.xy;
1730                                 i = emenuhit(2, &m, &cmdmenu);
1731                                 m.xy = o;
1732                                 docmd(i, &m);
1733                         } else if(m.buttons & 4){
1734                                 if(root->down){
1735                                         Page *x;
1736
1737                                         qlock(&pagelock);
1738                                         pagemenu.lasthit = pageindex(current);
1739                                         x = pageat(emenuhit(3, &m, &pagemenu));
1740                                         qunlock(&pagelock);
1741                                         forward = 0;
1742                                         showpage(x);
1743                                 }
1744                         } else if(m.buttons & 8){
1745                                 scroll(screen->r.min.y - m.xy.y);
1746                         } else if(m.buttons & 16){
1747                                 scroll(m.xy.y - screen->r.min.y);
1748                         }
1749                         break;
1750                 case Ekeyboard:
1751                         switch(e.kbdc){
1752                         case Kup:
1753                                 scroll(-Dy(screen->r)/3);
1754                                 break;
1755                         case Kpgup:
1756                                 scroll(-Dy(screen->r)/2);
1757                                 break;
1758                         case Kdown:
1759                                 scroll(Dy(screen->r)/3);
1760                                 break;
1761                         case Kpgdown:
1762                                 scroll(Dy(screen->r)/2);
1763                                 break;
1764                         default:
1765                                 for(i = 0; i<nelem(cmds); i++)
1766                                         if((cmds[i].k1 == e.kbdc) ||
1767                                            (cmds[i].k2 == e.kbdc) ||
1768                                            (cmds[i].k3 == e.kbdc))
1769                                                 break;
1770                                 if(i < nelem(cmds)){
1771                                         docmd(i, &m);
1772                                         break;
1773                                 }
1774                                 if((e.kbdc < 0x20) || 
1775                                    (e.kbdc & 0xFF00) == KF || 
1776                                    (e.kbdc & 0xFF00) == Spec)
1777                                         break;
1778                                 snprint(buf, sizeof(buf), "%C", (Rune)e.kbdc);
1779                                 if(eenter("Go to", buf, sizeof(buf), &m) > 0){
1780                                         forward = 0;
1781                                         showpage(findpage(buf));
1782                                 }
1783                         }
1784                         break;
1785                 case Eplumb:
1786                         pm = e.v;
1787                         if(pm && pm->ndata > 0){
1788                                 Page *j;
1789                                 int fd;
1790
1791                                 fd = -1;
1792                                 s = plumblookup(pm->attr, "action");
1793                                 if(s && strcmp(s, "quit")==0)
1794                                         exits(0);
1795                                 if(s && strcmp(s, "showdata")==0){
1796                                         if((fd = createtmp("plumb")) < 0){
1797                                                 fprint(2, "plumb: createtmp: %r\n");
1798                                                 goto Plumbfree;
1799                                         }
1800                                         s = malloc(NPATH);
1801                                         if(fd2path(fd, s, NPATH) < 0){
1802                                                 close(fd);
1803                                                 goto Plumbfree;
1804                                         }
1805                                         write(fd, pm->data, pm->ndata);
1806                                 }else if(pm->data[0] == '/'){
1807                                         s = strdup(pm->data);
1808                                 }else{
1809                                         s = malloc(strlen(pm->wdir)+1+pm->ndata+1);
1810                                         sprint(s, "%s/%s", pm->wdir, pm->data);
1811                                         cleanname(s);
1812                                 }
1813                                 j = trywalk(s, plumblookup(pm->attr, "addr"));
1814                                 if(j == nil){
1815                                         current = root;
1816                                         drawlock(0);
1817                                         j = addpage(root, s, popenfile, s, fd);
1818                                         drawlock(1);
1819                                 }
1820                                 forward = 0;
1821                                 showpage(j);
1822                         }
1823                 Plumbfree:
1824                         plumbfree(pm);
1825                         break;
1826                 }
1827         }
1828 }