]> git.lizzy.rs Git - plan9front.git/commitdiff
rio: make window focus changes deterministic, cleanup wind.c
authorcinap_lenrek <cinap_lenrek@felloff.net>
Sun, 7 Mar 2021 19:26:30 +0000 (20:26 +0100)
committercinap_lenrek <cinap_lenrek@felloff.net>
Sun, 7 Mar 2021 19:26:30 +0000 (20:26 +0100)
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.

sys/src/cmd/rio/dat.h
sys/src/cmd/rio/rio.c
sys/src/cmd/rio/wctl.c
sys/src/cmd/rio/wind.c

index edc855595e586af95713d9f076b06f8f2c9c3eba..dac32da5fde3210f6291c169032e81b913cb5e01 100644 (file)
@@ -183,45 +183,25 @@ Window*   wtop(Point);
 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
 {
index a28fb2ccc14eaad49aa0956677cee3a4a2ad5fd6..9c18a6955ebf2d28856f72fdabf0ae2c0e473f97 100644 (file)
@@ -530,8 +530,10 @@ mousethread(void*)
                                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 @@ resized(void)
                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 @@ delete(void)
        Window *w;
 
        w = pointto(TRUE);
-       if(w)
+       if(w!=nil)
                wsendctlmesg(w, Deleted, ZR, nil);
 }
 
@@ -1016,8 +1019,10 @@ resize(void)
                return;
        incref(w);
        i = sweep();
-       if(i)
+       if(i!=nil){
+               wcurrent(w);
                wsendctlmesg(w, Reshaped, i->r, i);
+       }
        wclose(w);
 }
 
@@ -1032,8 +1037,10 @@ move(void)
                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 @@ whide(Window *w)
        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 @@ wunhide(Window *w)
        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 @@ unhide(int j)
        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;
                }
index bef98fda95e7e03071d70bd450e6afd4ba22f8aa..b2de73e86636e139a400fa3b4c0d4a6669ac32ec 100644 (file)
@@ -380,6 +380,7 @@ wctlcmd(Window *w, Rectangle r, int cmd, char *err)
                } 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 @@ wctlcmd(Window *w, Rectangle r, int cmd, char *err)
                        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)){
index 1fd1b671c639b972eb7ed29b966c4bafabde5f69..6869cb8af218c6da24846a529b5f5711fda2fbb5 100644 (file)
 #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++;
-
-                               /* 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;
-                       }
-                       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);
-       }
-}
-
-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;
-}
-
-/*
- * 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)
+enum
 {
-       int *notefd;
-
-       notefd = v;
-       write(*notefd, "interrupt", 9);
-       close(*notefd);
-       free(notefd);
-}
+       HiWater = 640000,       /* max size of history */
+       LoWater = 400000,       /* min size of history after max'ed */
+       MinWater        = 20000,        /* room to leave available when reallocating */
+};
 
-int
-windfilewidth(Window *w, uint q0, int oneelement)
+static uint
+winsert(Window *w, Rune *r, int n, uint q0)
 {
-       uint q;
-       Rune r;
+       uint m;
 
-       q = q0;
-       while(q > 0){
-               r = w->r[q-1];
-               if(r<=' ' || r=='=' || r=='^' || r=='(' || r=='{')
-                       break;
-               if(oneelement && r=='/')
-                       break;
-               --q;
+       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;
        }
-       return q0-q;
-}
-
-void
-showcandidates(Window *w, Completion *c)
-{
-       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]);
+       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;
                }
-               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);
+       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;
 }
 
-typedef struct Completejob Completejob;
-struct Completejob
-{
-       char    *dir;
-       char    *str;
-       Window  *win;
-};
-
-void
-completeproc(void *arg)
+static void
+wfill(Window *w)
 {
-       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);
+       Rune *rp;
+       int i, n, m, nl;
 
-       wclose(job->win);
+       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);
 
