shithub: 9ferno

ref: 670f4e90ee6028ffee399e5a96fb5e4cd72fc7bc

View raw version
implement WmVt;

# note: this code was hacked together in a hurry from some decade-old C code
# of mine, so don't expect it to be pretty...
# Also, don't expect it to be finished... I had to rush to check this
# in... it's just been worked on as a side-project from time to time
# But it's good enough to be useful most of the time
 
include "sys.m";
	sys: Sys;
	sprint: import sys;
include "draw.m";
	draw: Draw;
	Display, Font, Black, Rect, Image, Point, Endsquare, Enddisc: import draw;
include "tk.m";
	tk: Tk;
	Toplevel: import tk;
include "tkclient.m";
	tkclient: Tkclient;
include "sh.m";

CON_Maxnpts:	con 1000;
Maxnhits:	con 5;

 
WmVt: module {
	init:   fn(ctxt: ref Draw->Context, argv: list of string);
};



VT_MAXPARAM: con 8;


Vt: adt {
	y1, y2: int;
	mode: int;	# misc mode parameters 
	qmode: int;	# extended mode parameters
	attr: int; 	# display attributes 
	fg: int;	# foreground color 
	bg: int;	# background color 

	# saved values:
	save_x, save_y: int;
	save_attr: int;
	save_fg, save_bg: int;
	save_mode: int;
	save_qmode: int;

	# escape code parsing:
	esc: int;	# escape mode 
	pcount: int;	# parameter count
	etype: int;	# escape code type
	ptype: int;	# current parameter type
	value: int;	# current value
	param: array of int;

	# display info:
	wid, hgt: int;
	x, y: int;
	dx, dy: int;
	nlcr: int;
	ccc: int;
	scr: array of string;
	cc: array of string;
};


display: ref Display;
t: ref Toplevel;
canvas: ref Image;
canvrect: Rect;
org: Point;
font: ref Font;
stderr: ref Sys->FD;
vt: ref Vt;
pad: string;
vtc := array[16] of ref Image;
raw := 0;
echo := 1;
reverse := 0;
sq := "";

inpchan: chan of string;


shwin_cfg := array[] of {
	"frame .f",
	"pack .c .f -side top -fill x",
	"pack propagate . 0",
	"focus .f",
	"bind .f <Key> {send keys {%A}}",
	"bind . <Configure> {send cmd resize}",
	"update"
};


titlebar()
{
	tk->cmd(t, "destroy .Wm_t.S");
	tk->cmd(t, "button .Wm_t.S -bg #aaaaaa -fg white -text {" +
		sprint("%d x %d", vt.wid, vt.hgt) + "}; " +
		"pack .Wm_t.S -side right");
	c := "green";
	if(raw)
		c = "red";
	tk->cmd(t, "destroy .Wm_t.k");
	tk->cmd(t, "button .Wm_t.k -bitmap keyboard.bit"+
		" -background "+c+" -command {send wm_title raw}; " +
		"pack .Wm_t.k -side right");
	c = "red";
	if(echo)
		c = "green";
	tk->cmd(t, "destroy .Wm_t.d");
	tk->cmd(t, "button .Wm_t.d -bitmap display.bit"+
		" -background "+c+" -command {send wm_title echo}; " +
				"pack .Wm_t.d -side right");
	c = "white";
	if(reverse)
		c = "black";
	tk->cmd(t, "destroy .Wm_t.r");
	tk->cmd(t, "button .Wm_t.r -width 24 -height 24 "+
		" -background "+c+" -command {send wm_title reverse}; " +
				"pack .Wm_t.r -side right");
	tk->cmd(t, "update");
}

