ref: ee60859199f151377a32b6932bde88cc6fe6007a
dir: /main.c/
#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); } Rectangle newrect(void) { static int i = 0; int minx, miny, dx, dy; // dx = min(600, Dx(screen->r) - 2*Borderwidth); // dy = min(400, Dy(screen->r) - 2*Borderwidth); dx = 600; dy = 400; minx = 32 + 16*i; miny = 32 + 16*i; i++; i %= 10; return Rect(minx, miny, minx+dx, miny+dy); } 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; Channel *cpid; void *args[5]; int pid; w = wcreate(r); assert(w); w->scrolling = scrolling; cpid = chancreate(sizeof(int), 0); assert(cpid); args[0] = w; args[1] = cpid; args[2] = "/bin/rc"; args[3] = rcargv; args[4] = nil; proccreate(winshell, args, mainstacksize); pid = recvul(cpid); chanfree(cpid); if(pid == 0){ print("proc create failed\n"); return nil; } wsetpid(w, pid, 1); wsetname(w); 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, ZR, nil); } 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, ZR, nil); 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; 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 }