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