shithub: lola

ref: 224c03c17e5c664b1b5b8417d1940301d9fccfd5
dir: /wind.c/

View raw version
#include "inc.h"

Window *bottomwin, *topwin;
Window *windows[1000];	// TMP
int nwindows;
Window *hidden[1000];
int nhidden;
Window *focused, *cursorwin;

static void winthread(void *arg);

static void
wlistpushback(Window *w)
{
	w->higher = bottomwin;
	if(bottomwin) bottomwin->lower = w;
	w->lower = nil;
	if(topwin == nil) topwin = w;
	bottomwin = w;
}

static void
wlistpushfront(Window *w)
{
	w->lower = topwin;
	if(topwin) topwin->higher = w;
	w->higher = nil;
	if(bottomwin == nil) bottomwin = w;
	topwin = w;
}

static void
wlistremove(Window *w)
{
	if(w->lower)
		w->lower->higher = w->higher;
	else
		bottomwin = w->higher;
	if(w->higher)
		w->higher->lower = w->lower;
	else
		topwin = w->lower;
	w->higher = nil;
	w->lower = nil;
}

void
wcalcrects(Window *w)
{
	if(w->noborder)
		w->contrect = w->img->r;
	else
		w->contrect = insetrect(w->img->r, Borderwidth);
	Rectangle r = insetrect(w->contrect, 1);
	w->scrollr = r;
	w->scrollr.max.x = w->scrollr.min.x + 12;
	w->textr = r;
	w->textr.min.x = w->scrollr.max.x + 4;
}

void
wdecor(Window *w)
{
	if(w->noborder)
		return;
	int c = w->holdmode ?
		w == focused ? TITLEHOLD : LTITLEHOLD :
		w == focused ? TITLE : LTITLE;
	border(w->img, w->img->r, Borderwidth, colors[c], ZP);
}

void
wsetcolors(Window *w)
{
	int c = w->holdmode ?
		w == focused ? HOLDTEXT : PALEHOLDTEXT :
		w == focused ? TEXT : PALETEXT;
	w->text.cols[TEXT] = colors[c];
}

static void
wsetsize(Window *w, Rectangle r)
{
	Rectangle hr;

	if(w->img)
		freeimage(w->img);
	if(w->hidden){
		hr = rectaddpt(r, subpt(screen->r.max, r.min));
		w->img = allocwindow(wscreen, hr, Refbackup, DNofill);
		originwindow(w->img, r.min, hr.min);
	}else
		w->img = allocwindow(wscreen, r, Refbackup, DNofill);
	wcalcrects(w);
	draw(w->img, w->img->r, colors[BACK], nil, ZP);
	xinit(&w->text, w->textr, w->scrollr, font, w->img, colors);
}

static int id = 1;

Window*
wcreate(Rectangle r, bool hidden, bool scrolling)
{
	Window *w;

	w = emalloc(sizeof(Window));
	incref(w);
	w->id = id++;
	w->notefd = -1;
	wsetlabel(w, "<unnamed>");
	w->dir = estrdup(startdir);
	w->hidden = hidden;
	w->scrolling = scrolling;
	wsetsize(w, r);
	wdecor(w);
	wlistpushfront(w);
	// TMP - make dynamic
	windows[nwindows++] = w;

	w->mc.c = chancreate(sizeof(Mouse), 16);
	w->mc.image = w->img;

	w->gone = chancreate(sizeof(int), 0);
	w->kbd = chancreate(sizeof(char*), 16);
	w->ctl = chancreate(sizeof(int), 0);
	w->conswrite = chancreate(sizeof(Channel**), 0);
	w->consread = chancreate(sizeof(Channel**), 0);
	w->kbdread = chancreate(sizeof(Channel**), 0);
	w->mouseread = chancreate(sizeof(Channel**), 0);
	w->wctlread = chancreate(sizeof(Channel**), 0);
	w->complete = chancreate(sizeof(Completion*), 0);
	threadcreate(winthread, w, mainstacksize);

	wsetname(w);
	wfocus(w);

	return w;
}

