]> git.lizzy.rs Git - plan9front.git/blob - sys/src/cmd/page.c
page: fix filedescriptor leakage
[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         switch(rfork(RFPROC|RFFDG|RFREND|RFNOWAIT)){
205         case -1:
206                 close(pfd[0]);
207                 close(pfd[1]);
208                 goto Err;
209         case 0:
210                 dupfds(fd, pfd[1], 2, -1);
211                 va_start(arg, fmt);
212                 vsnprint(buf, sizeof buf, fmt, arg);
213                 va_end(arg);
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         switch(rfork(RFPROC|RFFDG|RFREND)){
290         case -1:
291                 close(p->fd);
292                 p->fd = -1;
293                 return -1;
294         case 0:
295                 dupfds(p->fd, 1, 2, -1);
296                 argv[0] = "rc";
297                 argv[1] = "-c";
298                 snprint(cmd, sizeof(cmd), "%s -m %s /fd/0", p->data, mnt);
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|RFFDG|RFMEM|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         switch(rfork(RFPROC|RFFDG|RFREND|RFNOWAIT)){
453         case -1:
454                 goto Err2;
455         case 0:
456                 if(pdf)
457                         dupfds(pin[1], pout[1], nullfd, pdat[1], ifd, -1);
458                 else
459                         dupfds(nullfd, nullfd, nullfd, pdat[1], ifd, -1);
460                 if(p->data)
461                         pipeline(4, "%s", (char*)p->data);
462                 argv[0] = "gs";
463                 argv[1] = "-q";
464                 argv[2] = "-sDEVICE=plan9";
465                 argv[3] = "-sOutputFile=/fd/3";
466                 argv[4] = "-dBATCH";
467                 argv[5] = pdf ? "-dDELAYSAFER" : "-dSAFER";
468                 argv[6] = "-dQUIET";
469                 argv[7] = "-dTextAlphaBits=4";
470                 argv[8] = "-dGraphicsAlphaBits=4";
471                 snprint(buf, sizeof buf, "-r%d", ppi);
472                 argv[9] = buf;
473                 argv[10] = "-dDOINTERPOLATE";
474                 argv[11] = pdf ? "-" : "/fd/4";
475                 argv[12] = nil;
476                 exec("/bin/gs", argv);
477                 sysfatal("exec: %r");
478         }
479
480         close(pin[1]);
481         close(pout[1]);
482         close(pdat[1]);
483         close(ifd);
484
485         if(pdf){
486                 Ghost *gs;
487                 char *prolog =
488                         "/PAGEOUT (/fd/1) (w) file def\n"
489                         "/PAGE== { PAGEOUT exch write==only PAGEOUT (\\n) writestring PAGEOUT flushfile } def\n"
490                         "\n"
491                         "/Page null def\n"
492                         "/Page# 0 def\n"
493                         "/PDFSave null def\n"
494                         "/DSCPageCount 0 def\n"
495                         "/DoPDFPage {dup /Page# exch store pdfgetpage pdfshowpage } def\n"
496                         "\n"
497                         "GS_PDF_ProcSet begin\n"
498                         "pdfdict begin\n"
499                         "(/fd/4) (r) file { DELAYSAFER { .setsafe } if } stopped pop pdfopen begin\n"
500                         "\n"
501                         "pdfpagecount PAGE==\n";
502
503                 n = strlen(prolog);
504                 if(write(pin[0], prolog, n) != n)
505                         goto Out;
506                 if((n = read(pout[0], buf, sizeof(buf)-1)) < 0)
507                         goto Out;
508                 buf[n] = 0;
509                 n = atoi(buf);
510                 if(n <= 0){
511                         werrstr("no pages");
512                         goto Out;
513                 }
514                 gs = mallocz(sizeof(*gs), 1);
515                 gs->pin = pin[0];
516                 gs->pout = pout[0];
517                 gs->pdat = pdat[0];
518                 for(i=1; i<=n; i++){
519                         snprint(nam, sizeof nam, "%d", i);
520                         addpage(p, nam, popenpdf, gs, -1);
521                 }
522
523                 /* keep ghostscript arround */
524                 return -1;
525         } else {
526                 i = 0;
527                 ofd = -1;
528                 while((n = read(pdat[0], buf, sizeof(buf))) >= 0){
529                         if(ofd >= 0 && (n <= 0 || infernobithdr(buf, n))){
530                                 snprint(nam, sizeof nam, "%d", i);
531                                 addpage(p, nam, popenimg, nil, ofd);
532                                 ofd = -1;
533                         }
534                         if(n <= 0)
535                                 break;
536                         if(ofd < 0){
537                                 snprint(nam, sizeof nam, "%.4d", ++i);
538                                 if((ofd = createtmp(nam)) < 0)
539                                         ofd = dup(nullfd, -1);
540                         }
541                         if(write(ofd, buf, n) != n)
542                                 break;
543                 }
544                 if(ofd >= 0)
545                         close(ofd);
546         }
547 Out:
548         close(pin[0]);
549         close(pout[0]);
550         close(pdat[0]);
551         return -1;
552 }
553
554 int
555 filetype(char *buf, int nbuf, char *typ, int ntyp)
556 {
557         int n, ifd[2], ofd[2];
558         char *argv[3];
559
560         if(infernobithdr(buf, nbuf)){
561                 strncpy(typ, "image/p9bit", ntyp);
562                 return 0;
563         }
564
565         typ[0] = 0;
566         if(pipe(ifd) < 0)
567                 return -1;
568         if(pipe(ofd) < 0){
569                 close(ifd[0]);
570                 close(ifd[1]);
571                 return -1;
572         }
573         if(rfork(RFPROC|RFFDG|RFREND|RFNOWAIT) == 0){
574                 dupfds(ifd[1], ofd[1], 2, -1);
575                 argv[0] = "file";
576                 argv[1] = "-m";
577                 argv[2] = 0;
578                 exec("/bin/file", argv);
579         }
580         close(ifd[1]);
581         close(ofd[1]);
582         if(rfork(RFPROC|RFFDG|RFNOWAIT) == 0){
583                 dupfds(ifd[0], -1);
584                 write(0, buf, nbuf);
585                 exits(nil);
586         }
587         close(ifd[0]);
588         if((n = readn(ofd[0], typ, ntyp-1)) < 0)
589                 n = 0;
590         close(ofd[0]);
591         while(n > 0 && typ[n-1] == '\n')
592                 n--;
593         typ[n] = 0;
594         return 0;
595 }
596
597 int
598 dircmp(void *p1, void *p2)
599 {
600         Dir *d1, *d2;
601
602         d1 = p1;
603         d2 = p2;
604
605         return strcmp(d1->name, d2->name);
606 }
607
608 int
609 popenfile(Page *p)
610 {
611         static struct {
612                 char    *typ;
613                 void    *open;
614                 void    *data;
615         } tab[] = {
616         "application/pdf",              popengs,        nil,
617         "application/postscript",       popengs,        nil,
618         "application/troff",            popengs,        "lp -dstdout",
619         "text/plain",                   popengs,        "lp -dstdout",
620         "text/html",                    popengs,        "uhtml | html2ms | tbl | troff -ms | lp -dstdout",
621         "application/dvi",              popengs,        "dvips -Pps -r0 -q1 -f1",
622         "application/doc",              popengs,        "doc2ps",
623         "application/zip",              popentape,      "fs/zipfs",
624         "application/x-tar",            popentape,      "fs/tarfs",
625         "application/x-ustar",          popentape,      "fs/tarfs",
626         "application/x-compress",       popenfilter,    "uncompress",
627         "application/x-gzip",           popenfilter,    "gunzip",
628         "application/x-bzip2",          popenfilter,    "bunzip2",
629         "image/gif",                    popenimg,       "gif -t9",
630         "image/jpeg",                   popenimg,       "jpg -t9",
631         "image/png",                    popenimg,       "png -t9",
632         "image/ppm",                    popenimg,       "ppm -t9",
633         "image/bmp",                    popenimg,       "bmp -t9",
634         "image/p9bit",                  popenimg,       nil,
635         };
636
637         char buf[NBUF], typ[128], *file;
638         int i, n, fd, tfd;
639         Dir *d;
640
641         fd = p->fd;
642         p->fd = -1;
643         file = p->data;
644         p->data = nil;
645         if(fd < 0){
646                 if((fd = open(file, OREAD)) < 0){
647                 Err0:
648                         free(file);
649                         return -1;
650                 }
651         }
652         seek(fd, 0, 0);
653         if((d = dirfstat(fd)) == nil){
654         Err1:
655                 close(fd);
656                 goto Err0;
657         }
658         if(d->mode & DMDIR){
659                 free(d);
660                 d = nil;
661
662                 snprint(buf, sizeof(buf), "%s/META-INF/container.xml", file);
663                 if((tfd = open(buf, OREAD)) >= 0){
664                         close(fd);
665                         p->fd = tfd;
666                         p->data = file;
667                         p->open = popenepub;
668                         return p->open(p);
669                 }
670
671                 if((n = dirreadall(fd, &d)) < 0)
672                         goto Err1;
673                 qsort(d, n, sizeof d[0], dircmp);
674                 for(i = 0; i<n; i++)
675                         addpage(p, d[i].name, popenfile, smprint("%s/%s", file, d[i].name), -1);
676                 free(d);
677                 goto Err1;
678         }
679         free(d);
680
681         memset(buf, 0, NBUF/2);
682         if((n = readn(fd, buf, NBUF/2)) <= 0)
683                 goto Err1;
684         filetype(buf, n, typ, sizeof(typ));
685         for(i=0; i<nelem(tab); i++)
686                 if(strncmp(typ, tab[i].typ, strlen(tab[i].typ)) == 0)
687                         break;
688         if(i == nelem(tab)){
689                 werrstr("unknown image format: %s", typ);
690                 goto Err1;
691         }
692         p->fd = fd;
693         p->data = tab[i].data;
694         p->open = tab[i].open;
695         if(seek(fd, 0, 0) < 0)
696                 goto Noseek;
697         if((i = read(fd, buf+n, n)) < 0)
698                 goto Err1;
699         if(i != n || memcmp(buf, buf+n, i)){
700                 n += i;
701         Noseek:
702                 if((tfd = createtmp("file")) < 0)
703                         goto Err1;
704                 while(n > 0){
705                         if(write(tfd, buf, n) != n)
706                                 goto Err2;
707                         if((n = read(fd, buf, sizeof(buf))) < 0)
708                                 goto Err2;
709                 }
710                 if(dup(tfd, fd) < 0){
711                 Err2:
712                         close(tfd);
713                         goto Err1;
714                 }
715                 close(tfd);
716         }
717         free(file);
718         return p->open(p);
719 }
720
721 Page*
722 nextpage(Page *p)
723 {
724         if(p){
725                 if(p->down)
726                         return p->down;
727                 if(p->next)
728                         return p->next;
729                 if(p->up)
730                         return p->up->next;
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                         p->image = readimage(display, fd, 1);
776                         close(fd);
777                 }
778                 if(p->image == nil)
779                         p->open = nil;
780         }
781         p->gen = pagegen;
782 }
783
784 void
785 unloadpage(Page *p)
786 {
787         if(p->open == nil || p->image == nil)
788                 return;
789         lockdisplay(display);
790         freeimage(p->image);
791         unlockdisplay(display);
792         p->image = nil;
793 }
794
795 void
796 unloadpages(int age)
797 {
798         Page *p;
799
800         for(p = root->down; p; p = nextpage(p)){
801                 if(age == 0)    /* synchronous flush */
802                         qlock(p);
803                 else if(!canqlock(p))
804                         continue;
805                 if((pagegen - p->gen) >= age)
806                         unloadpage(p);
807                 qunlock(p);
808         }
809 }
810
811 void
812 loadpages(Page *p, int ahead, int oviewgen)
813 {
814         int i;
815
816         ahead++;        /* load at least one */
817         unloadpages(ahead*2);
818         for(i = 0; i < ahead && p; p = nextpage(p), i++){
819                 if(viewgen != oviewgen)
820                         break;
821                 if(canqlock(p)){
822                         loadpage(p);
823                         if(viewgen != oviewgen){
824                                 unloadpage(p);
825                                 qunlock(p);
826                                 break;
827                         }
828                         if(p == current){
829                                 Point size;
830
831                                 esetcursor(nil);
832                                 size = pagesize(p);
833                                 if(size.x && size.y && newwin){
834                                         newwin = 0;
835                                         resizewin(size);
836                                 }
837                                 lockdisplay(display);
838                                 drawpage(p);
839                                 unlockdisplay(display);
840                         }
841                         qunlock(p);
842                 }
843         }
844 }
845
846 /*
847  * A draw operation that touches only the area contained in bot but not in top.
848  * mp and sp get aligned with bot.min.
849  */
850 static void
851 gendrawdiff(Image *dst, Rectangle bot, Rectangle top, 
852         Image *src, Point sp, Image *mask, Point mp, int op)
853 {
854         Rectangle r;
855         Point origin;
856         Point delta;
857
858         USED(op);
859
860         if(Dx(bot)*Dy(bot) == 0)
861                 return;
862
863         /* no points in bot - top */
864         if(rectinrect(bot, top))
865                 return;
866
867         /* bot - top â‰¡ bot */
868         if(Dx(top)*Dy(top)==0 || rectXrect(bot, top)==0){
869                 gendrawop(dst, bot, src, sp, mask, mp, op);
870                 return;
871         }
872
873         origin = bot.min;
874         /* split bot into rectangles that don't intersect top */
875         /* left side */
876         if(bot.min.x < top.min.x){
877                 r = Rect(bot.min.x, bot.min.y, top.min.x, bot.max.y);
878                 delta = subpt(r.min, origin);
879                 gendrawop(dst, r, src, addpt(sp, delta), mask, addpt(mp, delta), op);
880                 bot.min.x = top.min.x;
881         }
882
883         /* right side */
884         if(bot.max.x > top.max.x){
885                 r = Rect(top.max.x, bot.min.y, bot.max.x, bot.max.y);
886                 delta = subpt(r.min, origin);
887                 gendrawop(dst, r, src, addpt(sp, delta), mask, addpt(mp, delta), op);
888                 bot.max.x = top.max.x;
889         }
890
891         /* top */
892         if(bot.min.y < top.min.y){
893                 r = Rect(bot.min.x, bot.min.y, bot.max.x, top.min.y);
894                 delta = subpt(r.min, origin);
895                 gendrawop(dst, r, src, addpt(sp, delta), mask, addpt(mp, delta), op);
896                 bot.min.y = top.min.y;
897         }
898
899         /* bottom */
900         if(bot.max.y > top.max.y){
901                 r = Rect(bot.min.x, top.max.y, bot.max.x, bot.max.y);
902                 delta = subpt(r.min, origin);
903                 gendrawop(dst, r, src, addpt(sp, delta), mask, addpt(mp, delta), op);
904                 bot.max.y = top.max.y;
905         }
906 }
907
908 void
909 zoomdraw(Image *d, Rectangle r, Rectangle top, Image *s, Point sp, int f)
910 {
911         int w, x, y;
912         Image *t;
913         Point a;
914
915         if(f <= 1){
916                 gendrawdiff(d, r, top, paper, sp, nil, ZP, SoverD);
917                 gendrawdiff(d, r, top, s, sp, nil, ZP, SoverD);
918                 return;
919         }
920         a = ZP;
921         if(r.min.x < d->r.min.x){
922                 sp.x += (d->r.min.x - r.min.x)/f;
923                 a.x = (d->r.min.x - r.min.x)%f;
924                 r.min.x = d->r.min.x;
925         }
926         if(r.min.y < d->r.min.y){
927                 sp.y += (d->r.min.y - r.min.y)/f;
928                 a.y = (d->r.min.y - r.min.y)%f;
929                 r.min.y = d->r.min.y;
930         }
931         rectclip(&r, d->r);
932         w = s->r.max.x - sp.x;
933         if(w > Dx(r))
934                 w = Dx(r);
935         t = allocimage(display, Rect(r.min.x, r.min.y, r.min.x+w, r.max.y), s->chan, 0, 0);
936         if(t == nil)
937                 return;
938         for(y=r.min.y; y<r.max.y; y++){
939                 draw(t, Rect(r.min.x, y, r.min.x+w, y+1), s, nil, sp);
940                 if(++a.y == f){
941                         a.y = 0;
942                         sp.y++;
943                 }
944         }
945         x=r.min.x;
946         for(sp=r.min; x<r.max.x; sp.x++){
947                 gendrawdiff(d, Rect(x, r.min.y, x+1, r.max.y), top, paper, sp, nil, ZP, SoverD);
948                 gendrawdiff(d, Rect(x, r.min.y, x+1, r.max.y), top, t, sp, nil, ZP, SoverD);
949                 for(x++; ++a.x<f && x<r.max.x; x++)
950                         gendrawdiff(d, Rect(x, r.min.y, x+1, r.max.y), top, d, 
951                                 Pt(x-1, r.min.y), nil, ZP, SoverD);
952                 a.x = 0;
953         }
954         freeimage(t);
955 }
956
957 Point
958 pagesize(Page *p)
959 {
960         return p->image ? mulpt(subpt(p->image->r.max, p->image->r.min), zoom) : ZP;
961 }
962
963 void
964 drawframe(Rectangle r)
965 {
966         border(screen, r, -Borderwidth, frame, ZP);
967         gendrawdiff(screen, screen->r, insetrect(r, -Borderwidth), ground, ZP, nil, ZP, SoverD);
968         flushimage(display, 1);
969 }
970
971 void
972 drawpage(Page *p)
973 {
974         Rectangle r;
975         Image *i;
976
977         if(i = p->image){
978                 r = rectaddpt(Rpt(ZP, pagesize(p)), addpt(pos, screen->r.min));
979                 zoomdraw(screen, r, ZR, i, i->r.min, zoom);
980         } else {
981                 r = Rpt(ZP, stringsize(font, p->label));
982                 r = rectaddpt(r, addpt(subpt(divpt(subpt(screen->r.max, screen->r.min), 2),
983                         divpt(r.max, 2)), screen->r.min));
984                 draw(screen, r, paper, nil, ZP);
985                 string(screen, r.min, display->black, ZP, font, p->label);
986         }
987         drawframe(r);
988 }
989
990 void
991 translate(Page *p, Point d)
992 {
993         Rectangle r, nr;
994         Image *i;
995
996         i = p->image;
997         if(i==0 || d.x==0 && d.y==0)
998                 return;
999         r = rectaddpt(Rpt(ZP, pagesize(p)), addpt(pos, screen->r.min));
1000         pos = addpt(pos, d);
1001         nr = rectaddpt(r, d);
1002         rectclip(&r, screen->r);
1003         draw(screen, rectaddpt(r, d), screen, nil, r.min);
1004         zoomdraw(screen, nr, rectaddpt(r, d), i, i->r.min, zoom);
1005         drawframe(nr);
1006 }
1007
1008 Page*
1009 findpage(char *name)
1010 {
1011         Page *p;
1012         int n;
1013
1014         n = strlen(name);
1015         /* look in current document first */
1016         if(current && current->up){
1017                 for(p = current->up->down; p; p = p->next)
1018                         if(cistrncmp(p->label, name, n) == 0)
1019                                 return p;
1020         }
1021         /* look everywhere */
1022         for(p = root->down; p; p = nextpage(p))
1023                 if(cistrncmp(p->label, name, n) == 0)
1024                         return p;
1025         return nil;
1026 }
1027
1028 Page*
1029 pageat(int i)
1030 {
1031         Page *p;
1032
1033         for(p = root->down; i > 0 && p; p = nextpage(p))
1034                 i--;
1035         return i ? nil : p;
1036 }
1037
1038 int
1039 pageindex(Page *x)
1040 {
1041         Page *p;
1042         int i;
1043
1044         for(i = 0, p = root->down; p && p != x; p = nextpage(p))
1045                 i++;
1046         return (p == x) ? i : -1;
1047 }
1048
1049 char*
1050 pagemenugen(int i)
1051 {
1052         Page *p;
1053         if(p = pageat(i))
1054                 return p->label;
1055         return nil;
1056 }
1057
1058 void
1059 showpage(Page *p)
1060 {
1061         static int nproc;
1062         int oviewgen;
1063
1064         if(p == nil)
1065                 return;
1066         esetcursor(&reading);
1067         current = p;
1068         oviewgen = viewgen;
1069         if(++nproc > NPROC)
1070                 if(waitpid() > 0)
1071                         nproc--;
1072         switch(rfork(RFPROC|RFMEM)){
1073         case -1:
1074                 sysfatal("rfork: %r");
1075         case 0:
1076                 loadpages(p, NAHEAD, oviewgen);
1077                 exits(nil);
1078         }
1079 }
1080
1081 void
1082 shownext(void)
1083 {
1084         Page *p;
1085
1086         for(p = nextpage(current); p; p = nextpage(p))
1087                 if(p->image || p->open)
1088                         break;
1089         showpage(p);
1090 }
1091
1092 void
1093 showprev(void)
1094 {
1095         Page *p;
1096
1097         for(p = prevpage(current); p; p = prevpage(p))
1098                 if(p->image || p->open)
1099                         break;
1100         showpage(p);
1101 }
1102
1103 void
1104 zerox(Page *p)
1105 {
1106         char nam[64], *argv[4];
1107         int fd;
1108
1109         if(p == nil)
1110                 return;
1111         esetcursor(&reading);
1112         qlock(p);
1113         if((fd = openpage(p)) < 0)
1114                 goto Out;
1115         if(rfork(RFPROC|RFFDG|RFREND|RFENVG|RFNOTEG|RFNOWAIT) == 0){
1116                 dupfds(fd, 1, 2, -1);
1117                 snprint(nam, sizeof nam, "/bin/%s", argv0);
1118                 argv[0] = argv0;
1119                 argv[1] = "-w";
1120                 argv[2] = nil;
1121                 exec(nam, argv);
1122                 sysfatal("exec: %r");
1123         }
1124         close(fd);
1125 Out:
1126         qunlock(p);
1127         esetcursor(nil);
1128 }
1129
1130 void
1131 eresized(int new)
1132 {
1133         Page *p;
1134
1135         lockdisplay(display);
1136         if(new && getwindow(display, Refnone) == -1)
1137                 sysfatal("getwindow: %r");
1138         if(p = current){
1139                 if(canqlock(p)){
1140                         drawpage(p);
1141                         qunlock(p);
1142                 }
1143         }
1144         unlockdisplay(display);
1145 }
1146
1147 void killcohort(void)
1148 {
1149         int i;
1150         for(i=0;i!=3;i++){      /* It's a long way to the kitchen */
1151                 postnote(PNGROUP, getpid(), "kill");
1152                 sleep(1);
1153         }
1154 }
1155
1156 void drawerr(Display *, char *msg)
1157 {
1158         sysfatal("draw: %s", msg);
1159 }
1160
1161 void
1162 usage(void)
1163 {
1164         fprint(2, "usage: %s [ -iRw ] [ -p ppi ] [ file ... ]\n", argv0);
1165         exits("usage");
1166 }
1167
1168 void
1169 main(int argc, char *argv[])
1170 {
1171         enum { Eplumb = 4 };
1172         char jump[32];
1173         Plumbmsg *pm;
1174         Point o;
1175         Mouse m;
1176         Event e;
1177         char *s;
1178         int i;
1179
1180         ARGBEGIN {
1181         case 'a':
1182         case 'v':
1183         case 'V':
1184         case 'P':
1185                 break;
1186         case 'R':
1187                 newwin = -1;
1188                 break;
1189         case 'w':
1190                 newwin = 1;
1191                 break;
1192         case 'i':
1193                 imode = 1;
1194                 break;
1195         case 'p':
1196                 ppi = atoi(EARGF(usage()));
1197                 break;
1198         default:
1199                 usage();
1200         } ARGEND;
1201
1202         /*
1203          * so that we can stop all subprocesses with a note,
1204          * and to isolate rendezvous from other processes
1205          */
1206         rfork(RFNOTEG|RFNAMEG|RFREND);
1207         atexit(killcohort);
1208         atnotify(catchnote, 1);
1209         if(newwin > 0){
1210                 s = smprint("-pid %d", getpid());
1211                 if(newwindow(s) < 0)
1212                         sysfatal("newwindow: %r");
1213                 free(s);
1214         }
1215         initdraw(drawerr, nil, argv0);
1216         paper = display->white;
1217         frame = display->black;
1218         ground = allocimage(display, Rect(0,0,1,1), screen->chan, 1, 0x777777FF);
1219         display->locking = 1;
1220         unlockdisplay(display);
1221         einit(Ekeyboard|Emouse);
1222         eplumb(Eplumb, "image");
1223         if((nullfd = open("/dev/null", ORDWR)) < 0)
1224                 sysfatal("open: %r");
1225         current = root = addpage(nil, "root", nil, nil, -1);
1226
1227         if(*argv == nil && !imode)
1228                 addpage(root, "stdin", popenfile, strdup("/fd/0"), -1);
1229         for(; *argv; argv++)
1230                 addpage(root, shortname(*argv), popenfile, strdup(*argv), -1);
1231
1232         jump[0] = 0;
1233         for(;;){
1234                 i=event(&e);
1235                 switch(i){
1236                 case Emouse:
1237                         lockdisplay(display);
1238                         m = e.mouse;
1239                         if(m.buttons & 1){
1240                                 if(current == nil || !canqlock(current))
1241                                         goto Unlock;
1242                                 for(;;) {
1243                                         o = m.xy;
1244                                         m = emouse();
1245                                         if((m.buttons & 1) == 0)
1246                                                 break;
1247                                         translate(current, subpt(m.xy, o));
1248                                 }
1249                                 qunlock(current);
1250                                 goto Unlock;
1251                         }
1252                         if(m.buttons & 2){
1253                                 o = m.xy;
1254                                 i = emenuhit(2, &m, &menu);
1255                                 if(i < 0 || i >= nelem(menuitems) || menuitems[i]==nil)
1256                                         goto Unlock;
1257                                 s = menuitems[i];
1258                                 if(strcmp(s, "orig size")==0){
1259                                         pos = ZP;
1260                                         zoom = 1;
1261                                         resize = ZP;
1262                                         rotate = 0;
1263                                 Unload:
1264                                         viewgen++;
1265                                         unlockdisplay(display);
1266                                         esetcursor(&reading);
1267                                         unloadpages(0);
1268                                         showpage(current);
1269                                         continue;
1270                                 }
1271                                 if(strncmp(s, "rotate ", 7)==0){
1272                                         rotate += atoi(s+7);
1273                                         rotate %= 360;
1274                                         goto Unload;
1275                                 }
1276                                 if(strcmp(s, "upside down")==0){
1277                                         rotate += 180;
1278                                         goto Unload;
1279                                 }
1280                                 if(strcmp(s, "fit width")==0){
1281                                         pos = ZP;
1282                                         zoom = 1;
1283                                         resize = subpt(screen->r.max, screen->r.min);
1284                                         resize.y = 0;
1285                                         goto Unload;
1286                                 }
1287                                 if(strcmp(s, "fit height")==0){
1288                                         pos = ZP;
1289                                         zoom = 1;
1290                                         resize = subpt(screen->r.max, screen->r.min);
1291                                         resize.x = 0;
1292                                         goto Unload;
1293                                 }
1294                                 if(strncmp(s, "zoom", 4)==0){
1295                                         if(current && canqlock(current)){
1296                                                 o = subpt(o, screen->r.min);
1297                                                 if(strstr(s, "in")){
1298                                                         if(zoom < 0x40000000){
1299                                                                 zoom *= 2;
1300                                                                 pos =  addpt(mulpt(subpt(pos, o), 2), o);
1301                                                         }
1302                                                 }else{
1303                                                         if(zoom > 1){
1304                                                                 zoom /= 2;
1305                                                                 pos =  addpt(divpt(subpt(pos, o), 2), o);
1306                                                         }
1307                                                 }
1308                                                 drawpage(current);
1309                                                 qunlock(current);
1310                                         }
1311                                 }
1312                                 unlockdisplay(display);
1313                                 if(strcmp(s, "next")==0)
1314                                         shownext();
1315                                 if(strcmp(s, "prev")==0)
1316                                         showprev();
1317                                 if(strcmp(s, "zerox")==0)
1318                                         zerox(current);
1319                                 if(strcmp(s, "quit")==0)
1320                                         exits(0);
1321                                 continue;
1322                         }
1323                         if(m.buttons & 4){
1324                                 if(root->down == nil)
1325                                         goto Unlock;
1326                                 pagemenu.lasthit = pageindex(current);
1327                                 i = emenuhit(3, &m, &pagemenu);
1328                                 unlockdisplay(display);
1329                                 if(i != -1)
1330                                         showpage(pageat(i));
1331                                 continue;
1332                         }
1333                 Unlock:
1334                         unlockdisplay(display);
1335                         break;
1336                 case Ekeyboard:
1337                         switch(e.kbdc){
1338                         case 'q':
1339                         case Kdel:
1340                         case Keof:
1341                                 exits(0);
1342                         case Kup:
1343                                 if(current == nil || !canqlock(current))
1344                                         break;
1345                                 lockdisplay(display);
1346                                 if(pos.y < 0){
1347                                         translate(current, Pt(0, Dy(screen->r)/2));
1348                                         unlockdisplay(display);
1349                                         qunlock(current);
1350                                         continue;
1351                                 }
1352                                 unlockdisplay(display);
1353                                 qunlock(current);
1354                                 if(prevpage(current))
1355                                         pos.y = 0;
1356                         case '-':
1357                         case Kbs:
1358                         case Kleft:
1359                                 showprev();
1360                                 break;
1361                         case Kdown:
1362                                 if(current == nil || !canqlock(current))
1363                                         break;
1364                                 o = addpt(pos, pagesize(current));
1365                                 lockdisplay(display);
1366                                 if(o.y > Dy(screen->r)){
1367                                         translate(current, Pt(0, -Dy(screen->r)/2));
1368                                         unlockdisplay(display);
1369                                         qunlock(current);
1370                                         continue;
1371                                 }
1372                                 unlockdisplay(display);
1373                                 qunlock(current);
1374                                 if(nextpage(current))
1375                                         pos.y = 0;
1376                         case '\n':
1377                                 if(jump[0]){
1378                                         showpage(findpage(jump));
1379                                         jump[0] = 0;
1380                                         break;
1381                                 }
1382                         case ' ':
1383                         case Kright:
1384                                 shownext();
1385                                 break;
1386                         default:
1387                                 i = strlen(jump);
1388                                 if(i+1 < sizeof(jump)){
1389                                         jump[i] = e.kbdc;
1390                                         jump[i+1] = 0;
1391                                 }
1392                         }
1393                         break;
1394                 case Eplumb:
1395                         pm = e.v;
1396                         if(pm && pm->ndata > 0){
1397                                 int fd;
1398
1399                                 fd = -1;
1400                                 s = plumblookup(pm->attr, "action");
1401                                 if(s && strcmp(s, "quit")==0)
1402                                         exits(0);
1403                                 if(s && strcmp(s, "showdata")==0){
1404                                         if((fd = createtmp("plumb")) < 0){
1405                                                 fprint(2, "plumb: createtmp: %r\n");
1406                                                 goto Plumbfree;
1407                                         }
1408                                         s = malloc(NPATH);
1409                                         if(fd2path(fd, s, NPATH) < 0){
1410                                                 close(fd);
1411                                                 goto Plumbfree;
1412                                         }
1413                                         write(fd, pm->data, pm->ndata);
1414                                 }else if(pm->data[0] == '/'){
1415                                         s = strdup(pm->data);
1416                                 }else{
1417                                         s = malloc(strlen(pm->wdir)+1+pm->ndata+1);
1418                                         sprint(s, "%s/%s", pm->wdir, pm->data);
1419                                         cleanname(s);
1420                                 }
1421                                 showpage(addpage(root, shortname(s), popenfile, s, fd));
1422                         }
1423                 Plumbfree:
1424                         plumbfree(pm);
1425                         break;
1426                 }
1427         }
1428 }