ref: 8b40d63248fa6b3895203964adaf95d600956113
dir: /main.c/
#include "inc.h" typedef struct RKeyboardctl RKeyboardctl; struct RKeyboardctl { Keyboardctl; int kbdfd; }; RKeyboardctl *kbctl; Mousectl *mctl; int scrolling = 1; char *startdir; int shiftdown; int gotscreen; int servekbd; 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; } 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) { if(w == nil) 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(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->img->r, mctl->buttons, mctl); if(!eqrect(r, w->img->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->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; } /* 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*(Borderwidth+1)+font->height) return 0; /* 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, Borderwidth))) 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*Borderwidth); dy = min(400, Dy(screen->r) - 2*Borderwidth); minx = 32 + 16*i; miny = 32 + 16*i; i++; i %= 10; return Rect(minx, miny, minx+dx, miny+dy); } 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(nil); break; case Delete: w = pick(); if(w) wdelete(w); 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; threadsetname("mousethread"); while(readmouse(mctl) != -1){ w = wpointto(mctl->xy); cursorwin = w; again: if(w == nil){ setcursornormal(nil); if(mctl->buttons & 4) btn3menu(); }else if(!ptinrect(mctl->xy, w->contrect)){ /* border */ setcursornormal(corners[whichcorner(w->img->r, mctl->xy)]); if(mctl->buttons & 7){ wraise(w); wfocus(w); if(mctl->buttons & 4) grab(w); if(mctl->buttons & 3) bandresize(w); } }else if(w != focused){ 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){ wsetcursor(w); 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{ wsetcursor(w); drainmouse(mctl, w->mc.c); } } } 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; } 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; initdata(); 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 }