]> git.lizzy.rs Git - plan9front.git/blobdiff - sys/src/cmd/page.c
kernel: keep segment locked for data2txt
[plan9front.git] / sys / src / cmd / page.c
index eb2e761c133b41386d57ada76f884e93725b389f..520ab8f701427c5304217364f0817463ed6f5ad0 100644 (file)
@@ -14,7 +14,6 @@ struct Page {
        void    *data;
        int     (*open)(Page *);
 
-       char    *text;
        Image   *image;
        int     fd;
        int     gen;
@@ -36,7 +35,8 @@ Point resize, pos;
 Page *root, *current;
 QLock pagelock;
 int nullfd;
-Image *background;
+
+Image *frame, *paper, *ground;
 
 char pagespool[] = "/tmp/pagespool.";
 
@@ -47,36 +47,57 @@ enum {
        NPATH = 1024,
 };
 
-char *pagemenugen(int i);
+enum {
+       Corigsize,
+       Czoomin,
+       Czoomout,
+       Cfitwidth,
+       Cfitheight,
+       Crotate90,
+       Cupsidedown,
+       Cdummy1,
+       Cnext,
+       Cprev,
+       Czerox,
+       Cwrite,
+       Cdummy2,
+       Cquit,
+};
 
-char *menuitems[] = {
-       "orig size",
-       "rotate 90",
-       "upside down",
-       "",
-       "fit width",
-       "fit height",
-       "",
-       "zoom in",
-       "zoom out",
-       "",
-       "next",
-       "prev",
-       "zerox",
-       "",
-       "quit",
-       nil
+struct {
+       char    *m;
+       Rune    k1;
+       Rune    k2;
+       Rune    k3;
+} cmds[] = {
+       [Corigsize]     "orig size",    'o', Kesc, 0,
+       [Czoomin]       "zoom in",      '+', 0, 0,
+       [Czoomout]      "zoom out",     '-', 0, 0,
+       [Cfitwidth]     "fit width",    'f', 0, 0,
+       [Cfitheight]    "fit height",   'h', 0, 0,
+       [Crotate90]     "rotate 90",    'r', 0, 0,
+       [Cupsidedown]   "upside down",  'u', 0, 0,
+       [Cdummy1]       "",             0, 0, 0,
+       [Cnext]         "next",         Kright, ' ', '\n', 
+       [Cprev]         "prev",         Kleft, Kbs, 0,
+       [Czerox]        "zerox",        'z', 0, 0,
+       [Cwrite]        "write",        'w', 0, 0,
+       [Cdummy2]       "",             0, 0, 0,
+       [Cquit]         "quit",         'q', Kdel, Keof,
 };
 
+char *pagemenugen(int i);
+char *cmdmenugen(int i);
+
 Menu pagemenu = {
        nil,
        pagemenugen,
        -1,
 };
 
-Menu menu = {
-       menuitems,
+Menu cmdmenu = {
        nil,
+       cmdmenugen,
        -1,
 };
 
@@ -104,7 +125,6 @@ addpage(Page *up, char *label, int (*popen)(Page *), void *pdata, int fd)
        p = mallocz(sizeof(*p), 1);
        p->label = strdup(label);
        p->gen = pagegen;
-       p->text = nil;
        p->image = nil;
        p->data = pdata;
        p->open = popen;
@@ -144,11 +164,11 @@ resizewin(Point size)
 }
 
 int
-createtmp(ulong id, char *pfx)
+createtmp(char *pfx)
 {
+       static ulong id = 1;
        char nam[64];
-
-       snprint(nam, sizeof nam, "%s%s%.12d%.8lux", pagespool, pfx, getpid(), id);
+       snprint(nam, sizeof nam, "%s%s%.12d%.8lux", pagespool, pfx, getpid(), id++);
        return create(nam, OEXCL|ORCLOSE|ORDWR, 0600);
 }
 
@@ -164,6 +184,32 @@ catchnote(void *, char *msg)
        return 0;
 }
 
+void
+dupfds(int fd, ...)
+{
+       int mfd, n, i;
+       va_list arg;
+       Dir *dir;
+
+       va_start(arg, fd);
+       for(mfd = 0; fd >= 0; fd = va_arg(arg, int), mfd++)
+               if(fd != mfd)
+                       if(dup(fd, mfd) < 0)
+                               sysfatal("dup: %r");
+       va_end(arg);
+       if((fd = open("/fd", OREAD)) < 0)
+               sysfatal("open: %r");
+       n = dirreadall(fd, &dir);
+       for(i=0; i<n; i++){
+               if(strstr(dir[i].name, "ctl"))
+                       continue;
+               fd = atoi(dir[i].name);
+               if(fd >= mfd)
+                       close(fd);
+       }
+       free(dir);
+}
+
 void
 pipeline(int fd, char *fmt, ...)
 {
@@ -176,23 +222,16 @@ pipeline(int fd, char *fmt, ...)
                dup(nullfd, fd);
                return;
        }
-       switch(rfork(RFPROC|RFFDG|RFREND|RFNOWAIT)){
+       va_start(arg, fmt);
+       vsnprint(buf, sizeof buf, fmt, arg);
+       va_end(arg);
+       switch(rfork(RFPROC|RFMEM|RFFDG|RFREND|RFNOWAIT)){
        case -1:
                close(pfd[0]);
                close(pfd[1]);
                goto Err;
        case 0:
-               if(dup(fd, 0)<0)
-                       exits("dup");
-               if(dup(pfd[1], 1)<0)
-                       exits("dup");
-               close(fd);
-               close(pfd[1]);
-               close(pfd[0]);
-               va_start(arg, fmt);
-               vsnprint(buf, sizeof buf, fmt, arg);
-               va_end(arg);
-
+               dupfds(fd, pfd[1], 2, -1);
                argv[0] = "rc";
                argv[1] = "-c";
                argv[2] = buf;
@@ -219,7 +258,7 @@ int
 popenfile(Page*);
 
 int
-popenconv(Page *p)
+popenimg(Page *p)
 {
        char nam[NPATH];
        int fd;
@@ -249,6 +288,18 @@ popenconv(Page *p)
        return fd;
 }
 
+int
+popenfilter(Page *p)
+{
+       seek(p->fd, 0, 0);
+       if(p->data){
+               pipeline(p->fd, "%s", (char*)p->data);
+               p->data = nil;
+       }
+       p->open = popenfile;
+       return p->open(p);
+}
+
 int
 popentape(Page *p)
 {
@@ -256,17 +307,16 @@ popentape(Page *p)
 
        seek(p->fd, 0, 0);
        snprint(mnt, sizeof(mnt), "/n/tapefs.%.12d%.8lux", getpid(), (ulong)p);
-       switch(rfork(RFREND|RFPROC|RFFDG)){
+       snprint(cmd, sizeof(cmd), "%s -m %s /fd/0", p->data, mnt);
+       switch(rfork(RFPROC|RFMEM|RFFDG|RFREND)){
        case -1:
                close(p->fd);
                p->fd = -1;
                return -1;
        case 0:
-               dup(p->fd, 0);
-               close(p->fd);
+               dupfds(p->fd, 1, 2, -1);
                argv[0] = "rc";
                argv[1] = "-c";
-               snprint(cmd, sizeof(cmd), "%s -m %s /fd/0", p->data, mnt);
                argv[2] = cmd;
                argv[3] = nil;
                exec("/bin/rc", argv);
@@ -323,7 +373,6 @@ popenepub(Page *p)
                addpage(p, shortname(buf), popenfile, strdup(buf), -1);
        }
        close(fd);
-       p->text = strdup(p->label);
        return -1;
 }
 
@@ -346,29 +395,24 @@ popenpdf(Page *p)
 
        if(pipe(pfd) < 0)
                return -1;
-       switch(rfork(RFFDG|RFPROC|RFMEM|RFNOWAIT)){
+       switch(rfork(RFPROC|RFMEM|RFFDG|RFNOWAIT)){
        case -1:
                close(pfd[0]);
                close(pfd[1]);
                return -1;
        case 0:
-               close(pfd[0]);
                gs = p->data;
                qlock(gs);
-               fprint(gs->pin, "%s DoPDFPage\n"
+               dupfds(gs->pdat, gs->pin, pfd[1], -1);
+               fprint(1, "%s DoPDFPage\n"
                        "(/fd/3) (w) file "
                        "dup flushfile "
                        "dup (THIS IS NOT AN INFERNO BITMAP\\n) writestring "
                        "flushfile\n", p->label);
-               while((n = read(gs->pdat, buf, sizeof buf)) > 0){
+               while((n = read(0, buf, sizeof buf)) > 0){
                        if(memcmp(buf, "THIS IS NOT AN INFERNO BITMAP\n", 30) == 0)
                                break;
-                       if(pfd[1] < 0)
-                               continue;
-                       if(write(pfd[1], buf, n) != n){
-                               close(pfd[1]);
-                               pfd[1]=-1;
-                       }
+                       write(2, buf, n);
                }
                qunlock(gs);
                exits(nil);
@@ -408,7 +452,6 @@ popengs(Page *p)
        seek(ifd, 0, 0);
        if(memcmp(buf, "%PDF-", 5) == 0)
                pdf = 1;
-       p->text = strdup(p->label);
        if(pipe(pin) < 0){
        Err0:
                close(ifd);
@@ -427,39 +470,17 @@ popengs(Page *p)
                goto Err1;
        }
 
-       switch(rfork(RFREND|RFPROC|RFFDG|RFNOWAIT)){
+       argv[0] = p->data;
+       switch(rfork(RFPROC|RFMEM|RFFDG|RFREND|RFNOWAIT)){
        case -1:
                goto Err2;
        case 0:
-               if(pdf){
-                       if(dup(pin[1], 0)<0)
-                               exits("dup");
-                       if(dup(pout[1], 1)<0)
-                               exits("dup");
-               } else {
-                       if(dup(nullfd, 0)<0)
-                               exits("dup");
-                       if(dup(nullfd, 1)<0)
-                               exits("dup");
-               }
-               if(dup(nullfd, 2)<0)
-                       exits("dup");
-               if(dup(pdat[1], 3)<0)
-                       exits("dup");
-               if(dup(ifd, 4)<0)
-                       exits("dup");
-
-               close(pin[0]);
-               close(pin[1]);
-               close(pout[0]);
-               close(pout[1]);
-               close(pdat[0]);
-               close(pdat[1]);
-               close(ifd);
-
-               if(p->data)
-                       pipeline(4, "%s", (char*)p->data);
-
+               if(pdf)
+                       dupfds(pin[1], pout[1], 2, pdat[1], ifd, -1);
+               else
+                       dupfds(nullfd, nullfd, 2, pdat[1], ifd, -1);
+               if(argv[0])
+                       pipeline(4, "%s", argv[0]);
                argv[0] = "gs";
                argv[1] = "-q";
                argv[2] = "-sDEVICE=plan9";
@@ -529,14 +550,14 @@ popengs(Page *p)
                while((n = read(pdat[0], buf, sizeof(buf))) >= 0){
                        if(ofd >= 0 && (n <= 0 || infernobithdr(buf, n))){
                                snprint(nam, sizeof nam, "%d", i);
-                               addpage(p, nam, popenconv, nil, ofd);
+                               addpage(p, nam, popenimg, nil, ofd);
                                ofd = -1;
                        }
                        if(n <= 0)
                                break;
                        if(ofd < 0){
                                snprint(nam, sizeof nam, "%.4d", ++i);
-                               if((ofd = createtmp((ulong)p, nam)) < 0)
+                               if((ofd = createtmp(nam)) < 0)
                                        ofd = dup(nullfd, -1);
                        }
                        if(write(ofd, buf, n) != n)
@@ -552,6 +573,49 @@ Out:
        return -1;
 }
 
+int
+filetype(char *buf, int nbuf, char *typ, int ntyp)
+{
+       int n, ifd[2], ofd[2];
+       char *argv[3];
+
+       if(infernobithdr(buf, nbuf)){
+               strncpy(typ, "image/p9bit", ntyp);
+               return 0;
+       }
+
+       typ[0] = 0;
+       if(pipe(ifd) < 0)
+               return -1;
+       if(pipe(ofd) < 0){
+               close(ifd[0]);
+               close(ifd[1]);
+               return -1;
+       }
+       if(rfork(RFPROC|RFMEM|RFFDG|RFREND|RFNOWAIT) == 0){
+               dupfds(ifd[1], ofd[1], 2, -1);
+               argv[0] = "file";
+               argv[1] = "-m";
+               argv[2] = 0;
+               exec("/bin/file", argv);
+       }
+       close(ifd[1]);
+       close(ofd[1]);
+       if(rfork(RFPROC|RFMEM|RFFDG|RFNOWAIT) == 0){
+               dupfds(ifd[0], -1);
+               write(0, buf, nbuf);
+               exits(nil);
+       }
+       close(ifd[0]);
+       if((n = readn(ofd[0], typ, ntyp-1)) < 0)
+               n = 0;
+       close(ofd[0]);
+       while(n > 0 && typ[n-1] == '\n')
+               n--;
+       typ[n] = 0;
+       return 0;
+}
+
 int
 dircmp(void *p1, void *p2)
 {
@@ -566,17 +630,43 @@ dircmp(void *p1, void *p2)
 int
 popenfile(Page *p)
 {
-       char buf[NBUF], *file;
+       static struct {
+               char    *typ;
+               void    *open;
+               void    *data;
+       } tab[] = {
+       "application/pdf",              popengs,        nil,
+       "application/postscript",       popengs,        nil,
+       "application/troff",            popengs,        "lp -dstdout",
+       "text/plain",                   popengs,        "lp -dstdout",
+       "text/html",                    popengs,        "uhtml | html2ms | tbl | troff -ms | lp -dstdout",
+       "application/dvi",              popengs,        "dvips -Pps -r0 -q1 -f1",
+       "application/doc",              popengs,        "doc2ps",
+       "application/zip",              popentape,      "fs/zipfs",
+       "application/x-tar",            popentape,      "fs/tarfs",
+       "application/x-ustar",          popentape,      "fs/tarfs",
+       "application/x-compress",       popenfilter,    "uncompress",
+       "application/x-gzip",           popenfilter,    "gunzip",
+       "application/x-bzip2",          popenfilter,    "bunzip2",
+       "image/gif",                    popenimg,       "gif -t9",
+       "image/jpeg",                   popenimg,       "jpg -t9",
+       "image/png",                    popenimg,       "png -t9",
+       "image/ppm",                    popenimg,       "ppm -t9",
+       "image/bmp",                    popenimg,       "bmp -t9",
+       "image/p9bit",                  popenimg,       nil,
+       };
+
+       char buf[NBUF], typ[128], *file;
        int i, n, fd, tfd;
        Dir *d;
 
        fd = p->fd;
        p->fd = -1;
        file = p->data;
+       p->data = nil;
        if(fd < 0){
                if((fd = open(file, OREAD)) < 0){
                Err0:
-                       p->data = nil;
                        free(file);
                        return -1;
                }
@@ -595,6 +685,7 @@ popenfile(Page *p)
                if((tfd = open(buf, OREAD)) >= 0){
                        close(fd);
                        p->fd = tfd;
+                       p->data = file;
                        p->open = popenepub;
                        return p->open(p);
                }
@@ -605,82 +696,32 @@ popenfile(Page *p)
                for(i = 0; i<n; i++)
                        addpage(p, d[i].name, popenfile, smprint("%s/%s", file, d[i].name), -1);
                free(d);
-               p->text = strdup(p->label);
                goto Err1;
        }
        free(d);
 
-       memset(buf, 0, 32+1);
-       if((n = read(fd, buf, 32)) <= 0)
+       memset(buf, 0, NBUF/2);
+       if((n = readn(fd, buf, NBUF/2)) <= 0)
                goto Err1;
-
-       p->fd = fd;
-       p->data = nil;
-       p->open = popenconv;
-       if(memcmp(buf, "%PDF-", 5) == 0 || strstr(buf, "%!"))
-               p->open = popengs;
-       else if(memcmp(buf, "x T ", 4) == 0){
-               p->data = "lp -dstdout";
-               p->open = popengs;
-       }
-       else if(cistrncmp(buf, "<?xml", 5) == 0 ||
-               cistrncmp(buf, "<!DOCTYPE", 9) == 0 ||
-               cistrncmp(buf, "<HTML", 5) == 0){
-               p->data = "html2ms | troff -ms | lp -dstdout";
-               p->open = popengs;
-       }
-       else if(memcmp(buf, "\xF7\x02\x01\x83\x92\xC0\x1C;", 8) == 0){
-               p->data = "dvips -Pps -r0 -q1 -f1";
-               p->open = popengs;
-       }
-       else if(memcmp(buf, "\x1F\x8B", 2) == 0){
-               p->data = "gunzip";
-               p->open = popengs;
-       }
-       else if(memcmp(buf, "\xD0\xCF\x11\xE0\xA1\xB1\x1A\xE1", 8) == 0){
-               p->data = "doc2ps";
-               p->open = popengs;
-       }
-       else if(memcmp(buf, "PK\x03\x04", 4) == 0){
-               p->data = "fs/zipfs";
-               p->open = popentape;
-       }else if(memcmp(buf, "GIF", 3) == 0)
-               p->data = "gif -t9";
-       else if(memcmp(buf, "\111\111\052\000", 4) == 0) 
-               p->data = "fb/tiff2pic | fb/3to1 rgbv | fb/pcp -tplan9";
-       else if(memcmp(buf, "\115\115\000\052", 4) == 0)
-               p->data = "fb/tiff2pic | fb/3to1 rgbv | fb/pcp -tplan9";
-       else if(memcmp(buf, "\377\330\377", 3) == 0)
-               p->data = "jpg -t9";
-       else if(memcmp(buf, "\211PNG\r\n\032\n", 3) == 0)
-               p->data = "png -t9";
-       else if(memcmp(buf, "\0PC Research, Inc", 17) == 0)
-               p->data = "aux/g3p9bit -g";
-       else if(memcmp(buf, "TYPE=ccitt-g31", 14) == 0)
-               p->data = "aux/g3p9bit -g";
-       else if(memcmp(buf, "II*", 3) == 0)
-               p->data = "aux/g3p9bit -g";
-       else if(memcmp(buf, "TYPE=", 5) == 0)
-               p->data = "fb/3to1 rgbv |fb/pcp -tplan9";
-       else if(buf[0] == 'P' && '0' <= buf[1] && buf[1] <= '9')
-               p->data = "ppm -t9";
-       else if(memcmp(buf, "BM", 2) == 0)
-               p->data = "bmp -t9";
-       else if(infernobithdr(buf, n))
-               p->data = nil;
-       else {
-               werrstr("unknown image format");
+       filetype(buf, n, typ, sizeof(typ));
+       for(i=0; i<nelem(tab); i++)
+               if(strncmp(typ, tab[i].typ, strlen(tab[i].typ)) == 0)
+                       break;
+       if(i == nelem(tab)){
+               werrstr("unknown image format: %s", typ);
                goto Err1;
        }
-
+       p->fd = fd;
+       p->data = tab[i].data;
+       p->open = tab[i].open;
        if(seek(fd, 0, 0) < 0)
                goto Noseek;
-       if((i = read(fd, buf+n, n)) < 0)
+       if((i = readn(fd, buf+n, n)) < 0)
                goto Err1;
        if(i != n || memcmp(buf, buf+n, i)){
                n += i;
        Noseek:
-               if((tfd = createtmp((ulong)p, "file")) < 0)
+               if((tfd = createtmp("file")) < 0)
                        goto Err1;
                while(n > 0){
                        if(write(tfd, buf, n) != n)
@@ -702,13 +743,12 @@ popenfile(Page *p)
 Page*
 nextpage(Page *p)
 {
-       if(p){
-               if(p->down)
-                       return p->down;
+       if(p && p->down)
+               return p->down;
+       while(p){
                if(p->next)
                        return p->next;
-               if(p->up)
-                       return p->up->next;
+               p = p->up;
        }
        return nil;
 }
@@ -750,14 +790,15 @@ loadpage(Page *p)
 {
        int fd;
 
-       if(p->open && p->image == nil && p->text == nil){
+       if(p->open && p->image == nil){
                if((fd = openpage(p)) >= 0){
                        pagegen++;
-                       p->image = readimage(display, fd, 1);
+                       if((p->image = readimage(display, fd, 1)) == nil)
+                               fprint(2, "readimage: %r\n");
                        close(fd);
                }
-               if(p->image == nil && p->text == nil)
-                       p->text = smprint("%s: %r", p->label);
+               if(p->image == nil)
+                       p->open = nil;
        }
        p->gen = pagegen;
 }
@@ -765,17 +806,12 @@ loadpage(Page *p)
 void
 unloadpage(Page *p)
 {
-       if(p->open){
-               if(p->text)
-                       free(p->text);
-               p->text = nil;
-               if(p->image){
-                       lockdisplay(display);
-                       freeimage(p->image);
-                       unlockdisplay(display);
-               }
-               p->image = nil;
-       }
+       if(p->open == nil || p->image == nil)
+               return;
+       lockdisplay(display);
+       freeimage(p->image);
+       unlockdisplay(display);
+       p->image = nil;
 }
 
 void
@@ -841,8 +877,6 @@ gendrawdiff(Image *dst, Rectangle bot, Rectangle top,
        Point origin;
        Point delta;
 
-       USED(op);
-
        if(Dx(bot)*Dy(bot) == 0)
                return;
 
@@ -891,17 +925,23 @@ gendrawdiff(Image *dst, Rectangle bot, Rectangle top,
        }
 }
 
+int
+alphachan(ulong chan)
+{
+       for(; chan; chan >>= 8)
+               if(TYPE(chan) == CAlpha)
+                       return 1;
+       return 0;
+}
+
 void
-zoomdraw(Image *d, Rectangle r, Rectangle top, Image *s, Point sp, int f)
+zoomdraw(Image *d, Rectangle r, Rectangle top, Image *b, Image *s, Point sp, int f)
 {
-       int w, x, y;
+       Rectangle dr;
        Image *t;
        Point a;
+       int w;
 
-       if(f <= 1){
-               gendrawdiff(d, r, top, s, sp, nil, ZP, S);
-               return;
-       }
        a = ZP;
        if(r.min.x < d->r.min.x){
                sp.x += (d->r.min.x - r.min.x)/f;
@@ -917,23 +957,35 @@ zoomdraw(Image *d, Rectangle r, Rectangle top, Image *s, Point sp, int f)
        w = s->r.max.x - sp.x;
        if(w > Dx(r))
                w = Dx(r);
-       t = allocimage(display, Rect(r.min.x, r.min.y, r.min.x+w, r.max.y), s->chan, 0, DNofill);
-       if(t == nil)
+       dr = r;
+       dr.max.x = dr.min.x+w;
+       if(!alphachan(s->chan))
+               b = nil;
+       if(f <= 1){
+               if(b) gendrawdiff(d, dr, top, b, sp, nil, ZP, SoverD);
+               gendrawdiff(d, dr, top, s, sp, nil, ZP, SoverD);
                return;
-       for(y=r.min.y; y<r.max.y; y++){
-               draw(t, Rect(r.min.x, y, r.min.x+w, y+1), s, nil, sp);
-               if(++a.y == zoom){
+       }
+       if((t = allocimage(display, dr, s->chan, 0, 0)) == nil)
+               return;
+       for(; dr.min.y < r.max.y; dr.min.y++){
+               dr.max.y = dr.min.y+1;
+               draw(t, dr, s, nil, sp);
+               if(++a.y == f){
                        a.y = 0;
                        sp.y++;
                }
        }
-       sp = r.min;
-       for(x=r.min.x; x<r.max.x; x++){
-               gendrawdiff(d, Rect(x, r.min.y, x+1, r.max.y), top, t, sp, nil, ZP, S);
-               if(++a.x == f){
-                       a.x = 0;
-                       sp.x++;
+       dr = r;
+       for(sp=dr.min; dr.min.x < r.max.x; sp.x++){
+               dr.max.x = dr.min.x+1;
+               if(b) gendrawdiff(d, dr, top, b, sp, nil, ZP, SoverD);
+               gendrawdiff(d, dr, top, t, sp, nil, ZP, SoverD);
+               for(dr.min.x++; ++a.x < f && dr.min.x < r.max.x; dr.min.x++){
+                       dr.max.x = dr.min.x+1;
+                       gendrawdiff(d, dr, top, d, Pt(dr.min.x-1, dr.min.y), nil, ZP, SoverD);
                }
+               a.x = 0;
        }
        freeimage(t);
 }
@@ -944,51 +996,49 @@ pagesize(Page *p)
        return p->image ? mulpt(subpt(p->image->r.max, p->image->r.min), zoom) : ZP;
 }
 
+void
+drawframe(Rectangle r)
+{
+       border(screen, r, -Borderwidth, frame, ZP);
+       gendrawdiff(screen, screen->r, insetrect(r, -Borderwidth), ground, ZP, nil, ZP, SoverD);
+       flushimage(display, 1);
+}
+
 void
 drawpage(Page *p)
 {
        Rectangle r;
        Image *i;
 
-       if((i = p->image) == nil){
-               char *s;
-
-               if((s = p->text) == nil)
-                       s = "...";
-               r.min = ZP;
-               r.max = stringsize(font, p->text);
-               r = rectaddpt(r, addpt(subpt(divpt(subpt(screen->r.max, screen->r.min), 2), divpt(r.max, 2)),
-                       screen->r.min));
-               draw(screen, r, display->white, nil, ZP);
-               string(screen, r.min, display->black, ZP, font, s);
-       } else {
+       if(i = p->image){
                r = rectaddpt(Rpt(ZP, pagesize(p)), addpt(pos, screen->r.min));
-               zoomdraw(screen, r, ZR, i, i->r.min, zoom);
+               zoomdraw(screen, r, ZR, paper, i, i->r.min, zoom);
+       } else {
+               r = Rpt(ZP, stringsize(font, p->label));
+               r = rectaddpt(r, addpt(subpt(divpt(subpt(screen->r.max, screen->r.min), 2),
+                       divpt(r.max, 2)), screen->r.min));
+               draw(screen, r, paper, nil, ZP);
+               string(screen, r.min, display->black, ZP, font, p->label);
        }
-       gendrawdiff(screen, screen->r, r, background, ZP, nil, ZP, S);
-       border(screen, r, -Borderwidth, display->black, ZP);
-       flushimage(display, 1);
+       drawframe(r);
 }
 
 void
 translate(Page *p, Point d)
 {
-       Rectangle r, or, nr;
+       Rectangle r, nr;
        Image *i;
 
        i = p->image;
-       if((i==0) || (d.x==0 && d.y==0))
+       if(i==0 || d.x==0 && d.y==0)
                return;
        r = rectaddpt(Rpt(ZP, pagesize(p)), addpt(pos, screen->r.min));
        pos = addpt(pos, d);
        nr = rectaddpt(r, d);
-       or = r;
-       rectclip(&or, screen->r);
-       draw(screen, rectaddpt(or, d), screen, nil, or.min);
-       zoomdraw(screen, nr, rectaddpt(or, d), i, i->r.min, zoom);
-       gendrawdiff(screen, screen->r, nr, background, ZP, nil, ZP, S);
-       border(screen, nr, -Borderwidth, display->black, ZP);
-       flushimage(display, 1);
+       rectclip(&r, screen->r);
+       draw(screen, rectaddpt(r, d), screen, nil, r.min);
+       zoomdraw(screen, nr, rectaddpt(r, d), paper, i, i->r.min, zoom);
+       drawframe(nr);
 }
 
 Page*
@@ -1041,6 +1091,14 @@ pagemenugen(int i)
        return nil;
 }
 
+char*
+cmdmenugen(int i)
+{
+       if(i < 0 || i >= nelem(cmds))
+               return nil;
+       return cmds[i].m;
+}
+
 void
 showpage(Page *p)
 {
@@ -1064,6 +1122,28 @@ showpage(Page *p)
        }
 }
 
+void
+shownext(void)
+{
+       Page *p;
+
+       for(p = nextpage(current); p; p = nextpage(p))
+               if(p->image || p->open)
+                       break;
+       showpage(p);
+}
+
+void
+showprev(void)
+{
+       Page *p;
+
+       for(p = prevpage(current); p; p = prevpage(p))
+               if(p->image || p->open)
+                       break;
+       showpage(p);
+}
+
 void
 zerox(Page *p)
 {
@@ -1076,10 +1156,8 @@ zerox(Page *p)
        qlock(p);
        if((fd = openpage(p)) < 0)
                goto Out;
-       if(rfork(RFREND|RFFDG|RFPROC|RFENVG|RFNOTEG|RFNOWAIT) == 0){
-               dup(fd, 0);
-               close(fd);
-
+       if(rfork(RFPROC|RFMEM|RFFDG|RFENVG|RFNOTEG|RFNOWAIT) == 0){
+               dupfds(fd, 1, 2, -1);
                snprint(nam, sizeof nam, "/bin/%s", argv0);
                argv[0] = argv0;
                argv[1] = "-w";
@@ -1110,11 +1188,12 @@ eresized(int new)
        unlockdisplay(display);
 }
 
+int cohort = -1;
 void killcohort(void)
 {
        int i;
        for(i=0;i!=3;i++){      /* It's a long way to the kitchen */
-               postnote(PNGROUP, getpid(), "kill");
+               postnote(PNGROUP, cohort, "kill");
                sleep(1);
        }
 }
@@ -1131,11 +1210,111 @@ usage(void)
        exits("usage");
 }
 
+int
+docmd(int i, Mouse *m)
+{
+       char buf[NPATH], *s;
+       Point o;
+       int fd;
+
+       switch(i){
+       case Corigsize:
+               pos = ZP;
+               zoom = 1;
+               resize = ZP;
+               rotate = 0;
+       Unload:
+               viewgen++;
+               unlockdisplay(display);
+               esetcursor(&reading);
+               unloadpages(0);
+               showpage(current);
+               return 0;
+       case Cupsidedown:
+               rotate += 90;
+       case Crotate90:
+               rotate += 90;
+               rotate %= 360;
+               goto Unload;
+       case Cfitwidth:
+               pos = ZP;
+               zoom = 1;
+               resize = subpt(screen->r.max, screen->r.min);
+               resize.y = 0;
+               goto Unload;
+       case Cfitheight:
+               pos = ZP;
+               zoom = 1;
+               resize = subpt(screen->r.max, screen->r.min);
+               resize.x = 0;
+               goto Unload;
+       case Czoomin:
+       case Czoomout:
+               if(current == nil || !canqlock(current))
+                       return 1;
+               o = subpt(m->xy, screen->r.min);
+               if(i == Czoomin){
+                       if(zoom < 0x40000000){
+                               zoom *= 2;
+                               pos =  addpt(mulpt(subpt(pos, o), 2), o);
+                       }
+               }else{
+                       if(zoom > 1){
+                               zoom /= 2;
+                               pos =  addpt(divpt(subpt(pos, o), 2), o);
+                       }
+               }
+               drawpage(current);
+               qunlock(current);
+               return 1;
+       case Cwrite:
+               if(current == nil || !canqlock(current))
+                       return 1;
+               if(current->image){
+                       s = nil;
+                       if(current->up && current->up != root)
+                               s = current->up->label;
+                       snprint(buf, sizeof(buf), "%s%s%s.bit",
+                               s ? s : "",
+                               s ? "." : "",
+                               current->label);
+                       if(eenter("Write", buf, sizeof(buf), m) > 0){
+                               if((fd = create(buf, OWRITE, 0666)) < 0){
+                                       errstr(buf, sizeof(buf));
+                                       eenter(buf, 0, 0, m);
+                               } else {
+                                       esetcursor(&reading);
+                                       writeimage(fd, current->image, 0);
+                                       close(fd);
+                                       esetcursor(nil);
+                               }
+                       }
+               }
+               qunlock(current);
+               return 1;
+       case Cnext:
+               unlockdisplay(display);
+               shownext();
+               return 0;
+       case Cprev:
+               unlockdisplay(display);
+               showprev();
+               return 0;
+       case Czerox:
+               unlockdisplay(display);
+               zerox(current);
+               return 0;
+       case Cquit:
+               exits(0);
+       }
+       return 1;
+}
+
 void
 main(int argc, char *argv[])
 {
        enum { Eplumb = 4 };
-       char jump[32];
+       char buf[NPATH];
        Plumbmsg *pm;
        Point o;
        Mouse m;
@@ -1169,32 +1348,40 @@ main(int argc, char *argv[])
         * so that we can stop all subprocesses with a note,
         * and to isolate rendezvous from other processes
         */
-       rfork(RFNOTEG|RFNAMEG|RFREND);
-       atexit(killcohort);
        atnotify(catchnote, 1);
+       if(cohort = rfork(RFPROC|RFNOTEG|RFNAMEG|RFREND)){
+               atexit(killcohort);
+               waitpid();
+               exits(0);
+       }
+       cohort = getpid();
+       atexit(killcohort);
+
        if(newwin > 0){
                s = smprint("-pid %d", getpid());
                if(newwindow(s) < 0)
                        sysfatal("newwindow: %r");
                free(s);
        }
-       initdraw(drawerr, nil, argv0);
-       background = allocimage(display, Rect(0,0,1,1), screen->chan, 1, 0x777777FF);
-       draw(screen, screen->r, background, nil, ZP);
-       flushimage(display, 1);
+       if(initdraw(drawerr, nil, argv0) < 0)
+               sysfatal("initdraw: %r");
+       paper = display->white;
+       frame = display->black;
+       ground = allocimage(display, Rect(0,0,1,1), screen->chan, 1, 0x777777FF);
        display->locking = 1;
        unlockdisplay(display);
        einit(Ekeyboard|Emouse);
        eplumb(Eplumb, "image");
-       nullfd = open("/dev/null", ORDWR);
-       current = root = addpage(nil, "root", nil, nil, -1);
+       memset(&m, 0, sizeof(m));
+       if((nullfd = open("/dev/null", ORDWR)) < 0)
+               sysfatal("open: %r");
+       current = root = addpage(nil, "", nil, nil, -1);
 
        if(*argv == nil && !imode)
                addpage(root, "stdin", popenfile, strdup("/fd/0"), -1);
        for(; *argv; argv++)
                addpage(root, shortname(*argv), popenfile, strdup(*argv), -1);
 
-       jump[0] = 0;
        for(;;){
                i=event(&e);
                switch(i){
@@ -1202,158 +1389,93 @@ main(int argc, char *argv[])
                        lockdisplay(display);
                        m = e.mouse;
                        if(m.buttons & 1){
-                               if(current == nil || !canqlock(current))
-                                       goto Unlock;
-                               for(;;) {
-                                       o = m.xy;
-                                       m = emouse();
-                                       if((m.buttons & 1) == 0)
-                                               break;
-                                       translate(current, subpt(m.xy, o));
+                               if(current &&  canqlock(current)){
+                                       for(;;) {
+                                               o = m.xy;
+                                               m = emouse();
+                                               if((m.buttons & 1) == 0)
+                                                       break;
+                                               translate(current, subpt(m.xy, o));
+                                       }
+                                       qunlock(current);
                                }
-                               qunlock(current);
-                               goto Unlock;
-                       }
-                       if(m.buttons & 2){
-                               i = emenuhit(2, &m, &menu);
-                               if(i < 0 || i >= nelem(menuitems) || menuitems[i]==nil)
-                                       goto Unlock;
-                               s = menuitems[i];
-                               if(strcmp(s, "orig size")==0){
-                                       pos = ZP;
-                                       zoom = 1;
-                                       resize = ZP;
-                                       rotate = 0;
-                               Unload:
-                                       viewgen++;
+                       } else if(m.buttons & 2){
+                               o = m.xy;
+                               i = emenuhit(2, &m, &cmdmenu);
+                               m.xy = o;
+                               if(!docmd(i, &m))
+                                       continue;
+                       } else if(m.buttons & 4){
+                               if(root->down){
+                                       Page *x;
+
+                                       qlock(&pagelock);
+                                       pagemenu.lasthit = pageindex(current);
+                                       x = pageat(emenuhit(3, &m, &pagemenu));
+                                       qunlock(&pagelock);
                                        unlockdisplay(display);
-                                       esetcursor(&reading);
-                                       unloadpages(0);
-                                       showpage(current);
+                                       showpage(x);
                                        continue;
                                }
-                               if(strncmp(s, "rotate ", 7)==0){
-                                       rotate += atoi(s+7);
-                                       rotate %= 360;
-                                       goto Unload;
-                               }
-                               if(strcmp(s, "upside down")==0){
-                                       rotate += 180;
-                                       goto Unload;
-                               }
-                               if(strcmp(s, "fit width")==0){
-                                       pos = ZP;
-                                       zoom = 1;
-                                       resize = subpt(screen->r.max, screen->r.min);
-                                       resize.y = 0;
-                                       goto Unload;
-                               }
-                               if(strcmp(s, "fit height")==0){
-                                       pos = ZP;
-                                       zoom = 1;
-                                       resize = subpt(screen->r.max, screen->r.min);
-                                       resize.x = 0;
-                                       goto Unload;
-                               }
-                               if(strncmp(s, "zoom", 4)==0){
-                                       if(current && canqlock(current)){
-                                               o = subpt(m.xy, screen->r.min);
-                                               if(strstr(s, "in")){
-                                                       if(zoom < 0x40000000){
-                                                               zoom *= 2;
-                                                               pos =  addpt(mulpt(subpt(pos, o), 2), o);
-                                                       }
-                                               }else{
-                                                       if(zoom > 1){
-                                                               zoom /= 2;
-                                                               pos =  addpt(divpt(subpt(pos, o), 2), o);
-                                                       }
-                                               }
-                                               drawpage(current);
-                                               qunlock(current);
-                                       }
-                               }
-                               unlockdisplay(display);
-                               if(strcmp(s, "next")==0)
-                                       showpage(nextpage(current));
-                               if(strcmp(s, "prev")==0)
-                                       showpage(prevpage(current));
-                               if(strcmp(s, "zerox")==0)
-                                       zerox(current);
-                               if(strcmp(s, "quit")==0)
-                                       exits(0);
-                               continue;
                        }
-                       if(m.buttons & 4){
-                               if(root->down == nil)
-                                       goto Unlock;
-                               pagemenu.lasthit = pageindex(current);
-                               i = emenuhit(3, &m, &pagemenu);
-                               unlockdisplay(display);
-                               if(i != -1)
-                                       showpage(pageat(i));
-                               continue;
-                       }
-               Unlock:
                        unlockdisplay(display);
                        break;
                case Ekeyboard:
+                       lockdisplay(display);
                        switch(e.kbdc){
-                       case 'q':
-                       case Kdel:
-                       case Keof:
-                               exits(0);
                        case Kup:
                                if(current == nil || !canqlock(current))
                                        break;
-                               lockdisplay(display);
                                if(pos.y < 0){
                                        translate(current, Pt(0, Dy(screen->r)/2));
-                                       unlockdisplay(display);
                                        qunlock(current);
-                                       continue;
+                                       break;
                                }
-                               unlockdisplay(display);
-                               qunlock(current);
                                if(prevpage(current))
                                        pos.y = 0;
-                       case '-':
-                       case Kbs:
-                       case Kleft:
-                               showpage(prevpage(current));
+                               qunlock(current);
+                               if(!docmd(Cprev, &m))
+                                       continue;
                                break;
                        case Kdown:
                                if(current == nil || !canqlock(current))
                                        break;
                                o = addpt(pos, pagesize(current));
-                               lockdisplay(display);
                                if(o.y > Dy(screen->r)){
                                        translate(current, Pt(0, -Dy(screen->r)/2));
-                                       unlockdisplay(display);
                                        qunlock(current);
-                                       continue;
+                                       break;
                                }
-                               unlockdisplay(display);
-                               qunlock(current);
                                if(nextpage(current))
                                        pos.y = 0;
-                       case '\n':
-                               if(jump[0]){
-                                       showpage(findpage(jump));
-                                       jump[0] = 0;
-                                       break;
-                               }
-                       case ' ':
-                       case Kright:
-                               showpage(nextpage(current));
+                               qunlock(current);
+                               if(!docmd(Cnext, &m))
+                                       continue;
                                break;
                        default:
-                               i = strlen(jump);
-                               if(i+1 < sizeof(jump)){
-                                       jump[i] = e.kbdc;
-                                       jump[i+1] = 0;
+                               for(i = 0; i<nelem(cmds); i++)
+                                       if((cmds[i].k1 == e.kbdc) ||
+                                          (cmds[i].k2 == e.kbdc) ||
+                                          (cmds[i].k3 == e.kbdc))
+                                               break;
+                               if(i < nelem(cmds)){
+                                       if(!docmd(i, &m))
+                                               continue;
+                                       break;
+                               }
+                               if((e.kbdc < 0x20) || 
+                                  (e.kbdc & 0xFF00) == KF || 
+                                  (e.kbdc & 0xFF00) == Spec)
+                                       break;
+                               snprint(buf, sizeof(buf), "%C", (Rune)e.kbdc);
+                               i = eenter("Go to", buf, sizeof(buf), &m);
+                               if(i > 0){
+                                       unlockdisplay(display);
+                                       showpage(findpage(buf));
+                                       continue;
                                }
                        }
+                       unlockdisplay(display);
                        break;
                case Eplumb:
                        pm = e.v;
@@ -1365,9 +1487,7 @@ main(int argc, char *argv[])
                                if(s && strcmp(s, "quit")==0)
                                        exits(0);
                                if(s && strcmp(s, "showdata")==0){
-                                       static ulong plumbid;
-
-                                       if((fd = createtmp(plumbid++, "plumb")) < 0){
+                                       if((fd = createtmp("plumb")) < 0){
                                                fprint(2, "plumb: createtmp: %r\n");
                                                goto Plumbfree;
                                        }