plan9fox/sys/src/cmd/page.c
2016-11-17 02:11:35 +01:00

1816 lines
33 KiB
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 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;
Image *frame, *paper, *ground;
char pagespool[] = "/tmp/pagespool.";
enum {
NPROC = 4,
NBUF = 8*1024,
NPATH = 1024,
};
enum {
Corigsize,
Czoomin,
Czoomout,
Cfitwidth,
Cfitheight,
Crotate90,
Cupsidedown,
Cdummy1,
Cnext,
Cprev,
Csnarf,
Czerox,
Cwrite,
Cext,
Cdummy2,
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,
[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 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 *);
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 -f1",
"application/doc", popengs, "doc2ps",
"application/zip", popentape, "fs/zipfs",
"application/x-tar", popentape, "fs/tarfs",
"application/x-ustar", popentape, "fs/tarfs",
"application/x-compress", popenfilter, "uncompress",
"application/x-gzip", popenfilter, "gunzip",
"application/x-bzip2", popenfilter, "bunzip2",
"image/gif", popenimg, "gif",
"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
loadpage(Page *p)
{
int fd;
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);
}
}
}
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(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);
}
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);
}
}
}
/*
* 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, frame, ZP);
gendrawdiff(screen, screen->r, insetrect(r, -Borderwidth), ground, ZP, nil, ZP, SoverD);
flushimage(display, 1);
}
void
drawpage(Page *p)
{
Rectangle r;
Image *i;
if((i = p->image) != nil){
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->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->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), 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 */
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;
switch(rfork(RFPROC|RFMEM)){
case -1:
sysfatal("rfork: %r");
case 0:
loadpages(p, oviewgen);
exits(nil);
}
if(++nproc >= NPROC)
if(waitpid() > 0)
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");
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;
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 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");
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");
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);
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;
}
}
}