shithub: lola

ref: 95fa0cb9868e932653e84533fe41691ad4e1579e
dir: /main.c/

View raw version
#include "inc.h"

bool scrolling;
bool notitle;
int ndeskx = 3;
int ndesky = 3;

RKeyboardctl *kbctl;
Mousectl *mctl;
char *startdir;
bool shiftdown, ctldown;
bool gotscreen;
bool servekbd;

Screen *wscreen;
Image *fakebg;

Channel *pickchan;

void
killprocs(void)
{
	int i;

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

static char *oknotes[] ={
	"delete",
	"hangup",
	"kill",
	"exit",
	nil,	// for debugging
	nil
};

int
notehandler(void*, char *msg)
{
	int i;

	killprocs();
	for(i = 0; oknotes[i]; i++)
		if(strncmp(oknotes[i], msg, strlen(oknotes[i])) == 0)
			threadexitsall(msg);
	fprint(2, "lola %d: abort: %s\n", getpid(), msg);
	abort();
	return 0;
}

/*
 * /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, Window *w)
{
	if(w) send(w->mc.c, &mc->Mouse);
	while(mc->buttons){
		readmouse(mc);
		/* stop sending once focus changes.
		 * buttons released in wfocus() */
		if(w != focused) w = nil;
		if(w) send(w->mc.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;
}

int
whichside(int x, int lo, int hi)
{
	return	x < lo+20 ? 0 :
		x > hi-20 ? 2 :
		1;
}

/* 0 1 2
 * 3   5
 * 6 7 8 */
int
whichcorner(Rectangle r, Point p)
{
	int i, j;
	
	i = whichside(p.x, r.min.x, r.max.x);
	j = whichside(p.y, r.min.y, r.max.y);
	return 3*j+i;
}

/* replace corner or edge of rect with point */
Rectangle
changerect(Rectangle r, int corner, Point p)
{
	switch(corner){
	case 0: return Rect(p.x, p.y, r.max.x, r.max.y);
	case 1: return Rect(r.min.x, p.y, r.max.x, r.max.y);
	case 2: return Rect(r.min.x, p.y, p.x+1, r.max.y);
	case 3: return Rect(p.x, r.min.y, r.max.x, r.max.y);
	case 5: return Rect(r.min.x, r.min.y, p.x+1, r.max.y);
	case 6: return Rect(p.x, r.min.y, r.max.x, p.y+1);
	case 7: return Rect(r.min.x, r.min.y, r.max.x, p.y+1);
	case 8: return Rect(r.min.x, r.min.y, p.x+1, p.y+1);
	}
	return r;
}

Rectangle
bandrect(Rectangle r, int but, Mousectl *mc)
{
	Rectangle or, nr;
	int corner, ncorner;

	or = r;
	corner = whichcorner(r, mc->xy);
	setcursornormal(corners[corner]);

	do{
		drawgetrect(r, 1);
		readmouse(mc);
		drawgetrect(r, 0);
		nr = canonrect(changerect(r, corner, mc->xy));
		if(goodrect(nr))
			r = nr;
		ncorner = whichcorner(r, mc->xy);
		/* can switch from edge to corner, but not vice versa */
		if(ncorner != corner && ncorner != 4 && (corner|~ncorner) & 1){
			corner = ncorner;
			setcursornormal(corners[corner]);
		}
	}while(mc->buttons == but);

	if(mc->buttons){
		drainmouse(mctl, nil);
		return or;
	}

	setcursornormal(nil);
	return r;
}

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(Window *w, int btn)
{
	if(w == nil)
		w = clickwindow(btn, mctl);
	if(w == nil)
		setcursoroverride(nil, FALSE);
	else{
		Rectangle r = dragrect(btn, w->frame->r, mctl);
		if((Dx(r) > 0 || Dy(r) > 0) && !eqrect(r, w->frame->r)){
			wmove(w, r.min);
			wfocus(w);
			flushimage(display, 1);
		}
	}
}

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

void
bandresize(Window *w)
{
	Rectangle r;
	r = bandrect(w->frame->r, mctl->buttons, mctl);
	if(!eqrect(r, w->frame->r)){
		wresize(w, r);
		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->frame->r) == 0 || Dy(t->frame->r) == 0 || rectXrect(r, t->frame->r) == 0)
			continue;
		if(r.min.y < t->frame->r.min.y)
			if(!obscured(w, Rect(r.min.x, r.min.y, r.max.x, t->frame->r.min.y), t))
				return 0;
		if(r.min.x < t->frame->r.min.x)
			if(!obscured(w, Rect(r.min.x, r.min.y, t->frame->r.min.x, r.max.y), t))
				return 0;
		if(r.max.y > t->frame->r.max.y)
			if(!obscured(w, Rect(r.min.x, t->frame->r.max.y, r.max.x, r.max.y), t))
				return 0;
		if(r.max.x > t->frame->r.max.x)
			if(!obscured(w, Rect(t->frame->r.max.x, r.min.y, r.max.x, r.max.y), t))
				return 0;
		return 1;
	}
	return 0;
}

