shithub: riscv

Download patch

ref: b28c3db57884d406d5a39750fdc9e46baeb139af
parent: bc54898807d27e79ba9f1b595ef3e09e3da67522
author: cinap_lenrek <cinap_lenrek@felloff.net>
date: Sun Aug 20 15:22:30 EDT 2017

vt: implement /dev/cons and /dev/consctl as a fileserver, winch, incremental redraw

we used to bind a pipe to /dev/cons and /dev/consctl with some
shared segment hack to pass tty info arround. now we implement
this as a fileserver.

add support for "winchon"/"winchoff" ctl message to enable interrupt
on window size change. (used by ssh)

keep track of fullscreen scrolls, avoiding redrawing the whole
screen each time.

--- a/sys/src/cmd/vt/cons.h
+++ b/sys/src/cmd/vt/cons.h
@@ -3,10 +3,17 @@
 struct Consstate{
 	int raw;
 	int hold;
+	int winch;
 };
+extern Consstate cs[];
 
-extern Consstate*	consctl(void);
-extern Consstate*	cs;
+typedef struct Buf	Buf;
+struct Buf
+{
+	int	n;
+	char	*s;
+	char	b[];
+};
 
 #define	INSET	2
 #define	BUFS	32
@@ -75,4 +82,4 @@
 extern int nocolor;
 
 extern void setdim(int, int);
