shithub: lola

ref: d525a8fd26307afc029ae75f7adfed741d5ee36f
dir: /main.c/

View raw version
#include "inc.h"

Cursor whitearrow = {
	{0, 0},
	{0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0xFF, 0xFC, 
	 0xFF, 0xF0, 0xFF, 0xF0, 0xFF, 0xF8, 0xFF, 0xFC, 
	 0xFF, 0xFE, 0xFF, 0xFF, 0xFF, 0xFE, 0xFF, 0xFC, 
	 0xF3, 0xF8, 0xF1, 0xF0, 0xE0, 0xE0, 0xC0, 0x40, },
	{0xFF, 0xFF, 0xFF, 0xFF, 0xC0, 0x06, 0xC0, 0x1C, 
	 0xC0, 0x30, 0xC0, 0x30, 0xC0, 0x38, 0xC0, 0x1C, 
	 0xC0, 0x0E, 0xC0, 0x07, 0xCE, 0x0E, 0xDF, 0x1C, 
	 0xD3, 0xB8, 0xF1, 0xF0, 0xE0, 0xE0, 0xC0, 0x40, }
};

Cursor query = {
	{-7,-7},
	{0x0f, 0xf0, 0x1f, 0xf8, 0x3f, 0xfc, 0x7f, 0xfe, 
	 0x7c, 0x7e, 0x78, 0x7e, 0x00, 0xfc, 0x01, 0xf8, 
	 0x03, 0xf0, 0x07, 0xe0, 0x07, 0xc0, 0x07, 0xc0, 
	 0x07, 0xc0, 0x07, 0xc0, 0x07, 0xc0, 0x07, 0xc0, },
	{0x00, 0x00, 0x0f, 0xf0, 0x1f, 0xf8, 0x3c, 0x3c, 
	 0x38, 0x1c, 0x00, 0x3c, 0x00, 0x78, 0x00, 0xf0, 
	 0x01, 0xe0, 0x03, 0xc0, 0x03, 0x80, 0x03, 0x80, 
	 0x00, 0x00, 0x03, 0x80, 0x03, 0x80, 0x00, 0x00, }
};

Cursor crosscursor = {
	{-7, -7},
	{0x03, 0xC0, 0x03, 0xC0, 0x03, 0xC0, 0x03, 0xC0,
	 0x03, 0xC0, 0x03, 0xC0, 0xFF, 0xFF, 0xFF, 0xFF,
	 0xFF, 0xFF, 0xFF, 0xFF, 0x03, 0xC0, 0x03, 0xC0,
	 0x03, 0xC0, 0x03, 0xC0, 0x03, 0xC0, 0x03, 0xC0, },
	{0x00, 0x00, 0x01, 0x80, 0x01, 0x80, 0x01, 0x80,
	 0x01, 0x80, 0x01, 0x80, 0x01, 0x80, 0x7F, 0xFE,
	 0x7F, 0xFE, 0x01, 0x80, 0x01, 0x80, 0x01, 0x80,
	 0x01, 0x80, 0x01, 0x80, 0x01, 0x80, 0x00, 0x00, }
};

Cursor boxcursor = {
	{-7, -7},
	{0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
	 0xFF, 0xFF, 0xF8, 0x1F, 0xF8, 0x1F, 0xF8, 0x1F,
	 0xF8, 0x1F, 0xF8, 0x1F, 0xF8, 0x1F, 0xFF, 0xFF,
	 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, },
	{0x00, 0x00, 0x7F, 0xFE, 0x7F, 0xFE, 0x7F, 0xFE,
	 0x70, 0x0E, 0x70, 0x0E, 0x70, 0x0E, 0x70, 0x0E,
	 0x70, 0x0E, 0x70, 0x0E, 0x70, 0x0E, 0x70, 0x0E,
	 0x7F, 0xFE, 0x7F, 0xFE, 0x7F, 0xFE, 0x00, 0x00, }
};

