ref: 3efb5bbb4061056e523858b134c555949591efe2
dir: /appl/examples/minitel/miniterm.b/
# # Copyright © 1998 Vita Nuova Limited. All rights reserved. # implement Miniterm; include "sys.m"; sys: Sys; print, fprint, sprint, read: import sys; include "draw.m"; draw: Draw; include "tk.m"; tk: Tk; include "tkclient.m"; tkclient: Tkclient; include "dial.m"; dial: Dial; include "miniterm.m"; Miniterm: module { init: fn(ctxt: ref Draw->Context, argv: list of string); }; pgrp: int = 0; debug: array of int = array[256] of {* => 0}; stderr: ref Sys->FD; # Minitel terminal identification request - reply sequence TERMINALID1 := array [] of { byte SOH, byte 'S', byte 'X', byte '1', byte 'H', byte 'N', byte EOT }; TERMINALID2 := array [] of { byte SOH, byte 'C', byte 'g', byte '1', byte EOT }; # Minitel module identifiers Mscreen, Mmodem, Mkeyb, Msocket, Nmodule: con iota; Pscreen, Pmodem, Pkeyb, Psocket: con (1 << iota); Modname := array [Nmodule] of { Mscreen => "S", Mmodem => "M", Mkeyb => "K", Msocket => "C", * => "?", }; # attributes common to all modules Module: adt { path: int; # bitset to connected modules disabled: int; }; # A BufChan queues events from the terminal to the modules BufChan: adt { path: int; # id bit ch: chan of ref Event; # set to `in' or `dummy' channel ev: ref Event; # next event to send in: chan of ref Event; # real channel for Events to the device q: array of ref Event; # subsequent events to send }; # holds state information for the minitel `protocol` (chapter 6) PState: adt { state: int; arg: array of int; # up to 3 arguments: X,Y,Z nargs: int; # expected number of arguments n: int; # progress skip: int; # transparency; bytes to skip }; PSstart, PSesc, PSarg: con iota; # states # Terminal display modes Videotex, Mixed, Ascii, # Connection methods Direct, Network, # Terminal connection states Local, Connecting, Online, # Special features Echo : con (1 << iota); Terminal: adt { in: chan of ref Event; out: array of ref BufChan; # buffered output to the minitel modules mode: int; # display mode state: int; # connection state spec: int; # special features connect: int; # Direct, or Network toplevel: ref Tk->Toplevel; cmd: chan of string; # from Tk proto: array of ref PState; # minitel protocol state netaddr: string; # network address to dial buttonsleft: int; # display buttons on the LHS (40 cols) terminalid: array of byte; # ENQROM response kbctl: chan of string; # softkeyboard control kbmode: string; # softkeyboard mode init: fn(t: self ref Terminal, toplevel: ref Tk->Toplevel, connect: int); run: fn(t: self ref Terminal, done: chan of int); reset: fn(t: self ref Terminal); quit: fn(t: self ref Terminal); layout: fn(t: self ref Terminal, cols: int); setkbmode: fn(t: self ref Terminal, tmode: int); }; include "arg.m"; include "event.m"; include "event.b"; include "keyb.b"; include "modem.b"; include "socket.b"; include "screen.b"; K: ref Keyb; M: ref Modem; C: ref Socket; S: ref Screen; T: ref Terminal; Modules: array of ref Module; init(ctxt: ref Draw->Context, argv: list of string) { s: string; netaddr: string = nil; sys = load Sys Sys->PATH; tk = load Tk Tk->PATH; tkclient = load Tkclient Tkclient->PATH; tkclient->init(); draw = load Draw Draw->PATH; dial = load Dial Dial->PATH; stderr = sys->fildes(2); pgrp = sys->pctl(Sys->NEWPGRP|Sys->FORKNS, nil); arg := load Arg Arg->PATH; arg->init(argv); arg->setusage("miniterm [netaddr]"); while((c := arg->opt()) != 0){ case c { 'D' => s = arg->earg(); for(i := 0; i < len s; i++){ c = s[i]; if(c < len debug) debug[c] += 1; } * => arg->usage(); } } argv = arg->argv(); if(len argv > 0) { netaddr = hd argv; argv = tl argv; } if(argv != nil) arg->usage(); arg = nil; # usage: miniterm modem[!init[!number]] # or miniterm tcp!a.b.c.d connect: int; initstr := dialstr := string nil; if(netaddr == nil) netaddr = "tcp!pdc.minitelfr.com!513"; # gateway (nil, words) := sys->tokenize(netaddr, "!"); if(len words == 0) { connect = Direct; words = "modem" :: nil; } if(hd words == "modem") { connect = Direct; words = tl words; if(words != nil) { initstr = hd words; words = tl words; if(words != nil) dialstr = hd words; } if(initstr == "*") initstr = nil; if(dialstr == "*") dialstr = nil; } else { connect = Network; dialstr = netaddr; } T = ref Terminal; K = ref Keyb; M = ref Modem; C = ref Socket; S = ref Screen; Modules = array [Nmodule] of { Mscreen => S.m, Mmodem => M.m, Mkeyb => K.m, Msocket => C.m, }; toplevel := tk->toplevel(ctxt.display, ""); inittk(toplevel, connect); T.init(toplevel, connect); K.init(toplevel); M.init(connect, initstr, dialstr); C.init(); case connect { Direct => S.init(ctxt, Rect((0,0), (640,425)), Rect((0,0), (640,425))); Network => S.init(ctxt, Rect((0,0), (596,440)), Rect((0,50), (640,350))); } done := chan of int; spawn K.run(); spawn M.run(); spawn C.run(); spawn S.run(); spawn T.run(done); <- done; # now tidy up K.quit(); M.quit(); C.quit(); S.quit(); T.quit(); } # the keyboard module handles keypresses and focus BTN40x25: con "-height 24 -font {/fonts/lucidasans/unicode.6.font}"; BTNCTL: con "-width 60 -height 20 -font {/fonts/lucidasans/unicode.7.font}"; BTNMAIN: con "-width 80 -height 20 -font {/fonts/lucidasans/unicode.7.font}"; tkinitbs := array[] of { "button .cxfin -text {Cx/Fin} -command {send keyb skey Connect}", "button .done -text {Quitter} -command {send keyb skey Exit}", "button .hup -text {Raccr.} -command {send term hangup}", "button .somm -text {Somm.} -command {send keyb skey Index}", "button .guide -text {Guide} -command {send keyb skey Guide}", "button .annul -text {Annul.} -command {send keyb skey Cancel}", "button .corr -text {Corr.} -command {send keyb skey Correct}", "button .retour -text {Retour} -command {send keyb skey Previous}", "button .suite -text {Suite} -command {send keyb skey Next}", "button .repet -text {Répét.} -command {send keyb skey Repeat}", "button .envoi -text {Envoi} -command {send keyb skey Send}", "button .play -text {P} -command {send term play}", # "button .db -text {D} -command {send term debug}" , "button .kb -text {Clavier} -command {send term keyboard}", "button .move -text {<-} -command {send term buttonsleft} " + BTN40x25, }; tkinitdirect := array [] of { ". configure -background black -height 480 -width 640", ".cxfin configure " + BTNCTL, ".hup configure " + BTNCTL, ".done configure " + BTNCTL, ".somm configure " + BTNMAIN, ".guide configure " + BTNMAIN, ".annul configure " + BTNMAIN, ".corr configure " + BTNMAIN, ".retour configure " + BTNMAIN, ".suite configure " + BTNMAIN, ".repet configure " + BTNMAIN, ".envoi configure " + BTNMAIN, # ".play configure " + BTNCTL, # ".db configure " + BTNCTL, ".kb configure " + BTNCTL, "canvas .c -height 425 -width 640 -background black", "bind .c <Configure> {send term resize}", "bind .c <Key> {send keyb key %K}", "bind .c <FocusIn> {send keyb focusin}", "bind .c <FocusOut> {send keyb focusout}", "bind .c <ButtonRelease> {focus .c; send keyb click %x %y}", "frame .k -height 55 -width 640 -background black", "pack propagate .k no", "frame .klhs -background black", "frame .krhs -background black", "frame .krows -background black", "frame .k1 -background black", "frame .k2 -background black", "pack .cxfin -in .klhs -anchor w -pady 4", "pack .hup -in .klhs -anchor w", "pack .somm .annul .retour .repet -in .k1 -side left -padx 2", "pack .guide .corr .suite .envoi -in .k2 -side left -padx 2", "pack .kb -in .krhs -anchor e -pady 4", "pack .done -in .krhs -anchor e", "pack .k1 -in .krows -pady 4", "pack .k2 -in .krows", "pack .klhs .krows .krhs -in .k -side left -expand 1 -fill x", "pack .c .k", "focus .c", "update", }; tkinitip := array [] of { ". configure -background black -height 440 -width 640", # ip 40x25 mode support "canvas .c40 -height 440 -width 596 -background black", "bind .c40 <Configure> {send term resize}", "bind .c40 <Key> {send keyb key %K}", "bind .c40 <FocusIn> {send keyb focusin}", "bind .c40 <FocusOut> {send keyb focusout}", "bind .c40 <ButtonRelease> {focus .c40; send keyb click %x %y}", "frame .k -height 427 -width 44 -background black", "frame .gap1 -background black", "frame .gap2 -background black", "pack propagate .k no", # ip 80x25 mode support "frame .padtop -height 50", "canvas .c80 -height 300 -width 640 -background black", "bind .c80 <Configure> {send term resize}", "bind .c80 <Key> {send keyb key %K}", "bind .c80 <FocusIn> {send keyb focusin}", "bind .c80 <FocusOut> {send keyb focusout}", "bind .c80 <ButtonRelease> {focus .c80; send keyb click %x %y}", "frame .k80 -height 90 -width 640 -background black", "pack propagate .k80 no", "frame .klhs -background black", "frame .krows -background black", "frame .krow1 -background black", "frame .krow2 -background black", "frame .krhs -background black", "pack .krow1 .krow2 -in .krows -pady 2", "pack .klhs -in .k80 -side left", "pack .krows -in .k80 -side left -expand 1", "pack .krhs -in .k80 -side left", }; tkip40x25show := array [] of { ".cxfin configure " + BTN40x25, ".hup configure " + BTN40x25, ".done configure " + BTN40x25, ".somm configure " + BTN40x25, ".guide configure " + BTN40x25, ".annul configure " + BTN40x25, ".corr configure " + BTN40x25, ".retour configure " + BTN40x25, ".suite configure " + BTN40x25, ".repet configure " + BTN40x25, ".envoi configure " + BTN40x25, ".play configure " + BTN40x25, # ".db configure " + BTN40x25, ".kb configure " + BTN40x25, "pack .cxfin -in .k -side top -fill x", "pack .gap1 -in .k -side top -expand 1", "pack .guide .repet .somm .annul .corr .retour .suite .envoi -in .k -side top -fill x", "pack .gap2 -in .k -side top -expand 1", "pack .done .hup .kb .move -in .k -side bottom -pady 2 -fill x", # "pack .db -in .k -side bottom", }; tkip40x25lhs := array [] of { ".move configure -text {->} -command {send term buttonsright}", "pack .k .c40 -side left", "focus .c40", "update", }; tkip40x25rhs := array [] of { ".move configure -text {<-} -command {send term buttonsleft}", "pack .c40 .k -side left", "focus .c40", "update", }; tkip40x25hide := array [] of { "pack forget .k .c40", }; tkip80x25show := array [] of { ".cxfin configure " + BTNCTL, ".hup configure " + BTNCTL, ".done configure " + BTNCTL, ".somm configure " + BTNMAIN, ".guide configure " + BTNMAIN, ".annul configure " + BTNMAIN, ".corr configure " + BTNMAIN, ".retour configure " + BTNMAIN, ".suite configure " + BTNMAIN, ".repet configure " + BTNMAIN, ".envoi configure " + BTNMAIN, # ".play configure " + BTNCTL, # ".db configure " + BTNCTL, ".kb configure " + BTNCTL, "pack .cxfin .hup -in .klhs -anchor w -pady 2", "pack .somm .annul .retour .repet -in .krow1 -side left -padx 2", "pack .guide .corr .suite .envoi -in .krow2 -side left -padx 2", "pack .done .kb -in .krhs -anchor e -pady 2", "pack .padtop .c80 .k80 -side top", "focus .c80", "update", }; tkip80x25hide := array [] of { "pack forget .padtop .c80 .k80", }; inittk(toplevel: ref Tk->Toplevel, connect: int) { tkcmds(toplevel, tkinitbs); if(connect == Direct) tkcmds(toplevel, tkinitdirect); else tkcmds(toplevel, tkinitip); } Terminal.layout(t: self ref Terminal, cols: int) { if(t.connect == Direct) return; if(cols == 80) { tkcmds(t.toplevel, tkip40x25hide); tkcmds(t.toplevel, tkip80x25show); } else { tkcmds(t.toplevel, tkip80x25hide); tkcmds(t.toplevel, tkip40x25show); if (t.buttonsleft) tkcmds(t.toplevel, tkip40x25lhs); else tkcmds(t.toplevel, tkip40x25rhs); } } Terminal.init(t: self ref Terminal, toplevel: ref Tk->Toplevel, connect: int) { t.in = chan of ref Event; t.proto = array [Nmodule] of { Mscreen => ref PState(PSstart, array [] of {0,0,0}, 0, 0, 0), Mmodem => ref PState(PSstart, array [] of {0,0,0}, 0, 0, 0), Mkeyb => ref PState(PSstart, array [] of {0,0,0}, 0, 0, 0), Msocket => ref PState(PSstart, array [] of {0,0,0}, 0, 0, 0), }; t.toplevel = toplevel; t.connect = connect; if (t.connect == Direct) t.spec = 0; else t.spec = Echo; t.cmd = chan of string; tk->namechan(t.toplevel, t.cmd, "term"); # Tk -> terminal t.state = Local; t.buttonsleft = 0; t.kbctl = nil; t.kbmode = "minitel"; t.reset(); } Terminal.reset(t: self ref Terminal) { t.mode = Videotex; } Terminal.run(t: self ref Terminal, done: chan of int) { t.out = array [Nmodule] of { Mscreen => ref BufChan(Pscreen, nil, nil, S.in, array [0] of ref Event), Mmodem => ref BufChan(Pmodem, nil, nil, M.in, array [0] of ref Event), Mkeyb => ref BufChan(Pkeyb, nil, nil, K.in, array [0] of ref Event), Msocket => ref BufChan(Psocket, nil, nil, C.in, array [0] of ref Event), }; modcount := Nmodule; if(debug['P']) post(ref Event.Eproto(Pmodem, 0, Cplay, "play", 0,0,0)); Evloop: for(;;) { ev: ref Event = nil; post(nil); alt { # recv message from one of the modules ev =<- t.in => if(ev == nil) { # modules ack Equit with nil if(--modcount == 0) break Evloop; continue; } pick e := ev { Equit => # close modules down post(ref Event.Equit(Pscreen|Pmodem|Pkeyb|Psocket,0)); continue; } eva := protocol(ev); while(len eva > 0) { post(eva[0]); eva = eva[1:]; } # send message to `plumbed' modules t.out[Mscreen].ch <- = t.out[Mscreen].ev => t.out[Mscreen].ev = nil; t.out[Mmodem].ch <- = t.out[Mmodem].ev => t.out[Mmodem].ev = nil; t.out[Mkeyb].ch <- = t.out[Mkeyb].ev => t.out[Mkeyb].ev = nil; t.out[Msocket].ch <- = t.out[Msocket].ev => t.out[Msocket].ev = nil; # recv message from Tk cmd := <- t.cmd => (n, word) := sys->tokenize(cmd, " "); if(n >0) case hd word { "resize" => ; "play" => # for testing only post(ref Event.Eproto(Pmodem, Mmodem, Cplay, "play", 0,0,0)); "keyboard" => if (t.kbctl == nil) { e: string; (e, t.kbctl) = kb(t); if (e != nil) sys->print("cannot start keyboard: %s\n", e); } else t.kbctl <- = "click"; "hangup" => if(T.state == Online || T.state == Connecting) post(ref Event.Eproto(Pmodem, 0, Cdisconnect, "",0,0,0)); "buttonsleft" => tkcmds(t.toplevel, tkip40x25lhs); t.buttonsleft = 1; if(S.image != nil) draw->(S.image.origin)(Point(0,0), Point(44, 0)); if (t.kbctl != nil) t.kbctl <- = "fg"; "buttonsright" => tkcmds(t.toplevel, tkip40x25rhs); t.buttonsleft = 0; if(S.image != nil) draw->(S.image.origin)(Point(0,0), Point(0, 0)); if (t.kbctl != nil) t.kbctl <- = "fg"; "debug" => debug['s'] ^= 1; debug['m'] ^= 1; } } } if (t.kbctl != nil) t.kbctl <- = "quit"; t.kbctl = nil; done <-= 0; } kb(t: ref Terminal): (string, chan of string) { s := chan of string; spawn dokb(t, s); e := <- s; if (e != nil) return (e, nil); return (nil, s); } Terminal.setkbmode(t: self ref Terminal, tmode: int) { case tmode { Videotex => t.kbmode = "minitel"; Mixed or Ascii => t.kbmode = "standard"; } if(t.kbctl != nil) { t.kbctl <-= "mode"; t.kbctl <-= "fg"; } } include "swkeyb.m"; dokb(t: ref Terminal, c: chan of string) { keyboard := load Keyboard Keyboard->PATH; if (keyboard == nil) { c <- = "cannot load keyboard"; return; } kbctl := chan of string; (top, m) := tkclient->toplevel(S.ctxt, "", "Keyboard", 0); tk->cmd(top, "pack .Wm_t -fill x"); tk->cmd(top, "update"); keyboard->chaninit(top, S.ctxt, ".keys", kbctl); tk->cmd(top, "pack .keys"); kbctl <-= t.kbmode ; kbon := 1; c <- = nil; # all ok, we are now ready to accept commands for (;;) alt { mcmd := <- m => if (mcmd == "exit") { if (kbon) { tk->cmd(top, ". unmap; update"); kbon = 0; } } else tkclient->wmctl(top, mcmd); kbcmd := <- c => case kbcmd { "fg" => if (kbon) tk->cmd(top, "raise .;update"); "click" => if (kbon) { tk->cmd(top, ". unmap; update"); kbon = 0; } else { tk->cmd(top, ". map; raise ."); kbon = 1; } "mode" => kbctl <- = t.kbmode; "quit" => kbctl <- = "kill"; top = nil; # ensure tkclient not blocked on a send to us (probably overkill!) alt { <- m => ; * => ; } return; } } } Terminal.quit(nil: self ref Terminal) { } # a minitel module sends an event to the terminal for routing send(e: ref Event) { if(debug['e'] && e != nil) fprint(stderr, "%s: -> %s\n", Modname[e.from], e.str()); T.in <- = e; } # post an event to one or more modules post(e: ref Event) { i,l: int; for(i=0; i<Nmodule; i++) { # `ev' is cleared once sent, reload it from the front of `q' b: ref BufChan = T.out[i]; l = len b.q; if(b.ev == nil && l != 0) { b.ev = b.q[0]; na := array [l-1] of ref Event; na[0:] = b.q[1:]; b.q = na; } if (e != nil) { if(e.path & b.path) { if(debug['e'] > 0) { pick de := e { * => fprint(stderr, "[%s<-%s] %s\n", Modname[i], Modname[e.from], e.str()); } } if(b.ev == nil) # nothing queued b.ev = e; else { # enqueue it l = len b.q; na := array [l+1] of ref Event; na[0:] = b.q[0:]; na[l] = e; b.q = na; } } } # set a dummy channel if nothing to send if(b.ev == nil) b.ch = chan of ref Event; else b.ch = b.in; } } # run the terminal protocol protocol(ev: ref Event): array of ref Event { # Introduced by the following sequences, the minitel protocol can be # embedded in any normal data sequence # ESC,0x39,X # ESC,0x3a,X,Y # ESC,0x3b,X,Y,Z # ESC,0x61 - cursor position request ea := array [0] of ref Event; # resulting sequence of Events changed := 0; # if set, results are found in `ea' pick e := ev { Edata => d0 := 0; # offset of start of last data sequence p := T.proto[e.from]; for(i:=0; i<len e.data; i++) { ch := int e.data[i]; # if(debug['p']) # fprint(stderr, "protocol: [%s] %d %ux (%c)\n", Modname[e.from], p.state, ch, ch); if(p.skip > 0) { # in transparency mode if(ch == 0 && e.from == Mmodem) # 5.0 continue; p.skip--; continue; } case p.state { PSstart => if(ch == ESC) { p.state = PSesc; changed = 1; if(i > d0) ea = eappend(ea, ref Event.Edata(e.path, e.from, e.data[d0:i])); d0 = i+1; } PSesc => p.state = PSarg; p.n = 0; d0 = i+1; changed = 1; if(ch >= 16r39 && ch <= 16r3b) #PRO1,2,3 p.nargs = ch - 16r39 + 1; else if(ch == 16r61) # cursor position request p.nargs = 0; else if(ch == ESC) { ea = eappend(ea, ref Event.Edata(e.path, e.from, array [] of { byte ESC })); p.state = PSesc; } else { # false alarm, restore as data ea = eappend(ea, ref Event.Edata(e.path, e.from, array [] of { byte ESC, byte ch })); p.state = PSstart; } PSarg => # expect `nargs' bytes d0 = i+1; changed =1; if(p.n < p.nargs) p.arg[p.n++] = ch; if(p.n == p.nargs) { # got complete protocol sequence pe := proto(e.from, p); if(pe != nil) ea = eappend(ea, pe); p.state = PSstart; } } } if(changed) { # some interpretation, results in `ea' if(i > d0) ea = eappend(ea, ref Event.Edata(e.path, e.from, e.data[d0:i])); return ea; } ev = e; return array [] of {ev}; } return array [] of {ev}; } # append to an Event array eappend(ea: array of ref Event, e: ref Event): array of ref Event { l := len ea; na := array [l+1] of ref Event; na[0:] = ea[0:]; na[l] = e; return na; } # act on a received protocol sequence # some sequences are handled here by the terminal and result in a posted reply # others are returned `inline' as Eproto events with the normal data stream. proto(from: int, p: ref PState): ref Event { if(debug['p']) { fprint(stderr, "PRO%d: %ux", p.nargs, p.arg[0]); if(p.nargs > 1) fprint(stderr, " %ux", p.arg[1]); if(p.nargs > 2) fprint(stderr, " %ux", p.arg[2]); fprint(stderr, " (%s)\n", Modname[from]); } case p.nargs { 0 => # cursor position request ESC 0x61 reply := array [] of { byte US, byte S.pos.y, byte S.pos.x }; post(ref Event.Edata(Pmodem, from, reply)); 1 => case p.arg[0] { PROTOCOLSTATUS => ; ENQROM => # identification request post(ref Event.Edata(Pmodem, from, T.terminalid)); if(T.terminalid == TERMINALID1) T.terminalid = TERMINALID2; SETRAM1 or SETRAM2 => ; FUNCTIONINGSTATUS => # 11.3 PRO2(Pmodem, from, REPFUNCTIONINGSTATUS, osb()); CONNECT => ; DISCONNECT => return ref Event.Eproto(Pscreen, from, Cscreenoff, "",0,0,0); RESET => # reset the minitel terminal all := Pscreen|Pmodem|Pkeyb|Psocket; post(ref Event.Eproto(all, from, Creset, "",0,0,0)); # check T.reset(); reply := array [] of { byte SEP, byte 16r5E }; post(ref Event.Edata(Pmodem, from, reply)); } 2 => case p.arg[0] { TO => # request for module status PRO3(Pmodem, from, FROM, p.arg[1], psb(p.arg[1])); NOBROADCAST => ; BROADCAST => ; TRANSPARENCY => # transparency mode - skip bytes p.skip = p.arg[1]; if(p.skip < 1 || p.skip > 127) # 5.0 p.skip = 0; else { reply := array [] of { byte SEP, byte 16r57 }; post(ref Event.Edata(Pmodem, from, reply)); } KEYBOARDSTATUS => if(p.arg[1] == RxKeyb) PRO3(Pmodem, from, REPKEYBOARDSTATUS, RxKeyb, kosb()); START => x := osb(); if(p.arg[1] == PROCEDURE) x |= 16r04; if(p.arg[1] == SCROLLING) x |= 16r02; PRO2(Pmodem, from, REPFUNCTIONINGSTATUS, x); case p.arg[1] { PROCEDURE => # activate error correction procedure sys->print("activate error correction\n"); return ref Event.Eproto(Pmodem, from, Cstartecp, "",0,0,0); SCROLLING => # set screen to scroll return ref Event.Eproto(Pscreen, from, Cproto, "",START,SCROLLING,0); LOWERCASE => # set keyb to invert case return ref Event.Eproto(Pkeyb, from, Cproto, "",START,LOWERCASE,0); } STOP => x := osb(); if(p.arg[1] == SCROLLING) x &= ~16r02; PRO2(Pmodem, from, REPFUNCTIONINGSTATUS, osb()); case p.arg[1] { PROCEDURE => # deactivate error correction procedure sys->print("deactivate error correction\n"); return ref Event.Eproto(Pmodem, from, Cstopecp, "",0,0,0); SCROLLING => # set screen to no scroll return ref Event.Eproto(Pscreen, from, Cproto, "",STOP,SCROLLING,0); LOWERCASE => # set keyb to not invert case return ref Event.Eproto(Pkeyb, from, Cproto, "",STOP,LOWERCASE,0); } COPY => # copy screen to socket # not implemented ; MIXED => # change video mode (12.1) case p.arg[1] { MIXED1 => # videotex -> mixed reply := array [] of { byte SEP, byte 16r70 }; return ref Event.Eproto(Pscreen, from, Cproto, "",MIXED,MIXED1,0); MIXED2 => # mixed -> videotex reply := array [] of { byte SEP, byte 16r71 }; return ref Event.Eproto(Pscreen, from, Cproto, "",MIXED,MIXED2,0); } ASCII => # change video mode (12.2) # TODO ; } 3 => case p.arg[0] { OFF or ON => # link, unlink, enable, disable modcmd(p.arg[0], p.arg[1], p.arg[2]); PRO3(Pmodem, from, FROM, p.arg[1], psb(TxCode(p.arg[1]))); START => case p.arg[1] { RxKeyb => # keyboard mode case p.arg[2] { ETEN => # extended keyboard K.spec |= Extend; C0 => # cursor control key coding from col 0 K.spec |= C0keys; } PRO3(Pmodem, from, REPKEYBOARDSTATUS, RxKeyb, kosb()); } STOP => # keyboard mode case p.arg[1] { RxKeyb => # keyboard mode case p.arg[2] { ETEN => # extended keyboard K.spec &= ~Extend; C0 => # cursor control key coding from col 0 K.spec &= ~C0keys; } PRO3(Pmodem, from, REPKEYBOARDSTATUS, RxKeyb, kosb()); } } } return nil; } # post a PRO3 sequence to all modules on `path' PRO3(path, from, x, y, z: int) { data := array [] of { byte ESC, byte 16r3b, byte x, byte y, byte z}; post(ref Event.Edata(path, from, data)); } # post a PRO2 sequence to all modules on `path' PRO2(path, from, x, y: int) { data := array [] of { byte ESC, byte 16r3a, byte x, byte y}; post(ref Event.Edata(path, from, data)); } # post a PRO1 sequence to all modules on `path' PRO1(path, from, x: int) { data := array [] of { byte ESC, byte 16r39, byte x}; post(ref Event.Edata(path, from, data)); } # make or break links between modules, or enable and disable modcmd(cmd, from, targ: int) { from = RxTx(from); targ = RxTx(targ); if(from == targ) # enable or disable module if(cmd == ON) Modules[from].disabled = 0; else Modules[from].disabled = 1; else # modify path if(cmd == ON) Modules[from].path |= (1<<targ); else Modules[from].path &= ~(1<<targ); } # determine the path status byte (3.4) # if bit 3 of `code' is set then a receive path status byte is returned # otherwise a transmit path status byte psb(code: int): int { this := RxTx(code); b := 16r40; # bit 6 always set if(code == RxCode(code)) { # want a receive path status byte mask := (1<<this); if(Modules[Mscreen].path & mask) b |= 16r01; if(Modules[Mkeyb].path & mask) b |= 16r02; if(Modules[Mmodem].path & mask) b |= 16r04; if(Modules[Msocket].path & mask) b |= 16r08; } else { mod := Modules[this]; if(mod.path & Mscreen) b |= 16r01; if(mod.path & Mkeyb) b |= 16r02; if(mod.path & Mmodem) b |= 16r04; if(mod.path & Msocket) b |= 16r08; } # if(parity(b)) # b ^= 16r80; return b; } # convert `code' to a receive code by setting bit 3 RxCode(code: int): int { return (code | 16r08)&16rff; } # covert `code' to a send code by clearing bit 3 TxCode(code: int): int { return (code & ~16r08)&16rff; } # return 0 on even parity, 1 otherwise # only the bottom 8 bits are considered parity(b: int): int { bits := 8; p := 0; while(bits-- > 0) { if(b&1) p ^= 1; b >>= 1; } return p; } # convert Rx or Tx code to a module code RxTx(code: int): int { rv := 0; case code { TxScreen or RxScreen => rv = Mscreen; TxKeyb or RxKeyb => rv = Mkeyb; TxModem or RxModem => rv = Mmodem; TxSocket or RxSocket => rv = Msocket; * => fatal("invalid module code"); } return rv; } # generate an operating status byte (11.2) osb(): int { b := 16r40; if(S.cols == 80) b |= 16r01; if(S.spec & Scroll) b |= 16r02; if(M.spec & Ecp) b |= 16r04; if(K.spec & Invert) b |= 16r08; # if(parity(b)) # b ^= 16r80; return b; } # generate a keyboard operating status byte (9.1.2) kosb(): int { b := 16r40; if(K.spec & Extend) b |= 16r01; if(K.spec & C0keys) b |= 16r04; # if(parity(b)) # b ^= 16r80; return b; } hex(v, n: int): string { return sprint("%.*ux", n, v); } tostr(ch: int): string { str := ""; str[0] = ch; return str; } toint(s: string, base: int): (int, string) { if(base < 0 || base > 36) return (0, s); c := 0; for(i := 0; i < len s; i++) { c = s[i]; if(c != ' ' && c != '\t' && c != '\n') break; } neg := 0; if(c == '+' || c == '-') { if(c == '-') neg = 1; i++; } ok := 0; n := 0; for(; i < len s; i++) { c = s[i]; v := base; case c { 'a' to 'z' => v = c - 'a' + 10; 'A' to 'Z' => v = c - 'A' + 10; '0' to '9' => v = c - '0'; } if(v >= base) break; ok = 1; n = n * base + v; } if(!ok) return (0, s); if(neg) n = -n; return (n, s[i:]); } tolower(s: string): string { r := s; for(i := 0; i < len r; i++) { c := r[i]; if(c >= int 'A' && c <= int 'Z') r[i] = r[i] + (int 'a' - int 'A'); } return r; } # duplicate `ch' exactly `n' times dup(ch, n: int): string { str := ""; for(i:=0; i<n; i++) str[i] = ch; return str; } fatal(msg: string) { fprint(stderr, "fatal: %s\n", msg); exits(msg); } exits(s: string) { if(s==nil); # raise "fail: miniterm " + s; fd := sys->open("#p/" + string pgrp + "/ctl", sys->OWRITE); if(fd != nil) sys->fprint(fd, "killgrp"); exit; } # Minitel byte MSB and LSB classification (p.87) MSB(ch: int): int { return (ch&16r70)>>4; } LSB(ch: int): int { return (ch&16r0f); } # Minitel character set classification (p.92) ISC0(ch: int): int { msb := (ch&16r70)>>4; return msb == 0 || msb == 1; } ISC1(ch: int): int { return ch >= 16r40 && ch <= 16r5f; } ISG0(ch: int): int { # 0x20 (space) and 0x7f (DEL) are not in G0 return ch > 16r20 && ch < 16r7f; } tkcmds(t: ref Tk->Toplevel, cmds: array of string) { n := len cmds; for (ix := 0; ix < n; ix++) tk->cmd(t, cmds[ix]); }