-
+extern void mountcons(void);
--- a/sys/src/cmd/vt/consctl.c
+++ /dev/null
@@ -1,71 +1,0 @@
-#include <u.h>
-#include <libc.h>
-#include <draw.h>
-#include "cons.h"
-
-/*
- *  bind a pipe onto consctl and keep reading it to
- *  get changes to console state.
- */
-Consstate*
-consctl(void)
-{
-	int i, n, fd, tries;
-	char buf[128];
-	Consstate *x;
-	char *field[10];
-
-	x = segattach(0, "shared", 0, sizeof *x);
-	if(x == (void*)-1)
-		sysfatal("segattach: %r");
-
-	/* a pipe to simulate consctl */
-	if(bind("#|", "/mnt/consctl", MBEFORE) < 0
-	|| bind("/mnt/consctl/data1", "/dev/consctl", MREPL) < 0)
-		sysfatal("bind consctl: %r");
-
-	/* a pipe to simulate the /dev/cons */
-	if(bind("#|", "/mnt/cons", MREPL) < 0
-	|| bind("/mnt/cons/data1", "/dev/cons", MREPL) < 0)
-		sysfatal("bind cons: %r");
-
-	switch(fork()){
-	case -1:
-		sysfatal("fork: %r");
-	case 0:
-		break;
-	default:
-		return x;
-	}
-
-	notify(0);
-
-	for(tries = 0; tries < 100; tries++){
-		x->raw = 0;
-		x->hold = 0;
-		fd = open("/mnt/consctl/data", OREAD);
-		if(fd < 0)
-			break;
-		tries = 0;
-		for(;;){
-			n = read(fd, buf, sizeof(buf)-1);
-			if(n <= 0)
-				break;
-			buf[n] = 0;
-			n = getfields(buf, field, 10, 1, " ");
-			for(i = 0; i < n; i++){
-				if(strcmp(field[i], "rawon") == 0)
-					x->raw = 1;
-				else if(strcmp(field[i], "rawoff") == 0)
-					x->raw = 0;
-				else if(strcmp(field[i], "holdon") == 0)
-					x->hold = 1;
-				else if(strcmp(field[i], "holdoff") == 0)
-					x->hold = 0;
-			}
-		}
-		close(fd);
-	}
-	exits(0);
-	return 0;		/* dummy to keep compiler quiet*/
-}
--- /dev/null
+++ b/sys/src/cmd/vt/fs.c
@@ -1,0 +1,213 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+
+#include "cons.h"
+
+#include <thread.h>
+#include <fcall.h>
+#include <9p.h>
+
+extern Channel *hc[2];
+
+static File *devcons, *devconsctl;
+
+static Channel *readreq;
+static Channel *flushreq;
+
+static void
+fsreader(void*)
+{
+	Req *r, *fr;
+	Buf *b;
+	int n;
+
+	b = nil;
+	r = nil;
+	for(;;){
+		Alt a[] = {
+			{ flushreq, &fr, CHANRCV },
+			{ readreq, &r, r == nil ? CHANRCV : CHANNOP },
+			{ hc[0], &b, b == nil ? CHANRCV : CHANNOP },
+			{ nil, nil, b == nil || r == nil ? CHANEND : CHANNOBLK },
+		};
+		if(alt(a) == 0){
+			if(fr->oldreq == r){
+				respond(r, "interrupted");
+				r = nil;
+			}
+			respond(fr, nil);
+		}
+		if(b == nil || r == nil)
+			continue;
+		r->ofcall.count = 0;
+		while((n = r->ifcall.count - r->ofcall.count) > 0){
+			if(n > b->n)
+				n = b->n;
+			memmove((char*)r->ofcall.data + r->ofcall.count, b->s, n);
+			r->ofcall.count += n;
+			b->s += n, b->n -= n;
+			if(b->n <= 0){
+				free(b);
+				if((b = nbrecvp(hc[0])) == nil)
+					break;
+			}
+		}
+		respond(r, nil);
+		r = nil;
+	}
+}
+
+static void
+fsread(Req *r)
+{
+	if(r->fid->file == devcons){
+		sendp(readreq, r);
+		return;
+	}
+	respond(r, "not implemented");
+}
+
+typedef struct Partutf Partutf;
+struct Partutf
+{
+	int	n;
+	char	s[UTFmax];
+};
+
+static Rune*
+cvtc2r(char *b, int n, Partutf *u)
+{
+	char *cp, *ep;
+	Rune *rp, *rb;
+
+	cp = b, ep = b + n;
+	rp = rb = emalloc9p(sizeof(Rune)*(n+2));
+
+	while(u->n > 0 && cp < ep){
+		u->s[u->n++] = *cp++;
+		if(fullrune(u->s, u->n)){
+			chartorune(rp, u->s);
+			if(*rp != 0)
+				rp++;
+			u->n = 0;
+			break;
+		}
+	}
+	if(u->n == 0){
+		while(cp < ep && fullrune(cp, ep - cp)){
+			cp += chartorune(rp, cp);
+			if(*rp != 0)
+				rp++;
+		}
+		n = ep - cp;
+		if(n > 0){
+			memmove(u->s, cp, n);
+			u->n = n;
+		}
+	}
+	if(rb == rp){
+		free(rb);
+		return nil;
+	}
+	*rp = 0;
+
+	return rb;
+}
+
+static void
+fswrite(Req *r)
+{
+	if(r->fid->file == devcons){
+		Partutf *u;
+		Rune *rp;
+
+		if((u = r->fid->aux) == nil)
+			u = r->fid->aux = emalloc9p(sizeof(*u));
+		if((rp = cvtc2r((char*)r->ifcall.data, r->ifcall.count, u)) != nil)
+			sendp(hc[1], rp);
+
+		r->ofcall.count = r->ifcall.count;
+		respond(r, nil);
+		return;
+	}
+	if(r->fid->file == devconsctl){
+		char *s = r->ifcall.data;
+		int n = r->ifcall.count;
+
+		if(n >= 5 && strncmp(s, "rawon", 5) == 0)
+			cs->raw = 1;
+		else if(n >= 6 && strncmp(s, "rawoff", 6) == 0)
+			cs->raw = 0;
+		else if(n >= 6 && strncmp(s, "holdon", 6) == 0)
+			cs->hold = 1;
+		else if(n >= 7 && strncmp(s, "holdoff", 7) == 0)
+			cs->hold = 0;
+		else if(n >= 7 && strncmp(s, "winchon", 7) == 0)
+			cs->winch = 1;
+		else if(n >= 8 && strncmp(s, "winchoff", 8) == 0)
+			cs->winch = 0;
+
+		r->ofcall.count = r->ifcall.count;
+		respond(r, nil);
+		return;
+	}
+
+	respond(r, "not implemented");
+}
+
+static void
+fsflush(Req *r)
+{
+	sendp(flushreq, r);
+}
+
+static void
+fsdestroyfid(Fid *f)
+{
+	if(f->file == devconsctl && f->omode >= 0){
+		cs->raw = 0;
+		cs->hold = 0;
+		cs->winch = 0;
+	}
+	if(f->aux != nil){
+		free(f->aux);
+		f->aux = nil;
+	}
+}
+
+static void
+fsstart(Srv*)
+{
+	flushreq = chancreate(sizeof(Req*), 4);
+	readreq = chancreate(sizeof(Req*), 4);
+	proccreate(fsreader, nil, 16*1024);
+}
+
+static void
+fsend(Srv*)
+{
+	sendp(hc[1], nil);
+}
+
+Srv fs = {
+.read=fsread,
+.write=fswrite,
+.flush=fsflush,
+.destroyfid=fsdestroyfid,
+.start=fsstart,
+.end=fsend,
+};
+
+void
+mountcons(void)
+{
+	fs.tree = alloctree("vt", "vt", DMDIR|0555, nil);
+	devcons = createfile(fs.tree->root, "cons", "vt", 0666, nil);
+	if(devcons == nil)
+		sysfatal("creating /dev/cons: %r");
+	devconsctl = createfile(fs.tree->root, "consctl", "vt", 0666, nil);
+	if(devconsctl == nil)
+		sysfatal("creating /dev/consctl: %r");
+	threadpostmountsrv(&fs, nil, "/dev", MBEFORE);
+}
--- a/sys/src/cmd/vt/main.c
+++ b/sys/src/cmd/vt/main.c
@@ -1,11 +1,16 @@
 #include <u.h>
 #include <libc.h>
 #include <draw.h>
