ref: db10b4c70cf1c7eeaca2b51d415bdf0f37da23c7
parent: 4026a6866022dc165bebf5de58e60d16c1e11a11
author: qwx <[email protected]>
date: Sat Aug 19 05:12:17 EDT 2023
page: theming, delete and invert commands
--- /dev/null
+++ b/sys/src/cmd/page.c
@@ -1,0 +1,1956 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+#include <event.h>
+#include <cursor.h>
+#include <keyboard.h>
+#include <plumb.h>
+
+typedef struct Page Page;
+struct Page {
+ char *name;
+ char *delim;
+
+ QLock;
+ char *ext;
+ void *data;
+ int (*open)(Page *);
+
+ Image *image;
+ int fd;
+
+ Page *up;
+ Page *next;
+ Page *down;
+ Page *tail;
+
+ Page *lnext;
+ Page *lprev;
+};
+
+int zoom = 1;
+int ppi = 100;
+int imode;
+int newwin;
+int rotate;
+int invert;
+int viewgen;
+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;
+
+enum{
+ Cground,
+ Cpaper,
+ Cframe,
+ Ctext,
+ Ncols,
+};
+Image *cols[Ncols];
+
+char pagespool[] = "/tmp/pagespool.";
+
+enum {
+ NPROC = 8,
+ NBUF = 8*1024,
+ NPATH = 1024,
+};
+
+enum {
+ Corigsize,
+ Czoomin,
+ Czoomout,
+ Cfitwidth,
+ Cfitheight,
+ Crotate90,
+ Cupsidedown,
+ Cinvert,
+ Cdummy1,
+ Cnext,
+ Cprev,
+ Csnarf,
+ Czerox,
+ Cwrite,
+ Cext,
+ Cpop,
+ Cdummy2,
+ Cdelete,
+ Cdummy3,
+ Cquit,
+};
+
+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,
+ [Cinvert] "invert", 'i', 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,
+ [Cpop] "pop", 'p', 0, 0,
+ [Cdummy2] "", 0, 0, 0,
+ [Cdelete] "delete", 'D', 0, 0,
+ [Cdummy3] "", 0, 0, 0,
+ [Cquit] "quit", 'q', Kdel, Keof,
+};
+
+char *pagemenugen(int i);
+char *cmdmenugen(int i);
+
+Menu pagemenu = {
+ nil,
+ pagemenugen,
+ -1,
+};
+
+Menu cmdmenu = {
+ nil,
+ cmdmenugen,
+ -1,
+};
+
+Cursor reading = {
+ {-1, -1},
+ {0xff, 0x80, 0xff, 0x80, 0xff, 0x00, 0xfe, 0x00,
+ 0xff, 0x00, 0xff, 0x80, 0xff, 0xc0, 0xef, 0xe0,
+ 0xc7, 0xf0, 0x03, 0xf0, 0x01, 0xe0, 0x00, 0xc0,
+ 0x03, 0xff, 0x03, 0xff, 0x03, 0xff, 0x03, 0xff, },
+ {0x00, 0x00, 0x7f, 0x00, 0x7e, 0x00, 0x7c, 0x00,
+ 0x7e, 0x00, 0x7f, 0x00, 0x6f, 0x80, 0x47, 0xc0,
+ 0x03, 0xe0, 0x01, 0xf0, 0x00, 0xe0, 0x00, 0x40,
+ 0x00, 0x00, 0x01, 0xb6, 0x01, 0xb6, 0x00, 0x00, }
+};
+
+int pagewalk1(Page *p);
+void showpage1(Page *);
+void showpage(Page *);
+void drawpage(Page *);
+Point pagesize(Page *);
+void drawlock(int);
+
+Page*
+addpage(Page *up, char *name, int (*popen)(Page *), void *pdata, int fd)
+{
+ Page *p;
+
+ p = mallocz(sizeof(*p), 1);
+ p->name = strdup(name);
+ p->delim = "!";
+ p->image = nil;
+ p->data = pdata;
+ p->open = popen;
+ p->fd = fd;
+
+ qlock(&pagelock);
+ if(p->up = up){
+ if(up->tail == nil)
+ up->down = up->tail = p;
+ else {
+ up->tail->next = p;
+ up->tail = p;
+ }
+ }
+ qunlock(&pagelock);
+
+ if(up && current == up){
+ if(!pagewalk1(p))
+ return p;
+ showpage1(p);
+ }
+ return p;
+}
+
+void
+resizewin(Point size)
+{
+ int wctl;
+
+ if((wctl = open("/dev/wctl", OWRITE)) < 0)
+ 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);
+}
+
+int
+createtmp(char *pfx)
+{
+ static ulong id = 1;
+ char nam[64];
+ snprint(nam, sizeof nam, "%s%s%.12d%.8lux", pagespool, pfx, getpid(), id++);
+ return create(nam, OEXCL|ORCLOSE|ORDWR, 0600);
+}
+
+int
+catchnote(void *, char *msg)
+{
+ if(strstr(msg, "sys: write on closed pipe"))
+ return 1;
+ if(strstr(msg, "hangup"))
+ return 1;
+ if(strstr(msg, "alarm"))
+ return 1;
+ if(strstr(msg, "interrupt"))
+ return 1;
+ if(strstr(msg, "kill"))
+ exits("killed");
+ 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, ...)
+{
+ char buf[NPATH], *argv[4];
+ va_list arg;
+ int pfd[2];
+
+ if(pipe(pfd) < 0){
+ Err:
+ dup(nullfd, fd);
+ return;
+ }
+ 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);
+ argv[0] = "rc";
+ argv[1] = "-c";
+ argv[2] = buf;
+ argv[3] = nil;
+ exec("/bin/rc", argv);
+ sysfatal("exec: %r");
+ }
+ close(pfd[1]);
+ dup(pfd[0], fd);
+ 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*
+pageaddr(Page *p, char *buf, int nbuf)
+{
+ buf[0] = 0;
+ pageaddr1(p, buf, buf+nbuf);
+ return buf;
+}
+
+int
+popenfile(Page*);
+
+int
+popenimg(Page *p)
+{
+ char nam[NPATH];
+ int fd;
+
+ if((fd = dup(p->fd, -1)) < 0){
+ close(p->fd);
+ p->fd = -1;
+ return -1;
+ }
+
+ seek(fd, 0, 0);
+ 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
+ * be reopened.
+ */
+ fd2path(p->fd, nam, sizeof(nam));
+ if(strncmp(nam, pagespool, strlen(pagespool))){
+ close(p->fd);
+ p->fd = -1;
+ p->data = strdup(nam);
+ p->open = popenfile;
+ }
+
+ return fd;
+}
+
+int
+popenfilter(Page *p)
+{
+ seek(p->fd, 0, 0);
+ if(p->data){
+ pipeline(p->fd, "exec %s", (char*)p->data);
+ p->data = nil;
+ }
+ p->open = popenfile;
+ return p->open(p);
+}
+
+int
+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)(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;
+ return -1;
+ case 0:
+ dupfds(p->fd, 1, 2, -1);
+ argv[0] = "rc";
+ argv[1] = "-c";
+ argv[2] = cmd;
+ argv[3] = nil;
+ exec("/bin/rc", argv);
+ sysfatal("exec: %r");
+ }
+ close(p->fd);
+ waitpid();
+ p->fd = -1;
+ p->data = strdup(mnt);
+ p->open = popenfile;
+ return p->open(p);
+}
+
+int
+popenepub(Page *p)
+{
+ char buf[NPATH], *s, *e;
+ int n, fd;
+
+ fd = p->fd;
+ p->fd = -1;
+ s = buf;
+ e = buf+sizeof(buf)-1;
+ s += snprint(s, e-s, "%s/", (char*)p->data);
+ free(p->data);
+ p->data = nil;
+ pipeline(fd, "awk '/\\<rootfile/{"
+ "if(match($0, /full\\-path\\=\\\"([^\\\"]+)\\\"/)){"
+ "print substr($0, RSTART+11,RLENGTH-12);exit}}'");
+ n = read(fd, s, e - s);
+ close(fd);
+ if(n <= 0)
+ return -1;
+ while(n > 0 && s[n-1] == '\n')
+ n--;
+ s += n;
+ *s = 0;
+ if((fd = open(buf, OREAD)) < 0)
+ return -1;
+ pipeline(fd, "awk '/\\<item/{"
+ "if(match($0, /id\\=\\\"([^\\\"]+)\\\"/)){"
+ "id=substr($0, RSTART+4, RLENGTH-5);"
+ "if(match($0, /href\\=\\\"([^\\\"]+)\\\"/)){"
+ "item[id]=substr($0, RSTART+6, RLENGTH-7)}}};"
+ "/\\<itemref/{"
+ "if(match($0, /idref\\=\\\"([^\\\"]+)\\\"/)){"
+ "ref=substr($0, RSTART+7, RLENGTH-8);"
+ "print item[ref]; fflush}}'");
+ s = strrchr(buf, '/')+1;
+ while((n = read(fd, s, e-s)) > 0){
+ while(n > 0 && s[n-1] == '\n')
+ n--;
+ s[n] = 0;
+ addpage(p, s, popenfile, strdup(buf), -1);
+ }
+ close(fd);
+ return -1;
+}
+
+typedef struct Ghost Ghost;
+struct Ghost
+{
+ QLock;
+
+ int pin;
+ int pout;
+ int pdat;
+};
+
+int
+popenpdf(Page *p)
+{
+ char buf[NBUF];
+ int n, pfd[2];
+ Ghost *gs;
+
+ if(pipe(pfd) < 0)
+ return -1;
+ switch(rfork(RFPROC|RFMEM|RFFDG|RFNOWAIT)){
+ case -1:
+ close(pfd[0]);
+ close(pfd[1]);
+ return -1;
+ case 0:
+ gs = p->data;
+ qlock(gs);
+ 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->name);
+ while((n = read(0, buf, sizeof buf)) > 0){
+ if(memcmp(buf, "THIS IS NOT AN INFERNO BITMAP\n", 30) == 0)
+ break;
+ write(2, buf, n);
+ }
+ qunlock(gs);
+ exits(nil);
+ }
+ close(pfd[1]);
+ return pfd[0];
+}
+
+int
+infernobithdr(char *buf, int n)
+{
+ if(n >= 11){
+ if(memcmp(buf, "compressed\n", 11) == 0)
+ return 1;
+ if(strtochan((char*)buf))
+ return 1;
+ if(memcmp(buf, " ", 10) == 0 &&
+ '0' <= buf[10] && buf[10] <= '9' &&
+ buf[11] == ' ')
+ return 1;
+ }
+ return 0;
+}
+
+int
+popengs(Page *p)
+{
+ int n, i, pdf, ifd, ofd, pin[2], pout[2], pdat[2];
+ char buf[NBUF], nam[32], *argv[16];
+
+ pdf = 0;
+ ifd = p->fd;
+ p->fd = -1;
+ p->open = nil;
+ seek(ifd, 0, 0);
+ if(read(ifd, buf, 5) != 5)
+ goto Err0;
+ seek(ifd, 0, 0);
+ if(memcmp(buf, "%PDF-", 5) == 0)
+ pdf = 1;
+ if(pipe(pin) < 0){
+ Err0:
+ close(ifd);
+ return -1;
+ }
+ if(pipe(pout) < 0){
+ Err1:
+ close(pin[0]);
+ close(pin[1]);
+ goto Err0;
+ }
+ if(pipe(pdat) < 0){
+ Err2:
+ close(pdat[0]);
+ close(pdat[1]);
+ goto Err1;
+ }
+
+ 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], 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";
+ argv[3] = "-sOutputFile=/fd/3";
+ argv[4] = "-dBATCH";
+ argv[5] = pdf ? "-dDELAYSAFER" : "-dSAFER";
+ argv[6] = "-dQUIET";
+ argv[7] = "-dTextAlphaBits=4";
+ argv[8] = "-dGraphicsAlphaBits=4";
+ snprint(buf, sizeof buf, "-r%d", ppi);
+ argv[9] = buf;
+ argv[10] = "-dDOINTERPOLATE";
+ argv[11] = pdf ? "-" : "/fd/4";
+ argv[12] = nil;
+ exec("/bin/gs", argv);
+ sysfatal("exec: %r");
+ }
+
+ close(pin[1]);
+ close(pout[1]);
+ close(pdat[1]);
+ close(ifd);
+
+ if(pdf){
+ Ghost *gs;
+ char *prolog =
+ "/PAGEOUT (/fd/1) (w) file def\n"
+ "/PAGE== { PAGEOUT exch write==only PAGEOUT (\\n) writestring PAGEOUT flushfile } def\n"
+ "\n"
+ "/Page null def\n"
+ "/Page# 0 def\n"
+ "/PDFSave null def\n"
+ "/DSCPageCount 0 def\n"
+ "/DoPDFPage {dup /Page# exch store pdfgetpage pdfshowpage } def\n"
+ "\n"
+ "GS_PDF_ProcSet begin\n"
+ "pdfdict begin\n"
+ "(/fd/4) (r) file { DELAYSAFER { .setsafe } if } stopped pop pdfopen begin\n"
+ "\n"
+ "pdfpagecount PAGE==\n";
+
+ n = strlen(prolog);
+ if(write(pin[0], prolog, n) != n)
+ goto Out;
+ if((n = read(pout[0], buf, sizeof(buf)-1)) < 0)
+ goto Out;
+ buf[n] = 0;
+ n = atoi(buf);
+ if(n <= 0){
+ werrstr("no pages");
+ goto Out;
+ }
+ gs = mallocz(sizeof(*gs), 1);
+ gs->pin = pin[0];
+ gs->pout = pout[0];
+ gs->pdat = pdat[0];
+ for(i=1; i<=n; i++){
+ snprint(nam, sizeof nam, "%d", i);
+ addpage(p, nam, popenpdf, gs, -1);
+ }
+
+ /* keep ghostscript arround */
+ return -1;
+ } else {
+ i = 0;
+ ofd = -1;
+ 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, popenimg, nil, ofd);
+ ofd = -1;
+ }
+ if(n <= 0)
+ break;
+ if(ofd < 0){
+ snprint(nam, sizeof nam, "%.4d", ++i);
+ if((ofd = createtmp(nam)) < 0)
+ ofd = dup(nullfd, -1);
+ }
+ if(write(ofd, buf, n) != n)
+ break;
+ }
+ if(ofd >= 0)
+ close(ofd);
+ }
+Out:
+ close(pin[0]);
+ close(pout[0]);
+ close(pdat[0]);
+ 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)
+{
+ Dir *d1, *d2;
+
+ d1 = p1;
+ d2 = p2;
+
+ return strcmp(d1->name, d2->name);
+}
+
+int
+popenfile(Page *p)
+{
+ 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 /fd/0",
+ "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",
+ "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,
+ };
+
+ char buf[NBUF], typ[128], *file;
+ int i, n, fd, tfd;
+ Dir *d;
+
+ 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:
+ free(file);
+ return -1;
+ }
+ }
+ seek(fd, 0, 0);
+ if((d = dirfstat(fd)) == nil){
+ Err1:
+ close(fd);
+ goto Err0;
+ }
+ if(d->mode & DMDIR){
+ free(d);
+ d = nil;
+
+ snprint(buf, sizeof(buf), "%s/META-INF/container.xml", file);
+ if((tfd = open(buf, OREAD)) >= 0){
+ close(fd);
+ p->fd = tfd;
+ p->data = file;
+ 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);
+ for(i = 0; i<n; i++)
+ addpage(p, d[i].name, popenfile, smprint("%s/%s", file, d[i].name), -1);
+ free(d);
+ goto Err1;
+ }
+ free(d);
+
+ memset(buf, 0, NBUF/2);
+ if((n = readn(fd, buf, NBUF/2)) <= 0)
+ goto Err1;
+ 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 = readn(fd, buf+n, n)) < 0)
+ goto Err1;
+ if(i != n || memcmp(buf, buf+n, i)){
+ n += i;
+ Noseek:
+ if((tfd = createtmp("file")) < 0)
+ goto Err1;
+ while(n > 0){
+ if(write(tfd, buf, n) != n)
+ goto Err2;
+ if((n = read(fd, buf, sizeof(buf))) < 0)
+ goto Err2;
+ }
+ if(dup(tfd, fd) < 0){
+ Err2:
+ close(tfd);
+ goto Err1;
+ }
+ close(tfd);
+ }
+ free(file);
+ return p->open(p);
+}
+
+Page*
+nextpage(Page *p)
+{
+ if(p != nil && p->down != nil)
+ return p->down;
+ while(p != nil){
+ if(p->next != nil)
+ return p->next;
+ p = p->up;
+ }
+ return nil;
+}
+
+Page*
+prevpage(Page *x)
+{
+ Page *p, *t;
+
+ if(x != nil){
+ for(p = root->down; p != nil; p = t)
+ if((t = nextpage(p)) == x)
+ return p;
+ }
+ return nil;
+}
+
+int
+openpage(Page *p)
+{
+ int fd;
+
+ fd = -1;
+ if(p->open == nil || (fd = p->open(p)) < 0)
+ p->open = nil;
+ else {
+ if(rotate)
+ pipeline(fd, "exec rotate -r %d", rotate);
+ if(resize.x)
+ pipeline(fd, "exec resize -x %d", resize.x);
+ else if(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
+invertimage(Image *i)
+{
+ int n, m;
+ uchar *b;
+ uintptr *buf, *p;
+
+ n = imagesize(i);
+ if((buf = malloc(n)) == nil)
+ return;
+ unloadimage(i, i->r, (uchar*)buf, n);
+ m = n;
+ for(p=buf; m>=sizeof *p; m-=sizeof *p, p++)
+ *p = ~*p;
+ for(b=(uchar*)p; m>0; m--, b++)
+ *b = ~*b;
+ loadimage(i, i->r, (uchar*)buf, n);
+ free(buf);
+}
+
+void
+loadpage(Page *p)
+{
+ int fd;
+
+ 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);
+ if(invert)
+ invertimage(p->image);
+ imemsize += imagesize(p->image);
+ unlockdisplay(display);
+ }
+ }
+}
+
+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(ulong limit)
+{
+ Page *p;
+
+ while(imemsize >= limit && (p = lru.lprev) != &lru){
+ qlock(p);
+ unloadpage(p);
+ qunlock(p);
+ }
+}
+
+void
+loadpages(Page *p, int oviewgen)
+{
+ while(p != nil && viewgen == oviewgen){
+ qlock(&lru);
+ llinkhead(p);
+ qunlock(&lru);
+ if(!canqlock(p))
+ goto next;
+ 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);
+ }
+ qunlock(p);
+ next:
+ 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);
+ }
+ }
+}
+
+/* doesn't actually free the page entry or touch links to avoid breakage */
+Page*
+freepage(Page *p, Page *prev)
+{
+ Page *next, *up;
+
+ drawlock(0);
+ unloadpage(p);
+ drawlock(1);
+ if(p->fd >= 0)
+ close(p->fd);
+ p->fd = -1;
+ /* not touching p->data */
+ free(p->name);
+ p->name = nil;
+ p->open = nil;
+ next = nextpage(p);
+ up = p->up;
+ if(up->down == p){
+ if(up->tail != p)
+ up->down = next;
+ else
+ up->down = nil;
+ }else if(up->tail == p){
+ up->tail = prev;
+ prev->next = nil;
+ }else
+ prev->next = next;
+ return next;
+}
+
+Page*
+poppage(Page *p, int del)
+{
+ Page *t, *prev, *next;
+
+ if(p == nil)
+ return nil;
+ if(p == root)
+ return p;
+ if(del){
+ if(!(access(p->name, OREAD) == 0 && remove(p->name) == 0
+ || p->data != nil && access(p->data, OREAD) == 0 && remove(p->data) == 0)){
+ fprint(2, "remove %s: %r", p->name);
+ return p;
+ }
+ }
+ qlock(&pagelock);
+ for(t = p->down, prev = p; t != nil && t->up != p->up; prev = t, t = next){
+ qlock(t);
+ next = freepage(t, prev);
+ qunlock(t);
+ }
+ p->down = nil;
+ prev = prevpage(p);
+ next = freepage(p, prev);
+ qunlock(&pagelock);
+ qunlock(p);
+ if(next != nil){
+ forward = 1;
+ return next;
+ }
+ forward = -1;
+ return prev;
+}
+
+/*
+ * A draw operation that touches only the area contained in bot but not in top.
+ * mp and sp get aligned with bot.min.
+ */
+static void
+gendrawdiff(Image *dst, Rectangle bot, Rectangle top,
+ Image *src, Point sp, Image *mask, Point mp, int op)
+{
+ Rectangle r;
+ Point origin;
+ Point delta;
+
+ if(Dx(bot)*Dy(bot) == 0)
+ return;
+
+ /* no points in bot - top */
+ if(rectinrect(bot, top))
+ return;
+
+ /* bot - top ≡ bot */
+ if(Dx(top)*Dy(top)==0 || rectXrect(bot, top)==0){
+ gendrawop(dst, bot, src, sp, mask, mp, op);
+ return;
+ }
+
+ origin = bot.min;
+ /* split bot into rectangles that don't intersect top */
+ /* left side */
+ if(bot.min.x < top.min.x){
+ r = Rect(bot.min.x, bot.min.y, top.min.x, bot.max.y);
+ delta = subpt(r.min, origin);
+ gendrawop(dst, r, src, addpt(sp, delta), mask, addpt(mp, delta), op);
+ bot.min.x = top.min.x;
+ }
+
+ /* right side */
+ if(bot.max.x > top.max.x){
+ r = Rect(top.max.x, bot.min.y, bot.max.x, bot.max.y);
+ delta = subpt(r.min, origin);
+ gendrawop(dst, r, src, addpt(sp, delta), mask, addpt(mp, delta), op);
+ bot.max.x = top.max.x;
+ }
+
+ /* top */
+ if(bot.min.y < top.min.y){
+ r = Rect(bot.min.x, bot.min.y, bot.max.x, top.min.y);
+ delta = subpt(r.min, origin);
+ gendrawop(dst, r, src, addpt(sp, delta), mask, addpt(mp, delta), op);
+ bot.min.y = top.min.y;
+ }
+
+ /* bottom */
+ if(bot.max.y > top.max.y){
+ r = Rect(bot.min.x, top.max.y, bot.max.x, bot.max.y);
+ delta = subpt(r.min, origin);
+ gendrawop(dst, r, src, addpt(sp, delta), mask, addpt(mp, delta), op);
+ bot.max.y = top.max.y;
+ }
+}
+
+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 *b, Image *s, Point sp, int f)
+{
+ Rectangle dr;
+ Image *t;
+ Point a;
+ int w;
+
+ a = ZP;
+ if(r.min.x < d->r.min.x){
+ sp.x += (d->r.min.x - r.min.x)/f;
+ a.x = (d->r.min.x - r.min.x)%f;
+ r.min.x = d->r.min.x;
+ }
+ if(r.min.y < d->r.min.y){
+ sp.y += (d->r.min.y - r.min.y)/f;
+ a.y = (d->r.min.y - r.min.y)%f;
+ r.min.y = d->r.min.y;
+ }
+ rectclip(&r, d->r);
+ w = s->r.max.x - sp.x;
+ if(w > Dx(r))
+ w = Dx(r);
+ 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;
+ }
+ 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++;
+ }
+ }
+ dr = r;
+ for(sp=dr.min; dr.min.x < r.max.x; sp.x++){
+ dr.max.x = dr.min.x+1;
+ 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;
+ gendrawdiff(d, dr, top, d, Pt(dr.min.x-1, dr.min.y), nil, ZP, SoverD);
+ }
+ a.x = 0;
+ }
+ freeimage(t);
+}
+
+Point
+pagesize(Page *p)
+{
+ return p->image != nil ? mulpt(subpt(p->image->r.max, p->image->r.min), zoom) : ZP;
+}
+
+void
+drawframe(Rectangle r)
+{
+ border(screen, r, -Borderwidth, cols[Cframe], ZP);
+ gendrawdiff(screen, screen->r, insetrect(r, -Borderwidth), cols[Cground], ZP, nil, ZP, SoverD);
+ flushimage(display, 1);
+}
+
+void
+drawpage(Page *p)
+{
+ Rectangle r;
+ Image *i;
+
+ if((i = p->image) != nil){
+ r = rectaddpt(Rpt(ZP, pagesize(p)), addpt(pos, screen->r.min));
+ zoomdraw(screen, r, ZR, cols[Cpaper], i, i->r.min, zoom);
+ } else {
+ 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, cols[Cground], nil, ZP);
+ string(screen, r.min, cols[Ctext], ZP, font, p->name);
+ }
+ drawframe(r);
+}
+
+void
+translate(Page *p, Point d)
+{
+ Rectangle r, nr;
+ Image *i;
+
+ i = p->image;
+ 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);
+ if(rectclip(&r, screen->r))
+ draw(screen, rectaddpt(r, d), screen, nil, r.min);
+ else
+ r = ZR;
+ zoomdraw(screen, nr, rectaddpt(r, d), cols[Cpaper], 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 */
+ 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 */
+ 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*
+pageat(int i)
+{
+ Page *p;
+
+ for(p = root->down; i > 0 && p != nil; p = nextpage(p))
+ i--;
+ return i ? nil : p;
+}
+
+int
+pageindex(Page *x)
+{
+ Page *p;
+ int i;
+
+ for(i = 0, p = root->down; p != nil && p != x; p = nextpage(p))
+ i++;
+ return (p == x) ? i : -1;
+}
+
+char*
+pagemenugen(int i)
+{
+ Page *p;
+
+ 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
+showpage1(Page *p)
+{
+ static int nproc;
+ int oviewgen;
+
+ if(p == nil)
+ return;
+ esetcursor(&reading);
+ writeaddr(p, "/dev/label");
+ current = p;
+ oviewgen = viewgen;
+ if(nproc >= NPROC)
+ waitpid();
+ switch(rfork(RFPROC|RFMEM)){
+ case -1:
+ sysfatal("rfork: %r");
+ case 0:
+ loadpages(p, oviewgen);
+ nproc--;
+ exits(nil);
+ }
+ nproc++;
+}
+
+/* recursive display lock, called from main proc only */
+void
+drawlock(int dolock){
+ static int ref = 0;
+ if(dolock){
+ if(ref++ == 0)
+ lockdisplay(display);
+ } else {
+ if(--ref == 0)
+ unlockdisplay(display);
+ }
+}
+
+
+void
+showpage(Page *p)
+{
+ if(p == nil)
+ return;
+ drawlock(0);
+ unloadpages(imemlimit);
+ showpage1(p);
+ drawlock(1);
+}
+
+void
+zerox(Page *p)
+{
+ char nam[64], *argv[4];
+ int fd;
+
+ if(p == nil)
+ return;
+ drawlock(0);
+ qlock(p);
+ if((fd = openpage(p)) < 0)
+ goto Out;
+ 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";
+ argv[2] = nil;
+ exec(nam, argv);
+ sysfatal("exec: %r");
+ }
+ close(fd);
+Out:
+ qunlock(p);
+ 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
+eresized(int new)
+{
+ Page *p;
+
+ drawlock(1);
+ if(new && getwindow(display, Refnone) == -1)
+ sysfatal("getwindow: %r");
+ draw(screen, screen->r, cols[Cground], nil, ZP);
+ if((p = current) != nil){
+ if(canqlock(p)){
+ drawpage(p);
+ qunlock(p);
+ }
+ }
+ 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, cohort, "kill");
+ sleep(1);
+ }
+}
+
+void drawerr(Display *, char *msg)
+{
+ sysfatal("draw: %s", msg);
+}
+
+void
+usage(void)
+{
+ 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, del;
+ Page *p;
+
+ del = 0;
+ 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 Cinvert:
+ invert = !invert;
+ 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 Cdelete:
+ del = 1;
+ /* wet floor */
+ case Cpop:
+ if(current == nil || !canqlock(current))
+ break;
+ if((p = poppage(current, del)) == current){
+ qunlock(current);
+ break;
+ }
+ if((current = p) == nil){
+ drawlock(0);
+ draw(screen, screen->r, cols[Cground], nil, ZP);
+ drawframe(screen->r);
+ drawlock(1);
+ break;
+ }
+ showpage(current);
+ 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 buf[NPATH];
+ Plumbmsg *pm;
+ Point o;
+ Mouse m;
+ Event e;
+ char *s;
+ int i;
+
+ quotefmtinstall();
+
+ ARGBEGIN {
+ case 'a':
+ case 'v':
+ case 'V':
+ case 'P':
+ break;
+ case 'R':
+ if(newwin == 0)
+ newwin = -1;
+ break;
+ case 'w':
+ newwin = 1;
+ break;
+ 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;
+ default:
+ 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
+ */
+ atnotify(catchnote, 1);
+ if(cohort = rfork(RFPROC|RFNOTEG|RFNAMEG|RFREND)){
+ atexit(killcohort);
+ waitpid();
+ exits(0);
+ }
+ cohort = getpid();
+ atexit(killcohort);
+ if(initdraw(drawerr, nil, argv0) < 0)
+ sysfatal("initdraw: %r");
+ Theme th[nelem(cols)] = {
+ [Cground] { "back", 0x777777FF },
+ [Cpaper] { "paper", DWhite },
+ [Cframe] { "border", DBlack },
+ [Ctext] { "text", DBlack },
+ };
+ readtheme(th, nelem(th), nil);
+ for(i=0; i<nelem(cols); i++)
+ cols[i] = allocimage(display, Rect(0,0,1,1), screen->chan, 1, th[i].c);
+ display->locking = 1;
+ draw(screen, screen->r, cols[Cground], nil, ZP);
+ unlockdisplay(display);
+
+ einit(Ekeyboard|Emouse);
+ eplumb(Eplumb, "image");
+ memset(&m, 0, sizeof(m));
+ if((nullfd = open("/dev/null", ORDWR)) < 0)
+ sysfatal("open: %r");
+ 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, *argv, popenfile, strdup(*argv), -1);
+ for(i=0; i<NPROC/4; i++) /* rice */
+ showpage1(current);
+
+ drawlock(1);
+ for(;;){
+ drawlock(0);
+ i=event(&e);
+ drawlock(1);
+
+ switch(i){
+ case Emouse:
+ m = e.mouse;
+ if(m.buttons & 1){
+ if(current && canqlock(current)){
+ for(;;) {
+ o = m.xy;
+ m = emouse();
+ if((m.buttons & 1) == 0)
+ break;
+ translate(current, subpt(m.xy, o));
+ }
+ qunlock(current);
+ }
+ } else if(m.buttons & 2){
+ o = m.xy;
+ 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);
+ }
+ } 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);
+ }
+ break;
+ case Ekeyboard:
+ switch(e.kbdc){
+ case Kup:
+ scroll(-Dy(screen->r)/3);
+ break;
+ case Kpgup:
+ scroll(-Dy(screen->r)/2);
+ break;
+ case Kdown:
+ 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;
+ }
+ if((e.kbdc < 0x20) ||
+ (e.kbdc & 0xFF00) == KF ||
+ (e.kbdc & 0xFF00) == Spec)
+ break;
+ 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;
+ s = plumblookup(pm->attr, "action");
+ if(s && strcmp(s, "quit")==0)
+ exits(0);
+ if(s && strcmp(s, "showdata")==0){
+ if((fd = createtmp("plumb")) < 0){
+ fprint(2, "plumb: createtmp: %r\n");
+ goto Plumbfree;
+ }
+ s = malloc(NPATH);
+ if(fd2path(fd, s, NPATH) < 0){
+ close(fd);
+ goto Plumbfree;
+ }
+ write(fd, pm->data, pm->ndata);
+ }else if(pm->data[0] == '/'){
+ s = strdup(pm->data);
+ }else{
+ s = malloc(strlen(pm->wdir)+1+pm->ndata+1);
+ sprint(s, "%s/%s", pm->wdir, pm->data);
+ cleanname(s);
+ }
+ 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);
+ break;
+ }
+ }
+}