my previous message was meant to include a small addition which I omitted by accident and which turned out to be wrong anyway. here is the rest of the picture.
I presume that there is no program to read the text on the graphical 9vx terminal (which would be required in order to "visualize" rio), so instead i've made a small addition to the win program that runs inside 'acme' allowing all input and output to be tee-d (logged) to a separate file somewhere in the plan9 tree. that file can then be then tail-ed, catted or whatever is needed in order to provide the text of the acme conversation to a third program. the changes are rather rude, but i hope they help. here is how to install them: copy the attached main.c to /acme/bin/source/win (inside plan9). cd to that directory and run 'mk install' from within 9vx. then copy the new acme.dump file attached in this email to your home directory in 9vx (/usr/youruser, it has to be in a home directory because / is not writeable in 9vx). edit this file. the last line provides the application to start. the argument to win which we just added with the new code tells it to dump the conversation to /usr/andrey/test.txt. change this to point to a file in your home directory inside 9vx. this is the file which will contain the entire conversation and update it in real-time. i imagine that whatever program reads text will be using that file as its source. start 9vx and acme again inside via "acme -l /path/to/acme.dump" and it should start in a single-column mode with the new 'win' program copying its i/o to the file of your choosing. i admin it's convoluted, but it was a nice exercise in trying to understand the system from a completely different point of view. hope it's useful. andrey
acme.dump
Description: Binary data
#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; int logfile = -1; 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, *f; quotefmtinstall(); rfork(RFNAMEG); ARGBEGIN{ case 'd': debug = 1; chatty9p++; break; case 'e': eraseinput = 1; break; case 'D': {extern int _threaddebuglevel; _threaddebuglevel = 1<<20; } case 'f': f = EARGF(usage); logfile=create(f, OWRITE, 0666); if(logfile < 0) sysfatal("can not open log file %s: %r", f); }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, ¬epg); 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); if(logfile) write(logfile, 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)); if(logfile) write(logfile, 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); if(logfile) write(logfile, 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; if(logfile) write(logfile, e->b, e->nb); 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); }