various little bug fixes
diff --git a/src/cmd/acme/dat.h b/src/cmd/acme/dat.h
index 789ce1c..b5c4eaf 100644
--- a/src/cmd/acme/dat.h
+++ b/src/cmd/acme/dat.h
@@ -174,6 +174,9 @@
 	uint	org;
 	uint	q0;
 	uint	q1;
+	uint	oldorg;
+	uint	oldq0;
+	uint	oldq1;
 	int	what;
 	int	tabstop;
 	Window	*w;
diff --git a/src/cmd/acme/exec.c b/src/cmd/acme/exec.c
index 15b1467..835be5b 100644
--- a/src/cmd/acme/exec.c
+++ b/src/cmd/acme/exec.c
@@ -580,6 +580,9 @@
 	r = bytetorune(name, &n);
 	for(i=0; i<t->file->ntext; i++){
 		u = t->file->text[i];
+		u->oldorg = u->org;
+		u->oldq0 = u->q0;
+		u->oldq1 = u->q1;
 		/* second and subsequent calls with zero an already empty buffer, but OK */
 		textreset(u);
 		windirfree(u->w);
@@ -601,6 +604,14 @@
 	t->file->unread = FALSE;
 	for(i=0; i<t->file->ntext; i++){
 		u = t->file->text[i];
+		if(u->oldorg > u->file->b.nc)
+			u->oldorg = u->file->b.nc;
+		if(u->oldq0 > u->file->b.nc)
+			u->oldq0 = u->file->b.nc;
+		if(u->oldq1 > u->file->b.nc)
+			u->oldq1 = u->file->b.nc;
+		u->org = u->oldorg;
+		textshow(u, u->oldq0, u->oldq1, 1);
 		textsetselect(&u->w->tag, u->w->tag.file->b.nc, u->w->tag.file->b.nc);
 		textscrdraw(u);
 	}
diff --git a/src/cmd/bzip2/bzip2.c b/src/cmd/bzip2/bzip2.c
index 17c2d06..e7ac995 100644
--- a/src/cmd/bzip2/bzip2.c
+++ b/src/cmd/bzip2/bzip2.c
@@ -23,7 +23,9 @@
 main(int argc, char **argv)
 {
 	int i, ok, stdout;
+	char **oargv;
 
+	oargv = argv;
 	level = 6;
 	stdout = 0;
 	ARGBEGIN{
@@ -36,6 +38,20 @@
 	case 'c':
 		stdout++;
 		break;
+	case 'd':
+		/*
+		 * gnu tar expects bzip2 -d to decompress
+		 * humor it.  ugh.
+		 */
+		/* remove -d from command line - magic! */
+		if(strcmp(argv[0], "-d") == 0){
+			while(*argv++)
+				*(argv-1) = *argv;
+		}else
+			memmove(_args-1, _args, strlen(_args)+1);
+		exec("bunzip2", oargv);
+		sysfatal("exec bunzip2 failed");
+		break;
 	case '1': case '2': case '3': case '4':
 	case '5': case '6': case '7': case '8': case '9':
 		level = ARGC() - '0';
diff --git a/src/cmd/fmt.c b/src/cmd/fmt.c
new file mode 100644
index 0000000..e43731e
--- /dev/null
+++ b/src/cmd/fmt.c
@@ -0,0 +1,241 @@
+#include <u.h>
+#include <libc.h>
+#include <bio.h>
+#include <ctype.h>
+
+/*
+ * block up paragraphs, possibly with indentation
+ */
+
+int extraindent = 0;		/* how many spaces to indent all lines */
+int indent = 0;			/* current value of indent, before extra indent */
+int length = 70;		/* how many columns per output line */
+int join = 1;			/* can lines be joined? */
+int maxtab = 8;
+Biobuf bin;
+Biobuf bout;
+
+typedef struct Word Word;
+struct Word{
+	int	bol;
+	int	indent;
+	char	text[1];
+};
+
+void	fmt(void);
+
+void
+usage(void)
+{
+	fprint(2, "usage: %s [-j] [-i indent] [-l length] [file...]\n", argv0);
+	exits("usage");
+}
+
+void
+main(int argc, char **argv)
+{
+	int i, f;
+	char *s, *err;
+
+	ARGBEGIN{
+	case 'i':
+		extraindent = atoi(EARGF(usage()));
+		break;
+	case 'j':
+		join = 0;
+		break;
+	case 'w':
+	case 'l':
+		length = atoi(EARGF(usage()));
+		break;
+	default:
+		usage();
+	}ARGEND
+
+	if(length <= indent){
+		fprint(2, "%s: line length<=indentation\n", argv0);
+		exits("length");
+	}
+
+	s=getenv("tabstop");
+	if(s!=nil && atoi(s)>0)
+		maxtab=atoi(s);
+	err = nil;
+	Binit(&bout, 1, OWRITE);
+	if(argc <= 0){
+		Binit(&bin, 0, OREAD);
+		fmt();
+	}else{
+		for(i=0; i<argc; i++){
+			f = open(argv[i], OREAD);
+			if(f < 0){
+				fprint(2, "%s: can't open %s: %r\n", argv0, argv[i]);
+				err = "open";
+			}else{
+				Binit(&bin, f, OREAD);
+				fmt();
+				Bterm(&bin);
+				if(i != argc-1)
+					Bputc(&bout, '\n');
+			}
+		}
+	}
+	exits(err);
+}
+
+int
+indentof(char **linep)
+{
+	int i, ind;
+	char *line;
+
+	ind = 0;
+	line = *linep;
+	for(i=0; line[i]; i++)
+		switch(line[i]){
+		default:
+			*linep = line;
+			return ind;
+		case ' ':
+			ind++;
+			break;
+		case '\t':
+			ind += maxtab;
+			ind -= ind%maxtab;
+			break;
+		}
+			
+	/* plain white space doesn't change the indent */
+	*linep = "";
+	return indent;
+}
+
+Word**
+addword(Word **words, int *nwordp, char *s, int l, int indent, int bol)
+{
+	Word *w;
+
+	w = malloc(sizeof(Word)+l+1);
+	memmove(w->text, s, l);
+	w->text[l] = '\0';
+	w->indent = indent;
+	w->bol = bol;
+	words = realloc(words, (*nwordp+1)*sizeof(Word*));
+	words[(*nwordp)++] = w;
+	return words;
+}
+
+Word**
+parseline(char *line, Word **words, int *nwordp)
+{
+	int ind, l, bol;
+
+	ind = indentof(&line);
+	indent = ind;
+	bol = 1;
+	for(;;){
+		/* find next word */
+		while(*line==' ' || *line=='\t')
+			line++;
+		if(*line == '\0'){
+			if(bol)
+				return addword(words, nwordp, "", 0, -1, bol);
+			break;
+		}
+		/* how long is this word? */
+		for(l=0; line[l]; l++)
+			if(line[l]==' ' || line[l]=='\t')
+				break;
+		words = addword(words, nwordp, line, l, indent, bol);
+		bol = 0;
+		line += l;
+	}
+	return words;
+}
+
+void
+printindent(int w)
+{
+	while(w >= maxtab){
+		Bputc(&bout, '\t');
+		w -= maxtab;
+	}
+	while(w > 0){
+		Bputc(&bout, ' ');
+		w--;
+	}
+}
+
+/* give extra space if word ends with period, etc. */
+int
+nspaceafter(char *s)
+{
+	int n;
+
+	n = strlen(s);
+	if(n < 2)
+		return 1;
+	if(isupper(s[0]) && n < 4)
+		return 1;
+	if(strchr(".!?", s[n-1]) != nil)
+		return 2;
+	return 1;
+}
+	
+
+void
+printwords(Word **w, int nw)
+{
+	int i, j, n, col, nsp;
+
+	/* one output line per loop */
+	for(i=0; i<nw; ){
+		/* if it's a blank line, print it */
+		if(w[i]->indent == -1){
+			Bputc(&bout, '\n');
+			if(++i == nw)	/* out of words */
+				break;
+		}
+		/* emit leading indent */
+		col = extraindent+w[i]->indent;
+		printindent(col);
+		/* emit words until overflow; always emit at least one word */
+		for(n=0;; n++){
+			Bprint(&bout, "%s", w[i]->text);
+			col += utflen(w[i]->text);
+			if(++i == nw)
+				break;	/* out of words */
+			if(w[i]->indent != w[i-1]->indent)
+				break;	/* indent change */
+			nsp = nspaceafter(w[i-1]->text);
+			if(col+nsp+utflen(w[i]->text) > extraindent+length)
+				break;	/* fold line */
+			if(!join && n != 0 && w[i]->bol)
+				break;
+			for(j=0; j<nsp; j++)
+				Bputc(&bout, ' ');	/* emit space; another word will follow */
+			col += nsp;
+		}
+		/* emit newline */
+		Bputc(&bout, '\n');
+	}
+}
+
+void
+fmt(void)
+{
+	char *s;
+	int i, nw;
+	Word **w;
+
+	nw = 0;
+	w = nil;
+	while((s = Brdstr(&bin, '\n', 1)) != nil){
+		w = parseline(s, w, &nw);
+		free(s);
+	}
+	printwords(w, nw);
+	for(i=0; i<nw; i++)
+		free(w[i]);
+	free(w);
+}
diff --git a/src/cmd/gzip/gzip.c b/src/cmd/gzip/gzip.c
index 10954d3..f37405b 100644
--- a/src/cmd/gzip/gzip.c
+++ b/src/cmd/gzip/gzip.c
@@ -29,13 +29,29 @@
 main(int argc, char *argv[])
 {
 	int i, ok, stdout;
+	char **oargv;
 
+	oargv = argv;
 	level = 6;
 	stdout = 0;
 	ARGBEGIN{
 	case 'D':
 		debug++;
 		break;
+	case 'd':
+		/*
+		 * gnu tar expects gzip -d to decompress
+		 * humor it.  ugh.
+		 */
+		/* remove -d from command line - magic! */
+		if(strcmp(argv[0], "-d") == 0){
+			while(*argv++)
+				*(argv-1) = *argv;
+		}else
+			memmove(_args-1, _args, strlen(_args)+1);
+		exec("gunzip", oargv);
+		sysfatal("exec gunzip failed");
+		break;
 	case 'v':
 		verbose++;
 		break;
diff --git a/src/cmd/mkfile b/src/cmd/mkfile
index 2b367c6..25a334d 100644
--- a/src/cmd/mkfile
+++ b/src/cmd/mkfile
@@ -2,8 +2,8 @@
 <$PLAN9/src/mkhdr
 
 TARG=`ls *.c | sed 's/\.c//'`
-LDFLAGS=$LDFLAGS
-SHORTLIB=sec fs mux regexp9 thread bio 9
+LDFLAGS=$LDFLAGS -L$X11/lib -lX11
+SHORTLIB=sec fs mux regexp9 draw thread bio 9
 
 <$PLAN9/src/mkmany
 
diff --git a/src/cmd/rio/menu.c b/src/cmd/rio/menu.c
index bf621f2..1be69a2 100644
--- a/src/cmd/rio/menu.c
+++ b/src/cmd/rio/menu.c
@@ -47,11 +47,10 @@
 	if (s == 0)
 		return;
 	c = getclient(e->window, 0);
-	if (c) {
+	if(c){
 		if (debug) fprintf(stderr, "but: e x=%d y=%d c x=%d y=%d dx=%d dy=%d BORDR %d\n",
 				e->x, e->y, c->x, c->y, c->dx, c->dy, BORDER);
-	    if (e->x <= BORDER || e->x > (c->dx + BORDER) ||
-	        e->y <= BORDER || e->y > (c->dy + BORDER)) {
+		if(borderorient(c, e->x, e->y) != BorderUnknown){
 			switch (e->button) {
 			case Button1:
 			case Button2:
@@ -63,11 +62,10 @@
 			default:
 				return;
 			}
-	    }
+		}
 		e->x += c->x - BORDER;
 		e->y += c->y - BORDER;
-	}
-	else if (e->window != e->root) {
+	} else if (e->window != e->root) {
 		if (debug) fprintf(stderr, "but no client: e x=%d y=%d\n",
 				e->x, e->y);
 		XTranslateCoordinates(dpy, e->window, s->root, e->x, e->y,
diff --git a/src/cmd/tweak.c b/src/cmd/tweak.c
new file mode 100644
index 0000000..20b6ac9
--- /dev/null
+++ b/src/cmd/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[] = {
+	[Mopen]	"open",
+	[Mread]	"read",
+	[Mwrite]	"write",
+	[Mcopy]	"copy",
+	[Mchar]	"char",
+	[Mpixels]	"pixels",
+	[Mclose]	"close",
+	[Mexit]	"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;
+	}
+}