summaryrefslogtreecommitdiff
path: root/acme/bin/source/win
diff options
context:
space:
mode:
Diffstat (limited to 'acme/bin/source/win')
-rw-r--r--acme/bin/source/win/_fs.c146
-rw-r--r--acme/bin/source/win/_main.c651
-rw-r--r--acme/bin/source/win/dat.h95
-rw-r--r--acme/bin/source/win/fs.c147
-rw-r--r--acme/bin/source/win/main.c646
-rw-r--r--acme/bin/source/win/mkfile25
-rw-r--r--acme/bin/source/win/pipe.c175
-rw-r--r--acme/bin/source/win/util.c90
-rw-r--r--acme/bin/source/win/win.c264
9 files changed, 2239 insertions, 0 deletions
diff --git a/acme/bin/source/win/_fs.c b/acme/bin/source/win/_fs.c
new file mode 100644
index 000000000..6791a21f2
--- /dev/null
+++ b/acme/bin/source/win/_fs.c
@@ -0,0 +1,146 @@
+#include <u.h>
+#include <libc.h>
+#include <fcall.h>
+#include <thread.h>
+#include <9p.h>
+#include "dat.h"
+
+Channel *fschan;
+Channel *writechan;
+
+static File *devcons, *devnew;
+
+static void
+fsread(Req *r)
+{
+ Fsevent e;
+
+ if(r->fid->file == devnew){
+ if(r->fid->aux==nil){
+ respond(r, "phase error");
+ return;
+ }
+ readstr(r, r->fid->aux);
+ respond(r, nil);
+ return;
+ }
+
+ assert(r->fid->file == devcons);
+ e.type = 'r';
+ e.r = r;
+ send(fschan, &e);
+}
+
+static void
+fsflush(Req *r)
+{
+ Fsevent e;
+
+ e.type = 'f';
+ e.r = r;
+ send(fschan, &e);
+}
+
+static void
+fswrite(Req *r)
+{
+ static Event *e[4];
+ Event *ep;
+ int i, j, nb, wid, pid;
+ Rune rune;
+ char *s;
+ char tmp[UTFmax], *t;
+ static int n, partial;
+
+ if(r->fid->file == devnew){
+ if(r->fid->aux){
+ respond(r, "already created a window");
+ return;
+ }
+ s = emalloc(r->ifcall.count+1);
+ memmove(s, r->ifcall.data, r->ifcall.count);
+ s[r->ifcall.count] = 0;
+ pid = strtol(s, &t, 0);
+ if(*t==' ')
+ t++;
+ i = newpipewin(pid, t);
+ free(s);
+ s = emalloc(32);
+ sprint(s, "%lud", (ulong)i);
+ r->fid->aux = s;
+ r->ofcall.count = r->ifcall.count;
+ respond(r, nil);
+ return;
+ }
+
+ assert(r->fid->file == devcons);
+
+ if(e[0] == nil){
+ for(i=0; i<nelem(e); i++){
+ e[i] = emalloc(sizeof(Event));
+ e[i]->c1 = 'S';
+ }
+ }
+
+ ep = e[n];
+ n = (n+1)%nelem(e);
+ assert(r->ifcall.count <= 8192); /* is this guaranteed by lib9p? */
+ nb = r->ifcall.count;
+ memmove(ep->b+partial, r->ifcall.data, nb);
+ nb += partial;
+ ep->b[nb] = '\0';
+ if(strlen(ep->b) < nb){ /* nulls in data */
+ t = ep->b;
+ for(i=j=0; i<nb; i++)
+ if(ep->b[i] != '\0')
+ t[j++] = ep->b[i];
+ nb = j;
+ t[j] = '\0';
+ }
+ /* process bytes into runes, transferring terminal partial runes into next buffer */
+ for(i=j=0; i<nb && fullrune(ep->b+i, nb-i); i+=wid,j++)
+ wid = chartorune(&rune, ep->b+i);
+ memmove(tmp, ep->b+i, nb-i);
+ partial = nb-i;
+ ep->nb = i;
+ ep->nr = j;
+ ep->b[i] = '\0';
+ if(i != 0){
+ sendp(win->cevent, ep);
+ recvp(writechan);
+ }
+ partial = nb-i;
+ memmove(e[n]->b, tmp, partial);
+ r->ofcall.count = r->ifcall.count;
+ respond(r, nil);
+}
+
+void
+fsdestroyfid(Fid *fid)
+{
+ if(fid->aux)
+ free(fid->aux);
+}
+
+Srv fs = {
+.read= fsread,
+.write= fswrite,
+.flush= fsflush,
+.destroyfid= fsdestroyfid,
+.leavefdsopen= 1,
+};
+
+void
+mountcons(void)
+{
+ fschan = chancreate(sizeof(Fsevent), 0);
+ writechan = chancreate(sizeof(void*), 0);
+ fs.tree = alloctree("win", "win", DMDIR|0555, nil);
+ devcons = createfile(fs.tree->root, "cons", "win", 0666, nil);
+ if(devcons == nil)
+ sysfatal("creating /dev/cons: %r");
+ devnew = createfile(fs.tree->root, "wnew", "win", 0666, nil);
+ if(devnew == nil)
+ sysfatal("creating /dev/wnew: %r");
+ threadpostmountsrv(&fs, nil, "/dev", MBEFORE);
+}
diff --git a/acme/bin/source/win/_main.c b/acme/bin/source/win/_main.c
new file mode 100644
index 000000000..4ac9c19d5
--- /dev/null
+++ b/acme/bin/source/win/_main.c
@@ -0,0 +1,651 @@
+#include <u.h>
+#include <libc.h>
+#include <bio.h>
+#include <thread.h>
+#include <fcall.h>
+#include <9p.h>
+#include <ctype.h>
+#include "dat.h"
+
+void mainctl(void*);
+void startcmd(char *[], int*);
+void stdout2body(void*);
+
+int debug;
+int notepg;
+int eraseinput;
+int dirty = 0;
+
+Window *win; /* the main window */
+
+void
+usage(void)
+{
+ fprint(2, "usage: win [command]\n");
+ threadexitsall("usage");
+}
+
+void
+threadmain(int argc, char *argv[])
+{
+ int i, j;
+ char *dir, *tag, *name;
+ char buf[1024], **av;
+
+ quotefmtinstall();
+ rfork(RFNAMEG);
+ ARGBEGIN{
+ case 'd':
+ debug = 1;
+ chatty9p++;
+ break;
+ case 'e':
+ eraseinput = 1;
+ break;
+ case 'D':
+{extern int _threaddebuglevel;
+ _threaddebuglevel = 1<<20;
+}
+ }ARGEND
+
+ if(argc == 0){
+ av = emalloc(3*sizeof(char*));
+ av[0] = "rc";
+ av[1] = "-i";
+ name = getenv("sysname");
+ }else{
+ av = argv;
+ name = utfrrune(av[0], '/');
+ if(name)
+ name++;
+ else
+ name = av[0];
+ }
+
+ if(getwd(buf, sizeof buf) == 0)
+ dir = "/";
+ else
+ dir = buf;
+ dir = estrdup(dir);
+ tag = estrdup(dir);
+ tag = eappend(estrdup(tag), "/-", name);
+ win = newwindow();
+ snprint(buf, sizeof buf, "%d", win->id);
+ putenv("winid", buf);
+ winname(win, tag);
+ wintagwrite(win, "Send Noscroll", 5+8);
+ threadcreate(mainctl, win, STACK);
+ mountcons();
+ threadcreate(fsloop, nil, STACK);
+ startpipe();
+ startcmd(av, &notepg);
+
+ strcpy(buf, "win");
+ j = 3;
+ for(i=0; i<argc && j+1+strlen(argv[i])+1<sizeof buf; i++){
+ strcpy(buf+j, " ");
+ strcpy(buf+j+1, argv[i]);
+ j += 1+strlen(argv[i]);
+ }
+
+ ctlprint(win->ctl, "scroll");
+ winsetdump(win, dir, buf);
+}
+
+int
+EQUAL(char *s, char *t)
+{
+ while(tolower(*s) == tolower(*t++))
+ if(*s++ == '\0')
+ return 1;
+ return 0;
+}
+
+int
+command(Window *w, char *s)
+{
+ while(*s==' ' || *s=='\t' || *s=='\n')
+ s++;
+ if(strcmp(s, "Delete")==0){
+ windel(w, 1);
+ threadexitsall(nil);
+ return 1;
+ }
+ if(strcmp(s, "Del")==0){
+ if(windel(w, 0))
+ threadexitsall(nil);
+ return 1;
+ }
+ if(EQUAL(s, "scroll")){
+ ctlprint(w->ctl, "scroll\nshow");
+ return 1;
+ }
+ if(EQUAL(s, "noscroll")){
+ ctlprint(w->ctl, "noscroll");
+ return 1;
+ }
+ return 0;
+}
+
+static long
+utfncpy(char *to, char *from, int n)
+{
+ char *end, *e;
+
+ e = to+n;
+ if(to >= e)
+ return 0;
+ end = memccpy(to, from, '\0', e - to);
+ if(end == nil){
+ end = e;
+ if(end[-1]&0x80){
+ if(end-2>=to && (end[-2]&0xE0)==0xC0)
+ return end-to;
+ if(end-3>=to && (end[-3]&0xF0)==0xE0)
+ return end-to;
+ while(end>to && (*--end&0xC0)==0x80)
+ ;
+ }
+ }else
+ end--;
+ return end - to;
+}
+
+/* sendinput and fsloop run in the same proc (can't interrupt each other). */
+static Req *q;
+static Req **eq;
+static int
+__sendinput(Window *w, ulong q0, ulong q1)
+{
+ char *s, *t;
+ int n, nb, eofchar;
+ static int partial;
+ static char tmp[UTFmax];
+ Req *r;
+ Rune rune;
+
+ if(!q)
+ return 0;
+
+ r = q;
+ n = 0;
+ if(partial){
+ Partial:
+ nb = partial;
+ if(nb > r->ifcall.count)
+ nb = r->ifcall.count;
+ memmove(r->ofcall.data, tmp, nb);
+ if(nb!=partial)
+ memmove(tmp, tmp+nb, partial-nb);
+ partial -= nb;
+ q = r->aux;
+ if(q == nil)
+ eq = &q;
+ r->aux = nil;
+ r->ofcall.count = nb;
+ if(debug)
+ fprint(2, "satisfy read with partial\n");
+ respond(r, nil);
+ return n;
+ }
+ if(q0==q1)
+ return 0;
+ s = emalloc((q1-q0)*UTFmax+1);
+ n = winread(w, q0, q1, s);
+ s[n] = '\0';
+ t = strpbrk(s, "\n\004");
+ if(t == nil){
+ free(s);
+ return 0;
+ }
+ r = q;
+ eofchar = 0;
+ if(*t == '\004'){
+ eofchar = 1;
+ *t = '\0';
+ }else
+ *++t = '\0';
+ nb = utfncpy((char*)r->ofcall.data, s, r->ifcall.count);
+ if(nb==0 && s<t && r->ifcall.count > 0){
+ partial = utfncpy(tmp, s, UTFmax);
+ assert(partial > 0);
+ chartorune(&rune, tmp);
+ partial = runelen(rune);
+ free(s);
+ n = 1;
+ goto Partial;
+ }
+ n = utfnlen(r->ofcall.data, nb);
+ if(nb==strlen(s) && eofchar)
+ n++;
+ r->ofcall.count = nb;
+ q = r->aux;
+ if(q == nil)
+ eq = &q;
+ r->aux = nil;
+ if(debug)
+ fprint(2, "read returns %lud-%lud: %.*q\n", q0, q0+n, n, r->ofcall.data);
+ respond(r, nil);
+ return n;
+}
+
+static int
+_sendinput(Window *w, ulong q0, ulong *q1)
+{
+ char buf[32];
+ int n;
+
+ n = __sendinput(w, q0, *q1);
+ if(!n || !eraseinput)
+ return n;
+ /* erase q0 to q0+n */
+ sprint(buf, "#%lud,#%lud", q0, q0+n);
+ winsetaddr(w, buf, 0);
+ write(w->data, buf, 0);
+ *q1 -= n;
+ return 0;
+}
+
+int
+sendinput(Window *w, ulong q0, ulong *q1)
+{
+ ulong n;
+ Req *oq;
+
+ n = 0;
+ do {
+ oq = q;
+ n += _sendinput(w, q0+n, q1);
+ } while(q != oq);
+ return n;
+}
+
+Event esendinput;
+void
+fsloop(void*)
+{
+ Fsevent e;
+ Req **l, *r;
+
+ eq = &q;
+ memset(&esendinput, 0, sizeof esendinput);
+ esendinput.c1 = 'C';
+ for(;;){
+ while(recv(fschan, &e) == -1)
+ ;
+ r = e.r;
+ switch(e.type){
+ case 'r':
+ *eq = r;
+ r->aux = nil;
+ eq = &r->aux;
+ /* call sendinput with hostpt and endpt */
+ sendp(win->cevent, &esendinput);
+ break;
+ case 'f':
+ for(l=&q; *l; l=&(*l)->aux){
+ if(*l == r->oldreq){
+ *l = (*l)->aux;
+ if(*l == nil)
+ eq = l;
+ respond(r->oldreq, "interrupted");
+ break;
+ }
+ }
+ respond(r, nil);
+ break;
+ }
+ }
+}
+
+void
+sendit(char *s)
+{
+// char tmp[32];
+
+ write(win->body, s, strlen(s));
+/*
+ * RSC: The problem here is that other procs can call sendit,
+ * so we lose our single-threadedness if we call sendinput.
+ * In fact, we don't even have the right queue memory,
+ * I think that we'll get a write event from the body write above,
+ * and we can do the sendinput then, from our single thread.
+ *
+ * I still need to figure out how to test this assertion for
+ * programs that use /srv/win*
+ *
+ winselect(win, "$", 0);
+ seek(win->addr, 0UL, 0);
+ if(read(win->addr, tmp, 2*12) == 2*12)
+ hostpt += sendinput(win, hostpt, atol(tmp), );
+ */
+}
+
+void
+execevent(Window *w, Event *e, int (*command)(Window*, char*))
+{
+ Event *ea, *e2;
+ int n, na, len, needfree;
+ char *s, *t;
+
+ ea = nil;
+ e2 = nil;
+ if(e->flag & 2)
+ e2 = recvp(w->cevent);
+ if(e->flag & 8){
+ ea = recvp(w->cevent);
+ na = ea->nb;
+ recvp(w->cevent);
+ }else
+ na = 0;
+
+ needfree = 0;
+ s = e->b;
+ if(e->nb==0 && (e->flag&2)){
+ s = e2->b;
+ e->q0 = e2->q0;
+ e->q1 = e2->q1;
+ e->nb = e2->nb;
+ }
+ if(e->nb==0 && e->q0<e->q1){
+ /* fetch data from window */
+ s = emalloc((e->q1-e->q0)*UTFmax+2);
+ n = winread(w, e->q0, e->q1, s);
+ s[n] = '\0';
+ needfree = 1;
+ }else
+ if(na){
+ t = emalloc(strlen(s)+1+na+2);
+ sprint(t, "%s %s", s, ea->b);
+ if(needfree)
+ free(s);
+ s = t;
+ needfree = 1;
+ }
+
+ /* if it's a known command, do it */
+ /* if it's a long message, it can't be for us anyway */
+ if(!command(w, s) && s[0]!='\0'){ /* send it as typed text */
+ /* if it's a built-in from the tag, send it back */
+ if(e->flag & 1)
+ fprint(w->event, "%c%c%d %d\n", e->c1, e->c2, e->q0, e->q1);
+ else{ /* send text to main window */
+ len = strlen(s);
+ if(len>0 && s[len-1]!='\n' && s[len-1]!='\004'){
+ if(!needfree){
+ /* if(needfree), we left room for a newline before */
+ t = emalloc(len+2);
+ strcpy(t, s);
+ s = t;
+ needfree = 1;
+ }
+ s[len++] = '\n';
+ s[len] = '\0';
+ }
+ sendit(s);
+ }
+ }
+ if(needfree)
+ free(s);
+}
+
+int
+hasboundary(Rune *r, int nr)
+{
+ int i;
+
+ for(i=0; i<nr; i++)
+ if(r[i]=='\n' || r[i]=='\004')
+ return 1;
+ return 0;
+}
+
+void
+mainctl(void *v)
+{
+ Window *w;
+ Event *e;
+ int delta, pendingS, pendingK;
+ ulong hostpt, endpt;
+ char tmp[32];
+
+ w = v;
+ proccreate(wineventproc, w, STACK);
+
+ hostpt = 0;
+ endpt = 0;
+ winsetaddr(w, "0", 0);
+ pendingS = 0;
+ pendingK = 0;
+ for(;;){
+ if(debug)
+ fprint(2, "input range %lud-%lud\n", hostpt, endpt);
+ e = recvp(w->cevent);
+ if(debug)
+ fprint(2, "msg: %C %C %d %d %d %d %q\n",
+ e->c1 ? e->c1 : ' ', e->c2 ? e->c2 : ' ', e->q0, e->q1, e->flag, e->nb, e->b);
+ switch(e->c1){
+ default:
+ Unknown:
+ fprint(2, "unknown message %c%c\n", e->c1, e->c2);
+ break;
+
+ case 'C': /* input needed for /dev/cons */
+ if(pendingS)
+ pendingK = 1;
+ else
+ hostpt += sendinput(w, hostpt, &endpt);
+ break;
+
+ case 'S': /* output to stdout */
+ sprint(tmp, "#%lud", hostpt);
+ winsetaddr(w, tmp, 0);
+ write(w->data, e->b, e->nb);
+ pendingS += utfnlen(e->b, e->nb);
+ break;
+
+ case 'E': /* write to tag or body; body happens due to sendit */
+ delta = e->q1-e->q0;
+ if(e->c2=='I'){
+ endpt += delta;
+ if(e->q0 < hostpt)
+ hostpt += delta;
+ else
+ hostpt += sendinput(w, hostpt, &endpt);
+ break;
+ }
+ if(!islower(e->c2))
+ fprint(2, "win msg: %C %C %d %d %d %d %q\n",
+ e->c1, e->c2, e->q0, e->q1, e->flag, e->nb, e->b);
+ break;
+
+ case 'F': /* generated by our actions (specifically case 'S' above) */
+ delta = e->q1-e->q0;
+ if(e->c2=='D'){
+ /* we know about the delete by _sendinput */
+ break;
+ }
+ if(e->c2=='I'){
+ pendingS -= e->q1 - e->q0;
+ if(pendingS < 0)
+ fprint(2, "win: pendingS = %d\n", pendingS);
+ if(e->q0 != hostpt)
+ fprint(2, "win: insert at %d expected %lud\n", e->q0, hostpt);
+ endpt += delta;
+ hostpt += delta;
+ sendp(writechan, nil);
+ if(pendingS == 0 && pendingK){
+ pendingK = 0;
+ hostpt += sendinput(w, hostpt, &endpt);
+ }
+ break;
+ }
+ if(!islower(e->c2))
+ fprint(2, "win msg: %C %C %d %d %d %d %q\n",
+ e->c1, e->c2, e->q0, e->q1, e->flag, e->nb, e->b);
+ break;
+
+ case 'K':
+ delta = e->q1-e->q0;
+ switch(e->c2){
+ case 'D':
+ endpt -= delta;
+ if(e->q1 < hostpt)
+ hostpt -= delta;
+ else if(e->q0 < hostpt)
+ hostpt = e->q0;
+ break;
+ case 'I':
+ delta = e->q1 - e->q0;
+ endpt += delta;
+ if(endpt < e->q1) /* just in case */
+ endpt = e->q1;
+ if(e->q0 < hostpt)
+ hostpt += delta;
+ if(e->nr>0 && e->r[e->nr-1]==0x7F){
+ write(notepg, "interrupt", 9);
+ hostpt = endpt;
+ break;
+ }
+ if(e->q0 >= hostpt
+ && hasboundary(e->r, e->nr)){
+ /*
+ * If we are between the S message (which
+ * we processed by inserting text in the
+ * window) and the F message notifying us
+ * that the text has been inserted, then our
+ * impression of the hostpt and acme's
+ * may be different. This could be seen if you
+ * hit enter a bunch of times in a con
+ * session. To work around the unreliability,
+ * only send input if we don't have an S pending.
+ * The same race occurs between when a character
+ * is typed and when we get notice of it, but
+ * since characters tend to be typed at the end
+ * of the buffer, we don't run into it. There's
+ * no workaround possible for this typing race,
+ * since we can't tell when the user has typed
+ * something but we just haven't been notified.
+ */
+ if(pendingS)
+ pendingK = 1;
+ else
+ hostpt += sendinput(w, hostpt, &endpt);
+ }
+ break;
+ }
+ break;
+
+ case 'M': /* mouse */
+ delta = e->q1-e->q0;
+ switch(e->c2){
+ case 'x':
+ case 'X':
+ execevent(w, e, command);
+ break;
+
+ case 'l': /* reflect all searches back to acme */
+ case 'L':
+ if(e->flag & 2)
+ recvp(w->cevent);
+ winwriteevent(w, e);
+ break;
+
+ case 'I':
+ endpt += delta;
+ if(e->q0 < hostpt)
+ hostpt += delta;
+ else
+ hostpt += sendinput(w, hostpt, &endpt);
+ break;
+
+ case 'D':
+ endpt -= delta;
+ if(e->q1 < hostpt)
+ hostpt -= delta;
+ else if(e->q0 < hostpt)
+ hostpt = e->q0;
+ break;
+ case 'd': /* modify away; we don't care */
+ case 'i':
+ break;
+
+ default:
+ goto Unknown;
+ }
+ }
+ }
+}
+
+enum
+{
+ NARGS = 100,
+ NARGCHAR = 8*1024,
+ EXECSTACK = STACK+(NARGS+1)*sizeof(char*)+NARGCHAR
+};
+
+struct Exec
+{
+ char **argv;
+ Channel *cpid;
+};
+
+int
+lookinbin(char *s)
+{
+ if(s[0] == '/')
+ return 0;
+ if(s[0]=='.' && s[1]=='/')
+ return 0;
+ if(s[0]=='.' && s[1]=='.' && s[2]=='/')
+ return 0;
+ return 1;
+}
+
+/* adapted from mail. not entirely free of details from that environment */
+void
+execproc(void *v)
+{
+ struct Exec *e;
+ char *cmd, **av;
+ Channel *cpid;
+
+ e = v;
+ rfork(RFCFDG|RFNOTEG);
+ av = e->argv;
+ close(0);
+ open("/dev/cons", OREAD);
+ close(1);
+ open("/dev/cons", OWRITE);
+ dup(1, 2);
+ cpid = e->cpid;
+ free(e);
+ procexec(cpid, av[0], av);
+ if(lookinbin(av[0])){
+ cmd = estrstrdup("/bin/", av[0]);
+ procexec(cpid, cmd, av);
+ }
+ error("can't exec %s: %r", av[0]);
+}
+
+void
+startcmd(char *argv[], int *notepg)
+{
+ struct Exec *e;
+ Channel *cpid;
+ char buf[64];
+ int pid;
+
+ e = emalloc(sizeof(struct Exec));
+ e->argv = argv;
+ cpid = chancreate(sizeof(ulong), 0);
+ e->cpid = cpid;
+ sprint(buf, "/mnt/wsys/%d", win->id);
+ bind(buf, "/dev/acme", MREPL);
+ proccreate(execproc, e, EXECSTACK);
+ do
+ pid = recvul(cpid);
+ while(pid == -1);
+ sprint(buf, "/proc/%d/notepg", pid);
+ *notepg = open(buf, OWRITE);
+}
diff --git a/acme/bin/source/win/dat.h b/acme/bin/source/win/dat.h
new file mode 100644
index 000000000..38d31dcf0
--- /dev/null
+++ b/acme/bin/source/win/dat.h
@@ -0,0 +1,95 @@
+typedef struct Fsevent Fsevent;
+typedef struct Event Event;
+typedef struct Message Message;
+typedef struct Window Window;
+
+enum
+{
+ STACK = 8192,
+ NPIPEDATA = 8000,
+ NPIPE = NPIPEDATA+32,
+ /* EVENTSIZE is really 256 in acme, but we use events internally and want bigger buffers */
+ EVENTSIZE = 8192,
+ NEVENT = 5,
+};
+
+struct Fsevent
+{
+ int type;
+ void *r;
+};
+
+struct Event
+{
+ int c1;
+ int c2;
+ int q0;
+ int q1;
+ int flag;
+ int nb;
+ int nr;
+ char b[EVENTSIZE*UTFmax+1];
+ Rune r[EVENTSIZE+1];
+};
+
+struct Window
+{
+ /* file descriptors */
+ int ctl;
+ int event;
+ int addr;
+ int data;
+ int body;
+
+ /* event input */
+ char buf[512];
+ char *bufp;
+ int nbuf;
+ Event e[NEVENT];
+
+ int id;
+ int open;
+ Channel *cevent;
+};
+
+extern Window* newwindow(void);
+extern int winopenfile(Window*, char*);
+extern void wintagwrite(Window*, char*, int);
+extern void winname(Window*, char*);
+extern void winwriteevent(Window*, Event*);
+extern int winread(Window*, uint, uint, char*);
+extern int windel(Window*, int);
+extern void wingetevent(Window*, Event*);
+extern void wineventproc(void*);
+extern void winclean(Window*);
+extern int winselect(Window*, char*, int);
+extern int winsetaddr(Window*, char*, int);
+extern void windormant(Window*);
+extern void winsetdump(Window*, char*, char*);
+
+extern void ctlprint(int, char*, ...);
+extern void* emalloc(uint);
+extern char* estrdup(char*);
+extern char* estrstrdup(char*, char*);
+extern char* egrow(char*, char*, char*);
+extern char* eappend(char*, char*, char*);
+extern void error(char*, ...);
+
+extern void startpipe(void);
+extern void sendit(char*);
+extern void execevent(Window *w, Event *e, int (*)(Window*, char*));
+
+extern void mountcons(void);
+extern void fsloop(void*);
+
+extern int newpipewin(int, char*);
+extern void startpipe(void);
+extern int pipecommand(Window*, char*);
+extern void pipectl(void*);
+
+#pragma varargck argpos error 1
+#pragma varargck argpos ctlprint 2
+
+extern Window *win;
+extern Channel *fschan, *writechan;
+
diff --git a/acme/bin/source/win/fs.c b/acme/bin/source/win/fs.c
new file mode 100644
index 000000000..6c41eb072
--- /dev/null
+++ b/acme/bin/source/win/fs.c
@@ -0,0 +1,147 @@
+#include <u.h>
+#include <libc.h>
+#include <fcall.h>
+#include <thread.h>
+#include <9p.h>
+#include "dat.h"
+
+Channel *fschan;
+Channel *writechan;
+
+static File *devcons, *devnew;
+
+static void
+fsread(Req *r)
+{
+ Fsevent e;
+
+ if(r->fid->file == devnew){
+ if(r->fid->aux==nil){
+ respond(r, "phase error");
+ return;
+ }
+ readstr(r, r->fid->aux);
+ respond(r, nil);
+ return;
+ }
+
+ assert(r->fid->file == devcons);
+ e.type = 'r';
+ e.r = r;
+ send(fschan, &e);
+}
+
+static void
+fsflush(Req *r)
+{
+ Fsevent e;
+
+ e.type = 'f';
+ e.r = r;
+ send(fschan, &e);
+}
+
+static void
+fswrite(Req *r)
+{
+ static Event *e[4];
+ Event *ep;
+ int i, j, ei, nb, wid, pid;
+ Rune rune;
+ char *s;
+ char tmp[UTFmax], *t;
+ static int n, partial;
+
+ if(r->fid->file == devnew){
+ if(r->fid->aux){
+ respond(r, "already created a window");
+ return;
+ }
+ s = emalloc(r->ifcall.count+1);
+ memmove(s, r->ifcall.data, r->ifcall.count);
+ s[r->ifcall.count] = 0;
+ pid = strtol(s, &t, 0);
+ if(*t==' ')
+ t++;
+ i = newpipewin(pid, t);
+ free(s);
+ s = emalloc(32);
+ sprint(s, "%lud", (ulong)i);
+ r->fid->aux = s;
+ r->ofcall.count = r->ifcall.count;
+ respond(r, nil);
+ return;
+ }
+
+ assert(r->fid->file == devcons);
+
+ if(e[0] == nil){
+ for(i=0; i<nelem(e); i++){
+ e[i] = emalloc(sizeof(Event));
+ e[i]->c1 = 'S';
+ }
+ }
+
+ ep = e[n];
+ n = (n+1)%nelem(e);
+ assert(r->ifcall.count <= 8192); /* is this guaranteed by lib9p? */
+ nb = r->ifcall.count;
+ memmove(ep->b+partial, r->ifcall.data, nb);
+ nb += partial;
+ ep->b[nb] = '\0';
+ if(strlen(ep->b) < nb){ /* nulls in data */
+ t = ep->b;
+ for(i=j=0; i<nb; i++)
+ if(ep->b[i] != '\0')
+ t[j++] = ep->b[i];
+ nb = j;
+ t[j] = '\0';
+ }
+ ei = nb>8192? 8192 : nb;
+ /* process bytes into runes, transferring terminal partial runes into next buffer */
+ for(i=j=0; i<ei && fullrune(ep->b+i, ei-i); i+=wid,j++)
+ wid = chartorune(&rune, ep->b+i);
+ memmove(tmp, ep->b+i, nb-i);
+ partial = nb-i;
+ ep->nb = i;
+ ep->nr = j;
+ ep->b[i] = '\0';
+ if(i != 0){
+ sendp(win->cevent, ep);
+ recvp(writechan);
+ }
+ partial = nb-i;
+ memmove(e[n]->b, tmp, partial);
+ r->ofcall.count = r->ifcall.count;
+ respond(r, nil);
+}
+
+void
+fsdestroyfid(Fid *fid)
+{
+ if(fid->aux)
+ free(fid->aux);
+}
+
+Srv fs = {
+.read= fsread,
+.write= fswrite,
+.flush= fsflush,
+.destroyfid= fsdestroyfid,
+.leavefdsopen= 1,
+};
+
+void
+mountcons(void)
+{
+ fschan = chancreate(sizeof(Fsevent), 0);
+ writechan = chancreate(sizeof(void*), 0);
+ fs.tree = alloctree("win", "win", DMDIR|0555, nil);
+ devcons = createfile(fs.tree->root, "cons", "win", 0666, nil);
+ if(devcons == nil)
+ sysfatal("creating /dev/cons: %r");
+ devnew = createfile(fs.tree->root, "wnew", "win", 0666, nil);
+ if(devnew == nil)
+ sysfatal("creating /dev/wnew: %r");
+ threadpostmountsrv(&fs, nil, "/dev", MBEFORE);
+}
diff --git a/acme/bin/source/win/main.c b/acme/bin/source/win/main.c
new file mode 100644
index 000000000..916f2b05f
--- /dev/null
+++ b/acme/bin/source/win/main.c
@@ -0,0 +1,646 @@
+#include <u.h>
+#include <libc.h>
+#include <bio.h>
+#include <thread.h>
+#include <fcall.h>
+#include <9p.h>
+#include <ctype.h>
+#include "dat.h"
+
+void mainctl(void*);
+void startcmd(char *[], int*);
+void stdout2body(void*);
+
+int debug;
+int notepg;
+int eraseinput;
+int dirty = 0;
+
+Window *win; /* the main window */
+
+void
+usage(void)
+{
+ fprint(2, "usage: win [command]\n");
+ threadexitsall("usage");
+}
+
+void
+threadmain(int argc, char *argv[])
+{
+ int i, j;
+ char *dir, *tag, *name;
+ char buf[1024], **av;
+
+ quotefmtinstall();
+ rfork(RFNAMEG);
+ ARGBEGIN{
+ case 'd':
+ debug = 1;
+ chatty9p++;
+ break;
+ case 'e':
+ eraseinput = 1;
+ break;
+ case 'D':
+{extern int _threaddebuglevel;
+ _threaddebuglevel = 1<<20;
+}
+ }ARGEND
+
+ if(argc == 0){
+ av = emalloc(3*sizeof(char*));
+ av[0] = "rc";
+ av[1] = "-i";
+ name = getenv("sysname");
+ }else{
+ av = argv;
+ name = utfrrune(av[0], '/');
+ if(name)
+ name++;
+ else
+ name = av[0];
+ }
+
+ if(getwd(buf, sizeof buf) == 0)
+ dir = "/";
+ else
+ dir = buf;
+ dir = estrdup(dir);
+ tag = estrdup(dir);
+ tag = eappend(estrdup(tag), "/-", name);
+ win = newwindow();
+ snprint(buf, sizeof buf, "%d", win->id);
+ putenv("winid", buf);
+ winname(win, tag);
+ wintagwrite(win, "Send Noscroll", 5+8);
+ threadcreate(mainctl, win, STACK);
+ mountcons();
+ threadcreate(fsloop, nil, STACK);
+ startpipe();
+ startcmd(av, &notepg);
+
+ strcpy(buf, "win");
+ j = 3;
+ for(i=0; i<argc && j+1+strlen(argv[i])+1<sizeof buf; i++){
+ strcpy(buf+j, " ");
+ strcpy(buf+j+1, argv[i]);
+ j += 1+strlen(argv[i]);
+ }
+
+ ctlprint(win->ctl, "scroll");
+ winsetdump(win, dir, buf);
+}
+
+int
+EQUAL(char *s, char *t)
+{
+ while(tolower(*s) == tolower(*t++))
+ if(*s++ == '\0')
+ return 1;
+ return 0;
+}
+
+int
+command(Window *w, char *s)
+{
+ while(*s==' ' || *s=='\t' || *s=='\n')
+ s++;
+ if(strcmp(s, "Delete")==0 || strcmp(s, "Del")==0){
+ windel(w, 1);
+ threadexitsall(nil);
+ return 1;
+ }
+ if(EQUAL(s, "scroll")){
+ ctlprint(w->ctl, "scroll\nshow");
+ return 1;
+ }
+ if(EQUAL(s, "noscroll")){
+ ctlprint(w->ctl, "noscroll");
+ return 1;
+ }
+ return 0;
+}
+
+static long
+utfncpy(char *to, char *from, int n)
+{
+ char *end, *e;
+
+ e = to+n;
+ if(to >= e)
+ return 0;
+ end = memccpy(to, from, '\0', e - to);
+ if(end == nil){
+ end = e;
+ if(end[-1]&0x80){
+ if(end-2>=to && (end[-2]&0xE0)==0xC0)
+ return end-to;
+ if(end-3>=to && (end[-3]&0xF0)==0xE0)
+ return end-to;
+ while(end>to && (*--end&0xC0)==0x80)
+ ;
+ }
+ }else
+ end--;
+ return end - to;
+}
+
+/* sendinput and fsloop run in the same proc (can't interrupt each other). */
+static Req *q;
+static Req **eq;
+static int
+__sendinput(Window *w, ulong q0, ulong q1)
+{
+ char *s, *t;
+ int n, nb, eofchar;
+ static int partial;
+ static char tmp[UTFmax];
+ Req *r;
+ Rune rune;
+
+ if(!q)
+ return 0;
+
+ r = q;
+ n = 0;
+ if(partial){
+ Partial:
+ nb = partial;
+ if(nb > r->ifcall.count)
+ nb = r->ifcall.count;
+ memmove(r->ofcall.data, tmp, nb);
+ if(nb!=partial)
+ memmove(tmp, tmp+nb, partial-nb);
+ partial -= nb;
+ q = r->aux;
+ if(q == nil)
+ eq = &q;
+ r->aux = nil;
+ r->ofcall.count = nb;
+ if(debug)
+ fprint(2, "satisfy read with partial\n");
+ respond(r, nil);
+ return n;
+ }
+ if(q0==q1)
+ return 0;
+ s = emalloc((q1-q0)*UTFmax+1);
+ n = winread(w, q0, q1, s);
+ s[n] = '\0';
+ t = strpbrk(s, "\n\004");
+ if(t == nil){
+ free(s);
+ return 0;
+ }
+ r = q;
+ eofchar = 0;
+ if(*t == '\004'){
+ eofchar = 1;
+ *t = '\0';
+ }else
+ *++t = '\0';
+ nb = utfncpy((char*)r->ofcall.data, s, r->ifcall.count);
+ if(nb==0 && s<t && r->ifcall.count > 0){
+ partial = utfncpy(tmp, s, UTFmax);
+ assert(partial > 0);
+ chartorune(&rune, tmp);
+ partial = runelen(rune);
+ free(s);
+ n = 1;
+ goto Partial;
+ }
+ n = utfnlen(r->ofcall.data, nb);
+ if(nb==strlen(s) && eofchar)
+ n++;
+ r->ofcall.count = nb;
+ q = r->aux;
+ if(q == nil)
+ eq = &q;
+ r->aux = nil;
+ if(debug)
+ fprint(2, "read returns %lud-%lud: %.*q\n", q0, q0+n, n, r->ofcall.data);
+ respond(r, nil);
+ return n;
+}
+
+static int
+_sendinput(Window *w, ulong q0, ulong *q1)
+{
+ char buf[32];
+ int n;
+
+ n = __sendinput(w, q0, *q1);
+ if(!n || !eraseinput)
+ return n;
+ /* erase q0 to q0+n */
+ sprint(buf, "#%lud,#%lud", q0, q0+n);
+ winsetaddr(w, buf, 0);
+ write(w->data, buf, 0);
+ *q1 -= n;
+ return 0;
+}
+
+int
+sendinput(Window *w, ulong q0, ulong *q1)
+{
+ ulong n;
+ Req *oq;
+
+ n = 0;
+ do {
+ oq = q;
+ n += _sendinput(w, q0+n, q1);
+ } while(q != oq);
+ return n;
+}
+
+Event esendinput;
+void
+fsloop(void*)
+{
+ Fsevent e;
+ Req **l, *r;
+
+ eq = &q;
+ memset(&esendinput, 0, sizeof esendinput);
+ esendinput.c1 = 'C';
+ for(;;){
+ while(recv(fschan, &e) == -1)
+ ;
+ r = e.r;
+ switch(e.type){
+ case 'r':
+ *eq = r;
+ r->aux = nil;
+ eq = &r->aux;
+ /* call sendinput with hostpt and endpt */
+ sendp(win->cevent, &esendinput);
+ break;
+ case 'f':
+ for(l=&q; *l; l=&(*l)->aux){
+ if(*l == r->oldreq){
+ *l = (*l)->aux;
+ if(*l == nil)
+ eq = l;
+ respond(r->oldreq, "interrupted");
+ break;
+ }
+ }
+ respond(r, nil);
+ break;
+ }
+ }
+}
+
+void
+sendit(char *s)
+{
+// char tmp[32];
+
+ write(win->body, s, strlen(s));
+/*
+ * RSC: The problem here is that other procs can call sendit,
+ * so we lose our single-threadedness if we call sendinput.
+ * In fact, we don't even have the right queue memory,
+ * I think that we'll get a write event from the body write above,
+ * and we can do the sendinput then, from our single thread.
+ *
+ * I still need to figure out how to test this assertion for
+ * programs that use /srv/win*
+ *
+ winselect(win, "$", 0);
+ seek(win->addr, 0UL, 0);
+ if(read(win->addr, tmp, 2*12) == 2*12)
+ hostpt += sendinput(win, hostpt, atol(tmp), );
+ */
+}
+
+void
+execevent(Window *w, Event *e, int (*command)(Window*, char*))
+{
+ Event *ea, *e2;
+ int n, na, len, needfree;
+ char *s, *t;
+
+ ea = nil;
+ e2 = nil;
+ if(e->flag & 2)
+ e2 = recvp(w->cevent);
+ if(e->flag & 8){
+ ea = recvp(w->cevent);
+ na = ea->nb;
+ recvp(w->cevent);
+ }else
+ na = 0;
+
+ needfree = 0;
+ s = e->b;
+ if(e->nb==0 && (e->flag&2)){
+ s = e2->b;
+ e->q0 = e2->q0;
+ e->q1 = e2->q1;
+ e->nb = e2->nb;
+ }
+ if(e->nb==0 && e->q0<e->q1){
+ /* fetch data from window */
+ s = emalloc((e->q1-e->q0)*UTFmax+2);
+ n = winread(w, e->q0, e->q1, s);
+ s[n] = '\0';
+ needfree = 1;
+ }else
+ if(na){
+ t = emalloc(strlen(s)+1+na+2);
+ sprint(t, "%s %s", s, ea->b);
+ if(needfree)
+ free(s);
+ s = t;
+ needfree = 1;
+ }
+
+ /* if it's a known command, do it */
+ /* if it's a long message, it can't be for us anyway */
+ if(!command(w, s) && s[0]!='\0'){ /* send it as typed text */
+ /* if it's a built-in from the tag, send it back */
+ if(e->flag & 1)
+ fprint(w->event, "%c%c%d %d\n", e->c1, e->c2, e->q0, e->q1);
+ else{ /* send text to main window */
+ len = strlen(s);
+ if(len>0 && s[len-1]!='\n' && s[len-1]!='\004'){
+ if(!needfree){
+ /* if(needfree), we left room for a newline before */
+ t = emalloc(len+2);
+ strcpy(t, s);
+ s = t;
+ needfree = 1;
+ }
+ s[len++] = '\n';
+ s[len] = '\0';
+ }
+ sendit(s);
+ }
+ }
+ if(needfree)
+ free(s);
+}
+
+int
+hasboundary(Rune *r, int nr)
+{
+ int i;
+
+ for(i=0; i<nr; i++)
+ if(r[i]=='\n' || r[i]=='\004')
+ return 1;
+ return 0;
+}
+
+void
+mainctl(void *v)
+{
+ Window *w;
+ Event *e;
+ int delta, pendingS, pendingK;
+ ulong hostpt, endpt;
+ char tmp[32];
+
+ w = v;
+ proccreate(wineventproc, w, STACK);
+
+ hostpt = 0;
+ endpt = 0;
+ winsetaddr(w, "0", 0);
+ pendingS = 0;
+ pendingK = 0;
+ for(;;){
+ if(debug)
+ fprint(2, "input range %lud-%lud\n", hostpt, endpt);
+ e = recvp(w->cevent);
+ if(debug)
+ fprint(2, "msg: %C %C %d %d %d %d %q\n",
+ e->c1 ? e->c1 : ' ', e->c2 ? e->c2 : ' ', e->q0, e->q1, e->flag, e->nb, e->b);
+ switch(e->c1){
+ default:
+ Unknown:
+ fprint(2, "unknown message %c%c\n", e->c1, e->c2);
+ break;
+
+ case 'C': /* input needed for /dev/cons */
+ if(pendingS)
+ pendingK = 1;
+ else
+ hostpt += sendinput(w, hostpt, &endpt);
+ break;
+
+ case 'S': /* output to stdout */
+ sprint(tmp, "#%lud", hostpt);
+ winsetaddr(w, tmp, 0);
+ write(w->data, e->b, e->nb);
+ pendingS += e->nr;
+ break;
+
+ case 'E': /* write to tag or body; body happens due to sendit */
+ delta = e->q1-e->q0;
+ if(e->c2=='I'){
+ endpt += delta;
+ if(e->q0 < hostpt)
+ hostpt += delta;
+ else
+ hostpt += sendinput(w, hostpt, &endpt);
+ break;
+ }
+ if(!islower(e->c2))
+ fprint(2, "win msg: %C %C %d %d %d %d %q\n",
+ e->c1, e->c2, e->q0, e->q1, e->flag, e->nb, e->b);
+ break;
+
+ case 'F': /* generated by our actions (specifically case 'S' above) */
+ delta = e->q1-e->q0;
+ if(e->c2=='D'){
+ /* we know about the delete by _sendinput */
+ break;
+ }
+ if(e->c2=='I'){
+ pendingS -= e->q1 - e->q0;
+ if(pendingS < 0)
+ fprint(2, "win: pendingS = %d\n", pendingS);
+ if(e->q0 != hostpt)
+ fprint(2, "win: insert at %d expected %lud\n", e->q0, hostpt);
+ endpt += delta;
+ hostpt += delta;
+ sendp(writechan, nil);
+ if(pendingS == 0 && pendingK){
+ pendingK = 0;
+ hostpt += sendinput(w, hostpt, &endpt);
+ }
+ break;
+ }
+ if(!islower(e->c2))
+ fprint(2, "win msg: %C %C %d %d %d %d %q\n",
+ e->c1, e->c2, e->q0, e->q1, e->flag, e->nb, e->b);
+ break;
+
+ case 'K':
+ delta = e->q1-e->q0;
+ switch(e->c2){
+ case 'D':
+ endpt -= delta;
+ if(e->q1 < hostpt)
+ hostpt -= delta;
+ else if(e->q0 < hostpt)
+ hostpt = e->q0;
+ break;
+ case 'I':
+ delta = e->q1 - e->q0;
+ endpt += delta;
+ if(endpt < e->q1) /* just in case */
+ endpt = e->q1;
+ if(e->q0 < hostpt)
+ hostpt += delta;
+ if(e->nr>0 && e->r[e->nr-1]==0x7F){
+ write(notepg, "interrupt", 9);
+ hostpt = endpt;
+ break;
+ }
+ if(e->q0 >= hostpt
+ && hasboundary(e->r, e->nr)){
+ /*
+ * If we are between the S message (which
+ * we processed by inserting text in the
+ * window) and the F message notifying us
+ * that the text has been inserted, then our
+ * impression of the hostpt and acme's
+ * may be different. This could be seen if you
+ * hit enter a bunch of times in a con
+ * session. To work around the unreliability,
+ * only send input if we don't have an S pending.
+ * The same race occurs between when a character
+ * is typed and when we get notice of it, but
+ * since characters tend to be typed at the end
+ * of the buffer, we don't run into it. There's
+ * no workaround possible for this typing race,
+ * since we can't tell when the user has typed
+ * something but we just haven't been notified.
+ */
+ if(pendingS)
+ pendingK = 1;
+ else
+ hostpt += sendinput(w, hostpt, &endpt);
+ }
+ break;
+ }
+ break;
+
+ case 'M': /* mouse */
+ delta = e->q1-e->q0;
+ switch(e->c2){
+ case 'x':
+ case 'X':
+ execevent(w, e, command);
+ break;
+
+ case 'l': /* reflect all searches back to acme */
+ case 'L':
+ if(e->flag & 2)
+ recvp(w->cevent);
+ winwriteevent(w, e);
+ break;
+
+ case 'I':
+ endpt += delta;
+ if(e->q0 < hostpt)
+ hostpt += delta;
+ else
+ hostpt += sendinput(w, hostpt, &endpt);
+ break;
+
+ case 'D':
+ endpt -= delta;
+ if(e->q1 < hostpt)
+ hostpt -= delta;
+ else if(e->q0 < hostpt)
+ hostpt = e->q0;
+ break;
+ case 'd': /* modify away; we don't care */
+ case 'i':
+ break;
+
+ default:
+ goto Unknown;
+ }
+ }
+ }
+}
+
+enum
+{
+ NARGS = 100,
+ NARGCHAR = 8*1024,
+ EXECSTACK = STACK+(NARGS+1)*sizeof(char*)+NARGCHAR
+};
+
+struct Exec
+{
+ char **argv;
+ Channel *cpid;
+};
+
+int
+lookinbin(char *s)
+{
+ if(s[0] == '/')
+ return 0;
+ if(s[0]=='.' && s[1]=='/')
+ return 0;
+ if(s[0]=='.' && s[1]=='.' && s[2]=='/')
+ return 0;
+ return 1;
+}
+
+/* adapted from mail. not entirely free of details from that environment */
+void
+execproc(void *v)
+{
+ struct Exec *e;
+ char *cmd, **av;
+ Channel *cpid;
+
+ e = v;
+ rfork(RFCFDG|RFNOTEG);
+ av = e->argv;
+ close(0);
+ open("/dev/cons", OREAD);
+ close(1);
+ open("/dev/cons", OWRITE);
+ dup(1, 2);
+ cpid = e->cpid;
+ free(e);
+ procexec(cpid, av[0], av);
+ if(lookinbin(av[0])){
+ cmd = estrstrdup("/bin/", av[0]);
+ procexec(cpid, cmd, av);
+ }
+ error("can't exec %s: %r", av[0]);
+}
+
+void
+startcmd(char *argv[], int *notepg)
+{
+ struct Exec *e;
+ Channel *cpid;
+ char buf[64];
+ int pid;
+
+ e = emalloc(sizeof(struct Exec));
+ e->argv = argv;
+ cpid = chancreate(sizeof(ulong), 0);
+ e->cpid = cpid;
+ sprint(buf, "/mnt/wsys/%d", win->id);
+ bind(buf, "/dev/acme", MREPL);
+ proccreate(execproc, e, EXECSTACK);
+ do
+ pid = recvul(cpid);
+ while(pid == -1);
+ sprint(buf, "/proc/%d/notepg", pid);
+ *notepg = open(buf, OWRITE);
+}
diff --git a/acme/bin/source/win/mkfile b/acme/bin/source/win/mkfile
new file mode 100644
index 000000000..608f95b86
--- /dev/null
+++ b/acme/bin/source/win/mkfile
@@ -0,0 +1,25 @@
+</$objtype/mkfile
+
+TARG=win
+OFILES=\
+ fs.$O\
+ main.$O\
+ pipe.$O\
+ util.$O\
+ win.$O
+
+HFILES=dat.h
+LIB=/$objtype/lib/lib9p.a
+
+BIN=/acme/bin/$objtype
+</sys/src/cmd/mkone
+
+UPDATE=\
+ mkfile\
+ $HFILES\
+ ${OFILES:%.$O=%.c}\
+ ${TARG:%=/acme/bin/$objtype/%}\
+
+syms:V:
+ 8c -a main.c >syms
+ 8c -aa util.c win.c >>syms
diff --git a/acme/bin/source/win/pipe.c b/acme/bin/source/win/pipe.c
new file mode 100644
index 000000000..280f726c9
--- /dev/null
+++ b/acme/bin/source/win/pipe.c
@@ -0,0 +1,175 @@
+#include <u.h>
+#include <libc.h>
+#include <fcall.h>
+#include <thread.h>
+#include <9p.h>
+#include "dat.h"
+
+typedef struct Wpid Wpid;
+struct Wpid
+{
+ int pid;
+ Window *w;
+ Wpid *next;
+};
+
+void pipectl(void*);
+
+int pipefd;
+Wpid *wpid;
+int snarffd;
+Channel *newpipechan;
+
+int
+newpipewin(int pid, char *p)
+{
+ int id;
+ Window *w;
+ Wpid *wp;
+
+ w = newwindow();
+ winname(w, p);
+ wintagwrite(w, "Send ", 5);
+ wp = emalloc(sizeof(Wpid));
+ wp->pid = pid;
+ wp->w = w;
+ wp->next = wpid; /* BUG: this happens in fsread proc (we don't use wpid, so it's okay) */
+ wpid = wp;
+ id = w->id;
+ sendp(newpipechan, w);
+ return id;
+}
+
+int
+pipecommand(Window *w, char *s)
+{
+ ulong q0, q1;
+ char tmp[32], *t;
+ int n, k;
+
+ while(*s==' ' || *s=='\t' || *s=='\n')
+ s++;
+ if(strcmp(s, "Delete")==0){
+ windel(w, 1);
+ threadexits(nil);
+ return 1;
+ }
+ if(strcmp(s, "Del")==0){
+ if(windel(w, 0))
+ threadexits(nil);
+ return 1;
+ }
+ if(strcmp(s, "Send") == 0){
+ if(w->addr < 0)
+ w->addr = winopenfile(w, "addr");
+ ctlprint(w->ctl, "addr=dot\n");
+ seek(w->addr, 0UL, 0);
+ if(read(w->addr, tmp, 2*12) == 2*12){
+ q0 = atol(tmp+0*12);
+ q1 = atol(tmp+1*12);
+ if(q0 == q1){
+ t = nil;
+ k = 0;
+ if(snarffd > 0){
+ seek(0, snarffd, 0);
+ for(;;){
+ t = realloc(t, k+8192+2);
+ if(t == nil)
+ error("alloc failed: %r\n");
+ n = read(snarffd, t+k, 8192);
+ if(n <= 0)
+ break;
+ k += n;
+ }
+ t[k] = 0;
+ }
+ }else{
+ t = emalloc((q1-q0)*UTFmax+2);
+ winread(w, q0, q1, t);
+ k = strlen(t);
+ }
+ if(t!=nil && t[0]!='\0'){
+ if(t[k-1]!='\n' && t[k-1]!='\004'){
+ t[k++] = '\n';
+ t[k] = '\0';
+ }
+ sendit(t);
+ }
+ free(t);
+ }
+ return 1;
+ }
+ return 0;
+}
+
+void
+pipectl(void *v)
+{
+ Window *w;
+ Event *e;
+
+ w = v;
+ proccreate(wineventproc, w, STACK);
+
+ windormant(w);
+ winsetaddr(w, "0", 0);
+ for(;;){
+ e = recvp(w->cevent);
+ switch(e->c1){
+ default:
+ Unknown:
+ fprint(2, "unknown message %c%c\n", e->c1, e->c2);
+ break;
+
+ case 'E': /* write to body; can't affect us */
+ break;
+
+ case 'F': /* generated by our actions; ignore */
+ break;
+
+ case 'K': /* ignore */
+ break;
+
+ case 'M':
+ switch(e->c2){
+ case 'x':
+ case 'X':
+ execevent(w, e, pipecommand);
+ break;
+
+ case 'l': /* reflect all searches back to acme */
+ case 'L':
+ if(e->flag & 2)
+ recvp(w->cevent);
+ winwriteevent(w, e);
+ break;
+
+ case 'I': /* modify away; we don't care */
+ case 'i':
+ case 'D':
+ case 'd':
+ break;
+
+ default:
+ goto Unknown;
+ }
+ }
+ }
+}
+
+void
+newpipethread(void*)
+{
+ Window *w;
+
+ while(w = recvp(newpipechan))
+ threadcreate(pipectl, w, STACK);
+}
+
+void
+startpipe(void)
+{
+ newpipechan = chancreate(sizeof(Window*), 0);
+ threadcreate(newpipethread, nil, STACK);
+ snarffd = open("/dev/snarf", OREAD|OCEXEC);
+}
diff --git a/acme/bin/source/win/util.c b/acme/bin/source/win/util.c
new file mode 100644
index 000000000..ec8bd0c99
--- /dev/null
+++ b/acme/bin/source/win/util.c
@@ -0,0 +1,90 @@
+#include <u.h>
+#include <libc.h>
+#include <bio.h>
+#include <thread.h>
+#include "dat.h"
+
+void*
+emalloc(uint n)
+{
+ void *p;
+
+ p = malloc(n);
+ if(p == nil)
+ error("can't malloc: %r");
+ memset(p, 0, n);
+ return p;
+}
+
+char*
+estrdup(char *s)
+{
+ char *t;
+
+ t = emalloc(strlen(s)+1);
+ strcpy(t, s);
+ return t;
+}
+
+char*
+estrstrdup(char *s, char *t)
+{
+ char *u;
+
+ u = emalloc(strlen(s)+strlen(t)+1);
+ sprint(u, "%s%s", s, t);
+ return u;
+}
+
+char*
+eappend(char *s, char *sep, char *t)
+{
+ char *u;
+
+ if(t == nil)
+ u = estrstrdup(s, sep);
+ else{
+ u = emalloc(strlen(s)+strlen(sep)+strlen(t)+1);
+ sprint(u, "%s%s%s", s, sep, t);
+ }
+ free(s);
+ return u;
+}
+
+char*
+egrow(char *s, char *sep, char *t)
+{
+ s = eappend(s, sep, t);
+ free(t);
+ return s;
+}
+
+void
+error(char *fmt, ...)
+{
+ Fmt f;
+ char buf[64];
+ va_list arg;
+
+ fmtfdinit(&f, 2, buf, sizeof buf);
+ fmtprint(&f, "win: ");
+ va_start(arg, fmt);
+ fmtvprint(&f, fmt, arg);
+ va_end(arg);
+ fmtprint(&f, "\n");
+ fmtfdflush(&f);
+ threadexitsall(fmt);
+}
+
+void
+ctlprint(int fd, char *fmt, ...)
+{
+ int n;
+ va_list arg;
+
+ va_start(arg, fmt);
+ n = vfprint(fd, fmt, arg);
+ va_end(arg);
+ if(n <= 0)
+ error("control file write error: %r");
+}
diff --git a/acme/bin/source/win/win.c b/acme/bin/source/win/win.c
new file mode 100644
index 000000000..2e7f883ed
--- /dev/null
+++ b/acme/bin/source/win/win.c
@@ -0,0 +1,264 @@
+#include <u.h>
+#include <libc.h>
+#include <bio.h>
+#include <thread.h>
+#include "dat.h"
+
+Window*
+newwindow(void)
+{
+ char buf[12];
+ Window *w;
+
+ w = emalloc(sizeof(Window));
+ w->ctl = open("/mnt/wsys/new/ctl", ORDWR|OCEXEC);
+ if(w->ctl<0 || read(w->ctl, buf, 12)!=12)
+ error("can't open window ctl file: %r");
+ ctlprint(w->ctl, "noscroll\n");
+ w->id = atoi(buf);
+ w->event = winopenfile(w, "event");
+ w->addr = winopenfile(w, "addr");
+ w->body = winopenfile(w, "body");
+ w->data = winopenfile(w, "data");
+ w->cevent = chancreate(sizeof(Event*), 0);
+ return w;
+}
+
+void
+winsetdump(Window *w, char *dir, char *cmd)
+{
+ if(dir != nil)
+ ctlprint(w->ctl, "dumpdir %s\n", dir);
+ if(cmd != nil)
+ ctlprint(w->ctl, "dump %s\n", cmd);
+}
+
+void
+wineventproc(void *v)
+{
+ Window *w;
+ int i;
+
+ w = v;
+ for(i=0; ; i++){
+ if(i >= NEVENT)
+ i = 0;
+ wingetevent(w, &w->e[i]);
+ sendp(w->cevent, &w->e[i]);
+ }
+}
+
+int
+winopenfile(Window *w, char *f)
+{
+ char buf[64];
+ int fd;
+
+ sprint(buf, "/mnt/wsys/%d/%s", w->id, f);
+ fd = open(buf, ORDWR|OCEXEC);
+ if(fd < 0)
+ error("can't open window file %s: %r", f);
+ return fd;
+}
+
+void
+wintagwrite(Window *w, char *s, int n)
+{
+ int fd;
+
+ fd = winopenfile(w, "tag");
+ if(write(fd, s, n) != n)
+ error("tag write: %r");
+ close(fd);
+}
+
+void
+winname(Window *w, char *s)
+{
+ ctlprint(w->ctl, "name %s\n", s);
+}
+
+int
+wingetec(Window *w)
+{
+ if(w->nbuf == 0){
+ w->nbuf = read(w->event, w->buf, sizeof w->buf);
+ if(w->nbuf <= 0){
+ /* probably because window has exited, and only called by wineventproc, so just shut down */
+ threadexits(nil);
+ }
+ w->bufp = w->buf;
+ }
+ w->nbuf--;
+ return *w->bufp++;
+}
+
+int
+wingeten(Window *w)
+{
+ int n, c;
+
+ n = 0;
+ while('0'<=(c=wingetec(w)) && c<='9')
+ n = n*10+(c-'0');
+ if(c != ' ')
+ error("event number syntax");
+ return n;
+}
+
+int
+wingeter(Window *w, char *buf, int *nb)
+{
+ Rune r;
+ int n;
+
+ r = wingetec(w);
+ buf[0] = r;
+ n = 1;
+ if(r >= Runeself) {
+ while(!fullrune(buf, n))
+ buf[n++] = wingetec(w);
+ chartorune(&r, buf);
+ }
+ *nb = n;
+ return r;
+}
+
+void
+wingetevent(Window *w, Event *e)
+{
+ int i, nb;
+
+ e->c1 = wingetec(w);
+ e->c2 = wingetec(w);
+ e->q0 = wingeten(w);
+ e->q1 = wingeten(w);
+ e->flag = wingeten(w);
+ e->nr = wingeten(w);
+ if(e->nr > EVENTSIZE)
+ error("event string too long");
+ e->nb = 0;
+ for(i=0; i<e->nr; i++){
+ e->r[i] = wingeter(w, e->b+e->nb, &nb);
+ e->nb += nb;
+ }
+ e->r[e->nr] = 0;
+ e->b[e->nb] = 0;
+ if(wingetec(w) != '\n')
+ error("event syntax error");
+}
+
+void
+winwriteevent(Window *w, Event *e)
+{
+ fprint(w->event, "%c%c%d %d\n", e->c1, e->c2, e->q0, e->q1);
+}
+
+static int
+nrunes(char *s, int nb)
+{
+ int i, n;
+ Rune r;
+
+ n = 0;
+ for(i=0; i<nb; n++)
+ i += chartorune(&r, s+i);
+ return n;
+}
+
+int
+winread(Window *w, uint q0, uint q1, char *data)
+{
+ int m, n, nr, nb;
+ char buf[256];
+
+ if(w->addr < 0)
+ w->addr = winopenfile(w, "addr");
+ if(w->data < 0)
+ w->data = winopenfile(w, "data");
+ m = q0;
+ nb = 0;
+ while(m < q1){
+ n = sprint(buf, "#%d", m);
+ if(write(w->addr, buf, n) != n)
+ error("error writing addr: %r");
+ n = read(w->data, buf, sizeof buf);
+ if(n < 0)
+ error("reading data: %r");
+ nr = nrunes(buf, n);
+ while(m+nr >q1){
+ do; while(n>0 && (buf[--n]&0xC0)==0x80);
+ --nr;
+ }
+ if(n == 0)
+ break;
+ memmove(data, buf, n);
+ nb += n;
+ data += n;
+ *data = 0;
+ m += nr;
+ }
+ return nb;
+}
+
+void
+windormant(Window *w)
+{
+ if(w->addr >= 0){
+ close(w->addr);
+ w->addr = -1;
+ }
+ if(w->body >= 0){
+ close(w->body);
+ w->body = -1;
+ }
+ if(w->data >= 0){
+ close(w->data);
+ w->data = -1;
+ }
+}
+
+int
+windel(Window *w, int sure)
+{
+ if(sure)
+ write(w->ctl, "delete\n", 7);
+ else if(write(w->ctl, "del\n", 4) != 4)
+ return 0;
+ /* event proc will die due to read error from event file */
+ windormant(w);
+ close(w->ctl);
+ w->ctl = -1;
+ close(w->event);
+ w->event = -1;
+ return 1;
+}
+
+void
+winclean(Window *w)
+{
+ ctlprint(w->ctl, "clean\n");
+}
+
+int
+winsetaddr(Window *w, char *addr, int errok)
+{
+ if(w->addr < 0)
+ w->addr = winopenfile(w, "addr");
+ if(write(w->addr, addr, strlen(addr)) < 0){
+ if(!errok)
+ error("error writing addr(%s): %r", addr);
+ return 0;
+ }
+ return 1;
+}
+
+int
+winselect(Window *w, char *addr, int errok)
+{
+ if(winsetaddr(w, addr, errok)){
+ ctlprint(w->ctl, "dot=addr\n");
+ return 1;
+ }
+ return 0;
+}