]> git.lizzy.rs Git - plan9front.git/blobdiff - sys/src/cmd/page.c
cc: fix void cast crash
[plan9front.git] / sys / src / cmd / page.c
index 79777a7b537df2d10255efd8efb4eea4f591ec4f..49478d3a41770cc136cd1b39c060308847691ea4 100644 (file)
@@ -8,20 +8,24 @@
 
 typedef struct Page Page;
 struct Page {
-       char    *label;
+       char    *name;
+       char    *delim;
 
        QLock;
+       char    *ext;
        void    *data;
        int     (*open)(Page *);
 
        Image   *image;
        int     fd;
-       int     gen;
 
        Page    *up;
        Page    *next;
        Page    *down;
        Page    *tail;
+
+       Page    *lnext;
+       Page    *lprev;
 };
 
 int zoom = 1;
@@ -30,11 +34,20 @@ int imode;
 int newwin;
 int rotate;
 int viewgen;
-int pagegen;
+int forward;   /* read ahead direction: >= 0 forwards, < 0 backwards */
 Point resize, pos;
 Page *root, *current;
+Page lru;
 QLock pagelock;
 int nullfd;
+char *pagewalk = nil;
+
+enum {
+       MiB     = 1024*1024,
+};
+
+ulong imemlimit = 16*MiB;
+ulong imemsize;
 
 Image *frame, *paper, *ground;
 
@@ -42,41 +55,65 @@ char pagespool[] = "/tmp/pagespool.";
 
 enum {
        NPROC = 4,
-       NAHEAD = 2,
        NBUF = 8*1024,
        NPATH = 1024,
 };
 
-char *pagemenugen(int i);
+enum {
+       Corigsize,
+       Czoomin,
+       Czoomout,
+       Cfitwidth,
+       Cfitheight,
+       Crotate90,
+       Cupsidedown,
+       Cdummy1,
+       Cnext,
+       Cprev,
+       Csnarf,
+       Czerox,
+       Cwrite,
+       Cext,
+       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,
+       [Csnarf]        "snarf",        's', 0, 0,
+       [Czerox]        "zerox",        'z', 0, 0,
+       [Cwrite]        "write",        'w', 0, 0,
+       [Cext]          "ext",          'x', 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,
 };
 
@@ -92,27 +129,25 @@ Cursor reading = {
         0x00, 0x00, 0x01, 0xb6, 0x01, 0xb6, 0x00, 0x00, }
 };
 
+int pagewalk1(Page *p);
+void showpage1(Page *);
 void showpage(Page *);
 void drawpage(Page *);
 Point pagesize(Page *);
 
 Page*
-addpage(Page *up, char *label, int (*popen)(Page *), void *pdata, int fd)
+addpage(Page *up, char *name, int (*popen)(Page *), void *pdata, int fd)
 {
        Page *p;
 
        p = mallocz(sizeof(*p), 1);
-       p->label = strdup(label);
-       p->gen = pagegen;
-       p->image = nil;
+       p->name = strdup(name);
+       p->delim = "!";
+       p->image = nil;
        p->data = pdata;
        p->open = popen;
        p->fd = fd;
 
-       p->down = nil;
-       p->tail = nil;
-       p->next = nil;
-
        qlock(&pagelock);
        if(p->up = up){
                if(up->tail == nil)
@@ -124,8 +159,11 @@ addpage(Page *up, char *label, int (*popen)(Page *), void *pdata, int fd)
        }
        qunlock(&pagelock);
 
-       if(up && current == up)
-               showpage(p);
+       if(up && current == up){
+               if(!pagewalk1(p))
+                       return p;
+               showpage1(p);
+       }
        return p;
 }
 
@@ -138,6 +176,16 @@ resizewin(Point size)
                return;
        /* add rio border */
        size = addpt(size, Pt(Borderwidth*2, Borderwidth*2));
+       if(display->image != nil){
+               Point dsize = subpt(display->image->r.max, display->image->r.min);
+               if(size.x > dsize.x)
+                       size.x = dsize.x;
+               if(size.y > dsize.y)
+                       size.y = dsize.y;
+               /* can't just conver whole display */
+               if(eqpt(size, dsize))
+                       size.y--;
+       }
        fprint(wctl, "resize -dx %d -dy %d\n", size.x, size.y);
        close(wctl);
 }
@@ -160,6 +208,10 @@ catchnote(void *, char *msg)
                return 1;
        if(strstr(msg, "alarm"))
                return 1;
+       if(strstr(msg, "interrupt"))
+               return 1;
+       if(strstr(msg, "kill"))
+               exits("killed");
        return 0;
 }
 
@@ -201,16 +253,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:
                dupfds(fd, pfd[1], 2, -1);
