Subversion Repositories planix.SVN

Rev

Rev 2 | Blame | Compare with Previous | Last modification | View Log | RSS feed

/*
 * the actual viewer that handles screen stuff
 */

#include <u.h>
#include <libc.h>
#include <draw.h>
#include <cursor.h>
#include <event.h>
#include <bio.h>
#include <plumb.h>
#include <ctype.h>
#include <keyboard.h>
#include "page.h"

Document *doc;
Image *im;
Image *tofree;
int page;
int angle = 0;
int showbottom = 0;             /* on the next showpage, move the image so the bottom is visible. */

Rectangle ulrange;      /* the upper left corner of the image must be in this rectangle */
Point ul;                       /* the upper left corner of the image is at this point on the screen */

Point pclip(Point, Rectangle);
Rectangle mkrange(Rectangle screenr, Rectangle imr);
void redraw(Image*);

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, }
};

Cursor query = {
        {-7,-7},
        {0x0f, 0xf0, 0x1f, 0xf8, 0x3f, 0xfc, 0x7f, 0xfe, 
         0x7c, 0x7e, 0x78, 0x7e, 0x00, 0xfc, 0x01, 0xf8, 
         0x03, 0xf0, 0x07, 0xe0, 0x07, 0xc0, 0x07, 0xc0, 
         0x07, 0xc0, 0x07, 0xc0, 0x07, 0xc0, 0x07, 0xc0, },
        {0x00, 0x00, 0x0f, 0xf0, 0x1f, 0xf8, 0x3c, 0x3c, 
         0x38, 0x1c, 0x00, 0x3c, 0x00, 0x78, 0x00, 0xf0, 
         0x01, 0xe0, 0x03, 0xc0, 0x03, 0x80, 0x03, 0x80, 
         0x00, 0x00, 0x03, 0x80, 0x03, 0x80, 0x00, 0x00, }
};

enum {
        Left = 1,
        Middle = 2,
        Right = 4,

        RMenu = 3,
};

static void
delayfreeimage(Image *m)
{
        if(m == tofree)
                return;
        if(tofree)
                freeimage(tofree);
        tofree = m;
}

void
unhide(void)
{
        static int wctl = -1;

        if(wctl < 0)
                wctl = open("/dev/wctl", OWRITE);
        if(wctl < 0)
                return;

        write(wctl, "unhide", 6);
}

int 
max(int a, int b)
{
        return a > b ? a : b;
}

int 
min(int a, int b)
{
        return a < b ? a : b;
}


char*
menugen(int n)
{
        static char menustr[32];
        char *p;
        int len;

        if(n == doc->npage)
                return "exit";
        if(n > doc->npage)
                return nil;

        if(reverse)
                n = doc->npage-1-n;

        p = doc->pagename(doc, n);
        len = (sizeof menustr)-2;

        if(strlen(p) > len && strrchr(p, '/'))
                p = strrchr(p, '/')+1;
        if(strlen(p) > len)
                p = p+strlen(p)-len;

        strcpy(menustr+1, p);
        if(page == n)
                menustr[0] = '>';
        else
                menustr[0] = ' ';
        return menustr;
}

void
showpage(int page, Menu *m)
{
        if(doc->fwdonly)
                m->lasthit = 0; /* this page */
        else
                m->lasthit = reverse ? doc->npage-1-page : page;
        
        esetcursor(&reading);
        delayfreeimage(nil);
        im = cachedpage(doc, angle, page);
        if(im == nil)
                wexits(0);
        if(resizing)
                resize(Dx(im->r), Dy(im->r));

        esetcursor(nil);
        if(showbottom){
                ul.y = screen->r.max.y - Dy(im->r);
                showbottom = 0;
        }

        redraw(screen);
        flushimage(display, 1);
}

