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 "wmlib.m"; wmlib: Wmlib; 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; ones: ref Image; 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 {send keys {%A}}", "bind . {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, argv: list of string) { sys = load Sys Sys->PATH; draw = load Draw Draw->PATH; tk = load Tk Tk->PATH; wmlib = load Wmlib Wmlib->PATH; stderr = sys->fildes(2); sys->pctl(Sys->FORKNS, nil); sys->pctl(Sys->NEWPGRP, nil); menubut: chan of string; wmlib->init(); (t, menubut) = wmlib->titlebar(ctxt.screen, "", "WmVt", Wmlib->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; inamechan(t, cmd, "cmd"); tk->cmd(t, "canvas .c -height " + string (vt.hgt*font.height) + + " -width " + string (vt.wid*font.width("0")) + " -background red"); wmlib->tkcmds(t, shwin_cfg); titlebar(); keys := chan of string; tk->namechan(t, keys, "keys"); canvas = t.image; canvrect = canvposn(t); org = canvrect.min; ones = display.ones; 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.ldepth, 1, display.rgb2cmap(r, g, b)); } 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 { 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 wmlib->titlectl(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 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= 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>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], ones, (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], ones, (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, ones, (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], ones, (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(vt: ref Vt, x,y: int) { } VT_BEEP(vt: 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 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 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 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= 0) vt.mode |= (1< for(i=0; i= 0) vt.mode &= ~(1<= 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 (int, 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"); }