-               va_start(arg, fmt);
-               vsnprint(buf, sizeof buf, fmt, arg);
-               va_end(arg);
                argv[0] = "rc";
                argv[1] = "-c";
                argv[2] = buf;
@@ -223,14 +275,50 @@ pipeline(int fd, char *fmt, ...)
        close(pfd[0]);
 }
 
+static char*
+shortlabel(char *s)
+{
+       enum { NR=60 };
+       static char buf[NR*UTFmax];
+       int i, k, l;
+       Rune r;
+
+       l = utflen(s);
+       if(l < NR-2)
+               return s;
+       k = i = 0;
+       while(i < NR/2){
+               k += chartorune(&r, s+k);
+               i++;
+       }
+       strncpy(buf, s, k);
+       strcpy(buf+k, "...");
+       while((l-i) >= NR/2-4){
+               k += chartorune(&r, s+k);
+               i++;
+       }
+       strcat(buf, s+k);
+       return buf;
+}
+
+static char*
+pageaddr1(Page *p, char *s, char *e)
+{
+       if(p == nil || p == root)
+               return s;
+       return seprint(pageaddr1(p->up, s, e), e, "%s%s", p->up->delim, p->name);
+}
+
+/*
+ * returns address string of a page in the form:
+ * /dir/filename!page!subpage!...
+ */
 char*
-shortname(char *s)
+pageaddr(Page *p, char *buf, int nbuf)
 {
-       char *x;
-       if(x = strrchr(s, '/'))
-               if(x[1] != 0)
-                       return x+1;
-       return s;
+       buf[0] = 0;
+       pageaddr1(p, buf, buf+nbuf);
+       return buf;
 }
 
 int
@@ -249,8 +337,13 @@ popenimg(Page *p)
        }
 
        seek(fd, 0, 0);