/* called from winthread when it exits */
static void
wfree(Window *w)
{
	if(w->notefd >= 0)
		close(w->notefd);
	xclear(&w->text);
	chanclose(w->mc.c);
	chanclose(w->gone);
	chanclose(w->kbd);
	chanclose(w->ctl);
	chanclose(w->conswrite);
	chanclose(w->consread);
	chanclose(w->kbdread);
	chanclose(w->mouseread);
	chanclose(w->wctlread);
	chanclose(w->complete);
	free(w->label);
	free(w);
}

static void
wclose(Window *w)
{
	int i;

	if(w->deleted)
		return;
	w->deleted = TRUE;
	assert(w != focused);	/* this must be done elsewhere */
	wlistremove(w);
	for(i = 0; i < nwindows; i++)
		if(windows[i] == w){
			nwindows--;
			memmove(&windows[i], &windows[i+1], (nwindows-i)*sizeof(Window*));
			break;
		}
	if(w->img){
		/* rio does this, not sure if useful */
		originwindow(w->img, w->img->r.min, screen->r.max);
		freeimage(w->img);
	}
	w->img = nil;
	flushimage(display, 1);
}

int
wrelease(Window *w)
{
	int i;

	i = decref(w);
	if(i > 0)
		return 0;
	if(i < 0)
		panic("negative ref count");
	wunfocus(w);
	wclose(w);
	wsendmsg(w, Closed);
	return 1;
}

void
wsendmsg(Window *w, int type)
{
	assert(w->threadname != threadgetname());
	sendul(w->ctl, type);
}

Window*
wfind(int id)
{
	int i;

	for(i = 0; i < nwindows; i++)
		if(windows[i]->id == id)
			return windows[i];
	return nil;
}

Window*
wpointto(Point pt)
{
	Window *w;

	for(w = topwin; w; w = w->lower)
		if(!w->hidden && ptinrect(pt, w->img->r))
			return w;
	return nil;
}

void
wsetcursor(Window *w)
{
	if(w == cursorwin)
		setcursornormal(w->holdmode ? &whitearrow : w->cursorp);
}

void
wsetlabel(Window *w, char *label)
{
	free(w->label);
	w->label = estrdup(label);
}

void
wsetname(Window *w)
{
	int i, n;
	char err[ERRMAX];
	
	n = snprint(w->name, sizeof(w->name)-2, "%s.%d.%d", w->noborder ? "noborder" : "window", w->id, w->namecount++);
	for(i='A'; i<='Z'; i++){
		if(nameimage(w->img, w->name, 1) > 0)
			return;
		errstr(err, sizeof err);
		if(strcmp(err, "image name in use") != 0)
			break;
		w->name[n] = i;
		w->name[n+1] = 0;
	}
	w->name[0] = 0;
	fprint(2, "lola: setname failed: %s\n", err);
}

void
wsetpid(Window *w, int pid, int dolabel)
{
	char buf[32];
	int ofd;

	ofd = w->notefd;
	if(pid <= 0)
		w->notefd = -1;
	else{
		if(dolabel){
			snprint(buf, sizeof(buf), "rc %lud", (ulong)pid);
			wsetlabel(w, buf);
		}
		snprint(buf, sizeof(buf), "/proc/%lud/notepg", (ulong)pid);
		w->notefd = open(buf, OWRITE|OCEXEC);
	}
	if(ofd >= 0)
		close(ofd);
}

void
wdelete(Window *w)
{
	wunfocus(w);
	wsendmsg(w, Deleted);
}

void
wrepaint(Window *w)
{
	wsetcolors(w);
	wdecor(w);
	if(!w->mouseopen)
		xredraw(&w->text);
}