Cursor sightcursor = {
	{-7, -7},
	{0x1F, 0xF8, 0x3F, 0xFC, 0x7F, 0xFE, 0xFB, 0xDF,
	 0xF3, 0xCF, 0xE3, 0xC7, 0xFF, 0xFF, 0xFF, 0xFF,
	 0xFF, 0xFF, 0xFF, 0xFF, 0xE3, 0xC7, 0xF3, 0xCF,
	 0x7B, 0xDF, 0x7F, 0xFE, 0x3F, 0xFC, 0x1F, 0xF8, },
	{0x00, 0x00, 0x0F, 0xF0, 0x31, 0x8C, 0x21, 0x84,
	 0x41, 0x82, 0x41, 0x82, 0x41, 0x82, 0x7F, 0xFE,
	 0x7F, 0xFE, 0x41, 0x82, 0x41, 0x82, 0x41, 0x82,
	 0x21, 0x84, 0x31, 0x8C, 0x0F, 0xF0, 0x00, 0x00, }
};

typedef struct RKeyboardctl RKeyboardctl;
struct RKeyboardctl
{
	Keyboardctl;
	int kbdfd;
};

RKeyboardctl *kbctl;
Mousectl *mctl;
int scrolling = 1;
char *startdir;
int shiftdown;
int gotscreen;
int servekbd;

Image *background;
Image *colors[NumColors];
Screen *wscreen;

void
killprocs(void)
{
	int i;

	for(i = 0; i < nwindows; i++)
		if(windows[i]->notefd >= 0)
			write(windows[i]->notefd, "hangup", 6);
}

/*
 * /dev/snarf updates when the file is closed, so we must open our own
 * fd here rather than use snarffd
 */
void
putsnarf(void)
{
	int fd, i, n;

	if(snarffd<0 || nsnarf==0)
		return;
	fd = open("/dev/snarf", OWRITE|OCEXEC);
	if(fd < 0)
		return;
	/* snarf buffer could be huge, so fprint will truncate; do it in blocks */
	for(i=0; i<nsnarf; i+=n){
		n = nsnarf-i;
		if(n >= 256)
			n = 256;
		if(fprint(fd, "%.*S", n, snarf+i) < 0)
			break;
	}
	close(fd);
}

void
setsnarf(char *s, int ns)
{
	free(snarf);
	snarf = runesmprint("%.*s", ns, s);
	nsnarf = runestrlen(snarf);
	snarfversion++;
}

void
getsnarf(void)
{
	int i, n;
	char *s, *sn;

	if(snarffd < 0)
		return;
	sn = nil;
	i = 0;
	seek(snarffd, 0, 0);
	for(;;){
		if(i > MAXSNARF)
			break;
		if((s = realloc(sn, i+1024+1)) == nil)
			break;
		sn = s;
		if((n = read(snarffd, sn+i, 1024)) <= 0)
			break;
		i += n;
	}
	if(i == 0)
		return;
	sn[i] = 0;
	setsnarf(sn, i);
	free(sn);
}

static int overridecursor;
static Cursor *ovcursor;
static Cursor *normalcursor;
Cursor *cursor;

void
setmousecursor(Cursor *c)
{
	if(cursor == c)
		return;
	cursor = c;
	setcursor(mctl, c);
}

void
setcursoroverride(Cursor *c, int ov)
{
	overridecursor = ov;
	ovcursor = c;
	setmousecursor(overridecursor ? ovcursor : normalcursor);
}

void
setcursornormal(Cursor *c)
{
	normalcursor = c;
	setmousecursor(overridecursor ? ovcursor : normalcursor);
}

char *rcargv[] = { "rc", "-i", nil };

Window*
new(Rectangle r)
{
	Window *w;

	w = wcreate(r, FALSE, scrolling);
	assert(w);
	if(wincmd(w, 0, nil, rcargv) == 0)
		return nil;
	return w;
}

void
drainmouse(Mousectl *mc, Channel *c)
{
	if(c) send(c, &mc->Mouse);
	while(mc->buttons){
		readmouse(mc);
		if(c) send(c, &mc->Mouse);
	}
}