-       if(p->data)
-               pipeline(fd, "%s", (char*)p->data);
+       if(p->data){
+               p->ext = p->data;
+               if(strcmp(p->ext, "ico") == 0)
+                       pipeline(fd, "exec %s -c", p->ext);
+               else
+                       pipeline(fd, "exec %s -t9", p->ext);
+       }
 
        /*
         * dont keep the file descriptor arround if it can simply
@@ -272,7 +365,7 @@ popenfilter(Page *p)
 {
        seek(p->fd, 0, 0);
        if(p->data){
-               pipeline(p->fd, "%s", (char*)p->data);
+               pipeline(p->fd, "exec %s", (char*)p->data);
                p->data = nil;
        }
        p->open = popenfile;
@@ -285,8 +378,9 @@ popentape(Page *p)
        char mnt[32], cmd[64], *argv[4];
 
        seek(p->fd, 0, 0);
-       snprint(mnt, sizeof(mnt), "/n/tapefs.%.12d%.8lux", getpid(), (ulong)p);
-       switch(rfork(RFPROC|RFFDG|RFREND)){
+       snprint(mnt, sizeof(mnt), "/n/tapefs.%.12d%.8lux", getpid(), (ulong)(uintptr)p);
+       snprint(cmd, sizeof(cmd), "exec %s -m %s /fd/0", (char*)p->data, mnt);
+       switch(rfork(RFPROC|RFMEM|RFFDG|RFREND)){
        case -1:
                close(p->fd);
                p->fd = -1;
@@ -295,7 +389,6 @@ popentape(Page *p)
                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);
@@ -349,7 +442,7 @@ popenepub(Page *p)
                while(n > 0 && s[n-1] == '\n')
                        n--;
                s[n] = 0;
-               addpage(p, shortname(buf), popenfile, strdup(buf), -1);
+               addpage(p, s, popenfile, strdup(buf), -1);
        }
        close(fd);
        return -1;
@@ -374,7 +467,7 @@ popenpdf(Page *p)
 
        if(pipe(pfd) < 0)
                return -1;
-       switch(rfork(RFPROC|RFFDG|RFMEM|RFNOWAIT)){
+       switch(rfork(RFPROC|RFMEM|RFFDG|RFNOWAIT)){
        case -1:
                close(pfd[0]);
                close(pfd[1]);
@@ -387,7 +480,7 @@ popenpdf(Page *p)
                        "(/fd/3) (w) file "
                        "dup flushfile "
                        "dup (THIS IS NOT AN INFERNO BITMAP\\n) writestring "
-                       "flushfile\n", p->label);
+                       "flushfile\n", p->name);
                while((n = read(0, buf, sizeof buf)) > 0){
                        if(memcmp(buf, "THIS IS NOT AN INFERNO BITMAP\n", 30) == 0)
                                break;
@@ -425,6 +518,7 @@ popengs(Page *p)
        pdf = 0;
        ifd = p->fd;
        p->fd = -1;
+       p->open = nil;
        seek(ifd, 0, 0);
        if(read(ifd, buf, 5) != 5)
                goto Err0;
@@ -449,16 +543,17 @@ popengs(Page *p)
                goto Err1;
        }
 
-       switch(rfork(RFPROC|RFFDG|RFREND|RFNOWAIT)){
+       argv[0] = (char*)p->data;
+       switch(rfork(RFPROC|RFMEM|RFFDG|RFREND|RFNOWAIT)){
        case -1:
                goto Err2;
        case 0:
                if(pdf)
-                       dupfds(pin[1], pout[1], nullfd, pdat[1], ifd, -1);
+                       dupfds(pin[1], pout[1], 2, pdat[1], ifd, -1);
                else
-                       dupfds(nullfd, nullfd, nullfd, pdat[1], ifd, -1);
-               if(p->data)
-                       pipeline(4, "%s", (char*)p->data);
+                       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";
@@ -570,7 +665,7 @@ filetype(char *buf, int nbuf, char *typ, int ntyp)
                close(ifd[1]);
                return -1;
        }
-       if(rfork(RFPROC|RFFDG|RFREND|RFNOWAIT) == 0){
+       if(rfork(RFPROC|RFMEM|RFFDG|RFREND|RFNOWAIT) == 0){
                dupfds(ifd[1], ofd[1], 2, -1);
                argv[0] = "file";
                argv[1] = "-m";
@@ -579,7 +674,7 @@ filetype(char *buf, int nbuf, char *typ, int ntyp)
        }
        close(ifd[1]);
        close(ofd[1]);
-       if(rfork(RFPROC|RFFDG|RFNOWAIT) == 0){
+       if(rfork(RFPROC|RFMEM|RFFDG|RFNOWAIT) == 0){
                dupfds(ifd[0], -1);
                write(0, buf, nbuf);
                exits(nil);
@@ -626,11 +721,14 @@ popenfile(Page *p)
        "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/gif",                    popenimg,       "gif",
+       "image/jpeg",                   popenimg,       "jpg",
+       "image/png",                    popenimg,       "png",
+       "image/tiff",                   popenimg,       "tif",
+       "image/ppm",                    popenimg,       "ppm",
+       "image/bmp",                    popenimg,       "bmp",
+       "image/tga",                    popenimg,       "tga",
+       "image/x-icon",                 popenimg,       "ico",
        "image/p9bit",                  popenimg,       nil,
        };
 
@@ -640,8 +738,10 @@ popenfile(Page *p)
 
        fd = p->fd;
        p->fd = -1;
+       p->ext = nil;
        file = p->data;
        p->data = nil;
+       p->open = nil;
        if(fd < 0){
                if((fd = open(file, OREAD)) < 0){
                Err0:
@@ -667,7 +767,8 @@ popenfile(Page *p)
                        p->open = popenepub;
                        return p->open(p);
                }
-
+               if(strcmp(pageaddr(p, buf, sizeof(buf)), file) == 0)
+                       p->delim = "/";
                if((n = dirreadall(fd, &d)) < 0)
                        goto Err1;
                qsort(d, n, sizeof d[0], dircmp);
@@ -694,7 +795,7 @@ popenfile(Page *p)
        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;
@@ -721,13 +822,12 @@ popenfile(Page *p)
 Page*
 nextpage(Page *p)
 {
-       if(p){
-               if(p->down)
-                       return p->down;
-               if(p->next)
+       if(p != nil && p->down != nil)
+               return p->down;
+       while(p != nil){
+               if(p->next != nil)
                        return p->next;
-               if(p->up)
-                       return p->up->next;
+               p = p->up;
        }
        return nil;
 }
@@ -737,8 +837,8 @@ prevpage(Page *x)
 {
        Page *p, *t;
 
-       if(x){
-               for(p = root->down; p; p = t)
+       if(x != nil){
+               for(p = root->down; p != nil; p = t)
                        if((t = nextpage(p)) == x)
                                return p;
        }
@@ -755,91 +855,133 @@ openpage(Page *p)
                p->open = nil;
        else {
                if(rotate)
-                       pipeline(fd, "rotate -r %d", rotate);
+                       pipeline(fd, "exec rotate -r %d", rotate);
                if(resize.x)
-                       pipeline(fd, "resize -x %d", resize.x);
+                       pipeline(fd, "exec resize -x %d", resize.x);
                else if(resize.y)
-                       pipeline(fd, "resize -y %d", resize.y);
+                       pipeline(fd, "exec resize -y %d", resize.y);
        }
        return fd;
 }
 
+static ulong
+imagesize(Image *i)
+{
+       if(i == nil)
+               return 0;
+       return Dy(i->r)*bytesperline(i->r, i->depth);
+}
+
+static void
+lunlink(Page *p)
+{
+       if(p->lnext == nil || p->lnext == p)
+               return;
+       p->lnext->lprev = p->lprev;
+       p->lprev->lnext = p->lnext;
+       p->lnext = nil;
+       p->lprev = nil;
+}
+
+static void
+llinkhead(Page *p)
+{
+       lunlink(p);
+       p->lnext = lru.lnext;
+       p->lprev = &lru;
+       p->lnext->lprev = p;
+       p->lprev->lnext = p;
+}
+
 void
 loadpage(Page *p)
 {
        int fd;
 
-       if(p->open && p->image == nil){
-               if((fd = openpage(p)) >= 0){
-                       pagegen++;
+       qlock(&lru);
+       llinkhead(p);
+       qunlock(&lru);
+
+       if(p->open != nil && p->image == nil){
+               fd = openpage(p);
+               if(fd >= 0){
                        if((p->image = readimage(display, fd, 1)) == nil)
                                fprint(2, "readimage: %r\n");
                        close(fd);
                }
                if(p->image == nil)
                        p->open = nil;
+               else {
+                       lockdisplay(display);
+                       imemsize += imagesize(p->image);
+                       unlockdisplay(display);
+               }
        }
-       p->gen = pagegen;
 }
 
 void
 unloadpage(Page *p)
 {
+       qlock(&lru);
+       lunlink(p);
+       qunlock(&lru);
+
        if(p->open == nil || p->image == nil)
                return;
        lockdisplay(display);
+       imemsize -= imagesize(p->image);
        freeimage(p->image);
        unlockdisplay(display);
        p->image = nil;
 }
 
 void
-unloadpages(int age)
+unloadpages(ulong limit)
 {
        Page *p;
 
-       for(p = root->down; p; p = nextpage(p)){
-               if(age == 0)    /* synchronous flush */
-                       qlock(p);
-               else if(!canqlock(p))
-                       continue;
-               if((pagegen - p->gen) >= age)
-                       unloadpage(p);
+       while(imemsize >= limit && (p = lru.lprev) != &lru){
+               qlock(p);
+               unloadpage(p);
                qunlock(p);
        }
 }
 
 void