char*
writebitmap(void)
{
        char basename[64];
        char name[64+30];
        static char result[200];
        char *p, *q;
        int fd;

        if(im == nil)
                return "no image";

        memset(basename, 0, sizeof basename);
        if(doc->docname)
                strncpy(basename, doc->docname, sizeof(basename)-1);
        else if((p = menugen(page)) && p[0] != '\0')
                strncpy(basename, p+1, sizeof(basename)-1);

        if(basename[0]) {
                if(q = strrchr(basename, '/'))
                        q++;
                else
                        q = basename;
                if(p = strchr(q, '.'))
                        *p = 0;
                
                memset(name, 0, sizeof name);
                snprint(name, sizeof(name)-1, "%s.%d.bit", q, page+1);
                if(access(name, 0) >= 0) {
                        strcat(name, "XXXX");
                        mktemp(name);
                }
                if(access(name, 0) >= 0)
                        return "couldn't think of a name for bitmap";
        } else {
                strcpy(name, "bitXXXX");
                mktemp(name);
                if(access(name, 0) >= 0) 
                        return "couldn't think of a name for bitmap";
        }

        if((fd = create(name, OWRITE, 0666)) < 0) {
                snprint(result, sizeof result, "cannot create %s: %r", name);
                return result;
        }

        if(writeimage(fd, im, 0) < 0) {
                snprint(result, sizeof result, "cannot writeimage: %r");
                close(fd);
                return result;
        }
        close(fd);

        snprint(result, sizeof result, "wrote %s", name);
        return result;
}

static void translate(Point);

static int
showdata(Plumbmsg *msg)
{
        char *s;

        s = plumblookup(msg->attr, "action");
        return s && strcmp(s, "showdata")==0;
}

static int
plumbquit(Plumbmsg *msg)
{
        char *s;

        s = plumblookup(msg->attr, "action");
        return s && strcmp(s, "quit")==0;
}

/* correspond to entries in miditems[] below,
 * changing one means you need to change
 */
enum{
        Restore = 0,
        Zin,
        Fit,
        Rot,
        Upside,
        Empty1,
        Next,
        Prev,
        Zerox,
        Empty2,
        Reverse,
        Del,
        Write,
        Empty3,
        Exit,
};
 
