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