init(ctxt: ref Draw->Context, nil: list of string)
{
	sys = load Sys Sys->PATH;
	if (ctxt == nil) {
		sys->fprint(sys->fildes(2), "vt: no window context\n");
		raise "fail:bad context";
	}
	draw = load Draw Draw->PATH;
	tk = load Tk Tk->PATH;
	tkclient = load Tkclient Tkclient->PATH;

	stderr = sys->fildes(2);

	sys->pctl(Sys->FORKNS, nil);
	sys->pctl(Sys->NEWPGRP, nil);

	menubut: chan of string;
	tkclient->init();
	(t, menubut) = tkclient->toplevel(ctxt, "", "WmVt", Tkclient->Appl);

	display = ctxt.display;	
	font = Font.open(display, "*default*");

	vt = ref Vt;
	vt.hgt = 24;
	vt.wid = 80;
	vt.scr = array[vt.hgt] of string;
	vt.cc = array[vt.hgt] of string;
	vt_init(vt);

	pad = "";
	for(i:=0; i<vt.wid; i++) 
		pad[i] = ' ';

	cmd := chan of string;
	tk->namechan(t, cmd, "cmd");
	tk->cmd(t, "canvas .c -height "
		+ string (vt.hgt*font.height) +
		+ " -width " + string (vt.wid*font.width("0")) +
		" -background red");
	tkcmds(t, shwin_cfg);
	tkclient->onscreen(t, nil);
	tkclient->startinput(t, "kbd"::"ptr"::nil);
	titlebar();

	keys := chan of string;
	tk->namechan(t, keys, "keys");
 
	canvas = t.image;
	canvrect = canvposn(t);
	org = canvrect.min;
	
	npts := 0;
	WasUp := 1;

	for(i=0; i<16; i++) {
		r := 0;
		g := 0;
		b := 0;
		v := 192;
		if(i&8)
			v = 255;
		if(i&1)
			r = v;
		if(i&2)
			g = v;
		if(i&4)
			b = v;
		vtc[i] = display.newimage(((0,0),(1,1)), t.image.chans,
				1, display.rgb2cmap(r, g, b));
		if (vtc[i] == nil) {
			sys->fprint(sys->fildes(2), "Failed to allocate image\n");
			exit;
		}
	}

	vt_write(vt, "\u001b[2J");

	ioc := chan of (int, ref Sys->FileIO, ref Sys->FileIO);
	spawn newsh(ctxt, ioc);
	
	(pid, file, filectl) := <- ioc;
	if((file == nil) || (filectl == nil)) {
		sys->print("newsh: %r\n");
		return;
	}

	# XXX - need to kill this later
	ic := chan of string;
	spawn consinp(ic, file.read);

	inpchan = ic;	# hack

	for(;;) alt {
	s := <-t.ctxt.kbd =>
		tk->keyboard(t, s);
	s := <-t.ctxt.ptr =>
		tk->pointer(t, *s);
	s := <-t.ctxt.ctl or
	s = <-t.wreq =>
		tkclient->wmctl(t, s);
	menu := <- menubut =>
		if(menu == "exit") {
			kill(pid);
			return;
		}
		else if(menu == "raw") {
			raw = !raw;
			titlebar();
			redraw();
		}
		else if(menu == "echo") {
			echo = !echo;
			titlebar();
			redraw();
		}
		else if(menu == "reverse") {
			reverse = !reverse;
			tmp := vtc[0];
			vtc[0] = vtc[7];
			vtc[7] = tmp;
			titlebar();
			redraw();
		} else
			tkclient->wmctl(t, menu);
		tk->cmd(t, "focus .f");

	s := <- cmd =>
		(n, cmdstr) := sys->tokenize(s, " \t\n");
		case hd cmdstr {
		"quit" =>
			exit;
		"resize" =>
			# sys->print("resize\n");
			canvas = t.image;
			canvrect = canvposn(t);
			org = canvrect.min;
			# sys->print("%d,%d %d,%d\n", canvrect.max.x, canvrect.min.x,
			#	canvas.r.max.x, canvas.r.min.x);
			resize((canvrect.max.x-canvrect.min.x)/font.width("0"),
				(canvrect.max.y-canvrect.min.y)/font.height);
			titlebar();
			redraw();
		}

	c := <- keys =>
		ic <-= c[1:2];
		if(echo)
			scwrite(c[1:2]);

	(off, data, fid, wc) := <- file.write =>
		if(wc == nil)
			return;
		if(echo && !raw && sq != "") {
			s := "";
			for(i=0; i<len sq; i++)
				s += "\b \b";
			scwrite(s);
		}
		scwrite(string data);
		if(echo && !raw && sq != "")
			scwrite(sq);
		wc <-= (len data, nil);
	(off, data, fid, wc) := <- filectl.write =>
		if(string data == "rawon") {
			raw = 1;
			echo = 0;
			titlebar();
			redraw();
		}
		if(string data == "rawoff") {
			raw = 0;
			echo = 1;
			titlebar();
			redraw();
		}
		wc <-= (len data, nil);
	}
}