void
viewer(Document *dd)
{
        int i, fd, n, oldpage;
        int nxt;
        Menu menu, midmenu;
        Mouse m;
        Event e;
        Point dxy, oxy, xy0;
        Rectangle r;
        Image *tmp;
        static char *fwditems[] = { "this page", "next page", "exit", 0 };
        static char *miditems[] = {
                "orig size",
                "zoom in",
                "fit window",
                "rotate 90",
                "upside down",
                "",
                "next",
                "prev",
                "zerox",
                "", 
                "reverse",
                "discard",
                "write",
                "", 
                "quit", 
                0 
        };
        char *s;
        enum { Eplumb = 4 };
        Plumbmsg *pm;

        doc = dd;    /* save global for menuhit */
        ul = screen->r.min;
        einit(Emouse|Ekeyboard);
        if(doc->addpage != nil)
                eplumb(Eplumb, "image");

        esetcursor(&reading);
        r.min = ZP;

        /*
         * im is a global pointer to the current image.
         * eventually, i think we will have a layer between
         * the display routines and the ps/pdf/whatever routines
         * to perhaps cache and handle images of different
         * sizes, etc.
         */
        im = 0;
        page = reverse ? doc->npage-1 : 0;

        if(doc->fwdonly) {
                menu.item = fwditems;
                menu.gen = 0;
                menu.lasthit = 0;
        } else {
                menu.item = 0;
                menu.gen = menugen;
                menu.lasthit = 0;
        }

        midmenu.item = miditems;
        midmenu.gen = 0;
        midmenu.lasthit = Next;

        if(doc->docname != nil)
                setlabel(doc->docname);
        showpage(page, &menu);
        esetcursor(nil);

        nxt = 0;
        for(;;) {
                /*
                 * throughout, if doc->fwdonly is set, we restrict the functionality
                 * a fair amount.  we don't care about doc->npage anymore, and
                 * all that can be done is select the next page.
                 */
                unlockdisplay(display);
                i = eread(Emouse|Ekeyboard|Eplumb, &e);
                lockdisplay(display);
                switch(i){
                case Ekeyboard:
                        if(e.kbdc <= 0xFF && isdigit(e.kbdc)) {
                                nxt = nxt*10+e.kbdc-'0';
                                break;
                        } else if(e.kbdc != '\n')
                                nxt = 0;
                        switch(e.kbdc) {
                        case 'r':       /* reverse page order */
                                if(doc->fwdonly)
                                        break;
                                reverse = !reverse;
                                menu.lasthit = doc->npage-1-menu.lasthit;

                                /*
                                 * the theory is that if we are reversing the
                                 * document order and are on the first or last
                                 * page then we're just starting and really want
                                 * to view the other end.  maybe the if
                                 * should be dropped and this should happen always.
                                 */
                                if(page == 0 || page == doc->npage-1) {
                                        page = doc->npage-1-page;
                                        showpage(page, &menu);
                                }
                                break;
                        case 'w':       /* write bitmap of current screen */
                                esetcursor(&reading);
                                s = writebitmap();
                                if(s)
                                        string(screen, addpt(screen->r.min, Pt(5,5)), display->black, ZP,
                                                display->defaultfont, s);
                                esetcursor(nil);
                                flushimage(display, 1);
                                break;
                        case 'd':       /* remove image from working set */
                                if(doc->rmpage && page < doc->npage) {
                                        if(doc->rmpage(doc, page) >= 0) {
                                                if(doc->npage < 0)
                                                        wexits(0);
                                                if(page >= doc->npage)
                                                        page = doc->npage-1;
                                                showpage(page, &menu);
                                        }
                                }
                                break;
                        case 'q':
                        case 0x04: /* ctrl-d */
                                wexits(0);
                        case 'u':
                                if(im==nil)
                                        break;
                                angle = (angle+180) % 360;
                                showpage(page, &menu);
                                break;
                        case '-':
                        case '\b':
                        case Kleft:
                                if(page > 0 && !doc->fwdonly) {
                                        --page;
                                        showpage(page, &menu);
                                }
                                break;
                        case '\n':
                                if(nxt) {
                                        nxt--;
                                        if(nxt >= 0 && nxt < doc->npage && !doc->fwdonly)
                                                showpage(page=nxt, &menu);
                                        nxt = 0;
                                        break;
                                }
                                goto Gotonext;
                        case Kright:
                        case ' ':
                        Gotonext:
                                if(doc->npage && ++page >= doc->npage && !doc->fwdonly)
                                        wexits(0);
                                showpage(page, &menu);
                                break;

                        /*
                         * The upper y coordinate of the image is at ul.y in screen->r.
                         * Panning up means moving the upper left corner down.  If the
                         * upper left corner is currently visible, we need to go back a page.
                         */
                        case Kup:
                                if(screen->r.min.y <= ul.y && ul.y < screen->r.max.y){
                                        if(page > 0 && !doc->fwdonly){
                                                --page;
                                                showbottom = 1;
                                                showpage(page, &menu);
                                        }
                                } else {
                                        i = Dy(screen->r)/2;
                                        if(i > 10)
                                                i -= 10;
                                        if(i+ul.y > screen->r.min.y)
                                                i = screen->r.min.y - ul.y;
                                        translate(Pt(0, i));
                                }
                                break;

                        /*
                         * If the lower y coordinate is on the screen, we go to the next page.
                         * The lower y coordinate is at ul.y + Dy(im->r).
                         */
                        case Kdown:
                                i = ul.y + Dy(im->r);
                                if(screen->r.min.y <= i && i <= screen->r.max.y){
                                        ul.y = screen->r.min.y;
                                        goto Gotonext;
                                } else {
                                        i = -Dy(screen->r)/2;
                                        if(i < -10)
                                                i += 10;
                                        if(i+ul.y+Dy(im->r) <= screen->r.max.y)
                                                i = screen->r.max.y - Dy(im->r) - ul.y - 1;
                                        translate(Pt(0, i));
                                }
                                break;
                        default:
                                esetcursor(&query);
                                sleep(1000);
                                esetcursor(nil);
                                break;  
                        }
                        break;

                case Emouse:
                        m = e.mouse;
                        switch(m.buttons){
                        case Left:
                                oxy = m.xy;
                                xy0 = oxy;
                                do {
                                        dxy = subpt(m.xy, oxy);
                                        oxy = m.xy;     
                                        translate(dxy);
                                        unlockdisplay(display);
                                        m = emouse();
                                        lockdisplay(display);
                                } while(m.buttons == Left);
                                if(m.buttons) {
                                        dxy = subpt(xy0, oxy);
                                        translate(dxy);
                                }
                                break;
        
                        case Middle:
                                if(doc->npage == 0)
                                        break;

                                unlockdisplay(display);
                                n = emenuhit(Middle, &m, &midmenu);
                                lockdisplay(display);
                                if(n == -1)
                                        break;
                                switch(n){
                                case Next:      /* next */
                                        if(reverse)
                                                page--;
                                        else
                                                page++;
                                        if(page < 0) {
                                                if(reverse) return;
                                                else page = 0;
                                        }

                                        if((page >= doc->npage) && !doc->fwdonly)
                                                return;
        
                                        showpage(page, &menu);
                                        nxt = 0;
                                        break;
                                case Prev:      /* prev */
                                        if(reverse)
                                                page++;
                                        else
                                                page--;
                                        if(page < 0) {
                                                if(reverse) return;
                                                else page = 0;
                                        }

                                        if((page >= doc->npage) && !doc->fwdonly && !reverse)
                                                return;
        
                                        showpage(page, &menu);
                                        nxt = 0;
                                        break;
                                case Zerox:     /* prev */
                                        zerox();
                                        break;
                                case Zin:       /* zoom in */
                                        {
                                                double delta;
                                                Rectangle r;

                                                r = egetrect(Middle, &m);
                                                if((rectclip(&r, rectaddpt(im->r, ul)) == 0) ||
                                                        Dx(r) == 0 || Dy(r) == 0)
                                                        break;
                                                /* use the smaller side to expand */
                                                if(Dx(r) < Dy(r))
                                                        delta = (double)Dx(im->r)/(double)Dx(r);
                                                else
                                                        delta = (double)Dy(im->r)/(double)Dy(r);

                                                esetcursor(&reading);
                                                tmp = xallocimage(display, 
                                                                Rect(0, 0, (int)((double)Dx(im->r)*delta), (int)((double)Dy(im->r)*delta)), 
                                                                im->chan, 0, DBlack);
                                                if(tmp == nil) {
                                                        fprint(2, "out of memory during zoom: %r\n");
                                                        wexits("memory");
                                                }
                                                resample(im, tmp);
                                                im = tmp;
                                                delayfreeimage(tmp);
                                                esetcursor(nil);
                                                ul = screen->r.min;
                                                redraw(screen);
                                                flushimage(display, 1);
                                                break;
                                        }
                                case Fit:       /* fit */
                                        {
                                                double delta;
                                                Rectangle r;
                                                
                                                delta = (double)Dx(screen->r)/(double)Dx(im->r);
                                                if((double)Dy(im->r)*delta > Dy(screen->r))
                                                        delta = (double)Dy(screen->r)/(double)Dy(im->r);

                                                r = Rect(0, 0, (int)((double)Dx(im->r)*delta), (int)((double)Dy(im->r)*delta));
                                                esetcursor(&reading);
                                                tmp = xallocimage(display, r, im->chan, 0, DBlack);
                                                if(tmp == nil) {
                                                        fprint(2, "out of memory during fit: %r\n");
                                                        wexits("memory");
                                                }
                                                resample(im, tmp);
                                                im = tmp;
                                                delayfreeimage(tmp);
                                                esetcursor(nil);
                                                ul = screen->r.min;
                                                redraw(screen);
                                                flushimage(display, 1);
                                                break;
                                        }
                                case Rot:       /* rotate 90 */
                                        angle = (angle+90) % 360;
                                        showpage(page, &menu);
                                        break;
                                case Upside:    /* upside-down */
                                        angle = (angle+180) % 360;
                                        showpage(page, &menu);
                                        break;
                                case Restore:   /* restore */
                                        showpage(page, &menu);
                                        break;
                                case Reverse:   /* reverse */
                                        if(doc->fwdonly)
                                                break;
                                        reverse = !reverse;
                                        menu.lasthit = doc->npage-1-menu.lasthit;
        
                                        if(page == 0 || page == doc->npage-1) {
                                                page = doc->npage-1-page;
                                                showpage(page, &menu);
                                        }
                                        break;
                                case Write: /* write */
                                        esetcursor(&reading);
                                        s = writebitmap();
                                        if(s)
                                                string(screen, addpt(screen->r.min, Pt(5,5)), display->black, ZP,
                                                        display->defaultfont, s);
                                        esetcursor(nil);
                                        flushimage(display, 1);
                                        break;
                                case Del: /* delete */
                                        if(doc->rmpage && page < doc->npage) {
                                                if(doc->rmpage(doc, page) >= 0) {
                                                        if(doc->npage < 0)
                                                                wexits(0);
                                                        if(page >= doc->npage)
                                                                page = doc->npage-1;
                                                        showpage(page, &menu);
                                                }
                                        }
                                        break;
                                case Exit:      /* exit */
                                        return;
                                case Empty1:
                                case Empty2:
                                case Empty3:
                                        break;

                                }; 

        
        
                        case Right:
                                if(doc->npage == 0)
                                        break;

                                oldpage = page;
                                unlockdisplay(display);
                                n = emenuhit(RMenu, &m, &menu);
                                lockdisplay(display);
                                if(n == -1)
                                        break;
        
                                if(doc->fwdonly) {
                                        switch(n){
                                        case 0:  /* this page */
                                                break;
                                        case 1: /* next page */
                                                showpage(++page, &menu);
                                                break;
                                        case 2: /* exit */
                                                return;
                                        }
                                        break;
                                }
        
                                if(n == doc->npage)
                                        return;
                                else
                                        page = reverse ? doc->npage-1-n : n;
        
                                if(oldpage != page)
                                        showpage(page, &menu);
                                nxt = 0;
                                break;
                        }
                        break;

                case Eplumb:
                        pm = e.v;
                        if(pm->ndata <= 0){
                                plumbfree(pm);
                                break;
                        }
                        if(plumbquit(pm))
                                exits(nil);
                        if(showdata(pm)) {
                                s = estrdup("/tmp/pageplumbXXXXXXX");
                                fd = opentemp(s);
                                write(fd, pm->data, pm->ndata);
                                /* lose fd reference on purpose; the file is open ORCLOSE */
                        } else if(pm->data[0] == '/') {
                                s = estrdup(pm->data);
                        } else {
                                s = emalloc(strlen(pm->wdir)+1+pm->ndata+1);
                                sprint(s, "%s/%s", pm->wdir, pm->data);
                                cleanname(s);
                        }
                        if((i = doc->addpage(doc, s)) >= 0) {
                                page = i;
                                unhide();
                                showpage(page, &menu);
                        }
                        free(s);
                        plumbfree(pm);
                        break;
                }
        }
}

