shithub: plan9front

Download patch

ref: b5783b1e39122f466c1a577720496284223e2217
parent: ea347ee7f181208be5a2ace0e35d268ccfefa1e4
author: cinap_lenrek <cinap_lenrek@felloff.net>
date: Sun Mar 7 14:26:30 EST 2021

rio: make window focus changes deterministic, cleanup wind.c

Switching window focus used to be non deterministic
as the current window in focus (Window *input) was set
concurrently while processing window messages such as
Resized and Topped.

This implements a new approach where wcurrent() and
wuncurrent() are responsible for the synchronization
and switch of the input.

It is implemented by sending a Repaint message to the
old input window first, neccesarily waiting until that
window releases the focus and then input is updated
and then a Topped or Reshaped message is send to the
new input window.

Note, that when the whole screen is resized that no
input changes need to happening anymore.

--- a/sys/src/cmd/rio/dat.h	Sat Mar  6 16:52:00 2021
+++ b/sys/src/cmd/rio/dat.h	Sun Mar  7 14:26:30 2021
@@ -183,45 +183,25 @@
 void		wtopme(Window*);
 void		wbottomme(Window*);
 char*	wcontents(Window*, int*);
-int		wbswidth(Window*, Rune);
-int		wclickmatch(Window*, int, int, int, uint*);
 int		wclose(Window*);
-int		wctlmesg(Window*, int, Rectangle, void*);
 uint		wbacknl(Window*, uint, uint);
-uint		winsert(Window*, Rune*, int, uint);
-void		waddraw(Window*, Rune*, int);
-void		wborder(Window*, int);
-void		wclunk(Window*);
-void		wclosewin(Window*);
 void		wcurrent(Window*);
+void		wuncurrent(Window*);
 void		wcut(Window*);
-void		wdelete(Window*, uint, uint);
-void		wstretchsel(Window*, uint, uint*, uint*, int);
-void		wfill(Window*);
-void		wframescroll(Window*, int);
-void		wkeyctl(Window*, Rune);
-void		wmousectl(Window*);
-void		wmovemouse(Window*, Point);
 void		wpaste(Window*);
 void		wplumb(Window*);
 void		wlook(Window*);
-void		wrefresh(Window*);
-void		wrepaint(Window*);
-void		wresize(Window*, Image*);
 void		wscrdraw(Window*);
 void		wscroll(Window*, int);
-void		wselect(Window*);
 void		wsend(Window*);
 void		wsendctlmesg(Window*, int, Rectangle, void*);
 void		wsetcursor(Window*, int);
 void		wsetname(Window*);
 void		wsetorigin(Window*, uint, int);
 void		wsetpid(Window*, int, int);
-void		wsetselect(Window*, uint, uint);
 void		wshow(Window*, uint);
 void		wsnarf(Window*);
 void 		wscrsleep(Window*, uint);
-void		wsetcols(Window*, int);
 
 struct Dirtab
 {
--- a/sys/src/cmd/rio/rio.c	Sat Mar  6 16:52:00 2021
+++ b/sys/src/cmd/rio/rio.c	Sun Mar  7 14:26:30 2021
@@ -530,8 +530,10 @@
 				else
 					i = drag(winput);
 				sweeping = FALSE;
-				if(i != nil)
+				if(i != nil){
+					wcurrent(winput);
 					wsendctlmesg(winput, Reshaped, i->r, i);
+				}
 				wclose(winput);
 				continue;
 			}
@@ -616,9 +618,10 @@
 		if(j < nhidden){
 			im = allocimage(display, r, screen->chan, 0, DNofill);
 			r = ZR;
-		} else
+		} else {
 			im = allocwindow(wscreen, r, Refbackup, DNofill);
-		if(im)
+		}
+		if(im!=nil)
 			wsendctlmesg(w, Reshaped, r, im);
 		wclose(w);
 	}
@@ -1001,7 +1004,7 @@
 	Window *w;
 
 	w = pointto(TRUE);
-	if(w)
+	if(w!=nil)
 		wsendctlmesg(w, Deleted, ZR, nil);
 }
 
@@ -1016,8 +1019,10 @@
 		return;
 	incref(w);
 	i = sweep();
-	if(i)
+	if(i!=nil){
+		wcurrent(w);
 		wsendctlmesg(w, Reshaped, i->r, i);
+	}
 	wclose(w);
 }
 
@@ -1032,8 +1037,10 @@
 		return;
 	incref(w);
 	i = drag(w);
-	if(i)
+	if(i!=nil){
+		wcurrent(w);
 		wsendctlmesg(w, Reshaped, i->r, i);
+	}
 	wclose(w);
 }
 
@@ -1049,8 +1056,9 @@
 	if(nhidden >= nelem(hidden))
 		return 0;
 	incref(w);
+	wuncurrent(w);
 	i = allocimage(display, w->screenr, w->i->chan, 0, DNofill);