/* restore window order after reshaping has disturbed it */
void
worder(void)
{
	Window *w;
	for(w = bottomwin; w; w = w->higher)
		if(!w->hidden)
			topwindow(w->img);
}

void
wresize(Window *w, Rectangle r)
{
	wsetsize(w, r);
	if(w != topwin && !w->hidden)
		worder();
	wsendmsg(w, Resized);
}

void
wmove(Window *w, Point pos)
{
	Point delta;
	/* BUG: originwindow causes the old window rect to be drawn onto the new one
	 * with backing store of allocscreen
	 * happens in _freeimage1(*winp); in libdraw/init.c:gengetwindow
	 * where *winp has the old rectangle
	 *
	 * We don't care if we're handling resizing ourselves though */

	if(w->mouseopen){
		delta = subpt(pos, w->img->r.min);
		wresize(w, rectaddpt(w->img->r, delta));
	}else{
		originwindow(w->img, pos, w->hidden ? screen->r.max : pos);
		if(w != topwin && !w->hidden)
			worder();
		wcalcrects(w);
		xsetrects(&w->text, w->textr, w->scrollr);
		wsendmsg(w, Moved);
	}
}

void
wraise(Window *w)
{
	wlistremove(w);
	wlistpushfront(w);
	topwindow(w->img);
	flushimage(display, 1);
}

void
wlower(Window *w)
{
	wlistremove(w);
	wlistpushback(w);
	bottomwindow(w->img);
	flushimage(display, 1);
}

void
wfocus(Window *w)
{
	Window *prev;

	if(w == focused)
		return;
	prev = focused;
	focused = w;
	sendp(wintap, w);
	/* TODO a bit ugly the repetition,
	 * but this might not stay anyways */
	if(prev){
		prev->wctlready = TRUE;
		wrepaint(prev);
		wsendmsg(prev, Wakeup);
	}
	if(focused){
		focused->wctlready = TRUE;
		wrepaint(focused);
		wsendmsg(focused, Wakeup);
	}
}

void
wunfocus(Window *w)
{
	if(w == focused)
		wfocus(nil);
}

int
whide(Window *w)
{
	if(w->hidden)
		return -1;
	incref(w);
	wunfocus(w);
	w->hidden = TRUE;
	w->wctlready = TRUE;
	originwindow(w->img, w->img->r.min, screen->r.max);
	wsendmsg(w, Wakeup);
	wrelease(w);
	return 1;
}

int
wunhide(Window *w)
{
	if(!w->hidden)
		return -1;
	incref(w);
	w->hidden = FALSE;
	w->wctlready = TRUE;
	originwindow(w->img, w->img->r.min, w->img->r.min);
	wraise(w);
	wfocus(w);
	wrelease(w);
	return 1;
}

void
wsethold(Window *w, int hold)
{
	int prev;

	if(hold)
		prev = w->holdmode++;
	else
		prev = --w->holdmode;
	if(prev == 0){
		wsetcursor(w);
		wrepaint(w);
	}
}

void
wmovemouse(Window *w, Point pt)
{
	// TODO? rio also checks menuing and such
	if(w == focused && wpointto(mctl->xy) == w)
		moveto(mctl, pt);
}

/*
 * 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);
}

/*
 * Filename completion
 */

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);

	wrelease(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->text.r[q-1];
		if(r<=' ' || r=='=' || r=='^' || r=='(' || r=='{')
			break;
		if(oneelement && r=='/')
			break;
		--q;
	}
	return q0-q;
}