Window*
clickwindow(int but, Mousectl *mc)
{
	Window *w;

	but = 1<<(but-1);
	setcursoroverride(&sightcursor, TRUE);
	drainmouse(mc, nil);
	while(!(mc->buttons & but)){
		readmouse(mc);
		if(mc->buttons & (7^but)){
			setcursoroverride(nil, FALSE);
			drainmouse(mc, nil);
			return nil;
		}
	}
	w = wpointto(mc->xy);
	return w;
}

Rectangle
dragrect(int but, Rectangle r, Mousectl *mc)
{
	Rectangle rc;
	Point start, end;

	but = 1<<(but-1);
	setcursoroverride(&boxcursor, TRUE);
	start = mc->xy;
	end = mc->xy;
	do{
		rc = rectaddpt(r, subpt(end, start));
		drawgetrect(rc, 1);
		readmouse(mc);
		drawgetrect(rc, 0);
		end = mc->xy;
	}while(mc->buttons == but);

	setcursoroverride(nil, FALSE);
	if(mc->buttons & (7^but)){
		rc.min.x = rc.max.x = 0;
		rc.min.y = rc.max.y = 0;
		drainmouse(mc, nil);
	}
	return rc;
}

Rectangle
sweeprect(int but, Mousectl *mc)
{
	Rectangle r, rc;

	but = 1<<(but-1);
	setcursoroverride(&crosscursor, TRUE);
	drainmouse(mc, nil);
	while(!(mc->buttons & but)){
		readmouse(mc);
		if(mc->buttons & (7^but))
			goto Return;
	}
	r.min = mc->xy;
	r.max = mc->xy;
	do{
		rc = canonrect(r);
		drawgetrect(rc, 1);
		readmouse(mc);
		drawgetrect(rc, 0);
		r.max = mc->xy;
	}while(mc->buttons == but);

    Return:
	setcursoroverride(nil, FALSE);
	if(mc->buttons & (7^but)){
		rc.min.x = rc.max.x = 0;
		rc.min.y = rc.max.y = 0;
		drainmouse(mc, nil);
	}
	return rc;
}

Window*
pick(void)
{
	Window *w1, *w2;

	w1 = clickwindow(3, mctl);
	drainmouse(mctl, nil);
	setcursoroverride(nil, FALSE);
	w2 = wpointto(mctl->xy);
	if(w1 != w2)
		return nil;
	return w1;
}

void
grab(void)
{
	Window *w = clickwindow(3, mctl);
	if(w == nil)
		setcursoroverride(nil, FALSE);
	else{
		Rectangle r = dragrect(3, w->img->r, mctl);
		if(Dx(r) > 0 || Dy(r) > 0){
			wmove(w, r.min);
			wfocus(w);
			flushimage(display, 1);
		}
	}
}

void
sweep(Window *w)
{
	Rectangle r = sweeprect(3, mctl);
	if(Dx(r) > 10 && Dy(r) > 10){
		if(w){
			wresize(w, r);
			wraise(w);
		}else{
			w = new(r);
		}
		wfocus(w);
		flushimage(display, 1);
	}
}

int
obscured(Window *w, Rectangle r, Window *t)
{
	if(Dx(r) < font->height || Dy(r) < font->height)
		return 1;
	if(!rectclip(&r, screen->r))
		return 1;
	for(; t; t = t->higher){
		if(t->hidden || Dx(t->img->r) == 0 || Dy(t->img->r) == 0 || rectXrect(r, t->img->r) == 0)
			continue;
		if(r.min.y < t->img->r.min.y)
			if(!obscured(w, Rect(r.min.x, r.min.y, r.max.x, t->img->r.min.y), t))
				return 0;
		if(r.min.x < t->img->r.min.x)
			if(!obscured(w, Rect(r.min.x, r.min.y, t->img->r.min.x, r.max.y), t))
				return 0;
		if(r.max.y > t->img->r.max.y)
			if(!obscured(w, Rect(r.min.x, t->img->r.max.y, r.max.x, r.max.y), t))
				return 0;
		if(r.max.x > t->img->r.max.x)
			if(!obscured(w, Rect(t->img->r.max.x, r.min.y, r.max.x, r.max.y), t))
				return 0;
		return 1;
	}
	return 0;
}