resize(wid,hgt: int)
{
	scr := array[hgt] of string;
	cc := array[hgt] of string;
	for(y :=0; y<hgt; y++) {
		oy := y + hgt - vt.hgt;
		if(oy < vt.hgt && oy >= 0) {
			scr[y] = vt.scr[oy];
			cc[y] = vt.cc[oy];
		} else {
			scr[y] = "";
			cc[y] = "";
		}
	}
	vt.x += wid - vt.wid;
	vt.y += hgt - vt.hgt;
	if(vt.x < 0)
		vt.x = 0;
	if(vt.x >= wid)
		vt.x = wid;
	if(vt.y < 0)
		vt.y = 0;
	if(vt.y >= hgt)
		vt.y = hgt;
	vt.wid = wid;
	vt.hgt = hgt;
	vt.scr = scr;
	vt.cc = cc;
}


fixdx := 0;
fixdy := 0;

canvposn(t: ref Toplevel): Rect
{
	r: Rect;

	r.min.x = int tk->cmd(t, ".c cget -actx") + int tk->cmd(t, ".dx get");
	r.min.y = int tk->cmd(t, ".c cget -acty") + int tk->cmd(t, ".dy get");
	r.max.x = r.min.x + int tk->cmd(t, ".c cget -width") + int tk->cmd(t, ".dw get");
	r.max.y = r.min.y + int tk->cmd(t, ".c cget -height") + int tk->cmd(t, ".dh get");

	# correction for Tk bug (width/height not correct):
	dx := (t.image.r.max.x - t.image.r.min.x) - (r.max.x - r.min.x);
	dy := (t.image.r.max.y - t.image.r.min.y) - (r.max.y - r.min.y);
	if(fixdx == 0) {
		fixdx = dx;
		fixdy = dy;
	} else {
		r.max.x += dx-fixdx;
		r.max.y += dy-fixdy;
	}
	return r;
}


redraw()
{
	# sys->print("redraw\n");
	for(y:=0; y<vt.hgt; y++) {
		xp := canvrect.min.x;
		yp := canvrect.max.y-(vt.hgt-y)*font.height;
		f := 0;
		for(x:=0; x<=len vt.cc[y]; x++) {
			if(x == len vt.cc[y] || (vt.cc[y][x]>>4) != (vt.cc[y][f]>>4)) {
				if(x == len vt.cc[y])
					w := canvrect.max.x-xp;
				else
					w = font.width(vt.scr[y][f:x]);
				if(len vt.cc[y] == 0)
					ccc := 7;
				else
					ccc = vt.cc[y][f];
				canvas.draw(((xp,yp),(xp+w,yp+font.height)),
					vtc[ccc>>4], nil, (0, 0));
				xp += w;
				f = x;
			}
		}
		xp = canvrect.min.x;
		f = 0;
		for(x=1; x<=len vt.scr[y]; x++) {
			if(x == len vt.scr[y] || (vt.cc[y][x]&15) != (vt.cc[y][f]&15)) {
				canvas.text((xp,yp), vtc[vt.cc[y][f]&15],
					(0, 0), font, vt.scr[y][f:x]);
				xp += font.width(vt.scr[y][f:x]);
				f = x;
			}
		}
	}
}