Image *gray;

/*
 * 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;

        USED(op);

        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;
        }
}

static void
drawdiff(Image *dst, Rectangle bot, Rectangle top, Image *src, Image *mask, Point p, int op)
{
        gendrawdiff(dst, bot, top, src, p, mask, p, op);
}

/*
 * Translate the image in the window by delta.
 */
static void
translate(Point delta)
{
        Point u;
        Rectangle r, or;

        if(im == nil)
                return;

        u = pclip(addpt(ul, delta), ulrange);
        delta = subpt(u, ul);
        if(delta.x == 0 && delta.y == 0)
                return;

        /*
         * The upper left corner of the image is currently at ul.
         * We want to move it to u.
         */
        or = rectaddpt(Rpt(ZP, Pt(Dx(im->r), Dy(im->r))), ul);
        r = rectaddpt(or, delta);

        drawop(screen, r, screen, nil, ul, S);
        ul = u;

        /* fill in gray where image used to be but isn't. */
        drawdiff(screen, insetrect(or, -2), insetrect(r, -2), gray, nil, ZP, S);

        /* fill in black border */
        drawdiff(screen, insetrect(r, -2), r, display->black, nil, ZP, S);

        /* fill in image where it used to be off the screen. */
        if(rectclip(&or, screen->r))
                drawdiff(screen, r, rectaddpt(or, delta), im, nil, im->r.min, S);
        else
                drawop(screen, r, im, nil, im->r.min, S);
        flushimage(display, 1);
}