+
+#include "cons.h"
+
 #include <thread.h>
+#include <fcall.h>
+#include <9p.h>
+
+#include <bio.h>
 #include <mouse.h>
 #include <keyboard.h>
-#include <bio.h>
-#include "cons.h"
 
 char	*menutext2[] = {
 	"backup",
@@ -40,6 +45,7 @@
 int	olines;
 int	peekc;
 int	cursoron = 1;
+int	hostclosed = 0;
 Menu	menu2;
 Menu	menu3;
 Rune	*histp;
@@ -52,9 +58,15 @@
 #define onscreena(x, y) &onscreenabuf[((y)*(xmax+2) + (x))]
 #define onscreenc(x, y) &onscreencbuf[((y)*(xmax+2) + (x))]
 
+uchar	*screenchangebuf;
+uint	scrolloff;
+
+#define	screenchange(y)	screenchangebuf[((y)+scrolloff) % (ymax+1)]
+
 int	yscrmin, yscrmax;
 int	attr, defattr;
 
+Image	*cursorsave;
 Image	*bordercol;
 Image	*colors[8];
 Image	*hicolors[8];
@@ -100,13 +112,12 @@
 
 Mousectl	*mc;
 Keyboardctl	*kc;
-Channel		*hc;
-Consstate	*cs;
+Channel		*hc[2];
+Consstate	cs[1];
 
 int	nocolor;
 int	logfd = -1;
-int	hostfd = -1;
-int	hostpid;
+int	hostpid = -1;
 Biobuf	*snarffp = 0;
 Rune	*hostbuf, *hostbufp;
 char	echo_input[BSIZE];
@@ -118,7 +129,6 @@
 struct funckey *fk, *appfk;
 
 /* functions */
-void	initialize(int, char **);
 int	waitchar(void);
 void	waitio(void);
 int	rcvchar(void);
@@ -130,59 +140,53 @@
 int	alnum(int);
 void	escapedump(int,uchar *,int);
 
-int
-start_host(void)
+static Channel *pidchan;
+
+static void
+runcmd(void *args)
 {
-	switch((hostpid = rfork(RFPROC|RFNAMEG|RFFDG|RFNOTEG))) {
-	case 0:
-		close(0);
-		open("/dev/cons", OREAD);
-		close(1);
-		open("/dev/cons", OWRITE);
-		dup(1, 2);
-		execl("/bin/rc","rcX",nil);
-		fprint(2, "failed to start up rc: %r\n");
-		_exits("rc");
-	case -1:
-		fprint(2,"rc startup: fork: %r\n");
-		_exits("rc_fork");
+	char **argv = args;
+	char *cmd;
+
+	rfork(RFNAMEG);
+	mountcons();
+
+	rfork(RFFDG);
+	close(0);
+	open("/dev/cons", OREAD);
+	close(1);
+	open("/dev/cons", OWRITE);
+	dup(1, 2);
+
+	cmd = nil;
+	while(*argv != nil){
+		if(cmd == nil)
+			cmd = strdup(*argv);
+		else
+			cmd = smprint("%s %q", cmd, *argv);
+		argv++;
 	}
-	return open("/mnt/cons/data", ORDWR);
+
+	procexecl(pidchan, "/bin/rc", "rcX", cmd == nil ? nil : "-c", cmd, nil);
+	sysfatal("%r");
 }
 
 void
 send_interrupt(void)
 {
-	postnote(PNGROUP, hostpid, "interrupt");
+	if(hostpid > 0)
+		postnote(PNGROUP, hostpid, "interrupt");
 }
 
 void
-hostreader(void*)
+sendnchars(int n, char *p)
 {
-	char cb[BSIZE+1], *cp;
-	Rune *rb, *rp;
-	int n, r;
+	Buf *b;
 
-	n = 0;
-	while((r = read(hostfd, cb+n, BSIZE-n)) > 0){
-		n += r;
-		rb = mallocz((n+1)*sizeof(Rune), 0);
-		for(rp = rb, cp = cb; n > 0; n -= r, cp += r){
-			if(!fullrune(cp, n))
-				break;
-			r = chartorune(rp, cp);
-			if(*rp != 0)
-				rp++;
-		}
-		if(rp > rb){
-			*rp = 0;
-			sendp(hc, rb);
-		} else {
-			free(rb);
-		}
-		if(n > 0) memmove(cb, cp, n);
-	}
-	sendp(hc, nil);
+	b = emalloc9p(sizeof(Buf)+n);
+	memmove(b->s = b->b, p, b->n = n);
+	if(nbsendp(hc[0], b) < 0)
+		free(b);
 }
 
 static void
@@ -189,20 +193,10 @@
 shutdown(void)
 {
 	send_interrupt();
-	postnote(PNGROUP, getpid(), "exit");
 	threadexitsall(nil);
 }
 
 void
-threadmain(int argc, char **argv)
-{
-	rfork(RFNAMEG|RFNOTEG|RFENVG);
-	atexit(shutdown);
-	initialize(argc, argv);
-	emulate();
-}
-
-void
 usage(void)
 {
 	fprint(2, "usage: %s [-2abcrx] [-f font] [-l logfile] [cmd...]\n", argv0);
@@ -210,7 +204,7 @@
 }
 
 void
-initialize(int argc, char **argv)
+threadmain(int argc, char **argv)
 {
 	int rflag;
 	int i, blkbg;
@@ -257,6 +251,9 @@
 		break;
 	}ARGEND;
 
+	quotefmtinstall();
+	atexit(shutdown);
+
 	if(initdraw(0, fontname, term) < 0)
 		sysfatal("inidraw failed: %r");
 	if((mc = initmouse("/dev/mouse", screen)) == nil)
@@ -263,8 +260,10 @@
 		sysfatal("initmouse failed: %r");
 	if((kc = initkeyboard("/dev/cons")) == nil)
 		sysfatal("initkeyboard failed: %r");
-	if((cs = consctl()) == nil)
-		sysfatal("consctl failed: %r");
+
+	hc[0] = chancreate(sizeof(Buf*), 8);	/* input to host */
+	hc[1] = chancreate(sizeof(Rune*), 8);	/* output from host */
+
 	cs->raw = rflag;
 
 	histp = hist;
@@ -290,19 +289,11 @@
 	fgcolor = (blkbg? display->white: display->black);
 	resize();
 
-	hc = chancreate(sizeof(Rune*), 5);
-	if((hostfd = start_host()) >= 0)
-		proccreate(hostreader, nil, BSIZE+1024);
+	pidchan = chancreate(sizeof(int), 0);
+	proccreate(runcmd, argv, 16*1024);
+	hostpid = recvul(pidchan);
 
-	while(*argv != nil){
-		sendnchars(strlen(*argv), *argv);
-		if(argv[1] == nil){
-			sendnchars(1, "\n");
-			break;
-		}
-		sendnchars(1, " ");
-		argv++;
-	}
+	emulate();
 }
 
 Image*
@@ -334,6 +325,16 @@
 }
 
 void