scwrite(s: string)
{
	putchar(vt.x, vt.y, vtscr(vt.y, vt.x), vtcc(vt.y, vt.x));
	vt_write(vt, s);
	putchar(vt.x, vt.y, vtscr(vt.y, vt.x), vtcc(vt.y, vt.x) ^ 16rff);
}

putchar(x,y: int, ch: int, ccc: int)
{
	if(len vt.scr[y] < x) {
		vt.scr[y] += pad[0:x-len vt.scr[y]];
		vt.cc[y] += pad[0:x-len vt.cc[y]];
	}
	xp := canvrect.min.x+font.width(vt.scr[y][0:x]);
	yp := canvrect.max.y-(vt.hgt-y)*font.height;
	s: string;
	s[0] = ch;
	canvas.draw(((xp,yp),(xp+font.width(s),yp+font.height)),
				vtc[ccc>>4], nil, (0, 0));
	canvas.text((xp,yp), vtc[ccc&15], (0, 0), font, s);
}

VT_PUTCHAR(vt: ref Vt, x,y: int, ch: int)
{
	if(len vt.scr[y] < x) {
		vt.scr[y] += pad[0:x-len vt.scr[y]];
		vt.cc[y] += pad[0:x-len vt.cc[y]];
	}
	vt.scr[y][x] = ch;
	vt.cc[y][x] = vt.ccc;
	putchar(x, y, ch, int vt.ccc);
}

VT_SCROLL_UP(vt: ref Vt, x1,y1,x2,y2,n: int)
{
	# XXX - needs to handle vertical slices
	for(i:=y1; i<=y2-n; i++) {
		vt.scr[i] = vt.scr[i+n];
		vt.cc[i] = vt.cc[i+n];
	}
	r: Rect;
	r.min.x = canvrect.min.x;
	r.max.x = r.min.x+(x2-x1+1)*font.width(" ");
	r.min.y = canvrect.max.y-(vt.hgt-y1)*font.height;
	r.max.y = r.min.y+(y2-y1-n+1)*font.height;
	canvas.draw(r, canvas, nil, (r.min.x, r.min.y+font.height*n));
	VT_CLEAR(vt, x1,y2-n+1,x2,y2);
}

VT_SCROLL_DOWN(vt: ref Vt, x1,y1,x2,y2,n: int)
{
	# XXX - needs to handle vertical slices
	for(i:=y2; i>=y1+n; i--) {
		vt.scr[i] = vt.scr[i-n];
		vt.cc[i] = vt.cc[i-n];
	}
	VT_CLEAR(vt, x1,y1,x2,y1+n-1);
	redraw();
}

VT_SCROLL_LEFT(vt: ref Vt, x1,y1,x2,y2,n: int)
{
	# XXX - shouldn't always scroll whole line
	for(y:=y1; y<=y2; y++) {
		if(len vt.scr[y] > n) {
			vt.scr[y] = vt.scr[y][n:];
			vt.cc[y] = vt.cc[y][n:];
		} else {
			vt.scr[y] = "";
			vt.cc[y] = "";
		}
	}
	redraw();
}

VT_SCROLL_RIGHT(vt: ref Vt, x1,y1,x2,y2,n: int)
{
	# XXX - shouldn't always scroll whole line
	for(y:=y1; y<=y2; y++) {
		vt.scr[y] = pad[0:n] + vt.scr[y];
		vt.cc[y] = pad[0:n] + vt.cc[y];
	}
	redraw();
}