enum {
	Cut,
	Paste,
	Snarf,
	Plumb,
	Look,
	Send,
	Scroll
};
char *menu2str[] = {
	"cut",
	"paste",
	"snarf",
	"plumb",
	"look",
	"send",
	"scroll",
	nil
};
Menu menu2 = { menu2str };

enum {
	New,
	Reshape,
	Move,
	Delete,
	Hide,
	Exit
};
int Hidden = Exit+1;
char *menu3str[7 + nelem(hidden)] = {
	"New",
	"Resize",
	"Move",
	"Delete",
	"Hide",
	"Exit",
	nil
};
Menu menu3 = { menu3str };

void
btn2menu(Window *w)
{
	int sel;
	Text *x;
	Cursor *c;

	x = &w->text;
	menu2str[Scroll] = w->scrolling ? "noscroll" : "scroll";
	sel = menuhit(2, mctl, &menu2, wscreen);
	switch(sel){
	case Cut:
		xsnarf(x);
		xcut(x);
		xscrdraw(x);			// TODO let cut handle this?
		break;
	case Paste:
		xpaste(x);
		break;
	case Snarf:
		xsnarf(x);
		xscrdraw(x);			// TODO let snarf handle this?
		break;
	case Plumb:
		if(xplumb(x, w->dir, fsys.msize-1024)){
			c = cursor;
			setcursoroverride(&query, TRUE);
			sleep(300);
			setcursoroverride(c, FALSE);
		}
		break;
	case Look:
		xlook(x);
		break;
	case Send:
		xsend(x);
		break;
	case Scroll:
		w->scrolling = !w->scrolling;
		if(w->scrolling)
			xshow(x, x->nr);
		break;
	}
	wsendmsg(w, Wakeup);
}

void
btn3menu(void)
{
	Window *w, *t;
	int i, sel;

	nhidden = 0;
	for(i = 0; i < nwindows; i++){
		t = windows[i];
		if(t->hidden || obscured(t, t->img->r, t->higher)){
			hidden[nhidden] = windows[i];
			menu3str[nhidden+Hidden] = windows[i]->label;
			nhidden++;	
		}
	}
	menu3str[nhidden+Hidden] = nil;

	sel = menuhit(3, mctl, &menu3, wscreen);
	switch(sel){
	case New:
		sweep(nil);
		break;
	case Reshape:
		w = pick();
		if(w) sweep(w);
		break;
	case Move:
		grab();
		break;
	case Delete:
		w = pick();
		if(w) wsendmsg(w, Deleted);
		break;
	case Hide:
		w = pick();
		if(w) whide(w);
		break;
	case Exit:
		killprocs();
		threadexitsall(nil);
	default:
		if(sel >= Hidden){
			w = hidden[sel-Hidden];
			if(w->hidden)
				wunhide(w);
			else{
				wraise(w);
				wfocus(w);
			}
		}
		break;
	}
}

void
mthread(void*)
{
	Window *w;
	int inside;

	threadsetname("mousethread");
	while(readmouse(mctl) != -1){
		w = wpointto(mctl->xy);
again:
		inside = w && w == focused && ptinrect(mctl->xy, w->contrect);

		cursorwin = w;
		if(w)
			wsetcursor(w);
		else
			setcursornormal(nil);

/* TODO: handle borders */
		if(inside && w->mouseopen){
			drainmouse(mctl, w->mc.c);
		}else if(inside){
// TODO: this can't happen with rio, but maybe we should support it
if(mctl->buttons && topwin != w)
wraise(w);
			if(mctl->buttons & (1|8|16) || ptinrect(mctl->xy, w->text.scrollr))
				drainmouse(mctl, w->mc.c);
			if(mctl->buttons & 2){
				incref(w);
				btn2menu(w);
				wrelease(w);
			}
			if(mctl->buttons & 4)
				btn3menu();
		}else if(w){
			if(mctl->buttons & 7 ||
			   mctl->buttons & (8|16) && focused->mouseopen){
				wraise(w);
				wfocus(w);
				if(ptinrect(mctl->xy, w->contrect)){	// temp hack for borders
				if(mctl->buttons & 1)
					drainmouse(mctl, nil);
				else
					goto again;
				}
			}
		}else{
			if(mctl->buttons & 4)
				btn3menu();
		}
	}
}