+hidecursor(void)
+{
+	if(cursorsave == nil)
+		return;
+	draw(screen, cursorsave->r, cursorsave, nil, cursorsave->r.min);
+	freeimage(cursorsave);
+	cursorsave = nil;
+}
+
+void
 drawscreen(void)
 {
 	int x, y, n;
@@ -342,26 +343,28 @@
 	Rune *rp;
 	Point p, q;
 
-	draw(screen, screen->r, bgcolor, nil, ZP);
+	hidecursor();
+	
+	if(scrolloff != 0){
+		n = scrolloff % (ymax+1);
+		draw(screen, Rpt(pt(0,0), pt(xmax+2, ymax+1-n)), screen, nil, pt(0, n));
+	}
 
-	/* draw background */
 	for(y = 0; y <= ymax; y++){
+		if(!screenchange(y))
+			continue;
+		screenchange(y) = 0;
+
 		for(x = 0; x <= xmax; x += n){
 			cp = onscreenc(x, y);
 			ap = onscreena(x, y);
 			c = bgcol(*ap, *cp);
-			if(c == bgcolor){
-				n = 1;
-				continue;
-			}
 			for(n = 1; x+n <= xmax && bgcol(ap[n], cp[n]) == c; n++)
 				;
 			draw(screen, Rpt(pt(x, y), pt(x+n, y+1)), c, nil, ZP);
 		}
-	}
+		draw(screen, Rpt(pt(x, y), pt(x+1, y+1)), bgcolor, nil, ZP);
 
-	/* draw foreground */
-	for(y = 0; y <= ymax; y++){
 		for(x = 0; x <= xmax; x += n){
 			rp = onscreenr(x, y);
 			if(*rp == 0){
@@ -387,6 +390,8 @@
 				bordercol,
 				ZP, font, L">", 1);
 	}
+
+	scrolloff = 0;
 }
 
 void
@@ -393,11 +398,20 @@
 drawcursor(void)
 {
 	Image *col;
+	Rectangle r;
 
+	hidecursor();
 	if(cursoron == 0)
 		return;
-	col = (blocked || hostfd < 0) ? red : bordercol;
-	border(screen, Rpt(pt(x, y), pt(x+1, y+1)), 2, col, ZP);
+
+	col = (blocked || hostclosed) ? red : bordercol;
+	r = Rpt(pt(x, y), pt(x+1, y+1));
+
+	cursorsave = allocimage(display, r, screen->chan, 0, DNofill);
+	draw(cursorsave, r, screen, nil, r.min);
+
+	border(screen, r, 2, col, ZP);
+	
 }
 
 void
@@ -404,8 +418,12 @@
 clear(int x1, int y1, int x2, int y2)
 {
 	int c = (attr & 0x0F00)>>8; /* bgcolor */
+
+	if(y1 < 0 || y1 > ymax || x1 < 0 || x1 > xmax || y2 <= y1 || x2 <= x1)
+		return;
 	
 	while(y1 < y2){
+		screenchange(y1) = 1;
 		if(x1 < x2){
 			memset(onscreenr(x1, y1), 0, (x2-x1)*sizeof(Rune));
 			memset(onscreena(x1, y1), 0, x2-x1);
@@ -713,8 +731,8 @@
 			if(host_avail())
 				return(rcvchar());
 			free(hostbuf);
-			hostbufp = hostbuf = nbrecvp(hc);
-			if(host_avail() && nrand(8))
+			hostbufp = hostbuf = nbrecvp(hc[1]);
+			if(host_avail() && nrand(32))
 				return(rcvchar());
 		}
 		drawscreen();
@@ -731,7 +749,7 @@
 		{ mc->c, &mc->Mouse, CHANRCV },
 		{ mc->resizec, nil, CHANRCV },
 		{ kc->c, &kbdchar, CHANRCV },
-		{ hc, &hostbuf, CHANRCV },
+		{ hc[1], &hostbuf, CHANRCV },
 		{ nil, nil, CHANEND },
 	};
 	if(blocked)
@@ -755,10 +773,8 @@
 		break;
 	case AHOST:
 		hostbufp = hostbuf;
-		if(hostbuf == nil){
-			close(hostfd);
-			hostfd = -1;
-		}
+		if(hostbuf == nil)
+			hostclosed = 1;
 		break;
 	}
 }
