9fans - fans of the OS Plan 9 from Bell Labs
 help / color / mirror / Atom feed
From: "andrey mirtchovski" <mirtchovski@gmail.com>
To: "Fans of the OS Plan 9 from Bell Labs" <9fans@9fans.net>
Subject: Re: [9fans] success with 9vx from a terminal
Date: Wed, 22 Oct 2008 11:56:06 -0600	[thread overview]
Message-ID: <14ec7b180810221056w6beb4db3lf1e2c484734da24e@mail.gmail.com> (raw)
In-Reply-To: <14ec7b180810220954n6ce413b3t3095f07230e7d843@mail.gmail.com>

[-- Attachment #1: Type: text/plain, Size: 1787 bytes --]

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

[-- Attachment #2: acme.dump --]
[-- Type: application/octet-stream, Size: 306 bytes --]

/usr/andrey
/lib/font/bit/lucidasans/euro.8.font
/lib/font/bit/lucm/unicode.9.font
          0
e          0           0           0           0           1 
          2          48           2           0           1 /usr/andrey/-vx32 Del Snarf | Look Send Noscroll
/usr/andrey
win -f /usr/andrey/test.txt

[-- Warning: decoded text below may be mangled, UTF-8 assumed --]
[-- Attachment #3: main.c --]
[-- Type: text/x-csrc; name=main.c, Size: 12767 bytes --]

#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, &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);
	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);
}

  reply	other threads:[~2008-10-22 17:56 UTC|newest]

Thread overview: 26+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2008-10-22  8:35 Chris Brannon
2008-10-22 16:54 ` andrey mirtchovski
2008-10-22 17:56   ` andrey mirtchovski [this message]
2008-10-22 17:56   ` erik quanstrom
2008-10-22 18:52     ` andrey mirtchovski
2008-10-22 23:43   ` Roman V. Shaposhnik
2008-11-09 16:55     ` sqweek
2008-11-09 20:50       ` Charles Forsyth
2008-11-10 22:24       ` Roman V. Shaposhnik
2008-11-10 22:49         ` ron minnich
2008-11-10 22:50           ` Roman V. Shaposhnik
2008-11-10 23:14             ` ron minnich
2008-11-10 23:42             ` andrey mirtchovski
2008-11-11  0:42               ` Roman V. Shaposhnik
2008-11-11  1:04                 ` andrey mirtchovski
2008-11-11 16:47                   ` ron minnich
2008-11-10 23:27         ` erik quanstrom
2008-11-12  4:10           ` Roman Shaposhnik
2008-10-22  9:15 gdiaz
2008-10-22  9:18 ` Christian Kellermann
2008-10-22 11:42 erik quanstrom
2008-10-22 12:25 ` roger peppe
2008-10-22 14:22 ` john
2008-10-22 16:28   ` erik quanstrom
2008-10-22 16:35     ` Roman V. Shaposhnik
     [not found] <0bff7fbe9fa7f6b6305adedeba12e019@quanstro.net>
2008-10-22 12:01 ` Christian Kellermann

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=14ec7b180810221056w6beb4db3lf1e2c484734da24e@mail.gmail.com \
    --to=mirtchovski@gmail.com \
    --cc=9fans@9fans.net \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).