VT_CLEAR(vt: ref Vt, x1,y1,x2,y2: int)
{
	# XXX - needs to handle vertical slices
	for(y:=y1; y<=y2; y++) {
		vt.scr[y] = "";
		vt.cc[y] = "";
	}
	r: Rect;
	r.min.x = canvrect.min.x;
	r.max.x = r.min.x + (x2-x1+1)*font.width(" ");
	r.min.y = canvrect.max.y-(vt.hgt-y1)*font.height;
	r.max.y = r.min.y + (y2-y1+1)*font.height;
	canvas.draw(r, vtc[vt.ccc>>4], nil, (0, 0));
}

VT_SET_COLOR(vt: ref Vt)
{
	if(vt.attr & (1<<7))
		vt.ccc = ((vt.fg<<4) | vt.bg);
	else
		vt.ccc = ((vt.bg<<4) | vt.fg);
	if(vt.attr & (1<<1))
		vt.ccc ^= (1<<3);
}

vtscr(y,x: int): int
{
	if(vt.scr[y] == nil)
		return ' ';
	if(x >= len vt.scr[y])
		return ' ';
	return vt.scr[y][x];
}

vtcc(y,x: int): int
{
	if(vt.cc[y] == nil)
		return 7;
	if(x >= len vt.cc[y])
		return 7;
	return vt.cc[y][x];
}

VT_SET_CURSOR(nil: ref Vt, x,y: int)
{
}

VT_BEEP(nil: ref Vt)
{
	redraw();
}

# function for simulated typing (for returning status)
VT_TYPE(vt: ref Vt, b: string)
{
	inpchan <-= b;
}


#############################################################################


vt_save_state(vt: ref Vt)
{
	vt.save_x = vt.x;
	vt.save_y = vt.y;
	vt.save_attr = vt.attr;
	vt.save_fg = vt.fg;
	vt.save_bg = vt.bg;
	vt.save_mode = vt.mode;
	vt.save_qmode = vt.qmode;
}

vt_restore_state(vt: ref Vt)
{
	vt.x = vt.save_x;
	vt.y = vt.save_y;
	vt.attr = vt.save_attr;
	vt.fg = vt.save_fg;
	vt.bg = vt.save_bg;
	vt.mode = vt.save_mode;
	vt.qmode = vt.save_qmode;
	VT_SET_COLOR(vt);
}



# expects vt.wid, vt.hgt and implementation
# variables to be initialized first: 

vt_init(vt: ref Vt)
{
	vt.fg = 7;
	vt.bg = 0;
	vt.attr = 0;
	vt.mode = 0;
	vt.qmode = (1<<7);
	vt.y1 = 0;
	vt.y2 = vt.hgt-1;
	vt.x = 0;
	vt.y = 0;
	vt.dx = 1;
	vt.dy = 1;
	vt.esc = 0;
	vt.pcount = 0;
	vt.param = array[VT_MAXPARAM] of int;
	vt_save_state(vt);
	VT_SET_COLOR(vt);
}


vt_checkscroll(vt: ref Vt, s: string)
{
	i := 0;
	n: int;
	if (vt.y == vt.y2+1 || vt.y >= vt.hgt) {
		n = 1;
		while(i < len s && n < (vt.y2-vt.y1)) {
			c := s[i++];
			if(c == 27 || c > 126 || c < 0)
				break;
			if(c == '\n')
				n++;
		}
              	vt.y = vt.y2-n+1;
		VT_SCROLL_UP(vt,0,vt.y1,vt.wid-1,vt.y2,n);
       	} else if (vt.y == vt.y1-1) {
		vt.y = vt.y1;
		VT_SCROLL_DOWN(vt,0,vt.y1,vt.wid-1,vt.y2,1);
	} else if (vt.y < 0)
		vt.y = 0;
}