@@ -780,6 +796,8 @@
 	putenvint("LINES", ymax+1);
 	putenvint("COLS", xmax+1);
 	putenv("TERM", term);
+	if(cs->winch)
+		send_interrupt();
 }
 
 void
@@ -799,14 +817,20 @@
 	margin.x = (Dx(screen->r) - (xmax+1)*ftsize.x) / 2;
 	margin.y = (Dy(screen->r) - (ymax+1)*ftsize.y) / 2;
 
+	free(screenchangebuf);
+	screenchangebuf = emalloc9p(ymax+1);
+	scrolloff = 0;
+
 	free(onscreenrbuf);
-	onscreenrbuf = mallocz((ymax+1)*(xmax+2)*sizeof(Rune), 1);
+	onscreenrbuf = emalloc9p((ymax+1)*(xmax+2)*sizeof(Rune));
 	free(onscreenabuf);
-	onscreenabuf = mallocz((ymax+1)*(xmax+2), 1);
+	onscreenabuf = emalloc9p((ymax+1)*(xmax+2));
 	free(onscreencbuf);
-	onscreencbuf = mallocz((ymax+1)*(xmax+2), 1);
+	onscreencbuf = emalloc9p((ymax+1)*(xmax+2));
 	clear(0,0,xmax+1,ymax+1);
 
