ref: 9e8b2bef252b18322aa341e1995d8ae70ea22938
dir: /sys/src/cmd/page.c/
#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; } } }