static void
namecomplete(Window *w)
{
	Text *x;
	int nstr, npath;
	Rune *path, *str;
	char *dir, *root;
	Completejob *job;

	x = &w->text;
	/* control-f: filename completion; works back to white space or / */
	if(x->q0<x->nr && x->r[x->q0]>' ')	/* must be at end of word */
		return;
	nstr = windfilewidth(w, x->q0, TRUE);
	str = x->r+(x->q0-nstr);
	npath = windfilewidth(w, x->q0-nstr, FALSE);
	path = x->r+(x->q0-nstr-npath);

	/* is path rooted? if not, we need to make it relative to window path */
	if(npath>0 && path[0]=='/')
		dir = smprint("%.*S", npath, path);
	else {
		if(strcmp(w->dir, "") == 0)
			root = ".";
		else
			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 = smprint("%.*S", nstr, str);
	job->dir = cleanname(dir);
	job->win = w;
	incref(w);
	proccreate(completeproc, job, mainstacksize);
}

static void
showcandidates(Window *w, Completion *c)
{
	Text *x;
	int i;
	Fmt f;
	Rune *rp;
	uint nr, qline;
	char *s;

	x = &w->text;
	runefmtstrinit(&f);
	if (c->nmatch == 0)
		s = "[no matches in ";
	else
		s = "[";
	if(c->nfile > 32)
		fmtprint(&f, "%s%d files]\n", s, c->nfile);
	else{
		fmtprint(&f, "%s", s);
		for(i=0; i<c->nfile; i++){
			if(i > 0)
				fmtprint(&f, " ");
			fmtprint(&f, "%s", c->filename[i]);
		}
		fmtprint(&f, "]\n");
	}
	rp = runefmtstrflush(&f);
	nr = runestrlen(rp);

	/* place text at beginning of line before cursor and host point */
	qline = min(x->qh, x->q0);
	while(qline>0 && x->r[qline-1] != '\n')
		qline--;

	if(qline == x->qh){
		/* advance host point to avoid readback */
		x->qh = xinsert(x, rp, nr, qline)+nr;
	}else{
		xinsert(x, rp, nr, qline);
	}
	free(rp);
}

void
wkeyctl(Window *w, Rune r)
{
	Text *x;
	int nlines, n;
	int *notefd;

	x = &w->text;
	nlines = x->maxlines;	/* need signed */
	if(!w->mouseopen){
		switch(r){

		/* Scrolling */
		case Kscrollonedown:
			n = mousescrollsize(x->maxlines);
			xscrolln(x, max(n, 1));
			return;
		case Kdown:
			xscrolln(x, shiftdown ? 1 : nlines/3);
			return;
		case Kpgdown:
			xscrolln(x, nlines*2/3);
			return;
		case Kscrolloneup:
			n = mousescrollsize(x->maxlines);
			xscrolln(x, -max(n, 1));
			return;
		case Kup:
			xscrolln(x, -(shiftdown ? 1 : nlines/3));
			return;
		case Kpgup:
			xscrolln(x, -nlines*2/3);
			return;

		case Khome:
			xshow(x, 0);
			return;
		case Kend:
			xshow(x, x->nr);
			return;

		/* Cursor movement */
		case Kleft:
			if(x->q0 > 0)
				xplacetick(x, x->q0-1);
			return;
		case Kright:
			if(x->q1 < x->nr)
				xplacetick(x, x->q1+1);
			return;
		case CTRL('A'):
			while(x->q0 > 0 && x->r[x->q0-1] != '\n' &&
			      x->q0 != x->qh)
				x->q0--;
			xplacetick(x, x->q0);
			return;
		case CTRL('E'):
			while(x->q0 < x->nr && x->r[x->q0] != '\n')
				x->q0++;
			xplacetick(x, x->q0);
			return;
		case CTRL('B'):
			xplacetick(x, x->qh);
			return;

		/* Hold mode */
		case Kesc:
			wsethold(w, !w->holdmode);
			return;
		case Kdel:
			if(w->holdmode)
				wsethold(w, 0);
			break;
		}
	}

	if(x->rawmode && (x->q0 == x->nr || w->mouseopen))
		xaddraw(x, &r, 1);
	else if(r == Kdel){
		x->qh = x->nr;
		xshow(x, x->qh);
		if(w->notefd < 0)
			return;
		notefd = emalloc(sizeof(int));
		*notefd = dup(w->notefd, -1);
		proccreate(interruptproc, notefd, 4096);
	}else if(r == CTRL('F') || r == Kins)
		namecomplete(w);
	else
		xtype(x, r);
}

void
wmousectl(Window *w)
{
	int but;

	for(but = 1; but < 6; but++)
		if(w->mc.buttons == 1<<(but-1))
			goto found;
	return;
found:

	incref(w);
	if(shiftdown && but > 3)
		wkeyctl(w, but == 4 ? Kscrolloneup : Kscrollonedown);
	else if(ptinrect(w->mc.xy, w->text.scrollr) || but > 3)
		xscroll(&w->text, &w->mc, but);
	else if(but == 1)
		xselect(&w->text, &w->mc);
	wrelease(w);
}

int
winctl(Window *w, int type)
{
	Text *x;
	int i;

	x = &w->text;
	switch(type){
	case Closed:
		wfree(w);
		return 1;

	case Deleted:
		if(w->notefd >= 0)
			write(w->notefd, "hangup", 6);
		wclose(w);
		break;

	case Resized:
		wsetname(w);
	case Moved:
		w->resized = TRUE;
		w->wctlready = TRUE;
		w->mc.buttons = 0;	/* avoid re-triggering clicks on resize */
		w->mq.counter++;	/* cause mouse to be re-read */
		wdecor(w);
		break;

	case Refresh:
/* TODO: clean this up? */
		if(w->deleted)
			break;
		draw(w->img, w->img->r, x->cols[BACK], nil, ZP);
		wdecor(w);
		xfill(x);
		x->ticked = 0;
		if(x->p0 > 0)
			frdrawsel(x, frptofchar(x, 0), 0, x->p0, 0);
		if(x->p1 < x->nchars)
			frdrawsel(x, frptofchar(x, x->p1), x->p1, x->nchars, 0);
		frdrawsel(x, frptofchar(x, x->p0), x->p0, x->p1, 1);
		x->lastsr = ZR;
		xscrdraw(x);
		break;

	case Holdon:
		wsethold(w, TRUE);
		break;
	case Holdoff:
		wsethold(w, FALSE);
		break;

	case Rawon:
		break;
	case Rawoff:
// TODO: better to remove one by one? not sure if wkeyctl is safe
		for(i = 0; i < x->nraw; i++)
			wkeyctl(w, x->raw[i]);
		x->nraw = 0;
		break;
	}
	return 0;
}

static void
winthread(void *arg)
{
	Window *w;
	Text *x;
	Rune r, *rp;
	char *s;
	int cm;
	enum { AKbd, AMouse, ACtl, AConsWrite, AConsRead,
		AKbdRead, AMouseRead, AWctlRead, AComplete, Agone, NALT };
	Alt alts[NALT+1];
	Channel *fsc;
	Stringpair pair;
	int i, nb, nr, initial;
	uint q0;
	RuneConvBuf cnv;
	Mousestate m;
	Completion *comp;

	w = arg;
	threadsetname("winthread-%d", w->id);
	w->threadname = threadgetname();
	x = &w->text;
	nr = 0;
	memset(&cnv, 0, sizeof(cnv));
	fsc = chancreate(sizeof(Stringpair), 0);

	alts[AKbd] = ALT(w->kbd, &s, CHANRCV);
	alts[AMouse] = ALT(w->mc.c, &w->mc.Mouse, CHANRCV);
	alts[ACtl] = ALT(w->ctl, &cm, CHANRCV);
	alts[AConsWrite] = ALT(w->conswrite, &fsc, CHANSND);
	alts[AConsRead] = ALT(w->consread, &fsc, CHANSND);
	alts[AKbdRead] = ALT(w->kbdread, &fsc, CHANSND);
	alts[AMouseRead] = ALT(w->mouseread, &fsc, CHANSND);
	alts[AWctlRead] = ALT(w->wctlread, &fsc, CHANSND);
	alts[AComplete] = ALT(w->complete, &comp, CHANRCV);
	alts[Agone] = ALT(w->gone, nil, CHANNOP);
	alts[NALT].op = CHANEND;

	for(;;){
		if(w->deleted){
			alts[Agone].op = CHANSND;
			alts[AConsWrite].op = CHANNOP;
			alts[AConsRead].op = CHANNOP;
			alts[AKbdRead].op = CHANNOP;
			alts[AMouseRead].op = CHANNOP;
			alts[AWctlRead].op = CHANNOP;
		}else{
			nr = xninput(x);
			if(!w->holdmode && (nr >= 0 || cnv.n > 0 || x->rawmode && x->nraw > 0))
				alts[AConsRead].op = CHANSND;
			else
				alts[AConsRead].op = CHANNOP;
			if(w->scrolling || w->mouseopen || x->qh <= x->org+x->nchars)
				alts[AConsWrite].op = CHANSND;
			else
				alts[AConsWrite].op = CHANNOP;
			if(w->kbdopen && (w->kq.ri != w->kq.wi || w->kq.full))
				alts[AKbdRead].op = CHANSND;
			else
				alts[AKbdRead].op = CHANNOP;
			if(w->mouseopen && w->mq.counter != w->mq.lastcounter)
				alts[AMouseRead].op = CHANSND;
			else
				alts[AMouseRead].op = CHANNOP;
			alts[AWctlRead].op = w->wctlready ? CHANSND : CHANNOP;
		}

		switch(alt(alts)){
		case AKbd:
			if(!w->kq.full){
				w->kq.q[w->kq.wi++] = s;
				w->kq.wi %= nelem(w->kq.q);
				w->kq.full = w->kq.wi == w->kq.ri;
			}else
				free(s);
			if(!w->kbdopen)
			while(w->kq.ri != w->kq.wi || w->kq.full){
				s = w->kq.q[w->kq.ri++];
				w->kq.ri %= nelem(w->kq.q);
				w->kq.full = FALSE;
				if(*s == 'c'){
					chartorune(&r, s+1);
					if(r)
						wkeyctl(w, r);
				}
				free(s);
			}
			break;

		case AKbdRead:
			recv(fsc, &pair);
			nb = 0;
			while(w->kq.ri != w->kq.wi || w->kq.full){
				s = w->kq.q[w->kq.ri];
				i = strlen(s)+1;
				if(nb+i > pair.ns)
					break;
				w->kq.ri = (w->kq.ri+1) % nelem(w->kq.q);
				w->kq.full = FALSE;
				memmove((char*)pair.s + nb, s, i);
				free(s);
				nb += i;
			}
			pair.ns = nb;
			send(fsc, &pair);
			break;

		case AMouse:
			if(w->mouseopen){
				Mousestate *mp;
				w->mq.counter++;
				/* queue click events in ring buffer.
				 * pure movement only in else branch of the case below */
				if(!w->mq.full && w->mq.lastb != w->mc.buttons){
					mp = &w->mq.q[w->mq.wi++];
					w->mq.wi %= nelem(w->mq.q);
					w->mq.full = w->mq.wi == w->mq.ri;
					mp->Mouse = w->mc;
					mp->counter = w->mq.counter;
					w->mq.lastb = w->mc.buttons;
				}
			}else
				wmousectl(w);
			break;

		case AMouseRead:
			recv(fsc, &pair);
			w->mq.full = FALSE;
			/* first return queued clicks, then current state */
			if(w->mq.wi != w->mq.ri){
				m = w->mq.q[w->mq.ri++];
				w->mq.ri %= nelem(w->mq.q);
			}else
				m = (Mousestate){w->mc.Mouse, w->mq.counter};
			w->mq.lastcounter = m.counter;

			pair.ns = snprint(pair.s, pair.ns, "%c%11d %11d %11d %11ld ",
				"mr"[w->resized], m.xy.x, m.xy.y, m.buttons, m.msec);
			w->resized = FALSE;
			send(fsc, &pair);
			break;

		case AConsWrite:
			recv(fsc, &pair);
			initial = handlebs(&pair);
			if(initial){
				initial = min(initial, x->qh);
				xdelete(x, x->qh-initial, x->qh);
			}
			x->qh = xinsert(x, pair.s, pair.ns, x->qh) + pair.ns;
			free(pair.s);
			if(w->scrolling || w->mouseopen)
				xshow(x, x->qh);
			xscrdraw(x);
			break;

		case AConsRead:
			recv(fsc, &pair);
			cnvsize(&cnv, pair.ns);
			nr = r2bfill(&cnv, x->r+x->qh, nr);
			x->qh += nr;
			/* if flushed by ^D, skip the ^D */
			if(!(nr > 0 && x->r[x->qh-1] == '\n') &&
			   x->qh < x->nr && x->r[x->qh] == CTRL('D'))
				x->qh++;
			if(x->rawmode){
				nr = r2bfill(&cnv, x->raw, x->nraw);
				x->nraw -= nr;
				runemove(x->raw, x->raw+nr, x->nraw);
			}
			r2bfinish(&cnv, &pair);
			send(fsc, &pair);
			break;

		case AWctlRead:
			w->wctlready = FALSE;
			recv(fsc, &pair);
			pair.ns = snprint(pair.s, pair.ns, "%11d %11d %11d %11d %11s %11s ",
				w->img->r.min.x, w->img->r.min.y, w->img->r.max.x, w->img->r.max.y,
				w == focused ? "current" : "notcurrent",
				w->hidden ? "hidden" : "visible");
			send(fsc, &pair);
			break;

		case ACtl:
			if(winctl(w, cm)){
				free(cnv.buf);
				return;
			}
			break;

		case AComplete:
			if(w->img!=nil){
				if(!comp->advance)
					showcandidates(w, comp);
				if(comp->advance){
					rp = runesmprint("%s", comp->string);
					if(rp){
						nr = runestrlen(rp);
						q0 = x->q0;
						q0 = xinsert(x, rp, nr, q0);
						xshow(x, q0+nr);
						free(rp);
					}
				}
			}
			freecompletion(comp);
			break;
		}
		flushimage(display, 1);
	}
}

static void
shellproc(void *args)
{
	Window *w;
	Channel *pidc;
	void **arg;
	char *cmd, *dir;
	char **argv;

	arg = args;
	w = arg[0];
	pidc = arg[1];
	cmd = arg[2];
	argv = arg[3];
	dir = arg[4];
	rfork(RFNAMEG|RFFDG|RFENVG);
	if(fsmount(w->id) < 0){
		fprint(2, "mount failed: %r\n");
		sendul(pidc, 0);
		threadexits("mount failed");
	}
	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(wrelease(w) == 0){	/* remove extra ref hanging from creation */
		notify(nil);
		dup(1, 2);
		if(dir)
			chdir(dir);
		procexec(pidc, cmd, argv);
		_exits("exec failed");
	}
}

int
wincmd(Window *w, int pid, char *dir, char **argv)
{
	Channel *cpid;
	void *args[5];

	if(argv){
		cpid = chancreate(sizeof(int), 0);
		assert(cpid);
		args[0] = w;
		args[1] = cpid;
		args[2] = "/bin/rc";
		args[3] = argv;
		args[4] = dir;
		proccreate(shellproc, args, mainstacksize);
		pid = recvul(cpid);
		chanfree(cpid);
		if(pid == 0){
			wdelete(w);
			return 0;
		}
	}

	wsetpid(w, pid, 1);
	if(dir){
		free(w->dir);
		w->dir = estrdup(dir);
	}

	return pid;
}