diff options
Diffstat (limited to 'appl/wm/telnet.b')
| -rw-r--r-- | appl/wm/telnet.b | 820 |
1 files changed, 820 insertions, 0 deletions
diff --git a/appl/wm/telnet.b b/appl/wm/telnet.b new file mode 100644 index 00000000..077dd6aa --- /dev/null +++ b/appl/wm/telnet.b @@ -0,0 +1,820 @@ +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; +} |