void
resthread(void*)
{
	Window *w;
	Rectangle or, nr;
	Point delta;

	threadsetname("resizethread");
	for(;;){
		recvul(mctl->resizec);
		or = screen->clipr;
		if(getwindow(display, Refnone) < 0)
			sysfatal("resize failed: %r");
		nr = screen->clipr;

		freescrtemps();
		freescreen(wscreen);
		wscreen = allocscreen(screen, background, 0);
		draw(screen, screen->r, background, nil, ZP);

		delta = subpt(nr.min, or.min);
		for(w = bottomwin; w; w = w->higher){
			Rectangle r = w->img->r;
			freeimage(w->img);
			w->img = nil;
			wresize(w, rectaddpt(r, delta));
			if(w->hidden)
				originwindow(w->img, w->img->r.min, screen->r.max);
		}

		flushimage(display, 1);
	}
}

static void
_ioproc(void *arg)
{
	int m, n, nerr;
	char buf[1024], *e, *p;
	Rune r;
	RKeyboardctl *kc;

	kc = arg;
	threadsetname("kbdproc");
	n = 0;
	nerr = 0;
	if(kc->kbdfd >= 0){
		while(kc->kbdfd >= 0){
			m = read(kc->kbdfd, buf, sizeof(buf)-1);
			if(m <= 0){
				yield();	/* if error is due to exiting, we'll exit here */
				if(kc->kbdfd < 0)
					break;
				fprint(2, "keyboard: short read: %r\n");
				if(m<0 || ++nerr>10)
					threadexits("read error");
				continue;
			}
			/* one read can return multiple messages, delimited by NUL
			 * split them up for sending on the channel */
			e = buf+m;
			e[-1] = 0;
			e[0] = 0;
			for(p = buf; p < e; p += strlen(p)+1)
				chanprint(kc->c, "%s", p);
		}
	}else{
		while(kc->consfd >= 0){
			m = read(kc->consfd, buf+n, sizeof buf-n);
			if(m <= 0){
				yield();	/* if error is due to exiting, we'll exit here */
				if(kc->consfd < 0)
					break;
				fprint(2, "keyboard: short read: %r\n");
				if(m<0 || ++nerr>10)
					threadexits("read error");
				continue;
			}
			nerr = 0;
			n += m;
			while(n>0 && fullrune(buf, n)){
				m = chartorune(&r, buf);
				n -= m;
				memmove(buf, buf+m, n);
				if(chanprint(kc->c, "c%C", r) < 0)
					break;
			}
		}
	}
	chanfree(kc->c);
	free(kc->file);
	free(kc);
}

RKeyboardctl*
initkbd(char *file, char *kbdfile)
{
	RKeyboardctl *kc;
	char *t;

	if(file == nil)
		file = "/dev/cons";
	if(kbdfile == nil)
		kbdfile = "/dev/kbd";

	kc = mallocz(sizeof(RKeyboardctl), 1);
	if(kc == nil)
		return nil;
	kc->file = strdup(file);
// TODO: handle file == nil
	kc->consfd = open(file, ORDWR|OCEXEC);
	t = malloc(strlen(file)+16);
	if(kc->consfd<0 || t==nil)
		goto Error1;
	sprint(t, "%sctl", file);
	kc->ctlfd = open(t, OWRITE|OCEXEC);
	if(kc->ctlfd < 0){
		fprint(2, "initkeyboard: can't open %s: %r\n", t);
		goto Error2;
	}
	if(ctlkeyboard(kc, "rawon") < 0){
		fprint(2, "initkeyboard: can't turn on raw mode on %s: %r\n", t);
		close(kc->ctlfd);
		goto Error2;
	}
	free(t);
	kc->kbdfd = open(kbdfile, OREAD|OCEXEC);
	kc->c = chancreate(sizeof(char*), 20);
	kc->pid = proccreate(_ioproc, kc, 4096);
	return kc;

Error2:
	close(kc->consfd);
Error1:
	free(t);
	free(kc->file);
	free(kc);
	return nil;
}