/* Check that newly created window will be of manageable size */
int
goodrect(Rectangle r)
{
	if(badrect(r) || !eqrect(canonrect(r), r))
		return 0;
	/* reasonable sizes only please */
	if(Dx(r) > BIG*Dx(screen->r))
		return 0;
	if(Dy(r) > BIG*Dy(screen->r))
		return 0;
	/*
	 * the height has to be big enough to fit one line of text.
	 * that includes the border on each side with an extra pixel
	 * so that the text is still drawn
	 */
	if(Dx(r) < 100 || Dy(r) < 2*(bordersz+1)+font->height)
		return 0;
//TODO(vdesk) this changes
	/* window must be on screen */
	if(!rectXrect(screen->r, r))
		return 0;
	/* must have some screen and border visible so we can move it out of the way */
	if(rectinrect(screen->r, insetrect(r, bordersz)))
		return 0;
	return 1;
}

/* Rectangle for new window */
Rectangle
newrect(void)
{
	static int i = 0;
	int minx, miny, dx, dy;

	dx = min(600, Dx(screen->r) - 2*bordersz);
	dy = min(400, Dy(screen->r) - 2*bordersz);
	minx = 32 + 16*i;
	miny = 32 + 16*i;
	i++;
	i %= 10;

	return Rect(minx, miny, minx+dx, miny+dy);
}


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

	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);
		break;
	case Paste:
		xpaste(x);
		break;
	case Snarf:
		xsnarf(x);
		break;
	case Plumb:
		if(xplumb(x, "lola", 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)
{
	enum {
		New,
		Reshape,
		Move,
		Delete,
		Hide,
		Hidden
	};
	static char *str[Hidden+1 + MAXWINDOWS] = {
		"New",
		"Resize",
		"Move",
		"Delete",
		"Hide",
		nil
	};
	static Menu menu = { str };

	static Window *hidden[MAXWINDOWS];
	int nhidden;
	Window *w, *t;
	int i, sel;

	nhidden = 0;
	for(i = 0; i < nwindows; i++){
		t = windows[i];
		if(!rectXrect(screen->r, t->frame->r))
			continue;
		if(t->hidden || obscured(t, t->frame->r, t->higher)){
			hidden[nhidden] = windows[i];
			str[nhidden+Hidden] = windows[i]->label;
			nhidden++;	
		}
	}
	str[nhidden+Hidden] = nil;

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

void
btn13menu(void)
{
	enum {
		RefreshScreen,
		Exit
	};
	static char *str[] = {
		"Refresh",
		"Exit",
		nil
	};
	static Menu menu = { str };

	switch(menuhit(3, mctl, &menu, wscreen)){
	case RefreshScreen:
		refresh();
		break;
	case Exit:
		killprocs();
		threadexitsall(nil);
	}
}

void
btn12menu(void)
{
	int dx, dy, i, j;

	dx = Dx(screen->r);
	dy = Dy(screen->r);
	i = screenoff.x/dx;
	j = screenoff.y/dy;
	Point ssel = dmenuhit(2, mctl, ndeskx, ndesky, Pt(i,j));
	if(ssel.x >= 0 && ssel.y >= 0 && 
	   (ssel.x*dx != screenoff.x || ssel.y*dy != screenoff.y))
		screenoffset(ssel.x*dx, ssel.y*dy);
}

void
mthread(void*)
{
	Window *w;
	Channel *wc;

	threadsetname("mousethread");
	enum { Amouse, Apick, NALT };
	Alt alts[NALT+1] = {
		[Amouse]	{.c = mctl->c, .v = &mctl->Mouse, .op = CHANRCV},
		[Apick]		{.c = pickchan, .v = &wc, .op = CHANRCV},
		[NALT]		{.op = CHANEND},
	};
	for(;;){
		// normally done in readmouse
		Display *d = mctl->image->display;
		if(d->bufp > d->buf)
			flushimage(d, 1);
		switch(alt(alts)){
		case Apick:
			sendp(wc, pick());
			break;
		case Amouse:
			w = wpointto(mctl->xy);
			cursorwin = w;
again:
			if(w == nil){
				/* background */
				setcursornormal(nil);
				while(mctl->buttons & 1){
					if(mctl->buttons & 2)
						btn12menu();
					else if(mctl->buttons & 4)
						btn13menu();
					readmouse(mctl);
				}
				if(mctl->buttons & 4)
					btn3menu();
			}else if(!ptinrect(mctl->xy, w->contrect)){
				/* decoration */
				if(!w->noborder &&
				   !ptinrect(mctl->xy, insetrect(w->frame->r, bordersz))){
					/* border */
					setcursornormal(corners[whichcorner(w->frame->r, mctl->xy)]);
					if(mctl->buttons & 7){
						wraise(w);
						wfocus(w);
						if(mctl->buttons & 4)
							grab(w, 3);
						if(mctl->buttons & 3)
							bandresize(w);
					}
				}else{
					/* title bar */
					setcursornormal(nil);
					wtitlectl(w);
				}
			}else if(w != focused){
				/* inactive window */
				wsetcursor(w);
				if(mctl->buttons & 7 ||
				   mctl->buttons & (8|16) && focused->mouseopen){
					wraise(w);
					wfocus(w);
					if(mctl->buttons & 1)
						drainmouse(mctl, nil);
					else
						goto again;
				}
			}else if(!w->mouseopen){
				/* active text window */
				wsetcursor(w);
				if(mctl->buttons && topwin != w)
					wraise(w);
				if(mctl->buttons & (1|8|16) || ptinrect(mctl->xy, w->text.scrollr))
					drainmouse(mctl, w);
				if(mctl->buttons & 2){
					incref(w);
					btn2menu(w);
					wrelease(w);
				}
				if(mctl->buttons & 4)
					btn3menu();
			}else{
				/* active graphics window */
				wsetcursor(w);
				drainmouse(mctl, w);
			}
		}
	}
}

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;

		freeimage(fakebg);
		freescreen(wscreen);
		wscreen = allocscreen(screen, background, 0);
		fakebg = allocwindow(wscreen, screen->r, Refbackup, DNofill);
		draw(fakebg, fakebg->r, background, nil, ZP);

		delta = subpt(nr.min, or.min);
		for(w = bottomwin; w; w = w->higher){
			if(w->maximized){
				wrestore(w);
				wresize(w, rectaddpt(w->frame->r, delta));
				wmaximize(w);
			}else
				wresize(w, rectaddpt(w->frame->r, delta));
		}

		flushimage(display, 1);
	}
}