vt_write(vt: ref Vt, s: string)
{
	ch: int;
	check_scroll: int;
	n: int;
	i := 0;

        while(i < len s) {
	    check_scroll = 0;
            ch = s[i++];
	    case vt.esc {
	    1 =>
		if(ch == '[') {
			vt.etype = ch;
			vt.esc++;
			vt.value = 0;
			vt.pcount = 0;
			vt.ptype = 1;
			for(n=0; n<VT_MAXPARAM; n++)
				vt.param[n] = 0;
		} else {
			check_scroll = vt_call_ncsi(vt, ch);
			vt.esc = 0;
		}	
	    2 =>
		if(ch >= '0' && ch <= '9') 
			vt.value=(vt.value)*10+(ch-'0');
		else if(ch == '?')
			vt.ptype = -1;
		else {
			vt.param[vt.pcount++] = vt.value*vt.ptype;
			if(ch == ';') {
				if(vt.pcount >= VT_MAXPARAM)
					vt.pcount = VT_MAXPARAM-1;
				vt.value = 0;
			} else {
				check_scroll = vt_call_csi(vt, ch);
				vt.esc = 0;
			}
		}
	    * =>
		case ch {
                '\n' =>
                        vt.y += vt.dy;
			check_scroll = 1;
			if(vt.nlcr)
                        	vt.x = 0;
                '\r' =>
                        vt.x = 0;
                '\b' =>
                        if (vt.x > 0)
                                vt.x -= vt.dx;
                '\t' =>
			n = (vt.x & ~7)+8;
			if(vt.mode & (1<<4))
				VT_SCROLL_RIGHT(vt, vt.x,vt.y,
				  vt.wid-1,vt.y, n - vt.x);
                        vt.x = n;
			if(vt.x > vt.wid) {
				vt.x = 0; 
				vt.y++;
				check_scroll = 1;
			}
		7 =>
			VT_BEEP(vt);
		11 =>
			vt.x = 0;
			vt.y = vt.y1;
		12 =>
			VT_CLEAR(vt,0,vt.y1,vt.wid-1,vt.y2);
		27 =>
			vt.esc++;
		133 =>
			vt.x = 0;
			vt.y++;
			check_scroll = 1;
		132 =>
			vt.y++;
			check_scroll = 1;
		136 =>	# XXX - set a tabstop 
			;
		141 =>
			vt.y--;
			check_scroll = 1;
		142 =>	# XXX -- map G2 into GL for next char only
			;
		143 =>	# XXX -- map G3 into GL for next char 
			;
		144 =>	# XXX -- device control string 
			;
		145 =>	# XXX -- start of string - ignored 
			;
		146 =>	# XXX -- device attribute request 
			;
		147 =>
			vt.esc = 2;
			vt.etype = '[';
			vt.esc++;
			vt.value = 0;
			vt.pcount = 0;
			vt.ptype = 1;
			for(n=0; n<VT_MAXPARAM; n++)
				vt.param[n] = 0;
                * =>
			if(vt.mode & (1<<4))
				VT_SCROLL_RIGHT(vt,vt.x,vt.y,
				  vt.wid-1,vt.y,1);	
			if(ch>=32 || ch <=126) {
				if(vt.qmode & (1<<15)) {
					if(vt.x >= vt.wid-1 && (vt.qmode & (1<<7))) {
						vt.x = 0;
						vt.y += vt.dy;
						vt_checkscroll(vt, s[i:]);
					}
					vt.qmode &= ~(1<<15);
				}
				VT_PUTCHAR(vt,vt.x,vt.y,ch);
                       		if((vt.x += vt.dx) >= vt.wid) {
					vt.x = vt.wid-1; 
					vt.qmode |= (1<<15);
                        	}
			}
                }
	    }
	    if(check_scroll)
		vt_checkscroll(vt, s[i:]); 
	    if(vt.x < 0)
		vt.x = 0;
	    else if(vt.x >= vt.wid)
		vt.x = vt.wid-1;
	    if(vt.y < 0)
		vt.y = 0;
	    else if(vt.y >= vt.hgt)
		vt.y = vt.hgt-1;
	}
	VT_SET_CURSOR(vt, vt.x, vt.y);
}