-loadpages(Page *p, int ahead, int oviewgen)
+loadpages(Page *p, int oviewgen)
 {
-       int i;
-
-       ahead++;        /* load at least one */
-       unloadpages(ahead*2);
-       for(i = 0; i < ahead && p; p = nextpage(p), i++){
-               if(viewgen != oviewgen)
+       while(p != nil && viewgen == oviewgen){
+               qlock(p);
+               loadpage(p);
+               if(viewgen != oviewgen){
+                       unloadpage(p);
+                       qunlock(p);
                        break;
-               if(canqlock(p)){
-                       loadpage(p);
-                       if(viewgen != oviewgen){
-                               unloadpage(p);
-                               qunlock(p);
-                               break;
-                       }
-                       if(p == current){
-                               Point size;
-
-                               esetcursor(nil);
-                               size = pagesize(p);
-                               if(size.x && size.y && newwin){
-                                       newwin = 0;
-                                       resizewin(size);
-                               }
-                               lockdisplay(display);
-                               drawpage(p);
-                               unlockdisplay(display);
+               }
+               if(p == current){
+                       Point size;
+
+                       esetcursor(nil);
+                       size = pagesize(p);
+                       if(size.x && size.y && newwin){
+                               newwin = 0;
+                               resizewin(size);
                        }
-                       qunlock(p);
+                       lockdisplay(display);
+                       drawpage(p);
+                       unlockdisplay(display);
+               }
+               qunlock(p);
+               if(p != current && imemsize >= imemlimit)
+                       break;          /* only one page ahead once we reach the limit */
+               if(forward < 0){
+                       if(p->up == nil || p->up->down == p)
+                               break;
+                       p = prevpage(p);
+               } else {
+                       if(p->next == nil)
+                               break;
+                       p = nextpage(p);
                }
        }
 }
@@ -958,7 +1100,7 @@ zoomdraw(Image *d, Rectangle r, Rectangle top, Image *b, Image *s, Point sp, int
        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);
+               if(b != nil) 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;
@@ -972,7 +1114,7 @@ zoomdraw(Image *d, Rectangle r, Rectangle top, Image *b, Image *s, Point sp, int
 Point
 pagesize(Page *p)
 {
-       return p->image ? mulpt(subpt(p->image->r.max, p->image->r.min), zoom) : ZP;
+       return p->image != nil ? mulpt(subpt(p->image->r.max, p->image->r.min), zoom) : ZP;
 }
 
 void
@@ -989,15 +1131,15 @@ drawpage(Page *p)
        Rectangle r;
        Image *i;
 
-       if(i = p->image){
+       if((i = p->image) != nil){
                r = rectaddpt(Rpt(ZP, pagesize(p)), addpt(pos, screen->r.min));
                zoomdraw(screen, r, ZR, paper, i, i->r.min, zoom);
        } else {
-               r = Rpt(ZP, stringsize(font, p->label));
+               r = Rpt(ZP, stringsize(font, p->name));
                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);
+               string(screen, r.min, display->black, ZP, font, p->name);
        }
        drawframe(r);
 }
@@ -1009,35 +1151,106 @@ translate(Page *p, Point d)
        Image *i;
 
        i = p->image;
-       if(i==0 || d.x==0 && d.y==0)
+       if(i==nil || 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);
-       rectclip(&r, screen->r);
-       draw(screen, rectaddpt(r, d), screen, nil, r.min);
+       if(rectclip(&r, screen->r))
+               draw(screen, rectaddpt(r, d), screen, nil, r.min);
+       else
+               r = ZR;
        zoomdraw(screen, nr, rectaddpt(r, d), paper, i, i->r.min, zoom);
        drawframe(nr);
 }
 
+int
+pagewalk1(Page *p)
+{
+       char *s;
+       int n;
+
+       if((s = pagewalk) == nil || *s == 0)
+               return 1;
+       n = strlen(p->name);
+       if(n == 0 || strncmp(s, p->name, n) != 0)
+               return 0;
+       if(s[n] == 0){
+               pagewalk = nil;
+               return 1;
+       }
+       if(s[n] == '/' || s[n] == '!'){
+               pagewalk = s + n+1;
+               return 1;
+       }
+       return 0;
+}
+
+Page*
+trywalk(char *name, char *addr)
+{
+       static char buf[NPATH];
+       Page *p, *a;
+
+       pagewalk = nil;
+       memset(buf, 0, sizeof(buf));
+       snprint(buf, sizeof(buf), "%s%s%s",
+               name != nil ? name : "",
+               (name != nil && addr != nil) ? "!" : "", 
+               addr != nil ? addr : "");
+       pagewalk = buf;
+
+       a = nil;
+       if(root != nil){
+               p = root->down;
+       Loop:
+               for(; p != nil; p = p->next)
+                       if(pagewalk1(p)){
+                               a = p;
+                               p = p->down;
+                               goto Loop;
+                       }
+       }
+       return a;
+}
+
 Page*
 findpage(char *name)
 {
        Page *p;
        int n;
 
+       if(name == nil)
+               return nil;
+
        n = strlen(name);
-       /* look in current document first */
-       if(current && current->up){
-               for(p = current->up->down; p; p = p->next)
-                       if(cistrncmp(p->label, name, n) == 0)
+       /* look in current document */
+       if(current != nil && current->up != nil){
+               for(p = current->up->down; p != nil; p = p->next)
+                       if(cistrncmp(p->name, name, n) == 0)
                                return p;
        }
        /* look everywhere */
-       for(p = root->down; p; p = nextpage(p))
-               if(cistrncmp(p->label, name, n) == 0)
-                       return p;
-       return nil;
+       if(root != nil){
+               for(p = root->down; p != nil; p = nextpage(p))
+                       if(cistrncmp(p->name, name, n) == 0)
+                               return p;
+       }
+       /* try bookmark */
+       return trywalk(name, nil);
+}
+
+void
+writeaddr(Page *p, char *file)
+{
+       char buf[NPATH], *s;
+       int fd;
+
+       s = pageaddr(p, buf, sizeof(buf));
+       if((fd = open(file, OWRITE)) >= 0){
+               write(fd, s, strlen(s));
+               close(fd);
+       }
 }
 
 Page*
@@ -1045,7 +1258,7 @@ pageat(int i)
 {
        Page *p;
 
-       for(p = root->down; i > 0 && p; p = nextpage(p))
+       for(p = root->down; i > 0 && p != nil; p = nextpage(p))
                i--;
        return i ? nil : p;
 }
@@ -1056,7 +1269,7 @@ pageindex(Page *x)
        Page *p;
        int i;
 
-       for(i = 0, p = root->down; p && p != x; p = nextpage(p))
+       for(i = 0, p = root->down; p != nil && p != x; p = nextpage(p))
                i++;
        return (p == x) ? i : -1;
 }
@@ -1065,13 +1278,27 @@ char*
 pagemenugen(int i)
 {
        Page *p;
-       if(p = pageat(i))
-               return p->label;
+
+       if((p = pageat(i)) != nil)
+               return shortlabel(p->name);
        return nil;
 }
 
+char*
+cmdmenugen(int i)
+{
+       if(i < 0 || i >= nelem(cmds))
+               return nil;
+       return cmds[i].m;
+}
+
+/*
+ * spawn new proc to load a run of pages starting with p
+ * the display should *not* be locked as it gets called
+ * from recursive page load.
+ */
 void
-showpage(Page *p)
+showpage1(Page *p)
 {
        static int nproc;
        int oviewgen;
@@ -1079,40 +1306,44 @@ showpage(Page *p)
        if(p == nil)
                return;
        esetcursor(&reading);
+       writeaddr(p, "/dev/label");
        current = p;
        oviewgen = viewgen;
-       if(++nproc > NPROC)
-               if(waitpid() > 0)
-                       nproc--;
        switch(rfork(RFPROC|RFMEM)){
        case -1:
                sysfatal("rfork: %r");
        case 0:
-               loadpages(p, NAHEAD, oviewgen);
+               loadpages(p, oviewgen);
                exits(nil);
        }
+       if(++nproc >= NPROC)
+               if(waitpid() > 0)
+                       nproc--;
 }
 
+/* recursive display lock, called from main proc only */
 void
-shownext(void)
-{
-       Page *p;
-
-       for(p = nextpage(current); p; p = nextpage(p))
-               if(p->image || p->open)
-                       break;
-       showpage(p);
+drawlock(int dolock){
+       static int ref = 0;
+       if(dolock){
+               if(ref++ == 0)
+                       lockdisplay(display);
+       } else {
+               if(--ref == 0)
+                       unlockdisplay(display);
+       }
 }
 
+
 void
-showprev(void)
+showpage(Page *p)
 {
-       Page *p;
-
-       for(p = prevpage(current); p; p = prevpage(p))
-               if(p->image || p->open)
-                       break;
-       showpage(p);
+       if(p == nil)
+               return;
+       drawlock(0);
+       unloadpages(imemlimit);
+       showpage1(p);
+       drawlock(1);
 }
 
 void
@@ -1123,11 +1354,11 @@ zerox(Page *p)
 
        if(p == nil)
                return;
-       esetcursor(&reading);
+       drawlock(0);
        qlock(p);
        if((fd = openpage(p)) < 0)
                goto Out;
-       if(rfork(RFPROC|RFFDG|RFREND|RFENVG|RFNOTEG|RFNOWAIT) == 0){
+       if(rfork(RFPROC|RFMEM|RFFDG|RFENVG|RFNOTEG|RFNOWAIT) == 0){
                dupfds(fd, 1, 2, -1);
                snprint(nam, sizeof nam, "/bin/%s", argv0);
                argv[0] = argv0;
@@ -1139,7 +1370,50 @@ zerox(Page *p)
        close(fd);
 Out:
        qunlock(p);
-       esetcursor(nil);
+       drawlock(1);
+}
+
+void
+showext(Page *p)
+{
+       char label[64], *argv[4];
+       Point ps;
+       int fd;
+
+       if(p->ext == nil)
+               return;
+       snprint(label, sizeof(label), "%s %s", p->ext, p->name);
+       ps = Pt(0, 0);
+       if(p->image != nil)
+               ps = addpt(subpt(p->image->r.max, p->image->r.min), Pt(24, 24));
+       drawlock(0);
+       if((fd = p->fd) < 0){
+               if(p->open != popenfile)
+                       return;
+               fd = open((char*)p->data, OREAD);
+       } else {
+               fd = dup(fd, -1);
+               seek(fd, 0, 0);
+       }
+       if(rfork(RFPROC|RFMEM|RFFDG|RFNOTEG|RFREND|RFNOWAIT) == 0){
+               if(newwindow(nil) != -1){
+                       dupfds(fd, open("/dev/cons", OWRITE), open("/dev/cons", OWRITE), -1);
+                       if((fd = open("/dev/label", OWRITE)) >= 0){
+                               write(fd, label, strlen(label));
+                               close(fd);
+                       }
+                       if(ps.x && ps.y)
+                               resizewin(ps);
+                       argv[0] = "rc";
+                       argv[1] = "-c";
+                       argv[2] = p->ext;
+                       argv[3] = nil;
+                       exec("/bin/rc", argv);
+               }
+               exits(0);
+       }
+       close(fd);
+       drawlock(1);
 }
 
 void
@@ -1147,23 +1421,24 @@ eresized(int new)
 {
        Page *p;
 
-       lockdisplay(display);
+       drawlock(1);
        if(new && getwindow(display, Refnone) == -1)
                sysfatal("getwindow: %r");
-       if(p = current){
+       if((p = current) != nil){
                if(canqlock(p)){
                        drawpage(p);
                        qunlock(p);
                }
        }
-       unlockdisplay(display);
+       drawlock(0);
 }
 
+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);
        }
 }
@@ -1176,15 +1451,169 @@ void drawerr(Display *, char *msg)
 void
 usage(void)
 {
-       fprint(2, "usage: %s [ -iRw ] [ -p ppi ] [ file ... ]\n", argv0);
+       fprint(2, "usage: %s [ -iRw ] [ -m mb ] [ -p ppi ] [ -j addr ] [ file ... ]\n", argv0);
        exits("usage");
 }
 
+void
+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++;
+               drawlock(0);
+               unloadpages(0);
+               showpage1(current);
+               drawlock(1);
+               break;
+       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))
+                       break;
+               o = subpt(m->xy, screen->r.min);
+               if(i == Czoomin){
+                       if(zoom < 0x1000){
+                               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);
+               break;
+       case Cwrite:
+               if(current == nil || !canqlock(current))
+                       break;
+               if(current->image != nil){
+                       s = nil;
+                       if(current->up != nil && current->up != root)
+                               s = current->up->name;
+                       snprint(buf, sizeof(buf), "%s%s%s.bit",
+                               s != nil ? s : "",
+                               s != nil ? "." : "",
+                               current->name);
+                       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);
+               break;
+       case Cext:
+               if(current == nil || !canqlock(current))
+                       break;
+               showext(current);
+               qunlock(current);
+               break;
+       case Csnarf:
+               writeaddr(current, "/dev/snarf");
+               break;
+       case Cnext:
+               forward = 1;
+               showpage(nextpage(current));
+               break;
+       case Cprev:
+               forward = -1;
+               showpage(prevpage(current));
+               break;
+       case Czerox:
+               zerox(current);
+               break;
+       case Cquit:
+               exits(0);
+       }
+}
+
+void
+scroll(int y)
+{
+       Point z;
+       Page *p;
+
+       if(current == nil || !canqlock(current))
+               return;
+       if(y < 0){
+               if(pos.y >= 0){
+                       p = prevpage(current);
+                       if(p != nil){
+                               qunlock(current);
+                               z = ZP;
+                               if(canqlock(p)){
+                                       z = pagesize(p);
+                                       qunlock(p);
+                               }
+                               if(z.y == 0)
+                                       z.y = Dy(screen->r);
+                               if(pos.y+z.y > Dy(screen->r))
+                                       pos.y = Dy(screen->r) - z.y;
+                               forward = -1;
+                               showpage(p);
+                               return;
+                       }
+                       y = 0;
+               }
+       } else {
+               z = pagesize(current);
+               if(pos.y+z.y <= Dy(screen->r)){
+                       p = nextpage(current);
+                       if(p != nil){
+                               qunlock(current);
+                               if(pos.y < 0)
+                                       pos.y = 0;
+                               forward = 1;
+                               showpage(p);
+                               return;
+                       }
+                       y = 0;
+               }
+       }
+       translate(current, Pt(0, -y));
+       qunlock(current);
+}
+
 void
 main(int argc, char *argv[])
 {
        enum { Eplumb = 4 };
-       char jump[32];
+       char buf[NPATH];
        Plumbmsg *pm;
        Point o;
        Mouse m;
@@ -1192,6 +1621,8 @@ main(int argc, char *argv[])
        char *s;
        int i;
 
+       quotefmtinstall();
+
        ARGBEGIN {
        case 'a':
        case 'v':
@@ -1199,7 +1630,8 @@ main(int argc, char *argv[])
        case 'P':
                break;
        case 'R':
-               newwin = -1;
+               if(newwin == 0)
+                       newwin = -1;
                break;
        case 'w':
                newwin = 1;
@@ -1207,6 +1639,12 @@ main(int argc, char *argv[])
        case 'i':
                imode = 1;
                break;
+       case 'j':
+               trywalk(EARGF(usage()), nil);
+               break;
+       case 'm':
+               imemlimit = atol(EARGF(usage()))*MiB;
+               break;
        case 'p':
                ppi = atoi(EARGF(usage()));
                break;
@@ -1214,201 +1652,127 @@ main(int argc, char *argv[])
                usage();
        } ARGEND;
 
+       if(newwin > 0){
+               if(newwindow(nil) < 0)
+                       sysfatal("newwindow: %r");
+       }
+
        /*
         * 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(newwin > 0){
-               s = smprint("-pid %d", getpid());
-               if(newwindow(s) < 0)
-                       sysfatal("newwindow: %r");
-               free(s);
+       if(cohort = rfork(RFPROC|RFNOTEG|RFNAMEG|RFREND)){
+               atexit(killcohort);
+               waitpid();
+               exits(0);
        }
-       initdraw(drawerr, nil, argv0);
+       cohort = getpid();
+       atexit(killcohort);
+       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");
+       memset(&m, 0, sizeof(m));
        if((nullfd = open("/dev/null", ORDWR)) < 0)
                sysfatal("open: %r");
-       current = root = addpage(nil, "root", nil, nil, -1);
-
+       dup(nullfd, 1);
+       lru.lprev = &lru;
+       lru.lnext = &lru;
+       current = root = addpage(nil, "", nil, nil, -1);
+       root->delim = "";
        if(*argv == nil && !imode)
                addpage(root, "stdin", popenfile, strdup("/fd/0"), -1);
        for(; *argv; argv++)
-               addpage(root, shortname(*argv), popenfile, strdup(*argv), -1);
+               addpage(root, *argv, popenfile, strdup(*argv), -1);
 
-       jump[0] = 0;
+       drawlock(1);
        for(;;){
+               drawlock(0);
                i=event(&e);
+               drawlock(1);
+
                switch(i){
                case Emouse:
-                       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){
+                       } else if(m.buttons & 2){
                                o = m.xy;
-                               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++;
-                                       unlockdisplay(display);
-                                       esetcursor(&reading);
-                                       unloadpages(0);
-                                       showpage(current);
-                                       continue;
-                               }
-                               if(strncmp(s, "rotate ", 7)==0){
-                                       rotate += atoi(s+7);
-                                       rotate %= 360;
-                                       goto Unload;
+                               i = emenuhit(2, &m, &cmdmenu);
+                               m.xy = o;
+                               docmd(i, &m);
+                       } else if(m.buttons & 4){
+                               if(root->down){
+                                       Page *x;
+
+                                       qlock(&pagelock);
+                                       pagemenu.lasthit = pageindex(current);
+                                       x = pageat(emenuhit(3, &m, &pagemenu));
+                                       qunlock(&pagelock);
+                                       forward = 0;
+                                       showpage(x);
                                }
-                               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(o, 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)
-                                       shownext();
-                               if(strcmp(s, "prev")==0)
-                                       showprev();
-                               if(strcmp(s, "zerox")==0)
-                                       zerox(current);
-                               if(strcmp(s, "quit")==0)
-                                       exits(0);
-                               continue;
+                       } else if(m.buttons & 8){
+                               scroll(screen->r.min.y - m.xy.y);
+                       } else if(m.buttons & 16){
+                               scroll(m.xy.y - screen->r.min.y);
                        }
-                       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:
                        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;
-                               }
-                               unlockdisplay(display);
-                               qunlock(current);
-                               if(prevpage(current))
-                                       pos.y = 0;
-                       case '-':
-                       case Kbs:
-                       case Kleft:
-                               showprev();
+                               scroll(-Dy(screen->r)/3);
+                               break;
+                       case Kpgup:
+                               scroll(-Dy(screen->r)/2);
                                break;
                        case Kdown:
-                               if(current == nil || !canqlock(current))
+                               scroll(Dy(screen->r)/3);
+                               break;
+                       case Kpgdown:
+                               scroll(Dy(screen->r)/2);
+                               break;
+                       default:
+                               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)){
+                                       docmd(i, &m);
                                        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;
                                }
-                               unlockdisplay(display);
-                               qunlock(current);
-                               if(nextpage(current))
-                                       pos.y = 0;
-                       case '\n':
-                               if(jump[0]){
-                                       showpage(findpage(jump));
-                                       jump[0] = 0;
+                               if((e.kbdc < 0x20) || 
+                                  (e.kbdc & 0xFF00) == KF || 
+                                  (e.kbdc & 0xFF00) == Spec)
                                        break;
-                               }
-                       case ' ':
-                       case Kright:
-                               shownext();
-                               break;
-                       default:
-                               i = strlen(jump);
-                               if(i+1 < sizeof(jump)){
-                                       jump[i] = e.kbdc;
-                                       jump[i+1] = 0;
+                               snprint(buf, sizeof(buf), "%C", (Rune)e.kbdc);
+                               if(eenter("Go to", buf, sizeof(buf), &m) > 0){
+                                       forward = 0;
+                                       showpage(findpage(buf));
                                }
                        }
                        break;
                case Eplumb:
                        pm = e.v;
                        if(pm && pm->ndata > 0){
+                               Page *j;
                                int fd;
 
                                fd = -1;
@@ -1433,7 +1797,15 @@ main(int argc, char *argv[])
                                        sprint(s, "%s/%s", pm->wdir, pm->data);
                                        cleanname(s);
                                }
-                               showpage(addpage(root, shortname(s), popenfile, s, fd));
+                               j = trywalk(s, plumblookup(pm->attr, "addr"));
+                               if(j == nil){
+                                       current = root;
+                                       drawlock(0);
+                                       j = addpage(root, s, popenfile, s, fd);
+                                       drawlock(1);
+                               }
+                               forward = 0;
+                               showpage(j);
                        }
                Plumbfree:
                        plumbfree(pm);