void
redraw(Image *screen)
{
        Rectangle r;

        if(im == nil)
                return;

        ulrange.max = screen->r.max;
        ulrange.min = subpt(screen->r.min, Pt(Dx(im->r), Dy(im->r)));

        ul = pclip(ul, ulrange);
        drawop(screen, screen->r, im, nil, subpt(im->r.min, subpt(ul, screen->r.min)), S);

        if(im->repl)
                return;

        /* fill in any outer edges */
        /* black border */
        r = rectaddpt(im->r, subpt(ul, im->r.min));
        border(screen, r, -2, display->black, ZP);
        r.min = subpt(r.min, Pt(2,2));
        r.max = addpt(r.max, Pt(2,2));

        /* gray for the rest */
        if(gray == nil) {
                gray = xallocimage(display, Rect(0,0,1,1), RGB24, 1, 0x888888FF);
                if(gray == nil) {
                        fprint(2, "g out of memory: %r\n");
                        wexits("mem");
                }
        }
        border(screen, r, -4000, gray, ZP);
//      flushimage(display, 0); 
}

void
eresized(int new)
{
        Rectangle r;
        r = screen->r;
        if(new && getwindow(display, Refnone) < 0)
                fprint(2,"can't reattach to window");
        ul = addpt(ul, subpt(screen->r.min, r.min));
        redraw(screen);
}

