ref: 1049552fb14efa811ca879b35244b8ea684995c1
author: aap <aap@papnet.eu>
date: Fri Jan 27 15:40:05 EST 2023
first commit
--- /dev/null
+++ b/TODO
@@ -1,0 +1,25 @@
+rethink resizing and repainting
+implement all Qids
+ wctl
+ tap
+tap
+border resize/move
+move mouse
+write text
+wctl
+release keys and buttons when unfocused
+...
+
+ideas:
+ case-insensitive 'look'
+ virtual screen (like fvwm)
+ cursor movement
+ decorations (at least make them possible)
+ tabbed window
+
+problems:
+ Borderwidth hardcoded in gengetwindow
+ originwindow doesn't work with gengetwindow
+ non-origin screen breaks samterm scrollbars
+ raw mode where?
+ initkeyboard with /dev/kbd support (also fix leaks in old code)
--- /dev/null
+++ b/fs.c
@@ -1,0 +1,868 @@
+#include "inc.h"
+
+enum {
+ Qroot,
+ Qwsys,
+
+ Qcons,
+ Qconsctl,
+ Qcursor,
+ Qwinid,
+ Qwinname,
+ Qlabel,
+ Qkbd,
+ Qmouse,
+ Qscreen,
+ Qsnarf,
+ Qtext,
+ Qwdir,
+// Qwctl,
+ Qwindow,
+// Qtap, //
+
+ NQids
+};
+
+typedef struct Dirent Dirent;
+struct Dirent
+{
+ int path;
+ int type;
+ char *name;
+};
+
+Dirent dirents[] = {
+ Qroot, QTDIR, ".",
+ Qwsys, QTDIR, "wsys",
+ Qwinid, QTFILE, "winid",
+ Qwinname, QTFILE, "winname",
+ Qwdir, QTFILE, "wdir",
+ Qlabel, QTFILE, "label",
+ Qsnarf, QTFILE, "snarf",
+ Qtext, QTFILE, "text",
+ Qcons, QTFILE, "cons",
+ Qconsctl, QTFILE, "consctl",
+ Qkbd, QTFILE, "kbd",
+ Qmouse, QTFILE, "mouse",
+ Qcursor, QTFILE, "cursor",
+ Qscreen, QTFILE, "screen",
+ Qwindow, QTFILE, "window",
+};
+
+char Eperm[] = "permission denied";
+char Eexist[] = "file does not exist";
+char Enotdir[] = "not a directory";
+char Ebadfcall[] = "bad fcall type";
+char Eoffset[] = "illegal offset";
+char Enomem[] = "out of memory";
+
+char Eflush[] = "interrupted";
+char Einuse[] = "file in use";
+char Edeleted[] = "window deleted";
+char Etooshort[] = "buffer too small";
+char Eshort[] = "short i/o request";
+char Elong[] = "snarf buffer too long";
+char Eunkid[] = "unknown id in attach";
+char Ebadrect[] = "bad rectangle in attach";
+char Ewindow[] = "cannot make window";
+char Enowindow[] = "window has no image";
+char Ebadmouse[] = "bad format on /dev/mouse";
+
+/* Extension of a Fid, fid->aux */
+typedef struct Xfid Xfid;
+struct Xfid
+{
+ Fid *fid;
+ Channel *xc;
+ Channel *flush; // cancel read/write
+ Window *w;
+ RuneConvBuf cnv;
+ Xfid *next;
+};
+static Xfid *xfidfree;
+
+typedef struct XfidMsg XfidMsg;
+struct XfidMsg
+{
+ Req *r;
+ void (*f)(Req*);
+};
+
+static void
+xfidthread(void *a)
+{
+ Xfid *xf = a;
+ XfidMsg xm;
+
+ for(;;){
+ recv(xf->xc, &xm);
+ (*xm.f)(xm.r);
+ }
+}
+
+static void
+toxfid(Req *r, void (*f)(Req*))
+{
+ Xfid *xf;
+ XfidMsg xm;
+
+ xf = r->fid->aux;
+ xm.r = r;
+ xm.f = f;
+ send(xf->xc, &xm);
+}
+
+static Xfid*
+getxfid(Fid *fid, Window *w)
+{
+ Xfid *xf;
+ if(xfidfree){
+ xf = xfidfree;
+ xfidfree = xf->next;
+ }else{
+ xf = emalloc(sizeof(Xfid));
+ xf->xc = chancreate(sizeof(XfidMsg), 0);
+ xf->flush = chancreate(sizeof(int), 0);
+ threadcreate(xfidthread, xf, mainstacksize);
+ }
+ memset(&xf->cnv, 0, sizeof(xf->cnv));
+ xf->fid = fid;
+ xf->w = w;
+ incref(w);
+ xf->next = nil;
+ return xf;
+}
+
+static void
+freexfid(Xfid *xf)
+{
+ wrelease(xf->w);
+ free(xf->cnv.buf);
+ xf->fid = nil;
+ xf->w = nil;
+ xf->next = xfidfree;
+ xfidfree = xf;
+}
+
+#define QID(w, q) ((w)<<8|(q))
+#define QWIN(q) ((q)>>8)
+#define QFILE(q) ((q)&0xFF)
+
+static void
+fsattach(Req *r)
+{
+ Window *w;
+ char *end;
+ int id;
+
+ w = nil;
+ if(strcmp(r->ifcall.aname, "new") == 0){
+ w = wcreate(rectaddpt(newrect(), screen->r.min));
+wsetpid(w, -1, 1);
+wsetname(w);
+ flushimage(display, 1);
+ decref(w); /* don't delete, xfid will take it */
+ }else if(id = strtol(r->ifcall.aname, &end, 10), *end == '\0'){
+ w = wfind(id);
+ }
+ if(w == nil){
+ respond(r, "bad attach name");
+ return;
+ }
+
+ r->fid->aux = getxfid(r->fid, w);
+ r->fid->qid = (Qid){QID(w->id,Qroot),0,QTDIR};
+ r->ofcall.qid = r->fid->qid;
+ respond(r, nil);
+}
+
+static char*
+fsclone(Fid *fid, Fid *newfid)
+{
+ Xfid *xf;
+
+ xf = fid->aux;
+ if(xf)
+ newfid->aux = getxfid(newfid, xf->w);
+ return nil;
+}
+
+int
+skipfile(char *name)
+{
+ return gotscreen && strcmp(name, "screen") == 0 ||
+ snarffd >= 0 && strcmp(name, "snarf") == 0 ||
+ !servekbd && strcmp(name, "kbd") == 0;
+}
+
+
+static char*
+fswalk1(Fid *fid, char *name, Qid *qid)
+{
+ int i;
+ Dirent *d;
+ Xfid *xf;
+ Window *w;
+ int dir;
+
+ xf = fid->aux;
+ w = xf->w;
+ dir = QFILE(fid->qid.path);
+ if(dir == Qroot){
+ if(strcmp(name, "..") == 0){
+ /* This sucks because we don't know which window we came from
+ * error out for now */
+ return "vorwärts immer, rückwärts nimmer";
+ }
+ for(i = 0; i < nelem(dirents); i++){
+ d = &dirents[i];
+ if(!skipfile(d->name) && strcmp(name, d->name) == 0){
+ fid->qid = (Qid){QID(w->id,d->path), 0, d->type};
+ *qid = fid->qid;
+ return nil;
+ }
+ }
+ }else if(dir == Qwsys){
+ char *end;
+ int id;
+ if(strcmp(name, "..") == 0){
+ fid->qid = (Qid){QID(w->id,Qroot), 0, QTDIR};
+ *qid = fid->qid;
+ return nil;
+ }
+ if(id = strtol(name, &end, 10), *end == '\0'){
+ w = wfind(id);
+ if(w){
+ incref(w);
+ wrelease(xf->w);
+ xf->w = w;
+ fid->qid = (Qid){QID(w->id,Qroot), 0, QTDIR};
+ *qid = fid->qid;
+ return nil;
+ }
+ }
+ }
+ return "no such file";
+}
+
+static int
+genrootdir(int n, Dir *d, void *a)
+{
+ Window *w = a;
+ int i;
+
+ n++; /* -1 is root dir */
+ i = 0;
+ while(n--){
+ i++;
+ if(i >= nelem(dirents))
+ return -1;
+ /* we know the last file is never skipped */
+ while(skipfile(dirents[i].name)) i++;
+ }
+
+ d->atime = time(nil);
+ d->mtime = d->atime;
+ d->uid = estrdup9p(getuser());
+ d->gid = estrdup9p(d->uid);
+ d->muid = estrdup9p(d->uid);
+ d->qid = (Qid){QID(w->id,dirents[i].path), 0, dirents[i].type};
+ d->mode = 0664;
+ if(dirents[i].type & QTDIR)
+ d->mode |= 0111;
+ d->name = estrdup9p(dirents[i].name);
+ d->length = 0;
+ return 0;
+}
+
+static int
+genwsysdir(int n, Dir *d, void*)
+{
+ d->atime = time(nil);
+ d->mtime = d->atime;
+ d->uid = estrdup9p(getuser());
+ d->gid = estrdup9p(d->uid);
+ d->muid = estrdup9p(d->uid);
+
+ if(n == -1){
+ d->qid = (Qid){Qwsys, 0, QTDIR};
+ d->mode = 0775;
+ d->name = estrdup9p("wsys");
+ d->length = 0;
+ return 0;
+ }
+ if(n < nwindows){
+ int id = windows[n]->id;
+ d->qid = (Qid){QID(id,Qroot), 0, QTDIR};
+ d->mode = 0775;
+ d->name = smprint("%d", id);
+ d->length = 0;
+ return 0;
+ }
+
+ return -1;
+}
+
+static int ntsnarf;
+static char *tsnarf;
+
+static void
+xfopen(Req *r)
+{
+ Xfid *xf;
+ Window *w;
+
+ xf = r->fid->aux;
+ w = xf->w;
+
+ if(w == nil || w->deleted){
+ respond(r, Edeleted);
+ return;
+ }
+
+ switch(QFILE(xf->fid->qid.path)){
+ case Qsnarf:
+ r->ifcall.mode &= ~OTRUNC;
+ if(r->ifcall.mode==ORDWR || r->ifcall.mode==OWRITE)
+ ntsnarf = 0;
+ break;
+
+ case Qconsctl:
+ if(w->consctlopen){
+ respond(r, Einuse);
+ return;
+ }
+ w->consctlopen = TRUE;
+ break;
+
+ case Qkbd:
+ if(w->kbdopen){
+ respond(r, Einuse);
+ return;
+ }
+ w->kbdopen = TRUE;
+ break;
+
+ case Qmouse:
+ if(w->mouseopen){
+ respond(r, Einuse);
+ return;
+ }
+// TODO: copy comment from rio
+ w->resized = FALSE;
+ w->mouseopen = TRUE;
+ break;
+ }
+
+ respond(r, nil);
+}
+
+static void
+xfclose(Xfid *xf)
+{
+ Window *w;
+ Text *x;
+
+ w = xf->w;
+ x = &w->text;
+
+ switch(QFILE(xf->fid->qid.path)){
+ /* replace snarf buffer when /dev/snarf is closed */
+ case Qsnarf:
+ if(xf->fid->omode==ORDWR || xf->fid->omode==OWRITE){
+ setsnarf(tsnarf, ntsnarf);
+ ntsnarf = 0;
+ }
+ break;
+
+ case Qconsctl:
+ if(x->rawmode){
+ x->rawmode = 0;
+ wsendmsg(w, Rawoff, ZR, nil);
+ }
+ if(w->holdmode > 0){
+ w->holdmode = 1;
+ wsendmsg(w, Holdoff, ZR, nil);
+ }
+ w->consctlopen = FALSE;
+ break;
+
+ case Qkbd:
+ w->kbdopen = FALSE;
+ break;
+
+ case Qmouse:
+ w->mouseopen = FALSE;
+ w->resized = FALSE;
+ wsendmsg(w, Refresh, ZR, nil);
+ break;
+
+ case Qcursor:
+ w->cursorp = nil;
+ wsetcursor(w);
+ break;
+ }
+}
+
+static int
+readimgdata(Image *i, char *t, Rectangle r, int offset, int n)
+{
+ int ww, oo, y, m;
+ uchar *tt;
+
+ ww = bytesperline(r, i->depth);
+ r.min.y += offset/ww;
+ if(r.min.y >= r.max.y)
+ return 0;
+ y = r.min.y + (n + ww-1)/ww;
+ if(y < r.max.y)
+ r.max.y = y;
+ m = ww * Dy(r);
+ oo = offset % ww;
+ if(oo == 0 && n >= m)
+ return unloadimage(i, r, (uchar*)t, n);
+ if((tt = malloc(m)) == nil)
+ return -1;
+ m = unloadimage(i, r, tt, m) - oo;
+ if(m > 0){
+ if(n < m) m = n;
+ memmove(t, tt + oo, m);
+ }
+ free(tt);
+ return m;
+}
+
+/* Fill request from image,
+ * returns only either header or data */
+char*
+readimg(Req *r, Image *img)
+{
+ char *head;
+ char cbuf[30];
+ Rectangle rect;
+ int n;
+
+ rect = img->r;
+ if(r->ifcall.offset < 5*12){
+ head = smprint("%11s %11d %11d %11d %11d ",
+ chantostr(cbuf, img->chan),
+ rect.min.x, rect.min.y, rect.max.x, rect.max.y);
+ readstr(r, head);
+ free(head);
+ }else{
+ /* count is unsigned, so check with n */
+ n = readimgdata(img, r->ofcall.data, rect, r->ifcall.offset-5*12, r->ifcall.count);
+ if(n < 0)
+ return Enomem;
+ r->ofcall.count = n;
+ }
+ return nil;
+}
+
+static char*
+readblocking(Req *r, Channel *readchan)
+{
+ Xfid *xf;
+ Window *w;
+ Channel *chan;
+ Stringpair pair;
+ enum { Adata, Agone, Aflush, NALT };
+ Alt alts[NALT+1];
+
+ xf = r->fid->aux;
+ w = xf->w;
+
+ alts[Adata] = ALT(readchan, &chan, CHANRCV);
+ alts[Agone] = ALT(w->gone, nil, CHANRCV);
+ alts[Aflush] = ALT(xf->flush, nil, CHANRCV);
+ alts[NALT].op = CHANEND;
+ switch(alt(alts)){
+ case Adata:
+ pair.s = r->ofcall.data;
+ pair.ns = r->ifcall.count;
+ send(chan, &pair);
+ recv(chan, &pair);
+ r->ofcall.count = pair.ns;
+ return nil;
+ case Agone:
+ return Edeleted;
+ case Aflush:
+ return Eflush;
+ }
+ return nil; /* can't happen */
+}
+
+static void
+xfread(Req *r)
+{
+ Xfid *xf;
+ Window *w;
+ char *data;
+
+ xf = r->fid->aux;
+ w = xf->w;
+
+ if(w == nil || w->deleted){
+ respond(r, Edeleted);
+ return;
+ }
+
+ switch(QFILE(xf->fid->qid.path)){
+ case Qwinid:
+ data = smprint("%11d ", w->id);
+ readstr(r, data);
+ free(data);
+ break;
+ case Qwinname:
+ readstr(r, w->name);
+ break;
+ case Qlabel:
+ readstr(r, w->label);
+ break;
+ case Qsnarf:
+ data = smprint("%.*S", nsnarf, snarf);
+ readstr(r, data);
+ free(data);
+ break;
+ case Qtext:
+ data = smprint("%.*S", w->text.nr, w->text.r);
+ readstr(r, data);
+ free(data);
+ break;
+ case Qcons:
+ respond(r, readblocking(r, w->consread));
+ return;
+ case Qkbd:
+ respond(r, readblocking(r, w->kbdread));
+ return;
+ case Qmouse:
+ respond(r, readblocking(r, w->mouseread));
+ return;
+ case Qcursor:
+ respond(r, "cursor read not implemented");
+ return;
+ case Qscreen:
+ respond(r, readimg(r, screen));
+ return;
+ case Qwindow:
+ respond(r, readimg(r, w->img));
+ return;
+ default:
+ respond(r, "cannot read");
+ return;
+ }
+ respond(r, nil);
+}
+
+static void
+xfwrite(Req *r)
+{
+ Xfid *xf;
+ Window *w;
+ Text *x;
+ vlong offset;
+ u32int count;
+ char *data, *p;
+ Channel *kbd;
+ Stringpair pair;
+ enum { Adata, Agone, Aflush, NALT };
+ Alt alts[NALT+1];
+
+ xf = r->fid->aux;
+ w = xf->w;
+ x = &w->text;
+ offset = r->ifcall.offset;
+ count = r->ifcall.count;
+ data = r->ifcall.data;
+
+ if(w == nil || w->deleted){
+ respond(r, Edeleted);
+ return;
+ }
+ int f = QFILE(r->fid->qid.path);
+ switch(f){
+ case Qcons:
+ alts[Adata] = ALT(w->conswrite, &kbd, CHANRCV);
+ alts[Agone] = ALT(w->gone, nil, CHANRCV);
+ alts[Aflush] = ALT(xf->flush, nil, CHANRCV);
+ alts[NALT].op = CHANEND;
+ switch(alt(alts)){
+ case Adata:
+ cnvsize(&xf->cnv, count);
+ memmove(xf->cnv.buf+xf->cnv.n, data, count);
+ xf->cnv.n += count;
+ pair = b2r(&xf->cnv);
+ r->ofcall.count = r->ifcall.count;
+ send(kbd, &pair);
+ break;
+ case Agone:
+ respond(r, Edeleted);
+ return;
+ case Aflush:
+ respond(r, Eflush);
+ return;
+ }
+ break;
+
+ case Qconsctl:
+ if(strncmp(data, "holdon", 6) == 0){
+ wsendmsg(w, Holdon, ZR, nil);
+ break;
+ }
+ if(strncmp(data, "holdoff", 7) == 0){
+ wsendmsg(w, Holdoff, ZR, nil);
+ break;
+ }
+ if(strncmp(data, "rawon", 5) == 0){
+// TODO: apparently we turn of hold mode here
+ if(x->rawmode++ == 0)
+ wsendmsg(w, Rawon, ZR, nil);
+ break;
+ }
+ if(strncmp(data, "rawoff", 6) == 0){
+ if(--x->rawmode == 0)
+ wsendmsg(w, Rawoff, ZR, nil);
+ break;
+ }
+ respond(r, "unknown control message");
+ return;
+
+ case Qcursor:
+ if(count < 2*4+2*2*16)
+ w->cursorp = nil;
+ else{
+ w->cursor.offset.x = BGLONG(data+0*4);
+ w->cursor.offset.y = BGLONG(data+1*4);
+ memmove(w->cursor.clr, data+2*4, 2*2*16);
+ w->cursorp = &w->cursor;
+ }
+ cursor = (void*)(uintptr)~0; /* invalide cache */
+ wsetcursor(w);
+ break;
+
+ case Qlabel:
+ if(offset != 0){
+ respond(r, "non-zero offset writing label");
+ return;
+ }
+ w->label = realloc(w->label, count+1);
+ memmove(w->label, data, count);
+ w->label[count] = 0;
+ break;
+
+ case Qsnarf:
+ if(count == 0)
+ break;
+ /* always append only */
+ if(ntsnarf > MAXSNARF){ /* avoid thrashing when people cut huge text */
+ respond(r, Elong);
+ return;
+ }
+ p = realloc(tsnarf, ntsnarf+count);
+ if(p == nil){
+ respond(r, Enomem);
+ return;
+ }
+ tsnarf = p;
+ memmove(tsnarf+ntsnarf, data, count);
+ ntsnarf += count;
+ break;
+
+ case Qwdir:
+ if(count > 0 && data[count-1] == '\n')
+ data[--count] = '\0';
+ if(count == 0)
+ break;
+ /* assume data comes in a single write */
+ if(data[0] == '/')
+ p = smprint("%.*s", count, data);
+ else
+ p = smprint("%s/%.*s", w->dir, count, data);
+ if(p == nil){
+ respond(r, Enomem);
+ return;
+ }
+ free(w->dir);
+ w->dir = cleanname(p);
+ break;
+
+ default:
+ respond(r, "cannot write");
+ return;
+ }
+ respond(r, nil);
+}
+
+static void
+fsopen(Req *r)
+{
+ toxfid(r, xfopen);
+}
+
+static void
+freefid(Fid *fid)
+{
+ Xfid *xf;
+
+ xf = fid->aux;
+ if(xf){
+ xfclose(xf);
+ freexfid(xf);
+ }
+ fid->aux = nil;
+}
+
+static void
+fsread(Req *r)
+{
+ Xfid *xf;
+
+ if((r->fid->qid.type & QTDIR) == 0){
+ toxfid(r, xfread);
+ return;
+ }
+
+ switch(QFILE(r->fid->qid.path)){
+ case Qroot:
+ xf = r->fid->aux;
+ dirread9p(r, genrootdir, xf->w);
+ break;
+ case Qwsys:
+ dirread9p(r, genwsysdir, nil);
+ break;
+ }
+ respond(r, nil);
+}
+
+static void
+fswrite(Req *r)
+{
+ toxfid(r, xfwrite);
+}
+
+static void
+fsflush(Req *r)
+{
+ Req *or;
+ Xfid *xf;
+ int dummy = 0;
+
+ or = r->oldreq;
+ xf = or->fid->aux;
+ assert(xf);
+
+ /* TODO: not entirely sure this is right.
+ * is it possible no-one is listening? */
+ send(xf->flush, &dummy);
+ respond(r, nil);
+}
+
+static void
+fsstat(Req *r)
+{
+ Xfid *xf;
+ int f;
+
+ xf = r->fid->aux;
+ f = QFILE(r->fid->qid.path);
+ genrootdir(f-1, &r->d, xf->w);
+ respond(r, nil);
+}
+
+Srv fsys = {
+ .attach fsattach,
+ .open fsopen,
+ .read fsread,
+ .write fswrite,
+ .stat fsstat,
+ .flush fsflush,
+ .walk1 fswalk1,
+ .clone fsclone,
+ .destroyfid freefid,
+ nil
+};
+
+void
+post(char *name, int srvfd)
+{
+ char buf[80];
+ int fd;
+
+ snprint(buf, sizeof buf, "/srv/%s", name);
+ fd = create(buf, OWRITE|ORCLOSE|OCEXEC, 0600);
+ if(fd < 0)
+ panic(buf);
+ if(fprint(fd, "%d", srvfd) < 0)
+ panic("post");
+ putenv("wsys", buf);
+ /* leave fd open */
+}
+
+static Ioproc *io9p;
+
+/* copy & paste from /sys/src/libc/9sys/read9pmsg.c
+ * changed to use ioreadn instead of readn */
+int
+read9pmsg(int fd, void *abuf, uint n)
+{
+ int m, len;
+ uchar *buf;
+
+ buf = abuf;
+
+ /* read count */
+ m = ioreadn(io9p, fd, buf, BIT32SZ);
+ if(m != BIT32SZ){
+ if(m < 0)
+ return -1;
+ return 0;
+ }
+
+ len = GBIT32(buf);
+ if(len <= BIT32SZ || len > n){
+ werrstr("bad length in 9P2000 message header");
+ return -1;
+ }
+ len -= BIT32SZ;
+ m = ioreadn(io9p, fd, buf+BIT32SZ, len);
+ if(m < len)
+ return 0;
+ return BIT32SZ+m;
+}
+
+int fsysfd;
+char srvpipe[64];
+//char srvwctl[64];
+
+void
+fs(void)
+{
+ io9p = ioproc();
+
+ int fd[2];
+ if(pipe(fd) < 0)
+ panic("pipe");
+ fsysfd = fd[0]; /* don't close for children */
+ fsys.infd = fsys.outfd = fd[1];
+ snprint(srvpipe, sizeof(srvpipe), "lola.%s.%lud", getuser(), (ulong)getpid());
+ post(srvpipe, fd[0]);
+// chatty9p++;
+ srv(&fsys);
+}
+
+int
+fsmount(int id)
+{ char buf[32];
+
+ close(fsys.infd); /* close server end so mount won't hang if exiting */
+ snprint(buf, sizeof buf, "%d", id);
+ if(mount(fsysfd, -1, "/mnt/wsys", MREPL, buf) == -1){
+ fprint(2, "mount failed: %r\n");
+ return -1;
+ }
+ if(bind("/mnt/wsys", "/dev", MBEFORE) == -1){
+ fprint(2, "bind failed: %r\n");
+ return -1;
+ }
+ return 0;
+}
--- /dev/null
+++ b/inc.h
@@ -1,0 +1,284 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+#include <thread.h>
+#include <keyboard.h>
+#include <mouse.h>
+#include <cursor.h>
+#include <frame.h>
+#include <fcall.h>
+#include <9p.h>
+#include <complete.h>
+#include <plumb.h>
+
+enum {
+ FALSE = 0,
+ TRUE = 1
+};
+
+#define ALT(c, v, t) (Alt){ c, v, t, nil, nil, 0 };
+
+#define CTRL(c) ((c)&0x1F)
+
+extern Rune *snarf;
+extern int nsnarf;
+extern int snarfversion;
+extern int snarffd;
+enum { MAXSNARF = 100*1024 };
+void putsnarf(void);
+void getsnarf(void);
+void setsnarf(char *s, int ns);
+
+typedef struct Text Text;
+struct Text
+{
+ Frame;
+ Rectangle scrollr, lastsr;
+ Image *i;
+ Rune *r;
+ uint nr;
+ uint maxr;
+ uint org; /* start of Frame's text */
+ uint q0, q1; /* selection */
+ uint qh; /* host point, output here */
+
+ /* not entirely happy with this in here */
+ int rawmode;
+ Rune *raw;
+ int nraw;
+
+ int posx;
+};
+
+void xinit(Text *x, Rectangle textr, Rectangle scrollr, Font *ft, Image *b, Image **cols);
+void xsetrects(Text *x, Rectangle textr, Rectangle scrollr);
+void xclear(Text *x);
+void xredraw(Text *x);
+uint xinsert(Text *x, Rune *r, int n, uint q0);
+void xfill(Text *x);
+void xdelete(Text *x, uint q0, uint q1);
+void xsetselect(Text *x, uint q0, uint q1);
+void xselect(Text *x, Mousectl *mc);
+void xscrdraw(Text *x);
+void xscroll(Text *x, Mousectl *mc, int but);
+void xscrolln(Text *x, int n);
+void xtickupdn(Text *x, int d);
+void xshow(Text *x, uint q0);
+void xplacetick(Text *x, uint q);
+void xtype(Text *x, Rune r);
+int xninput(Text *x);
+void xaddraw(Text *x, Rune *r, int nr);
+void xlook(Text *x);
+void xsnarf(Text *x);
+void xcut(Text *x);
+void xpaste(Text *x);
+void xsend(Text *x);
+int xplumb(Text *w, char *dir, int maxsize);
+
+enum
+{
+ // NCOL is defined by libframe, add more after it
+ TITLE = NCOL,
+ LTITLE,
+ TITLEHOLD,
+ LTITLEHOLD,
+
+ PALETEXT,
+ HOLDTEXT,
+ PALEHOLDTEXT,
+
+ NumColors
+};
+
+extern Image *colors[NumColors];
+extern Screen *wscreen;
+extern Mousectl *mctl;
+extern int scrolling;
+extern char *startdir;
+extern int shiftdown;
+extern int gotscreen;
+extern int servekbd;
+
+extern Cursor whitearrow;
+extern Cursor *cursor;
+void setcursoroverride(Cursor *c, int ov);
+void setcursornormal(Cursor *c);
+
+
+typedef struct RuneConvBuf RuneConvBuf;
+struct RuneConvBuf
+{
+ char *buf;
+ int maxbuf; // allocated size
+ int nb; // size
+ int n; // filled
+};
+
+typedef struct Stringpair Stringpair;
+struct Stringpair /* rune and nrune or byte and nbyte */
+{
+ void *s;
+ int ns;
+};
+
+typedef struct Mousestate Mousestate;
+struct Mousestate
+{
+ Mouse;
+ ulong counter; /* serial no. of mouse event */
+};
+
+typedef struct Mousequeue Mousequeue;
+struct Mousequeue
+{
+ Mousestate q[16];
+ int ri; /* read index into queue */
+ int wi; /* write index */
+ ulong counter; /* serial no. of last mouse event we received */
+ ulong lastcounter; /* serial no. of last mouse event sent to client */
+ int lastb; /* last button state we received */
+ uchar full; /* filled the queue; no more recording until client comes back */
+};
+
+typedef struct Kbdqueue Kbdqueue;
+struct Kbdqueue
+{
+ char *q[32];
+ int ri;
+ int wi;
+ uchar full;
+};
+
+enum
+{
+ Closed,
+ Reshaped,
+ Deleted,
+ Refresh,
+ Holdon,
+ Holdoff,
+ Rawon,
+ Rawoff,
+ Wakeup
+};
+
+typedef struct Wctlmesg Wctlmesg;
+struct Wctlmesg
+{
+ int type;
+ Rectangle r;
+ void *p;
+};
+
+typedef struct Window Window;
+struct Window
+{
+ Ref;
+ int deleted;
+ int hidden;
+ Window *lower;
+ Window *higher;
+ Image *img;
+ int id;
+ char name[32];
+ int namecount;
+ char *label;
+ Rectangle contrect;
+ int notefd;
+ char *dir;
+
+ Text text;
+ Rectangle scrollr;
+ Rectangle textr;
+ int scrolling;
+ int holdmode;
+
+ Mousectl mc;
+ Mousequeue mq;
+ int mouseopen;
+ int resized;
+
+ Cursor *cursorp;
+ Cursor cursor;
+
+ Channel *kbd;
+ Kbdqueue kq;
+ int consctlopen;
+ int kbdopen;
+
+ Channel *gone; // window gone
+ Channel *ctl; // Wctlmesg
+ /* channels to xfids */
+ Channel *conswrite;
+ Channel *consread;
+ Channel *kbdread;
+ Channel *mouseread;
+ Channel *complete;
+};
+
+extern Window *bottomwin, *topwin;
+extern Window *windows[1000]; // TMP
+extern int nwindows;
+extern Window *hidden[1000];
+extern int nhidden;
+extern Window *focused, *cursorwin;
+
+void wdecor(Window *w);
+void wresize(Window *w, Rectangle r);
+Window *wcreate(Rectangle r);
+int wrelease(Window *w);
+void wsendmsg(Window *w, int type, Rectangle r, void *p);
+Window *wfind(int id);
+Window *wpointto(Point pt);
+void wsetcursor(Window *w);
+void wsetlabel(Window *w, char *label);
+void wmove(Window *w, Point pos);
+void wrmove(Window *w, Point delta);
+void wrmovescreen(Point delta);
+void wraise(Window *w);
+void wlower(Window *w);
+void wfocus(Window *w);
+void whide(Window *w);
+void wunhide(Window *w);
+void wsethold(Window *w, int hold);
+void wtype(Window *w, Rune r);
+void wsetname(Window *w);
+void wsetpid(Window *w, int pid, int dolabel);
+void winshell(void *args);
+
+
+Rectangle newrect(void);
+
+extern Srv fsys;
+void fs(void);
+int fsmount(int id);
+
+#define runemalloc(n) malloc((n)*sizeof(Rune))
+#define runerealloc(a, n) realloc(a, (n)*sizeof(Rune))
+#define runemove(a, b, n) memmove(a, b, (n)*sizeof(Rune))
+#define min(a, b) ((a) < (b) ? (a) : (b))
+#define max(a, b) ((a) > (b) ? (a) : (b))
+
+void panic(char *s);
+void *emalloc(ulong size);
+void *erealloc(void *p, ulong size);
+char *estrdup(char *s);
+int handlebs(Stringpair *pair);
+void cnvsize(RuneConvBuf *cnv, int nb);
+int r2bfill(RuneConvBuf *cnv, Rune *rp, int nr);
+void r2bfinish(RuneConvBuf *cnv, Stringpair *pair);
+Stringpair b2r(RuneConvBuf *cnv);
+
+
+typedef struct Timer Timer;
+struct Timer
+{
+ int dt;
+ int cancel;
+ Channel *c; /* chan(int) */
+ Timer *next;
+};
+void timerinit(void);
+Timer *timerstart(int dt);
+void timerstop(Timer *t);
+void timercancel(Timer *t);
--- /dev/null
+++ b/main.c
@@ -1,0 +1,799 @@
+#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;
+
+ 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;
+
+ 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;
+}
+
+void
+kbthread(void*)
+{
+ char *s;
+
+ for(;;){
+ recv(kbctl->c, &s);
+ if(*s == 'k' || *s == 'K')
+ shiftdown = utfrune(s+1, Kshift) != nil;
+ if(focused)
+ send(focused->kbd, &s);
+ else
+ free(s);
+ }
+}
+
+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");
+
+ 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(kbthread, nil, mainstacksize);
+ threadcreate(resthread, nil, mainstacksize);
+
+ flushimage(display, 1);
+
+ fs();
+ // not reached
+}
--- /dev/null
+++ b/mkfile
@@ -1,0 +1,16 @@
+< /$objtype/mkfile
+
+TARG=drawtest
+OFILES=\
+ main.$O \
+ text.$O \
+ wind.$O \
+ fs.$O \
+ util.$O \
+ time.$O
+
+HFILES=inc.h
+
+BIN=$home/bin/$objtype
+
+< /sys/src/cmd/mkone
--- /dev/null
+++ b/text.c
@@ -1,0 +1,956 @@
+#include "inc.h"
+
+enum
+{
+ HiWater = 640000, /* max size of history */
+ LoWater = 400000, /* min size of history after max'ed */
+ MinWater = 20000, /* room to leave available when reallocating */
+};
+
+void
+xinit(Text *x, Rectangle textr, Rectangle scrollr, Font *ft, Image *b, Image **cols)
+{
+ frinit(x, textr, ft, b, cols);
+ x->i = b;
+ x->scrollr = scrollr;
+ x->lastsr = ZR;
+ xfill(x);
+ xsetselect(x, x->q0, x->q1);
+ xscrdraw(x);
+}
+
+void
+xsetrects(Text *x, Rectangle textr, Rectangle scrollr)
+{
+ frsetrects(x, textr, x->b);
+ x->scrollr = scrollr;
+}
+
+void
+xclear(Text *x)
+{
+ free(x->r);
+ x->r = nil;
+ x->nr = 0;
+ free(x->raw);
+ x->r = nil;
+ x->nraw = 0;
+ frclear(x, TRUE);
+};
+
+void
+xredraw(Text *x)
+{
+ frredraw(x);
+ xscrdraw(x);
+}
+
+uint
+xinsert(Text *w, Rune *r, int n, uint q0)
+{
+ uint m;
+
+ if(n == 0)
+ return q0;
+ if(w->nr+n>HiWater && q0>=w->org && q0>=w->qh){
+ m = min(HiWater-LoWater, min(w->org, w->qh));
+ w->org -= m;
+ w->qh -= m;
+ if(w->q0 > m)
+ w->q0 -= m;
+ else
+ w->q0 = 0;
+ if(w->q1 > m)
+ w->q1 -= m;
+ else
+ w->q1 = 0;
+ w->nr -= m;
+ runemove(w->r, w->r+m, w->nr);
+ q0 -= m;
+ }
+ if(w->nr+n > w->maxr){
+ /*
+ * Minimize realloc breakage:
+ * Allocate at least MinWater
+ * Double allocation size each time
+ * But don't go much above HiWater
+ */
+ m = max(min(2*(w->nr+n), HiWater), w->nr+n)+MinWater;
+ if(m > HiWater)
+ m = max(HiWater+MinWater, w->nr+n);
+ if(m > w->maxr){
+ w->r = runerealloc(w->r, m);
+ w->maxr = m;
+ }
+ }
+ runemove(w->r+q0+n, w->r+q0, w->nr-q0);
+ runemove(w->r+q0, r, n);
+ w->nr += n;
+ /* if output touches, advance selection, not qh; works best for keyboard and output */
+ if(q0 <= w->q1)
+ w->q1 += n;
+ if(q0 <= w->q0)
+ w->q0 += n;
+ if(q0 < w->qh)
+ w->qh += n;
+ if(q0 < w->org)
+ w->org += n;
+ else if(q0 <= w->org+w->nchars){
+for(int i = 0; i < n; i++)
+if(r[i] == 0)
+abort();
+ frinsert(w, r, r+n, q0-w->org);
+}
+ return q0;
+}
+
+void
+xfill(Text *w)
+{
+ Rune *rp;
+ int i, n, m, nl;
+
+ while(w->lastlinefull == FALSE){
+ n = w->nr-(w->org+w->nchars);
+ if(n == 0)
+ break;
+ if(n > 2000) /* educated guess at reasonable amount */
+ n = 2000;
+ rp = w->r+(w->org+w->nchars);
+
+ /*
+ * it's expensive to frinsert more than we need, so
+ * count newlines.
+ */
+ nl = w->maxlines-w->nlines;
+ m = 0;
+ for(i=0; i<n; ){
+ if(rp[i++] == '\n'){
+ m++;
+ if(m >= nl)
+ break;
+ }
+ }
+ frinsert(w, rp, rp+i, w->nchars);
+ }
+}
+
+void
+xdelete(Text *w, uint q0, uint q1)
+{
+ uint n, p0, p1;
+
+ n = q1-q0;
+ if(n == 0)
+ return;
+ runemove(w->r+q0, w->r+q1, w->nr-q1);
+ w->nr -= n;
+ if(q0 < w->q0)
+ w->q0 -= min(n, w->q0-q0);
+ if(q0 < w->q1)
+ w->q1 -= min(n, w->q1-q0);
+ if(q1 < w->qh)
+ w->qh -= n;
+ else if(q0 < w->qh)
+ w->qh = q0;
+ if(q1 <= w->org)
+ w->org -= n;
+ else if(q0 < w->org+w->nchars){
+ p1 = q1 - w->org;
+ if(p1 > w->nchars)
+ p1 = w->nchars;
+ if(q0 < w->org){
+ w->org = q0;
+ p0 = 0;
+ }else
+ p0 = q0 - w->org;
+ frdelete(w, p0, p1);
+ xfill(w);
+ }
+}
+
+void
+xsetselect(Text *w, uint q0, uint q1)
+{
+ int p0, p1;
+
+ w->posx = -1;
+ /* w->p0 and w->p1 are always right; w->q0 and w->q1 may be off */
+ w->q0 = q0;
+ w->q1 = q1;
+ /* compute desired p0,p1 from q0,q1 */
+ p0 = q0-w->org;
+ p1 = q1-w->org;
+ if(p0 < 0)
+ p0 = 0;
+ if(p1 < 0)
+ p1 = 0;
+ if(p0 > w->nchars)
+ p0 = w->nchars;
+ if(p1 > w->nchars)
+ p1 = w->nchars;
+ if(p0==w->p0 && p1==w->p1)
+ return;
+ /* screen disagrees with desired selection */
+ if(w->p1<=p0 || p1<=w->p0 || p0==p1 || w->p1==w->p0){
+ /* no overlap or too easy to bother trying */
+ frdrawsel(w, frptofchar(w, w->p0), w->p0, w->p1, 0);
+ frdrawsel(w, frptofchar(w, p0), p0, p1, 1);
+ goto Return;
+ }
+ /* overlap; avoid unnecessary painting */
+ if(p0 < w->p0){
+ /* extend selection backwards */
+ frdrawsel(w, frptofchar(w, p0), p0, w->p0, 1);
+ }else if(p0 > w->p0){
+ /* trim first part of selection */
+ frdrawsel(w, frptofchar(w, w->p0), w->p0, p0, 0);
+ }
+ if(p1 > w->p1){
+ /* extend selection forwards */
+ frdrawsel(w, frptofchar(w, w->p1), w->p1, p1, 1);
+ }else if(p1 < w->p1){
+ /* trim last part of selection */
+ frdrawsel(w, frptofchar(w, p1), p1, w->p1, 0);
+ }
+
+ Return:
+ w->p0 = p0;
+ w->p1 = p1;
+}
+
+static void
+xsetorigin(Text *w, uint org, int exact)
+{
+ int i, a, fixup;
+ Rune *r;
+ uint n;
+
+ if(org>0 && !exact){
+ /* org is an estimate of the char posn; find a newline */
+ /* don't try harder than 256 chars */
+ for(i=0; i<256 && org<w->nr; i++){
+ if(w->r[org] == '\n'){
+ org++;
+ break;
+ }
+ org++;
+ }
+ }
+ a = org-w->org;
+ fixup = 0;
+ if(a>=0 && a<w->nchars){
+ frdelete(w, 0, a);
+ fixup = 1; /* frdelete can leave end of last line in wrong selection mode; it doesn't know what follows */
+ }else if(a<0 && -a<w->nchars){
+ n = w->org - org;
+ r = w->r+org;
+ frinsert(w, r, r+n, 0);
+ }else
+ frdelete(w, 0, w->nchars);
+ w->org = org;
+ xfill(w);
+ xscrdraw(w);
+ xsetselect(w, w->q0, w->q1);
+ if(fixup && w->p1 > w->p0)
+ frdrawsel(w, frptofchar(w, w->p1-1), w->p1-1, w->p1, 1);
+}
+
+
+/*
+ * Scrolling
+ */
+
+static Image *scrtmp;
+enum { BIG = 3 };
+
+static Image*
+scrtemps(void)
+{
+ int h;
+
+ if(scrtmp == nil){
+ h = BIG*Dy(screen->r);
+ scrtmp = allocimage(display, Rect(0, 0, 32, h), screen->chan, 0, DNofill);
+ }
+ return scrtmp;
+}
+
+/*
+void
+freescrtemps(void)
+{
+ if(scrtmp){
+ freeimage(scrtmp);
+ scrtmp = nil;
+ }
+}
+*/
+
+static Rectangle
+scrpos(Rectangle r, uint p0, uint p1, uint tot)
+{
+ Rectangle q;
+ int h;
+
+ q = r;
+ h = q.max.y-q.min.y;
+ if(tot == 0)
+ return q;
+ if(tot > 1024*1024){
+ tot>>=10;
+ p0>>=10;
+ p1>>=10;
+ }
+ if(p0 > 0)
+ q.min.y += h*p0/tot;
+ if(p1 < tot)
+ q.max.y -= h*(tot-p1)/tot;
+ if(q.max.y < q.min.y+2){
+ if(q.min.y+2 <= r.max.y)
+ q.max.y = q.min.y+2;
+ else
+ q.min.y = q.max.y-2;
+ }
+ return q;
+}
+
+void
+xscrdraw(Text *w)
+{
+ Rectangle r, r1, r2;
+ Image *b;
+
+ b = scrtemps();
+ if(b == nil || w->i == nil)
+ return;
+ r = w->scrollr;
+ r1 = r;
+ r1.min.x = 0;
+ r1.max.x = Dx(r);
+ r2 = scrpos(r1, w->org, w->org+w->nchars, w->nr);
+ if(!eqrect(r2, w->lastsr)){
+ w->lastsr = r2;
+ /* move r1, r2 to (0,0) to avoid clipping */
+ r2 = rectsubpt(r2, r1.min);
+ r1 = rectsubpt(r1, r1.min);
+ draw(b, r1, w->cols[BORD], nil, ZP);
+ draw(b, r2, w->cols[BACK], nil, ZP);
+ r2.min.x = r2.max.x-1;
+ draw(b, r2, w->cols[BORD], nil, ZP);
+ draw(w->i, r, b, nil, Pt(0, r1.min.y));
+ }
+}
+
+static uint
+xbacknl(Text *w, uint p, uint n)
+{
+ int i, j;
+
+ /* look for start of this line if n==0 */
+ if(n==0 && p>0 && w->r[p-1]!='\n')
+ n = 1;
+ i = n;
+ while(i-->0 && p>0){
+ --p; /* it's at a newline now; back over it */
+ if(p == 0)
+ break;
+ /* at 128 chars, call it a line anyway */
+ for(j=128; --j>0 && p>0; p--)
+ if(w->r[p-1]=='\n')
+ break;
+ }
+ return p;
+}
+
+static void
+xscrsleep(Mousectl *mc, uint dt)
+{
+ Timer *timer;
+ int y, b;
+ static Alt alts[3];
+
+ if(display->bufp > display->buf)
+ flushimage(display, 1);
+ timer = timerstart(dt);
+ y = mc->xy.y;
+ b = mc->buttons;
+ alts[0] = ALT(timer->c, nil, CHANRCV);
+ alts[1] = ALT(mc->c, &mc->Mouse, CHANRCV);
+ alts[2].op = CHANEND;
+ for(;;)
+ switch(alt(alts)){
+ case 0:
+ timerstop(timer);
+ return;
+ case 1:
+ if(abs(mc->xy.y-y)>2 || mc->buttons!=b){
+ timercancel(timer);
+ return;
+ }
+ break;
+ }
+}
+
+void
+xscroll(Text *w, Mousectl *mc, int but)
+{
+ uint p0, oldp0;
+ Rectangle s;
+ int y, my, h, first;
+
+ s = insetrect(w->scrollr, 1);
+ h = s.max.y-s.min.y;
+ oldp0 = ~0;
+ first = TRUE;
+ do{
+ my = mc->xy.y;
+ if(my < s.min.y)
+ my = s.min.y;
+ if(my >= s.max.y)
+ my = s.max.y;
+ if(but == 2){
+ y = my;
+ if(y > s.max.y-2)
+ y = s.max.y-2;
+ if(w->nr > 1024*1024)
+ p0 = ((w->nr>>10)*(y-s.min.y)/h)<<10;
+ else
+ p0 = w->nr*(y-s.min.y)/h;
+ if(oldp0 != p0)
+ xsetorigin(w, p0, FALSE);
+ oldp0 = p0;
+ readmouse(mc);
+ continue;
+ }
+ if(but == 1 || but == 4){
+ y = max(1, (my-s.min.y)/w->font->height);
+ p0 = xbacknl(w, w->org, y);
+ }else{
+ y = max(my, s.min.y+w->font->height);
+ p0 = w->org+frcharofpt(w, Pt(s.max.x, y));
+ }
+ if(oldp0 != p0)
+ xsetorigin(w, p0, TRUE);
+ oldp0 = p0;
+ /* debounce */
+ if(first){
+ if(display->bufp > display->buf)
+ flushimage(display, 1);
+ if(but > 3)
+ return;
+ sleep(200);
+ nbrecv(mc->c, &mc->Mouse);
+ first = FALSE;
+ }
+ xscrsleep(mc, 100);
+ }while(mc->buttons & (1<<(but-1)));
+ while(mc->buttons)
+ readmouse(mc);
+}
+
+void
+xscrolln(Text *x, int n)
+{
+ uint q0;
+
+ if(n < 0)
+ q0 = xbacknl(x, x->org, -n);
+ else
+ q0 = x->org+frcharofpt(x, Pt(x->Frame.r.min.x, x->Frame.r.min.y+n*x->font->height));
+ xsetorigin(x, q0, TRUE);
+}
+
+/* move tick up or down while staying at the same x position */
+void
+xtickupdn(Text *x, int d)
+{
+ Point p;
+ int py;
+ uint q0;
+
+ xshow(x, x->q0);
+ p = frptofchar(x, x->q0-x->org);
+ if(x->posx >= 0)
+ p.x = x->posx;
+ py = p.y;
+ p.y += d*x->font->height;
+ if(p.y < x->Frame.r.min.y ||
+ p.y > x->Frame.r.max.y-x->font->height){
+ xscrolln(x, d);
+ p.y = py;
+ }
+ q0 = x->org+frcharofpt(x, p);
+ xsetselect(x, q0, q0);
+ x->posx = p.x;
+}
+
+static Text *selecttext;
+static Mousectl *selectmc;
+static uint selectq;
+
+static void
+xframescroll(Text *x, int dl)
+{
+ uint endq;
+
+ if(dl == 0){
+ xscrsleep(selectmc, 100);
+ return;
+ }
+ if(dl < 0){
+ endq = x->org+x->p0;
+ }else{
+ if(x->org+x->nchars == x->nr)
+ return;
+ endq = x->org+x->p1;
+ }
+ xscrolln(x, dl);
+ xsetselect(x, min(selectq, endq), max(selectq, endq));
+}
+
+static void
+framescroll(Frame *f, int dl)
+{
+ if(f != &selecttext->Frame)
+ panic("frameselect not right frame");
+ xframescroll(selecttext, dl);
+}
+
+/*
+ * Selection and deletion helpers
+ */
+
+int
+iswordrune(Rune r)
+{
+ return isalpharune(r) || isdigitrune(r);
+}
+
+static int
+xbswidth(Text *w, Rune c)
+{
+ uint q, stop;
+ Rune r;
+ int wd, inword;
+
+ /* there is known to be at least one character to erase */
+ if(c == Kbs) /* ^H: erase character */
+ return 1;
+ q = w->q0;
+ stop = 0;
+ if(q > w->qh)
+ stop = w->qh;
+ inword = FALSE;
+ while(q > stop){
+ r = w->r[q-1];
+ if(r == '\n'){ /* eat at most one more character */
+ if(q == w->q0) /* eat the newline */
+ --q;
+ break;
+ }
+ /* ^W: erase word.
+ * delete a bunch of non-word characters
+ * followed by word characters */
+ if(c == CTRL('W')){
+ wd = iswordrune(r);
+ if(wd && !inword)
+ inword = TRUE;
+ else if(!wd && inword)
+ break;
+ }
+ --q;
+ }
+ return w->q0-q;
+}
+
+static Rune left1[] = { L'{', L'[', L'(', L'<', L'«', 0 };
+static Rune right1[] = { L'}', L']', L')', L'>', L'»', 0 };
+static Rune left2[] = { L'\n', 0 };
+static Rune left3[] = { L'\'', L'"', L'`', 0 };
+
+static Rune *left[] = {
+ left1,
+ left2,
+ left3,
+ nil
+};
+static Rune *right[] = {
+ right1,
+ left2,
+ left3,
+ nil
+};
+
+static int
+xclickmatch(Text *x, int cl, int cr, int dir, uint *q)
+{
+ Rune c;
+ int nest;
+
+ nest = 1;
+ for(;;){
+ if(dir > 0){
+ if(*q == x->nr)
+ break;
+ c = x->r[*q];
+ (*q)++;
+ }else{
+ if(*q == 0)
+ break;
+ (*q)--;
+ c = x->r[*q];
+ }
+ if(c == cr){
+ if(--nest==0)
+ return 1;
+ }else if(c == cl)
+ nest++;
+ }
+ return cl=='\n' && nest==1;
+}
+
+static int
+inmode(Rune r, int mode)
+{
+ return (mode == 1) ? iswordrune(r) : r && !isspacerune(r);
+}
+
+static void
+xstretchsel(Text *x, uint pt, uint *q0, uint *q1, int mode)
+{
+ int c, i;
+ Rune *r, *l, *p;
+ uint q;
+
+ *q0 = pt;
+ *q1 = pt;
+ for(i=0; left[i]!=nil; i++){
+ q = *q0;
+ l = left[i];
+ r = right[i];
+ /* try matching character to left, looking right */
+ if(q == 0)
+ c = '\n';
+ else
+ c = x->r[q-1];
+ p = runestrchr(l, c);
+ if(p != nil){
+ if(xclickmatch(x, c, r[p-l], 1, &q))
+ *q1 = q-(c!='\n');
+ return;
+ }
+ /* try matching character to right, looking left */
+ if(q == x->nr)
+ c = '\n';
+ else
+ c = x->r[q];
+ p = runestrchr(r, c);
+ if(p != nil){
+ if(xclickmatch(x, c, l[p-r], -1, &q)){
+ *q1 = *q0+(*q0<x->nr && c=='\n');
+ *q0 = q;
+ if(c!='\n' || q!=0 || x->r[0]=='\n')
+ (*q0)++;
+ }
+ return;
+ }
+ }
+ /* try filling out word to right */
+ while(*q1<x->nr && inmode(x->r[*q1], mode))
+ (*q1)++;
+ /* try filling out word to left */
+ while(*q0>0 && inmode(x->r[*q0-1], mode))
+ (*q0)--;
+}
+
+static Mouse lastclick;
+static Text *clickfrm;
+static uint clickcount;
+
+/* should be called with button 1 down */
+void
+xselect(Text *x, Mousectl *mc)
+{
+ uint q0, q1;
+ int dx, dy, dt, b;
+
+ /* reset click state if mouse is too different from last time */
+ dx = abs(mc->xy.x - lastclick.xy.x);
+ dy = abs(mc->xy.y - lastclick.xy.y);
+ dt = mc->msec - lastclick.msec;
+ if(x != clickfrm || dx > 3 || dy > 3 || dt >= 500)
+ clickcount = 0;
+
+ /* first button down can be a dragging selection or a click.
+ * subsequent buttons downs can only be clicks.
+ * both cases can be ended by chording. */
+ selectq = x->org+frcharofpt(x, mc->xy);
+ if(clickcount == 0){
+ /* what a kludge - can this be improved? */
+ selecttext = x;
+ selectmc = mc;
+ x->scroll = framescroll;
+ frselect(x, mc);
+ /* this is correct if the whole selection is visible */
+ q0 = x->org + x->p0;
+ q1 = x->org + x->p1;
+ /* otherwise replace one end with selectq */
+ if(selectq < x->org)
+ q0 = selectq;
+ if(selectq > x->org+x->nchars)
+ q1 = selectq;
+ xsetselect(x, q0, q1);
+
+ /* figure out whether it was a click */
+ if(q0 == q1 && mc->buttons == 0){
+ clickcount = 1;
+ clickfrm = x;
+ }
+ }else{
+ clickcount++;
+ xstretchsel(x, selectq, &q0, &q1, min(clickcount-1, 2));
+ xsetselect(x, q0, q1);
+ if(clickcount >= 3)
+ clickcount = 0;
+ b = mc->buttons;
+ while(mc->buttons == b)
+ readmouse(mc);
+ }
+ lastclick = mc->Mouse; /* a bit unsure if this is correct */
+
+ /* chording */
+ while(mc->buttons){
+ clickcount = 0;
+ b = mc->buttons;
+ if(b & 6){
+ if(b & 2){
+ xsnarf(x);
+ xcut(x);
+ }else{
+ xpaste(x);
+ }
+ xscrdraw(x); // TODO let cut/paste handle this?
+ }
+ while(mc->buttons == b)
+ readmouse(mc);
+ }
+}
+
+void
+xshow(Text *w, uint q0)
+{
+ int qe;
+ int nl;
+ uint q;
+
+ qe = w->org+w->nchars;
+ if(w->org<=q0 && (q0<qe || (q0==qe && qe==w->nr)))
+ xscrdraw(w);
+ else{
+ nl = 4*w->maxlines/5;
+ q = xbacknl(w, q0, nl);
+ /* avoid going backwards if trying to go forwards - long lines! */
+ if(!(q0>w->org && q<w->org))
+ xsetorigin(w, q, TRUE);
+ while(q0 > w->org+w->nchars)
+ xsetorigin(w, w->org+1, FALSE);
+ }
+}
+
+void
+xplacetick(Text *x, uint q)
+{
+ xsetselect(x, q, q);
+ xshow(x, q);
+}
+
+void
+xtype(Text *x, Rune r)
+{
+ uint q0, q1;
+ int nb;
+
+ switch(r){
+ case CTRL('H'): /* erase character */
+ case CTRL('W'): /* erase word */
+ case CTRL('U'): /* erase line */
+ if(x->q0==0 || x->q0==x->qh)
+ return;
+ nb = xbswidth(x, r);
+ q1 = x->q0;
+ q0 = q1-nb;
+ if(q0 < x->org){
+ q0 = x->org;
+ nb = q1-q0;
+ }
+ if(nb > 0){
+ xdelete(x, q0, q0+nb);
+ xsetselect(x, q0, q0);
+ }
+ break;
+ default:
+ xdelete(x, x->q0, x->q1);
+ xinsert(x, &r, 1, x->q0);
+ xshow(x, x->q0);
+ break;
+ }
+}
+
+int
+xninput(Text *x)
+{
+ uint q;
+ Rune r;
+
+ for(q = x->qh; q < x->nr; q++){
+ r = x->r[q];
+ if(r == '\n')
+ return q - x->qh + 1;
+ if(r == CTRL('D'))
+ return q - x->qh;
+ }
+ return -1;
+}
+
+void
+xaddraw(Text *x, Rune *r, int nr)
+{
+ x->raw = runerealloc(x->raw, x->nraw+nr);
+ runemove(x->raw+x->nraw, r, nr);
+ x->nraw += nr;
+}
+
+/* TODO: maybe pass what we're looking for in a string */
+void
+xlook(Text *x)
+{
+ int i, n, e;
+
+ i = x->q1;
+ n = i - x->q0;
+ e = x->nr - n;
+ if(n <= 0 || e < n)
+ return;
+
+ if(i > e)
+ i = 0;
+
+ while(runestrncmp(x->r+x->q0, x->r+i, n) != 0){
+ if(i < e)
+ i++;
+ else
+ i = 0;
+ }
+
+ xsetselect(x, i, i+n);
+ xshow(x, i);
+}
+
+Rune *snarf;
+int nsnarf;
+int snarfversion;
+int snarffd;
+
+void
+xsnarf(Text *x)
+{
+ if(x->q1 == x->q0)
+ return;
+ nsnarf = x->q1-x->q0;
+ snarf = runerealloc(snarf, nsnarf);
+ snarfversion++;
+ runemove(snarf, x->r+x->q0, nsnarf);
+ putsnarf();
+}
+
+void
+xcut(Text *x)
+{
+ if(x->q1 == x->q0)
+ return;
+ xdelete(x, x->q0, x->q1);
+ xsetselect(x, x->q0, x->q0);
+}
+
+void
+xpaste(Text *x)
+{
+ uint q0;
+
+ getsnarf();
+ if(nsnarf == 0)
+ return;
+ xcut(x);
+ q0 = x->q0;
+ if(x->rawmode && q0==x->nr){
+ xaddraw(x, snarf, nsnarf);
+ xsetselect(x, q0, q0);
+ }else{
+ q0 = xinsert(x, snarf, nsnarf, x->q0);
+ xsetselect(x, q0, q0+nsnarf);
+ }
+}
+
+void
+xsend(Text *x)
+{
+ getsnarf();
+ xsnarf(x);
+ if(nsnarf == 0)
+ return;
+ if(x->rawmode){
+ xaddraw(x, snarf, nsnarf);
+ if(snarf[nsnarf-1]!='\n' && snarf[nsnarf-1]!=CTRL('D'))
+ xaddraw(x, L"\n", 1);
+ }else{
+ xinsert(x, snarf, nsnarf, x->nr);
+ if(snarf[nsnarf-1]!='\n' && snarf[nsnarf-1]!=CTRL('D'))
+ xinsert(x, L"\n", 1, x->nr);
+ }
+ xplacetick(x, x->nr);
+}
+
+int
+xplumb(Text *w, char *dir, int maxsize)
+{
+ Plumbmsg *m;
+ static int fd = -2;
+ char buf[32];
+ uint p0, p1;
+
+ if(fd == -2)
+ fd = plumbopen("send", OWRITE|OCEXEC);
+ if(fd < 0)
+ return 0;
+ m = emalloc(sizeof(Plumbmsg));
+ m->src = estrdup("rio");
+ m->dst = nil;
+ m->wdir = estrdup(dir);
+ m->type = estrdup("text");
+ p0 = w->q0;
+ p1 = w->q1;
+ if(w->q1 > w->q0)
+ m->attr = nil;
+ else{
+ while(p0>0 && w->r[p0-1]!=' ' && w->r[p0-1]!='\t' && w->r[p0-1]!='\n')
+ p0--;
+ while(p1<w->nr && w->r[p1]!=' ' && w->r[p1]!='\t' && w->r[p1]!='\n')
+ p1++;
+ snprint(buf, sizeof(buf), "click=%d", w->q0-p0);
+ m->attr = plumbunpackattr(buf);
+ }
+ if(p1-p0 > maxsize){
+ plumbfree(m);
+ return 0; /* too large for 9P */
+ }
+// m->data = runetobyte(w->r+p0, p1-p0, &m->ndata);
+ m->data = smprint("%.*S", p1-p0, w->r+p0);
+ m->ndata = strlen(m->data);
+ if(plumbsend(fd, m) < 0){
+ plumbfree(m);
+ return 1;
+ }
+ plumbfree(m);
+ return 0;
+}
--- /dev/null
+++ b/time.c
@@ -1,0 +1,114 @@
+#include "inc.h"
+
+/* taken from rio */
+
+static Channel* ctimer; /* chan(Timer*)[100] */
+static Timer *timer;
+
+static uint
+msec(void)
+{
+ return nsec()/1000000;
+}
+
+void
+timerstop(Timer *t)
+{
+ t->next = timer;
+ timer = t;
+}
+
+void
+timercancel(Timer *t)
+{
+ t->cancel = TRUE;
+}
+
+static void
+timerproc(void*)
+{
+ int i, nt, na, dt, del;
+ Timer **t, *x;
+ uint old, new;
+
+ rfork(RFFDG);
+ threadsetname("TIMERPROC");
+ t = nil;
+ na = 0;
+ nt = 0;
+ old = msec();
+ for(;;){
+ sleep(1); /* will sleep minimum incr */
+ new = msec();
+ dt = new-old;
+ old = new;
+ if(dt < 0) /* timer wrapped; go around, losing a tick */
+ continue;
+ for(i=0; i<nt; i++){
+ x = t[i];
+ x->dt -= dt;
+ del = 0;
+ if(x->cancel){
+ timerstop(x);
+ del = 1;
+ }else if(x->dt <= 0){
+ /*
+ * avoid possible deadlock if client is
+ * now sending on ctimer
+ */
+ if(nbsendul(x->c, 0) > 0)
+ del = 1;
+ }
+ if(del){
+ memmove(&t[i], &t[i+1], (nt-i-1)*sizeof t[0]);
+ --nt;
+ --i;
+ }
+ }
+ if(nt == 0){
+ x = recvp(ctimer);
+ gotit:
+ if(nt == na){
+ na += 10;
+ t = realloc(t, na*sizeof(Timer*));
+ if(t == nil)
+ abort();
+ }
+ t[nt++] = x;
+ old = msec();
+ }
+ if(nbrecv(ctimer, &x) > 0)
+ goto gotit;
+ }
+}
+
+void
+timerinit(void)
+{
+ ctimer = chancreate(sizeof(Timer*), 100);
+ proccreate(timerproc, nil, mainstacksize);
+}
+
+/*
+ * timeralloc() and timerfree() don't lock, so can only be
+ * called from the main proc.
+ */
+
+Timer*
+timerstart(int dt)
+{
+ Timer *t;
+
+ t = timer;
+ if(t)
+ timer = timer->next;
+ else{
+ t = emalloc(sizeof(Timer));
+ t->c = chancreate(sizeof(int), 0);
+ }
+ t->next = nil;
+ t->dt = dt;
+ t->cancel = FALSE;
+ sendp(ctimer, t);
+ return t;
+}
--- /dev/null
+++ b/util.c
@@ -1,0 +1,128 @@
+#include "inc.h"
+
+void
+panic(char *s)
+{
+ fprint(2, "error: %s: %r\n", s);
+ threadexitsall("error");
+}
+
+void*
+emalloc(ulong size)
+{
+ void *p;
+
+ p = malloc(size);
+ if(p == nil)
+ panic("malloc failed");
+ memset(p, 0, size);
+ return p;
+}
+
+void*
+erealloc(void *p, ulong size)
+{
+ p = realloc(p, size);
+ if(p == nil)
+ panic("realloc failed");
+ return p;
+}
+
+char*
+estrdup(char *s)
+{
+ char *p;
+
+ p = malloc(strlen(s)+1);
+ if(p == nil)
+ panic("strdup failed");
+ strcpy(p, s);
+ return p;
+}
+
+/* Handle backspaces in a rune string.
+ * Set number of final runes,
+ * return number of runes to be deleted initially */
+int
+handlebs(Stringpair *pair)
+{
+ int initial;
+ Rune *start, *rp, *wp;
+ int i;
+
+ initial = 0;
+ start = rp = wp = pair->s;
+ for(i = 0; i < pair->ns; i++){
+ if(*rp == '\b'){
+ if(wp == start)
+ initial++;
+ else
+ wp--;
+ }else
+ *wp++ = *rp;
+ rp++;
+ }
+ pair->ns = wp - start;
+ return initial;
+}
+
+
+void
+cnvsize(RuneConvBuf *cnv, int nb)
+{
+ cnv->nb = nb;
+ if(cnv->maxbuf < nb+UTFmax){
+ cnv->maxbuf = nb+UTFmax;
+ cnv->buf = erealloc(cnv->buf, cnv->maxbuf);
+ }
+}
+
+int
+r2bfill(RuneConvBuf *cnv, Rune *rp, int nr)
+{
+ int i;
+ for(i = 0; cnv->n < cnv->nb && i < nr; i++)
+ cnv->n += runetochar(&cnv->buf[cnv->n], &rp[i]);
+ return i;
+}
+void
+r2bfinish(RuneConvBuf *cnv, Stringpair *pair)
+{
+ int nb;
+
+ nb = pair->ns;
+ pair->ns = min(nb, cnv->n);
+ memmove(pair->s, cnv->buf, pair->ns);
+ cnv->n = max(0, cnv->n-nb);
+ memmove(cnv->buf, cnv->buf+nb, cnv->n);
+}
+
+// TODO: not sure about the signature of this...
+// maybe pass in allocated pair?
+// don't include null runes
+Stringpair
+b2r(RuneConvBuf *cnv)
+{
+ Stringpair pair;
+ Rune *rp;
+ int i;
+
+ rp = runemalloc(cnv->n);
+ pair.s = rp;
+ pair.ns = 0;
+ i = 0;
+ // TODO: optimize this
+ // we know there are full runes until the end
+ while(fullrune(cnv->buf+i, cnv->n-i)){
+ i += chartorune(rp, cnv->buf+i);
+ if(*rp){
+ rp++;
+ pair.ns++;
+ }
+ }
+ memmove(cnv->buf, cnv->buf+i, cnv->n-i);
+ cnv->n -= i;
+
+ return pair;
+}
+
--- /dev/null
+++ b/wind.c
@@ -1,0 +1,982 @@
+#include "inc.h"
+
+Window *bottomwin, *topwin;
+Window *windows[1000]; // TMP
+int nwindows;
+Window *hidden[1000];
+int nhidden;
+Window *focused, *cursorwin;
+
+static void winthread(void *arg);
+
+static void
+wlistpushback(Window *w)
+{
+ w->higher = bottomwin;
+ if(bottomwin) bottomwin->lower = w;
+ w->lower = nil;
+ bottomwin = w;
+}
+
+static void
+wlistpushfront(Window *w)
+{
+ w->lower = topwin;
+ if(topwin) topwin->higher = w;
+ w->higher = nil;
+ 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
+wcalcrects(Window *w)
+{
+ w->contrect = insetrect(w->img->r, Borderwidth);
+ Rectangle 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
+wdecor(Window *w)
+{
+ int c = w->holdmode ?
+ w == focused ? TITLEHOLD : LTITLEHOLD :
+ w == focused ? TITLE : LTITLE;
+ border(w->img, w->img->r, Borderwidth, colors[c], ZP);
+}
+
+void
+wsetcolors(Window *w)
+{
+ int c = w->holdmode ?
+ w == focused ? HOLDTEXT : PALEHOLDTEXT :
+ w == focused ? TEXT : PALETEXT;
+ w->text.cols[TEXT] = colors[c];
+}
+
+static void
+wsetsize(Window *w, Rectangle r)
+{
+ if(w->img)
+ freeimage(w->img);
+ w->img = allocwindow(wscreen, r, Refbackup, DNofill);
+ wcalcrects(w);
+// might be worth a try!
+//replclipr(w->img,0,w->contrect);
+ draw(w->img, w->img->r, colors[BACK], nil, ZP);
+ xinit(&w->text, w->textr, w->scrollr, font, w->img, colors);
+ wdecor(w);
+}
+
+static int id = 1;
+
+Window*
+wcreate(Rectangle r)
+{
+ Window *w;
+
+ w = emalloc(sizeof(Window));
+ incref(w);
+ w->id = id++;
+ w->notefd = -1;
+ w->label = estrdup("<unnamed>");
+ w->dir = estrdup(startdir);
+ wsetsize(w, r);
+ wlistpushfront(w);
+ // TMP - make dynamic
+ windows[nwindows++] = w;
+
+ w->mc.c = chancreate(sizeof(Mouse), 16);
+ w->mc.image = w->img;
+
+ w->gone = chancreate(sizeof(int), 0);
+ w->kbd = chancreate(sizeof(char*), 16);
+ w->ctl = chancreate(sizeof(Wctlmesg), 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->complete = chancreate(sizeof(Completion*), 0);
+ threadcreate(winthread, w, mainstacksize);
+
+ 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->complete);
+ free(w->label);
+ free(w);
+}
+
+static void
+wclose(Window *w)
+{
+ int i;
+
+ if(w->deleted)
+ return;
+ w->deleted = TRUE;
+ if(focused == w)
+ wfocus(nil);
+ wlistremove(w);
+ for(i = 0; i < nwindows; i++)
+ if(windows[i] == w){
+ nwindows--;
+ memmove(&windows[i], &windows[i+1], (nwindows-i)*sizeof(Window*));
+ break;
+ }
+
+ if(w->img){
+// rio does this, useful?
+// originwindow(w->img, w->img->r.min, screen->r.max);
+ freeimage(w->img);
+ }
+ w->img = nil;
+ flushimage(display, 1);
+}
+
+int
+wrelease(Window *w)
+{
+ int i;
+
+ i = decref(w);
+ if(i > 0)
+ return 0;
+ if(i < 0)
+ panic("negative ref count");
+ wclose(w);
+ wsendmsg(w, Closed, ZR, nil);
+ return 1;
+}
+
+void
+wsendmsg(Window *w, int type, Rectangle r, void *p)
+{
+ Wctlmesg cm;
+
+ cm.type = type;
+ cm.r = r;
+ cm.p = p;
+ send(w->ctl, &cm);
+}
+
+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->img->r))
+ return w;
+ return nil;
+}
+
+void
+wsetcursor(Window *w)
+{
+ if(w != cursorwin)
+ return;
+
+ if(w->holdmode)
+ setcursornormal(&whitearrow);
+ else
+ setcursornormal(w->cursorp);
+}
+
+void
+wrepaint(Window *w)
+{
+ wsetcolors(w);
+ if(!w->mouseopen)
+ xredraw(&w->text);
+ wdecor(w);
+}
+
+void
+wsetlabel(Window *w, char *label)
+{
+ free(w->label);
+ w->label = estrdup(label);
+}
+
+void
+wresize(Window *w, Rectangle r)
+{
+// TODO: maybe call wsetsize from Reshaped handler?
+ wsetsize(w, r);
+ wsendmsg(w, Reshaped, w->img->r, nil);
+}
+
+void
+wmove(Window *w, Point pos)
+{
+ /* 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(w->mouseopen){
+ Point delta = subpt(pos, w->img->r.min);
+ wresize(w, rectaddpt(w->img->r, delta));
+ }else{
+ originwindow(w->img, pos, pos);
+ wcalcrects(w);
+ xsetrects(&w->text, w->textr, w->scrollr);
+
+// TODO: Reshaped changes winname, don't want that
+ w->resized = TRUE;
+ w->mc.buttons = 0; /* avoid re-triggering clicks on resize */
+ w->mq.counter++; /* cause mouse to be re-read */
+ }
+}
+
+void
+wrmove(Window *w, Point delta)
+{
+ wmove(w, addpt(w->img->r.min, delta));
+}
+
+/* currently UNUSED */
+void
+wrmovescreen(Point delta)
+{
+ Point pos;
+ Window *w;
+
+ for(w = bottomwin; w; w = w->higher){
+ pos = addpt(w->img->r.min, delta);
+ originwindow(w->img, pos, pos);
+ wcalcrects(w);
+ xsetrects(&w->text, w->textr, w->scrollr);
+ }
+ flushimage(display, 1);
+}
+
+void
+wraise(Window *w)
+{
+ wlistremove(w);
+ wlistpushfront(w);
+ topwindow(w->img);
+ flushimage(display, 1);
+}
+
+void
+wlower(Window *w)
+{
+ wlistremove(w);
+ wlistpushback(w);
+ bottomwindow(w->img);
+ flushimage(display, 1);
+}
+
+void
+wfocus(Window *w)
+{
+ Window *prev;
+
+ if(w == focused)
+ return;
+ prev = focused;
+ focused = w;
+ if(prev)
+ wrepaint(prev);
+ if(focused)
+ wrepaint(focused);
+}
+
+void
+whide(Window *w)
+{
+ if(w->hidden)
+ return;
+ incref(w);
+ if(w == focused)
+ wfocus(nil);
+ w->hidden = TRUE;
+ originwindow(w->img, w->img->r.min, screen->r.max);
+ wrelease(w);
+}
+
+void
+wunhide(Window *w)
+{
+ if(!w->hidden)
+ return;
+ incref(w);
+ w->hidden = FALSE;
+ originwindow(w->img, w->img->r.min, w->img->r.min);
+ wfocus(w);
+ wrelease(w);
+}
+
+void
+wsethold(Window *w, int hold)
+{
+ int prev;
+
+ if(hold)
+ prev = w->holdmode++;
+ else
+ prev = --w->holdmode;
+ if(prev == 0){
+ wsetcursor(w);
+ wrepaint(w);
+ }
+}
+
+/*
+ * 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);
+// xtickupdn(x, 1);
+ 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));
+// xtickupdn(x, -1);
+ 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, 0);
+ 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);
+}
+
+int
+winctl(Window *w, int type, Rectangle r, void *p)
+{
+ Text *x;
+ int i;
+
+ x = &w->text;
+(void)p;
+(void)r;
+ switch(type){
+ case Closed:
+ wfree(w);
+ return 1;
+
+ case Deleted:
+ if(w->notefd >= 0)
+ write(w->notefd, "hangup", 6);
+ wclose(w);
+ break;
+
+ case Reshaped:
+/* TODO: all the resizing code is shit */
+ wsetname(w);
+ w->resized = 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:
+/* TODO: clean this up? */
+ draw(w->img, w->img->r, x->cols[BACK], nil, ZP);
+ wdecor(w);
+ xfill(x);
+ x->ticked = 0;
+ if(x->p0 > 0)
+ frdrawsel(x, frptofchar(x, 0), 0, x->p0, 0);
+ if(x->p1 < x->nchars)
+ frdrawsel(x, frptofchar(x, x->p1), x->p1, x->nchars, 0);
+ frdrawsel(x, frptofchar(x, x->p0), x->p0, x->p1, 1);
+ x->lastsr = ZR;
+ xscrdraw(x);
+ 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;
+ }
+ return 0;
+}
+
+static void
+winthread(void *arg)
+{
+ Window *w;
+ Text *x;
+ Rune r, *rp;
+ char *s;
+ Wctlmesg cm;
+ enum { AKbd, AMouse, ACtl, AConsWrite, AConsRead, AKbdRead, AMouseRead, 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;
+ 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[AComplete] = ALT(w->complete, &comp, CHANRCV);
+ alts[Agone] = ALT(w->gone, nil, CHANNOP);
+ alts[NALT].op = CHANEND;
+
+ for(;;){
+ if(w->deleted){ // TODO? rio checks image here
+ alts[Agone].op = CHANSND;
+ alts[AConsWrite].op = CHANNOP;
+ alts[AConsRead].op = CHANNOP;
+ alts[AKbdRead].op = CHANNOP;
+ alts[AMouseRead].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 && (w->kq.ri != w->kq.wi || w->kq.full))
+ 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;
+ }
+
+ switch(alt(alts)){
+ case AKbd:
+ if(!w->kq.full){
+ w->kq.q[w->kq.wi++] = s;
+ w->kq.wi %= nelem(w->kq.q);
+ w->kq.full = w->kq.wi == w->kq.ri;
+ }else
+ free(s);
+ if(!w->kbdopen)
+ while(w->kq.ri != w->kq.wi || w->kq.full){
+ s = w->kq.q[w->kq.ri++];
+ w->kq.ri %= nelem(w->kq.q);
+ w->kq.full = FALSE;
+ if(*s == 'c'){
+ chartorune(&r, s+1);
+ if(r)
+ wkeyctl(w, r);
+ }
+ free(s);
+ }
+ break;
+
+ case AKbdRead:
+ recv(fsc, &pair);
+ nb = 0;
+ while(w->kq.ri != w->kq.wi || w->kq.full){
+ s = w->kq.q[w->kq.ri];
+ i = strlen(s)+1;
+ if(nb+i > pair.ns)
+ break;
+ w->kq.ri = (w->kq.ri+1) % nelem(w->kq.q);
+ w->kq.full = FALSE;
+ 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;
+
+ nb = 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;
+ pair.ns = min(nb, pair.ns);
+ 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 ACtl:
+ if(winctl(w, cm.type, cm.r, cm.p)){
+ free(cnv.buf);
+ return;
+ }
+ break;
+
+ case AComplete:
+ if(w->img!=nil){
+ 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);
+ }
+}
+
+void
+wsetname(Window *w)
+{
+ int i, n;
+ char err[ERRMAX];
+
+ n = snprint(w->name, sizeof(w->name)-2, "window.%d.%d", w->id, w->namecount++);
+ for(i='A'; i<='Z'; i++){
+ if(nameimage(w->img, 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, "rio: 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);
+ free(w->label);
+ w->label = estrdup(buf);
+ }
+ snprint(buf, sizeof(buf), "/proc/%lud/notepg", (ulong)pid);
+ w->notefd = open(buf, OWRITE|OCEXEC);
+ }
+ if(ofd >= 0)
+ close(ofd);
+}
+
+void
+winshell(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() */
+ }
+ if(wrelease(w) == 0){ /* remove extra ref hanging from creation */
+ notify(nil);
+ dup(1, 2);
+ if(dir)
+ chdir(dir);
+ procexec(pidc, cmd, argv);
+ _exits("exec failed");
+ }
+}