void
refresh(void)
{
	Window *w;

	draw(fakebg, fakebg->r, background, nil, ZP);
	for(w = bottomwin; w; w = w->higher){
		if(w->maximized){
			wrestore(w);
			wresize(w, w->frame->r);
			wmaximize(w);
		}else
			wresize(w, w->frame->r);
	}
}

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

Channel *opentap;	/* open fromtap or totap */
Channel *closetap;	/* close fromtap or totap */
Channel *fromtap;	/* input from kbd tap program to window */
Channel *totap;		/* our keyboard input to tap program */

void
keyboardtap(void*)
{
	char *s, *z;
	Channel *fschan, *chan;
	int n;
	Stringpair pair;
	Window *cur, *prev;
	Queue tapq;

	threadsetname("keyboardtap");

	fschan = chancreate(sizeof(Stringpair), 0);
	enum { Akbd, Afromtap, Atotap, Aopen, Aclose,  NALT };
	Alt alts[NALT+1] = {
		[Akbd]		{.c = kbctl->c, .v = &s, .op = CHANRCV},
		[Afromtap]	{.c = nil, .v = &s, .op = CHANNOP},
		[Atotap]	{.c = nil, .v = &fschan, .op = CHANNOP},
		[Aopen]		{.c = opentap, .v = &chan, .op = CHANRCV},
		[Aclose]	{.c = closetap, .v = &chan, .op = CHANRCV},
		[NALT]		{.op = CHANEND},
	};

	memset(&tapq, 0, sizeof(tapq));
	cur = nil;
	for(;;){
		if(alts[Atotap].c && !qempty(&tapq))
			alts[Atotap].op = CHANSND;
		else
			alts[Atotap].op = CHANNOP;
		switch(alt(alts)){
		case Akbd:
			/* from keyboard to tap or to window */
			if(*s == 'k' || *s == 'K'){
				shiftdown = utfrune(s+1, Kshift) != nil;
				ctldown = utfrune(s+1, Kctl) != nil;
			}
			prev = cur;
			cur = focused;
			if(totap){
				if(cur != prev && cur){
					/* notify tap of focus change */
					z = smprint("z%d", cur->id);
					if(!qadd(&tapq, z))
						free(z);
				}
				/* send to tap */
				if(qadd(&tapq, s))
					break;
				/* tap is wedged, send directly instead */
			}
			if(cur)
				sendp(cur->kbd, s);
			else
				free(s);
			break;

		case Afromtap:
			/* from tap to window */
			if(cur && cur == focused)
				sendp(cur->kbd, s);
			else
				free(s);
			break;

		case Atotap:
			/* send queued up messages */
			recv(fschan, &pair);
			s = qget(&tapq);
			n = strlen(s)+1;
			pair.ns = min(n, pair.ns);
			memmove(pair.s, s, pair.ns);
			free(s);
			send(fschan, &pair);
			break;

		case Aopen:
			if(chan == fromtap){
				alts[Afromtap].c = fromtap;
				alts[Afromtap].op = CHANRCV;
			}
			if(chan == totap)
				alts[Atotap].c = totap;
			break;

		case Aclose:
			if(chan == fromtap){
				fromtap = nil;
				alts[Afromtap].c = nil;
				alts[Afromtap].op = CHANNOP;
				// TODO: empty chan
			}
			if(chan == totap){
				totap = nil;
				alts[Atotap].c = nil;
				alts[Atotap].op = CHANNOP;
				while(!qempty(&tapq))
					free(qget(&tapq));
			}
			chanfree(chan);
			break;
		}
	}
}

