aboutsummaryrefslogtreecommitdiff
path: root/src/cmd/draw
diff options
context:
space:
mode:
authorrsc <devnull@localhost>2004-04-23 05:14:58 +0000
committerrsc <devnull@localhost>2004-04-23 05:14:58 +0000
commite21fee604e3b53fd8c57ac3817e6d829f62d6ee3 (patch)
tree80147a5d4d886cce392c7e18f5ecb9eaaeb2359d /src/cmd/draw
parentf0f4401f0cfc654646bdf21849627ebcbd5d82b5 (diff)
downloadplan9port-e21fee604e3b53fd8c57ac3817e6d829f62d6ee3.tar.gz
plan9port-e21fee604e3b53fd8c57ac3817e6d829f62d6ee3.tar.bz2
plan9port-e21fee604e3b53fd8c57ac3817e6d829f62d6ee3.zip
move these here
Diffstat (limited to 'src/cmd/draw')
-rw-r--r--src/cmd/draw/stats.c884
-rw-r--r--src/cmd/draw/tweak.c2058
2 files changed, 2942 insertions, 0 deletions
diff --git a/src/cmd/draw/stats.c b/src/cmd/draw/stats.c
new file mode 100644
index 00000000..07d20ad4
--- /dev/null
+++ b/src/cmd/draw/stats.c
@@ -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;
+}
+
diff --git a/src/cmd/draw/tweak.c b/src/cmd/draw/tweak.c
new file mode 100644
index 00000000..c7e12878
--- /dev/null
+++ b/src/cmd/draw/tweak.c
@@ -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;
+ }
+}