tmove these here - plan9port - [fork] Plan 9 from user space
 (HTM) git clone git://src.adamsgaard.dk/plan9port
 (DIR) Log
 (DIR) Files
 (DIR) Refs
 (DIR) README
 (DIR) LICENSE
       ---
 (DIR) commit e21fee604e3b53fd8c57ac3817e6d829f62d6ee3
 (DIR) parent f0f4401f0cfc654646bdf21849627ebcbd5d82b5
 (HTM) Author: rsc <devnull@localhost>
       Date:   Fri, 23 Apr 2004 05:14:58 +0000
       
       move these here
       
       Diffstat:
         A src/cmd/draw/stats.c                |     884 +++++++++++++++++++++++++++++++
         A src/cmd/draw/tweak.c                |    2058 +++++++++++++++++++++++++++++++
       
       2 files changed, 2942 insertions(+), 0 deletions(-)
       ---
 (DIR) diff --git a/src/cmd/draw/stats.c b/src/cmd/draw/stats.c
       t@@ -0,0 +1,884 @@
       +#include <u.h>
       +#include <libc.h>
       +#include <ctype.h>
       +#include <auth.h>
       +#include <fcall.h>
       +#include <draw.h>
       +#include <thread.h>
       +#include <mouse.h>
       +#include <keyboard.h>
       +
       +typedef struct Graph                Graph;
       +typedef struct Machine        Machine;
       +
       +enum
       +{
       +        Ncolor        = 6,
       +        Ysqueeze        = 2,        /* vertical squeezing of label text */
       +        Labspace        = 2,        /* room around label */
       +        Dot                = 2,        /* height of dot */
       +        Opwid        = 5,        /* strlen("add  ") or strlen("drop ") */
       +        Nlab                = 3,        /* max number of labels on y axis */
       +        Lablen        = 16,        /* max length of label */
       +        Lx                = 4,        /* label tick length */
       +
       +        STACK        = 8192,
       +        XSTACK        = 32768,
       +};
       +
       +enum
       +{
       +        Vbattery,
       +        Vcontext,
       +        Vcpu,
       +        Vether,
       +        Vethererr,
       +        Vetherin,
       +        Vetherout,
       +        Vfault,
       +        Vfork,
       +        Vidle,
       +        Vintr,
       +        Vload,
       +        Vmem,
       +        Vswap,
       +        Vsys,
       +        Vsyscall,
       +        Vuser,
       +        Nvalue,
       +};
       +
       +char*
       +labels[Nvalue] = 
       +{
       +        "battery",
       +        "context",
       +        "cpu",
       +        "ether",
       +        "ethererr",
       +        "etherin",
       +        "etherout",
       +        "fault",
       +        "fork",
       +        "idle",
       +        "intr",
       +        "load",
       +        "mem",
       +        "swap",
       +        "sys",
       +        "syscall",
       +        "user",
       +};        
       +
       +struct Graph
       +{
       +        int                colindex;
       +        Rectangle        r;
       +        int                *data;
       +        int                ndata;
       +        char                *label;
       +        int                value;
       +        void                (*update)(Graph*, ulong, ulong);
       +        Machine        *mach;
       +        int                overflow;
       +        Image        *overtmp;
       +        ulong        vmax;
       +};
       +
       +struct Machine
       +{
       +        char                *name;
       +        int                fd;
       +        int                pid;
       +        int                dead;
       +        int                absolute[Nvalue];
       +        ulong        last[Nvalue];
       +        ulong        val[Nvalue][2];
       +};
       +
       +char        *menu2str[Nvalue+1];
       +char xmenu2str[Nvalue+1][40];
       +
       +Menu        menu2 = {menu2str, nil};
       +int                present[Nvalue];
       +Image        *cols[Ncolor][3];
       +Graph        *graph;
       +Machine        *mach;
       +Font                *mediumfont;
       +char                *mysysname;
       +char                argchars[] = "bceEfiIlmnsw";
       +int                pids[1024];
       +int                 parity;        /* toggled to avoid patterns in textured background */
       +int                nmach;
       +int                ngraph;        /* totaly number is ngraph*nmach */
       +double        scale = 1.0;
       +int                logscale = 0;
       +int                ylabels = 0;
       +int                oldsystem = 0;
       +int                 sleeptime = 1000;
       +int                changedvmax;
       +
       +Mousectl *mc;
       +Keyboardctl *kc;
       +
       +void
       +killall(char *s)
       +{
       +        int i;
       +
       +        for(i=0; i<nmach; i++)
       +                if(mach[i].pid)
       +                        postnote(PNPROC, mach[i].pid, "kill");
       +        exits(s);
       +}
       +
       +void*
       +emalloc(ulong sz)
       +{
       +        void *v;
       +        v = malloc(sz);
       +        if(v == nil) {
       +                fprint(2, "stats: out of memory allocating %ld: %r\n", sz);
       +                killall("mem");
       +        }
       +        memset(v, 0, sz);
       +        return v;
       +}
       +
       +void*
       +erealloc(void *v, ulong sz)
       +{
       +        v = realloc(v, sz);
       +        if(v == nil) {
       +                fprint(2, "stats: out of memory reallocating %ld: %r\n", sz);
       +                killall("mem");
       +        }
       +        return v;
       +}
       +
       +char*
       +estrdup(char *s)
       +{
       +        char *t;
       +        if((t = strdup(s)) == nil) {
       +                fprint(2, "stats: out of memory in strdup(%.10s): %r\n", s);
       +                killall("mem");
       +        }
       +        return t;
       +}
       +
       +void
       +mkcol(int i, int c0, int c1, int c2)
       +{
       +        cols[i][0] = allocimagemix(display, c0, DWhite);
       +        cols[i][1] = allocimage(display, Rect(0,0,1,1), CMAP8, 1, c1);
       +        cols[i][2] = allocimage(display, Rect(0,0,1,1), CMAP8, 1, c2);
       +}
       +
       +void
       +colinit(void)
       +{
       +        mediumfont = openfont(display, "/lib/font/bit/pelm/latin1.8.font");
       +        if(mediumfont == nil)
       +                mediumfont = font;
       +
       +        /* Peach */
       +        mkcol(0, 0xFFAAAAFF, 0xFFAAAAFF, 0xBB5D5DFF);
       +        /* Aqua */
       +        mkcol(1, DPalebluegreen, DPalegreygreen, DPurpleblue);
       +        /* Yellow */
       +        mkcol(2, DPaleyellow, DDarkyellow, DYellowgreen);
       +        /* Green */
       +        mkcol(3, DPalegreen, DMedgreen, DDarkgreen);
       +        /* Blue */
       +        mkcol(4, 0x00AAFFFF, 0x00AAFFFF, 0x0088CCFF);
       +        /* Grey */
       +        cols[5][0] = allocimage(display, Rect(0,0,1,1), CMAP8, 1, 0xEEEEEEFF);
       +        cols[5][1] = allocimage(display, Rect(0,0,1,1), CMAP8, 1, 0xCCCCCCFF);
       +        cols[5][2] = allocimage(display, Rect(0,0,1,1), CMAP8, 1, 0x888888FF);
       +}
       +
       +void
       +label(Point p, int dy, char *text)
       +{
       +        char *s;
       +        Rune r[2];
       +        int w, maxw, maxy;
       +
       +        p.x += Labspace;
       +        maxy = p.y+dy;
       +        maxw = 0;
       +        r[1] = '\0';
       +        for(s=text; *s; ){
       +                if(p.y+mediumfont->height-Ysqueeze > maxy)
       +                        break;
       +                w = chartorune(r, s);
       +                s += w;
       +                w = runestringwidth(mediumfont, r);
       +                if(w > maxw)
       +                        maxw = w;
       +                runestring(screen, p, display->black, ZP, mediumfont, r);
       +                p.y += mediumfont->height-Ysqueeze;
       +        }
       +}
       +
       +Point
       +paritypt(int x)
       +{
       +        return Pt(x+parity, 0);
       +}
       +
       +Point
       +datapoint(Graph *g, int x, ulong v, ulong vmax)
       +{
       +        Point p;
       +        double y;
       +
       +        p.x = x;
       +        y = ((double)v)/(vmax*scale);
       +        if(logscale){
       +                /*
       +                 * Arrange scale to cover a factor of 1000.
       +                 * vmax corresponds to the 100 mark.
       +                 * 10*vmax is the top of the scale.
       +                 */
       +                if(y <= 0.)
       +                        y = 0;
       +                else{
       +                        y = log10(y);
       +                        /* 1 now corresponds to the top; -2 to the bottom; rescale */
       +                        y = (y+2.)/3.;
       +                }
       +        }
       +        p.y = g->r.max.y - Dy(g->r)*y - Dot;
       +        if(p.y < g->r.min.y)
       +                p.y = g->r.min.y;
       +        if(p.y > g->r.max.y-Dot)
       +                p.y = g->r.max.y-Dot;
       +        return p;
       +}
       +
       +void
       +drawdatum(Graph *g, int x, ulong prev, ulong v, ulong vmax)
       +{
       +        int c;
       +        Point p, q;
       +
       +        c = g->colindex;
       +        p = datapoint(g, x, v, vmax);
       +        q = datapoint(g, x, prev, vmax);
       +        if(p.y < q.y){
       +                draw(screen, Rect(p.x, g->r.min.y, p.x+1, p.y), cols[c][0], nil, paritypt(p.x));
       +                draw(screen, Rect(p.x, p.y, p.x+1, q.y+Dot), cols[c][2], nil, ZP);
       +                draw(screen, Rect(p.x, q.y+Dot, p.x+1, g->r.max.y), cols[c][1], nil, ZP);
       +        }else{
       +                draw(screen, Rect(p.x, g->r.min.y, p.x+1, q.y), cols[c][0], nil, paritypt(p.x));
       +                draw(screen, Rect(p.x, q.y, p.x+1, p.y+Dot), cols[c][2], nil, ZP);
       +                draw(screen, Rect(p.x, p.y+Dot, p.x+1, g->r.max.y), cols[c][1], nil, ZP);
       +        }
       +
       +}
       +
       +void
       +redraw(Graph *g, int vmax)
       +{
       +        int i, c;
       +
       +        if(vmax != g->vmax){
       +                g->vmax = vmax;
       +                changedvmax = 1;
       +        }
       +        c = g->colindex;
       +        draw(screen, g->r, cols[c][0], nil, paritypt(g->r.min.x));
       +        for(i=1; i<Dx(g->r); i++)
       +                drawdatum(g, g->r.max.x-i, g->data[i-1], g->data[i], vmax);
       +        drawdatum(g, g->r.min.x, g->data[i], g->data[i], vmax);
       +        g->overflow = 0;
       +}
       +
       +void
       +update1(Graph *g, ulong v, ulong vmax)
       +{
       +        char buf[32];
       +        int overflow;
       +
       +        if(vmax != g->vmax){
       +                g->vmax = vmax;
       +                changedvmax = 1;
       +        }
       +        if(g->overflow && g->overtmp!=nil)
       +                draw(screen, g->overtmp->r, g->overtmp, nil, g->overtmp->r.min);
       +        draw(screen, g->r, screen, nil, Pt(g->r.min.x+1, g->r.min.y));
       +        drawdatum(g, g->r.max.x-1, g->data[0], v, vmax);
       +        memmove(g->data+1, g->data, (g->ndata-1)*sizeof(g->data[0]));
       +        g->data[0] = v;
       +        g->overflow = 0;
       +        if(logscale)
       +                overflow = (v>10*vmax*scale);
       +        else
       +                overflow = (v>vmax*scale);
       +        if(overflow && g->overtmp!=nil){
       +                g->overflow = 1;
       +                draw(g->overtmp, g->overtmp->r, screen, nil, g->overtmp->r.min);
       +                sprint(buf, "%ld", v);
       +                string(screen, g->overtmp->r.min, display->black, ZP, mediumfont, buf);
       +        }
       +}
       +
       +void
       +usage(void)
       +{
       +        fprint(2, "usage: stats [-O] [-S scale] [-LY] [-%s] [machine...]\n", argchars);
       +        exits("usage");
       +}
       +
       +void
       +addgraph(int n)
       +{
       +        Graph *g, *ograph;
       +        int i, j;
       +        static int nadd;
       +
       +        if(n > Nvalue)
       +                abort();
       +        /* avoid two adjacent graphs of same color */
       +        if(ngraph>0 && graph[ngraph-1].colindex==nadd%Ncolor)
       +                nadd++;
       +        ograph = graph;
       +        graph = emalloc(nmach*(ngraph+1)*sizeof(Graph));
       +        for(i=0; i<nmach; i++)
       +                for(j=0; j<ngraph; j++)
       +                        graph[i*(ngraph+1)+j] = ograph[i*ngraph+j];
       +        free(ograph);
       +        ngraph++;
       +        for(i=0; i<nmach; i++){
       +                g = &graph[i*ngraph+(ngraph-1)];
       +                memset(g, 0, sizeof(Graph));
       +                g->value = n;
       +                g->label = menu2str[n]+Opwid;
       +                g->update = update1;        /* no other update functions yet */
       +                g->mach = &mach[i];
       +                g->colindex = nadd%Ncolor;
       +        }
       +        present[n] = 1;
       +        nadd++;
       +}
       +
       +void
       +dropgraph(int which)
       +{
       +        Graph *ograph;
       +        int i, j, n;
       +
       +        if(which > nelem(menu2str))
       +                abort();
       +        /* convert n to index in graph table */
       +        n = -1;
       +        for(i=0; i<ngraph; i++)
       +                if(strcmp(menu2str[which]+Opwid, graph[i].label) == 0){
       +                        n = i;
       +                        break;
       +                }
       +        if(n < 0){
       +                fprint(2, "stats: internal error can't drop graph\n");
       +                killall("error");
       +        }
       +        ograph = graph;
       +        graph = emalloc(nmach*(ngraph-1)*sizeof(Graph));
       +        for(i=0; i<nmach; i++){
       +                for(j=0; j<n; j++)
       +                        graph[i*(ngraph-1)+j] = ograph[i*ngraph+j];
       +                free(ograph[i*ngraph+j].data);
       +                freeimage(ograph[i*ngraph+j].overtmp);
       +                for(j++; j<ngraph; j++)
       +                        graph[i*(ngraph-1)+j-1] = ograph[i*ngraph+j];
       +        }
       +        free(ograph);
       +        ngraph--;
       +        present[which] = 0;
       +}
       +
       +int initmach(Machine*, char*);
       +
       +int
       +addmachine(char *name)
       +{
       +        if(ngraph > 0){
       +                fprint(2, "stats: internal error: ngraph>0 in addmachine()\n");
       +                usage();
       +        }
       +        if(mach == nil)
       +                nmach = 0;        /* a little dance to get us started with local machine by default */
       +        mach = erealloc(mach, (nmach+1)*sizeof(Machine));
       +        memset(mach+nmach, 0, sizeof(Machine));
       +        if (initmach(mach+nmach, name)){
       +                nmach++;
       +                return 1;
       +        } else
       +                return 0;
       +}
       +
       +void
       +newvalue(Machine *m, int i, ulong *v, ulong *vmax)
       +{
       +        ulong now;
       +
       +        if(m->absolute[i]){
       +                *v = m->val[i][0];
       +                *vmax = m->val[i][1];
       +        }else{
       +                now = m->val[i][0];
       +                *v = (vlong)((now - m->last[i])*sleeptime)/1000;
       +                m->last[i] = now;
       +                *vmax = m->val[i][1];
       +        }
       +        if(*vmax == 0)
       +                *vmax = 1;
       +}
       +
       +void
       +labelstrs(Graph *g, char strs[Nlab][Lablen], int *np)
       +{
       +        int j;
       +        ulong vmax;
       +
       +        vmax = g->vmax;
       +        if(logscale){
       +                for(j=1; j<=2; j++)
       +                        sprint(strs[j-1], "%g", scale*pow(10., j)*(double)vmax/100.);
       +                *np = 2;
       +        }else{
       +                for(j=1; j<=3; j++)
       +                        sprint(strs[j-1], "%g", scale*(double)j*(double)vmax/4.0);
       +                *np = 3;
       +        }
       +}
       +
       +int
       +labelwidth(void)
       +{
       +        int i, j, n, w, maxw;
       +        char strs[Nlab][Lablen];
       +
       +        maxw = 0;
       +        for(i=0; i<ngraph; i++){
       +                /* choose value for rightmost graph */
       +                labelstrs(&graph[ngraph*(nmach-1)+i], strs, &n);
       +                for(j=0; j<n; j++){
       +                        w = stringwidth(mediumfont, strs[j]);
       +                        if(w > maxw)
       +                                maxw = w;
       +                }
       +        }
       +        return maxw;
       +}
       +
       +void
       +resize(void)
       +{
       +        int i, j, k, n, startx, starty, x, y, dx, dy, ly, ondata, maxx, wid, nlab;
       +        Graph *g;
       +        Rectangle machr, r;
       +        ulong v, vmax;
       +        char buf[128], labs[Nlab][Lablen];
       +
       +        draw(screen, screen->r, display->white, nil, ZP);
       +
       +        /* label left edge */
       +        x = screen->r.min.x;
       +        y = screen->r.min.y + Labspace+mediumfont->height+Labspace;
       +        dy = (screen->r.max.y - y)/ngraph;
       +        dx = Labspace+stringwidth(mediumfont, "0")+Labspace;
       +        startx = x+dx+1;
       +        starty = y;
       +        for(i=0; i<ngraph; i++,y+=dy){
       +                draw(screen, Rect(x, y-1, screen->r.max.x, y), display->black, nil, ZP);
       +                draw(screen, Rect(x, y, x+dx, screen->r.max.y), cols[graph[i].colindex][0], nil, paritypt(x));
       +                label(Pt(x, y), dy, graph[i].label);
       +                draw(screen, Rect(x+dx, y, x+dx+1, screen->r.max.y), cols[graph[i].colindex][2], nil, ZP);
       +        }
       +
       +        /* label top edge */
       +        dx = (screen->r.max.x - startx)/nmach;
       +        for(x=startx, i=0; i<nmach; i++,x+=dx){
       +                draw(screen, Rect(x-1, starty-1, x, screen->r.max.y), display->black, nil, ZP);
       +                j = dx/stringwidth(mediumfont, "0");
       +        //        n = mach[i].nproc;
       +                n = 1;
       +                if(n>1 && j>=1+3+(n>10)+(n>100)){        /* first char of name + (n) */
       +                        j -= 3+(n>10)+(n>100);
       +                        if(j <= 0)
       +                                j = 1;
       +                        snprint(buf, sizeof buf, "%.*s(%d)", j, mach[i].name, n);
       +                }else
       +                        snprint(buf, sizeof buf, "%.*s", j, mach[i].name);
       +                string(screen, Pt(x+Labspace, screen->r.min.y + Labspace), display->black, ZP, mediumfont, buf);
       +        }
       +
       +        maxx = screen->r.max.x;
       +
       +        /* label right, if requested */
       +        if(ylabels && dy>Nlab*(mediumfont->height+1)){
       +                wid = labelwidth();
       +                if(wid < (maxx-startx)-30){
       +                        /* else there's not enough room */
       +                        maxx -= 1+Lx+wid;
       +                        draw(screen, Rect(maxx, starty, maxx+1, screen->r.max.y), display->black, nil, ZP);
       +                        y = starty;
       +                        for(j=0; j<ngraph; j++, y+=dy){
       +                                /* choose value for rightmost graph */
       +                                g = &graph[ngraph*(nmach-1)+j];
       +                                labelstrs(g, labs, &nlab);
       +                                r = Rect(maxx+1, y, screen->r.max.x, y+dy-1);
       +                                if(j == ngraph-1)
       +                                        r.max.y = screen->r.max.y;
       +                                draw(screen, r, cols[g->colindex][0], nil, paritypt(r.min.x));
       +                                for(k=0; k<nlab; k++){
       +                                        ly = y + (dy*(nlab-k)/(nlab+1));
       +                                        draw(screen, Rect(maxx+1, ly, maxx+1+Lx, ly+1), display->black, nil, ZP);
       +                                        ly -= mediumfont->height/2;
       +                                        string(screen, Pt(maxx+1+Lx, ly), display->black, ZP, mediumfont, labs[k]);
       +                                }
       +                        }
       +                }
       +        }
       +
       +        /* create graphs */
       +        for(i=0; i<nmach; i++){
       +                machr = Rect(startx+i*dx, starty, maxx, screen->r.max.y);
       +                if(i < nmach-1)
       +                        machr.max.x = startx+(i+1)*dx - 1;
       +                y = starty;
       +                for(j=0; j<ngraph; j++, y+=dy){
       +                        g = &graph[i*ngraph+j];
       +                        /* allocate data */
       +                        ondata = g->ndata;
       +                        g->ndata = Dx(machr)+1;        /* may be too many if label will be drawn here; so what? */
       +                        g->data = erealloc(g->data, g->ndata*sizeof(ulong));
       +                        if(g->ndata > ondata)
       +                                memset(g->data+ondata, 0, (g->ndata-ondata)*sizeof(ulong));
       +                        /* set geometry */
       +                        g->r = machr;
       +                        g->r.min.y = y;
       +                        g->r.max.y = y+dy - 1;
       +                        if(j == ngraph-1)
       +                                g->r.max.y = screen->r.max.y;
       +                        draw(screen, g->r, cols[g->colindex][0], nil, paritypt(g->r.min.x));
       +                        g->overflow = 0;
       +                        r = g->r;
       +                        r.max.y = r.min.y+mediumfont->height;
       +                        r.max.x = r.min.x+stringwidth(mediumfont, "9999999");
       +                        freeimage(g->overtmp);
       +                        g->overtmp = nil;
       +                        if(r.max.x <= g->r.max.x)
       +                                g->overtmp = allocimage(display, r, screen->chan, 0, -1);
       +                        newvalue(g->mach, g->value, &v, &vmax);
       +                        redraw(g, vmax);
       +                }
       +        }
       +
       +        flushimage(display, 1);
       +}
       +
       +void
       +eresized(int new)
       +{
       +        lockdisplay(display);
       +        if(new && getwindow(display, Refnone) < 0) {
       +                fprint(2, "stats: can't reattach to window\n");
       +                killall("reattach");
       +        }
       +        resize();
       +        unlockdisplay(display);
       +}
       +
       +void
       +mousethread(void *v)
       +{
       +        Mouse m;
       +        int i;
       +
       +        USED(v);
       +
       +        while(readmouse(mc) == 0){
       +                m = mc->m;
       +                if(m.buttons == 4){
       +                        for(i=0; i<Nvalue; i++)
       +                                if(present[i])
       +                                        memmove(menu2str[i], "drop ", Opwid);
       +                                else
       +                                        memmove(menu2str[i], "add  ", Opwid);
       +                        lockdisplay(display);
       +                        i = menuhit(3, mc, &menu2, nil);
       +                        if(i >= 0){
       +                                if(!present[i])
       +                                        addgraph(i);
       +                                else if(ngraph > 1)
       +                                        dropgraph(i);
       +                                resize();
       +                        }
       +                        unlockdisplay(display);
       +                }
       +        }
       +}
       +
       +void
       +resizethread(void *v)
       +{
       +        USED(v);
       +
       +        while(recv(mc->resizec, 0) == 1){
       +                lockdisplay(display);
       +                if(getwindow(display, Refnone) < 0)
       +                        sysfatal("attach to window: %r");
       +                resize();
       +                unlockdisplay(display);
       +        }
       +}
       +
       +void
       +keyboardthread(void *v)
       +{
       +        Rune r;
       +
       +        while(recv(kc->c, &r) == 1)
       +                if(r == 0x7F || r == 'q')
       +                        killall("quit");
       +}
       +
       +void machthread(void*);
       +
       +void
       +threadmain(int argc, char *argv[])
       +{
       +        int i, j;
       +        char *s;
       +        ulong v, vmax, nargs;
       +        char args[100];
       +
       +        nmach = 1;
       +        mysysname = sysname();
       +        if(mysysname == nil){
       +                fprint(2, "stats: can't find sysname: %r\n");
       +                exits("sysname");
       +        }
       +
       +        nargs = 0;
       +        ARGBEGIN{
       +        case 'T':
       +                s = ARGF();
       +                if(s == nil)
       +                        usage();
       +                i = atoi(s);
       +                if(i > 0)
       +                        sleeptime = 1000*i;
       +                break;
       +        case 'S':
       +                s = ARGF();
       +                if(s == nil)
       +                        usage();
       +                scale = atof(s);
       +                if(scale <= 0.)
       +                        usage();
       +                break;
       +        case 'L':
       +                logscale++;
       +                break;
       +        case 'Y':
       +                ylabels++;
       +                break;
       +        case 'O':
       +                oldsystem = 1;
       +                break;
       +        default:
       +                if(nargs>=sizeof args || strchr(argchars, ARGC())==nil)
       +                        usage();
       +                args[nargs++] = ARGC();
       +        }ARGEND
       +
       +        for(i=0; i<Nvalue; i++){
       +                menu2str[i] = xmenu2str[i];
       +                snprint(xmenu2str[i], sizeof xmenu2str[i], "add  %s", labels[i]);
       +        }
       +
       +        if(argc == 0){
       +                mach = emalloc(nmach*sizeof(Machine));
       +                initmach(&mach[0], mysysname);
       +        }else{
       +                for(i=j=0; i<argc; i++)
       +                        addmachine(argv[i]);
       +        }
       +
       +        for(i=0; i<nmach; i++)
       +                threadcreate(machthread, &mach[i], STACK);
       +
       +        for(i=0; i<nargs; i++)
       +        switch(args[i]){
       +        default:
       +                fprint(2, "stats: internal error: unknown arg %c\n", args[i]);
       +                usage();
       +        case 'b':
       +                addgraph(Vbattery);
       +                break;
       +        case 'c':
       +                addgraph(Vcontext);
       +                break;
       +        case 'e':
       +                addgraph(Vether);
       +                break;
       +        case 'E':
       +                addgraph(Vetherin);
       +                addgraph(Vetherout);
       +                break;
       +        case 'f':
       +                addgraph(Vfault);
       +                break;
       +        case 'i':
       +                addgraph(Vintr);
       +                break;
       +        case 'I':
       +                addgraph(Vload);
       +                addgraph(Vidle);
       +                break;
       +        case 'l':
       +                addgraph(Vload);
       +                break;
       +        case 'm':
       +                addgraph(Vmem);
       +                break;
       +        case 'n':
       +                addgraph(Vetherin);
       +                addgraph(Vetherout);
       +                addgraph(Vethererr);
       +                break;
       +        case 's':
       +                addgraph(Vsyscall);
       +                break;
       +        case 'w':
       +                addgraph(Vswap);
       +                break;
       +        }
       +
       +        if(ngraph == 0)
       +                addgraph(Vload);
       +
       +        for(i=0; i<nmach; i++)
       +                for(j=0; j<ngraph; j++)
       +                        graph[i*ngraph+j].mach = &mach[i];
       +
       +        if(initdraw(nil, nil, "stats") < 0)
       +                sysfatal("initdraw: %r");
       +        colinit();
       +        if((mc = initmouse(nil, nil)) == nil)
       +                sysfatal("initmouse: %r");
       +        if((kc = initkeyboard(nil)) == nil)
       +                sysfatal("initkeyboard: %r");
       +
       +        display->locking = 1;
       +        threadcreate(keyboardthread, nil, XSTACK);
       +        threadcreate(mousethread, nil, XSTACK);
       +        threadcreate(resizethread, nil, XSTACK);
       +
       +        resize();
       +        unlockdisplay(display);
       +
       +        for(;;){
       +                parity = 1-parity;
       +                lockdisplay(display);
       +                for(i=0; i<nmach*ngraph; i++){
       +                        newvalue(graph[i].mach, graph[i].value, &v, &vmax);
       +                        graph[i].update(&graph[i], v, vmax);
       +                }
       +                if(changedvmax){
       +                        changedvmax = 0;
       +                        resize();
       +                }
       +                flushimage(display, 1);
       +                unlockdisplay(display);
       +                threadsleep(sleeptime);
       +        }
       +}
       +
       +void
       +machthread(void *v)
       +{
       +        char buf[256], *f[4], *p;
       +        int i, n, t;
       +        Machine *m;
       +
       +        m = v;
       +        t = 0;
       +        for(;;){
       +                n = threadread(m->fd, buf+t, sizeof buf-t);
       +                m->dead = 0;
       +                if(n <= 0)
       +                        break;
       +                t += n;
       +                while((p = memchr(buf, '\n', t)) != nil){
       +                        *p++ = 0;
       +                        n = tokenize(buf, f, nelem(f));
       +                        if(n >= 3){
       +                                for(i=0; i<Nvalue; i++){
       +                                        if(strcmp(labels[i], f[0]) == 0){
       +                                                if(*f[1] == '='){
       +                                                        m->absolute[i] = 1;
       +                                                        f[1]++;
       +                                                }
       +                                                m->val[i][0] = strtoul(f[1], 0, 0);
       +                                                m->val[i][1] = strtoul(f[2], 0, 0);
       +                                        }
       +                                }
       +                        }
       +                        t -= (p-buf);
       +                        memmove(buf, p, t);
       +                }
       +        }
       +        if(m->fd){
       +                close(m->fd);
       +                m->fd = -1;
       +        }
       +        if(m->pid){
       +                postnote(PNPROC, m->pid, "kill");
       +                m->pid = 0;
       +        }
       +}
       +
       +int
       +initmach(Machine *m, char *name)
       +{
       +        char *args[5], *q;
       +        int p[2], kfd[3], pid;
       +
       +        m->name = name;
       +        if(strcmp(name, mysysname) == 0)
       +                name = nil;
       +
       +        if(pipe(p) < 0)
       +                sysfatal("pipe: %r");
       +
       +        memset(args, 0, sizeof args);
       +        args[0] = "sysstat";
       +        if(name){
       +                args[1] = name;
       +                if((q = strchr(name, ':')) != nil){
       +                        *q++ = 0;
       +                        args[2] = q;
       +                }
       +        }
       +        kfd[0] = open("/dev/null", OREAD);
       +        kfd[1] = p[1];
       +        kfd[2] = dup(2, -1);
       +        if((pid = threadspawn(kfd, "sysstat", args)) < 0){
       +                fprint(2, "spawn: %r\n");
       +                close(kfd[0]);
       +                close(p[0]);
       +                close(p[1]);
       +                return 0;
       +        }
       +        m->fd = p[0];
       +        m->pid = pid;
       +        if((q = strchr(m->name, '.')) != nil)
       +                *q = 0;
       +        return 1;
       +}
       +
 (DIR) diff --git a/src/cmd/draw/tweak.c b/src/cmd/draw/tweak.c
       t@@ -0,0 +1,2058 @@
       +#include <u.h>
       +#include <libc.h>
       +#include <draw.h>
       +#include <cursor.h>
       +#include <event.h>
       +#include <bio.h>
       +
       +typedef struct        Thing        Thing;
       +
       +struct Thing
       +{
       +        Image        *b;
       +        Subfont         *s;
       +        char                *name;        /* file name */
       +        int                face;                /* is 48x48 face file or cursor file*/
       +        Rectangle r;                /* drawing region */
       +        Rectangle tr;                /* text region */
       +        Rectangle er;                /* entire region */
       +        long                c;                /* character number in subfont */
       +        int                mod;        /* modified */
       +        int                mag;                /* magnification */
       +        Rune                off;                /* offset for subfont indices */
       +        Thing        *parent;        /* thing of which i'm an edit */
       +        Thing        *next;
       +};
       +
       +enum
       +{
       +        Border        = 1,
       +        Up                = 1,
       +        Down        = 0,
       +        Mag                = 4,
       +        Maxmag        = 10,
       +};
       +
       +enum
       +{
       +        NORMAL        =0,
       +        FACE        =1,
       +        CURSOR        =2
       +};
       +
       +enum
       +{
       +        Mopen,
       +        Mread,
       +        Mwrite,
       +        Mcopy,
       +        Mchar,
       +        Mpixels,
       +        Mclose,
       +        Mexit,
       +};
       +
       +enum
       +{
       +        Blue        = 54,
       +};
       +
       +char        *menu3str[] = {
       +        "open",
       +        "read",
       +        "write",
       +        "copy",
       +        "char",
       +        "pixels",
       +        "close",
       +        "exit",
       +        0,
       +};
       +
       +Menu        menu3 = {
       +        menu3str
       +};
       +
       +Cursor sweep0 = {
       +        {-7, -7},
       +        {0x03, 0xC0, 0x03, 0xC0, 0x03, 0xC0, 0x03, 0xC0,
       +         0x03, 0xC0, 0x03, 0xC0, 0xFF, 0xFF, 0xFF, 0xFF,
       +         0xFF, 0xFF, 0xFF, 0xFF, 0x03, 0xC0, 0x03, 0xC0,
       +         0x03, 0xC0, 0x03, 0xC0, 0x03, 0xC0, 0x03, 0xC0},
       +        {0x00, 0x00, 0x01, 0x80, 0x01, 0x80, 0x01, 0x80,
       +         0x01, 0x80, 0x01, 0x80, 0x01, 0x80, 0x7F, 0xFE,
       +         0x7F, 0xFE, 0x01, 0x80, 0x01, 0x80, 0x01, 0x80,
       +         0x01, 0x80, 0x01, 0x80, 0x01, 0x80, 0x00, 0x00}
       +};
       +
       +Cursor box = {
       +        {-7, -7},
       +        {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
       +         0xFF, 0xFF, 0xF8, 0x1F, 0xF8, 0x1F, 0xF8, 0x1F,
       +         0xF8, 0x1F, 0xF8, 0x1F, 0xF8, 0x1F, 0xFF, 0xFF,
       +         0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF},
       +        {0x00, 0x00, 0x7F, 0xFE, 0x7F, 0xFE, 0x7F, 0xFE,
       +         0x70, 0x0E, 0x70, 0x0E, 0x70, 0x0E, 0x70, 0x0E,
       +         0x70, 0x0E, 0x70, 0x0E, 0x70, 0x0E, 0x70, 0x0E,
       +         0x7F, 0xFE, 0x7F, 0xFE, 0x7F, 0xFE, 0x00, 0x00}
       +};
       +
       +Cursor sight = {
       +        {-7, -7},
       +        {0x1F, 0xF8, 0x3F, 0xFC, 0x7F, 0xFE, 0xFB, 0xDF,
       +         0xF3, 0xCF, 0xE3, 0xC7, 0xFF, 0xFF, 0xFF, 0xFF,
       +         0xFF, 0xFF, 0xFF, 0xFF, 0xE3, 0xC7, 0xF3, 0xCF,
       +         0x7B, 0xDF, 0x7F, 0xFE, 0x3F, 0xFC, 0x1F, 0xF8,},
       +        {0x00, 0x00, 0x0F, 0xF0, 0x31, 0x8C, 0x21, 0x84,
       +         0x41, 0x82, 0x41, 0x82, 0x41, 0x82, 0x7F, 0xFE,
       +         0x7F, 0xFE, 0x41, 0x82, 0x41, 0x82, 0x41, 0x82,
       +         0x21, 0x84, 0x31, 0x8C, 0x0F, 0xF0, 0x00, 0x00,}
       +};
       +
       +Cursor pixel = {
       +        {-7, -7},
       +        {0x1f, 0xf8, 0x3f, 0xfc,  0x7f, 0xfe,  0xf8, 0x1f,
       +        0xf0, 0x0f,  0xe0, 0x07, 0xe0, 0x07, 0xfe, 0x7f, 
       +        0xfe, 0x7f, 0xe0, 0x07, 0xe0, 0x07, 0xf0, 0x0f, 
       +        0x78, 0x1f, 0x7f, 0xfe, 0x3f, 0xfc, 0x1f, 0xf8, },
       +        {0x00, 0x00, 0x0f, 0xf0, 0x31, 0x8c, 0x21, 0x84, 
       +        0x41, 0x82, 0x41, 0x82, 0x41, 0x82, 0x40, 0x02, 
       +        0x40, 0x02, 0x41, 0x82, 0x41, 0x82, 0x41, 0x82, 
       +        0x21, 0x84, 0x31, 0x8c, 0x0f, 0xf0, 0x00, 0x00, }
       +};
       +
       +Cursor busy = {
       +        {-7, -7},
       +        {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
       +         0x00, 0x00, 0x00, 0x0c, 0x00, 0x8e, 0x1d, 0xc7,
       +         0xff, 0xe3, 0xff, 0xf3, 0xff, 0xff, 0x7f, 0xfe, 
       +         0x3f, 0xf8, 0x17, 0xf0, 0x03, 0xe0, 0x00, 0x00,},
       +        {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
       +         0x00, 0x00, 0x00, 0x08, 0x00, 0x04, 0x00, 0x82,
       +         0x04, 0x41, 0xff, 0xe1, 0x5f, 0xf1, 0x3f, 0xfe, 
       +         0x17, 0xf0, 0x03, 0xe0, 0x00, 0x00, 0x00, 0x00,}
       +};
       +
       +Cursor skull = {
       +        {-7,-7},
       +        {0x00, 0x00, 0x00, 0x00, 0xc0, 0x03, 0xe7, 0xe7, 
       +         0xff, 0xff, 0xff, 0xff, 0x3f, 0xfc, 0x1f, 0xf8, 
       +         0x0f, 0xf0, 0x3f, 0xfc, 0xff, 0xff, 0xff, 0xff, 
       +         0xef, 0xf7, 0xc7, 0xe3, 0x00, 0x00, 0x00, 0x00,},
       +        {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xC0, 0x03,
       +         0xE7, 0xE7, 0x3F, 0xFC, 0x0F, 0xF0, 0x0D, 0xB0,
       +         0x07, 0xE0, 0x06, 0x60, 0x37, 0xEC, 0xE4, 0x27,
       +         0xC3, 0xC3, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,}
       +};
       +
       +Rectangle        cntlr;                /* control region */
       +Rectangle        editr;                /* editing region */
       +Rectangle        textr;                /* text region */
       +Thing                *thing;
       +Mouse                mouse;
       +char                hex[] = "0123456789abcdefABCDEF";
       +jmp_buf                err;
       +char                *file;
       +int                mag;
       +int                but1val = 0;
       +int                but2val = 255;
       +int                invert = 0;
       +Image                *values[256];
       +Image                *greyvalues[256];
       +uchar                data[8192];
       +
       +Thing*        tget(char*);
       +void        mesg(char*, ...);
       +void        drawthing(Thing*, int);
       +void        xselect(void);
       +void        menu(void);
       +void        error(Display*, char*);
       +void        buttons(int);
       +void        drawall(void);
       +void        tclose1(Thing*);
       +
       +void
       +main(int argc, char *argv[])
       +{
       +        int i;
       +        Event e;
       +        Thing *t;
       +
       +        mag = Mag;
       +        if(initdraw(error, 0, "tweak") < 0){
       +                fprint(2, "tweak: initdraw failed: %r\n");
       +                exits("initdraw");
       +        }
       +        for(i=0; i<256; i++){
       +                values[i] = allocimage(display, Rect(0, 0, 1, 1), screen->chan, 1, cmap2rgba(i));
       +                greyvalues[i] = allocimage(display, Rect(0, 0, 1, 1), screen->chan, 1, (i<<24)|(i<<16)|(i<<8)|0xFF);
       +                if(values[i] == 0 || greyvalues[i] == 0)
       +                        drawerror(display, "can't allocate image");
       +        }
       +        einit(Emouse|Ekeyboard);
       +        eresized(0);
       +        i = 1;
       +        setjmp(err);
       +        for(; i<argc; i++){
       +                file = argv[i];
       +                t = tget(argv[i]);
       +                if(t)
       +                        drawthing(t, 1);
       +                flushimage(display, 1);
       +        }
       +        file = 0;
       +        setjmp(err);
       +        for(;;)
       +                switch(event(&e)){
       +                case Ekeyboard:
       +                        break;
       +                case Emouse:
       +                        mouse = e.mouse;
       +                        if(mouse.buttons & 3){
       +                                xselect();
       +                                break;
       +                        }
       +                        if(mouse.buttons & 4)
       +                                menu();
       +                }
       +}
       +
       +int
       +xlog2(int n)
       +{
       +        int i;
       +
       +        for(i=0; (1<<i) <= n; i++)
       +                if((1<<i) == n)
       +                        return i;
       +        fprint(2, "log2 %d = 0\n", n);
       +        return 0;
       +}
       +
       +void
       +error(Display *d, char *s)
       +{
       +        USED(d);
       +
       +        if(file)
       +                mesg("can't read %s: %s: %r", file, s);
       +        else
       +                mesg("/dev/bitblt error: %s", s);
       +        if(err[0])
       +                longjmp(err, 1);
       +        exits(s);
       +}
       +
       +void
       +redraw(Thing *t)
       +{
       +        Thing *nt;
       +        Point p;
       +
       +        if(thing==0 || thing==t)
       +                draw(screen, editr, display->white, nil, ZP);
       +        if(thing == 0)
       +                return;
       +        if(thing != t){
       +                for(nt=thing; nt->next!=t; nt=nt->next)
       +                        ;
       +                draw(screen, Rect(screen->r.min.x, nt->er.max.y, editr.max.x, editr.max.y),
       +                        display->white, nil, ZP);
       +        }
       +        for(nt=t; nt; nt=nt->next){
       +                drawthing(nt, 0);
       +                if(nt->next == 0){
       +                        p = Pt(editr.min.x, nt->er.max.y);
       +                        draw(screen, Rpt(p, editr.max), display->white, nil, ZP);
       +                }
       +        }
       +        mesg("");
       +}
       +
       +void
       +eresized(int new)
       +{
       +        if(new && getwindow(display, Refnone) < 0)
       +                error(display, "can't reattach to window");
       +        cntlr = insetrect(screen->clipr, 1);
       +        editr = cntlr;
       +        textr = editr;
       +        textr.min.y = textr.max.y - font->height;
       +        cntlr.max.y = cntlr.min.y + font->height;
       +        editr.min.y = cntlr.max.y+1;
       +        editr.max.y = textr.min.y-1;
       +        draw(screen, screen->clipr, display->white, nil, ZP);
       +        draw(screen, Rect(editr.min.x, editr.max.y, editr.max.x+1, editr.max.y+1), display->black, nil, ZP);
       +        replclipr(screen, 0, editr);
       +        drawall();
       +}
       +
       +void
       +mesgstr(Point p, int line, char *s)
       +{
       +        Rectangle c, r;
       +
       +        r.min = p;
       +        r.min.y += line*font->height;
       +        r.max.y = r.min.y+font->height;
       +        r.max.x = editr.max.x;
       +        c = screen->clipr;
       +        replclipr(screen, 0, r);
       +        draw(screen, r, values[0xDD], nil, ZP);
       +        r.min.x++;
       +        string(screen, r.min, display->black, ZP, font, s);
       +        replclipr(screen, 0, c);
       +        flushimage(display, 1);
       +}
       +
       +void
       +mesg(char *fmt, ...)
       +{
       +        char buf[1024];
       +        va_list arg;
       +
       +        va_start(arg, fmt);
       +        vseprint(buf, buf+sizeof(buf), fmt, arg);
       +        va_end(arg);
       +        mesgstr(textr.min, 0, buf);
       +}
       +
       +void
       +tmesg(Thing *t, int line, char *fmt, ...)
       +{
       +        char buf[1024];
       +        va_list arg;
       +
       +        va_start(arg, fmt);
       +        vseprint(buf, buf+sizeof(buf), fmt, arg);
       +        va_end(arg);
       +        mesgstr(t->tr.min, line, buf);
       +}
       +
       +
       +void
       +scntl(char *l)
       +{
       +        sprint(l, "mag: %d  but1: %d  but2: %d  invert-on-copy: %c", mag, but1val, but2val, "ny"[invert]);
       +}
       +
       +void
       +cntl(void)
       +{
       +        char buf[256];
       +
       +        scntl(buf);
       +        mesgstr(cntlr.min, 0, buf);
       +}
       +
       +void
       +stext(Thing *t, char *l0, char *l1)
       +{
       +        Fontchar *fc;
       +        char buf[256];
       +
       +        l1[0] = 0;
       +        sprint(buf, "depth:%d r:%d %d  %d %d ", 
       +                t->b->depth, t->b->r.min.x, t->b->r.min.y,
       +                t->b->r.max.x, t->b->r.max.y);
       +        if(t->parent)
       +                sprint(buf+strlen(buf), "mag: %d ", t->mag);
       +        sprint(l0, "%s file: %s", buf, t->name);
       +        if(t->c >= 0){
       +                fc = &t->parent->s->info[t->c];
       +                sprint(l1, "c(hex): %x c(char): %C x: %d "
       +                           "top: %d bottom: %d left: %d width: %d iwidth: %d",
       +                        (int)(t->c+t->parent->off), (int)(t->c+t->parent->off),
       +                        fc->x, fc->top, fc->bottom, fc->left,
       +                        fc->width, Dx(t->b->r));
       +        }else if(t->s)
       +                sprint(l1, "offset(hex): %ux n:%d  height:%d  ascent:%d",
       +                        t->off, t->s->n, t->s->height, t->s->ascent);
       +}
       +
       +void
       +text(Thing *t)
       +{
       +        char l0[256], l1[256];
       +
       +        stext(t, l0, l1);
       +        tmesg(t, 0, l0);
       +        if(l1[0])
       +                tmesg(t, 1, l1);
       +}
       +
       +void
       +drawall(void)
       +{
       +        Thing *t;
       +
       +        cntl();
       +        for(t=thing; t; t=t->next)
       +                drawthing(t, 0);
       +}
       +
       +int
       +value(Image *b, int x)
       +{
       +        int v, l, w;
       +        uchar mask;
       +
       +        w = b->depth;
       +        if(w > 8){
       +                mesg("ldepth too large");
       +                return 0;
       +        }
       +        l = xlog2(w);
       +        mask = (1<<w)-1;                /* ones at right end of word */
       +        x -= b->r.min.x&~(7>>l);        /* adjust x relative to first pixel */
       +        v = data[x>>(3-l)];
       +        v >>= ((7>>l)<<l) - ((x&(7>>l))<<l);        /* pixel at right end of word */
       +        v &= mask;                        /* pixel at right end of word */
       +        return v;
       +}
       +
       +int
       +bvalue(int v, int d)
       +{
       +        v &= (1<<d)-1;
       +        if(d > screen->depth)
       +                v >>= d - screen->depth;
       +        else
       +                while(d < screen->depth && d < 8){
       +                        v |= v << d;
       +                        d <<= 1;
       +                }
       +        if(v<0 || v>255){
       +                mesg("internal error: bad color");
       +                return Blue;
       +        }
       +        return v;
       +}
       +
       +void
       +drawthing(Thing *nt, int link)
       +{
       +        int n, nl, nf, i, x, y, sx, sy, fdx, dx, dy, v;
       +        Thing *t;
       +        Subfont *s;
       +        Image *b, *col;
       +        Point p, p1, p2;
       +
       +        if(link){
       +                nt->next = 0;
       +                if(thing == 0){
       +                        thing = nt;
       +                        y = editr.min.y;
       +                }else{
       +                        for(t=thing; t->next; t=t->next)
       +                                ;
       +                        t->next = nt;
       +                        y = t->er.max.y;
       +                }
       +        }else{
       +                if(thing == nt)
       +                        y = editr.min.y;
       +                else{
       +                        for(t=thing; t->next!=nt; t=t->next)
       +                                ;
       +                        y = t->er.max.y;
       +                }
       +        }
       +        s = nt->s;
       +        b = nt->b;
       +        nl = font->height;
       +        if(s || nt->c>=0)
       +                nl += font->height;
       +        fdx = Dx(editr) - 2*Border;
       +        dx = Dx(b->r);
       +        dy = Dy(b->r);
       +        if(nt->mag > 1){
       +                dx *= nt->mag;
       +                dy *= nt->mag;
       +                fdx -= fdx%nt->mag;
       +        }
       +        nf = 1 + dx/fdx;
       +        nt->er.min.y = y;
       +        nt->er.min.x = editr.min.x;
       +        nt->er.max.x = nt->er.min.x + Border + dx + Border;
       +        if(nt->er.max.x > editr.max.x)
       +                nt->er.max.x = editr.max.x;
       +        nt->er.max.y = nt->er.min.y + Border + nf*(dy+Border);
       +        nt->r = insetrect(nt->er, Border);
       +        nt->er.max.x = editr.max.x;
       +        draw(screen, nt->er, display->white, nil, ZP);
       +        for(i=0; i<nf; i++){
       +                p1 = Pt(nt->r.min.x-1, nt->r.min.y+i*(Border+dy));
       +                /* draw portion of bitmap */
       +                p = Pt(p1.x+1, p1.y);
       +                if(nt->mag == 1)
       +                        draw(screen, Rect(p.x, p.y, p.x+fdx+Dx(b->r), p.y+Dy(b->r)),
       +                                b, nil, Pt(b->r.min.x+i*fdx, b->r.min.y));
       +                else{
       +                        for(y=b->r.min.y; y<b->r.max.y; y++){
       +                                sy = p.y+(y-b->r.min.y)*nt->mag;
       +                                if((n=unloadimage(b, Rect(b->r.min.x, y, b->r.max.x, y+1), data, sizeof data)) < 0)
       +                                        fprint(2, "unloadimage: %r\n");
       +                                for(x=b->r.min.x+i*(fdx/nt->mag); x<b->r.max.x; x++){
       +                                        sx = p.x+(x-i*(fdx/nt->mag)-b->r.min.x)*nt->mag;
       +                                        if(sx >= nt->r.max.x)
       +                                                break;
       +                                        v = bvalue(value(b, x), b->depth);
       +                                        if(v == 255)
       +                                                continue;
       +                                        if(b->chan == GREY8)
       +                                                draw(screen, Rect(sx, sy, sx+nt->mag, sy+nt->mag),
       +                                                        greyvalues[v], nil, ZP);
       +                                        else
       +                                                draw(screen, Rect(sx, sy, sx+nt->mag, sy+nt->mag),
       +                                                        values[v], nil, ZP);
       +                                }
       +
       +                        }
       +                }
       +                /* line down left */
       +                if(i == 0)
       +                        col = display->black;
       +                else
       +                        col = display->white;
       +                draw(screen, Rect(p1.x, p1.y, p1.x+1, p1.y+dy+Border), col, nil, ZP);
       +                /* line across top */
       +                draw(screen, Rect(p1.x, p1.y-1, nt->r.max.x+Border, p1.y), display->black, nil, ZP);
       +                p2 = p1;
       +                if(i == nf-1){
       +                        p2.x += 1 + dx%fdx;
       +                        col = display->black;
       +                }else{
       +                        p2.x = nt->r.max.x;
       +                        col = display->white;
       +                }
       +                /* line down right */
       +                draw(screen, Rect(p2.x, p2.y, p2.x+1, p2.y+dy+Border), col, nil, ZP);
       +                /* line across bottom */
       +                if(i == nf-1){
       +                        p1.y += Border+dy;
       +                        draw(screen, Rect(p1.x, p1.y-1, p2.x,p1.y), display->black, nil, ZP);
       +                }
       +        }
       +        nt->tr.min.x = editr.min.x;
       +        nt->tr.max.x = editr.max.x;
       +        nt->tr.min.y = nt->er.max.y + Border;
       +        nt->tr.max.y = nt->tr.min.y + nl;
       +        nt->er.max.y = nt->tr.max.y + Border;
       +        text(nt);
       +}
       +
       +int
       +tohex(int c)
       +{
       +        if('0'<=c && c<='9')
       +                return c - '0';
       +        if('a'<=c && c<='f')
       +                return 10 + (c - 'a');
       +        if('A'<=c && c<='F')
       +                return 10 + (c - 'A');
       +        return 0;
       +}
       +
       +Thing*
       +tget(char *file)
       +{
       +        int i, j, fd, face, x, y, c, chan;
       +        Image *b;
       +        Subfont *s;
       +        Thing *t;
       +        Dir *d;
       +        jmp_buf oerr;
       +        uchar buf[256];
       +        char *data;
       +
       +        buf[0] = '\0';
       +        errstr((char*)buf, sizeof buf);        /* flush pending error message */
       +        memmove(oerr, err, sizeof err);
       +        d = nil;
       +        if(setjmp(err)){
       +   Err:
       +                free(d);
       +                memmove(err, oerr, sizeof err);
       +                return 0;
       +        }
       +        fd = open(file, OREAD);
       +        if(fd < 0){
       +                mesg("can't open %s: %r", file);
       +                goto Err;
       +        }
       +        d = dirfstat(fd);
       +        if(d == nil){
       +                mesg("can't stat bitmap file %s: %r", file);
       +                close(fd);
       +                goto Err;
       +        }
       +        if(read(fd, buf, 11) != 11){
       +                mesg("can't read %s: %r", file);
       +                close(fd);
       +                goto Err;
       +        }
       +        seek(fd, 0, 0);
       +        data = (char*)buf;
       +        if(*data == '{')
       +                data++;
       +        if(memcmp(data, "0x", 2)==0 && data[4]==','){
       +                /*
       +                 * cursor file
       +                 */
       +                face = CURSOR;
       +                s = 0;
       +                data = malloc(d->length+1);
       +                if(data == 0){
       +                        mesg("can't malloc buffer: %r");
       +                        close(fd);
       +                        goto Err;
       +                }
       +                data[d->length] = 0;
       +                if(read(fd, data, d->length) != d->length){
       +                        mesg("can't read cursor file %s: %r", file);
       +                        close(fd);
       +                        goto Err;
       +                }
       +                b = allocimage(display, Rect(0, 0, 16, 32), GREY1, 0, DNofill);
       +                if(b == 0){
       +                        mesg("image alloc failed file %s: %r", file);
       +                        free(data);
       +                        close(fd);
       +                        goto Err;
       +                }
       +                i = 0;
       +                for(x=0;x<64; ){
       +                        if((c=data[i]) == '\0')
       +                                goto ill;
       +                        if(c=='0' && data[i+1] == 'x'){
       +                                i += 2;
       +                                continue;
       +                        }
       +                        if(strchr(hex, c)){
       +                                buf[x++] = (tohex(c)<<4) | tohex(data[i+1]);
       +                                i += 2;
       +                                continue;
       +                        }
       +                        i++;
       +                }
       +                loadimage(b, Rect(0, 0, 16, 32), buf, sizeof buf);
       +                free(data);
       +        }else if(memcmp(buf, "0x", 2)==0){
       +                /*
       +                 * face file
       +                 */
       +                face = FACE;
       +                s = 0;
       +                data = malloc(d->length+1);
       +                if(data == 0){
       +                        mesg("can't malloc buffer: %r");
       +                        close(fd);
       +                        goto Err;
       +                }
       +                data[d->length] = 0;
       +                if(read(fd, data, d->length) != d->length){
       +                        mesg("can't read bitmap file %s: %r", file);
       +                        close(fd);
       +                        goto Err;
       +                }
       +                for(y=0,i=0; i<d->length; i++)
       +                        if(data[i] == '\n')
       +                                y++;
       +                if(y == 0){
       +        ill:
       +                        mesg("ill-formed face file %s", file);
       +                        close(fd);
       +                        free(data);
       +                        goto Err;
       +                }
       +                for(x=0,i=0; (c=data[i])!='\n'; ){
       +                        if(c==',' || c==' ' || c=='\t'){
       +                                i++;
       +                                continue;
       +                        }
       +                        if(c=='0' && data[i+1] == 'x'){
       +                                i += 2;
       +                                continue;
       +                        }
       +                        if(strchr(hex, c)){
       +                                x += 4;
       +                                i++;
       +                                continue;
       +                        }
       +                        goto ill;
       +                }
       +                if(x % y)
       +                        goto ill;
       +                switch(x / y){
       +                default:
       +                        goto ill;
       +                case 1:
       +                        chan = GREY1;
       +                        break;
       +                case 2:
       +                        chan = GREY2;
       +                        break;
       +                case 4:
       +                        chan = GREY4;
       +                        break;
       +                case 8:
       +                        chan = CMAP8;
       +                        break;
       +                }
       +                b = allocimage(display, Rect(0, 0, y, y), chan, 0, -1);
       +                if(b == 0){
       +                        mesg("image alloc failed file %s: %r", file);
       +                        free(data);
       +                        close(fd);
       +                        goto Err;
       +                }
       +                i = 0;
       +                for(j=0; j<y; j++){
       +                        for(x=0; (c=data[i])!='\n'; ){
       +                                if(c=='0' && data[i+1] == 'x'){
       +                                        i += 2;
       +                                        continue;
       +                                }
       +                                if(strchr(hex, c)){
       +                                        buf[x++] = ~((tohex(c)<<4) | tohex(data[i+1]));
       +                                        i += 2;
       +                                        continue;
       +                                }
       +                                i++;
       +                        }
       +                        i++;
       +                        loadimage(b, Rect(0, j, y, j+1), buf, sizeof buf);
       +                }
       +                free(data);
       +        }else{
       +                face = NORMAL;
       +                s = 0;
       +                b = readimage(display, fd, 0);
       +                if(b == 0){
       +                        mesg("can't read bitmap file %s: %r", file);
       +                        close(fd);
       +                        goto Err;
       +                }
       +                if(seek(fd, 0, 1) < d->length)
       +                        s = readsubfonti(display, file, fd, b, 0);
       +        }
       +        close(fd);
       +        t = malloc(sizeof(Thing));
       +        if(t == 0){
       +   nomem:
       +                mesg("malloc failed: %r");
       +                if(s)
       +                        freesubfont(s);
       +                else
       +                        freeimage(b);
       +                goto Err;
       +        }
       +        t->name = strdup(file);
       +        if(t->name == 0){
       +                free(t);
       +                goto nomem;
       +        }
       +        t->b = b;
       +        t->s = s;
       +        t->face = face;
       +        t->mod = 0;
       +        t->parent = 0;
       +        t->c = -1;
       +        t->mag = 1;
       +        t->off = 0;
       +        memmove(err, oerr, sizeof err);
       +        return t;
       +}
       +
       +int
       +atline(int x, Point p, char *line, char *buf)
       +{
       +        char *s, *c, *word, *hit;
       +        int w, wasblank;
       +        Rune r;
       +
       +        wasblank = 1;
       +        hit = 0;
       +        word = 0;
       +        for(s=line; *s; s+=w){
       +                w = chartorune(&r, s);
       +                x += runestringnwidth(font, &r, 1);
       +                if(wasblank && r!=' ')
       +                        word = s;
       +                wasblank = 0;
       +                if(r == ' '){
       +                        if(x >= p.x)
       +                                break;
       +                        wasblank = 1;
       +                }
       +                if(r == ':')
       +                        hit = word;
       +        }
       +        if(x < p.x)
       +                return 0;
       +        c = utfrune(hit, ':');
       +        strncpy(buf, hit, c-hit);
       +        buf[c-hit] = 0;
       +        return 1;
       +}
       +
       +int
       +attext(Thing *t, Point p, char *buf)
       +{
       +        char l0[256], l1[256];
       +
       +        if(!ptinrect(p, t->tr))
       +                return 0;
       +        stext(t, l0, l1);
       +        if(p.y < t->tr.min.y+font->height)
       +                return atline(t->r.min.x, p, l0, buf);
       +        else
       +                return atline(t->r.min.x, p, l1, buf);
       +}
       +
       +int
       +type(char *buf, char *tag)
       +{
       +        Rune r;
       +        char *p;
       +
       +        esetcursor(&busy);
       +        p = buf;
       +        for(;;){
       +                *p = 0;
       +                mesg("%s: %s", tag, buf);
       +                r = ekbd();
       +                switch(r){
       +                case '\n':
       +                        mesg("");
       +                        esetcursor(0);
       +                        return p-buf;
       +                case 0x15:        /* control-U */
       +                        p = buf;
       +                        break;
       +                case '\b':
       +                        if(p > buf)
       +                                --p;
       +                        break;
       +                default:
       +                        p += runetochar(p, &r);
       +                }
       +        }
       +        return 0;        /* shut up compiler */
       +}
       +
       +void
       +textedit(Thing *t, char *tag)
       +{
       +        char buf[256];
       +        char *s;
       +        Image *b;
       +        Subfont *f;
       +        Fontchar *fc, *nfc;
       +        Rectangle r;
       +        ulong chan;
       +        int i, ld, d, w, c, doredraw, fdx, x;
       +        Thing *nt;
       +
       +        buttons(Up);
       +        if(type(buf, tag) == 0)
       +                return;
       +        if(strcmp(tag, "file") == 0){
       +                for(s=buf; *s; s++)
       +                        if(*s <= ' '){
       +                                mesg("illegal file name");
       +                                return;
       +                        }
       +                if(strcmp(t->name, buf) != 0){
       +                        if(t->parent)
       +                                t->parent->mod = 1;
       +                        else
       +                                t->mod = 1;
       +                }
       +                for(nt=thing; nt; nt=nt->next)
       +                        if(t==nt || t->parent==nt || nt->parent==t){
       +                                free(nt->name);
       +                                nt->name = strdup(buf);
       +                                if(nt->name == 0){
       +                                        mesg("malloc failed: %r");
       +                                        return;
       +                                }
       +                                text(nt);
       +                        }
       +                return;
       +        }
       +        if(strcmp(tag, "depth") == 0){
       +                if(buf[0]<'0' || '9'<buf[0] || (d=atoi(buf))<0 || d>8 || xlog2(d)<0){
       +                        mesg("illegal ldepth");
       +                        return;
       +                }
       +                if(d == t->b->depth)
       +                        return;
       +                if(t->parent)
       +                        t->parent->mod = 1;
       +                else
       +                        t->mod = 1;
       +                if(d == 8)
       +                        chan = CMAP8;
       +                else
       +                        chan = CHAN1(CGrey, d);
       +                for(nt=thing; nt; nt=nt->next){
       +                        if(nt!=t && nt!=t->parent && nt->parent!=t)
       +                                continue;
       +                        b = allocimage(display, nt->b->r, chan, 0, 0);
       +                        if(b == 0){
       +        nobmem:
       +                                mesg("image alloc failed: %r");
       +                                return;
       +                        }
       +                        draw(b, b->r, nt->b, nil, nt->b->r.min);
       +                        freeimage(nt->b);
       +                        nt->b = b;
       +                        if(nt->s){
       +                                b = allocimage(display, nt->b->r, chan, 0, -1);
       +                                if(b == 0)
       +                                        goto nobmem;
       +                                draw(b, b->r, nt->b, nil, nt->b->r.min);
       +                                f = allocsubfont(t->name, nt->s->n, nt->s->height, nt->s->ascent, nt->s->info, b);
       +                                if(f == 0){
       +        nofmem:
       +                                        freeimage(b);
       +                                        mesg("can't make subfont: %r");
       +                                        return;
       +                                }
       +                                nt->s->info = 0;        /* prevent it being freed */
       +                                nt->s->bits = 0;
       +                                freesubfont(nt->s);
       +                                nt->s = f;
       +                        }
       +                        drawthing(nt, 0);
       +                }
       +                return;
       +        }
       +        if(strcmp(tag, "mag") == 0){
       +                if(buf[0]<'0' || '9'<buf[0] || (ld=atoi(buf))<=0 || ld>Maxmag){
       +                        mesg("illegal magnification");
       +                        return;
       +                }
       +                if(t->mag == ld)
       +                        return;
       +                t->mag = ld;
       +                redraw(t);
       +                return;
       +        }
       +        if(strcmp(tag, "r") == 0){
       +                if(t->s){
       +                        mesg("can't change rectangle of subfont\n");
       +                        return;
       +                }
       +                s = buf;
       +                r.min.x = strtoul(s, &s, 0);
       +                r.min.y = strtoul(s, &s, 0);
       +                r.max.x = strtoul(s, &s, 0);
       +                r.max.y = strtoul(s, &s, 0);
       +                if(Dx(r)<=0 || Dy(r)<=0){
       +                        mesg("illegal rectangle");
       +                        return;
       +                }
       +                if(t->parent)
       +                        t = t->parent;
       +                for(nt=thing; nt; nt=nt->next){
       +                        if(nt->parent==t && !rectinrect(nt->b->r, r))
       +                                tclose1(nt);
       +                }
       +                b = allocimage(display, r, t->b->chan, 0, 0);
       +                if(b == 0)
       +                        goto nobmem;
       +                draw(b, r, t->b, nil, r.min);
       +                freeimage(t->b);
       +                t->b = b;
       +                b = allocimage(display, r, t->b->chan, 0, 0);
       +                if(b == 0)
       +                        goto nobmem;
       +                redraw(t);
       +                t->mod = 1;
       +                return;
       +        }
       +        if(strcmp(tag, "ascent") == 0){
       +                if(buf[0]<'0' || '9'<buf[0] || (ld=atoi(buf))<0 || ld>t->s->height){
       +                        mesg("illegal ascent");
       +                        return;
       +                }
       +                if(t->s->ascent == ld)
       +                        return;
       +                t->s->ascent = ld;
       +                text(t);
       +                t->mod = 1;
       +                return;
       +        }
       +        if(strcmp(tag, "height") == 0){
       +                if(buf[0]<'0' || '9'<buf[0] || (ld=atoi(buf))<0){
       +                        mesg("illegal height");
       +                        return;
       +                }
       +                if(t->s->height == ld)
       +                        return;
       +                t->s->height = ld;
       +                text(t);
       +                t->mod = 1;
       +                return;
       +        }
       +        if(strcmp(tag, "left")==0 || strcmp(tag, "width") == 0){
       +                if(buf[0]<'0' || '9'<buf[0] || (ld=atoi(buf))<0){
       +                        mesg("illegal value");
       +                        return;
       +                }
       +                fc = &t->parent->s->info[t->c];
       +                if(strcmp(tag, "left")==0){
       +                        if(fc->left == ld)
       +                                return;
       +                        fc->left = ld;
       +                }else{
       +                        if(fc->width == ld)
       +                                return;
       +                        fc->width = ld;
       +                }
       +                text(t);
       +                t->parent->mod = 1;
       +                return;
       +        }
       +        if(strcmp(tag, "offset(hex)") == 0){
       +                if(!strchr(hex, buf[0])){
       +        illoff:
       +                        mesg("illegal offset");
       +                        return;
       +                }
       +                s = 0;
       +                ld = strtoul(buf, &s, 16);
       +                if(*s)
       +                        goto illoff;
       +                t->off = ld;
       +                text(t);
       +                for(nt=thing; nt; nt=nt->next)
       +                        if(nt->parent == t)
       +                                text(nt);
       +                return;
       +        }
       +        if(strcmp(tag, "n") == 0){
       +                if(buf[0]<'0' || '9'<buf[0] || (w=atoi(buf))<=0){
       +                        mesg("illegal n");
       +                        return;
       +                }
       +                f = t->s;
       +                if(w == f->n)
       +                        return;
       +                doredraw = 0;
       +        again:
       +                for(nt=thing; nt; nt=nt->next)
       +                        if(nt->parent == t){
       +                                doredraw = 1;
       +                                tclose1(nt);
       +                                goto again;
       +                        }
       +                r = t->b->r;
       +                if(w < f->n)
       +                        r.max.x = f->info[w].x;
       +                b = allocimage(display, r, t->b->chan, 0, 0);
       +                if(b == 0)
       +                        goto nobmem;
       +                draw(b, b->r, t->b, nil, r.min);
       +                fdx = Dx(editr) - 2*Border;
       +                if(Dx(t->b->r)/fdx != Dx(b->r)/fdx)
       +                        doredraw = 1;
       +                freeimage(t->b);
       +                t->b = b;
       +                b = allocimage(display, r, t->b->chan, 0, 0);
       +                if(b == 0)
       +                        goto nobmem;
       +                draw(b, b->r, t->b, nil, r.min);
       +                nfc = malloc((w+1)*sizeof(Fontchar));
       +                if(nfc == 0){
       +                        mesg("malloc failed");
       +                        freeimage(b);
       +                        return;
       +                }
       +                fc = f->info;
       +                for(i=0; i<=w && i<=f->n; i++)
       +                        nfc[i] = fc[i];
       +                if(w+1 < i)
       +                        memset(nfc+i, 0, ((w+1)-i)*sizeof(Fontchar));
       +                x = fc[f->n].x;
       +                for(; i<=w; i++)
       +                        nfc[i].x = x;
       +                f = allocsubfont(t->name, w, f->height, f->ascent, nfc, b);
       +                if(f == 0)
       +                        goto nofmem;
       +                t->s->bits = nil;        /* don't free it */
       +                freesubfont(t->s);
       +                f->info = nfc;
       +                t->s = f;
       +                if(doredraw)
       +                        redraw(thing);
       +                else
       +                        drawthing(t, 0);
       +                t->mod = 1;
       +                return;
       +        }
       +        if(strcmp(tag, "iwidth") == 0){
       +                if(buf[0]<'0' || '9'<buf[0] || (w=atoi(buf))<0){
       +                        mesg("illegal iwidth");
       +                        return;
       +                }
       +                w -= Dx(t->b->r);
       +                if(w == 0)
       +                        return;
       +                r = t->parent->b->r;
       +                r.max.x += w;
       +                c = t->c;
       +                t = t->parent;
       +                f = t->s;
       +                b = allocimage(display, r, t->b->chan, 0, 0);
       +                if(b == 0)
       +                        goto nobmem;
       +                fc = &f->info[c];
       +                draw(b, Rect(b->r.min.x, b->r.min.y,
       +                                b->r.min.x+(fc[1].x-t->b->r.min.x), b->r.min.y+Dy(t->b->r)),
       +                                t->b, nil, t->b->r.min);
       +                draw(b, Rect(fc[1].x+w, b->r.min.y, w+t->b->r.max.x, b->r.min.y+Dy(t->b->r)),
       +                        t->b, nil, Pt(fc[1].x, t->b->r.min.y));
       +                fdx = Dx(editr) - 2*Border;
       +                doredraw = 0;
       +                if(Dx(t->b->r)/fdx != Dx(b->r)/fdx)
       +                        doredraw = 1;
       +                freeimage(t->b);
       +                t->b = b;
       +                b = allocimage(display, r, t->b->chan, 0, 0);
       +                if(b == 0)
       +                        goto nobmem;
       +                draw(b, b->r, t->b, nil, t->b->r.min);
       +                fc = &f->info[c+1];
       +                for(i=c+1; i<=f->n; i++, fc++)
       +                        fc->x += w;
       +                f = allocsubfont(t->name, f->n, f->height, f->ascent,
       +                        f->info, b);
       +                if(f == 0)
       +                        goto nofmem;
       +                /* t->s and f share info; free carefully */
       +                fc = f->info;
       +                t->s->bits = nil;
       +                t->s->info = 0;
       +                freesubfont(t->s);
       +                f->info = fc;
       +                t->s = f;
       +                if(doredraw)
       +                        redraw(t);
       +                else
       +                        drawthing(t, 0);
       +                /* redraw all affected chars */
       +                for(nt=thing; nt; nt=nt->next){
       +                        if(nt->parent!=t || nt->c<c)
       +                                continue;
       +                        fc = &f->info[nt->c];
       +                        r.min.x = fc[0].x;
       +                        r.min.y = nt->b->r.min.y;
       +                        r.max.x = fc[1].x;
       +                        r.max.y = nt->b->r.max.y;
       +                        b = allocimage(display, r, nt->b->chan, 0, 0);
       +                        if(b == 0)
       +                                goto nobmem;
       +                        draw(b, r, t->b, nil, r.min);
       +                        doredraw = 0;
       +                        if(Dx(nt->b->r)/fdx != Dx(b->r)/fdx)
       +                                doredraw = 1;
       +                        freeimage(nt->b);
       +                        nt->b = b;
       +                        if(c != nt->c)
       +                                text(nt);
       +                        else{
       +                                if(doredraw)
       +                                        redraw(nt);
       +                                else
       +                                        drawthing(nt, 0);
       +                        }
       +                }
       +                t->mod = 1;
       +                return;
       +        }
       +        mesg("cannot edit %s in file %s", tag, t->name);
       +}
       +
       +void
       +cntledit(char *tag)
       +{
       +        char buf[256];
       +        ulong l;
       +
       +        buttons(Up);
       +        if(type(buf, tag) == 0)
       +                return;
       +        if(strcmp(tag, "mag") == 0){
       +                if(buf[0]<'0' || '9'<buf[0] || (l=atoi(buf))<=0 || l>Maxmag){
       +                        mesg("illegal magnification");
       +                        return;
       +                }
       +                mag = l;
       +                cntl();
       +                return;
       +        }
       +        if(strcmp(tag, "but1")==0
       +        || strcmp(tag, "but2")==0){
       +                if(buf[0]<'0' || '9'<buf[0] || (l=atoi(buf))<0 || l>255){
       +                        mesg("illegal value");
       +                        return;
       +                }
       +                if(strcmp(tag, "but1") == 0)
       +                        but1val = l;
       +                else if(strcmp(tag, "but2") == 0)
       +                        but2val = l;
       +                cntl();
       +                return;
       +        }
       +        if(strcmp(tag, "invert-on-copy")==0){
       +                if(buf[0]=='y' || buf[0]=='1')
       +                        invert = 1;
       +                else if(buf[0]=='n' || buf[0]=='0')
       +                        invert = 0;
       +                else{
       +                        mesg("illegal value");
       +                        return;
       +                }
       +                cntl();
       +                return;
       +        }
       +        mesg("cannot edit %s", tag);
       +}
       +
       +void
       +buttons(int ud)
       +{
       +        while((mouse.buttons==0) != ud)
       +                mouse = emouse();
       +}
       +
       +Point
       +screenpt(Thing *t, Point realp)
       +{
       +        int fdx, n;
       +        Point p;
       +
       +        fdx = Dx(editr)-2*Border;
       +        if(t->mag > 1)
       +                fdx -= fdx%t->mag;
       +        p = mulpt(subpt(realp, t->b->r.min), t->mag);
       +        if(fdx < Dx(t->b->r)*t->mag){
       +                n = p.x/fdx;
       +                p.y += n * (Dy(t->b->r)*t->mag+Border);
       +                p.x -= n * fdx;
       +        }
       +        p = addpt(p, t->r.min);
       +        return p;
       +}
       +
       +Point
       +realpt(Thing *t, Point screenp)
       +{
       +        int fdx, n, dy;
       +        Point p;
       +
       +        fdx = (Dx(editr)-2*Border);
       +        if(t->mag > 1)
       +                fdx -= fdx%t->mag;
       +        p.y = screenp.y-t->r.min.y;
       +        p.x = 0;
       +        if(fdx < Dx(t->b->r)*t->mag){
       +                dy = Dy(t->b->r)*t->mag+Border;
       +                n = (p.y/dy);
       +                p.x = n * fdx;
       +                p.y -= n * dy;
       +        }
       +        p.x += screenp.x-t->r.min.x;
       +        p = addpt(divpt(p, t->mag), t->b->r.min);
       +        return p;
       +}
       +
       +int
       +sweep(int but, Rectangle *r)
       +{
       +        Thing *t;
       +        Point p, q, lastq;
       +
       +        esetcursor(&sweep0);
       +        buttons(Down);
       +        if(mouse.buttons != (1<<(but-1))){
       +                buttons(Up);
       +                esetcursor(0);
       +                return 0;
       +        }
       +        p = mouse.xy;
       +        for(t=thing; t; t=t->next)
       +                if(ptinrect(p, t->r))
       +                        break;
       +        if(t)
       +                p = screenpt(t, realpt(t, p));
       +        r->min = p;
       +        r->max = p;
       +        esetcursor(&box);
       +        lastq = ZP;
       +        while(mouse.buttons == (1<<(but-1))){
       +                edrawgetrect(insetrect(*r, -Borderwidth), 1);
       +                mouse = emouse();
       +                edrawgetrect(insetrect(*r, -Borderwidth), 0);
       +                q = mouse.xy;
       +                if(t)
       +                        q = screenpt(t, realpt(t, q));
       +                if(eqpt(q, lastq))
       +                        continue;
       +                *r = canonrect(Rpt(p, q));
       +                lastq = q;
       +        }
       +        esetcursor(0);
       +        if(mouse.buttons){
       +                buttons(Up);
       +                return 0;
       +        }
       +        return 1;
       +}
       +
       +void
       +openedit(Thing *t, Point pt, int c)
       +{
       +        int x, y;
       +        Point p;
       +        Rectangle r;
       +        Rectangle br;
       +        Fontchar *fc;
       +        Thing *nt;
       +
       +        if(t->b->depth > 8){
       +                mesg("image has depth %d; can't handle >8", t->b->depth);
       +                return;
       +        }
       +        br = t->b->r;
       +        if(t->s == 0){
       +                c = -1; 
       +                /* if big enough to bother, sweep box */
       +                if(Dx(br)<=16 && Dy(br)<=16)
       +                        r = br;
       +                else{
       +                        if(!sweep(1, &r))
       +                                return;
       +                        r = rectaddpt(r, subpt(br.min, t->r.min));
       +                        if(!rectclip(&r, br))
       +                                return;
       +                        if(Dx(br) <= 8){
       +                                r.min.x = br.min.x;
       +                                r.max.x = br.max.x;
       +                        }else if(Dx(r) < 4){
       +            toosmall:
       +                                mesg("rectangle too small");
       +                                return;
       +                        }
       +                        if(Dy(br) <= 8){
       +                                r.min.y = br.min.y;
       +                                r.max.y = br.max.y;
       +                        }else if(Dy(r) < 4)
       +                                goto toosmall;
       +                }
       +        }else if(c >= 0){
       +                fc = &t->s->info[c];
       +                r.min.x = fc[0].x;
       +                r.min.y = br.min.y;
       +                r.max.x = fc[1].x;
       +                r.max.y = br.min.y + Dy(br);
       +        }else{
       +                /* just point at character */
       +                fc = t->s->info;
       +                p = addpt(pt, subpt(br.min, t->r.min));
       +                x = br.min.x;
       +                y = br.min.y;
       +                for(c=0; c<t->s->n; c++,fc++){
       +            again:
       +                        r.min.x = x;
       +                        r.min.y = y;
       +                        r.max.x = x + fc[1].x - fc[0].x;
       +                        r.max.y = y + Dy(br);
       +                        if(ptinrect(p, r))
       +                                goto found;
       +                        if(r.max.x >= br.min.x+Dx(t->r)){
       +                                x -= Dx(t->r);
       +                                y += t->s->height;
       +                                if(fc[1].x > fc[0].x)
       +                                        goto again;
       +                        }
       +                        x += fc[1].x - fc[0].x;
       +                }
       +                return;
       +           found:
       +                r = br;
       +                r.min.x = fc[0].x;
       +                r.max.x = fc[1].x;
       +        }
       +        nt = malloc(sizeof(Thing));
       +        if(nt == 0){
       +   nomem:
       +                mesg("can't allocate: %r");
       +                return;
       +        }
       +        memset(nt, 0, sizeof(Thing));
       +        nt->c = c;
       +        nt->b = allocimage(display, r, t->b->chan, 0, DNofill);
       +        if(nt->b == 0){
       +                free(nt);
       +                goto nomem;
       +        }
       +        draw(nt->b, r, t->b, nil, r.min);
       +        nt->name = strdup(t->name);
       +        if(nt->name == 0){
       +                freeimage(nt->b);
       +                free(nt);
       +                goto nomem;
       +        }
       +        nt->parent = t;
       +        nt->mag = mag;
       +        drawthing(nt, 1);
       +}
       +
       +void
       +ckinfo(Thing *t, Rectangle mod)
       +{
       +        int i, j, k, top, bot, n, zero;
       +        Fontchar *fc;
       +        Rectangle r;
       +        Image *b;
       +        Thing *nt;
       +
       +        if(t->parent)
       +                t = t->parent;
       +        if(t->s==0 || Dy(t->b->r)==0)
       +                return;
       +        b = 0;
       +        /* check bounding boxes */
       +        fc = &t->s->info[0];
       +        r.min.y = t->b->r.min.y;
       +        r.max.y = t->b->r.max.y;
       +        for(i=0; i<t->s->n; i++, fc++){
       +                r.min.x = fc[0].x;
       +                r.max.x = fc[1].x;
       +                if(!rectXrect(mod, r))
       +                        continue;
       +                if(b==0 || Dx(b->r)<Dx(r)){
       +                        if(b)
       +                                freeimage(b);
       +                        b = allocimage(display, rectsubpt(r, r.min), t->b->chan, 0, 0);
       +                        if(b == 0){
       +                                mesg("can't alloc image");
       +                                break;
       +                        }
       +                }
       +                draw(b, b->r, display->white, nil, ZP);
       +                draw(b, b->r, t->b, nil, r.min);
       +                top = 100000;
       +                bot = 0;
       +                n = 2+((Dx(r)/8)*t->b->depth);
       +                for(j=0; j<b->r.max.y; j++){
       +                        memset(data, 0, n);
       +                        unloadimage(b, Rect(b->r.min.x, j, b->r.max.x, j+1), data, sizeof data);
       +                        zero = 1;
       +                        for(k=0; k<n; k++)
       +                                if(data[k]){
       +                                        zero = 0;
       +                                        break;
       +                                }
       +                        if(!zero){
       +                                if(top > j)
       +                                        top = j;
       +                                bot = j+1;
       +                        }
       +                }
       +                if(top > j)
       +                        top = 0;
       +                if(top!=fc->top || bot!=fc->bottom){
       +                        fc->top = top;
       +                        fc->bottom = bot;
       +                        for(nt=thing; nt; nt=nt->next)
       +                                if(nt->parent==t && nt->c==i)
       +                                        text(nt);
       +                }
       +        }
       +        if(b)
       +                freeimage(b);
       +}
       +
       +void
       +twidpix(Thing *t, Point p, int set)
       +{
       +        Image *b, *v;
       +        int c;
       +
       +        b = t->b;
       +        if(!ptinrect(p, b->r))
       +                return;
       +        if(set)
       +                c = but1val;
       +        else
       +                c = but2val;
       +        if(b->chan == GREY8)
       +                v = greyvalues[c];
       +        else
       +                v = values[c];
       +        draw(b, Rect(p.x, p.y, p.x+1, p.y+1), v, nil, ZP);
       +        p = screenpt(t, p);
       +        draw(screen, Rect(p.x, p.y, p.x+t->mag, p.y+t->mag), v, nil, ZP);
       +}
       +
       +void
       +twiddle(Thing *t)
       +{
       +        int set;
       +        Point p, lastp;
       +        Image *b;
       +        Thing *nt;
       +        Rectangle mod;
       +
       +        if(mouse.buttons!=1 && mouse.buttons!=2){
       +                buttons(Up);
       +                return;
       +        }
       +        set = mouse.buttons==1;
       +        b = t->b;
       +        lastp = addpt(b->r.min, Pt(-1, -1));
       +        mod = Rpt(addpt(b->r.max, Pt(1, 1)), lastp);
       +        while(mouse.buttons){
       +                p = realpt(t, mouse.xy);
       +                if(!eqpt(p, lastp)){
       +                        lastp = p;
       +                        if(ptinrect(p, b->r)){
       +                                for(nt=thing; nt; nt=nt->next)
       +                                        if(nt->parent==t->parent || nt==t->parent)
       +                                                twidpix(nt, p, set);
       +                                if(t->parent)
       +                                        t->parent->mod = 1;
       +                                else
       +                                        t->mod = 1;
       +                                if(p.x < mod.min.x)
       +                                        mod.min.x = p.x;
       +                                if(p.y < mod.min.y)
       +                                        mod.min.y = p.y;
       +                                if(p.x >= mod.max.x)
       +                                        mod.max.x = p.x+1;
       +                                if(p.y >= mod.max.y)
       +                                        mod.max.y = p.y+1;
       +                        }
       +                }
       +                mouse = emouse();
       +        }
       +        ckinfo(t, mod);
       +}
       +
       +void
       +xselect(void)
       +{
       +        Thing *t;
       +        char line[128], buf[128];
       +        Point p;
       +
       +        if(ptinrect(mouse.xy, cntlr)){
       +                scntl(line);
       +                if(atline(cntlr.min.x, mouse.xy, line, buf)){
       +                        if(mouse.buttons == 1)
       +                                cntledit(buf);
       +                        else
       +                                buttons(Up);
       +                        return;
       +                }
       +                return;
       +        }
       +        for(t=thing; t; t=t->next){
       +                if(attext(t, mouse.xy, buf)){
       +                        if(mouse.buttons == 1)
       +                                textedit(t, buf);
       +                        else
       +                                buttons(Up);
       +                        return;
       +                }
       +                if(ptinrect(mouse.xy, t->r)){
       +                        if(t->parent == 0){
       +                                if(mouse.buttons == 1){
       +                                        p = mouse.xy;
       +                                        buttons(Up);
       +                                        openedit(t, p, -1);
       +                                }else
       +                                        buttons(Up);
       +                                return;
       +                        }
       +                        twiddle(t);
       +                        return;
       +                }
       +        }
       +}
       +
       +void
       +twrite(Thing *t)
       +{
       +        int i, j, x, y, fd, ws, ld;
       +        Biobuf buf;
       +        Rectangle r;
       +
       +        if(t->parent)
       +                t = t->parent;
       +        esetcursor(&busy);
       +        fd = create(t->name, OWRITE, 0666);
       +        if(fd < 0){
       +                mesg("can't write %s: %r", t->name);
       +                return;
       +        }
       +        if(t->face && t->b->depth <= 4){
       +                r = t->b->r;
       +                ld = xlog2(t->b->depth);
       +                /* This heuristic reflects peculiarly different formats */
       +                ws = 4;
       +                if(t->face == 2)        /* cursor file */
       +                        ws = 1;
       +                else if(Dx(r)<32 || ld==0)
       +                        ws = 2;
       +                Binit(&buf, fd, OWRITE);
       +                if(t->face == CURSOR)
       +                        Bprint(&buf, "{");
       +                for(y=r.min.y; y<r.max.y; y++){
       +                        unloadimage(t->b, Rect(r.min.x, y, r.max.x, y+1), data, sizeof data);
       +                        j = 0;
       +                        for(x=r.min.x; x<r.max.x; j+=ws,x+=ws*8>>ld){
       +                                Bprint(&buf, "0x");
       +                                for(i=0; i<ws; i++)
       +                                        Bprint(&buf, "%.2x", data[i+j]);
       +                                Bprint(&buf, ", ");
       +                        }
       +                        if(t->face == CURSOR){
       +                                switch(y){
       +                                case 3: case 7: case 11: case 19: case 23: case 27:
       +                                        Bprint(&buf, "\n ");
       +                                        break;
       +                                case 15:
       +                                        Bprint(&buf, "},\n{");
       +                                        break;
       +                                case 31:
       +                                        Bprint(&buf, "}\n");
       +                                        break;
       +                                }
       +                        }else
       +                                Bprint(&buf, "\n");
       +                }
       +                Bterm(&buf);
       +        }else
       +                if(writeimage(fd, t->b, 0)<0 || (t->s && writesubfont(fd, t->s)<0)){
       +                        close(fd);
       +                        mesg("can't write %s: %r", t->name);
       +                }
       +        t->mod = 0;
       +        close(fd);
       +        mesg("wrote %s", t->name);
       +}
       +
       +void
       +tpixels(void)
       +{
       +        Thing *t;
       +        Point p, lastp;
       +
       +        esetcursor(&pixel);
       +        for(;;){
       +                buttons(Down);
       +                if(mouse.buttons != 4)
       +                        break;
       +                for(t=thing; t; t=t->next){
       +                        lastp = Pt(-1, -1);
       +                        if(ptinrect(mouse.xy, t->r)){
       +                                while(ptinrect(mouse.xy, t->r) && mouse.buttons==4){
       +                                        p = realpt(t, mouse.xy);
       +                                        if(!eqpt(p, lastp)){
       +                                                if(p.y != lastp.y)
       +                                                        unloadimage(t->b, Rect(t->b->r.min.x, p.y, t->b->r.max.x, p.y+1), data, sizeof data);
       +                                                mesg("[%d,%d] = %d=0x%ux", p.x, p.y, value(t->b, p.x), value(t->b, p.x));
       +                                                lastp = p;
       +                                        }
       +                                        mouse = emouse();
       +                                }
       +                                goto Continue;
       +                        }
       +                }
       +                mouse = emouse();
       +    Continue:;
       +        }
       +        buttons(Up);
       +        esetcursor(0);
       +}
       +
       +void
       +tclose1(Thing *t)
       +{
       +        Thing *nt;
       +
       +        if(t == thing)
       +                thing = t->next;
       +        else{
       +                for(nt=thing; nt->next!=t; nt=nt->next)
       +                        ;
       +                nt->next = t->next;
       +        }
       +        do
       +                for(nt=thing; nt; nt=nt->next)
       +                        if(nt->parent == t){
       +                                tclose1(nt);
       +                                break;
       +                        }
       +        while(nt);
       +        if(t->s)
       +                freesubfont(t->s);
       +        else
       +                freeimage(t->b);
       +        free(t->name);
       +        free(t);
       +}
       +
       +void
       +tclose(Thing *t)
       +{
       +        Thing *ct;
       +
       +        if(t->mod){
       +                mesg("%s modified", t->name);
       +                t->mod = 0;
       +                return;
       +        }
       +        /* fiddle to save redrawing unmoved things */
       +        if(t == thing)
       +                ct = 0;
       +        else
       +                for(ct=thing; ct; ct=ct->next)
       +                        if(ct->next==t || ct->next->parent==t)
       +                                break;
       +        tclose1(t);
       +        if(ct)
       +                ct = ct->next;
       +        else
       +                ct = thing;
       +        redraw(ct);
       +}
       +
       +void
       +tread(Thing *t)
       +{
       +        Thing *nt, *new;
       +        Fontchar *i;
       +        Rectangle r;
       +        int nclosed;
       +
       +        if(t->parent)
       +                t = t->parent;
       +        new = tget(t->name);
       +        if(new == 0)
       +                return;
       +        nclosed = 0;
       +    again:
       +        for(nt=thing; nt; nt=nt->next)
       +                if(nt->parent == t){
       +                        if(!rectinrect(nt->b->r, new->b->r)
       +                        || new->b->depth!=nt->b->depth){
       +    closeit:
       +                                nclosed++;
       +                                nt->parent = 0;
       +                                tclose1(nt);
       +                                goto again;
       +                        }
       +                        if((t->s==0) != (new->s==0))
       +                                goto closeit;
       +                        if((t->face==0) != (new->face==0))
       +                                goto closeit;
       +                        if(t->s){        /* check same char */
       +                                if(nt->c >= new->s->n)
       +                                        goto closeit;
       +                                i = &new->s->info[nt->c];
       +                                r.min.x = i[0].x;
       +                                r.max.x = i[1].x;
       +                                r.min.y = new->b->r.min.y;
       +                                r.max.y = new->b->r.max.y;
       +                                if(!eqrect(r, nt->b->r))
       +                                        goto closeit;
       +                        }
       +                        nt->parent = new;
       +                        draw(nt->b, nt->b->r, new->b, nil, nt->b->r.min);
       +                }
       +        new->next = t->next;
       +        if(t == thing)
       +                thing = new;
       +        else{
       +                for(nt=thing; nt->next!=t; nt=nt->next)
       +                        ;
       +                nt->next = new;
       +        }
       +        if(t->s)
       +                freesubfont(t->s);
       +        else
       +                freeimage(t->b);
       +        free(t->name);
       +        free(t);
       +        for(nt=thing; nt; nt=nt->next)
       +                if(nt==new || nt->parent==new)
       +                        if(nclosed == 0)
       +                                drawthing(nt, 0);        /* can draw in place */
       +                        else{
       +                                redraw(nt);        /* must redraw all below */
       +                                break;
       +                        }
       +}
       +
       +void
       +tchar(Thing *t)
       +{
       +        char buf[256], *p;
       +        Rune r;
       +        ulong c, d;
       +
       +        if(t->s == 0){
       +                t = t->parent;
       +                if(t==0 || t->s==0){
       +                        mesg("not a subfont");
       +                        return;
       +                }
       +        }
       +        if(type(buf, "char (hex or character or hex-hex)") == 0)
       +                return;
       +        if(utflen(buf) == 1){
       +                chartorune(&r, buf);
       +                c = r;
       +                d = r;
       +        }else{
       +                if(!strchr(hex, buf[0])){
       +                        mesg("illegal hex character");
       +                        return;
       +                }
       +                c = strtoul(buf, 0, 16);
       +                d = c;
       +                p = utfrune(buf, '-');
       +                if(p){
       +                        d = strtoul(p+1, 0, 16);
       +                        if(d < c){
       +                                mesg("invalid range");
       +                                return;
       +                        }
       +                }
       +        }
       +        c -= t->off;
       +        d -= t->off;
       +        while(c <= d){
       +                if(c<0 || c>=t->s->n){
       +                        mesg("0x%lux not in font %s", c+t->off, t->name);
       +                        return;
       +                }
       +                openedit(t, Pt(0, 0), c);
       +                c++;
       +        }
       +}
       +
       +void
       +apply(void (*f)(Thing*))
       +{
       +        Thing *t;
       +
       +        esetcursor(&sight);
       +        buttons(Down);
       +        if(mouse.buttons == 4)
       +                for(t=thing; t; t=t->next)
       +                        if(ptinrect(mouse.xy, t->er)){
       +                                buttons(Up);
       +                                f(t);
       +                                break;
       +                        }
       +        buttons(Up);
       +        esetcursor(0);
       +}
       +
       +int
       +complement(Image *t)
       +{
       +        int i, n;
       +        uchar *buf;
       +
       +        n = Dy(t->r)*bytesperline(t->r, t->depth);
       +        buf = malloc(n);
       +        if(buf == 0)
       +                return 0;
       +        unloadimage(t, t->r, buf, n);
       +        for(i=0; i<n; i++)
       +                buf[i] = ~buf[i];
       +        loadimage(t, t->r, buf, n);
       +        free(buf);
       +        return 1;
       +}
       +
       +void
       +copy(void)
       +{
       +        Thing *st, *dt, *nt;
       +        Rectangle sr, dr, fr;
       +        Image *tmp;
       +        Point p1, p2;
       +        int but, up;
       +
       +        if(!sweep(3, &sr))
       +                return;
       +        for(st=thing; st; st=st->next)
       +                if(rectXrect(sr, st->r))
       +                        break;
       +        if(st == 0)
       +                return;
       +        /* click gives full rectangle */
       +        if(Dx(sr)<4 && Dy(sr)<4)
       +                sr = st->r;
       +        rectclip(&sr, st->r);
       +        p1 = realpt(st, sr.min);
       +        p2 = realpt(st, Pt(sr.min.x, sr.max.y));
       +        up = 0;
       +        if(p1.x != p2.x){        /* swept across a fold */
       +   onafold:
       +                mesg("sweep spans a fold");
       +                goto Return;
       +        }
       +        p2 = realpt(st, sr.max);
       +        sr.min = p1;
       +        sr.max = p2;
       +        fr.min = screenpt(st, sr.min);
       +        fr.max = screenpt(st, sr.max);
       +        p1 = subpt(p2, p1);        /* diagonal */
       +        if(p1.x==0 || p1.y==0)
       +                return;
       +        border(screen, fr, -1, values[Blue], ZP);
       +        esetcursor(&box);
       +        for(; mouse.buttons==0; mouse=emouse()){
       +                for(dt=thing; dt; dt=dt->next)
       +                        if(ptinrect(mouse.xy, dt->er))
       +                                break;
       +                if(up)
       +                        edrawgetrect(insetrect(dr, -Borderwidth), 0);
       +                up = 0;
       +                if(dt == 0)
       +                        continue;
       +                dr.max = screenpt(dt, realpt(dt, mouse.xy));
       +                dr.min = subpt(dr.max, mulpt(p1, dt->mag));
       +                if(!rectXrect(dr, dt->r))
       +                        continue;
       +                edrawgetrect(insetrect(dr, -Borderwidth), 1);
       +                up = 1;
       +        }
       +        /* if up==1, we had a hit */
       +        esetcursor(0);
       +        if(up)
       +                edrawgetrect(insetrect(dr, -Borderwidth), 0);
       +        but = mouse.buttons;
       +        buttons(Up);
       +        if(!up || but!=4)
       +                goto Return;
       +        dt = 0;
       +        for(nt=thing; nt; nt=nt->next)
       +                if(rectXrect(dr, nt->r)){
       +                        if(dt){
       +                                mesg("ambiguous sweep");
       +                                return;
       +                        }
       +                        dt = nt;
       +                }
       +        if(dt == 0)
       +                goto Return;
       +        p1 = realpt(dt, dr.min);
       +        p2 = realpt(dt, Pt(dr.min.x, dr.max.y));
       +        if(p1.x != p2.x)
       +                goto onafold;
       +        p2 = realpt(dt, dr.max);
       +        dr.min = p1;
       +        dr.max = p2;
       +
       +        if(invert){
       +                tmp = allocimage(display, dr, dt->b->chan, 0, 255);
       +                if(tmp == 0){
       +    nomem:
       +                        mesg("can't allocate temporary");
       +                        goto Return;
       +                }
       +                draw(tmp, dr, st->b, nil, sr.min);
       +                if(!complement(tmp))
       +                        goto nomem;
       +                draw(dt->b, dr, tmp, nil, dr.min);
       +                freeimage(tmp);
       +        }else
       +                draw(dt->b, dr, st->b, nil, sr.min);
       +        if(dt->parent){
       +                draw(dt->parent->b, dr, dt->b, nil, dr.min);
       +                dt = dt->parent;
       +        }
       +        drawthing(dt, 0);
       +        for(nt=thing; nt; nt=nt->next)
       +                if(nt->parent==dt && rectXrect(dr, nt->b->r)){
       +                        draw(nt->b, dr, dt->b, nil, dr.min);
       +                        drawthing(nt, 0);
       +                }
       +        ckinfo(dt, dr);
       +        dt->mod = 1;
       +
       +Return:
       +        /* clear blue box */
       +        drawthing(st, 0);
       +}
       +
       +void
       +menu(void)
       +{
       +        Thing *t;
       +        char *mod;
       +        int sel;
       +        char buf[256];
       +
       +        sel = emenuhit(3, &mouse, &menu3);
       +        switch(sel){
       +        case Mopen:
       +                if(type(buf, "file")){
       +                        t = tget(buf);
       +                        if(t)
       +                                drawthing(t, 1);
       +                }
       +                break;
       +        case Mwrite:
       +                apply(twrite);
       +                break;
       +        case Mread:
       +                apply(tread);
       +                break;
       +        case Mchar:
       +                apply(tchar);
       +                break;
       +        case Mcopy:
       +                copy();
       +                break;
       +        case Mpixels:
       +                tpixels();
       +                break;
       +        case Mclose:
       +                apply(tclose);
       +                break;
       +        case Mexit:
       +                mod = 0;
       +                for(t=thing; t; t=t->next)
       +                        if(t->mod){
       +                                mod = t->name;
       +                                t->mod = 0;
       +                        }
       +                if(mod){
       +                        mesg("%s modified", mod);
       +                        break;
       +                }
       +                esetcursor(&skull);
       +                buttons(Down);
       +                if(mouse.buttons == 4){
       +                        buttons(Up);
       +                        exits(0);
       +                }
       +                buttons(Up);
       +                esetcursor(0);
       +                break;
       +        }
       +}