/* clip p to be in r */
Point
pclip(Point p, Rectangle r)
{
        if(p.x < r.min.x)
                p.x = r.min.x;
        else if(p.x >= r.max.x)
                p.x = r.max.x-1;

        if(p.y < r.min.y)
                p.y = r.min.y;
        else if(p.y >= r.max.y)
                p.y = r.max.y-1;

        return p;
}

/*
 * resize is perhaps a misnomer. 
 * this really just grows the window to be at least dx across
 * and dy high.  if the window hits the bottom or right edge,
 * it is backed up until it hits the top or left edge.
 */
void
resize(int dx, int dy)
{
        static Rectangle sr;
        Rectangle r, or;

        dx += 2*Borderwidth;
        dy += 2*Borderwidth;
        if(wctlfd < 0){
                wctlfd = open("/dev/wctl", OWRITE);
                if(wctlfd < 0)
                        return;
        }

        r = insetrect(screen->r, -Borderwidth);
        if(Dx(r) >= dx && Dy(r) >= dy)
                return;

        if(Dx(sr)*Dy(sr) == 0)
                sr = screenrect();

        or = r;

        r.max.x = max(r.min.x+dx, r.max.x);
        r.max.y = max(r.min.y+dy, r.max.y);
        if(r.max.x > sr.max.x){
                if(Dx(r) > Dx(sr)){
                        r.min.x = 0;
                        r.max.x = sr.max.x;
                }else
                        r = rectaddpt(r, Pt(sr.max.x-r.max.x, 0));
        }
        if(r.max.y > sr.max.y){
                if(Dy(r) > Dy(sr)){
                        r.min.y = 0;
                        r.max.y = sr.max.y;
                }else
                        r = rectaddpt(r, Pt(0, sr.max.y-r.max.y));
        }

        /*
         * Sometimes we can't actually grow the window big enough,
         * and resizing it to the same shape makes it flash.
         */
        if(Dx(r) == Dx(or) && Dy(r) == Dy(or))
                return;

        fprint(wctlfd, "resize -minx %d -miny %d -maxx %d -maxy %d\n",
                r.min.x, r.min.y, r.max.x, r.max.y);
}