-       free(job->dir);
-       free(job->str);
-       free(job);
+               /*
+                * 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);
+       }
 }
 
-void
-namecomplete(Window *w)
+static void
+wsetselect(Window *w, uint q0, uint q1)
 {
-       int nstr, npath;
-       Rune *path, *str;
-       char *dir, *root;
-       Completejob *job;
+       int p0, p1;
 
-       /* 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 */
+       /* 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;
-       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
-                       root = w->dir;
-               dir = smprint("%s/%.*S", root, npath, path);
+       /* 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);
        }
-       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);
+    Return:
+       w->p0 = p0;
+       w->p1 = p1;
 }
 
-void
-wkeyctl(Window *w, Rune r)
+static void
+wborder(Window *w, int type)
 {
-       uint q0 ,q1;
-       int n, nb;
-       int *notefd;
+       Image *col;
 
-       switch(r){
-       case 0:
-       case Kcaps:
-       case Knum:
-       case Kshift:
-       case Kalt:
-       case Kctl:
-       case Kaltgr:
+       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;
        }
+       border(w->i, w->i->r, Selborder, col, ZP);
+}
 
-       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;
+static void
+wsetcols(Window *w, int topped)
+{
+       if(w->holding)
+               if(topped)
+                       w->cols[TEXT] = holdcol;
                else
-                       w->holding++;
-               wsetcursor(w, FALSE);
-               wrepaint(w);
-               if(r == Kesc)
+                       w->cols[TEXT] = lightholdcol;
+       else
+               if(topped)
+                       w->cols[TEXT] = cols[TEXT];
+               else
+                       w->cols[TEXT] = paletextcol;
+}
+
+void
+wsetname(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(r != Kdel){
-               wsnarf(w);
-               wcut(w);
+       w->name[0] = 0;
+       fprint(2, "rio: setname failed: %s\n", err);
+}
+
+static void
+wresize(Window *w, Image *i)
+{
+       Rectangle r;
+
+       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);
        }
-       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 == 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;
+}
+
+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);
+}
+
+static void
+wrefresh(Window *w)
+{
+       Rectangle r;
+
+       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);
+}
+
+/*
+ * 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,138 +592,123 @@ wbswidth(Window *w, Rune c)
 }
 
 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)
+wsetorigin(Window *w, uint org, int exact)
 {
-       Plumbmsg *m;
-       static int fd = -2;
-       char buf[32];
-       uint p0, p1;
-       Cursor *c;
+       int i, a, fixup;
+       Rune *r;
+       uint n;
 
-       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);
+       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++;
+               }
        }
-       plumbfree(m);
+       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
-wlook(Window *w)
+uint
+wbacknl(Window *w, uint p, uint n)
 {
-       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, j;
 
-       while(runestrncmp(w->r+w->q0, w->r+i, n) != 0){
-               if(i < e)
-                       i++;
-               else
-                       i = 0;
+       /* 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;
+}
 
-       wsetselect(w, i, i+n);
-       wshow(w, i);
+char*
+wcontents(Window *w, int *ip)
+{
+       return runetobyte(w->r, w->nr, ip);
 }
 
 void
-wmousectl(Window *w)
+wshow(Window *w, uint q0)
 {
-       int but;
+       int qe;
+       int nl;
+       uint q;
 
-       for(but=1;; but++){
-               if(but > 5)
-                       return;
-               if(w->mc.buttons == 1<<(but-1))
-                       break;
+       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);
        }
+}
 
-       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
+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
+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);
+       }
+       wsetselect(w, w->nr, w->nr);
+       wshow(w, w->nr);
+}
+
+static void
 wdelete(Window *w, uint q0, uint q1)
 {
        uint n, p0, p1;
@@ -945,880 +724,1095 @@ wdelete(Window *w, uint q0, uint 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);
-       }
-}
-
-
-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)
-{
-       if(f != &selectwin->Frame)
-               error("frameselect not right frame");
-       wframescroll(selectwin, dl);
-}
-
-void
-wframescroll(Window *w, int dl)
-{
-       uint q0;
-
-       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);
-}
-
-void
-wselect(Window *w)
-{
-       uint q0, q1;
-       int b, x, y, dx, dy, mode, first;
-
-       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;
+       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;
-
-       wcm.type = type;
-       wcm.r = r;
-       wcm.p = p;
-       send(w->cctl, &wcm);
+       if(w->q1 == w->q0)
+               return;
+       wdelete(w, w->q0, w->q1);
+       wsetselect(w, w->q0, w->q0);
 }
 
-int
-wctlmesg(Window *w, int m, Rectangle r, void *p)
+void
+wpaste(Window *w)
 {
-       Image *i = p;
+       uint q0;
 
-       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);
-
-                       /*
-                        * 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;
-
-                               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;
+       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);
        }
-       return m;
 }
 
-/*
- * Convert back to physical coordinates
- */
 void
-wmovemouse(Window *w, Point p)
+wlook(Window *w)
 {
-       if(w != input || menuing || sweeping)
+       int i, n, e;
+
+       i = w->q1;
+       n = i - w->q0;
+       e = w->nr - n;
+       if(n <= 0 || e < n)
                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);
+
+       if(i > e)
+               i = 0;
+
+       while(runestrncmp(w->r+w->q0, w->r+i, n) != 0){
+               if(i < e)
+                       i++;
+               else
+                       i = 0;
+       }
+
+       wsetselect(w, i, i+n);
+       wshow(w, i);
 }
 
 void
-wborder(Window *w, int type)
+wplumb(Window *w)
 {
-       Image *col;
+       Plumbmsg *m;
+       static int fd = -2;
+       char buf[32];
+       uint p0, p1;
+       Cursor *c;
 
-       if(w->i == nil)
+       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);
+}
+
+static void
+wkeyctl(Window *w, Rune r)
+{
+       uint q0 ,q1;
+       int n, nb;
+       int *notefd;
+
+       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);
 }