/*
 *    kbd    -----+-------> to tap
 *                 \
 *                  \
 * from tap  --------+----> window
 */

Channel *ctltap;	/* open/close */
Channel *resptap;	/* open/close err */
Channel	*fromtap;	/* input from kbd tap program to window */
Channel *totap;		/* our keyboard input to tap program */
Channel *wintap;	/* tell the tapthread which Window to send to */

static int tapseats[] = { [OREAD] Tapoff, [OWRITE] Tapoff };

char*
tapctlmsg(char *msg)
{
	int perm;

	perm = msg[1];
	switch(msg[0]){
	case Tapoff:
		if(perm == ORDWR)
			tapseats[OREAD] = Tapoff, tapseats[OWRITE] = Tapoff;
		else
			tapseats[perm] = Tapoff;
		break;
	case Tapon:
		switch(perm){
		case ORDWR:
			if(tapseats[OREAD] != Tapoff || tapseats[OWRITE] != Tapoff)
				return "seat taken";
			tapseats[OREAD] = Tapon, tapseats[OWRITE] = Tapon;
			break;
		case OREAD: case OWRITE:
			if(tapseats[perm] != Tapoff)
				return "seat taken";
			tapseats[perm] = Tapon;
			break;
		}
		break;
	}
	return nil;
}

/* BUG: there's a deadlock somewhere sometimes when you delete a ktrans window */
void
keyboardtap(void*)
{
	char *s, *ctl;
	char *e;
	char *watched;
	Channel *fschan;
	int n;
	Stringpair pair;
	Window *w, *cur;

	threadsetname("keyboardtap");
	fschan = chancreate(sizeof(Stringpair), 0);
	enum { Awin, Actl, Afrom, Adev, Ato, Ainp, Awatch, NALT };
	Alt alts[NALT+1] = {
		[Awin]	{.c = wintap, .v = &w, .op = CHANRCV},
		[Actl]	{.c = ctltap, .v = &ctl, .op = CHANRCV},
		[Afrom]	{.c = fromtap, .v = &s, .op = CHANRCV},
		[Adev]	{.c = kbctl->c, .v = &s, .op = CHANRCV},
		[Ato]	{.c = totap, .v = &fschan, .op = CHANNOP},
		[Ainp]	{.c = nil, .v = &s, .op = CHANNOP},
		[Awatch]{.c = totap, .v = &fschan, .op = CHANNOP},
		[NALT]	{.op = CHANEND},
	};

	cur = nil;
	watched = nil;
	for(;;)
		switch(alt(alts)){
		case Awin:
			cur = w;
			if(cur != nil){
				alts[Ainp].c = cur->kbd;
				if(tapseats[OREAD] == Tapoff)	
					goto Reset;
				if(alts[Awatch].op == CHANSND)
					free(watched);
				watched = smprint("%c%d", Tapfocus, cur->id);
				alts[Awatch].op = CHANSND;
			}
			if(alts[Ainp].op != CHANNOP || alts[Ato].op != CHANNOP)
				free(s);
			goto Reset;
		case Actl:
			e = tapctlmsg(ctl);
			sendp(resptap, e);
			if(e != nil || *ctl != Tapoff){
				free(ctl);
				break;
			}
			free(ctl);
			goto Reset;
		case Afrom:
			if(cur == nil){
				free(s);
				break;
			}
			alts[Afrom].op = CHANNOP;
			alts[Adev].op = CHANNOP;
			alts[Ato].op = CHANNOP;
			alts[Ainp].op = CHANSND;
			break;
		case Adev:
			if(tapseats[OWRITE] == Tapoff && cur == nil){
				free(s);
				break;
			}
			alts[Afrom].op = CHANNOP;
			alts[Adev].op = CHANNOP;
			if(tapseats[OWRITE] == Tapoff)
				alts[Ainp].op = CHANSND;
			else
				alts[Ato].op = CHANSND;
			break;

		/* These two do the xreq channel dance
		 * ugly... */
		case Ato:
			recv(fschan, &pair);
			n = strlen(s)+1;
			pair.ns = min(n, pair.ns);
			memmove(pair.s, s, pair.ns);
			free(s);
			send(fschan, &pair);
			goto Reset;
		case Awatch:
			recv(fschan, &pair);
			n = strlen(watched)+1;
			pair.ns = min(n, pair.ns);
			memmove(pair.s, watched, pair.ns);
			free(watched);
			send(fschan, &pair);
			alts[Awatch].op = CHANNOP;
			break;

		case Ainp:
			if(*s == 'k' || *s == 'K')
				shiftdown = utfrune(s+1, Kshift) != nil;
		Reset:
			alts[Ainp].op = CHANNOP;
			alts[Ato].op = CHANNOP;
			alts[Afrom].op = CHANRCV;
			alts[Adev].op = CHANRCV;
			break;
		}
}

