ref: 02ac617541ca1a7bf82b1615fb5a58235469b5d3
dir: /appl/wm/telnet.b/
implement WmTelnet;
include "sys.m";
sys: Sys;
Connection: import sys;
include "draw.m";
draw: Draw;
Context: import draw;
include "tk.m";
tk: Tk;
include "tkclient.m";
tkclient: Tkclient;
include "dialog.m";
dialog: Dialog;
WmTelnet: module
{
init: fn(ctxt: ref Draw->Context, args: list of string);
};
Iob: adt
{
fd: ref Sys->FD;
t: ref Tk->Toplevel;
out: cyclic ref Iob;
buf: array of byte;
ptr: int;
nbyte: int;
};
BS: con 8; # ^h backspace character
BSW: con 23; # ^w bacspace word
BSL: con 21; # ^u backspace line
EOT: con 4; # ^d end of file
ESC: con 27; # hold mode
HIWAT: con 2000; # maximum number of lines in transcript
LOWAT: con 1500; # amount to reduce to after high water
Name: con "Telnet";
ctxt: ref Context;
cmds: chan of string;
net: Connection;
stderr: ref Sys->FD;
mcrlf: int;
netinp: ref Iob;
# control characters
Se: con 240; # end subnegotiation
NOP: con 241;
Mark: con 242; # data mark
Break: con 243;
Interrupt: con 244;
Abort: con 245; # TENEX ^O
AreYouThere: con 246;
Erasechar: con 247; # erase last character
Eraseline: con 248; # erase line
GoAhead: con 249; # half duplex clear to send
Sb: con 250; # start subnegotiation
Will: con 251;
Wont: con 252;
Do: con 253;
Dont: con 254;
Iac: con 255;
# options
Binary, Echo, SGA, Stat, Timing,
Det, Term, EOR, Uid, Outmark,
Ttyloc, M3270, Padx3, Window, Speed,
Flow, Line, Xloc, Extend: con iota;
Opt: adt
{
name: string;
code: int;
noway: int;
remote: int; # remote value
local: int; # local value
};
opt := array[] of
{
Binary => Opt("binary", 0, 0, 0, 0),
Echo => Opt("echo", 1, 0, 0, 0),
SGA => Opt("suppress Go Ahead", 3, 0, 0, 0),
Stat => Opt("status", 5, 1, 0, 0),
Timing => Opt("timing", 6, 1, 0, 0),
Det => Opt("det", 20, 1, 0, 0),
Term => Opt("terminal", 24, 0, 0, 0),
EOR => Opt("end of record", 25, 1, 0, 0),
Uid => Opt("uid", 26, 1, 0, 0),
Outmark => Opt("outmark", 27, 1, 0, 0),
Ttyloc => Opt("ttyloc", 28, 1, 0, 0),
M3270 => Opt("3270 mode", 29, 1, 0, 0),
Padx3 => Opt("pad x.3", 30, 1, 0, 0),
Window => Opt("window size", 31, 1, 0, 0),
Speed => Opt("speed", 32, 1, 0, 0),
Flow => Opt("flow control", 33, 1, 0, 0),
Line => Opt("line mode", 34, 0, 0, 0),
Xloc => Opt("X display loc", 35, 1, 0, 0),
Extend => Opt("Extended", 255, 1, 0, 0),
};
shwin_cfg := array[] of {
"menu .m",
".m add command -text Cut -command {send edit cut}",
".m add command -text Paste -command {send edit paste}",
".m add command -text Snarf -command {send edit snarf}",
".m add command -text Send -command {send edit send}",
"frame .ft",
"scrollbar .ft.scroll -command {.ft.t yview}",
"text .ft.t -width 70w -height 25h -yscrollcommand {.ft.scroll set}",
"frame .mb",
"menubutton .mb.c -text Connect -menu .mbc",
"menubutton .mb.t -text Terminal -menu .mbt",
"menu .mbc",
".mbc add command -text {Remote System} -command {send cmd con}",
".mbc add command -text {Disconnect} -state disabled -command {send cmd dis}",
".mbc add command -text {Exit} -command {send cmd exit}",
".mbc add separator",
"menu .mbt",
".mbt add checkbutton -text {Line Mode} -command {send cmd line}",
".mbt add checkbutton -text {Map CR to LF} -command {send cmd crlf}",
"pack .mb.c .mb.t -side left",
"pack .ft.scroll -side left -fill y",
"pack .ft.t -fill both -expand 1",
"pack .mb -fill x",
"pack .ft -fill both -expand 1",
"pack propagate . 0",
"focus .ft.t",
"bind .ft.t <Key> {send keys {%A}}",
"bind .ft.t <Control-d> {send keys {%A}}",
"bind .ft.t <Control-h> {send keys {%A}}",
"bind .ft.t <ButtonPress-3> {send but3 %X %Y}",
"bind .ft.t <ButtonRelease-3> {}",
"bind .ft.t <DoubleButton-3> {}",
"bind .ft.t <Double-ButtonRelease-3> {}",
"bind .ft.t <ButtonPress-2> {}",
"bind .ft.t <ButtonRelease-2> {}",
"update"
};
connect_cfg := array[] of {
"frame .fl",
"label .fl.h -text Host",
"label .fl.p -text Port",
"pack .fl.h .fl.p",
"frame .el",
"entry .el.h",
"entry .el.p",
".el.p insert end 'telnet",
"pack .el.h .el.p",
"pack .Wm_t -fill x",
"pack .fl .el -side left",
"focus .el.h",
"bind .el.h <Key-\n> {send cmd ok}",
"bind .el.p <key-\n> {send cmd ok}",
"update"
};
connected_cfg := array[] of {
"focus .ft.t",
".mbc entryconfigure 0 -state disabled",
".mbc entryconfigure 1 -state normal"
};
menuindex := "0";
holding := 0;
init(C: ref Context, argv: list of string)
{
sys = load Sys Sys->PATH;
if (C == nil) {
sys->fprint(sys->fildes(2), "telnet: no window context\n");
raise "fail:bad context";
}
draw = load Draw Draw->PATH;
tk = load Tk Tk->PATH;
tkclient = load Tkclient Tkclient->PATH;
dialog = load Dialog Dialog->PATH;
ctxt = C;
tkclient->init();
dialog->init();
sys->pctl(Sys->NEWPGRP, nil);
stderr = sys->fildes(2);
tkargs := "";
argv = tl argv;
if(argv != nil) {
tkargs = hd argv;
argv = tl argv;
}
(t, titlectl) := tkclient->toplevel(ctxt, tkargs, Name, Tkclient->Appl);
edit := chan of string;
tk->namechan(t, edit, "edit");
for (cc:=0; cc<len shwin_cfg; cc++)
tk->cmd(t, shwin_cfg[cc]);
keys := chan of string;
tk->namechan(t, keys, "keys");
but3 := chan of string;
tk->namechan(t, but3, "but3");
cmds = chan of string;
tk->namechan(t, cmds, "cmd");
# outpoint is place in text to insert characters printed by programs
tk->cmd(t, ".ft.t mark set outpoint end; .ft.t mark gravity outpoint left");
tkclient->onscreen(t, nil);
tkclient->startinput(t, "kbd"::"ptr"::nil);
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 or
s = <-titlectl =>
if(s == "exit") {
kill();
return;
}
tkclient->wmctl(t, s);
ecmd := <-edit =>
editor(t, ecmd);
sendinput(t);
c := <-keys =>
if(opt[Echo].local == 0) {
sys->fprint(net.dfd, "%c", c[1]);
break;
}
cut(t, 1);
char := c[1];
if(char == '\\')
char = c[2];
update := ";.ft.t see insert;update";
case char{
* =>
tk->cmd(t, ".ft.t insert insert "+c+update);
'\n' or EOT =>
tk->cmd(t, ".ft.t insert insert "+c+update);
sendinput(t);
BS =>
if(!insat(t, "outpoint"))
tk->cmd(t, ".ft.t delete insert-1chars"+update);
ESC =>
holding ^= 1;
color := "blue";
if(!holding){
color = "black";
tkclient->settitle(t, Name);
sendinput(t);
}else
tkclient->settitle(t, Name+" (holding)");
tk->cmd(t, ".ft.t configure -foreground "+color+update);
BSL =>
if(insininput(t))
tk->cmd(t, ".ft.t delete outpoint insert"+update);
else
tk->cmd(t, ".ft.t delete {insert linestart} insert"+update);
BSW =>
if(insat(t, "outpoint"))
break;
a0 := isalnum(tk->cmd(t, ".ft.t get insert-1chars"));
a1 := isalnum(tk->cmd(t, ".ft.t get insert"));
start: string;
if(a0 && a1) # middle of word
start = "{insert wordstart}";
else if(a0) # end of word
start = "{insert-1chars wordstart}";
else{ # beginning or not in word; must search
s: string;
for(n:=1; ;){
s = tk->cmd(t, ".ft.t get insert-"+ string n +"chars");
if(s=="" || s=="\n"){
start = "insert-"+ string n+"chars";
break;
}
n++;
if(isalnum(s)){
start = "{insert-"+ string n+"chars wordstart}";
break;
}
}
}
# don't ^w across outpoint
if(tk->cmd(t, ".ft.t compare insert >= outpoint") == "1"
&& tk->cmd(t, ".ft.t compare "+start+" < outpoint") == "1")
start = "outpoint";
tk->cmd(t, ".ft.t delete " + start + " insert"+update);
}
c := <-but3 =>
(nil, l) := sys->tokenize(c, " ");
x := int hd l - 50;
y := int hd tl l - int tk->cmd(t, ".m yposition "+menuindex) - 10;
tk->cmd(t, ".m activate "+menuindex+"; .m post "+string x+" "+string y+
"; grab set .m; update");
c := <-cmds =>
case c {
"con" =>
tk->cmd(t, ".mb.c configure -state disabled");
connect(t);
tk->cmd(t, ".mb.c configure -state normal; update");
"dis" =>
tkclient->settitle(t, "Telnet");
tk->cmd(t, ".mbc entryconfigure 0 -state normal");
tk->cmd(t, ".mbc entryconfigure 1 -state disabled");
net.cfd = nil;
net.dfd = nil;
kill();
"exit" =>
kill();
return;
"crlf" =>
mcrlf = !mcrlf;
break;
"line" =>
if(opt[Line].local == 0)
send3(netinp, Iac, Will, opt[Line].code);
else
send3(netinp, Iac, Wont, opt[Line].code);
}
}
}
insat(t: ref Tk->Toplevel, mark: string): int
{
return tk->cmd(t, ".ft.t compare insert == "+mark) == "1";
}
insininput(t: ref Tk->Toplevel): int
{
if(tk->cmd(t, ".ft.t compare insert >= outpoint") != "1")
return 0;
return tk->cmd(t, ".ft.t compare {insert linestart} == {outpoint linestart}") == "1";
}
isalnum(s: string): int
{
if(s == "")
return 0;
c := s[0];
if('a' <= c && c <= 'z')
return 1;
if('A' <= c && c <= 'Z')
return 1;
if('0' <= c && c <= '9')
return 1;
if(c == '_')
return 1;
if(c > 16rA0)
return 1;
return 0;
}
editor(t: ref Tk->Toplevel, ecmd: string)
{
s, snarf: string;
case ecmd {
"cut" =>
menuindex = "0";
cut(t, 1);
"paste" =>
menuindex = "1";
snarf = tkclient->snarfget();
if(snarf == "")
break;
cut(t, 0);
tk->cmd(t, ".ft.t insert insert '"+snarf);
sendinput(t);
"snarf" =>
menuindex = "2";
if(tk->cmd(t, ".ft.t tag ranges sel") == "")
break;
snarf = tk->cmd(t, ".ft.t get sel.first sel.last");
tkclient->snarfput(snarf);
"send" =>
menuindex = "3";
if(tk->cmd(t, ".ft.t tag ranges sel") != ""){
snarf = tk->cmd(t, ".ft.t get sel.first sel.last");
tkclient->snarfput(snarf);
}else
snarf = tkclient->snarfget();
if(snarf != "")
s = snarf;
else
return;
if(s[len s-1] != '\n' && s[len s-1] != EOT)
s[len s] = '\n';
tk->cmd(t, ".ft.t see end; .ft.t insert end '"+s);
tk->cmd(t, ".ft.t mark set insert end");
tk->cmd(t, ".ft.t tag remove sel sel.first sel.last");
}
tk->cmd(t, "update");
}
cut(t: ref Tk->Toplevel, snarfit: int)
{
if(tk->cmd(t, ".ft.t tag ranges sel") == "")
return;
if(snarfit)
tkclient->snarfput(tk->cmd(t, ".ft.t get sel.first sel.last"));
tk->cmd(t, ".ft.t delete sel.first sel.last");
}
sendinput(t: ref Tk->Toplevel)
{
if(holding)
return;
input := tk->cmd(t, ".ft.t get outpoint end");
slen := len input;
if(slen == 0)
return;
for(i := 0; i < slen; i++)
if(input[i] == '\n' || input[i] == EOT)
break;
if(i >= slen)
return;
advance := string (i+1);
if(input[i] == EOT)
input = input[0:i];
else
input = input[0:i+1];
sys->fprint(net.dfd, "%s", input);
tk->cmd(t, ".ft.t mark set outpoint outpoint+" + advance + "chars");
}
kill()
{
path := sys->sprint("#p/%d/ctl", sys->pctl(0, nil));
fd := sys->open(path, sys->OWRITE);
if(fd != nil)
sys->fprint(fd, "killgrp");
}
connect(t: ref Tk->Toplevel)
{
(b, titlectl) := tkclient->toplevel(ctxt, nil, "Connect", 0);
for (c:=0; c<len connect_cfg; c++)
tk->cmd(b, connect_cfg[c]);
cmd := chan of string;
tk->namechan(b, cmd, "cmd");
tkclient->onscreen(b, nil);
tkclient->startinput(b, "kbd"::"ptr"::nil);
loop: for(;;) alt {
s := <-b.ctxt.kbd =>
tk->keyboard(b, s);
s := <-b.ctxt.ptr =>
tk->pointer(b, *s);
s := <-b.ctxt.ctl or
s = <-b.wreq or
s = <-titlectl =>
if(s == "exit")
return;
tkclient->wmctl(b, s);
<-cmd =>
break loop;
}
addr := sys->sprint("tcp!%s!%s",
tk->cmd(b, ".el.h get"),
tk->cmd(b, ".el.p get"));
tkclient->settitle(b, "Dialing");
tk->cmd(b, "update");
ok: int;
(ok, net) = sys->dial(addr, nil);
if(ok < 0) {
dialog->prompt(ctxt, b.image, "error -fg red",
"Connect", "Connection to host failed\n"+sys->sprint("%r"),
0, "Stop connect" :: nil);
return;
}
tkclient->settitle(t, "Telnet - "+addr);
for (c=0; c<len connected_cfg; c++)
tk->cmd(b, connected_cfg[c]);
spawn fromnet(t);
}
flush(t: ref Tk->Toplevel, data: array of byte)
{
cdata := string data;
ncdata := string len cdata + "chars;";
moveins := insat(t, "outpoint");
tk->cmd(t, ".ft.t insert outpoint '"+ cdata);
s := ".ft.t mark set outpoint outpoint+" + ncdata;
s += ".ft.t see outpoint;";
if(moveins)
s += ".ft.t mark set insert insert+" + ncdata;
s += "update";
tk->cmd(t, s);
nlines := int tk->cmd(t, ".ft.t index end");
if(nlines > HIWAT){
s = ".ft.t delete 1.0 "+ string (nlines-LOWAT) +".0;update";
tk->cmd(t, s);
}
}
iobnew(fd: ref Sys->FD, t: ref Tk->Toplevel, out: ref Iob, size: int): ref Iob
{
iob := ref Iob;
iob.fd = fd;
iob.t = t;
iob.out = out;
iob.buf = array[size] of byte;
iob.nbyte = 0;
iob.ptr = 0;
return iob;
}
iobget(iob: ref Iob): int
{
if(iob.nbyte == 0) {
if(iob.out != nil)
iobflush(iob.out);
iob.nbyte = sys->read(iob.fd, iob.buf, len iob.buf);
if(iob.nbyte <= 0)
return iob.nbyte;
iob.ptr = 0;
}
iob.nbyte--;
return int iob.buf[iob.ptr++];
}
iobput(iob: ref Iob, c: int)
{
iob.buf[iob.ptr++] = byte c;
if(iob.ptr == len iob.buf)
iobflush(iob);
}
iobflush(iob: ref Iob)
{
if(iob.fd == nil) {
flush(iob.t, iob.buf[0:iob.ptr]);
iob.ptr = 0;
}
}
fromnet(t: ref Tk->Toplevel)
{
conout := iobnew(nil, t, nil, 2048);
netinp = iobnew(net.dfd, nil, conout, 2048);
crnls := 0;
freenl := 0;
loop: for(;;) {
c := iobget(netinp);
case c {
-1 =>
cmds <-= "dis";
return;
'\n' => # skip nl after string of cr's */
if(!opt[Binary].local && !mcrlf) {
crnls++;
if(freenl == 0)
break;
freenl = 0;
continue loop;
}
'\r' =>
if(!opt[Binary].local && !mcrlf) {
if(crnls++ == 0){
freenl = 1;
c = '\n';
break;
}
continue loop;
}
Iac =>
c = iobget(netinp);
if(c == Iac)
break;
iobflush(conout);
if(control(netinp, c) < 0)
return;
continue loop;
}
iobput(conout, c);
}
}
control(bp: ref Iob, c: int): int
{
case c {
AreYouThere =>
sys->fprint(net.dfd, "Inferno telnet V1.0\r\n");
Sb =>
return sub(bp);
Will =>
return will(bp);
Wont =>
return wont(bp);
Do =>
return doit(bp);
Dont =>
return dont(bp);
Se =>
sys->fprint(stderr, "telnet: SE without an SB\n");
-1 =>
return -1;
* =>
break;
}
return 0;
}
sub(bp: ref Iob): int
{
subneg: string;
i := 0;
for(;;){
c := iobget(bp);
if(c == Iac) {
c = iobget(bp);
if(c == Se)
break;
subneg[i++] = Iac;
}
if(c < 0)
return -1;
subneg[i++] = c;
}
if(i == 0)
return 0;
sys->fprint(stderr, "sub %d %d n = %d\n", subneg[0], subneg[1], i);
for(i = 0; i < len opt; i++)
if(opt[i].code == subneg[0])
break;
if(i >= len opt)
return 0;
case i {
Term =>
sbsend(opt[Term].code, array of byte "dumb");
}
return 0;
}
sbsend(code: int, data: array of byte): int
{
buf := array[4+len data+2] of byte;
o := 4+len data;
buf[0] = byte Iac;
buf[1] = byte Sb;
buf[2] = byte code;
buf[3] = byte 0;
buf[4:] = data;
buf[o] = byte Iac;
o++;
buf[o] = byte Se;
return sys->write(net.dfd, buf, len buf);
}
will(bp: ref Iob): int
{
c := iobget(bp);
if(c < 0)
return -1;
sys->fprint(stderr, "will %d\n", c);
for(i := 0; i < len opt; i++)
if(opt[i].code == c)
break;
if(i >= len opt) {
send3(bp, Iac, Dont, c);
return 0;
}
rv := 0;
if(opt[i].noway)
send3(bp, Iac, Dont, c);
else
if(opt[i].remote == 0)
rv |= send3(bp, Iac, Do, c);
if(opt[i].remote == 0)
rv |= change(bp, i, Will);
opt[i].remote = 1;
return rv;
}
wont(bp: ref Iob): int
{
c := iobget(bp);
if(c < 0)
return -1;
sys->fprint(stderr, "wont %d\n", c);
for(i := 0; i < len opt; i++)
if(opt[i].code == c)
break;
if(i >= len opt)
return 0;
rv := 0;
if(opt[i].remote) {
rv |= change(bp, i, Wont);
rv |= send3(bp, Iac, Dont, c);
}
opt[i].remote = 0;
return rv;
}
doit(bp: ref Iob): int
{
c := iobget(bp);
if(c < 0)
return -1;
sys->fprint(stderr, "do %d\n", c);
for(i := 0; i < len opt; i++)
if(opt[i].code == c)
break;
if(i >= len opt || opt[i].noway) {
send3(bp, Iac, Wont, c);
return 0;
}
rv := 0;
if(opt[i].local == 0) {
rv |= change(bp, i, Do);
rv |= send3(bp, Iac, Will, c);
}
opt[i].local = 1;
return rv;
}
dont(bp: ref Iob): int
{
c := iobget(bp);
if(c < 0)
return -1;
sys->fprint(stderr, "dont %d\n", c);
for(i := 0; i < len opt; i++)
if(opt[i].code == c)
break;
if(i >= len opt || opt[i].noway)
return 0;
rv := 0;
if(opt[i].local){
opt[i].local = 0;
rv |= change(bp, i, Dont);
rv |= send3(bp, Iac, Wont, c);
}
opt[i].local = 0;
return rv;
}
change(nil: ref Iob, nil: int, nil: int): int
{
return 0;
}
send3(bp: ref Iob, c0: int, c1: int, c2: int): int
{
buf := array[3] of byte;
buf[0] = byte c0;
buf[1] = byte c1;
buf[2] = byte c2;
t: string;
case c0 {
Will => t = "Will";
Wont => t = "Wont";
Do => t = "Do";
Dont => t = "Dont";
}
if(t != nil)
sys->fprint(stderr, "r %s %d\n", t, c1);
r := sys->write(bp.fd, buf, 3);
if(r != 3)
return -1;
return 0;
}