vt_call_csi(vt: ref Vt, ch: int): int
{
	i, n: int;
	case ch {
	'A' =>
		vt.y -= vt_param(vt, 1,1,1,vt.hgt);
	'B' =>
		vt.y += vt_param(vt, 1,1,1,vt.hgt);
	'C' =>
		vt.x += vt_param(vt, 1,1,1,vt.wid);
	'D' =>
		vt.x -= vt_param(vt, 1,1,1,vt.wid);
	'f' or 'H' =>
		vt.y = vt_param(vt, 0,1,1,vt.hgt)-1;
		vt.x = vt_param(vt, 1,1,1,vt.wid)-1;
	'J' =>
		case vt.param[0] {
		0 => VT_CLEAR(vt,vt.x,vt.y,vt.wid-1,vt.y);
			VT_CLEAR(vt,0,vt.y+1,vt.wid-1,vt.y2); 
		1 => VT_CLEAR(vt,0,0,vt.wid-1,vt.y-1); 
			VT_CLEAR(vt,0,vt.y,vt.x,vt.y); 
		2 => VT_CLEAR(vt,0,vt.y1,vt.wid-1,vt.y2);	
		}
	'K' =>
		case vt.param[0] {
		0 => VT_CLEAR(vt,vt.x,vt.y,vt.wid-1,vt.y);
		1 => VT_CLEAR(vt,0,vt.y,vt.x,vt.y);
		2 => VT_CLEAR(vt,0,vt.y,vt.wid-1,vt.y); 
		}
	'L' =>
		n = vt_param(vt, 0,1,1,vt.hgt);
		VT_SCROLL_DOWN(vt,0,vt.y,vt.wid-1,vt.y2,n);	
	'M' =>
		n = vt_param(vt,0,1,1,vt.hgt);
		VT_SCROLL_UP(vt,0,vt.y,vt.wid-1,vt.y2,n);	
	'@' =>
		n = vt_param(vt,0,1,1,vt.wid-1-vt.x);
		VT_SCROLL_RIGHT(vt,vt.x,vt.y,vt.wid-1,vt.y,n);	
	'P' =>
		n = vt_param(vt,0,1,1,vt.wid-1-vt.x);
		VT_SCROLL_LEFT(vt,vt.x,vt.y,vt.wid-1,vt.y,n);	
	'X' =>
		n = vt_param(vt,0,1,1,vt.wid-1-vt.x);
		VT_CLEAR(vt,vt.x,vt.y,vt.x+n-1,vt.y);
	'm' =>
		if(vt.pcount == 0)
			vt.pcount++;
		for(i=0; i<vt.pcount; i++) {
			n = vt.param[i];
			if(!n) {
				vt.attr = 0; 
				vt.fg = 7;
				vt.bg = 0;
			} else if (n < 16)
				vt.attr |= (1<<n);
			else if (n < 28)
				vt.attr &= ~(1<<(n-20));
			else if (n < 38)
				vt.fg = n-30;
			else if (n < 48)
				vt.bg = n-40;
			else if (n < 58)
				vt.fg = n-50+8;
			else if (n < 68)
				vt.bg = n-60+8;
		}
		VT_SET_COLOR(vt);
	'c' =>
		if(vt.wid >= 132)
			VT_TYPE(vt, "\u001b[?61;1;6c");
		else
			VT_TYPE(vt, "\u001b[?61;6c");
	'n' => 
		n = vt_param(vt, 0,0,0,9);
		if(n == 5)
			VT_TYPE(vt, "\u001b[0n");
		if(n == 5 || n == 6) 
			VT_TYPE(vt, sprint("\u001b[%d;%dR",vt.y+1,vt.x+1));
	'r' =>
		vt.y1 = vt_param(vt, 0,1,1,vt.hgt)-1;
		vt.y2 = vt_param(vt, 1,vt.hgt,1,vt.hgt)-1;
	's' =>
		vt_save_state(vt);
	'u' =>
		vt_restore_state(vt);
	'h' =>
		for(i=0; i<vt.pcount; i++) {
			n = vt.param[i];
			if(n >= 0)
				vt.mode |= (1<<n);
			else
				vt.qmode |= (1<<(-n));
		}
	'l' =>
		for(i=0; i<vt.pcount; i++) {
			n = vt.param[i];
			if(n >= 0)
				vt.mode &= ~(1<<n);
			else
				vt.qmode &= ~(1<<(-n));
		}
	}

	if(vt.y < 0)
		vt.y = 0;
	if(vt.y >= vt.hgt)
		vt.y = vt.hgt-1;
	if(vt.x < 0)
		vt.x = 0;
	if(vt.x >= vt.wid)
		vt.x = vt.wid-1;
	return 0;
}