void
threadmain(int, char *[])
{
	char buf[256];
rfork(RFENVG);
newwindow("-dx 1280 -dy 800");

	if(getwd(buf, sizeof(buf)) == nil)
		startdir = estrdup(".");
	else
		startdir = estrdup(buf);
	if(initdraw(nil, nil, "lola") < 0)
		sysfatal("initdraw: %r");
	kbctl = initkbd(nil, nil);
	if(kbctl == nil)
		sysfatal("inikeyboard: %r");
	mctl = initmouse(nil, screen);
	if(mctl == nil)
		sysfatal("initmouse: %r");
	totap = chancreate(sizeof(Channel**), 0);
	fromtap = chancreate(sizeof(char*), 32);
	wintap = chancreate(sizeof(Window*), 0);
	ctltap = chancreate(sizeof(char*), 0);
	resptap = chancreate(sizeof(char*), 0);

	servekbd = kbctl->kbdfd >= 0;
	snarffd = open("/dev/snarf", OREAD|OCEXEC);
	gotscreen = access("/dev/screen", AEXIST)==0;

	background = allocimage(display, Rect(0,0,1,1), screen->chan, 1, 0x777777FF);
	colors[BACK] = allocimage(display, Rect(0,0,1,1), screen->chan, 1, 0xFFFFFFFF);
	colors[HIGH] = allocimage(display, Rect(0,0,1,1), screen->chan, 1, 0xCCCCCCFF);
	colors[BORD] = allocimage(display, Rect(0,0,1,1), screen->chan, 1, 0x999999FF);
	colors[TEXT] = allocimage(display, Rect(0,0,1,1), screen->chan, 1, 0x000000FF);
	colors[HTEXT] = allocimage(display, Rect(0,0,1,1), screen->chan, 1, 0x000000FF);

	colors[TITLE] = allocimage(display, Rect(0,0,1,1), screen->chan, 1, DGreygreen);
	colors[LTITLE] = allocimage(display, Rect(0,0,1,1), screen->chan, 1, DPalegreygreen);
	colors[TITLEHOLD] = allocimage(display, Rect(0,0,1,1), screen->chan, 1, DMedblue);
	colors[LTITLEHOLD] = allocimage(display, Rect(0,0,1,1), screen->chan, 1, DPalegreyblue);

	colors[PALETEXT] = allocimage(display, Rect(0,0,1,1), screen->chan, 1, 0x666666FF);
	colors[HOLDTEXT] = allocimage(display, Rect(0,0,1,1), screen->chan, 1, DMedblue);
	colors[PALEHOLDTEXT] = allocimage(display, Rect(0,0,1,1), screen->chan, 1, DGreyblue);

	wscreen = allocscreen(screen, background, 0);
	draw(screen, screen->r, background, nil, ZP);

	timerinit();


	threadcreate(mthread, nil, mainstacksize);
	threadcreate(resthread, nil, mainstacksize);
	/* proc so mouse keeps working if tap program crashes */
	proccreate(keyboardtap, nil, mainstacksize);

	flushimage(display, 1);

	fs();
	// not reached
}