+	draw(screen, screen->r, bgcolor, nil, ZP);
+
 	if(resize_flag || backc)
 		return;
 
@@ -1052,10 +1076,15 @@
 void
 shift(int x1, int y, int x2, int w)
 {
+	if(y < 0 || y > ymax || x1 < 0 || x2 < 0 || w <= 0)
+		return;
+
 	if(x1+w > xmax+1)
 		w = xmax+1 - x1;
 	if(x2+w > xmax+1)
 		w = xmax+1 - x2;
+
+	screenchange(y) = 1;
 	memmove(onscreenr(x1, y), onscreenr(x2, y), w*sizeof(Rune));
 	memmove(onscreena(x1, y), onscreena(x2, y), w);
 	memmove(onscreenc(x1, y), onscreenc(x2, y), w);
@@ -1064,9 +1093,30 @@
 void
 scroll(int sy, int ly, int dy, int cy)	/* source, limit, dest, which line to clear */
 {
-	memmove(onscreenr(0, dy), onscreenr(0, sy), (ly-sy)*(xmax+2)*sizeof(Rune));
-	memmove(onscreena(0, dy), onscreena(0, sy), (ly-sy)*(xmax+2));
-	memmove(onscreenc(0, dy), onscreenc(0, sy), (ly-sy)*(xmax+2));
+	int n, d, i;
+
+	if(sy < 0 || sy > ymax || dy < 0 || dy > ymax)
+		return;
+
+	n = ly - sy;
+	if(sy + n > ymax+1)
+		n = ymax+1 - sy;
+	if(dy + n > ymax+1)
+		n = ymax+1 - dy;
+
+	d = sy - dy;
+	if(n > 0 && d != 0){
+		if(d > 0 && dy == 0 && n >= ymax){
+			scrolloff += d;
+		} else {
+			for(i = 0; i < n; i++)
+				screenchange(dy+i) = 1;
+		}
+		memmove(onscreenr(0, dy), onscreenr(0, sy), n*(xmax+2)*sizeof(Rune));
+		memmove(onscreena(0, dy), onscreena(0, sy), n*(xmax+2));
+		memmove(onscreenc(0, dy), onscreenc(0, sy), n*(xmax+2));
+	}
+
 	clear(0, cy, xmax+1, cy+1);
 }
 
@@ -1079,13 +1129,13 @@
 		return;
 	if(y < half) {
 		clear(0, 0, xmax+1, ymax+1);
+		scrolloff = 0;
 		x = y = 0;
 		return;
 	}
-	memmove(onscreenr(0, 0), onscreenr(0, half), (ymax-half+1)*(xmax+2)*sizeof(Rune));
-	memmove(onscreena(0, 0), onscreena(0, half), (ymax-half+1)*(xmax+2));
-	memmove(onscreenc(0, 0), onscreenc(0, half), (ymax-half+1)*(xmax+2));
+	scroll(half, ymax+1, 0, ymax);
 	clear(0, y-half+1, xmax+1, ymax+1);
+
 	y -= half;
 	if(olines)
 		olines -= half;
@@ -1109,17 +1159,6 @@
 
 /* stubs */
 
-void
-sendnchars(int n,char *p)
-{
-	if(hostfd < 0)
-		return;
-	if(write(hostfd,p,n) < 0){
-		close(hostfd);
-		hostfd = -1;
-	}
-}
-
 int
 host_avail(void)
 {
@@ -1179,6 +1218,7 @@
 void
 drawstring(Rune *str, int n)
 {
+	screenchange(y) = 1;
 	memmove(onscreenr(x, y), str, n*sizeof(Rune));
 	memset(onscreena(x, y), attr & 0xFF, n);
 	memset(onscreenc(x, y), attr >> 8, n);
--- a/sys/src/cmd/vt/mkfile
+++ b/sys/src/cmd/vt/mkfile
@@ -3,9 +3,9 @@
 TARG=vt
 
 OFILES=\
-	consctl.$O\
 	main.$O\
 	vt.$O\
+	fs.$O\
 
 HFILES=cons.h
 
--- a/sys/src/cmd/vt/vt.c
+++ b/sys/src/cmd/vt/vt.c
@@ -23,9 +23,10 @@
 #include <u.h>
 #include <libc.h>
 #include <draw.h>
-#include <bio.h>
-#include <ctype.h>
+
 #include "cons.h"
+
+#include <ctype.h>
 
 int	wraparound = 1;
 int	originrelative = 0;