/*
 * If we allocimage after a resize but before flushing the draw buffer,
 * we won't have seen the reshape event, and we won't have called
 * getwindow, and allocimage will fail.  So we flushimage before every alloc.
 */
Image*
xallocimage(Display *d, Rectangle r, ulong chan, int repl, ulong val)
{
        flushimage(display, 0);
        return allocimage(d, r, chan, repl, val);
}

/* all code below this line should be in the library, but is stolen from colors instead */
static char*
rdenv(char *name)
{
        char *v;
        int fd, size;

        fd = open(name, OREAD);
        if(fd < 0)
                return 0;
        size = seek(fd, 0, 2);
        v = malloc(size+1);
        if(v == 0){
                fprint(2, "page: can't malloc: %r\n");
                wexits("no mem");
        }
        seek(fd, 0, 0);
        read(fd, v, size);
        v[size] = 0;
        close(fd);
        return v;
}

void
newwin(void)
{
        char *srv, *mntsrv;
        char spec[100];
        int srvfd, cons, pid;

        switch(rfork(RFFDG|RFPROC|RFNAMEG|RFENVG|RFNOTEG|RFNOWAIT)){
        case -1:
                fprint(2, "page: can't fork: %r\n");
                wexits("no fork");
        case 0:
                break;
        default:
                wexits(0);
        }

        srv = rdenv("/env/wsys");
        if(srv == 0){
                mntsrv = rdenv("/mnt/term/env/wsys");
                if(mntsrv == 0){
                        fprint(2, "page: can't find $wsys\n");
                        wexits("srv");
                }
                srv = malloc(strlen(mntsrv)+10);
                sprint(srv, "/mnt/term%s", mntsrv);
                free(mntsrv);
                pid  = 0;                       /* can't send notes to remote processes! */
        }else
                pid = getpid();
        srvfd = open(srv, ORDWR);
        if(srvfd == -1){
                fprint(2, "page: can't open %s: %r\n", srv);
                wexits("no srv");
        }
        free(srv);
        sprint(spec, "new -pid %d", pid);
        if(mount(srvfd, -1, "/mnt/wsys", 0, spec) == -1){
                fprint(2, "page: can't mount /mnt/wsys: %r (spec=%s)\n", spec);
                wexits("no mount");
        }
        close(srvfd);
        unmount("/mnt/acme", "/dev");
        bind("/mnt/wsys", "/dev", MBEFORE);
        cons = open("/dev/cons", OREAD);
        if(cons==-1){
        NoCons:
                fprint(2, "page: can't open /dev/cons: %r");
                wexits("no cons");
        }
        dup(cons, 0);
        close(cons);
        cons = open("/dev/cons", OWRITE);
        if(cons==-1)
                goto NoCons;
        dup(cons, 1);
        dup(cons, 2);
        close(cons);
//      wctlfd = open("/dev/wctl", OWRITE);
}

Rectangle
screenrect(void)
{
        int fd;
        char buf[12*5];

        fd = open("/dev/screen", OREAD);
        if(fd == -1)
                fd=open("/mnt/term/dev/screen", OREAD);
        if(fd == -1){
                fprint(2, "page: can't open /dev/screen: %r\n");
                wexits("window read");
        }
        if(read(fd, buf, sizeof buf) != sizeof buf){
                fprint(2, "page: can't read /dev/screen: %r\n");
                wexits("screen read");
        }
        close(fd);
        return Rect(atoi(buf+12), atoi(buf+24), atoi(buf+36), atoi(buf+48));
}

void
zerox(void)
{
        int pfd[2];

        pipe(pfd);
        switch(rfork(RFFDG|RFREND|RFPROC)) {
                case -1:
                        wexits("cannot fork in zerox: %r");
                case 0: 
                        dup(pfd[1], 0);
                        close(pfd[0]);
                        execl("/bin/page", "page", "-w", nil);
                        wexits("cannot exec in zerox: %r\n");
                default:
                        close(pfd[1]);
                        writeimage(pfd[0], im, 0);
                        close(pfd[0]);
                        break;
        }
}