ref: 4ae95680d7cf09182aa783cc934ca6c46e56a8b5
dir: /wind.c/
#include "inc.h" Window *bottomwin, *topwin; Window *windows[MAXWINDOWS]; int nwindows; Window *focused, *cursorwin; Point screenoff; 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 wmaximize(Window *w) { w->maximized = 1; w->noborder |= 2; w->origrect = w->frame->r; wresize(w, screen->r); } void wrestore(Window *w) { w->maximized = 0; w->noborder &= ~2; wresize(w, w->origrect); } void wcalcrects(Window *w, Rectangle r) { w->contrect = r; if(!w->noborder) w->contrect = insetrect(w->contrect, bordersz); if(!w->notitle){ w->titlerect = w->contrect; w->titlerect.max.y = w->titlerect.min.y + titlesz; w->contrect.min.y += titlesz; }else w->titlerect = ZR; 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 wsetcolors(Window *w) { int c = w->holdmode ? w == focused ? HOLDTEXT : PALEHOLDTEXT : w == focused ? TEXT : PALETEXT; w->text.cols[TEXT] = colors[c]; } static void wfreeimages(Window *w) { if(w->frame == nil) return; Screen *s = w->content->screen; freeimage(w->content); w->content = nil; freescreen(s); freeimage(w->frame); w->frame = nil; w->mc.image = nil; } static void wsetsize(Window *w, Rectangle r) { Rectangle hr; Screen *s; wfreeimages(w); if(w->hidden){ hr = rectaddpt(r, subpt(screen->r.max, r.min)); w->frame = allocwindow(wscreen, hr, Refbackup, DNofill); originwindow(w->frame, r.min, hr.min); }else w->frame = allocwindow(wscreen, r, Refbackup, DNofill); w->mc.image = w->frame; s = allocscreen(w->frame, colors[BACK], 0); assert(s); wcalcrects(w, r); w->content = allocwindow(s, w->contrect, Refnone, DNofill); assert(w->content); draw(w->frame, w->frame->r, colors[BACK], nil, ZP); xinit(&w->text, w->textr, w->scrollr, font, w->content, 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; w->notitle = notitle; // TODO: argument? wsetsize(w, r); wdecor(w); wlistpushfront(w); // TODO: could be more graceful here assert(nwindows < MAXWINDOWS); windows[nwindows++] = w; w->mc.c = chancreate(sizeof(Mouse), 16); 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); } /* logically and visually close the window. * struct and thread will stick around until all references are gone. * safe to call multiple times. */ 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; } wfreeimages(w); flushimage(display, 1); } int inwinthread(Window *w) { return w->threadname == threadgetname(); } /* decrement reference, close window once all references gone. */ void wrelease(Window *w) { if(decref(w) == 0){ /* increment ref count temporarily * so win thread doesn't exit too early */ incref(w); wunfocus(w); wclose(w); if(!inwinthread(w)) wsendmsg(w, Wakeup); decref(w); }else assert(w->ref > 0); } void wsendmsg(Window *w, int type) { assert(!inwinthread(w)); 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->frame->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); wdecor(w); } void wsetname(Window *w) { int i, n; char err[ERRMAX]; n = snprint(w->name, sizeof(w->name)-2, "%s.%d.%d", "noborder", w->id, w->namecount++); for(i='A'; i<='Z'; i++){ if(nameimage(w->content, 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->frame); } 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(1 || w->mouseopen){ delta = subpt(pos, w->frame->r.min); wresize(w, rectaddpt(w->frame->r, delta)); }else{ originwindow(w->frame, pos, w->hidden ? screen->r.max : pos); if(w != topwin && !w->hidden) worder(); wcalcrects(w, w->frame->r); xsetrects(&w->text, w->textr, w->scrollr); wsendmsg(w, Moved); } } void wraise(Window *w) { wlistremove(w); wlistpushfront(w); topwindow(w->frame); flushimage(display, 1); } void wlower(Window *w) { wlistremove(w); wlistpushback(w); bottomwindow(w->frame); bottomwindow(fakebg); flushimage(display, 1); } void wfocuschanged(Window *w) { if(w == nil) return; w->wctlready = TRUE; wrepaint(w); if(!inwinthread(w)) wsendmsg(w, Wakeup); } void wfocus(Window *w) { Window *prev; if(w == focused) return; prev = focused; focused = w; if(prev){ /* release keys (if possible) */ char *s = estrdup("K"); if(nbsendp(prev->kbd, s) != 1) free(s); /* release mouse buttons */ if(prev->mc.buttons){ prev->mc.buttons = 0; prev->mq.counter++; } } wfocuschanged(prev); wfocuschanged(focused); } /* Take away focus but also get rid of the window visually. * For hiding/deleting */ void wunfocus(Window *w) { if(!w->deleted) originwindow(w->frame, w->frame->r.min, screen->r.max); if(w == focused) wfocus(nil); } int whide(Window *w) { if(w->hidden) return -1; incref(w); wunfocus(w); w->hidden = TRUE; w->wctlready = TRUE; 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->frame, w->frame->r.min, w->frame->r.min); wraise(w); wfocus(w); wrelease(w); return 1; } void wsethold(Window *w, int hold) { int switched; if(hold) switched = w->holdmode++ == 0; else switched = --w->holdmode == 0; if(switched){ 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, FALSE); 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); } void winctl(Window *w, int type) { Text *x; int i; x = &w->text; switch(type){ 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: /* take over window again */ if(w->deleted) break; draw(w->content, w->content->r, x->cols[BACK], nil, ZP); wdecor(w); xfullredraw(&w->text); 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; } } 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 && !qempty(&w->kq)) 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(!qadd(&w->kq, s)) free(s); if(!w->kbdopen) while(!qempty(&w->kq)){ s = qget(&w->kq); if(*s == 'c'){ chartorune(&r, s+1); if(r) wkeyctl(w, r); } free(s); } break; case AKbdRead: recv(fsc, &pair); nb = 0; while(!qempty(&w->kq)){ s = w->kq.q[w->kq.ri]; i = strlen(s)+1; if(nb+i > pair.ns) break; qget(&w->kq); 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->frame->r.min.x, w->frame->r.min.y, w->frame->r.max.x, w->frame->r.max.y, w == focused ? "current" : "notcurrent", w->hidden ? "hidden" : "visible"); send(fsc, &pair); break; case ACtl: winctl(w, cm); break; case AComplete: if(w->frame){ 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); /* window is gone, clean up and exit thread */ if(w->ref == 0){ wfree(w); chanfree(fsc); free(cnv.buf); return; } } } 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() */ } /* remove extra ref hanging from creation. * not in main proc here, so be careful with wrelease! */ assert(w->ref > 1); wrelease(w); 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; } void screenoffset(int offx, int offy) { Window *w; Point off, delta; off = Pt(offx, offy); delta = subpt(off, screenoff); screenoff = off; for(w = bottomwin; w; w = w->higher){ if(w->sticky){ /* Don't move but cause resize event because * program may want some kind of notification */ wmove(w, w->frame->r.min); continue; } if(w->maximized){ wrestore(w); wmove(w, subpt(w->frame->r.min, delta)); wmaximize(w); }else wmove(w, subpt(w->frame->r.min, delta)); } flushimage(display, 1); }