Initial revision
diff --git a/src/cmd/samterm/Make.Darwin-PowerMacintosh b/src/cmd/samterm/Make.Darwin-PowerMacintosh
new file mode 100644
index 0000000..14b8d4e
--- /dev/null
+++ b/src/cmd/samterm/Make.Darwin-PowerMacintosh
@@ -0,0 +1,6 @@
+CC=gcc
+CFLAGS+=-Wall -Wno-missing-braces -Wno-parentheses -Wno-switch -O2 -g -c -I. -I${PREFIX}/include
+O=o
+AR=ar
+ARFLAGS=rvc
+NAN=nan64.$O
diff --git a/src/cmd/samterm/Make.FreeBSD-386 b/src/cmd/samterm/Make.FreeBSD-386
new file mode 100644
index 0000000..087ed3a
--- /dev/null
+++ b/src/cmd/samterm/Make.FreeBSD-386
@@ -0,0 +1,7 @@
+CC=gcc
+CFLAGS+=-Wall -Wno-missing-braces -Wno-parentheses -Wno-switch -O2 -g -c -I. -I$(PREFIX)/include
+O=o
+AR=ar
+ARFLAGS=rvc
+NAN=nan64.$O	# default, can be overriden by Make.$(SYSNAME)
+NAN=nan64.$O
diff --git a/src/cmd/samterm/Make.HP-UX-9000 b/src/cmd/samterm/Make.HP-UX-9000
new file mode 100644
index 0000000..edbdc11
--- /dev/null
+++ b/src/cmd/samterm/Make.HP-UX-9000
@@ -0,0 +1,6 @@
+CC=cc
+CFLAGS=-O -c -Ae -I.
+O=o
+AR=ar
+ARFLAGS=rvc
+NAN=nan64.$O
diff --git a/src/cmd/samterm/Make.Linux-386 b/src/cmd/samterm/Make.Linux-386
new file mode 100644
index 0000000..74b0252
--- /dev/null
+++ b/src/cmd/samterm/Make.Linux-386
@@ -0,0 +1,7 @@
+CC=gcc
+CFLAGS+=-Wall -Wno-missing-braces -Wno-parentheses -Wno-switch -O2 -g -c -I.
+O=o
+AR=ar
+ARFLAGS=rvc
+NAN=nan64.$O	# default, can be overriden by Make.$(SYSNAME)
+NAN=nan64.$O
diff --git a/src/cmd/samterm/Make.NetBSD-386 b/src/cmd/samterm/Make.NetBSD-386
new file mode 100644
index 0000000..087ed3a
--- /dev/null
+++ b/src/cmd/samterm/Make.NetBSD-386
@@ -0,0 +1,7 @@
+CC=gcc
+CFLAGS+=-Wall -Wno-missing-braces -Wno-parentheses -Wno-switch -O2 -g -c -I. -I$(PREFIX)/include
+O=o
+AR=ar
+ARFLAGS=rvc
+NAN=nan64.$O	# default, can be overriden by Make.$(SYSNAME)
+NAN=nan64.$O
diff --git a/src/cmd/samterm/Make.OSF1-alpha b/src/cmd/samterm/Make.OSF1-alpha
new file mode 100644
index 0000000..3d45279
--- /dev/null
+++ b/src/cmd/samterm/Make.OSF1-alpha
@@ -0,0 +1,6 @@
+CC=cc
+CFLAGS+=-g -c -I.
+O=o
+AR=ar
+ARFLAGS=rvc
+NAN=nan64.$O
diff --git a/src/cmd/samterm/Make.SunOS-sun4u b/src/cmd/samterm/Make.SunOS-sun4u
new file mode 100644
index 0000000..c5fe67b
--- /dev/null
+++ b/src/cmd/samterm/Make.SunOS-sun4u
@@ -0,0 +1,2 @@
+include Make.SunOS-sun4u-$(CC)
+NAN=nan64.$O
diff --git a/src/cmd/samterm/Make.SunOS-sun4u-cc b/src/cmd/samterm/Make.SunOS-sun4u-cc
new file mode 100644
index 0000000..829301d
--- /dev/null
+++ b/src/cmd/samterm/Make.SunOS-sun4u-cc
@@ -0,0 +1,6 @@
+CC=cc
+CFLAGS+=-g -c -I. -O
+O=o
+AR=ar
+ARFLAGS=rvc
+NAN=nan64.$O
diff --git a/src/cmd/samterm/Make.SunOS-sun4u-gcc b/src/cmd/samterm/Make.SunOS-sun4u-gcc
new file mode 100644
index 0000000..5c41594
--- /dev/null
+++ b/src/cmd/samterm/Make.SunOS-sun4u-gcc
@@ -0,0 +1,6 @@
+CC=gcc
+CFLAGS+=-Wall -Wno-missing-braces -Wno-parentheses -Wno-switch -O2 -g -c
+O=o
+AR=ar
+ARFLAGS=rvc
+NAN=nan64.$O
diff --git a/src/cmd/samterm/Makefile b/src/cmd/samterm/Makefile
new file mode 100644
index 0000000..1706b12
--- /dev/null
+++ b/src/cmd/samterm/Makefile
@@ -0,0 +1,94 @@
+
+# this works in gnu make
+SYSNAME:=${shell uname}
+OBJTYPE:=${shell uname -m | sed 's;i.86;386;; s;/.*;;; s; ;;g'}
+
+# this works in bsd make
+SYSNAME!=uname
+OBJTYPE!=uname -m | sed 's;i.86;386;; s;/.*;;; s; ;;g'
+
+# the gnu rules will mess up bsd but not vice versa,
+# hence the gnu rules come first.
+
+include Make.$(SYSNAME)-$(OBJTYPE)
+
+PREFIX=/usr/local
+
+NUKEFILES=
+
+TGZFILES=
+
+TARG=samterm
+OFILES=\
+	main.$O\
+	icons.$O\
+	menu.$O\
+	mesg.$O\
+	rasp.$O\
+	scroll.$O\
+	flayer.$O\
+	io.$O\
+	plan9.$O\
+
+HFILES=\
+	samterm.h\
+	flayer.h\
+	$(PREFIX)/include/frame.h\
+
+all: $(TARG)
+
+install:
+	install -c -m 0755 samterm $(PREFIX)/bin/samterm
+
+
+$(TARG): $(OFILES)
+	$(CC) -o $(TARG) $(OFILES) -L$(PREFIX)/lib -lframe -ldraw -lthread -l9 -lregexp9 -lbio -lfmt -lutf -L/usr/X11R6/lib -lX11 -lm -ldraw
+
+
+.c.$O:
+	$(CC) $(CFLAGS) -I/usr/X11R6/include -I../sam -I$(PREFIX)/include $*.c
+
+%.$O: %.c
+	$(CC) $(CFLAGS) -I/usr/X11R6/include -I../sam -I$(PREFIX)/include $*.c
+
+
+$(OFILES): $(HFILES)
+
+tgz:
+	rm -rf $(NAME)-$(VERSION)
+	mkdir $(NAME)-$(VERSION)
+	cp Makefile Make.* README LICENSE NOTICE *.[ch137] rpm.spec bundle.ports $(TGZFILES) $(NAME)-$(VERSION)
+	tar cf - $(NAME)-$(VERSION) | gzip >$(NAME)-$(VERSION).tgz
+	rm -rf $(NAME)-$(VERSION)
+
+clean:
+	rm -f $(OFILES) $(LIB)
+
+nuke:
+	rm -f $(OFILES) *.tgz *.rpm $(NUKEFILES)
+
+rpm:
+	make tgz
+	cp $(NAME)-$(VERSION).tgz /usr/src/RPM/SOURCES
+	rpm -ba rpm.spec
+	cp /usr/src/RPM/SRPMS/$(NAME)-$(VERSION)-1.src.rpm .
+	cp /usr/src/RPM/RPMS/i586/$(NAME)-$(VERSION)-1.i586.rpm .
+	scp *.rpm rsc@amsterdam.lcs.mit.edu:public_html/software
+
+PORTDIR=/usr/ports/$(PORTPLACE)
+
+ports:
+	make tgz
+	rm -rf $(PORTDIR)
+	mkdir $(PORTDIR)
+	cp $(NAME)-$(VERSION).tgz /usr/ports/distfiles
+	cat bundle.ports | (cd $(PORTDIR) && awk '$$1=="---" && $$3=="---" { ofile=$$2; next} {if(ofile) print >ofile}')
+	(cd $(PORTDIR); make makesum)
+	(cd $(PORTDIR); make)
+	(cd $(PORTDIR); /usr/local/bin/portlint)
+	rm -rf $(PORTDIR)/work
+	shar `find $(PORTDIR)` > ports.shar
+	(cd $(PORTDIR); tar cf - *) | gzip >$(NAME)-$(VERSION)-ports.tgz
+	scp *.tgz rsc@amsterdam.lcs.mit.edu:public_html/software
+
+.phony: all clean nuke install tgz rpm ports
diff --git a/src/cmd/samterm/Makefile.MID b/src/cmd/samterm/Makefile.MID
new file mode 100644
index 0000000..e65f98a
--- /dev/null
+++ b/src/cmd/samterm/Makefile.MID
@@ -0,0 +1,22 @@
+TARG=samterm
+OFILES=\
+	main.$O\
+	icons.$O\
+	menu.$O\
+	mesg.$O\
+	rasp.$O\
+	scroll.$O\
+	flayer.$O\
+	io.$O\
+	plan9.$O\
+
+HFILES=\
+	samterm.h\
+	flayer.h\
+	$(PREFIX)/include/frame.h\
+
+all: $(TARG)
+
+install:
+	install -c -m 0755 samterm $(PREFIX)/bin/samterm
+
diff --git a/src/cmd/samterm/flayer.c b/src/cmd/samterm/flayer.c
new file mode 100644
index 0000000..c308993
--- /dev/null
+++ b/src/cmd/samterm/flayer.c
@@ -0,0 +1,485 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+#include <thread.h>
+#include <mouse.h>
+#include <cursor.h>
+#include <keyboard.h>
+#include <frame.h>
+#include "flayer.h"
+#include "samterm.h"
+
+#define	DELTA	10
+
+static Flayer	**llist;	/* front to back */
+static int	nllist;
+static int	nlalloc;
+static Rectangle lDrect;
+
+Vis		visibility(Flayer *);
+void		newvisibilities(int);
+void		llinsert(Flayer*);
+void		lldelete(Flayer*);
+
+Image	*maincols[NCOL];
+Image	*cmdcols[NCOL];
+
+void
+flstart(Rectangle r)
+{
+	lDrect = r;
+
+	/* Main text is yellowish */
+	maincols[BACK] = allocimagemix(display, DPaleyellow, DWhite);
+	maincols[HIGH] = allocimage(display, Rect(0,0,1,1), screen->chan, 1, DDarkyellow);
+	maincols[BORD] = allocimage(display, Rect(0,0,2,2), screen->chan, 1, DYellowgreen);
+	maincols[TEXT] = display->black;
+	maincols[HTEXT] = display->black;
+
+	/* Command text is blueish */
+	cmdcols[BACK] = allocimagemix(display, DPalebluegreen, DWhite);
+	cmdcols[HIGH] = allocimage(display, Rect(0,0,1,1), screen->chan, 1, DPalegreygreen);
+	cmdcols[BORD] = allocimage(display, Rect(0,0,2,2), screen->chan, 1, DPurpleblue);
+	cmdcols[TEXT] = display->black;
+	cmdcols[HTEXT] = display->black;
+}
+
+void
+flnew(Flayer *l, Rune *(*fn)(Flayer*, long, ulong*), int u0, void *u1)
+{
+	if(nllist == nlalloc){
+		nlalloc += DELTA;
+		llist = realloc(llist, nlalloc*sizeof(Flayer**));
+		if(llist == 0)
+			panic("flnew");
+	}
+	l->textfn = fn;
+	l->user0 = u0;
+	l->user1 = u1;
+	l->lastsr = ZR;
+	llinsert(l);
+}
+
+Rectangle
+flrect(Flayer *l, Rectangle r)
+{
+	rectclip(&r, lDrect);
+	l->entire = r;
+	l->scroll = insetrect(r, FLMARGIN);
+	r.min.x =
+	 l->scroll.max.x = r.min.x+FLMARGIN+FLSCROLLWID+(FLGAP-FLMARGIN);
+	return r;
+}
+
+void
+flinit(Flayer *l, Rectangle r, Font *ft, Image **cols)
+{
+	lldelete(l);
+	llinsert(l);
+	l->visible = All;
+	l->origin = l->p0 = l->p1 = 0;
+	frinit(&l->f, insetrect(flrect(l, r), FLMARGIN), ft, screen, cols);
+	l->f.maxtab = maxtab*stringwidth(ft, "0");
+	newvisibilities(1);
+	draw(screen, l->entire, l->f.cols[BACK], nil, ZP);
+	scrdraw(l, 0L);
+	flborder(l, 0);
+}
+
+void
+flclose(Flayer *l)
+{
+	if(l->visible == All)
+		draw(screen, l->entire, display->white, nil, ZP);
+	else if(l->visible == Some){
+		if(l->f.b == 0)
+			l->f.b = allocimage(display, l->entire, screen->chan, 0, DNofill);
+		if(l->f.b){
+			draw(l->f.b, l->entire, display->white, nil, ZP);
+			flrefresh(l, l->entire, 0);
+		}
+	}
+	frclear(&l->f, 1);
+	lldelete(l);
+	if(l->f.b && l->visible!=All)
+		freeimage(l->f.b);
+	l->textfn = 0;
+	newvisibilities(1);
+}
+
+void
+flborder(Flayer *l, int wide)
+{
+	if(flprepare(l)){
+		border(l->f.b, l->entire, FLMARGIN, l->f.cols[BACK], ZP);
+		border(l->f.b, l->entire, wide? FLMARGIN : 1, l->f.cols[BORD], ZP);
+		if(l->visible==Some)
+			flrefresh(l, l->entire, 0);
+	}
+}
+
+Flayer *
+flwhich(Point p)
+{
+	int i;
+
+	if(p.x==0 && p.y==0)
+		return nllist? llist[0] : 0;
+	for(i=0; i<nllist; i++)
+		if(ptinrect(p, llist[i]->entire))
+			return llist[i];
+	return 0;
+}
+
+void
+flupfront(Flayer *l)
+{
+	int v = l->visible;
+
+	lldelete(l);
+	llinsert(l);
+	if(v!=All)
+		newvisibilities(0);
+}
+
+void
+newvisibilities(int redraw)
+	/* if redraw false, we know it's a flupfront, and needn't
+	 * redraw anyone becoming partially covered */
+{
+	int i;
+	Vis ov;
+	Flayer *l;
+
+	for(i = 0; i<nllist; i++){
+		l = llist[i];
+		l->lastsr = ZR;	/* make sure scroll bar gets redrawn */
+		ov = l->visible;
+		l->visible = visibility(l);
+#define	V(a, b)	(((a)<<2)|((b)))
+		switch(V(ov, l->visible)){
+		case V(Some, None):
+			if(l->f.b)
+				freeimage(l->f.b);
+		case V(All, None):
+		case V(All, Some):
+			l->f.b = 0;
+			frclear(&l->f, 0);
+			break;
+
+		case V(Some, Some):
+			if(l->f.b==0 && redraw)
+		case V(None, Some):
+				flprepare(l);
+			if(l->f.b && redraw){
+				flrefresh(l, l->entire, 0);
+				freeimage(l->f.b);
+				l->f.b = 0;
+				frclear(&l->f, 0);
+			}
+		case V(None, None):
+		case V(All, All):
+			break;
+
+		case V(Some, All):
+			if(l->f.b){
+				draw(screen, l->entire, l->f.b, nil, l->entire.min);
+				freeimage(l->f.b);
+				l->f.b = screen;
+				break;
+			}
+		case V(None, All):
+			flprepare(l);
+			break;
+		}
+		if(ov==None && l->visible!=None)
+			flnewlyvisible(l);
+	}
+}
+
+void
+llinsert(Flayer *l)
+{
+	int i;
+	for(i=nllist; i>0; --i)
+		llist[i]=llist[i-1];
+	llist[0]=l;
+	nllist++;
+}
+
+void
+lldelete(Flayer *l)
+{
+	int i;
+
+	for(i=0; i<nllist; i++)
+		if(llist[i]==l){
+			--nllist;
+			for(; i<nllist; i++)
+				llist[i] = llist[i+1];
+			return;
+		}
+	panic("lldelete");
+}
+
+void
+flinsert(Flayer *l, Rune *sp, Rune *ep, long p0)
+{
+	if(flprepare(l)){
+		frinsert(&l->f, sp, ep, p0-l->origin);
+		scrdraw(l, scrtotal(l));
+		if(l->visible==Some)
+			flrefresh(l, l->entire, 0);
+	}
+}
+
+void
+fldelete(Flayer *l, long p0, long p1)
+{
+	if(flprepare(l)){
+		p0 -= l->origin;
+		if(p0 < 0)
+			p0 = 0;
+		p1 -= l->origin;
+		if(p1<0)
+			p1 = 0;
+		frdelete(&l->f, p0, p1);
+		scrdraw(l, scrtotal(l));
+		if(l->visible==Some)
+			flrefresh(l, l->entire, 0);
+	}
+}
+
+int
+flselect(Flayer *l)
+{
+	int ret = 0;
+	if(l->visible!=All)
+		flupfront(l);
+	frselect(&l->f, mousectl);
+	if(l->f.p0==l->f.p1){
+		if(mousep->msec-l->click<Clicktime && l->f.p0+l->origin==l->p0){
+			ret = 1;
+			l->click = 0;
+		}else
+			l->click = mousep->msec;
+	}else
+		l->click = 0;
+	l->p0 = l->f.p0+l->origin, l->p1 = l->f.p1+l->origin;
+	return ret;
+}
+
+void
+flsetselect(Flayer *l, long p0, long p1)
+{
+	ulong fp0, fp1;
+
+	l->click = 0;
+	if(l->visible==None || !flprepare(l)){
+		l->p0 = p0, l->p1 = p1;
+		return;
+	}
+	l->p0 = p0, l->p1 = p1;
+	flfp0p1(l, &fp0, &fp1);
+	if(fp0==l->f.p0 && fp1==l->f.p1)
+		return;
+
+	if(fp1<=l->f.p0 || fp0>=l->f.p1 || l->f.p0==l->f.p1 || fp0==fp1){
+		/* no overlap or trivial repainting */
+		frdrawsel(&l->f, frptofchar(&l->f, l->f.p0), l->f.p0, l->f.p1, 0);
+		frdrawsel(&l->f, frptofchar(&l->f, fp0), fp0, fp1, 1);
+		goto Refresh;
+	}
+	/* the current selection and the desired selection overlap and are both non-empty */
+	if(fp0 < l->f.p0){
+		/* extend selection backwards */
+		frdrawsel(&l->f, frptofchar(&l->f, fp0), fp0, l->f.p0, 1);
+	}else if(fp0 > l->f.p0){
+		/* trim first part of selection */
+		frdrawsel(&l->f, frptofchar(&l->f, l->f.p0), l->f.p0, fp0, 0);
+	}
+	if(fp1 > l->f.p1){
+		/* extend selection forwards */
+		frdrawsel(&l->f, frptofchar(&l->f, l->f.p1), l->f.p1, fp1, 1);
+	}else if(fp1 < l->f.p1){
+		/* trim last part of selection */
+		frdrawsel(&l->f, frptofchar(&l->f, fp1), fp1, l->f.p1, 0);
+	}
+
+    Refresh:
+	l->f.p0 = fp0;
+	l->f.p1 = fp1;
+	if(l->visible==Some)
+		flrefresh(l, l->entire, 0);
+}
+
+void
+flfp0p1(Flayer *l, ulong *pp0, ulong *pp1)
+{
+	long p0 = l->p0-l->origin, p1 = l->p1-l->origin;
+
+	if(p0 < 0)
+		p0 = 0;
+	if(p1 < 0)
+		p1 = 0;
+	if(p0 > l->f.nchars)
+		p0 = l->f.nchars;
+	if(p1 > l->f.nchars)
+		p1 = l->f.nchars;
+	*pp0 = p0;
+	*pp1 = p1;
+}
+
+Rectangle
+rscale(Rectangle r, Point old, Point new)
+{
+	r.min.x = r.min.x*new.x/old.x;
+	r.min.y = r.min.y*new.y/old.y;
+	r.max.x = r.max.x*new.x/old.x;
+	r.max.y = r.max.y*new.y/old.y;
+	return r;
+}
+
+void
+flresize(Rectangle dr)
+{
+	int i;
+	Flayer *l;
+	Frame *f;
+	Rectangle r, olDrect;
+	int move;
+
+	olDrect = lDrect;
+	lDrect = dr;
+	move = 0;
+	/* no moving on rio; must repaint */
+	if(0 && Dx(dr)==Dx(olDrect) && Dy(dr)==Dy(olDrect))
+		move = 1;
+	else
+		draw(screen, lDrect, display->white, nil, ZP);
+	for(i=0; i<nllist; i++){
+		l = llist[i];
+		l->lastsr = ZR;
+		f = &l->f;
+		if(move)
+			r = rectaddpt(rectsubpt(l->entire, olDrect.min), dr.min);
+		else{
+			r = rectaddpt(rscale(rectsubpt(l->entire, olDrect.min),
+				subpt(olDrect.max, olDrect.min),
+				subpt(dr.max, dr.min)), dr.min);
+			if(l->visible==Some && f->b){
+				freeimage(f->b);
+				frclear(f, 0);
+			}
+			f->b = 0;
+			if(l->visible!=None)
+				frclear(f, 0);
+		}
+		if(!rectclip(&r, dr))
+			panic("flresize");
+		if(r.max.x-r.min.x<100)
+			r.min.x = dr.min.x;
+		if(r.max.x-r.min.x<100)
+			r.max.x = dr.max.x;
+		if(r.max.y-r.min.y<2*FLMARGIN+f->font->height)
+			r.min.y = dr.min.y;
+		if(r.max.y-r.min.y<2*FLMARGIN+f->font->height)
+			r.max.y = dr.max.y;
+		if(!move)
+			l->visible = None;
+		frsetrects(f, insetrect(flrect(l, r), FLMARGIN), f->b);
+		if(!move && f->b)
+			scrdraw(l, scrtotal(l));
+	}
+	newvisibilities(1);
+}
+
+int
+flprepare(Flayer *l)
+{
+	Frame *f;
+	ulong n;
+	Rune *r;
+
+	if(l->visible == None)
+		return 0;
+	f = &l->f;
+	if(f->b == 0){
+		if(l->visible == All)
+			f->b = screen;
+		else if((f->b = allocimage(display, l->entire, screen->chan, 0, 0))==0)
+			return 0;
+		draw(f->b, l->entire, f->cols[BACK], nil, ZP);
+		border(f->b, l->entire, l==llist[0]? FLMARGIN : 1, f->cols[BORD], ZP);
+		n = f->nchars;
+		frinit(f, f->entire, f->font, f->b, 0);
+		f->maxtab = maxtab*stringwidth(f->font, "0");
+		r = (*l->textfn)(l, n, &n);
+		frinsert(f, r, r+n, (ulong)0);
+		frdrawsel(f, frptofchar(f, f->p0), f->p0, f->p1, 0);
+		flfp0p1(l, &f->p0, &f->p1);
+		frdrawsel(f, frptofchar(f, f->p0), f->p0, f->p1, 1);
+		l->lastsr = ZR;
+		scrdraw(l, scrtotal(l));
+	}
+	return 1;
+}
+
+static	int	somevis, someinvis, justvis;
+
+Vis
+visibility(Flayer *l)
+{
+	somevis = someinvis = 0;
+	justvis = 1;
+	flrefresh(l, l->entire, 0);
+	justvis = 0;
+	if(somevis==0)
+		return None;
+	if(someinvis==0)
+		return All;
+	return Some;
+}
+
+void
+flrefresh(Flayer *l, Rectangle r, int i)
+{
+	Flayer *t;
+	Rectangle s;
+
+    Top:
+	if((t=llist[i++]) == l){
+		if(!justvis)
+			draw(screen, r, l->f.b, nil, r.min);
+		somevis = 1;
+	}else{
+		if(!rectXrect(t->entire, r))
+			goto Top;	/* avoid stacking unnecessarily */
+		if(t->entire.min.x>r.min.x){
+			s = r;
+			s.max.x = t->entire.min.x;
+			flrefresh(l, s, i);
+			r.min.x = t->entire.min.x;
+		}
+		if(t->entire.min.y>r.min.y){
+			s = r;
+			s.max.y = t->entire.min.y;
+			flrefresh(l, s, i);
+			r.min.y = t->entire.min.y;
+		}
+		if(t->entire.max.x<r.max.x){
+			s = r;
+			s.min.x = t->entire.max.x;
+			flrefresh(l, s, i);
+			r.max.x = t->entire.max.x;
+		}
+		if(t->entire.max.y<r.max.y){
+			s = r;
+			s.min.y = t->entire.max.y;
+			flrefresh(l, s, i);
+			r.max.y = t->entire.max.y;
+		}
+		/* remaining piece of r is blocked by t; forget about it */
+		someinvis = 1;
+	}
+}
diff --git a/src/cmd/samterm/flayer.h b/src/cmd/samterm/flayer.h
new file mode 100644
index 0000000..41306f7
--- /dev/null
+++ b/src/cmd/samterm/flayer.h
@@ -0,0 +1,50 @@
+typedef enum Vis{
+	None=0,
+	Some,
+	All,
+}Vis;
+
+enum{
+	Clicktime=1000,		/* one second */
+};
+
+typedef struct Flayer Flayer;
+
+struct Flayer
+{
+	Frame		f;
+	long		origin;	/* offset of first char in flayer */
+	long		p0, p1;
+	long		click;	/* time at which selection click occurred, in HZ */
+	Rune		*(*textfn)(Flayer*, long, ulong*);
+	int		user0;
+	void		*user1;
+	Rectangle	entire;
+	Rectangle	scroll;
+	Rectangle	lastsr;	/* geometry of scrollbar when last drawn */
+	Vis		visible;
+};
+
+void	flborder(Flayer*, int);
+void	flclose(Flayer*);
+void	fldelete(Flayer*, long, long);
+void	flfp0p1(Flayer*, ulong*, ulong*);
+void	flinit(Flayer*, Rectangle, Font*, Image**);
+void	flinsert(Flayer*, Rune*, Rune*, long);
+void	flnew(Flayer*, Rune *(*fn)(Flayer*, long, ulong*), int, void*);
+int	flprepare(Flayer*);
+Rectangle flrect(Flayer*, Rectangle);
+void	flrefresh(Flayer*, Rectangle, int);
+void	flresize(Rectangle);
+int	flselect(Flayer*);
+void	flsetselect(Flayer*, long, long);
+void	flstart(Rectangle);
+void	flupfront(Flayer*);
+Flayer	*flwhich(Point);
+
+#define	FLMARGIN	4
+#define	FLSCROLLWID	12
+#define	FLGAP		4
+
+extern	Image	*maincols[NCOL];
+extern	Image	*cmdcols[NCOL];
diff --git a/src/cmd/samterm/io.c b/src/cmd/samterm/io.c
new file mode 100644
index 0000000..dfdfd9c
--- /dev/null
+++ b/src/cmd/samterm/io.c
@@ -0,0 +1,293 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+#include <thread.h>
+#include <mouse.h>
+#include <cursor.h>
+#include <keyboard.h>
+#include <frame.h>
+#include "flayer.h"
+#include "samterm.h"
+
+int	cursorfd;
+int	plumbfd = -1;
+int	input;
+int	got;
+int	block;
+int	kbdc;
+int	resized;
+uchar	*hostp;
+uchar	*hoststop;
+uchar	*plumbbase;
+uchar	*plumbp;
+uchar	*plumbstop;
+Channel	*plumbc;
+Channel	*hostc;
+Mousectl	*mousectl;
+Mouse	*mousep;
+Keyboardctl *keyboardctl;
+void	panic(char*);
+
+void
+initio(void)
+{
+	threadsetname("main");
+	mousectl = initmouse(nil, display->image);
+	if(mousectl == nil){
+		fprint(2, "samterm: mouse init failed: %r\n");
+		threadexitsall("mouse");
+	}
+	mousep = &mousectl->m;
+	keyboardctl = initkeyboard(nil);
+	if(keyboardctl == nil){
+		fprint(2, "samterm: keyboard init failed: %r\n");
+		threadexitsall("kbd");
+	}
+	hoststart();
+	if(plumbstart() < 0)
+		extstart();
+}
+
+void
+getmouse(void)
+{
+	if(readmouse(mousectl) < 0)
+		panic("mouse");
+}
+
+void
+mouseunblock(void)
+{
+	got &= ~(1<<RMouse);
+}
+
+void
+kbdblock(void)
+{		/* ca suffit */
+	block = (1<<RKeyboard)|(1<<RPlumb);
+}
+
+int
+button(int but)
+{
+	getmouse();
+	return mousep->buttons&(1<<(but-1));
+}
+
+/*
+void
+externload(int i)
+{
+	plumbbase = malloc(plumbbuf[i].n);
+	if(plumbbase == 0)
+		return;
+	memmove(plumbbase, plumbbuf[i].data, plumbbuf[i].n);
+	plumbp = plumbbase;
+	plumbstop = plumbbase + plumbbuf[i].n;
+	got |= 1<<RPlumb;
+}
+*/
+
+int
+waitforio(void)
+{
+	Alt alts[NRes+1];
+	Rune r;
+	int i;
+	ulong type;
+
+again:
+
+	alts[RPlumb].c = plumbc;
+	alts[RPlumb].v = &i;
+	alts[RPlumb].op = CHANRCV;
+	if((block & (1<<RPlumb)) || plumbc == nil)
+		alts[RPlumb].op = CHANNOP;
+
+	alts[RHost].c = hostc;
+	alts[RHost].v = &i;
+	alts[RHost].op = CHANRCV;
+	if(block & (1<<RHost))
+		alts[RHost].op = CHANNOP;
+
+	alts[RKeyboard].c = keyboardctl->c;
+	alts[RKeyboard].v = &r;
+	alts[RKeyboard].op = CHANRCV;
+	if(block & (1<<RKeyboard))
+		alts[RKeyboard].op = CHANNOP;
+
+	alts[RMouse].c = mousectl->c;
+	alts[RMouse].v = &mousectl->m;
+	alts[RMouse].op = CHANRCV;
+	if(block & (1<<RMouse))
+		alts[RMouse].op = CHANNOP;
+
+	alts[RResize].c = mousectl->resizec;
+	alts[RResize].v = nil;
+	alts[RResize].op = CHANRCV;
+	if(block & (1<<RResize))
+		alts[RResize].op = CHANNOP;
+
+	alts[NRes].op = CHANEND;
+
+	if(got & ~block)
+		return got & ~block;
+	flushimage(display, 1);
+	type = alt(alts);
+	switch(type){
+	case RHost:
+		hostp = hostbuf[i].data;
+		hoststop = hostbuf[i].data + hostbuf[i].n;
+		block = 0;
+		break;
+/*
+	case RPlumb:
+		externload(i);
+		break;
+*/
+	case RKeyboard:
+		kbdc = r;
+		break;
+	case RMouse:
+		break;
+	case RResize:
+		resized = 1;
+		/* do the resize in line if we've finished initializing and we're not in a blocking state */
+		if(hasunlocked && block==0 && RESIZED())
+			resize();
+		goto again;
+	}
+	got |= 1<<type;
+	return got; 
+}
+
+int
+rcvchar(void)
+{
+	int c;
+
+	if(!(got & (1<<RHost)))
+		return -1;
+	c = *hostp++;
+	if(hostp == hoststop)
+		got &= ~(1<<RHost);
+	return c;
+}
+
+char*
+rcvstring(void)
+{
+	*hoststop = 0;
+	got &= ~(1<<RHost);
+	return (char*)hostp;
+}
+
+int
+getch(void)
+{
+	int c;
+
+	while((c = rcvchar()) == -1){
+		block = ~(1<<RHost);
+		waitforio();
+		block = 0;
+	}
+	return c;
+}
+
+int
+externchar(void)
+{
+	Rune r;
+
+    loop:
+	if(got & ((1<<RPlumb) & ~block)){
+		plumbp += chartorune(&r, (char*)plumbp);
+		if(plumbp >= plumbstop){
+			got &= ~(1<<RPlumb);
+			free(plumbbase);
+		}
+		if(r == 0)
+			goto loop;
+		return r;
+	}
+	return -1;
+}
+
+int kpeekc = -1;
+int
+ecankbd(void)
+{
+	Rune r;
+
+	if(kpeekc >= 0)
+		return 1;
+	if(nbrecv(keyboardctl->c, &r) > 0){
+		kpeekc = r;
+		return 1;
+	}
+	return 0;
+}
+
+int
+ekbd(void)
+{
+	int c;
+	Rune r;
+
+	if(kpeekc >= 0){
+		c = kpeekc;
+		kpeekc = -1;
+		return c;
+	}
+	if(recv(keyboardctl->c, &r) < 0){
+		fprint(2, "samterm: keybard recv error: %r\n");
+		panic("kbd");
+	}
+	return r;
+}
+
+int
+kbdchar(void)
+{
+	int c, i;
+
+	c = externchar();
+	if(c > 0)
+		return c;
+	if(got & (1<<RKeyboard)){
+		c = kbdc;
+		kbdc = -1;
+		got &= ~(1<<RKeyboard);
+		return c;
+	}
+#if 0
+	while(plumbc!=nil && nbrecv(plumbc, &i)>0){
+		externload(i);
+		c = externchar();
+		if(c > 0)
+			return c;
+	}
+#endif
+	if(!ecankbd())
+		return -1;
+	return ekbd();
+}
+
+int
+qpeekc(void)
+{
+	return kbdc;
+}
+
+int
+RESIZED(void)
+{
+	if(resized){
+		if(getwindow(display, Refnone) < 0)
+			panic("can't reattach to window");
+		resized = 0;
+		return 1;
+	}
+	return 0;
+}
diff --git a/src/cmd/samterm/main.c b/src/cmd/samterm/main.c
new file mode 100644
index 0000000..ef958d7
--- /dev/null
+++ b/src/cmd/samterm/main.c
@@ -0,0 +1,585 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+#include <thread.h>
+#include <mouse.h>
+#include <cursor.h>
+#include <keyboard.h>
+#include <frame.h>
+#include "flayer.h"
+#include "samterm.h"
+
+Text	cmd;
+Rune	*scratch;
+long	nscralloc;
+Cursor	*cursor;
+Flayer	*which = 0;
+Flayer	*work = 0;
+long	snarflen;
+long	typestart = -1;
+long	typeend = -1;
+long	typeesc = -1;
+long	modified = 0;		/* strange lookahead for menus */
+char	hostlock = 1;
+char	hasunlocked = 0;
+int	maxtab = 8;
+
+void
+threadmain(int argc, char *argv[])
+{
+	int i, got, scr;
+	Text *t;
+	Rectangle r;
+	Flayer *nwhich;
+
+	getscreen(argc, argv);
+	iconinit();
+	initio();
+	scratch = alloc(100*RUNESIZE);
+	nscralloc = 100;
+	r = screen->r;
+	r.max.y = r.min.y+Dy(r)/5;
+	flstart(screen->clipr);
+	rinit(&cmd.rasp);
+	flnew(&cmd.l[0], gettext, 1, &cmd);
+	flinit(&cmd.l[0], r, font, cmdcols);
+	cmd.nwin = 1;
+	which = &cmd.l[0];
+	cmd.tag = Untagged;
+	outTs(Tversion, VERSION);
+	startnewfile(Tstartcmdfile, &cmd);
+
+	got = 0;
+	for(;;got = waitforio()){
+		if(hasunlocked && RESIZED())
+			resize();
+		if(got&(1<<RHost))
+			rcv();
+		if(got&(1<<RPlumb)){
+			for(i=0; cmd.l[i].textfn==0; i++)
+				;
+			current(&cmd.l[i]);
+			flsetselect(which, cmd.rasp.nrunes, cmd.rasp.nrunes);
+			type(which, RPlumb);
+		}
+		if(got&(1<<RKeyboard))
+			if(which)
+				type(which, RKeyboard);
+			else
+				kbdblock();
+		if(got&(1<<RMouse)){
+			if(hostlock==2 || !ptinrect(mousep->xy, screen->r)){
+				mouseunblock();
+				continue;
+			}
+			nwhich = flwhich(mousep->xy);
+			scr = which && ptinrect(mousep->xy, which->scroll);
+			if(mousep->buttons)
+				flushtyping(1);
+			if(mousep->buttons&1){
+				if(nwhich){
+					if(nwhich!=which)
+						current(nwhich);
+					else if(scr)
+						scroll(which, 1);
+					else{
+						t=(Text *)which->user1;
+						if(flselect(which)){
+							outTsl(Tdclick, t->tag, which->p0);
+							t->lock++;
+						}else if(t!=&cmd)
+							outcmd();
+					}
+				}
+			}else if((mousep->buttons&2) && which){
+				if(scr)
+					scroll(which, 2);
+				else
+					menu2hit();
+			}else if((mousep->buttons&4)){
+				if(scr)
+					scroll(which, 3);
+				else
+					menu3hit();
+			}
+			mouseunblock();
+		}
+	}
+}
+
+
+void
+resize(void)
+{
+	int i;
+
+	flresize(screen->clipr);
+	for(i = 0; i<nname; i++)
+		if(text[i])
+			hcheck(text[i]->tag);
+}
+
+void
+current(Flayer *nw)
+{
+	Text *t;
+
+	if(which)
+		flborder(which, 0);
+	if(nw){
+		flushtyping(1);
+		flupfront(nw);
+		flborder(nw, 1);
+		buttons(Up);
+		t = (Text *)nw->user1;
+		t->front = nw-&t->l[0];
+		if(t != &cmd)
+			work = nw;
+	}
+	which = nw;
+}
+
+void
+closeup(Flayer *l)
+{
+	Text *t=(Text *)l->user1;
+	int m;
+
+	m = whichmenu(t->tag);
+	if(m < 0)
+		return;
+	flclose(l);
+	if(l == which){
+		which = 0;
+		current(flwhich(Pt(0, 0)));
+	}
+	if(l == work)
+		work = 0;
+	if(--t->nwin == 0){
+		rclear(&t->rasp);
+		free((uchar *)t);
+		text[m] = 0;
+	}else if(l == &t->l[t->front]){
+		for(m=0; m<NL; m++)	/* find one; any one will do */
+			if(t->l[m].textfn){
+				t->front = m;
+				return;
+			}
+		panic("close");
+	}
+}
+
+Flayer *
+findl(Text *t)
+{
+	int i;
+	for(i = 0; i<NL; i++)
+		if(t->l[i].textfn==0)
+			return &t->l[i];
+	return 0;
+}
+
+void
+duplicate(Flayer *l, Rectangle r, Font *f, int close)
+{
+	Text *t=(Text *)l->user1;
+	Flayer *nl = findl(t);
+	Rune *rp;
+	ulong n;
+
+	if(nl){
+		flnew(nl, gettext, l->user0, (char *)t);
+		flinit(nl, r, f, l->f.cols);
+		nl->origin = l->origin;
+		rp = (*l->textfn)(l, l->f.nchars, &n);
+		flinsert(nl, rp, rp+n, l->origin);
+		flsetselect(nl, l->p0, l->p1);
+		if(close){
+			flclose(l);
+			if(l==which)
+				which = 0;
+		}else
+			t->nwin++;
+		current(nl);
+		hcheck(t->tag);
+	}
+	setcursor(mousectl, cursor);
+}
+
+void
+buttons(int updown)
+{
+	while(((mousep->buttons&7)!=0) != updown)
+		getmouse();
+}
+
+int
+getr(Rectangle *rp)
+{
+	Point p;
+	Rectangle r;
+
+	*rp = getrect(3, mousectl);
+	if(rp->max.x && rp->max.x-rp->min.x<=5 && rp->max.y-rp->min.y<=5){
+		p = rp->min;
+		r = cmd.l[cmd.front].entire;
+		*rp = screen->r;
+		if(cmd.nwin==1){
+			if (p.y <= r.min.y)
+				rp->max.y = r.min.y;
+			else if (p.y >= r.max.y)
+				rp->min.y = r.max.y;
+			if (p.x <= r.min.x)
+				rp->max.x = r.min.x;
+			else if (p.x >= r.max.x)
+				rp->min.x = r.max.x;
+		}
+	}
+	return rectclip(rp, screen->r) &&
+	   rp->max.x-rp->min.x>100 && rp->max.y-rp->min.y>40;
+}
+
+void
+snarf(Text *t, int w)
+{
+	Flayer *l = &t->l[w];
+
+	if(l->p1>l->p0){
+		snarflen = l->p1-l->p0;
+		outTsll(Tsnarf, t->tag, l->p0, l->p1);
+	}
+}
+
+void
+cut(Text *t, int w, int save, int check)
+{
+	long p0, p1;
+	Flayer *l;
+
+	l = &t->l[w];
+	p0 = l->p0;
+	p1 = l->p1;
+	if(p0 == p1)
+		return;
+	if(p0 < 0)
+		panic("cut");
+	if(save)
+		snarf(t, w);
+	outTsll(Tcut, t->tag, p0, p1);
+	flsetselect(l, p0, p0);
+	t->lock++;
+	hcut(t->tag, p0, p1-p0);
+	if(check)
+		hcheck(t->tag);
+}
+
+void
+paste(Text *t, int w)
+{
+	if(snarflen){
+		cut(t, w, 0, 0);
+		t->lock++;
+		outTsl(Tpaste, t->tag, t->l[w].p0);
+	}
+}
+
+void
+scrorigin(Flayer *l, int but, long p0)
+{
+	Text *t=(Text *)l->user1;
+
+	switch(but){
+	case 1:
+		outTsll(Torigin, t->tag, l->origin, p0);
+		break;
+	case 2:
+		outTsll(Torigin, t->tag, p0, 1L);
+		break;
+	case 3:
+		horigin(t->tag,p0);
+	}
+}
+
+int
+alnum(int c)
+{
+	/*
+	 * Hard to get absolutely right.  Use what we know about ASCII
+	 * and assume anything above the Latin control characters is
+	 * potentially an alphanumeric.
+	 */
+	if(c<=' ')
+		return 0;
+	if(0x7F<=c && c<=0xA0)
+		return 0;
+	if(utfrune("!\"#$%&'()*+,-./:;<=>?@[\\]^`{|}~", c))
+		return 0;
+	return 1;
+}
+
+int
+raspc(Rasp *r, long p)
+{
+	ulong n;
+	rload(r, p, p+1, &n);
+	if(n)
+		return scratch[0];
+	return 0;
+}
+
+long
+ctlw(Rasp *r, long o, long p)
+{
+	int c;
+
+	if(--p < o)
+		return o;
+	if(raspc(r, p)=='\n')
+		return p;
+	for(; p>=o && !alnum(c=raspc(r, p)); --p)
+		if(c=='\n')
+			return p+1;
+	for(; p>o && alnum(raspc(r, p-1)); --p)
+		;
+	return p>=o? p : o;
+}
+
+long
+ctlu(Rasp *r, long o, long p)
+{
+	for(; p-1>=o && raspc(r, p-1)!='\n'; --p)
+		;
+	return p>=o? p : o;
+}
+
+int
+center(Flayer *l, long a)
+{
+	Text *t;
+
+	t = l->user1;
+	if(!t->lock && (a<l->origin || l->origin+l->f.nchars<a)){
+		if(a > t->rasp.nrunes)
+			a = t->rasp.nrunes;
+		outTsll(Torigin, t->tag, a, 2L);
+		return 1;
+	}
+	return 0;
+}
+
+int
+onethird(Flayer *l, long a)
+{
+	Text *t;
+	Rectangle s;
+	long lines;
+
+	t = l->user1;
+	if(!t->lock && (a<l->origin || l->origin+l->f.nchars<a)){
+		if(a > t->rasp.nrunes)
+			a = t->rasp.nrunes;
+		s = insetrect(l->scroll, 1);
+		lines = ((s.max.y-s.min.y)/l->f.font->height+1)/3;
+		if (lines < 2)
+			lines = 2;
+		outTsll(Torigin, t->tag, a, lines);
+		return 1;
+	}
+	return 0;
+}
+
+void
+flushtyping(int clearesc)
+{
+	Text *t;
+	ulong n;
+
+	if(clearesc)
+		typeesc = -1;	
+	if(typestart == typeend) {
+		modified = 0;
+		return;
+	}
+	t = which->user1;
+	if(t != &cmd)
+		modified = 1;
+	rload(&t->rasp, typestart, typeend, &n);
+	scratch[n] = 0;
+	if(t==&cmd && typeend==t->rasp.nrunes && scratch[typeend-typestart-1]=='\n'){
+		setlock();
+		outcmd();
+	}
+	outTslS(Ttype, t->tag, typestart, scratch);
+	typestart = -1;
+	typeend = -1;
+}
+
+#define	SCROLLKEY	Kdown
+#define	BACKSCROLLKEY	Kup
+#define	ESC		0x1B
+
+void
+type(Flayer *l, int res)	/* what a bloody mess this is */
+{
+	Text *t = (Text *)l->user1;
+	Rune buf[100];
+	Rune *p = buf;
+	int c, backspacing;
+	long a, a0;
+	int scrollkey;
+
+	scrollkey = 0;
+	if(res == RKeyboard)
+		scrollkey = (qpeekc()==SCROLLKEY || qpeekc()==BACKSCROLLKEY);	/* ICK */
+
+	if(hostlock || t->lock){
+		kbdblock();
+		return;
+	}
+	a = l->p0;
+	if(a!=l->p1 && !scrollkey){
+		flushtyping(1);
+		cut(t, t->front, 1, 1);
+		return;	/* it may now be locked */
+	}
+	backspacing = 0;
+	while((c = kbdchar())>0){
+		if(res == RKeyboard){
+			if(c==SCROLLKEY || c==BACKSCROLLKEY || c==ESC)
+				break;
+			/* backspace, ctrl-u, ctrl-w, del */
+			if(c=='\b' || c==0x15 || c==0x17 || c==0x7F){
+				backspacing = 1;
+				break;
+			}
+		}
+		*p++ = c;
+		if(c == '\n' || p >= buf+sizeof(buf)/sizeof(buf[0]))
+			break;
+	}
+	if(p > buf){
+		if(typestart < 0)
+			typestart = a;
+		if(typeesc < 0)
+			typeesc = a;
+		hgrow(t->tag, a, p-buf, 0);
+		t->lock++;	/* pretend we Trequest'ed for hdatarune*/
+		hdatarune(t->tag, a, buf, p-buf);
+		a += p-buf;
+		l->p0 = a;
+		l->p1 = a;
+		typeend = a;
+		if(c=='\n' || typeend-typestart>100)
+			flushtyping(0);
+		onethird(l, a);
+	}
+	if(c == SCROLLKEY){
+		flushtyping(0);
+		center(l, l->origin+l->f.nchars+1);
+		/* backspacing immediately after outcmd(): sorry */
+	}else if(c == BACKSCROLLKEY){
+		flushtyping(0);
+		a0 = l->origin-l->f.nchars;
+		if(a0 < 0)
+			a0 = 0;
+		center(l, a0);
+	}else if(backspacing && !hostlock){
+		if(l->f.p0>0 && a>0){
+			switch(c){
+			case '\b':
+			case 0x7F:	/* del */
+				l->p0 = a-1;
+				break;
+			case 0x15:	/* ctrl-u */
+				l->p0 = ctlu(&t->rasp, l->origin, a);
+				break;
+			case 0x17:	/* ctrl-w */
+				l->p0 = ctlw(&t->rasp, l->origin, a);
+				break;
+			}
+			l->p1 = a;
+			if(l->p1 != l->p0){
+				/* cut locally if possible */
+				if(typestart<=l->p0 && l->p1<=typeend){
+					t->lock++;	/* to call hcut */
+					hcut(t->tag, l->p0, l->p1-l->p0);
+					/* hcheck is local because we know rasp is contiguous */
+					hcheck(t->tag);
+				}else{
+					flushtyping(0);
+					cut(t, t->front, 0, 1);
+				}
+			}
+			if(typeesc >= l->p0)
+				typeesc = l->p0;
+			if(typestart >= 0){
+				if(typestart >= l->p0)
+					typestart = l->p0;
+				typeend = l->p0;
+				if(typestart == typeend){
+					typestart = -1;
+					typeend = -1;
+					modified = 0;
+				}
+			}
+		}
+	}else{
+		if(c==ESC && typeesc>=0){
+			l->p0 = typeesc;
+			l->p1 = a;
+			flushtyping(1);
+		}
+		for(l=t->l; l<&t->l[NL]; l++)
+			if(l->textfn)
+				flsetselect(l, l->p0, l->p1);
+	}
+}
+
+
+void
+outcmd(void){
+	if(work)
+		outTsll(Tworkfile, ((Text *)work->user1)->tag, work->p0, work->p1);
+}
+
+void
+panic(char *s)
+{
+	panic1(display, s);
+}
+
+void
+panic1(Display *d, char *s)
+{
+	fprint(2, "samterm:panic: ");
+	perror(s);
+	abort();
+}
+
+Rune*
+gettext(Flayer *l, long n, ulong *np)
+{
+	Text *t;
+
+	t = l->user1;
+	rload(&t->rasp, l->origin, l->origin+n, np);
+	return scratch;
+}
+
+long
+scrtotal(Flayer *l)
+{
+	return ((Text *)l->user1)->rasp.nrunes;
+}
+
+void*
+alloc(ulong n)
+{
+	void *p;
+
+	p = malloc(n);
+	if(p == 0)
+		panic("alloc");
+	memset(p, 0, n);
+	return p;
+}
diff --git a/src/cmd/samterm/menu.c b/src/cmd/samterm/menu.c
new file mode 100644
index 0000000..18070a8
--- /dev/null
+++ b/src/cmd/samterm/menu.c
@@ -0,0 +1,403 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+#include <thread.h>
+#include <mouse.h>
+#include <cursor.h>
+#include <keyboard.h>
+#include <frame.h>
+#include "flayer.h"
+#include "samterm.h"
+
+uchar	**name;	/* first byte is ' ' or '\'': modified state */
+Text	**text;	/* pointer to Text associated with file */
+ushort	*tag;		/* text[i].tag, even if text[i] not defined */
+int	nname;
+int	mname;
+int	mw;
+
+char	*genmenu3(int);
+char	*genmenu2(int);
+char	*genmenu2c(int);
+
+enum Menu2
+{
+	Cut,
+	Paste,
+	Snarf,
+	Plumb,
+	Look,
+	Exch,
+	Search,
+	NMENU2 = Search,
+	Send = Search,
+	NMENU2C
+};
+
+enum Menu3
+{
+	New,
+	Zerox,
+	Resize,
+	Close,
+	Write,
+	NMENU3
+};
+
+char	*menu2str[] = {
+	"cut",
+	"paste",
+	"snarf",
+	"plumb",
+	"look",
+	"<rio>",
+	0,		/* storage for last pattern */
+};
+
+char	*menu3str[] = {
+	"new",
+	"zerox",
+	"resize",
+	"close",
+	"write",
+};
+
+Menu	menu2 =	{0, genmenu2};
+Menu	menu2c ={0, genmenu2c};
+Menu	menu3 =	{0, genmenu3};
+
+void
+menu2hit(void)
+{
+	Text *t=(Text *)which->user1;
+	int w = which-t->l;
+	int m;
+
+	if(hversion==0 || plumbfd<0)
+		menu2str[Plumb] = "(plumb)";
+	m = menuhit(2, mousectl, t==&cmd? &menu2c : &menu2, nil);
+	if(hostlock || t->lock)
+		return;
+
+	switch(m){
+	case Cut:
+		cut(t, w, 1, 1);
+		break;
+
+	case Paste:
+		paste(t, w);
+		break;
+
+	case Snarf:
+		snarf(t, w);
+		break;
+
+	case Plumb:
+		if(hversion > 0)
+			outTsll(Tplumb, t->tag, which->p0, which->p1);
+		break;
+
+	case Exch:
+		snarf(t, w);
+		outT0(Tstartsnarf);
+		setlock();
+		break;
+
+	case Look:
+		outTsll(Tlook, t->tag, which->p0, which->p1);
+		setlock();
+		break;
+
+	case Search:
+		outcmd();
+		if(t==&cmd)
+			outTsll(Tsend, 0 /*ignored*/, which->p0, which->p1);
+		else
+			outT0(Tsearch);
+		setlock();
+		break;
+	}
+}
+
+void
+menu3hit(void)
+{
+	Rectangle r;
+	Flayer *l;
+	int m, i;
+	Text *t;
+
+	mw = -1;
+	m = menuhit(3, mousectl, &menu3, nil);
+	switch(m){
+	case -1:
+		break;
+
+	case New:
+		if(!hostlock)
+			sweeptext(1, 0);
+		break;
+
+	case Zerox:
+	case Resize:
+		if(!hostlock){
+			setcursor(mousectl, &bullseye);
+			buttons(Down);
+			if((mousep->buttons&4) && (l = flwhich(mousep->xy)) && getr(&r))
+				duplicate(l, r, l->f.font, m==Resize);
+			else
+				setcursor(mousectl, cursor);
+			buttons(Up);
+		}
+		break;
+
+	case Close:
+		if(!hostlock){
+			setcursor(mousectl, &bullseye);
+			buttons(Down);
+			if((mousep->buttons&4) && (l = flwhich(mousep->xy)) && !hostlock){
+				t=(Text *)l->user1;
+				if (t->nwin>1)
+					closeup(l);
+				else if(t!=&cmd) {
+					outTs(Tclose, t->tag);
+					setlock();
+				}
+			}
+			setcursor(mousectl, cursor);
+			buttons(Up);
+		}
+		break;
+
+	case Write:
+		if(!hostlock){
+			setcursor(mousectl, &bullseye);
+			buttons(Down);
+			if((mousep->buttons&4) && (l = flwhich(mousep->xy))){
+				outTs(Twrite, ((Text *)l->user1)->tag);
+				setlock();
+			}else
+				setcursor(mousectl, cursor);
+			buttons(Up);
+		}
+		break;
+
+	default:
+		if(t = text[m-NMENU3]){
+			i = t->front;
+			if(t->nwin==0 || t->l[i].textfn==0)
+				return;	/* not ready yet; try again later */
+			if(t->nwin>1 && which==&t->l[i])
+				do
+					if(++i==NL)
+						i = 0;
+				while(i!=t->front && t->l[i].textfn==0);
+			current(&t->l[i]);
+		}else if(!hostlock)
+			sweeptext(0, tag[m-NMENU3]);
+		break;
+	}
+}
+
+
+Text *
+sweeptext(int new, int tag)
+{
+	Rectangle r;
+	Text *t;
+
+	if(getr(&r) && (t = malloc(sizeof(Text)))){
+		memset((void*)t, 0, sizeof(Text));
+		current((Flayer *)0);
+		flnew(&t->l[0], gettext, 0, (char *)t);
+		flinit(&t->l[0], r, font, maincols);	/*bnl*/
+		t->nwin = 1;
+		rinit(&t->rasp);
+		if(new)
+			startnewfile(Tstartnewfile, t);
+		else{
+			rinit(&t->rasp);
+			t->tag = tag;
+			startfile(t);
+		}
+		return t;
+	}
+	return 0;
+}
+
+int
+whichmenu(int tg)
+{
+	int i;
+
+	for(i=0; i<nname; i++)
+		if(tag[i] == tg)
+			return i;
+	return -1;
+}
+
+void
+menuins(int n, uchar *s, Text *t, int m, int tg)
+{
+	int i;
+
+	if(nname == mname){
+		if(mname == 0)
+			mname = 32;
+		else
+			mname *= 2;
+		name = realloc(name, sizeof(name[0])*mname);
+		text = realloc(text, sizeof(text[0])*mname);
+		tag = realloc(tag, sizeof(tag[0])*mname);
+		if(name==nil || text==nil || tag==nil)
+			panic("realloc");
+	}
+	for(i=nname; i>n; --i)
+		name[i]=name[i-1], text[i]=text[i-1], tag[i]=tag[i-1];
+	text[n] = t;
+	tag[n] = tg;
+	name[n] = alloc(strlen((char*)s)+2);
+	name[n][0] = m;
+	strcpy((char*)name[n]+1, (char*)s);
+	nname++;
+	menu3.lasthit = n+NMENU3;
+}
+
+void
+menudel(int n)
+{
+	int i;
+
+	if(nname==0 || n>=nname || text[n])
+		panic("menudel");
+	free(name[n]);
+	--nname;
+	for(i = n; i<nname; i++)
+		name[i]=name[i+1], text[i]=text[i+1], tag[i]=tag[i+1];
+}
+
+void
+setpat(char *s)
+{
+	static char pat[17];
+
+	pat[0] = '/';
+	strncpy(pat+1, s, 15);
+	menu2str[Search] = pat;
+}
+
+#define	NBUF	64
+static uchar buf[NBUF*UTFmax]={' ', ' ', ' ', ' '};
+
+char *
+paren(char *s)
+{
+	uchar *t = buf;
+
+	*t++ = '(';
+	do; while(*t++ = *s++);
+	t[-1] = ')';
+	*t = 0;
+	return (char *)buf;
+}
+char*
+genmenu2(int n)
+{
+	Text *t=(Text *)which->user1;
+	char *p;
+	if(n>=NMENU2+(menu2str[Search]!=0))
+		return 0;
+	p = menu2str[n];
+	if(!hostlock && !t->lock || n==Search || n==Look)
+		return p;
+	return paren(p);
+}
+char*
+genmenu2c(int n)
+{
+	Text *t=(Text *)which->user1;
+	char *p;
+	if(n >= NMENU2C)
+		return 0;
+	if(n == Send)
+		p="send";
+	else
+		p = menu2str[n];
+	if(!hostlock && !t->lock)
+		return p;
+	return paren(p);
+}
+char *
+genmenu3(int n)
+{
+	Text *t;
+	int c, i, k, l, w;
+	Rune r;
+	char *p;
+
+	if(n >= NMENU3+nname)
+		return 0;
+	if(n < NMENU3){
+		p = menu3str[n];
+		if(hostlock)
+			p = paren(p);
+		return p;
+	}
+	n -= NMENU3;
+	if(n == 0)	/* unless we've been fooled, this is cmd */
+		return (char *)&name[n][1];
+	if(mw == -1){
+		mw = 7;	/* strlen("~~sam~~"); */
+		for(i=1; i<nname; i++){
+			w = utflen((char*)name[i]+1)+4;	/* include "'+. " */
+			if(w > mw)
+				mw = w;
+		}
+	}
+	if(mw > NBUF)
+		mw = NBUF;
+	t = text[n];
+	buf[0] = name[n][0];
+	buf[1] = '-';
+	buf[2] = ' ';
+	buf[3] = ' ';
+	if(t){
+		if(t->nwin == 1)
+			buf[1] = '+';
+		else if(t->nwin > 1)
+			buf[1] = '*';
+		if(work && t==(Text *)work->user1) {
+			buf[2]= '.';
+			if(modified)
+				buf[0] = '\'';
+		}
+	}
+	l = utflen((char*)name[n]+1);
+	if(l > NBUF-4-2){
+		i = 4;
+		k = 1;
+		while(i < NBUF/2){
+			k += chartorune(&r, (char*)name[n]+k);
+			i++;
+		}
+		c = name[n][k];
+		name[n][k] = 0;
+		strcpy((char*)buf+4, (char*)name[n]+1);
+		name[n][k] = c;
+		strcat((char*)buf, "...");
+		while((l-i) >= NBUF/2-4){
+			k += chartorune(&r, (char*)name[n]+k);
+			i++;
+		}
+		strcat((char*)buf, (char*)name[n]+k);
+	}else
+		strcpy((char*)buf+4, (char*)name[n]+1);
+	i = utflen((char*)buf);
+	k = strlen((char*)buf);
+	while(i<mw && k<sizeof buf-1){
+		buf[k++] = ' ';
+		i++;
+	}
+	buf[k] = 0;
+	return (char *)buf;
+}
diff --git a/src/cmd/samterm/mesg.c b/src/cmd/samterm/mesg.c
new file mode 100644
index 0000000..0971ee2
--- /dev/null
+++ b/src/cmd/samterm/mesg.c
@@ -0,0 +1,804 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+#include <thread.h>
+#include <mouse.h>
+#include <cursor.h>
+#include <keyboard.h>
+#include <frame.h>
+#include <plumb.h>
+#include "flayer.h"
+#include "samterm.h"
+
+#define	HSIZE	3	/* Type + short count */
+Header	h;
+uchar	indata[DATASIZE+1];	/* room for NUL */
+uchar	outdata[DATASIZE];
+short	outcount;
+int	hversion;
+
+void	inmesg(Hmesg, int);
+int	inshort(int);
+long	inlong(int);
+long	invlong(int);
+void	hsetdot(int, long, long);
+void	hmoveto(int, long);
+void	hsetsnarf(int);
+/* void	hplumb(int); */
+void	clrlock(void);
+int	snarfswap(char*, int, char**);
+
+void
+rcv(void)
+{
+	int c;
+	static int state = 0;
+	static int count = 0;
+	static int i = 0;
+	static int errs = 0;
+
+	while((c=rcvchar()) != -1)
+		switch(state){
+		case 0:
+			h.type = c;
+			state++;
+			break;
+
+		case 1:
+			h.count0 = c;
+			state++;
+			break;
+
+		case 2:
+			h.count1 = c;
+			count = h.count0|(h.count1<<8);
+			i = 0;
+			if(count > DATASIZE){
+				if(++errs < 5){
+					dumperrmsg(count, h.type, h.count0, c);
+					state = 0;
+					continue;
+				}
+				fprint(2, "type %d count %d\n", h.type, count);
+				panic("count>DATASIZE");
+			}
+			if(count == 0)
+				goto zerocount;
+			state++;
+			break;
+
+		case 3:
+			indata[i++] = c;
+			if(i == count){
+		zerocount:
+				indata[i] = 0;
+				inmesg(h.type, count);
+				state = count = 0;
+				continue;
+			}
+			break;
+		}
+}
+
+Text *
+whichtext(int tg)
+{
+	int i;
+
+	for(i=0; i<nname; i++)
+		if(tag[i] == tg)
+			return text[i];
+	panic("whichtext");
+	return 0;
+}
+
+void
+inmesg(Hmesg type, int count)
+{
+	Text *t;
+	int i, m;
+	long l;
+	Flayer *lp;
+
+	m = inshort(0);
+	l = inlong(2);
+	switch(type){
+	case -1:
+		panic("rcv error");
+	default:
+		fprint(2, "type %d\n", type);
+		panic("rcv unknown");
+
+	case Hversion:
+		hversion = m;
+		break;
+
+	case Hbindname:
+		l = invlong(2);		/* for 64-bit pointers */
+		if((i=whichmenu(m)) < 0)
+			break;
+		/* in case of a race, a bindname may already have occurred */
+		if((t=whichtext(m)) == 0)
+			t=(Text *)l;
+		else	/* let the old one win; clean up the new one */
+			while(((Text *)l)->nwin>0)
+				closeup(&((Text *)l)->l[((Text *)l)->front]);
+		text[i] = t;
+		text[i]->tag = m;
+		break;
+
+	case Hcurrent:
+		if(whichmenu(m)<0)
+			break;
+		t = whichtext(m);
+		i = which && ((Text *)which->user1)==&cmd && m!=cmd.tag;
+		if(t==0 && (t = sweeptext(0, m))==0)
+			break;
+		if(t->l[t->front].textfn==0)
+			panic("Hcurrent");
+		lp = &t->l[t->front];
+		if(i){
+			flupfront(lp);
+			flborder(lp, 0);
+			work = lp;
+		}else
+			current(lp);
+		break;
+
+	case Hmovname:
+		if((m=whichmenu(m)) < 0)
+			break;
+		t = text[m];
+		l = tag[m];
+		i = name[m][0];
+		text[m] = 0;	/* suppress panic in menudel */
+		menudel(m);
+		if(t == &cmd)
+			m = 0;
+		else{
+			if (nname>0 && text[0]==&cmd)
+				m = 1;
+			else m = 0;
+			for(; m<nname; m++)
+				if(strcmp((char*)indata+2, (char*)name[m]+1)<0)
+					break;
+		}
+		menuins(m, indata+2, t, i, (int)l);
+		break;
+
+	case Hgrow:
+		if(whichmenu(m) >= 0)
+			hgrow(m, l, inlong(6), 1);
+		break;
+
+	case Hnewname:
+		menuins(0, (uchar *)"", (Text *)0, ' ', m);
+		break;
+
+	case Hcheck0:
+		i = whichmenu(m);
+		if(i>=0) {
+			t = text[i];
+			if(t)
+				t->lock++;
+			outTs(Tcheck, m);
+		}
+		break;
+
+	case Hcheck:
+		i = whichmenu(m);
+		if(i>=0) {
+			t = text[i];
+			if(t && t->lock)
+				t->lock--;
+			hcheck(m);
+		}
+		break;
+
+	case Hunlock:
+		clrlock();
+		break;
+
+	case Hdata:
+		if(whichmenu(m) >= 0)
+			l += hdata(m, l, indata+6, count-6);
+	Checkscroll:
+		if(m == cmd.tag){
+			for(i=0; i<NL; i++){
+				lp = &cmd.l[i];
+				if(lp->textfn)
+					center(lp, l>=0? l : lp->p1);
+			}
+		}
+		break;
+
+	case Horigin:
+		if(whichmenu(m) >= 0)
+			horigin(m, l);
+		break;
+
+	case Hunlockfile:
+		if(whichmenu(m)>=0 && (t = whichtext(m))->lock){
+			--t->lock;
+			l = -1;
+			goto Checkscroll;
+		}
+		break;
+
+	case Hsetdot:
+		if(whichmenu(m) >= 0)
+			hsetdot(m, l, inlong(6));
+		break;
+
+	case Hgrowdata:
+		if(whichmenu(m)<0)
+			break;
+		hgrow(m, l, inlong(6), 0);
+		whichtext(m)->lock++;	/* fake the request */
+		l += hdata(m, l, indata+10, count-10);
+		goto Checkscroll;
+
+	case Hmoveto:
+		if(whichmenu(m)>=0)
+			hmoveto(m, l);
+		break;
+
+	case Hclean:
+		if((m = whichmenu(m)) >= 0)
+			name[m][0] = ' ';
+		break;
+
+	case Hdirty:
+		if((m = whichmenu(m))>=0)
+			name[m][0] = '\'';
+		break;
+
+	case Hdelname:
+		if((m=whichmenu(m)) >= 0)
+			menudel(m);
+		break;
+
+	case Hcut:
+		if(whichmenu(m) >= 0)
+			hcut(m, l, inlong(6));
+		break;
+
+	case Hclose:
+		if(whichmenu(m)<0 || (t = whichtext(m))==0)
+			break;
+		l = t->nwin;
+		for(i = 0,lp = t->l; l>0 && i<NL; i++,lp++)
+			if(lp->textfn){
+				closeup(lp);
+				--l;
+			}
+		break;
+
+	case Hsetpat:
+		setpat((char *)indata);
+		break;
+
+	case Hsetsnarf:
+		hsetsnarf(m);
+		break;
+
+	case Hsnarflen:
+		snarflen = inlong(0);
+		break;
+
+	case Hack:
+		outT0(Tack);
+		break;
+
+	case Hexit:
+		outT0(Texit);
+		threadexitsall(nil);
+		break;
+
+/*
+	case Hplumb:
+		hplumb(m);
+		break;
+*/
+	}
+}
+
+void
+setlock(void)
+{
+	hostlock++;
+	setcursor(mousectl, cursor = &lockarrow);
+}
+
+void
+clrlock(void)
+{
+	hasunlocked = 1;
+	if(hostlock > 0)
+		hostlock--;
+	if(hostlock == 0)
+		setcursor(mousectl, cursor=(Cursor *)0);
+}
+
+void
+startfile(Text *t)
+{
+	outTsv(Tstartfile, t->tag, t);		/* for 64-bit pointers */
+	setlock();
+}
+
+void
+startnewfile(int type, Text *t)
+{
+	t->tag = Untagged;
+	outTv(type, t);				/* for 64-bit pointers */
+}
+
+int
+inshort(int n)
+{
+	return indata[n]|(indata[n+1]<<8);
+}
+
+long
+inlong(int n)
+{
+	return indata[n]|(indata[n+1]<<8)|
+		((long)indata[n+2]<<16)|((long)indata[n+3]<<24);
+}
+
+long
+invlong(int n)
+{
+	long l;
+
+	l = (indata[n+7]<<24) | (indata[n+6]<<16) | (indata[n+5]<<8) | indata[n+4];
+	l = (l<<16) | (indata[n+3]<<8) | indata[n+2];
+	l = (l<<16) | (indata[n+1]<<8) | indata[n];
+	return l;
+}
+
+void
+outT0(Tmesg type)
+{
+	outstart(type);
+	outsend();
+}
+
+void
+outTl(Tmesg type, long l)
+{
+	outstart(type);
+	outlong(l);
+	outsend();
+}
+
+void
+outTs(Tmesg type, int s)
+{
+	outstart(type);
+	outshort(s);
+	outsend();
+}
+
+void
+outTss(Tmesg type, int s1, int s2)
+{
+	outstart(type);
+	outshort(s1);
+	outshort(s2);
+	outsend();
+}
+
+void
+outTsll(Tmesg type, int s1, long l1, long l2)
+{
+	outstart(type);
+	outshort(s1);
+	outlong(l1);
+	outlong(l2);
+	outsend();
+}
+
+void
+outTsl(Tmesg type, int s1, long l1)
+{
+	outstart(type);
+	outshort(s1);
+	outlong(l1);
+	outsend();
+}
+
+void
+outTsv(Tmesg type, int s1, void *l1)
+{
+	outstart(type);
+	outshort(s1);
+	outvlong(l1);
+	outsend();
+}
+
+void
+outTv(Tmesg type, void *l1)
+{
+	outstart(type);
+	outvlong(l1);
+	outsend();
+}
+
+void
+outTslS(Tmesg type, int s1, long l1, Rune *s)
+{
+	char buf[DATASIZE*3+1];
+	char *c;
+
+	outstart(type);
+	outshort(s1);
+	outlong(l1);
+	c = buf;
+	while(*s)
+		c += runetochar(c, s++);
+	*c++ = 0;
+	outcopy(c-buf, (uchar *)buf);
+	outsend();
+}
+
+void
+outTsls(Tmesg type, int s1, long l1, int s2)
+{
+	outstart(type);
+	outshort(s1);
+	outlong(l1);
+	outshort(s2);
+	outsend();
+}
+
+void
+outstart(Tmesg type)
+{
+	outdata[0] = type;
+	outcount = 0;
+}
+
+void
+outcopy(int count, uchar *data)
+{
+	while(count--)
+		outdata[HSIZE+outcount++] = *data++;	
+}
+
+void
+outshort(int s)
+{
+	uchar buf[2];
+
+	buf[0]=s;
+	buf[1]=s>>8;
+	outcopy(2, buf);
+}
+
+void
+outlong(long l)
+{
+	uchar buf[4];
+
+	buf[0]=l;
+	buf[1]=l>>8;
+	buf[2]=l>>16;
+	buf[3]=l>>24;
+	outcopy(4, buf);
+}
+
+void
+outvlong(void *v)
+{
+	int i;
+	ulong l;
+	uchar buf[8];
+
+	l = (ulong) v;
+	for(i = 0; i < sizeof(buf); i++, l >>= 8)
+		buf[i] = l;
+
+	outcopy(8, buf);
+}
+
+void
+outsend(void)
+{
+	if(outcount>DATASIZE-HSIZE)
+		panic("outcount>sizeof outdata");
+	outdata[1]=outcount;
+	outdata[2]=outcount>>8;
+	if(write(1, (char *)outdata, outcount+HSIZE)!=outcount+HSIZE)
+		panic("write error");
+}
+
+
+void
+hsetdot(int m, long p0, long p1)
+{
+	Text *t = whichtext(m);
+	Flayer *l = &t->l[t->front];
+
+	flushtyping(1);
+	flsetselect(l, p0, p1);
+}
+
+void
+horigin(int m, long p0)
+{
+	Text *t = whichtext(m);
+	Flayer *l = &t->l[t->front];
+	long a;
+	ulong n;
+	Rune *r;
+
+	if(!flprepare(l)){
+		l->origin = p0;
+		return;
+	}
+	a = p0-l->origin;
+	if(a>=0 && a<l->f.nchars)
+		frdelete(&l->f, 0, a);
+	else if(a<0 && -a<l->f.nchars){
+		r = rload(&t->rasp, p0, l->origin, &n);
+		frinsert(&l->f, r, r+n, 0);
+	}else
+		frdelete(&l->f, 0, l->f.nchars);
+	l->origin = p0;
+	scrdraw(l, t->rasp.nrunes);
+	if(l->visible==Some)
+		flrefresh(l, l->entire, 0);
+	hcheck(m);
+}
+
+void
+hmoveto(int m, long p0)
+{
+	Text *t = whichtext(m);
+	Flayer *l = &t->l[t->front];
+
+	if(p0<l->origin || p0-l->origin>l->f.nchars*9/10)
+		outTsll(Torigin, m, p0, 2L);
+}
+
+void
+hcheck(int m)
+{
+	Flayer *l;
+	Text *t;
+	int reqd = 0, i;
+	long n, nl, a;
+	Rune *r;
+
+	if(m == Untagged)
+		return;
+	t = whichtext(m);
+	if(t == 0)		/* possible in a half-built window */
+		return;
+	for(l = &t->l[0], i = 0; i<NL; i++, l++){
+		if(l->textfn==0 || !flprepare(l))	/* BUG: don't
+							   need this if BUG below
+							   is fixed */
+			continue;
+		a = t->l[i].origin;
+		n = rcontig(&t->rasp, a, a+l->f.nchars, 1);
+		if(n<l->f.nchars)	/* text missing in middle of screen */
+			a+=n;
+		else{			/* text missing at end of screen? */
+        Again:
+		 	if(l->f.lastlinefull)
+				goto Checksel;	/* all's well */
+			a = t->l[i].origin+l->f.nchars;
+			n = t->rasp.nrunes-a;
+			if(n==0)
+				goto Checksel;
+			if(n>TBLOCKSIZE)
+				n = TBLOCKSIZE;
+			n = rcontig(&t->rasp, a, a+n, 1);
+			if(n>0){
+				rload(&t->rasp, a, a+n, 0);
+				nl = l->f.nchars;
+				r = scratch;
+				flinsert(l, r, r+n, l->origin+nl);
+				if(nl == l->f.nchars)	/* made no progress */
+					goto Checksel;
+				goto Again;
+			}
+		}
+		if(!reqd){
+			n = rcontig(&t->rasp, a, a+TBLOCKSIZE, 0);
+			if(n <= 0)
+				panic("hcheck request==0");
+			outTsls(Trequest, m, a, (int)n);
+			outTs(Tcheck, m);
+			t->lock++;	/* for the Trequest */
+			t->lock++;	/* for the Tcheck */
+			reqd++;
+		}
+	    Checksel:
+		flsetselect(l, l->p0, l->p1);
+	}
+}
+
+void
+flnewlyvisible(Flayer *l)
+{
+	hcheck(((Text *)l->user1)->tag);
+}
+
+void
+hsetsnarf(int nc)
+{
+	char *s2;
+	char *s1;
+	int i;
+	int n;
+
+	setcursor(mousectl, &deadmouse);
+	s2 = alloc(nc+1);
+	for(i=0; i<nc; i++)
+		s2[i] = getch();
+	s2[nc] = 0;
+	n = snarfswap(s2, nc, &s1);
+	if(n >= 0){
+		if(!s1)
+			n = 0;
+		s1 = realloc(s1, n+1);
+		if (!s1)
+			panic("realloc");
+		s1[n] = 0;
+		snarflen = n;
+		outTs(Tsetsnarf, n);
+		if(n>0 && write(1, s1, n)!=n)
+			panic("snarf write error");
+		free(s1);
+	}else
+		outTs(Tsetsnarf, 0);
+	free(s2);
+	setcursor(mousectl, cursor);
+}
+
+/*
+void
+hplumb(int nc)
+{
+	int i;
+	char *s;
+	Plumbmsg *m;
+
+	s = alloc(nc);
+	for(i=0; i<nc; i++)
+		s[i] = getch();
+	if(plumbfd > 0){
+		m = plumbunpack(s, nc);
+		if(m != 0)
+			plumbsend(plumbfd, m);
+		plumbfree(m);
+	}
+	free(s);
+}
+*/
+
+void
+hgrow(int m, long a, long new, int req)
+{
+	int i;
+	Flayer *l;
+	Text *t = whichtext(m);
+	long o, b;
+
+	if(new <= 0)
+		panic("hgrow");
+	rresize(&t->rasp, a, 0L, new);
+	for(l = &t->l[0], i = 0; i<NL; i++, l++){
+		if(l->textfn == 0)
+			continue;
+		o = l->origin;
+		b = a-o-rmissing(&t->rasp, o, a);
+		if(a < o)
+			l->origin+=new;
+		if(a < l->p0)
+			l->p0+=new;
+		if(a < l->p1)
+			l->p1+=new;
+		/* must prevent b temporarily becoming unsigned */
+		if(!req || a<o || (b>0 && b>l->f.nchars) ||
+		    (l->f.nchars==0 && a-o>0))
+			continue;
+		if(new>TBLOCKSIZE)
+			new = TBLOCKSIZE;
+		outTsls(Trequest, m, a, (int)new);
+		t->lock++;
+		req = 0;
+	}
+}
+
+int
+hdata1(Text *t, long a, Rune *r, int len)
+{
+	int i;
+	Flayer *l;
+	long o, b;
+
+	for(l = &t->l[0], i=0; i<NL; i++, l++){
+		if(l->textfn==0)
+			continue;
+		o = l->origin;
+		b = a-o-rmissing(&t->rasp, o, a);
+		/* must prevent b temporarily becoming unsigned */
+		if(a<o || (b>0 && b>l->f.nchars))
+			continue;
+		flinsert(l, r, r+len, o+b);
+	}
+	rdata(&t->rasp, a, a+len, r);
+	rclean(&t->rasp);
+	return len;
+}
+
+int
+hdata(int m, long a, uchar *s, int len)
+{
+	int i, w;
+	Text *t = whichtext(m);
+	Rune buf[DATASIZE], *r;
+
+	if(t->lock)
+		--t->lock;
+	if(len == 0)
+		return 0;
+	r = buf;
+	for(i=0; i<len; i+=w,s+=w)
+		w = chartorune(r++, (char*)s);
+	return hdata1(t, a, buf, r-buf);
+}
+
+int
+hdatarune(int m, long a, Rune *r, int len)
+{
+	Text *t = whichtext(m);
+
+	if(t->lock)
+		--t->lock;
+	if(len == 0)
+		return 0;
+	return hdata1(t, a, r, len);
+}
+
+void
+hcut(int m, long a, long old)
+{
+	Flayer *l;
+	Text *t = whichtext(m);
+	int i;
+	long o, b;
+
+	if(t->lock)
+		--t->lock;
+	for(l = &t->l[0], i = 0; i<NL; i++, l++){
+		if(l->textfn == 0)
+			continue;
+		o = l->origin;
+		b = a-o-rmissing(&t->rasp, o, a);
+		/* must prevent b temporarily becoming unsigned */
+		if((b<0 || b<l->f.nchars) && a+old>=o){
+			fldelete(l, b<0? o : o+b,
+			    a+old-rmissing(&t->rasp, o, a+old));
+		}
+		if(a+old<o)
+			l->origin-=old;
+		else if(a<=o)
+			l->origin = a;
+		if(a+old<l->p0)
+			l->p0-=old;
+		else if(a<=l->p0)
+			l->p0 = a;
+		if(a+old<l->p1)
+			l->p1-=old;
+		else if(a<=l->p1)
+			l->p1 = a;
+	}
+	rresize(&t->rasp, a, old, 0L);
+	rclean(&t->rasp);
+}
diff --git a/src/cmd/samterm/mkfile b/src/cmd/samterm/mkfile
new file mode 100644
index 0000000..1015135
--- /dev/null
+++ b/src/cmd/samterm/mkfile
@@ -0,0 +1,9 @@
+all:V: Makefile Make.FreeBSD-386 Make.Linux-386 Make.HP-UX-9000 Make.OSF1-alpha \
+	Make.SunOS-sun4u Make.SunOS-sun4u-cc Make.SunOS-sun4u-gcc \
+	Make.NetBSD-386 Make.Darwin-PowerMacintosh
+
+Makefile:D: ../libutf/Makefile.TOP Makefile.MID ../libutf/Makefile.CMD ../libutf/Makefile.BOT
+	cat $prereq >$target
+
+Make.%: ../libutf/Make.%
+	cp $prereq $target
diff --git a/src/cmd/samterm/plan9.c b/src/cmd/samterm/plan9.c
new file mode 100644
index 0000000..d08349c
--- /dev/null
+++ b/src/cmd/samterm/plan9.c
@@ -0,0 +1,296 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+#include <thread.h>
+#include <mouse.h>
+#include <cursor.h>
+#include <keyboard.h>
+#include <frame.h>
+#include "flayer.h"
+#include "samterm.h"
+
+static char exname[64];
+
+void
+getscreen(int argc, char **argv)
+{
+	char *t;
+
+	USED(argc);
+	USED(argv);
+	if(initdraw(panic1, nil, "sam") < 0){
+		fprint(2, "samterm: initimage: %r\n");
+		threadexitsall("init");
+	}
+	t = getenv("tabstop");
+	if(t != nil)
+		maxtab = strtoul(t, nil, 0);
+	draw(screen, screen->clipr, display->white, nil, ZP);
+}
+
+int
+screensize(int *w, int *h)
+{
+	int fd, n;
+	char buf[5*12+1];
+
+	fd = open("/dev/screen", OREAD);
+	if(fd < 0)
+		return 0;
+	n = read(fd, buf, sizeof(buf)-1);
+	close(fd);
+	if (n != sizeof(buf)-1)
+		return 0;
+	buf[n] = 0;
+	if (h) {
+		*h = atoi(buf+4*12)-atoi(buf+2*12);
+		if (*h < 0)
+			return 0;
+	}
+	if (w) {
+		*w = atoi(buf+3*12)-atoi(buf+1*12);
+		if (*w < 0)
+			return 0;
+	}
+	return 1;
+}
+
+int
+snarfswap(char *fromsam, int nc, char **tosam)
+{
+	char *s1;
+	int f, n, ss;
+
+	f = open("/dev/snarf", 0);
+	if(f < 0)
+		return -1;
+	ss = SNARFSIZE;
+	if(hversion < 2)
+		ss = 4096;
+	*tosam = s1 = alloc(ss);
+	n = read(f, s1, ss-1);
+	close(f);
+	if(n < 0)
+		n = 0;
+	if (n == 0) {
+		*tosam = 0;
+		free(s1);
+	} else
+		s1[n] = 0;
+/*
+	f = create("/dev/snarf", 1, 0666);
+	if(f >= 0){
+		write(f, fromsam, nc);
+		close(f);
+	}
+*/
+	return n;
+}
+
+void
+dumperrmsg(int count, int type, int count0, int c)
+{
+	fprint(2, "samterm: host mesg: count %d %ux %ux %ux %s...ignored\n",
+		count, type, count0, c, rcvstring());
+}
+
+void
+removeextern(void)
+{
+	remove(exname);
+}
+
+Readbuf	hostbuf[2];
+/*
+Readbuf	plumbbuf[2];
+
+void
+extproc(void *argv)
+{
+	Channel *c;
+	int i, n, which, *fdp;
+	void **arg;
+
+	arg = argv;
+	c = arg[0];
+	fdp = arg[1];
+
+	i = 0;
+	for(;;){
+		i = 1-i;	/ * toggle * /
+		n = read(*fdp, plumbbuf[i].data, sizeof plumbbuf[i].data);
+		if(n <= 0){
+			fprint(2, "samterm: extern read error: %r\n");
+			threadexits("extern");	/ * not a fatal error * /
+		}
+		plumbbuf[i].n = n;
+		which = i;
+		send(c, &which);
+	}
+}
+*/
+
+void
+extstart(void)
+{
+	char buf[32];
+	int fd;
+	static int p[2];
+	static void *arg[2];
+
+return;
+	if(pipe(p) < 0)
+		return;
+	sprint(exname, "/srv/sam.%s", "rsc"/*getuser()*/);
+	fd = open(exname, 1, 0600);/* BUG was create */
+	if(fd < 0){	/* assume existing guy is more important */
+    Err:
+		close(p[0]);
+		close(p[1]);
+		return;
+	}
+	sprint(buf, "%d", p[0]);
+	if(write(fd, buf, strlen(buf)) <= 0)
+		goto Err;
+	close(fd);
+	/*
+	 * leave p[0] open so if the file is removed the event
+	 * library won't get an error
+	 */
+#if 0
+	plumbc = chancreate(sizeof(int), 0);
+	arg[0] = plumbc;
+	arg[1] = &p[1];
+	proccreate(extproc, arg, 1024);
+#endif
+	atexit(removeextern);
+}
+
+#if 0
+int
+plumbformat(int i)
+{
+	Plumbmsg *m;
+	char *addr, *data, *act;
+	int n;
+
+	data = (char*)plumbbuf[i].data;
+	m = plumbunpack(data, plumbbuf[i].n);
+	if(m == nil)
+		return 0;
+	n = m->ndata;
+	if(n == 0){
+		plumbfree(m);
+		return 0;
+	}
+	act = plumblookup(m->attr, "action");
+	if(act!=nil && strcmp(act, "showfile")!=0){
+		/* can't handle other cases yet */
+		plumbfree(m);
+		return 0;
+	}
+	addr = plumblookup(m->attr, "addr");
+	if(addr){
+		if(addr[0] == '\0')
+			addr = nil;
+		else
+			addr = strdup(addr);	/* copy to safe storage; we'll overwrite data */
+	}
+	memmove(data, "B ", 2);	/* we know there's enough room for this */
+	memmove(data+2, m->data, n);
+	n += 2;
+	if(data[n-1] != '\n')
+		data[n++] = '\n';
+	if(addr != nil){
+		if(n+strlen(addr)+1+1 <= READBUFSIZE)
+			n += sprint(data+n, "%s\n", addr);
+		free(addr);
+	}
+	plumbbuf[i].n = n;
+	plumbfree(m);
+	return 1;
+}
+
+void
+plumbproc(void *argv)
+{
+	Channel *c;
+	int i, n, which, *fdp;
+	void **arg;
+
+	arg = argv;
+	c = arg[0];
+	fdp = arg[1];
+
+	i = 0;
+	for(;;){
+		i = 1-i;	/* toggle */
+		n = read(*fdp, plumbbuf[i].data, READBUFSIZE);
+		if(n <= 0){
+			fprint(2, "samterm: plumb read error: %r\n");
+			threadexits("plumb");	/* not a fatal error */
+		}
+		plumbbuf[i].n = n;
+		if(plumbformat(i)){
+			which = i;
+			send(c, &which);
+		}
+	}
+}
+
+int
+plumbstart(void)
+{
+	static int fd;
+	static void *arg[2];
+
+	plumbfd = plumbopen("send", OWRITE|OCEXEC);	/* not open is ok */
+	fd = plumbopen("edit", OREAD|OCEXEC);
+	if(fd < 0)
+		return -1;
+	plumbc = chancreate(sizeof(int), 0);
+	if(plumbc == nil){
+		close(fd);
+		return -1;
+	}
+	arg[0] =plumbc;
+	arg[1] = &fd;
+	proccreate(plumbproc, arg, 4096);
+	return 1;
+}
+#endif
+
+int
+plumbstart(void)
+{
+	return -1;
+}
+
+void
+hostproc(void *arg)
+{
+	Channel *c;
+	int i, n, which;
+
+	c = arg;
+
+	i = 0;
+	for(;;){
+		i = 1-i;	/* toggle */
+		n = read(0, hostbuf[i].data, sizeof hostbuf[i].data);
+		if(n <= 0){
+			fprint(2, "samterm: host read error: %r\n");
+			threadexitsall("host");
+		}
+		hostbuf[i].n = n;
+		which = i;
+		send(c, &which);
+	}
+}
+
+void
+hoststart(void)
+{
+	hostc = chancreate(sizeof(int), 0);
+	proccreate(hostproc, hostc, 1024);
+}
diff --git a/src/cmd/samterm/samterm b/src/cmd/samterm/samterm
new file mode 100755
index 0000000..c5333ec
--- /dev/null
+++ b/src/cmd/samterm/samterm
Binary files differ
diff --git a/src/cmd/samterm/samterm.h b/src/cmd/samterm/samterm.h
new file mode 100644
index 0000000..3018c92
--- /dev/null
+++ b/src/cmd/samterm/samterm.h
@@ -0,0 +1,177 @@
+#define	SAMTERM
+
+#define	RUNESIZE	sizeof(Rune)
+#define	MAXFILES	256
+#define	READBUFSIZE 8192
+#define	NL	5
+
+enum{
+	Up,
+	Down
+};
+
+typedef struct Text	Text;
+typedef struct Section	Section;
+typedef struct Rasp	Rasp;
+typedef struct Readbuf Readbuf;
+
+struct Section
+{
+	long	nrunes;
+	Rune	*text;		/* if null, we haven't got it */
+	Section	*next;
+};
+
+struct Rasp
+{
+	long	nrunes;
+	Section	*sect;
+};
+
+#define	Untagged	((ushort)65535)
+
+struct Text
+{
+	Rasp	rasp;
+	short	nwin;
+	short	front;		/* input window */
+	ushort	tag;
+	char	lock;
+	Flayer	l[NL];		/* screen storage */
+};
+
+struct Readbuf
+{
+	short	n;					/* # bytes in buf */
+	uchar	data[READBUFSIZE];		/* data bytes */
+};
+
+enum Resource
+{
+	RHost,
+	RKeyboard,
+	RMouse,
+	RPlumb,
+	RResize,
+	NRes,
+};
+
+extern Text	**text;
+extern uchar	**name;
+extern ushort	*tag;
+extern int	nname;
+extern int	mname;
+extern Cursor	bullseye;
+extern Cursor	deadmouse;
+extern Cursor	lockarrow;
+extern Cursor	*cursor;
+extern Flayer	*which;
+extern Flayer	*work;
+extern Text	cmd;
+extern Rune	*scratch;
+extern long	nscralloc;
+extern char	hostlock;
+extern char	hasunlocked;
+extern long	snarflen;
+extern Mousectl* mousectl;
+extern Keyboardctl* keyboardctl;
+extern Mouse*	mousep;
+extern long	modified;
+extern int	maxtab;
+extern Readbuf	hostbuf[2];	/* double buffer; it's synchronous communication */
+extern Readbuf	plumbbuf[2];	/* double buffer; it's synchronous communication */
+extern Channel *plumbc;
+extern Channel *hostc;
+extern int	hversion;
+extern int	plumbfd;
+
+Rune	*gettext(Flayer*, long, ulong*);
+void	*alloc(ulong n);
+
+void	iconinit(void);
+void	getscreen(int, char**);
+void	initio(void);
+void	setlock(void);
+void	outcmd(void);
+void	rinit(Rasp*);
+void	startnewfile(int, Text*);
+void	getmouse(void);
+void	mouseunblock(void);
+void	kbdblock(void);
+void	extstart(void);
+void	hoststart(void);
+int	plumbstart(void);
+int	button(int but);
+int	load(char*, int);
+int	waitforio(void);
+int	rcvchar(void);
+int	getch(void);
+int	kbdchar(void);
+int	qpeekc(void);
+void	cut(Text*, int, int, int);
+void	paste(Text*, int);
+void	snarf(Text*, int);
+int	center(Flayer*, long);
+int	xmenuhit(int, Menu*);
+void	buttons(int);
+int	getr(Rectangle*);
+void	current(Flayer*);
+void	duplicate(Flayer*, Rectangle, Font*, int);
+void	startfile(Text*);
+void	panic(char*);
+void	panic1(Display*, char*);
+void	closeup(Flayer*);
+void	Strgrow(Rune**, long*, int);
+int	RESIZED(void);
+void	resize(void);
+void	rcv(void);
+void	type(Flayer*, int);
+void	menu2hit(void);
+void	menu3hit(void);
+void	scroll(Flayer*, int);
+void	hcheck(int);
+void	rclear(Rasp*);
+int	whichmenu(int);
+void	hcut(int, long, long);
+void	horigin(int, long);
+void	hgrow(int, long, long, int);
+int	hdata(int, long, uchar*, int);
+int	hdatarune(int, long, Rune*, int);
+Rune	*rload(Rasp*, ulong, ulong, ulong*);
+void	menuins(int, uchar*, Text*, int, int);
+void	menudel(int);
+Text	*sweeptext(int, int);
+void	setpat(char*);
+void	scrdraw(Flayer*, long tot);
+int	rcontig(Rasp*, ulong, ulong, int);
+int	rmissing(Rasp*, ulong, ulong);
+void	rresize(Rasp *, long, long, long);
+void	rdata(Rasp*, long, long, Rune*);
+void	rclean(Rasp*);
+void	scrorigin(Flayer*, int, long);
+long	scrtotal(Flayer*);
+void	flnewlyvisible(Flayer*);
+char	*rcvstring(void);
+void	Strcpy(Rune*, Rune*);
+void	Strncpy(Rune*, Rune*, long);
+void	flushtyping(int);
+void	dumperrmsg(int, int, int, int);
+int	screensize(int*,int*);
+void	getmouse(void);
+
+#include "mesg.h"
+
+void	outTs(Tmesg, int);
+void	outT0(Tmesg);
+void	outTl(Tmesg, long);
+void	outTslS(Tmesg, int, long, Rune*);
+void	outTsll(Tmesg, int, long, long);
+void	outTsl(Tmesg, int, long);
+void	outTsv(Tmesg, int, void*);
+void	outTv(Tmesg, void*);
+void	outstart(Tmesg);
+void	outcopy(int, uchar*);
+void	outshort(int);
+void	outlong(long);
+void	outvlong(void*);
+void	outsend(void);