void
initcmd(void *arg)
{
	char *cmd;
	char *wsys;
	int fd;

	cmd = arg;
	rfork(RFENVG|RFFDG|RFNOTEG|RFNAMEG);
	wsys = getenv("wsys");
	fd = open(wsys, ORDWR);
	if(fd < 0)
		fprint(2, "lola: failed to open wsys: %r\n");
	if(mount(fd, -1, "/mnt/wsys", MREPL, "none") < 0)
		fprint(2, "lola: failed to mount wsys: %r\n");
	if(bind("/mnt/wsys", "/dev/", MBEFORE) < 0)
		fprint(2, "lola: failed to bind wsys: %r\n");
	free(wsys);
	close(fd);
	procexecl(nil, "/bin/rc", "rc", "-c", cmd, nil);
	fprint(2, "lola: exec failed: %r\n");
	exits("exec");
}

void
usage(void)
{
	fprint(2, "usage: lola [-i initcmd] [-s] [-t]\n");
	exits("usage");
}

void
threadmain(int argc, char *argv[])
{
	char *initstr;
	char buf[256];
if(strcmp(argv[0]+1, ".out") == 0){
rfork(RFENVG);
newwindow("-dx 1280 -dy 800");
scrolling = TRUE;
notitle = FALSE;
oknotes[nelem(oknotes)-2] = "interrupt";
}

	initstr = nil;
	ARGBEGIN{
	case 'i':
		initstr = EARGF(usage());
		break;
	case 's':
		scrolling = TRUE;
		break;
	case 't':
		notitle = TRUE;
		break;
	default:
		usage();
	}ARGEND

	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");
	opentap = chancreate(sizeof(Channel*), 0);
	closetap = chancreate(sizeof(Channel*), 0);

	pickchan = chancreate(sizeof(Channel*), 0);

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

	initdata();

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

	timerinit();


	threadcreate(mthread, nil, mainstacksize);
	threadcreate(resthread, nil, mainstacksize);
	threadcreate(keyboardtap, nil, mainstacksize);

	flushimage(display, 1);

	startfs();

	if(initstr)
		proccreate(initcmd, initstr, mainstacksize);

	threadnotify(notehandler, 1);
}