vt_call_ncsi(vt: ref Vt, ch: int): int
{
	case ch {
	'E' =>
		vt.x = 0;
	'9' =>
		;
	'D' =>
		vt.y++;
		return 1;
	'H' =>	# XXX -- horizontal tab set
		;
	'6' =>
		;
	'M' =>
		vt.y--;
		return 1;
	'7' =>
		vt_save_state(vt);
	'8' =>
		vt_restore_state(vt);
	'=' =>
		;
	'>' =>
		;
	'#' =>
		;
	'(' =>
		;
	')' =>
		;
	}
	return 0;
}


vt_param(vt: ref Vt, n: int, def: int, min, max: int): int
{
	param := vt.param[n];
	if(param == 0)
		param = def;
	if(param < min)
		param = min;
	if(param > max)
		param = max;
	return param;
}

#############################################################################


consinp(cs: chan of string, cr: chan of (big, int, int, Sys->Rread))
{
	for(;;) {
		alt {
		sq += <- cs => ;

		(nil, nbytes, nil, rc) := <- cr =>
			p := 0;
			for(;;) {
				if(raw)
					p = len sq;
				else
					forloop:
					for(i := 0; i < len sq; i++) {
						case sq[i] {
						'\b' =>
							if(i > 0) {
								sq = sq[0:i-1] + sq[i+1:];
								--i;
							}
						'\n' =>
							p = i+1;
							break forloop;
						}
					}
				if(p > 0)
					break;
				sq += <- cs;
			}
			if(nbytes > p)
				nbytes = p;
			alt {
			rc <-= (array of byte sq[0:nbytes], "") =>
				sq = sq[nbytes:];
			* => ;
			}
		}
	}
}

newsh(ctxt: ref Draw->Context, ioc: chan of (int, ref Sys->FileIO, ref Sys->FileIO))
{
	pid := sys->pctl(sys->NEWFD, nil);

	sh := load Command "/dis/sh.dis";
	if(sh == nil) {
		ioc <-= (0, nil, nil);
		return;
	}

	tty := "cons."+string pid;

	sys->bind("#s","/chan",sys->MBEFORE);
	fio := sys->file2chan("/chan", tty);
	fioctl := sys->file2chan("/chan", tty + "ctl");
	ioc <-= (pid, fio, fioctl);
	if ((fio == nil) || (fioctl == nil))
		return;

	sys->bind("/chan/"+tty, "/dev/cons", sys->MREPL);
	sys->bind("/chan/"+tty+"ctl", "/dev/consctl", sys->MREPL);

	fd0 := sys->open("/dev/cons", sys->OREAD|sys->ORCLOSE);
	fd1 := sys->open("/dev/cons", sys->OWRITE);
	fd2 := sys->open("/dev/cons", sys->OWRITE);

	sh->init(ctxt, "sh" :: "-n" :: nil);
}

kill(pid: int)
{
	fd := sys->open("#p/"+string pid+"/ctl", sys->OWRITE);
	if(fd != nil)
		sys->fprint(fd, "killgrp");
}

tkcmds(t: ref Tk->Toplevel, cfg: array of string)
{
	for(i := 0; i < len cfg; i++)
		tk->cmd(t, cfg[i]);
}