-	if(i){
+	if(i!=nil){
 		hidden[nhidden++] = w;
 		wsendctlmesg(w, Reshaped, ZR, i);
 	}
@@ -1070,8 +1078,9 @@
 	if(j == nhidden)
 		return -1;	/* not hidden */
 	incref(w);
+	wcurrent(w);
 	i = allocwindow(wscreen, w->i->r, Refbackup, DNofill);
-	if(i){
+	if(i!=nil){
 		--nhidden;
 		memmove(hidden+j, hidden+j+1, (nhidden-j)*sizeof(Window*));
 		wsendctlmesg(w, Reshaped, w->i->r, i);
@@ -1109,8 +1118,9 @@
 	for(j=0; j<nwindow; j++)
 		if(window[j] == w){
 			incref(w);
-			wtopme(w);
 			wcurrent(w);
+			wtopme(w);
+			wsendctlmesg(w, Topped, ZR, nil);
 			wclose(w);
 			return;
 		}
--- a/sys/src/cmd/rio/wctl.c	Sat Mar  6 16:52:00 2021
+++ b/sys/src/cmd/rio/wctl.c	Sun Mar  7 14:26:30 2021
@@ -380,6 +380,7 @@
 		} else { /* hidden */
 			if(eqrect(r, w->i->r))
 				return 1;
+			wuncurrent(w);
 			i = allocimage(display, r, w->i->chan, 0, DNofill);
 			r = ZR;
 		}
@@ -409,8 +410,9 @@
 			strcpy(err, "window is hidden");
 			return -1;
 		}
-		wtopme(w);
 		wcurrent(w);
+		wtopme(w);
+		wsendctlmesg(w, Topped, ZR, nil);
 		return 1;
 	case Hide:
 		switch(whide(w)){
--- a/sys/src/cmd/rio/wind.c	Sat Mar  6 16:52:00 2021
+++ b/sys/src/cmd/rio/wind.c	Sun Mar  7 14:26:30 2021
@@ -12,758 +12,552 @@
 #include "dat.h"
 #include "fns.h"
 
-enum
+Window*
+wlookid(int id)
 {
-	HiWater	= 640000,	/* max size of history */
-	LoWater	= 400000,	/* min size of history after max'ed */
-	MinWater	= 20000,	/* room to leave available when reallocating */
-};
+	int i;
 
-static	int	topped;
-static	int	id;
-static	Cursor	*lastcursor;
+	for(i=0; i<nwindow; i++)
+		if(window[i]->id == id)
+			return window[i];
+	return nil;
+}
 
 Window*
-wmk(Image *i, Mousectl *mc, Channel *ck, Channel *cctl, int scrolling)
+wpointto(Point pt)
 {
-	Window *w;
-	Rectangle r;
+	int i;
+	Window *v, *w;
 
-	w = emalloc(sizeof(Window));
-	w->screenr = i->r;
-	r = insetrect(i->r, Selborder+1);
-	w->i = i;
-	w->mc = *mc;
-	w->ck = ck;
-	w->cctl = cctl;
-	w->cursorp = nil;
-	w->conswrite = chancreate(sizeof(Conswritemesg), 0);
-	w->consread =  chancreate(sizeof(Consreadmesg), 0);
-	w->kbdread =  chancreate(sizeof(Consreadmesg), 0);
-	w->mouseread =  chancreate(sizeof(Mousereadmesg), 0);
-	w->wctlread =  chancreate(sizeof(Consreadmesg), 0);
-	w->complete = chancreate(sizeof(Completion*), 0);
-	w->gone = chancreate(sizeof(char*), 0);
-	w->scrollr = r;
-	w->scrollr.max.x = r.min.x+Scrollwid;
-	w->lastsr = ZR;
-	r.min.x += Scrollwid+Scrollgap;
-	frinit(w, r, font, i, cols);
-	w->maxtab = maxtab*stringwidth(font, "0");
-	w->topped = ++topped;
-	w->id = ++id;
-	w->notefd = -1;
-	w->scrolling = scrolling;
-	w->dir = estrdup(startdir);
-	w->label = estrdup("<unnamed>");
-	r = insetrect(w->i->r, Selborder);
-	draw(w->i, r, cols[BACK], nil, w->entire.min);
-	wborder(w, Selborder);
-	wscrdraw(w);
-	incref(w);	/* ref will be removed after mounting; avoids delete before ready to be deleted */
+	w = nil;
+	for(i=0; i<nwindow; i++){
+		v = window[i];
+		if(ptinrect(pt, v->screenr))
+		if(w==nil || v->topped>w->topped)
+			w = v;
+	}
 	return w;
 }
 
+static	int	topped;
+
 void
-wsetname(Window *w)
+wtopme(Window *w)
 {
-	int i, n;
-	char err[ERRMAX];
-	
-	n = snprint(w->name, sizeof(w->name)-2, "window.%d.%d", w->id, w->namecount++);
-	for(i='A'; i<='Z'; i++){
-		if(nameimage(w->i, w->name, 1) > 0)
-			return;
-		errstr(err, sizeof err);
-		if(strcmp(err, "image name in use") != 0)
-			break;
-		w->name[n] = i;
-		w->name[n+1] = 0;
+	if(w!=nil && w->i!=nil && w->topped!=topped){
+		w->topped = ++topped;
+		topwindow(w->i);
+		flushimage(display, 1);
 	}
-	w->name[0] = 0;
-	fprint(2, "rio: setname failed: %s\n", err);
 }
 
 void
-wresize(Window *w, Image *i)
+wbottomme(Window *w)
 {
-	Rectangle r;
+	if(w!=nil && w->i!=nil){
+		w->topped = - ++topped;
+		bottomwindow(w->i);
+		flushimage(display, 1);
+	}
+}
 
-	wclosewin(w);
-	w->i = i;
-	w->mc.image = i;
-	r = insetrect(i->r, Selborder+1);
-	w->scrollr = r;
-	w->scrollr.max.x = r.min.x+Scrollwid;
-	w->lastsr = ZR;
-	r.min.x += Scrollwid+Scrollgap;
-	frclear(w, FALSE);
-	frinit(w, r, w->font, w->i, cols);
-	wsetcols(w, 1);
-	w->maxtab = maxtab*stringwidth(w->font, "0");
-	if(!w->mouseopen || !w->winnameread){
-		r = insetrect(w->i->r, Selborder);
-		draw(w->i, r, cols[BACK], nil, w->entire.min);
-		wfill(w);
-		wsetselect(w, w->q0, w->q1);
-		wscrdraw(w);
+Window*
+wtop(Point pt)
+{
+	Window *w;
+
+	w = wpointto(pt);
+	if(w!=nil){
+		incref(w);
+		wcurrent(w);
+		wtopme(w);
+		wsendctlmesg(w, Topped, ZR, nil);
+		wclose(w);
 	}
-	wborder(w, Selborder);
-	flushimage(display, 1);
-	wsetname(w);
-	w->topped = ++topped;
-	w->resized = TRUE;
-	w->winnameread = FALSE;
-	w->mouse.counter++;
-	w->wctlready = 1;
+	return w;
 }
 
 void
-wrefresh(Window *w)
+wcurrent(Window *w)
 {
-	Rectangle r;
+	Channel *c;
 
+	if(input == nil){
+		input = w;
+		return;
+	}
 	if(w == input)
-		wborder(w, Selborder);
-	else
-		wborder(w, Unselborder);
-	r = insetrect(w->i->r, Selborder);
-	draw(w->i, r, w->cols[BACK], nil, w->entire.min);
-	wfill(w);
-	w->ticked = 0;
-	if(w->p0 > 0)
-		frdrawsel(w, frptofchar(w, 0), 0, w->p0, 0);
-	if(w->p1 < w->nchars)
-		frdrawsel(w, frptofchar(w, w->p1), w->p1, w->nchars, 0);
-	frdrawsel(w, frptofchar(w, w->p0), w->p0, w->p1, 1);
-	w->lastsr = ZR;
-	wscrdraw(w);
+		return;
+	incref(input);
+	c = chancreate(sizeof(Window*), 0);
+	wsendctlmesg(input, Repaint, ZR, c);
+	sendp(c, w);		/* send the new input */
+	wclose(recvp(c));	/* release old input */
+	chanfree(c);
 }
 
-int
-wclose(Window *w)
+void
+wuncurrent(Window *w)
 {
-	int i;
+	Channel *c;
 
-	i = decref(w);
-	if(i > 0)
-		return 0;
-	if(i < 0)
-		error("negative ref count");
-	wclunk(w);
-	wsendctlmesg(w, Exited, ZR, nil);
-	return 1;
+	if(input == nil || w != input)
+		return;
+	c = chancreate(sizeof(Window*), 0);
+	wsendctlmesg(w, Repaint, ZR, c);
+	sendp(c, nil);
+	recvp(c);
+	chanfree(c);
 }
 
-void
-showcandidates(Window *, Completion *);
+static	Cursor	*lastcursor;
 
 void
-winctl(void *arg)
+riosetcursor(Cursor *p)
 {
-	Rune *rp, *up, r;
-	uint qh, q0;
-	int nr, nb, c, wid, i, npart, initial, lastb;
-	char *s, *t, part[3];
-	Window *w;
-	Mousestate *mp, m;
-	enum { WKbd, WKbdread, WMouse, WMouseread, WCtl, WCwrite, WCread, WWread, WComplete, Wgone, NWALT };
-	Alt alts[NWALT+1];
-	Consreadmesg crm;
-	Mousereadmesg mrm;
-	Conswritemesg cwm;
-	Stringpair pair;
-	Wctlmesg wcm;
-	Completion *cr;
-	char *kbdq[32], *kbds;
-	uint kbdqr, kbdqw;
+	if(p==lastcursor)
+		return;
+	setcursor(mousectl, p);
+	lastcursor = p;
+}
 
-	w = arg;
-	threadsetname("winctl-id%d", w->id);
+void
+wsetcursor(Window *w, int force)
+{
+	Cursor *p;
 
-	mrm.cm = chancreate(sizeof(Mouse), 0);
-	crm.c1 = chancreate(sizeof(Stringpair), 0);
-	crm.c2 = chancreate(sizeof(Stringpair), 0);
-	cwm.cw = chancreate(sizeof(Stringpair), 0);
-	
-	alts[WKbd].c = w->ck;
-	alts[WKbd].v = &kbds;
-	alts[WKbd].op = CHANRCV;
-	alts[WKbdread].c = w->kbdread;
-	alts[WKbdread].v = &crm;
-	alts[WKbdread].op = CHANSND;
-	alts[WMouse].c = w->mc.c;
-	alts[WMouse].v = &w->mc.Mouse;
-	alts[WMouse].op = CHANRCV;
-	alts[WMouseread].c = w->mouseread;
-	alts[WMouseread].v = &mrm;
-	alts[WMouseread].op = CHANSND;
-	alts[WCtl].c = w->cctl;
-	alts[WCtl].v = &wcm;
-	alts[WCtl].op = CHANRCV;
-	alts[WCwrite].c = w->conswrite;
-	alts[WCwrite].v = &cwm;
-	alts[WCwrite].op = CHANSND;
-	alts[WCread].c = w->consread;
-	alts[WCread].v = &crm;
-	alts[WCread].op = CHANSND;
-	alts[WWread].c = w->wctlread;
-	alts[WWread].v = &crm;
-	alts[WWread].op = CHANSND;
-	alts[WComplete].c = w->complete;
-	alts[WComplete].v = &cr;
-	alts[WComplete].op = CHANRCV;
-	alts[Wgone].c = w->gone;
-	alts[Wgone].v = "window deleted";
-	alts[Wgone].op = CHANNOP;
-	alts[NWALT].op = CHANEND;
+	if(menuing || sweeping || (w!=input && wpointto(mouse->xy)!=w))
+		return;
+	if(w==nil)
+		p = nil;
+	else {
+		p = w->cursorp;
+		if(p==nil && w->holding)
+			p = &whitearrow;
+	}
+	if(p && force)	/* force cursor reload */
+		lastcursor = nil;
+	riosetcursor(p);
+}
 
-	kbdqr = kbdqw = 0;
-	npart = 0;
-	lastb = -1;
-	for(;;){
-		if(w->i==nil){
-			/* window deleted */
-			alts[Wgone].op = CHANSND;
+static void
+waddraw(Window *w, Rune *r, int nr)
+{
+	w->raw = runerealloc(w->raw, w->nraw+nr);
+	runemove(w->raw+w->nraw, r, nr);
+	w->nraw += nr;
+}
 
-			alts[WKbdread].op = CHANNOP;
-			alts[WMouseread].op = CHANNOP;
-			alts[WCwrite].op = CHANNOP;
-			alts[WWread].op = CHANNOP;
-			alts[WCread].op = CHANNOP;
-		} else {
-			alts[WKbdread].op = (w->kbdopen && kbdqw != kbdqr) ?
-				CHANSND : CHANNOP;
-			alts[WMouseread].op = (w->mouseopen && w->mouse.counter != w->mouse.lastcounter) ? 
-				CHANSND : CHANNOP;
-			alts[WCwrite].op = w->scrolling || w->mouseopen || (w->qh <= w->org+w->nchars) ?
-				CHANSND : CHANNOP;
-			alts[WWread].op = w->wctlready ?
-				CHANSND : CHANNOP;
-			/* this code depends on NL and EOT fitting in a single byte */
-			/* kind of expensive for each loop; worth precomputing? */
-			if(w->holding)
-				alts[WCread].op = CHANNOP;
-			else if(npart || (w->rawing && w->nraw>0))
-				alts[WCread].op = CHANSND;
-			else{
-				alts[WCread].op = CHANNOP;
-				for(i=w->qh; i<w->nr; i++){
-					c = w->r[i];
-					if(c=='\n' || c=='\004'){
-						alts[WCread].op = CHANSND;
-						break;
-					}
-				}
-			}
-		}
-		switch(alt(alts)){
-		case WKbd:
-			if(kbdqw - kbdqr < nelem(kbdq))
-				kbdq[kbdqw++ % nelem(kbdq)] = kbds;
-			else
-				free(kbds);
-			if(w->kbdopen)
-				continue;
-			while(kbdqr != kbdqw){
-				kbds = kbdq[kbdqr++ % nelem(kbdq)];
-				if(*kbds == 'c'){
-					chartorune(&r, kbds+1);
-					if(r)
-						wkeyctl(w, r);
-				}
-				free(kbds);
-			}
-			break;
-		case WKbdread:
-			recv(crm.c1, &pair);
-			nb = 0;
-			while(kbdqr != kbdqw){
-				kbds = kbdq[kbdqr % nelem(kbdq)];
-				i = strlen(kbds)+1;
-				if(nb+i > pair.ns)
-					break;
-				memmove((char*)pair.s + nb, kbds, i);
-				free(kbds);
-				nb += i;
-				kbdqr++;
-			}
-			pair.ns = nb;
-			send(crm.c2, &pair);
-			continue;
-		case WMouse:
-			if(w->mouseopen) {
-				w->mouse.counter++;
+enum
+{
+	HiWater	= 640000,	/* max size of history */
+	LoWater	= 400000,	/* min size of history after max'ed */
+	MinWater	= 20000,	/* room to leave available when reallocating */
+};
 
-				/* queue click events */
-				if(!w->mouse.qfull && lastb != w->mc.buttons) {	/* add to ring */
-					mp = &w->mouse.queue[w->mouse.wi];
-					if(++w->mouse.wi == nelem(w->mouse.queue))
-						w->mouse.wi = 0;
-					if(w->mouse.wi == w->mouse.ri)
-						w->mouse.qfull = TRUE;
-					mp->Mouse = w->mc;
-					mp->counter = w->mouse.counter;
-					lastb = w->mc.buttons;
-				}
-			} else
-				wmousectl(w);
-			break;
-		case WMouseread:
-			/* send a queued event or, if the queue is empty, the current state */
-			/* if the queue has filled, we discard all the events it contained. */
-			/* the intent is to discard frantic clicking by the user during long latencies. */
-			w->mouse.qfull = FALSE;
-			if(w->mouse.wi != w->mouse.ri) {
-				m = w->mouse.queue[w->mouse.ri];
-				if(++w->mouse.ri == nelem(w->mouse.queue))
-					w->mouse.ri = 0;
-			} else
-				m = (Mousestate){w->mc.Mouse, w->mouse.counter};
+static uint
+winsert(Window *w, Rune *r, int n, uint q0)
+{
+	uint m;
 
-			w->mouse.lastcounter = m.counter;
-			send(mrm.cm, &m.Mouse);
-			continue;
-		case WCtl:
-			if(wctlmesg(w, wcm.type, wcm.r, wcm.p) == Exited){
-				while(kbdqr != kbdqw)
-					free(kbdq[kbdqr++ % nelem(kbdq)]);
-				chanfree(crm.c1);
-				chanfree(crm.c2);
-				chanfree(mrm.cm);
-				chanfree(cwm.cw);
-				threadexits(nil);
-			}
-			continue;
-		case WCwrite:
-			recv(cwm.cw, &pair);
-			rp = pair.s;
-			nr = pair.ns;
-			for(i=0; i<nr; i++)
-				if(rp[i] == '\b'){
-					up = rp+i;
-					initial = 0;
-					for(; i<nr; i++){
-						if(rp[i] == '\b'){
-							if(up == rp)
-								initial++;
-							else
-								up--;
-						}else
-							*up++ = rp[i];
-					}
-					if(initial){
-						if(initial > w->qh)
-							initial = w->qh;
-						qh = w->qh-initial;
-						wdelete(w, qh, qh+initial);
-						w->qh = qh;
-					}
-					nr = up - rp;
-					break;
-				}
-			w->qh = winsert(w, rp, nr, w->qh)+nr;
-			if(w->scrolling || w->mouseopen)
-				wshow(w, w->qh);
-			wsetselect(w, w->q0, w->q1);
-			wscrdraw(w);
-			free(rp);
-			break;
-		case WCread:
-			recv(crm.c1, &pair);
-			t = pair.s;
-			nb = pair.ns;
-			i = npart;
-			npart = 0;
-			if(i)
-				memmove(t, part, i);
-			while(i<nb && (w->qh<w->nr || w->nraw>0)){
-				if(w->qh == w->nr){
-					wid = runetochar(t+i, &w->raw[0]);
-					w->nraw--;
-					runemove(w->raw, w->raw+1, w->nraw);
-				}else
-					wid = runetochar(t+i, &w->r[w->qh++]);
-				c = t[i];	/* knows break characters fit in a byte */
-				i += wid;
-				if(!w->rawing && (c == '\n' || c=='\004')){
-					if(c == '\004')
-						i--;
-					break;
-				}
-			}
-			if(i==nb && w->qh<w->nr && w->r[w->qh]=='\004')
-				w->qh++;
-			if(i > nb){
-				npart = i-nb;
-				memmove(part, t+nb, npart);
-				i = nb;
-			}
-			pair.s = t;
-			pair.ns = i;
-			send(crm.c2, &pair);
-			continue;
-		case WWread:
-			w->wctlready = 0;
-			recv(crm.c1, &pair);
-			s = Dx(w->screenr) > 0 ? "visible" : "hidden";
-			t = "notcurrent";
-			if(w == input)
-				t = "current";
-			pair.ns = snprint(pair.s, pair.ns+1, "%11d %11d %11d %11d %11s %11s ",
-				w->i->r.min.x, w->i->r.min.y, w->i->r.max.x, w->i->r.max.y, t, s);
-			send(crm.c2, &pair);
-			continue;
-		case WComplete:
-			if(w->i!=nil){
-				if(!cr->advance)
-					showcandidates(w, cr);
-				if(cr->advance){
-					rp = runesmprint("%s", cr->string);
-					if(rp){
-						nr = runestrlen(rp);
-						q0 = w->q0;
-						q0 = winsert(w, rp, nr, q0);
-						wshow(w, q0+nr);
-						free(rp);
-					}
-				}
-			}
-			freecompletion(cr);
-			break;
+	if(n == 0)
+		return q0;
+	if(w->nr+n>HiWater && q0>=w->org && q0>=w->qh){
+		m = min(HiWater-LoWater, min(w->org, w->qh));
+		w->org -= m;
+		w->qh -= m;
+		if(w->q0 > m)
+			w->q0 -= m;
+		else
+			w->q0 = 0;
+		if(w->q1 > m)
+			w->q1 -= m;
+		else
+			w->q1 = 0;
+		w->nr -= m;
+		runemove(w->r, w->r+m, w->nr);
+		q0 -= m;
+	}
+	if(w->nr+n > w->maxr){
+		/*
+		 * Minimize realloc breakage:
+		 *	Allocate at least MinWater
+		 * 	Double allocation size each time
+		 *	But don't go much above HiWater
+		 */
+		m = max(min(2*(w->nr+n), HiWater), w->nr+n)+MinWater;
+		if(m > HiWater)
+			m = max(HiWater+MinWater, w->nr+n);
+		if(m > w->maxr){
+			w->r = runerealloc(w->r, m);
+			w->maxr = m;
 		}
-		if(w->i!=nil && Dx(w->screenr) > 0 && display->bufp > display->buf)
-			flushimage(display, 1);
 	}
+	runemove(w->r+q0+n, w->r+q0, w->nr-q0);
+	runemove(w->r+q0, r, n);
+	w->nr += n;
+	/* if output touches, advance selection, not qh; works best for keyboard and output */
+	if(q0 <= w->q1)
+		w->q1 += n;
+	if(q0 <= w->q0)
+		w->q0 += n;
+	if(q0 < w->qh)
+		w->qh += n;
+	if(q0 < w->org)
+		w->org += n;
+	else if(q0 <= w->org+w->nchars)
+		frinsert(w, r, r+n, q0-w->org);
+	return q0;
 }
 
-void
-waddraw(Window *w, Rune *r, int nr)
+static void
+wfill(Window *w)
 {
-	w->raw = runerealloc(w->raw, w->nraw+nr);
-	runemove(w->raw+w->nraw, r, nr);
-	w->nraw += nr;
-}
+	Rune *rp;
+	int i, n, m, nl;
 
-/*
- * Need to do this in a separate proc because if process we're interrupting
- * is dying and trying to print tombstone, kernel is blocked holding p->debug lock.
- */
-void
-interruptproc(void *v)
-{
-	int *notefd;
+	while(w->lastlinefull == FALSE){
+		n = w->nr-(w->org+w->nchars);
+		if(n == 0)
+			break;
+		if(n > 2000)	/* educated guess at reasonable amount */
+			n = 2000;
+		rp = w->r+(w->org+w->nchars);
 
-	notefd = v;
-	write(*notefd, "interrupt", 9);
-	close(*notefd);
-	free(notefd);
+		/*
+		 * it's expensive to frinsert more than we need, so
+		 * count newlines.
+		 */
+		nl = w->maxlines-w->nlines;
+		m = 0;
+		for(i=0; i<n; ){
+			if(rp[i++] == '\n'){
+				m++;
+				if(m >= nl)
+					break;
+			}
+		}
+		frinsert(w, rp, rp+i, w->nchars);
+	}
 }
 
-int
-windfilewidth(Window *w, uint q0, int oneelement)
+static void
+wsetselect(Window *w, uint q0, uint q1)
 {
-	uint q;
-	Rune r;
+	int p0, p1;
 
-	q = q0;
-	while(q > 0){
-		r = w->r[q-1];
-		if(r<=' ' || r=='=' || r=='^' || r=='(' || r=='{')
-			break;
-		if(oneelement && r=='/')
-			break;
-		--q;
+	/* w->p0 and w->p1 are always right; w->q0 and w->q1 may be off */
+	w->q0 = q0;
+	w->q1 = q1;
+	/* compute desired p0,p1 from q0,q1 */
+	p0 = q0-w->org;
+	p1 = q1-w->org;
+	if(p0 < 0)
+		p0 = 0;
+	if(p1 < 0)
+		p1 = 0;
+	if(p0 > w->nchars)
+		p0 = w->nchars;
+	if(p1 > w->nchars)
+		p1 = w->nchars;
+	if(p0==w->p0 && p1==w->p1)
+		return;
+	/* screen disagrees with desired selection */
+	if(w->p1<=p0 || p1<=w->p0 || p0==p1 || w->p1==w->p0){
+		/* no overlap or too easy to bother trying */
+		frdrawsel(w, frptofchar(w, w->p0), w->p0, w->p1, 0);
+		frdrawsel(w, frptofchar(w, p0), p0, p1, 1);
+		goto Return;
 	}
-	return q0-q;
+	/* overlap; avoid unnecessary painting */
+	if(p0 < w->p0){
+		/* extend selection backwards */
+		frdrawsel(w, frptofchar(w, p0), p0, w->p0, 1);
+	}else if(p0 > w->p0){
+		/* trim first part of selection */
+		frdrawsel(w, frptofchar(w, w->p0), w->p0, p0, 0);
+	}
+	if(p1 > w->p1){
+		/* extend selection forwards */
+		frdrawsel(w, frptofchar(w, w->p1), w->p1, p1, 1);
+	}else if(p1 < w->p1){
+		/* trim last part of selection */
+		frdrawsel(w, frptofchar(w, p1), p1, w->p1, 0);
+	}
+
+    Return:
+	w->p0 = p0;
+	w->p1 = p1;
 }
 
-void
-showcandidates(Window *w, Completion *c)
+static void
+wborder(Window *w, int type)
 {
-	int i;
-	Fmt f;
-	Rune *rp;
-	uint nr, qline;
-	char *s;
-
-	runefmtstrinit(&f);
-	if (c->nmatch == 0)
-		s = "[no matches in ";
-	else
-		s = "[";
-	if(c->nfile > 32)
-		fmtprint(&f, "%s%d files]\n", s, c->nfile);
-	else{
-		fmtprint(&f, "%s", s);
-		for(i=0; i<c->nfile; i++){
-			if(i > 0)
-				fmtprint(&f, " ");
-			fmtprint(&f, "%s", c->filename[i]);
-		}
-		fmtprint(&f, "]\n");
-	}
-	rp = runefmtstrflush(&f);
-	nr = runestrlen(rp);
-
-	/* place text at beginning of line before cursor and host point */
-	qline = min(w->qh, w->q0);
-	while(qline>0 && w->r[qline-1] != '\n')
-		qline--;
+	Image *col;
 
-	if(qline == w->qh){
-		/* advance host point to avoid readback */
-		w->qh = winsert(w, rp, nr, qline)+nr;
-	} else {
-		winsert(w, rp, nr, qline);
+	if(w->i == nil)
+		return;
+	if(w->holding){
+		if(type == Selborder)
+			col = holdcol;
+		else
+			col = paleholdcol;
+	}else{
+		if(type == Selborder)
+			col = titlecol;
+		else
+			col = lighttitlecol;
 	}
-	free(rp);
+	border(w->i, w->i->r, Selborder, col, ZP);
 }
 
-typedef struct Completejob Completejob;
-struct Completejob
-{
-	char	*dir;
-	char	*str;
-	Window	*win;
-};
-
-void
-completeproc(void *arg)
+static void
+wsetcols(Window *w, int topped)
 {
-	Completejob *job;
-	Completion *c;
-
-	job = arg;
-	threadsetname("namecomplete %s", job->dir);
-
-	c = complete(job->dir, job->str);
-	if(c != nil && sendp(job->win->complete, c) <= 0)
-		freecompletion(c);
-
-	wclose(job->win);
-
-	free(job->dir);
-	free(job->str);
-	free(job);
+	if(w->holding)
+		if(topped)
+			w->cols[TEXT] = holdcol;
+		else
+			w->cols[TEXT] = lightholdcol;
+	else
+		if(topped)
+			w->cols[TEXT] = cols[TEXT];
+		else
+			w->cols[TEXT] = paletextcol;
 }
 
 void
-namecomplete(Window *w)
+wsetname(Window *w)
 {
-	int nstr, npath;
-	Rune *path, *str;
-	char *dir, *root;
-	Completejob *job;
+	int i, n;
+	char err[ERRMAX];
+	
+	n = snprint(w->name, sizeof(w->name)-2, "window.%d.%d", w->id, w->namecount++);
+	for(i='A'; i<='Z'; i++){
+		if(nameimage(w->i, w->name, 1) > 0)
+			return;
+		errstr(err, sizeof err);
+		if(strcmp(err, "image name in use") != 0)
+			break;
+		w->name[n] = i;
+		w->name[n+1] = 0;
+	}
+	w->name[0] = 0;
+	fprint(2, "rio: setname failed: %s\n", err);
+}
 
-	/* control-f: filename completion; works back to white space or / */
-	if(w->q0<w->nr && w->r[w->q0]>' ')	/* must be at end of word */
-		return;
-	nstr = windfilewidth(w, w->q0, TRUE);
-	str = w->r+(w->q0-nstr);
-	npath = windfilewidth(w, w->q0-nstr, FALSE);
-	path = w->r+(w->q0-nstr-npath);
+static void
+wresize(Window *w, Image *i)
+{
+	Rectangle r;
 
-	/* is path rooted? if not, we need to make it relative to window path */
-	if(npath>0 && path[0]=='/')
-		dir = runetobyte(path, npath, &npath);
-	else {
-		if(strcmp(w->dir, "") == 0)
-			root = ".";
-		else
-			root = w->dir;
-		dir = smprint("%s/%.*S", root, npath, path);
+	w->i = i;
+	w->mc.image = i;
+	r = insetrect(i->r, Selborder+1);
+	w->scrollr = r;
+	w->scrollr.max.x = r.min.x+Scrollwid;
+	w->lastsr = ZR;
+	r.min.x += Scrollwid+Scrollgap;
+	frclear(w, FALSE);
+	frinit(w, r, w->font, w->i, cols);
+	wsetcols(w, w == input);
+	w->maxtab = maxtab*stringwidth(w->font, "0");
+	if(!w->mouseopen || !w->winnameread){
+		r = insetrect(w->i->r, Selborder);
+		draw(w->i, r, cols[BACK], nil, w->entire.min);
+		wfill(w);
+		wsetselect(w, w->q0, w->q1);
+		wscrdraw(w);
 	}
-	if(dir == nil)
-		return;
+	if(w == input)
+		wborder(w, Selborder);
+	else
+		wborder(w, Unselborder);
+	flushimage(display, 1);
+	wsetname(w);
+	w->topped = ++topped;
+	w->resized = TRUE;
+	w->winnameread = FALSE;
+	w->mouse.counter++;
+	w->wctlready = 1;
+}
 
-	/* run in background, winctl will collect the result on w->complete chan */
-	job = emalloc(sizeof *job);
-	job->str = runetobyte(str, nstr, &nstr);
-	job->dir = cleanname(dir);
-	job->win = w;
-	incref(w);
-	proccreate(completeproc, job, STACK);
+static void
+wrepaint(Window *w)
+{
+	wsetcols(w, w == input);
+	if(!w->mouseopen || !w->winnameread)
+		frredraw(w);
+	if(w == input)
+		wborder(w, Selborder);
+	else
+		wborder(w, Unselborder);
 }
 
-void
-wkeyctl(Window *w, Rune r)
+static void
+wrefresh(Window *w)
 {
-	uint q0 ,q1;
-	int n, nb;
-	int *notefd;
+	Rectangle r;
 
-	switch(r){
-	case 0:
-	case Kcaps:
-	case Knum:
-	case Kshift:
-	case Kalt:
-	case Kctl:
-	case Kaltgr:
-		return;
-	}
+	if(w == input)
+		wborder(w, Selborder);
+	else
+		wborder(w, Unselborder);
+	r = insetrect(w->i->r, Selborder);
+	draw(w->i, r, w->cols[BACK], nil, w->entire.min);
+	wfill(w);
+	w->ticked = 0;
+	if(w->p0 > 0)
+		frdrawsel(w, frptofchar(w, 0), 0, w->p0, 0);
+	if(w->p1 < w->nchars)
+		frdrawsel(w, frptofchar(w, w->p1), w->p1, w->nchars, 0);
+	frdrawsel(w, frptofchar(w, w->p0), w->p0, w->p1, 1);
+	w->lastsr = ZR;
+	wscrdraw(w);
+}
 
-	if(w->i==nil)
-		return;
-	/* navigation keys work only when mouse and kbd is not open */
-	if(!w->mouseopen)
-		switch(r){
-		case Kdown:
-			n = shiftdown ? 1 : w->maxlines/3;
-			goto case_Down;
-		case Kscrollonedown:
-			n = mousescrollsize(w->maxlines);
-			if(n <= 0)
-				n = 1;
-			goto case_Down;
-		case Kpgdown:
-			n = 2*w->maxlines/3;
-		case_Down:
-			q0 = w->org+frcharofpt(w, Pt(w->Frame.r.min.x, w->Frame.r.min.y+n*w->font->height));
-			wsetorigin(w, q0, TRUE);
-			return;
-		case Kup:
-			n = shiftdown ? 1 : w->maxlines/3;
-			goto case_Up;
-		case Kscrolloneup:
-			n = mousescrollsize(w->maxlines);
-			if(n <= 0)
-				n = 1;
-			goto case_Up;
-		case Kpgup:
-			n = 2*w->maxlines/3;
-		case_Up:
-			q0 = wbacknl(w, w->org, n);
-			wsetorigin(w, q0, TRUE);
-			return;
-		case Kleft:
-			if(w->q0 > 0){
-				q0 = w->q0-1;
-				wsetselect(w, q0, q0);
-				wshow(w, q0);
-			}
-			return;
-		case Kright:
-			if(w->q1 < w->nr){
-				q1 = w->q1+1;
-				wsetselect(w, q1, q1);
-				wshow(w, q1);
-			}
-			return;
-		case Khome:
-			wshow(w, 0);
-			return;
-		case Kend:
-			wshow(w, w->nr);
-			return;
-		case Kscroll:
-			w->scrolling ^= 1;
-			wshow(w, w->nr);
-			return;
-		case Ksoh:	/* ^A: beginning of line */
-			if(w->q0==0 || w->q0==w->qh || w->r[w->q0-1]=='\n')
-				return;
-			nb = wbswidth(w, 0x15 /* ^U */);
-			wsetselect(w, w->q0-nb, w->q0-nb);
-			wshow(w, w->q0);
-			return;
-		case Kenq:	/* ^E: end of line */
-			q0 = w->q0;
-			while(q0 < w->nr && w->r[q0]!='\n')
-				q0++;
-			wsetselect(w, q0, q0);
-			wshow(w, w->q0);
-			return;
-		case Kstx:	/* ^B: output point */
-			wsetselect(w, w->qh, w->qh);
-			wshow(w, w->q0);
-			return;
-		}
-	if(w->rawing && (w->q0==w->nr || w->mouseopen)){
-		waddraw(w, &r, 1);
-		return;
-	}
-	if(r==Kesc || (w->holding && r==Kdel)){	/* toggle hold */
-		if(w->holding)
-			--w->holding;
-		else
-			w->holding++;
-		wsetcursor(w, FALSE);
-		wrepaint(w);
-		if(r == Kesc)
-			return;
-	}
-	if(r != Kdel){
-		wsnarf(w);
-		wcut(w);
-	}
-	switch(r){
-	case Kdel:	/* send interrupt */
-		w->qh = w->nr;
-		wshow(w, w->qh);
-		if(w->notefd < 0)
-			return;
-		notefd = emalloc(sizeof(int));
-		*notefd = dup(w->notefd, -1);
-		proccreate(interruptproc, notefd, 4096);
-		return;
-	case Kack:	/* ^F: file name completion */
-	case Kins:	/* Insert: file name completion */
-		namecomplete(w);
-		return;
-	case Kbs:	/* ^H: erase character */
-	case Knack:	/* ^U: erase line */
-	case Ketb:	/* ^W: erase word */
-		if(w->q0==0 || w->q0==w->qh)
-			return;
-		nb = wbswidth(w, r);
-		q1 = w->q0;
-		q0 = q1-nb;
-		if(q0 < w->org){
-			q0 = w->org;
-			nb = q1-q0;
-		}
-		if(nb > 0){
-			wdelete(w, q0, q0+nb);
-			wsetselect(w, q0, q0);
-		}
-		return;
+/*
+ * Need to do this in a separate proc because if process we're interrupting
+ * is dying and trying to print tombstone, kernel is blocked holding p->debug lock.
+ */
+static void
+interruptproc(void *v)
+{
+	int *notefd;
+
+	notefd = v;
+	write(*notefd, "interrupt", 9);
+	close(*notefd);
+	free(notefd);
+}
+
+typedef struct Completejob Completejob;
+struct Completejob
+{
+	char	*dir;
+	char	*str;
+	Window	*win;
+};
+
+static void
+completeproc(void *arg)
+{
+	Completejob *job;
+	Completion *c;
+
+	job = arg;
+	threadsetname("namecomplete %s", job->dir);
+
+	c = complete(job->dir, job->str);
+	if(c != nil && sendp(job->win->complete, c) <= 0)
+		freecompletion(c);
+
+	wclose(job->win);
+
+	free(job->dir);
+	free(job->str);
+	free(job);
+}
+
+static int
+windfilewidth(Window *w, uint q0, int oneelement)
+{
+	uint q;
+	Rune r;
+
+	q = q0;
+	while(q > 0){
+		r = w->r[q-1];
+		if(r<=' ' || r=='=' || r=='^' || r=='(' || r=='{')
+			break;
+		if(oneelement && r=='/')
+			break;
+		--q;
 	}
-	/* otherwise ordinary character; just insert */
-	q0 = w->q0;
-	q0 = winsert(w, &r, 1, q0);
-	wshow(w, q0+1);
+	return q0-q;
 }
 
-void
-wsetcols(Window *w, int topped)
+static void
+namecomplete(Window *w)
 {
-	if(w->holding)
-		if(topped)
-			w->cols[TEXT] = holdcol;
-		else
-			w->cols[TEXT] = lightholdcol;
-	else
-		if(topped)
-			w->cols[TEXT] = cols[TEXT];
+	int nstr, npath;
+	Rune *path, *str;
+	char *dir, *root;
+	Completejob *job;
+
+	/* control-f: filename completion; works back to white space or / */
+	if(w->q0<w->nr && w->r[w->q0]>' ')	/* must be at end of word */
+		return;
+	nstr = windfilewidth(w, w->q0, TRUE);
+	str = w->r+(w->q0-nstr);
+	npath = windfilewidth(w, w->q0-nstr, FALSE);
+	path = w->r+(w->q0-nstr-npath);
+
+	/* is path rooted? if not, we need to make it relative to window path */
+	if(npath>0 && path[0]=='/')
+		dir = runetobyte(path, npath, &npath);
+	else {
+		if(strcmp(w->dir, "") == 0)
+			root = ".";
 		else
-			w->cols[TEXT] = paletextcol;
+			root = w->dir;
+		dir = smprint("%s/%.*S", root, npath, path);
+	}
+	if(dir == nil)
+		return;
+
+	/* run in background, winctl will collect the result on w->complete chan */
+	job = emalloc(sizeof *job);
+	job->str = runetobyte(str, nstr, &nstr);
+	job->dir = cleanname(dir);
+	job->win = w;
+	incref(w);
+	proccreate(completeproc, job, STACK);
 }
 
-void
-wrepaint(Window *w)
+static void
+showcandidates(Window *w, Completion *c)
 {
-	wsetcols(w, w == input);
-	if(!w->mouseopen || !w->winnameread)
-		frredraw(w);
-	if(w == input)
-		wborder(w, Selborder);
+	int i;
+	Fmt f;
+	Rune *rp;
+	uint nr, qline;
+	char *s;
+
+	runefmtstrinit(&f);
+	if (c->nmatch == 0)
+		s = "[no matches in ";
 	else
-		wborder(w, Unselborder);
+		s = "[";
+	if(c->nfile > 32)
+		fmtprint(&f, "%s%d files]\n", s, c->nfile);
+	else{
+		fmtprint(&f, "%s", s);
+		for(i=0; i<c->nfile; i++){
+			if(i > 0)
+				fmtprint(&f, " ");
+			fmtprint(&f, "%s", c->filename[i]);
+		}
+		fmtprint(&f, "]\n");
+	}
+	rp = runefmtstrflush(&f);
+	nr = runestrlen(rp);
+
+	/* place text at beginning of line before cursor and host point */
+	qline = min(w->qh, w->q0);
+	while(qline>0 && w->r[qline-1] != '\n')
+		qline--;
+
+	if(qline == w->qh){
+		/* advance host point to avoid readback */
+		w->qh = winsert(w, rp, nr, qline)+nr;
+	} else {
+		winsert(w, rp, nr, qline);
+	}
+	free(rp);
 }
 
-int
+static int
 wbswidth(Window *w, Rune c)
 {
 	uint q, eq, stop;
@@ -798,1027 +592,1227 @@
 }
 
 void
-wsnarf(Window *w)
-{
-	if(w->q1 == w->q0)
-		return;
-	nsnarf = w->q1-w->q0;
-	snarf = runerealloc(snarf, nsnarf);
-	snarfversion++;	/* maybe modified by parent */
-	runemove(snarf, w->r+w->q0, nsnarf);
-	putsnarf();
-}
-
-void
-wcut(Window *w)
-{
-	if(w->q1 == w->q0)
-		return;
-	wdelete(w, w->q0, w->q1);
-	wsetselect(w, w->q0, w->q0);
-}
-
-void
-wpaste(Window *w)
-{
-	uint q0;
-
-	if(nsnarf == 0)
-		return;
-	wcut(w);
-	q0 = w->q0;
-	if(w->rawing && q0==w->nr){
-		waddraw(w, snarf, nsnarf);
-		wsetselect(w, q0, q0);
-	}else{
-		q0 = winsert(w, snarf, nsnarf, w->q0);
-		wsetselect(w, q0, q0+nsnarf);
-	}
-}
-
-void
-wplumb(Window *w)
-{
-	Plumbmsg *m;
-	static int fd = -2;
-	char buf[32];
-	uint p0, p1;
-	Cursor *c;
-
-	if(fd == -2)
-		fd = plumbopen("send", OWRITE|OCEXEC);
-	if(fd < 0)
-		return;
-	m = emalloc(sizeof(Plumbmsg));
-	m->src = estrdup("rio");
-	m->dst = nil;
-	m->wdir = estrdup(w->dir);
-	m->type = estrdup("text");
-	p0 = w->q0;
-	p1 = w->q1;
-	if(w->q1 > w->q0)
-		m->attr = nil;
-	else{
-		while(p0>0 && w->r[p0-1]!=' ' && w->r[p0-1]!='\t' && w->r[p0-1]!='\n')
-			p0--;
-		while(p1<w->nr && w->r[p1]!=' ' && w->r[p1]!='\t' && w->r[p1]!='\n')
-			p1++;
-		snprint(buf, sizeof(buf), "click=%d", w->q0-p0);
-		m->attr = plumbunpackattr(buf);
-	}
-	if(p1-p0 > messagesize-1024){
-		plumbfree(m);
-		return;	/* too large for 9P */
-	}
-	m->data = runetobyte(w->r+p0, p1-p0, &m->ndata);
-	if(plumbsend(fd, m) < 0){
-		c = lastcursor;
-		riosetcursor(&query);
-		sleep(300);
-		riosetcursor(c);
-	}
-	plumbfree(m);
-}
-
-void
-wlook(Window *w)
+wsetorigin(Window *w, uint org, int exact)
 {
-	int i, n, e;
-
-	i = w->q1;
-	n = i - w->q0;
-	e = w->nr - n;
-	if(n <= 0 || e < n)
-		return;
-
-	if(i > e)
-		i = 0;
+	int i, a, fixup;
+	Rune *r;
+	uint n;
 
-	while(runestrncmp(w->r+w->q0, w->r+i, n) != 0){
-		if(i < e)
-			i++;
-		else
-			i = 0;
+	if(org>0 && !exact){
+		/* org is an estimate of the char posn; find a newline */
+		/* don't try harder than 256 chars */
+		for(i=0; i<256 && org<w->nr; i++){
+			if(w->r[org] == '\n'){
+				org++;
+				break;
+			}
+			org++;
+		}
 	}
-
-	wsetselect(w, i, i+n);
-	wshow(w, i);
+	a = org-w->org;
+	fixup = 0;
+	if(a>=0 && a<w->nchars){
+		frdelete(w, 0, a);
+		fixup = 1;	/* frdelete can leave end of last line in wrong selection mode; it doesn't know what follows */
+	}else if(a<0 && -a<w->nchars){
+		n = w->org - org;
+		r = w->r+org;
+		frinsert(w, r, r+n, 0);
+	}else
+		frdelete(w, 0, w->nchars);
+	w->org = org;
+	wfill(w);
+	wscrdraw(w);
+	wsetselect(w, w->q0, w->q1);
+	if(fixup && w->p1 > w->p0)
+		frdrawsel(w, frptofchar(w, w->p1-1), w->p1-1, w->p1, 1);
 }
 
-void
-wmousectl(Window *w)
+uint
+wbacknl(Window *w, uint p, uint n)
 {
-	int but;
+	int i, j;
 
-	for(but=1;; but++){
-		if(but > 5)
-			return;
-		if(w->mc.buttons == 1<<(but-1))
+	/* look for start of this line if n==0 */
+	if(n==0 && p>0 && w->r[p-1]!='\n')
+		n = 1;
+	i = n;
+	while(i-->0 && p>0){
+		--p;	/* it's at a newline now; back over it */
+		if(p == 0)
 			break;
+		/* at 128 chars, call it a line anyway */
+		for(j=128; --j>0 && p>0; p--)
+			if(w->r[p-1]=='\n')
+				break;
 	}
+	return p;
+}
 
-	incref(w);		/* hold up window while we track */
-	if(w->i != nil){
-		if(shiftdown && but > 3)
-			wkeyctl(w, but == 4 ? Kscrolloneup : Kscrollonedown);
-		else if(ptinrect(w->mc.xy, w->scrollr) || (but > 3))
-			wscroll(w, but);
-		else if(but == 1)
-			wselect(w);
-	}
-	wclose(w);
+char*
+wcontents(Window *w, int *ip)
+{
+	return runetobyte(w->r, w->nr, ip);
 }
 
 void
-wdelete(Window *w, uint q0, uint q1)
+wshow(Window *w, uint q0)
 {
-	uint n, p0, p1;
+	int qe;
+	int nl;
+	uint q;
 
-	n = q1-q0;
-	if(n == 0)
-		return;
-	runemove(w->r+q0, w->r+q1, w->nr-q1);
-	w->nr -= n;
-	if(q0 < w->q0)
-		w->q0 -= min(n, w->q0-q0);
-	if(q0 < w->q1)
-		w->q1 -= min(n, w->q1-q0);
-	if(q1 < w->qh)
-		w->qh -= n;
-	else if(q0 < w->qh)
-		w->qh = q0;
-	if(q1 <= w->org)
-		w->org -= n;
-	else if(q0 < w->org+w->nchars){
-		p1 = q1 - w->org;
-		if(p1 > w->nchars)
-			p1 = w->nchars;
-		if(q0 < w->org){
-			w->org = q0;
-			p0 = 0;
-		}else
-			p0 = q0 - w->org;
-		frdelete(w, p0, p1);
-		wfill(w);
+	qe = w->org+w->nchars;
+	if(w->org<=q0 && (q0<qe || (q0==qe && qe==w->nr)))
+		wscrdraw(w);
+	else{
+		nl = 4*w->maxlines/5;
+		q = wbacknl(w, q0, nl);
+		/* avoid going backwards if trying to go forwards - long lines! */
+		if(!(q0>w->org && q<w->org))
+			wsetorigin(w, q, TRUE);
+		while(q0 > w->org+w->nchars)
+			wsetorigin(w, w->org+1, FALSE);
 	}
 }
 
-
-static Window	*clickwin;
-static uint	clickmsec;
-static Point	clickpt;
-static uint	clickcount;
-static Window	*selectwin;
-static uint	selectq;
-
-/*
- * called from frame library
- */
 void
-framescroll(Frame *f, int dl)
+wsnarf(Window *w)
 {
-	if(f != &selectwin->Frame)
-		error("frameselect not right frame");
-	wframescroll(selectwin, dl);
+	if(w->q1 == w->q0)
+		return;
+	nsnarf = w->q1-w->q0;
+	snarf = runerealloc(snarf, nsnarf);
+	snarfversion++;	/* maybe modified by parent */
+	runemove(snarf, w->r+w->q0, nsnarf);
+	putsnarf();
 }
 
 void
-wframescroll(Window *w, int dl)
+wsend(Window *w)
 {
-	uint q0;
-
-	if(dl == 0){
-		wscrsleep(w, 100);
+	getsnarf();
+	wsnarf(w);
+	if(nsnarf == 0)
 		return;
-	}
-	if(dl < 0){
-		q0 = wbacknl(w, w->org, -dl);
-		if(selectq > w->org+w->p0)
-			wsetselect(w, w->org+w->p0, selectq);
-		else
-			wsetselect(w, selectq, w->org+w->p0);
+	if(w->rawing){
+		waddraw(w, snarf, nsnarf);
+		if(snarf[nsnarf-1]!='\n' && snarf[nsnarf-1]!='\004')
+			waddraw(w, L"\n", 1);
 	}else{
-		if(w->org+w->nchars == w->nr)
-			return;
-		q0 = w->org+frcharofpt(w, Pt(w->Frame.r.min.x, w->Frame.r.min.y+dl*w->font->height));
-		if(selectq >= w->org+w->p1)
-			wsetselect(w, w->org+w->p1, selectq);
-		else
-			wsetselect(w, selectq, w->org+w->p1);
+		winsert(w, snarf, nsnarf, w->nr);
+		if(snarf[nsnarf-1]!='\n' && snarf[nsnarf-1]!='\004')
+			winsert(w, L"\n", 1, w->nr);
 	}
-	wsetorigin(w, q0, TRUE);
+	wsetselect(w, w->nr, w->nr);
+	wshow(w, w->nr);
 }
 
-void
-wselect(Window *w)
+static void
+wdelete(Window *w, uint q0, uint q1)
 {
-	uint q0, q1;
-	int b, x, y, dx, dy, mode, first;
+	uint n, p0, p1;
 
-	first = 1;
-	selectwin = w;
-	/*
-	 * Double-click immediately if it might make sense.
-	 */
-	b = w->mc.buttons;
-	q0 = w->q0;
-	q1 = w->q1;
-	dx = abs(clickpt.x - w->mc.xy.x);
-	dy = abs(clickpt.y - w->mc.xy.y);
-	clickpt = w->mc.xy;
-	selectq = w->org+frcharofpt(w, w->mc.xy);
-	clickcount++;
-	if(w->mc.msec-clickmsec >= 500 || clickwin != w || clickcount > 3 || dx > 3 || dy > 3)
-		clickcount = 0;
-	if(clickwin == w && clickcount >= 1 && w->mc.msec-clickmsec < 500){
-		mode = (clickcount > 2) ? 2 : clickcount;
-		wstretchsel(w, selectq, &q0, &q1, mode);
-		wsetselect(w, q0, q1);
-		x = w->mc.xy.x;
-		y = w->mc.xy.y;
-		/* stay here until something interesting happens */
-		while(1){
-			readmouse(&w->mc);
-			dx = abs(w->mc.xy.x-x);
-			dy = abs(w->mc.xy.y-y);
-			if(w->mc.buttons != b || dx >= 3 && dy >= 3)
-				break;
-			clickcount++;
-			clickmsec = w->mc.msec;
-		}
-		w->mc.xy.x = x;	/* in case we're calling frselect */
-		w->mc.xy.y = y;
-		q0 = w->q0;	/* may have changed */
-		q1 = w->q1;
-		selectq = w->org+frcharofpt(w, w->mc.xy);
-	}
-	if(w->mc.buttons == b && clickcount == 0){
-		w->scroll = framescroll;
-		frselect(w, &w->mc);
-		/* horrible botch: while asleep, may have lost selection altogether */
-		if(selectq > w->nr)
-			selectq = w->org + w->p0;
-		w->Frame.scroll = nil;
-		if(selectq < w->org)
-			q0 = selectq;
-		else
-			q0 = w->org + w->p0;
-		if(selectq > w->org+w->nchars)
-			q1 = selectq;
-		else
-			q1 = w->org+w->p1;
-	}
-	if(q0 == q1){
-		mode = (clickcount > 2) ? 2 : clickcount;
-		if(q0==w->q0 && clickwin==w && w->mc.msec-clickmsec<500)
-			wstretchsel(w, selectq, &q0, &q1, mode);
-		else
-			clickwin = w;
-		clickmsec = w->mc.msec;
-	}
-	wsetselect(w, q0, q1);
-	while(w->mc.buttons){
-		w->mc.msec = 0;
-		b = w->mc.buttons;
-		if(b & 6){
-			if(b & 2){
-				wsnarf(w);
-				wcut(w);
-			}else{
-				if(first){
-					first = 0;
-					getsnarf();
-				}
-				wpaste(w);
-			}
-		}
-		wscrdraw(w);
-		while(w->mc.buttons == b)
-			readmouse(&w->mc);
-		if(w->mc.msec-clickmsec >= 500)
-			clickwin = nil;
+	n = q1-q0;
+	if(n == 0)
+		return;
+	runemove(w->r+q0, w->r+q1, w->nr-q1);
+	w->nr -= n;
+	if(q0 < w->q0)
+		w->q0 -= min(n, w->q0-q0);
+	if(q0 < w->q1)
+		w->q1 -= min(n, w->q1-q0);
+	if(q1 < w->qh)
+		w->qh -= n;
+	else if(q0 < w->qh)
+		w->qh = q0;
+	if(q1 <= w->org)
+		w->org -= n;
+	else if(q0 < w->org+w->nchars){
+		p1 = q1 - w->org;
+		if(p1 > w->nchars)
+			p1 = w->nchars;
+		if(q0 < w->org){
+			w->org = q0;
+			p0 = 0;
+		}else
+			p0 = q0 - w->org;
+		frdelete(w, p0, p1);
+		wfill(w);
 	}
 }
 
 void
-wsendctlmesg(Window *w, int type, Rectangle r, void *p)
+wcut(Window *w)
 {
-	Wctlmesg wcm;
+	if(w->q1 == w->q0)
+		return;
+	wdelete(w, w->q0, w->q1);
+	wsetselect(w, w->q0, w->q0);
+}
 
-	wcm.type = type;
-	wcm.r = r;
-	wcm.p = p;
-	send(w->cctl, &wcm);
+void
+wpaste(Window *w)
+{
+	uint q0;
+
+	if(nsnarf == 0)
+		return;
+	wcut(w);
+	q0 = w->q0;
+	if(w->rawing && q0==w->nr){
+		waddraw(w, snarf, nsnarf);
+		wsetselect(w, q0, q0);
+	}else{
+		q0 = winsert(w, snarf, nsnarf, w->q0);
+		wsetselect(w, q0, q0+nsnarf);
+	}
 }
 
-int
-wctlmesg(Window *w, int m, Rectangle r, void *p)
+void
+wlook(Window *w)
 {
-	Image *i = p;
+	int i, n, e;
 
-	switch(m){
-	default:
-		error("unknown control message");
-		break;
-	case Wakeup:
-		if(p!=nil)
-			sendp((Channel*)p, w);
-		break;
-	case Reshaped:
-		if(w->deleted){
-			freeimage(i);
-			break;
-		}
-		w->screenr = r;
-		wresize(w, i);
-		if(Dx(r)<=0){	/* window got hidden, if we had the input, drop it */
-			if(w==input)
-				input = nil;
-			break;
-		}
-		/* fall through to get input if needed */
-	case Topped:
-		if(w->deleted || w==input)
-			break;
-		if(input!=nil){
-			Window *oi;
-			Channel *c;
-	
-			oi = input;
-			incref(oi);
+	i = w->q1;
+	n = i - w->q0;
+	e = w->nr - n;
+	if(n <= 0 || e < n)
+		return;
 
-			/*
-			 * have to wait until old input responds before
-			 * changing input to us because the window might
-			 * currently be mouse tracking and it is not
-			 * prepared for getting its input revoked.
-			 */
-			c = chancreate(sizeof(void*), 0);
-			wsendctlmesg(oi, Wakeup, ZR, c);
-			recv(c, nil);
-			chanfree(c);
-
-			/*
-			 * if we are still top window and nobody else has taken
-			 * input from original window, take the input.
-			 */
-			if(!w->deleted && w->topped==topped && oi==input){
-				input = w;
+	if(i > e)
+		i = 0;
 
-				oi->wctlready = 1;
-				wsendctlmesg(oi, Repaint, ZR, nil);
-			}
-			wclose(oi);
-		} else {
-			input = w;
-			wsetcursor(w, FALSE);
-		}
-		w->wctlready = 1;
-		if(m!=Topped && w==input)
-			break;
-		/* fall thrugh for redraw after input change */
-	case Repaint:
-		if(w->i==nil || Dx(w->screenr)<=0)
-			break;
-		wrepaint(w);
-		flushimage(display, 1);
-		break;
-	case Refresh:
-		if(w->i==nil || Dx(w->screenr)<=0)
-			break;
-		wrefresh(w);
-		flushimage(display, 1);
-		break;
-	case Movemouse:
-		if(w->i==nil || Dx(w->screenr)<=0 || !ptinrect(r.min, w->i->r))
-			break;
-		wmovemouse(w, r.min);
-	case Rawon:
-		break;
-	case Rawoff:
-		while(w->nraw > 0){
-			wkeyctl(w, w->raw[0]);
-			--w->nraw;
-			runemove(w->raw, w->raw+1, w->nraw);
-		}
-		break;
-	case Holdon:
-	case Holdoff:
-		if(w->i==nil)
-			break;
-		wsetcursor(w, FALSE);
-		wrepaint(w);
-		flushimage(display, 1);
-		break;
-	case Truncate:
-		wdelete(w, 0, w->nr);
-		break;
-	case Deleted:
-		wclunk(w);
-		if(w->notefd >= 0)
-			write(w->notefd, "hangup", 6);
-		wclosewin(w);
-		flushimage(display, 1);
-		break;
-	case Exited:
-		wclosewin(w);
-		frclear(w, TRUE);
-		flushimage(display, 1);
-		if(w->notefd >= 0)
-			close(w->notefd);
-		chanfree(w->mc.c);
-		chanfree(w->ck);
-		chanfree(w->cctl);
-		chanfree(w->conswrite);
-		chanfree(w->consread);
-		chanfree(w->mouseread);
-		chanfree(w->wctlread);
-		chanfree(w->kbdread);
-		chanfree(w->complete);
-		chanfree(w->gone);
-		free(w->raw);
-		free(w->r);
-		free(w->dir);
-		free(w->label);
-		free(w);
-		break;
+	while(runestrncmp(w->r+w->q0, w->r+i, n) != 0){
+		if(i < e)
+			i++;
+		else
+			i = 0;
 	}
-	return m;
+
+	wsetselect(w, i, i+n);
+	wshow(w, i);
 }
 
-/*
- * Convert back to physical coordinates
- */
 void
-wmovemouse(Window *w, Point p)
+wplumb(Window *w)
 {
-	if(w != input || menuing || sweeping)
+	Plumbmsg *m;
+	static int fd = -2;
+	char buf[32];
+	uint p0, p1;
+	Cursor *c;
+
+	if(fd == -2)
+		fd = plumbopen("send", OWRITE|OCEXEC);
+	if(fd < 0)
 		return;
-	p.x += w->screenr.min.x-w->i->r.min.x;
-	p.y += w->screenr.min.y-w->i->r.min.y;
-	moveto(mousectl, p);
+	m = emalloc(sizeof(Plumbmsg));
+	m->src = estrdup("rio");
+	m->dst = nil;
+	m->wdir = estrdup(w->dir);
+	m->type = estrdup("text");
+	p0 = w->q0;
+	p1 = w->q1;
+	if(w->q1 > w->q0)
+		m->attr = nil;
+	else{
+		while(p0>0 && w->r[p0-1]!=' ' && w->r[p0-1]!='\t' && w->r[p0-1]!='\n')
+			p0--;
+		while(p1<w->nr && w->r[p1]!=' ' && w->r[p1]!='\t' && w->r[p1]!='\n')
+			p1++;
+		snprint(buf, sizeof(buf), "click=%d", w->q0-p0);
+		m->attr = plumbunpackattr(buf);
+	}
+	if(p1-p0 > messagesize-1024){
+		plumbfree(m);
+		return;	/* too large for 9P */
+	}
+	m->data = runetobyte(w->r+p0, p1-p0, &m->ndata);
+	if(plumbsend(fd, m) < 0){
+		c = lastcursor;
+		riosetcursor(&query);
+		sleep(300);
+		riosetcursor(c);
+	}
+	plumbfree(m);
 }
 
-void
-wborder(Window *w, int type)
+static void
+wkeyctl(Window *w, Rune r)
 {
-	Image *col;
+	uint q0 ,q1;
+	int n, nb;
+	int *notefd;
 
-	if(w->i == nil)
+	switch(r){
+	case 0:
+	case Kcaps:
+	case Knum:
+	case Kshift:
+	case Kalt:
+	case Kctl:
+	case Kaltgr:
+		return;
+	}
+
+	if(w->i==nil)
+		return;
+	/* navigation keys work only when mouse and kbd is not open */
+	if(!w->mouseopen)
+		switch(r){
+		case Kdown:
+			n = shiftdown ? 1 : w->maxlines/3;
+			goto case_Down;
+		case Kscrollonedown:
+			n = mousescrollsize(w->maxlines);
+			if(n <= 0)
+				n = 1;
+			goto case_Down;
+		case Kpgdown:
+			n = 2*w->maxlines/3;
+		case_Down:
+			q0 = w->org+frcharofpt(w, Pt(w->Frame.r.min.x, w->Frame.r.min.y+n*w->font->height));
+			wsetorigin(w, q0, TRUE);
+			return;
+		case Kup:
+			n = shiftdown ? 1 : w->maxlines/3;
+			goto case_Up;
+		case Kscrolloneup:
+			n = mousescrollsize(w->maxlines);
+			if(n <= 0)
+				n = 1;
+			goto case_Up;
+		case Kpgup:
+			n = 2*w->maxlines/3;
+		case_Up:
+			q0 = wbacknl(w, w->org, n);
+			wsetorigin(w, q0, TRUE);
+			return;
+		case Kleft:
+			if(w->q0 > 0){
+				q0 = w->q0-1;
+				wsetselect(w, q0, q0);
+				wshow(w, q0);
+			}
+			return;
+		case Kright:
+			if(w->q1 < w->nr){
+				q1 = w->q1+1;
+				wsetselect(w, q1, q1);
+				wshow(w, q1);
+			}
+			return;
+		case Khome:
+			wshow(w, 0);
+			return;
+		case Kend:
+			wshow(w, w->nr);
+			return;
+		case Kscroll:
+			w->scrolling ^= 1;
+			wshow(w, w->nr);
+			return;
+		case Ksoh:	/* ^A: beginning of line */
+			if(w->q0==0 || w->q0==w->qh || w->r[w->q0-1]=='\n')
+				return;
+			nb = wbswidth(w, 0x15 /* ^U */);
+			wsetselect(w, w->q0-nb, w->q0-nb);
+			wshow(w, w->q0);
+			return;
+		case Kenq:	/* ^E: end of line */
+			q0 = w->q0;
+			while(q0 < w->nr && w->r[q0]!='\n')
+				q0++;
+			wsetselect(w, q0, q0);
+			wshow(w, w->q0);
+			return;
+		case Kstx:	/* ^B: output point */
+			wsetselect(w, w->qh, w->qh);
+			wshow(w, w->q0);
+			return;
+		}
+	if(w->rawing && (w->q0==w->nr || w->mouseopen)){
+		waddraw(w, &r, 1);
 		return;
-	if(w->holding){
-		if(type == Selborder)
-			col = holdcol;
-		else
-			col = paleholdcol;
-	}else{
-		if(type == Selborder)
-			col = titlecol;
+	}
+	if(r==Kesc || (w->holding && r==Kdel)){	/* toggle hold */
+		if(w->holding)
+			--w->holding;
 		else
-			col = lighttitlecol;
+			w->holding++;
+		wsetcursor(w, FALSE);
+		wrepaint(w);
+		if(r == Kesc)
+			return;
 	}
-	border(w->i, w->i->r, Selborder, col, ZP);
-}
-
-Window*
-wpointto(Point pt)
-{
-	int i;
-	Window *v, *w;
-
-	w = nil;
-	for(i=0; i<nwindow; i++){
-		v = window[i];
-		if(ptinrect(pt, v->screenr))
-		if(w==nil || v->topped>w->topped)
-			w = v;
+	if(r != Kdel){
+		wsnarf(w);
+		wcut(w);
 	}
-	return w;
-}
-
-void
-wcurrent(Window *w)
-{
-	if(w!=nil && w!=input)
-		wsendctlmesg(w, Topped, ZR, nil);
-}
-
-void
-wsetcursor(Window *w, int force)
-{
-	Cursor *p;
-
-	if(menuing || sweeping || (w!=input && wpointto(mouse->xy)!=w))
+	switch(r){
+	case Kdel:	/* send interrupt */
+		w->qh = w->nr;
+		wshow(w, w->qh);
+		if(w->notefd < 0)
+			return;
+		notefd = emalloc(sizeof(int));
+		*notefd = dup(w->notefd, -1);
+		proccreate(interruptproc, notefd, 4096);
+		return;
+	case Kack:	/* ^F: file name completion */
+	case Kins:	/* Insert: file name completion */
+		namecomplete(w);
+		return;
+	case Kbs:	/* ^H: erase character */
+	case Knack:	/* ^U: erase line */
+	case Ketb:	/* ^W: erase word */
+		if(w->q0==0 || w->q0==w->qh)
+			return;
+		nb = wbswidth(w, r);
+		q1 = w->q0;
+		q0 = q1-nb;
+		if(q0 < w->org){
+			q0 = w->org;
+			nb = q1-q0;
+		}
+		if(nb > 0){
+			wdelete(w, q0, q0+nb);
+			wsetselect(w, q0, q0);
+		}
 		return;
-	if(w==nil)
-		p = nil;
-	else {
-		p = w->cursorp;
-		if(p==nil && w->holding)
-			p = &whitearrow;
 	}
-	if(p && force)	/* force cursor reload */
-		lastcursor = nil;
-	riosetcursor(p);
+	/* otherwise ordinary character; just insert */
+	q0 = w->q0;
+	q0 = winsert(w, &r, 1, q0);
+	wshow(w, q0+1);
 }
 
-void
-riosetcursor(Cursor *p)
-{
-	if(p==lastcursor)
-		return;
-	setcursor(mousectl, p);
-	lastcursor = p;
-}
+static Window	*clickwin;
+static uint	clickmsec;
+static Point	clickpt;
+static uint	clickcount;
+static Window	*selectwin;
+static uint	selectq;
 
-void
-wtopme(Window *w)
+static void
+wframescroll(Window *w, int dl)
 {
-	if(w!=nil && w->i!=nil && w->topped!=topped){
-		w->topped = ++topped;
-		topwindow(w->i);
-		flushimage(display, 1);
-	}
-}
+	uint q0;
 
-void
-wbottomme(Window *w)
-{
-	if(w!=nil && w->i!=nil){
-		w->topped = - ++topped;
-		bottomwindow(w->i);
-		flushimage(display, 1);
+	if(dl == 0){
+		wscrsleep(w, 100);
+		return;
+	}
+	if(dl < 0){
+		q0 = wbacknl(w, w->org, -dl);
+		if(selectq > w->org+w->p0)
+			wsetselect(w, w->org+w->p0, selectq);
+		else
+			wsetselect(w, selectq, w->org+w->p0);
+	}else{
+		if(w->org+w->nchars == w->nr)
+			return;
+		q0 = w->org+frcharofpt(w, Pt(w->Frame.r.min.x, w->Frame.r.min.y+dl*w->font->height));
+		if(selectq >= w->org+w->p1)
+			wsetselect(w, w->org+w->p1, selectq);
+		else
+			wsetselect(w, selectq, w->org+w->p1);
 	}
+	wsetorigin(w, q0, TRUE);
 }
 
-Window*
-wtop(Point pt)
+/*
+ * called from frame library
+ */
+static void
+framescroll(Frame *f, int dl)
 {
-	Window *w;
-
-	w = wpointto(pt);
-	if(w){
-		incref(w);
-		wtopme(w);
-		wcurrent(w);
-		wclose(w);
-	}
-	return w;
+	if(f != &selectwin->Frame)
+		error("frameselect not right frame");
+	wframescroll(selectwin, dl);
 }
 
-Window*
-wlookid(int id)
-{
-	int i;
+static Rune left1[] =  { L'{', L'[', L'(', L'<', L'«', 0 };
+static Rune right1[] = { L'}', L']', L')', L'>', L'»', 0 };
+static Rune left2[] =  { L'\n', 0 };
+static Rune left3[] =  { L'\'', L'"', L'`', 0 };
 
-	for(i=0; i<nwindow; i++)
-		if(window[i]->id == id)
-			return window[i];
-	return nil;
-}
+static Rune *left[] = {
+	left1,
+	left2,
+	left3,
+	nil
+};
+static Rune *right[] = {
+	right1,
+	left2,
+	left3,
+	nil
+};
 
-void
-wclunk(Window *w)
+static int
+wclickmatch(Window *w, int cl, int cr, int dir, uint *q)
 {
-	int i;
+	Rune c;
+	int nest;
 
-	if(w->deleted)
-		return;
-	w->deleted = TRUE;
-	if(w == input){
-		input = nil;
-		riosetcursor(nil);
-	}
-	if(w == wkeyboard)
-		wkeyboard = nil;
-	for(i=0; i<nhidden; i++)
-		if(hidden[i] == w){
-			--nhidden;
-			memmove(hidden+i, hidden+i+1, (nhidden-i)*sizeof(hidden[0]));
-			break;
-		}
-	for(i=0; i<nwindow; i++)
-		if(window[i] == w){
-			--nwindow;
-			memmove(window+i, window+i+1, (nwindow-i)*sizeof(window[0]));
-			break;
+	nest = 1;
+	for(;;){
+		if(dir > 0){
+			if(*q == w->nr)
+				break;
+			c = w->r[*q];
+			(*q)++;
+		}else{
+			if(*q == 0)
+				break;
+			(*q)--;
+			c = w->r[*q];
 		}
+		if(c == cr){
+			if(--nest==0)
+				return 1;
+		}else if(c == cl)
+			nest++;
+	}
+	return cl=='\n' && nest==1;
 }
 
-void
-wclosewin(Window *w)
+static int
+inmode(Rune r, int mode)
 {
-	Image *i = w->i;
-	if(i == nil)
-		return;
-	w->i = nil;
-	/* move it off-screen to hide it, in case client is slow in letting it go */
-	originwindow(i, i->r.min, view->r.max);
-	freeimage(i);
+	return (mode == 1) ? isalnum(r) : r && !isspace(r);
 }
 
-void
-wsetpid(Window *w, int pid, int dolabel)
+static void
+wstretchsel(Window *w, uint pt, uint *q0, uint *q1, int mode)
 {
-	char buf[32];
-	int ofd;
+	int c, i;
+	Rune *r, *l, *p;
+	uint q;
 
-	ofd = w->notefd;
-	if(pid <= 0)
-		w->notefd = -1;
-	else {
-		if(dolabel){
-			snprint(buf, sizeof(buf), "rc %lud", (ulong)pid);
-			free(w->label);
-			w->label = estrdup(buf);
+	*q0 = pt;
+	*q1 = pt;
+	for(i=0; left[i]!=nil; i++){
+		q = *q0;
+		l = left[i];
+		r = right[i];
+		/* try matching character to left, looking right */
+		if(q == 0)
+			c = '\n';
+		else
+			c = w->r[q-1];
+		p = strrune(l, c);
+		if(p != nil){
+			if(wclickmatch(w, c, r[p-l], 1, &q))
+				*q1 = q-(c!='\n');
+			return;
+		}
+		/* try matching character to right, looking left */
+		if(q == w->nr)
+			c = '\n';
+		else
+			c = w->r[q];
+		p = strrune(r, c);
+		if(p != nil){
+			if(wclickmatch(w, c, l[p-r], -1, &q)){
+				*q1 = *q0+(*q0<w->nr && c=='\n');
+				*q0 = q;
+				if(c!='\n' || q!=0 || w->r[0]=='\n')
+					(*q0)++;
+			}
+			return;
 		}
-		snprint(buf, sizeof(buf), "/proc/%lud/notepg", (ulong)pid);
-		w->notefd = open(buf, OWRITE|OCEXEC);
 	}
-	if(ofd >= 0)
-		close(ofd);
+	/* try filling out word to right */
+	while(*q1<w->nr && inmode(w->r[*q1], mode))
+		(*q1)++;
+	/* try filling out word to left */
+	while(*q0>0 && inmode(w->r[*q0-1], mode))
+		(*q0)--;
 }
 
-void
-winshell(void *args)
+static void
+wselect(Window *w)
 {
-	Window *w;
-	Channel *pidc;
-	void **arg;
-	char *cmd, *dir;
-	char **argv;
+	uint q0, q1;
+	int b, x, y, dx, dy, mode, first;
 
-	arg = args;
-	w = arg[0];
-	pidc = arg[1];
-	cmd = arg[2];
-	argv = arg[3];
-	dir = arg[4];
-	rfork(RFNAMEG|RFFDG|RFENVG);
-	if(filsysmount(filsys, w->id) < 0){
-		fprint(2, "mount failed: %r\n");
-		sendul(pidc, 0);
-		threadexits("mount failed");
+	first = 1;
+	selectwin = w;
+	/*
+	 * Double-click immediately if it might make sense.
+	 */
+	b = w->mc.buttons;
+	q0 = w->q0;
+	q1 = w->q1;
+	dx = abs(clickpt.x - w->mc.xy.x);
+	dy = abs(clickpt.y - w->mc.xy.y);
+	clickpt = w->mc.xy;
+	selectq = w->org+frcharofpt(w, w->mc.xy);
+	clickcount++;
+	if(w->mc.msec-clickmsec >= 500 || clickwin != w || clickcount > 3 || dx > 3 || dy > 3)
+		clickcount = 0;
+	if(clickwin == w && clickcount >= 1 && w->mc.msec-clickmsec < 500){
+		mode = (clickcount > 2) ? 2 : clickcount;
+		wstretchsel(w, selectq, &q0, &q1, mode);
+		wsetselect(w, q0, q1);
+		x = w->mc.xy.x;
+		y = w->mc.xy.y;
+		/* stay here until something interesting happens */
+		while(1){
+			readmouse(&w->mc);
+			dx = abs(w->mc.xy.x-x);
+			dy = abs(w->mc.xy.y-y);
+			if(w->mc.buttons != b || dx >= 3 && dy >= 3)
+				break;
+			clickcount++;
+			clickmsec = w->mc.msec;
+		}
+		w->mc.xy.x = x;	/* in case we're calling frselect */
+		w->mc.xy.y = y;
+		q0 = w->q0;	/* may have changed */
+		q1 = w->q1;
+		selectq = w->org+frcharofpt(w, w->mc.xy);
 	}
-	close(0);
-	if(open("/dev/cons", OREAD) < 0){
-		fprint(2, "can't open /dev/cons: %r\n");
-		sendul(pidc, 0);
-		threadexits("/dev/cons");
+	if(w->mc.buttons == b && clickcount == 0){
+		w->scroll = framescroll;
+		frselect(w, &w->mc);
+		/* horrible botch: while asleep, may have lost selection altogether */
+		if(selectq > w->nr)
+			selectq = w->org + w->p0;
+		w->Frame.scroll = nil;
+		if(selectq < w->org)
+			q0 = selectq;
+		else
+			q0 = w->org + w->p0;
+		if(selectq > w->org+w->nchars)
+			q1 = selectq;
+		else
+			q1 = w->org+w->p1;
 	}
-	close(1);
-	if(open("/dev/cons", OWRITE) < 0){
-		fprint(2, "can't open /dev/cons: %r\n");
-		sendul(pidc, 0);
-		threadexits("open");	/* BUG? was terminate() */
+	if(q0 == q1){
+		mode = (clickcount > 2) ? 2 : clickcount;
+		if(q0==w->q0 && clickwin==w && w->mc.msec-clickmsec<500)
+			wstretchsel(w, selectq, &q0, &q1, mode);
+		else
+			clickwin = w;
+		clickmsec = w->mc.msec;
 	}
-	if(wclose(w) == 0){	/* remove extra ref hanging from creation */
-		notify(nil);
-		dup(1, 2);
-		if(dir)
-			chdir(dir);
-		procexec(pidc, cmd, argv);
-		_exits("exec failed");
+	wsetselect(w, q0, q1);
+	while(w->mc.buttons){
+		w->mc.msec = 0;
+		b = w->mc.buttons;
+		if(b & 6){
+			if(b & 2){
+				wsnarf(w);
+				wcut(w);
+			}else{
+				if(first){
+					first = 0;
+					getsnarf();
+				}
+				wpaste(w);
+			}
+		}
+		wscrdraw(w);
+		while(w->mc.buttons == b)
+			readmouse(&w->mc);
+		if(w->mc.msec-clickmsec >= 500)
+			clickwin = nil;
 	}
 }
 
-static Rune left1[] =  { L'{', L'[', L'(', L'<', L'«', 0 };
-static Rune right1[] = { L'}', L']', L')', L'>', L'»', 0 };
-static Rune left2[] =  { L'\n', 0 };
-static Rune left3[] =  { L'\'', L'"', L'`', 0 };
+/*
+ * Convert back to physical coordinates
+ */
+static void
+wmovemouse(Window *w, Point p)
+{
+	if(w != input || menuing || sweeping)
+		return;
+	p.x += w->screenr.min.x-w->i->r.min.x;
+	p.y += w->screenr.min.y-w->i->r.min.y;
+	moveto(mousectl, p);
+}
 
-Rune *left[] = {
-	left1,
-	left2,
-	left3,
-	nil
-};
-Rune *right[] = {
-	right1,
-	left2,
-	left3,
-	nil
-};
 
-int
-inmode(Rune r, int mode)
+Window*
+wmk(Image *i, Mousectl *mc, Channel *ck, Channel *cctl, int scrolling)
+{
+	static int id;
+
+	Window *w;
+	Rectangle r;
+
+	w = emalloc(sizeof(Window));
+	w->screenr = i->r;
+	r = insetrect(i->r, Selborder+1);
+	w->i = i;
+	w->mc = *mc;
+	w->ck = ck;
+	w->cctl = cctl;
+	w->cursorp = nil;
+	w->conswrite = chancreate(sizeof(Conswritemesg), 0);
+	w->consread =  chancreate(sizeof(Consreadmesg), 0);
+	w->kbdread =  chancreate(sizeof(Consreadmesg), 0);
+	w->mouseread =  chancreate(sizeof(Mousereadmesg), 0);
+	w->wctlread =  chancreate(sizeof(Consreadmesg), 0);
+	w->complete = chancreate(sizeof(Completion*), 0);
+	w->gone = chancreate(sizeof(char*), 0);
+	w->scrollr = r;
+	w->scrollr.max.x = r.min.x+Scrollwid;
+	w->lastsr = ZR;
+	r.min.x += Scrollwid+Scrollgap;
+	frinit(w, r, font, i, cols);
+	w->maxtab = maxtab*stringwidth(font, "0");
+	w->topped = ++topped;
+	w->id = ++id;
+	w->notefd = -1;
+	w->scrolling = scrolling;
+	w->dir = estrdup(startdir);
+	w->label = estrdup("<unnamed>");
+	r = insetrect(w->i->r, Selborder);
+	draw(w->i, r, cols[BACK], nil, w->entire.min);
+	wborder(w, Selborder);
+	wscrdraw(w);
+	incref(w);	/* ref will be removed after mounting; avoids delete before ready to be deleted */
+	return w;
+}
+
+static void
+wclosewin(Window *w)
 {
-	return (mode == 1) ? isalnum(r) : r && !isspace(r);
+	Image *i = w->i;
+	if(i == nil)
+		return;
+	w->i = nil;
+	/* move it off-screen to hide it, in case client is slow in letting it go */
+	originwindow(i, i->r.min, view->r.max);
+	freeimage(i);
 }
 
-void
-wstretchsel(Window *w, uint pt, uint *q0, uint *q1, int mode)
+static void
+wclunk(Window *w)
 {
-	int c, i;
-	Rune *r, *l, *p;
-	uint q;
+	int i;
 
-	*q0 = pt;
-	*q1 = pt;
-	for(i=0; left[i]!=nil; i++){
-		q = *q0;
-		l = left[i];
-		r = right[i];
-		/* try matching character to left, looking right */
-		if(q == 0)
-			c = '\n';
-		else
-			c = w->r[q-1];
-		p = strrune(l, c);
-		if(p != nil){
-			if(wclickmatch(w, c, r[p-l], 1, &q))
-				*q1 = q-(c!='\n');
-			return;
+	if(w->deleted)
+		return;
+	w->deleted = TRUE;
+	if(w == input){
+		input = nil;
+		riosetcursor(nil);
+	}
+	if(w == wkeyboard)
+		wkeyboard = nil;
+	for(i=0; i<nhidden; i++)
+		if(hidden[i] == w){
+			--nhidden;
+			memmove(hidden+i, hidden+i+1, (nhidden-i)*sizeof(hidden[0]));
+			break;
 		}
-		/* try matching character to right, looking left */
-		if(q == w->nr)
-			c = '\n';
-		else
-			c = w->r[q];
-		p = strrune(r, c);
-		if(p != nil){
-			if(wclickmatch(w, c, l[p-r], -1, &q)){
-				*q1 = *q0+(*q0<w->nr && c=='\n');
-				*q0 = q;
-				if(c!='\n' || q!=0 || w->r[0]=='\n')
-					(*q0)++;
-			}
-			return;
+	for(i=0; i<nwindow; i++)
+		if(window[i] == w){
+			--nwindow;
+			memmove(window+i, window+i+1, (nwindow-i)*sizeof(window[0]));
+			break;
 		}
-	}
-	/* try filling out word to right */
-	while(*q1<w->nr && inmode(w->r[*q1], mode))
-		(*q1)++;
-	/* try filling out word to left */
-	while(*q0>0 && inmode(w->r[*q0-1], mode))
-		(*q0)--;
 }
 
 int
-wclickmatch(Window *w, int cl, int cr, int dir, uint *q)
+wclose(Window *w)
 {
-	Rune c;
-	int nest;
+	int i;
 
-	nest = 1;
-	for(;;){
-		if(dir > 0){
-			if(*q == w->nr)
-				break;
-			c = w->r[*q];
-			(*q)++;
-		}else{
-			if(*q == 0)
-				break;
-			(*q)--;
-			c = w->r[*q];
-		}
-		if(c == cr){
-			if(--nest==0)
-				return 1;
-		}else if(c == cl)
-			nest++;
-	}
-	return cl=='\n' && nest==1;
+	i = decref(w);
+	if(i > 0)
+		return 0;
+	if(i < 0)
+		error("negative ref count");
+	wclunk(w);
+	wsendctlmesg(w, Exited, ZR, nil);
+	return 1;
 }
 
+void
+wsendctlmesg(Window *w, int type, Rectangle r, void *p)
+{
+	Wctlmesg wcm;
 
-uint
-wbacknl(Window *w, uint p, uint n)
+	wcm.type = type;
+	wcm.r = r;
+	wcm.p = p;
+	send(w->cctl, &wcm);
+}
+
+static int
+wctlmesg(Window *w, int m, Rectangle r, void *p)
 {
-	int i, j;
+	Image *i = p;
 
-	/* look for start of this line if n==0 */
-	if(n==0 && p>0 && w->r[p-1]!='\n')
-		n = 1;
-	i = n;
-	while(i-->0 && p>0){
-		--p;	/* it's at a newline now; back over it */
-		if(p == 0)
+	switch(m){
+	default:
+		error("unknown control message");
+		break;
+	case Wakeup:
+		break;
+	case Reshaped:
+		if(w->deleted){
+			freeimage(i);
 			break;
-		/* at 128 chars, call it a line anyway */
-		for(j=128; --j>0 && p>0; p--)
-			if(w->r[p-1]=='\n')
-				break;
+		}
+		w->screenr = r;
+		wclosewin(w);
+		wresize(w, i);
+		wsetcursor(w, FALSE);
+		break;
+	case Topped:
+		if(w->deleted)
+			break;
+		w->wctlready = 1;
+		wsetcursor(w, FALSE);
+		/* fall thrugh for redraw after input change */
+	case Repaint:
+		if(p != nil){
+			/* sync with input change from wcurrent()/wuncurrent() */
+			Channel *c = p;
+			input = recvp(c);
+			sendp(c, w);
+		}
+		if(w->i==nil || Dx(w->screenr)<=0)
+			break;
+		wrepaint(w);
+		flushimage(display, 1);
+		break;
+	case Refresh:
+		if(w->i==nil || Dx(w->screenr)<=0)
+			break;
+		wrefresh(w);
+		flushimage(display, 1);
+		break;
+	case Movemouse:
+		if(w->i==nil || Dx(w->screenr)<=0 || !ptinrect(r.min, w->i->r))
+			break;
+		wmovemouse(w, r.min);
+	case Rawon:
+		break;
+	case Rawoff:
+		while(w->nraw > 0){
+			wkeyctl(w, w->raw[0]);
+			--w->nraw;
+			runemove(w->raw, w->raw+1, w->nraw);
+		}
+		break;
+	case Holdon:
+	case Holdoff:
+		if(w->i==nil)
+			break;
+		wsetcursor(w, FALSE);
+		wrepaint(w);
+		flushimage(display, 1);
+		break;
+	case Truncate:
+		wdelete(w, 0, w->nr);
+		break;
+	case Deleted:
+		wclunk(w);
+		if(w->notefd >= 0)
+			write(w->notefd, "hangup", 6);
+		wclosewin(w);
+		flushimage(display, 1);
+		break;
+	case Exited:
+		wclosewin(w);
+		frclear(w, TRUE);
+		flushimage(display, 1);
+		if(w->notefd >= 0)
+			close(w->notefd);
+		chanfree(w->mc.c);
+		chanfree(w->ck);
+		chanfree(w->cctl);
+		chanfree(w->conswrite);
+		chanfree(w->consread);
+		chanfree(w->mouseread);
+		chanfree(w->wctlread);
+		chanfree(w->kbdread);
+		chanfree(w->complete);
+		chanfree(w->gone);
+		free(w->raw);
+		free(w->r);
+		free(w->dir);
+		free(w->label);
+		free(w);
+		break;
 	}
-	return p;
+	return m;
 }
 
-void
-wshow(Window *w, uint q0)
+static void
+wmousectl(Window *w)
 {
-	int qe;
-	int nl;
-	uint q;
+	int but;
 
-	qe = w->org+w->nchars;
-	if(w->org<=q0 && (q0<qe || (q0==qe && qe==w->nr)))
-		wscrdraw(w);
-	else{
-		nl = 4*w->maxlines/5;
-		q = wbacknl(w, q0, nl);
-		/* avoid going backwards if trying to go forwards - long lines! */
-		if(!(q0>w->org && q<w->org))
-			wsetorigin(w, q, TRUE);
-		while(q0 > w->org+w->nchars)
-			wsetorigin(w, w->org+1, FALSE);
+	for(but=1;; but++){
+		if(but > 5)
+			return;
+		if(w->mc.buttons == 1<<(but-1))
+			break;
+	}
+
+	incref(w);		/* hold up window while we track */
+	if(w->i != nil){
+		if(shiftdown && but > 3)
+			wkeyctl(w, but == 4 ? Kscrolloneup : Kscrollonedown);
+		else if(ptinrect(w->mc.xy, w->scrollr) || (but > 3))
+			wscroll(w, but);
+		else if(but == 1)
+			wselect(w);
 	}
+	wclose(w);
 }
 
 void
-wsetorigin(Window *w, uint org, int exact)
+winctl(void *arg)
 {
-	int i, a, fixup;
-	Rune *r;
-	uint n;
+	Rune *rp, *up, r;
+	uint qh, q0;
+	int nr, nb, c, wid, i, npart, initial, lastb;
+	char *s, *t, part[3];
+	Window *w;
+	Mousestate *mp, m;
+	enum { WKbd, WKbdread, WMouse, WMouseread, WCtl, WCwrite, WCread, WWread, WComplete, Wgone, NWALT };
+	Alt alts[NWALT+1];
+	Consreadmesg crm;
+	Mousereadmesg mrm;
+	Conswritemesg cwm;
+	Stringpair pair;
+	Wctlmesg wcm;
+	Completion *cr;
+	char *kbdq[32], *kbds;
+	uint kbdqr, kbdqw;
 
-	if(org>0 && !exact){
-		/* org is an estimate of the char posn; find a newline */
-		/* don't try harder than 256 chars */
-		for(i=0; i<256 && org<w->nr; i++){
-			if(w->r[org] == '\n'){
-				org++;
-				break;
+	w = arg;
+	threadsetname("winctl-id%d", w->id);
+
+	mrm.cm = chancreate(sizeof(Mouse), 0);
+	crm.c1 = chancreate(sizeof(Stringpair), 0);
+	crm.c2 = chancreate(sizeof(Stringpair), 0);
+	cwm.cw = chancreate(sizeof(Stringpair), 0);
+	
+	alts[WKbd].c = w->ck;
+	alts[WKbd].v = &kbds;
+	alts[WKbd].op = CHANRCV;
+	alts[WKbdread].c = w->kbdread;
+	alts[WKbdread].v = &crm;
+	alts[WKbdread].op = CHANSND;
+	alts[WMouse].c = w->mc.c;
+	alts[WMouse].v = &w->mc.Mouse;
+	alts[WMouse].op = CHANRCV;
+	alts[WMouseread].c = w->mouseread;
+	alts[WMouseread].v = &mrm;
+	alts[WMouseread].op = CHANSND;
+	alts[WCtl].c = w->cctl;
+	alts[WCtl].v = &wcm;
+	alts[WCtl].op = CHANRCV;
+	alts[WCwrite].c = w->conswrite;
+	alts[WCwrite].v = &cwm;
+	alts[WCwrite].op = CHANSND;
+	alts[WCread].c = w->consread;
+	alts[WCread].v = &crm;
+	alts[WCread].op = CHANSND;
+	alts[WWread].c = w->wctlread;
+	alts[WWread].v = &crm;
+	alts[WWread].op = CHANSND;
+	alts[WComplete].c = w->complete;
+	alts[WComplete].v = &cr;
+	alts[WComplete].op = CHANRCV;
+	alts[Wgone].c = w->gone;
+	alts[Wgone].v = "window deleted";
+	alts[Wgone].op = CHANNOP;
+	alts[NWALT].op = CHANEND;
+
+	kbdqr = kbdqw = 0;
+	npart = 0;
+	lastb = -1;
+	for(;;){
+		if(w->i==nil){
+			/* window deleted */
+			alts[Wgone].op = CHANSND;
+
+			alts[WKbdread].op = CHANNOP;
+			alts[WMouseread].op = CHANNOP;
+			alts[WCwrite].op = CHANNOP;
+			alts[WWread].op = CHANNOP;
+			alts[WCread].op = CHANNOP;
+		} else {
+			alts[WKbdread].op = (w->kbdopen && kbdqw != kbdqr) ?
+				CHANSND : CHANNOP;
+			alts[WMouseread].op = (w->mouseopen && w->mouse.counter != w->mouse.lastcounter) ? 
+				CHANSND : CHANNOP;
+			alts[WCwrite].op = w->scrolling || w->mouseopen || (w->qh <= w->org+w->nchars) ?
+				CHANSND : CHANNOP;
+			alts[WWread].op = w->wctlready ?
+				CHANSND : CHANNOP;
+			/* this code depends on NL and EOT fitting in a single byte */
+			/* kind of expensive for each loop; worth precomputing? */
+			if(w->holding)
+				alts[WCread].op = CHANNOP;
+			else if(npart || (w->rawing && w->nraw>0))
+				alts[WCread].op = CHANSND;
+			else{
+				alts[WCread].op = CHANNOP;
+				for(i=w->qh; i<w->nr; i++){
+					c = w->r[i];
+					if(c=='\n' || c=='\004'){
+						alts[WCread].op = CHANSND;
+						break;
+					}
+				}
+			}
+		}
+		switch(alt(alts)){
+		case WKbd:
+			if(kbdqw - kbdqr < nelem(kbdq))
+				kbdq[kbdqw++ % nelem(kbdq)] = kbds;
+			else
+				free(kbds);
+			if(w->kbdopen)
+				continue;
+			while(kbdqr != kbdqw){
+				kbds = kbdq[kbdqr++ % nelem(kbdq)];
+				if(*kbds == 'c'){
+					chartorune(&r, kbds+1);
+					if(r)
+						wkeyctl(w, r);
+				}
+				free(kbds);
+			}
+			break;
+		case WKbdread:
+			recv(crm.c1, &pair);
+			nb = 0;
+			while(kbdqr != kbdqw){
+				kbds = kbdq[kbdqr % nelem(kbdq)];
+				i = strlen(kbds)+1;
+				if(nb+i > pair.ns)
+					break;
+				memmove((char*)pair.s + nb, kbds, i);
+				free(kbds);
+				nb += i;
+				kbdqr++;
+			}
+			pair.ns = nb;
+			send(crm.c2, &pair);
+			continue;
+		case WMouse:
+			if(w->mouseopen) {
+				w->mouse.counter++;
+
+				/* queue click events */
+				if(!w->mouse.qfull && lastb != w->mc.buttons) {	/* add to ring */
+					mp = &w->mouse.queue[w->mouse.wi];
+					if(++w->mouse.wi == nelem(w->mouse.queue))
+						w->mouse.wi = 0;
+					if(w->mouse.wi == w->mouse.ri)
+						w->mouse.qfull = TRUE;
+					mp->Mouse = w->mc;
+					mp->counter = w->mouse.counter;
+					lastb = w->mc.buttons;
+				}
+			} else
+				wmousectl(w);
+			break;
+		case WMouseread:
+			/* send a queued event or, if the queue is empty, the current state */
+			/* if the queue has filled, we discard all the events it contained. */
+			/* the intent is to discard frantic clicking by the user during long latencies. */
+			w->mouse.qfull = FALSE;
+			if(w->mouse.wi != w->mouse.ri) {
+				m = w->mouse.queue[w->mouse.ri];
+				if(++w->mouse.ri == nelem(w->mouse.queue))
+					w->mouse.ri = 0;
+			} else
+				m = (Mousestate){w->mc.Mouse, w->mouse.counter};
+
+			w->mouse.lastcounter = m.counter;
+			send(mrm.cm, &m.Mouse);
+			continue;
+		case WCtl:
+			if(wctlmesg(w, wcm.type, wcm.r, wcm.p) == Exited){
+				while(kbdqr != kbdqw)
+					free(kbdq[kbdqr++ % nelem(kbdq)]);
+				chanfree(crm.c1);
+				chanfree(crm.c2);
+				chanfree(mrm.cm);
+				chanfree(cwm.cw);
+				threadexits(nil);
+			}
+			continue;
+		case WCwrite:
+			recv(cwm.cw, &pair);
+			rp = pair.s;
+			nr = pair.ns;
+			for(i=0; i<nr; i++)
+				if(rp[i] == '\b'){
+					up = rp+i;
+					initial = 0;
+					for(; i<nr; i++){
+						if(rp[i] == '\b'){
+							if(up == rp)
+								initial++;
+							else
+								up--;
+						}else
+							*up++ = rp[i];
+					}
+					if(initial){
+						if(initial > w->qh)
+							initial = w->qh;
+						qh = w->qh-initial;
+						wdelete(w, qh, qh+initial);
+						w->qh = qh;
+					}
+					nr = up - rp;
+					break;
+				}
+			w->qh = winsert(w, rp, nr, w->qh)+nr;
+			if(w->scrolling || w->mouseopen)
+				wshow(w, w->qh);
+			wsetselect(w, w->q0, w->q1);
+			wscrdraw(w);
+			free(rp);
+			break;
+		case WCread:
+			recv(crm.c1, &pair);
+			t = pair.s;
+			nb = pair.ns;
+			i = npart;
+			npart = 0;
+			if(i)
+				memmove(t, part, i);
+			while(i<nb && (w->qh<w->nr || w->nraw>0)){
+				if(w->qh == w->nr){
+					wid = runetochar(t+i, &w->raw[0]);
+					w->nraw--;
+					runemove(w->raw, w->raw+1, w->nraw);
+				}else
+					wid = runetochar(t+i, &w->r[w->qh++]);
+				c = t[i];	/* knows break characters fit in a byte */
+				i += wid;
+				if(!w->rawing && (c == '\n' || c=='\004')){
+					if(c == '\004')
+						i--;
+					break;
+				}
+			}
+			if(i==nb && w->qh<w->nr && w->r[w->qh]=='\004')
+				w->qh++;
+			if(i > nb){
+				npart = i-nb;
+				memmove(part, t+nb, npart);
+				i = nb;
 			}
-			org++;
+			pair.s = t;
+			pair.ns = i;
+			send(crm.c2, &pair);
+			continue;
+		case WWread:
+			w->wctlready = 0;
+			recv(crm.c1, &pair);
+			s = Dx(w->screenr) > 0 ? "visible" : "hidden";
+			t = "notcurrent";
+			if(w == input)
+				t = "current";
+			pair.ns = snprint(pair.s, pair.ns+1, "%11d %11d %11d %11d %11s %11s ",
+				w->i->r.min.x, w->i->r.min.y, w->i->r.max.x, w->i->r.max.y, t, s);
+			send(crm.c2, &pair);
+			continue;
+		case WComplete:
+			if(w->i!=nil){
+				if(!cr->advance)
+					showcandidates(w, cr);
+				if(cr->advance){
+					rp = runesmprint("%s", cr->string);
+					if(rp){
+						nr = runestrlen(rp);
+						q0 = w->q0;
+						q0 = winsert(w, rp, nr, q0);
+						wshow(w, q0+nr);
+						free(rp);
+					}
+				}
+			}
+			freecompletion(cr);
+			break;
 		}
+		if(w->i!=nil && Dx(w->screenr) > 0 && display->bufp > display->buf)
+			flushimage(display, 1);
 	}
-	a = org-w->org;
-	fixup = 0;
-	if(a>=0 && a<w->nchars){
-		frdelete(w, 0, a);
-		fixup = 1;	/* frdelete can leave end of last line in wrong selection mode; it doesn't know what follows */
-	}else if(a<0 && -a<w->nchars){
-		n = w->org - org;
-		r = w->r+org;
-		frinsert(w, r, r+n, 0);
-	}else
-		frdelete(w, 0, w->nchars);
-	w->org = org;
-	wfill(w);
-	wscrdraw(w);
-	wsetselect(w, w->q0, w->q1);
-	if(fixup && w->p1 > w->p0)
-		frdrawsel(w, frptofchar(w, w->p1-1), w->p1-1, w->p1, 1);
 }
 
 void
-wsetselect(Window *w, uint q0, uint q1)
-{
-	int p0, p1;
-
-	/* w->p0 and w->p1 are always right; w->q0 and w->q1 may be off */
-	w->q0 = q0;
-	w->q1 = q1;
-	/* compute desired p0,p1 from q0,q1 */
-	p0 = q0-w->org;
-	p1 = q1-w->org;
-	if(p0 < 0)
-		p0 = 0;
-	if(p1 < 0)
-		p1 = 0;
-	if(p0 > w->nchars)
-		p0 = w->nchars;
-	if(p1 > w->nchars)
-		p1 = w->nchars;
-	if(p0==w->p0 && p1==w->p1)
-		return;
-	/* screen disagrees with desired selection */
-	if(w->p1<=p0 || p1<=w->p0 || p0==p1 || w->p1==w->p0){
-		/* no overlap or too easy to bother trying */
-		frdrawsel(w, frptofchar(w, w->p0), w->p0, w->p1, 0);
-		frdrawsel(w, frptofchar(w, p0), p0, p1, 1);
-		goto Return;
-	}
-	/* overlap; avoid unnecessary painting */
-	if(p0 < w->p0){
-		/* extend selection backwards */
-		frdrawsel(w, frptofchar(w, p0), p0, w->p0, 1);
-	}else if(p0 > w->p0){
-		/* trim first part of selection */
-		frdrawsel(w, frptofchar(w, w->p0), w->p0, p0, 0);
-	}
-	if(p1 > w->p1){
-		/* extend selection forwards */
-		frdrawsel(w, frptofchar(w, w->p1), w->p1, p1, 1);
-	}else if(p1 < w->p1){
-		/* trim last part of selection */
-		frdrawsel(w, frptofchar(w, p1), p1, w->p1, 0);
-	}
-
-    Return:
-	w->p0 = p0;
-	w->p1 = p1;
-}
-
-uint
-winsert(Window *w, Rune *r, int n, uint q0)
+wsetpid(Window *w, int pid, int dolabel)
 {
-	uint m;
+	char buf[32];
+	int ofd;
 
-	if(n == 0)
-		return q0;
-	if(w->nr+n>HiWater && q0>=w->org && q0>=w->qh){
-		m = min(HiWater-LoWater, min(w->org, w->qh));
-		w->org -= m;
-		w->qh -= m;
-		if(w->q0 > m)
-			w->q0 -= m;
-		else
-			w->q0 = 0;
-		if(w->q1 > m)
-			w->q1 -= m;
-		else
-			w->q1 = 0;
-		w->nr -= m;
-		runemove(w->r, w->r+m, w->nr);
-		q0 -= m;
-	}
-	if(w->nr+n > w->maxr){
-		/*
-		 * Minimize realloc breakage:
-		 *	Allocate at least MinWater
-		 * 	Double allocation size each time
-		 *	But don't go much above HiWater
-		 */
-		m = max(min(2*(w->nr+n), HiWater), w->nr+n)+MinWater;
-		if(m > HiWater)
-			m = max(HiWater+MinWater, w->nr+n);
-		if(m > w->maxr){
-			w->r = runerealloc(w->r, m);
-			w->maxr = m;
+	ofd = w->notefd;
+	if(pid <= 0)
+		w->notefd = -1;
+	else {
+		if(dolabel){
+			snprint(buf, sizeof(buf), "rc %lud", (ulong)pid);
+			free(w->label);
+			w->label = estrdup(buf);
 		}
+		snprint(buf, sizeof(buf), "/proc/%lud/notepg", (ulong)pid);
+		w->notefd = open(buf, OWRITE|OCEXEC);
 	}
-	runemove(w->r+q0+n, w->r+q0, w->nr-q0);
-	runemove(w->r+q0, r, n);
-	w->nr += n;
-	/* if output touches, advance selection, not qh; works best for keyboard and output */
-	if(q0 <= w->q1)
-		w->q1 += n;
-	if(q0 <= w->q0)
-		w->q0 += n;
-	if(q0 < w->qh)
-		w->qh += n;
-	if(q0 < w->org)
-		w->org += n;
-	else if(q0 <= w->org+w->nchars)
-		frinsert(w, r, r+n, q0-w->org);
-	return q0;
+	if(ofd >= 0)
+		close(ofd);
 }
 
 void
-wfill(Window *w)
+winshell(void *args)
 {
-	Rune *rp;
-	int i, n, m, nl;
-
-	while(w->lastlinefull == FALSE){
-		n = w->nr-(w->org+w->nchars);
-		if(n == 0)
-			break;
-		if(n > 2000)	/* educated guess at reasonable amount */
-			n = 2000;
-		rp = w->r+(w->org+w->nchars);
+	Window *w;
+	Channel *pidc;
+	void **arg;
+	char *cmd, *dir;
+	char **argv;
 
-		/*
-		 * it's expensive to frinsert more than we need, so
-		 * count newlines.
-		 */
-		nl = w->maxlines-w->nlines;
-		m = 0;
-		for(i=0; i<n; ){
-			if(rp[i++] == '\n'){
-				m++;
-				if(m >= nl)
-					break;
-			}
-		}
-		frinsert(w, rp, rp+i, w->nchars);
+	arg = args;
+	w = arg[0];
+	pidc = arg[1];
+	cmd = arg[2];
+	argv = arg[3];
+	dir = arg[4];
+	rfork(RFNAMEG|RFFDG|RFENVG);
+	if(filsysmount(filsys, w->id) < 0){
+		fprint(2, "mount failed: %r\n");
+		sendul(pidc, 0);
+		threadexits("mount failed");
 	}
-}
-
-char*
-wcontents(Window *w, int *ip)
-{
-	return runetobyte(w->r, w->nr, ip);
-}
-
-void
-wsend(Window *w)
-{
-	getsnarf();
-	wsnarf(w);
-	if(nsnarf == 0)
-		return;
-	if(w->rawing){
-		waddraw(w, snarf, nsnarf);
-		if(snarf[nsnarf-1]!='\n' && snarf[nsnarf-1]!='\004')
-			waddraw(w, L"\n", 1);
-	}else{
-		winsert(w, snarf, nsnarf, w->nr);
-		if(snarf[nsnarf-1]!='\n' && snarf[nsnarf-1]!='\004')
-			winsert(w, L"\n", 1, w->nr);
+	close(0);
+	if(open("/dev/cons", OREAD) < 0){
+		fprint(2, "can't open /dev/cons: %r\n");
+		sendul(pidc, 0);
+		threadexits("/dev/cons");
+	}
+	close(1);
+	if(open("/dev/cons", OWRITE) < 0){
+		fprint(2, "can't open /dev/cons: %r\n");
+		sendul(pidc, 0);
+		threadexits("open");	/* BUG? was terminate() */
+	}
+	if(wclose(w) == 0){	/* remove extra ref hanging from creation */
+		notify(nil);
+		dup(1, 2);
+		if(dir)
+			chdir(dir);
+		procexec(pidc, cmd, argv);
+		_exits("exec failed");
 	}
-	wsetselect(w, w->nr, w->nr);
-	wshow(w, w->nr);
 }