ref: 7ae7351ace73a70cbbaf5a2da016005485d0d43b
dir: /appl/examples/minitel/keyb.b/
# # Copyright © 1998 Vita Nuova Limited. All rights reserved. # # special keyboard operations Extend, # enable cursor and editing keys and control chars C0keys, # cursor keys send BS,HT,LF and VT Invert # case inversion : con 1 << iota; Keyb: adt { m: ref Module; # common attributes in: chan of ref Event; cmd: chan of string; # from Tk (keypresses and focus) spec: int; # special keyboard extensions init: fn(k: self ref Keyb, toplevel: ref Tk->Toplevel); reset: fn(k: self ref Keyb); run: fn(k: self ref Keyb); quit: fn(k: self ref Keyb); map: fn(k: self ref Keyb, key:int): array of byte; }; Keyb.init(k: self ref Keyb, toplevel: ref Tk->Toplevel) { k.in = chan of ref Event; k.cmd = chan of string; tk->namechan(toplevel, k.cmd, "keyb"); # Tk -> keyboard k.reset(); } Keyb.reset(k: self ref Keyb) { k.m = ref Module(Pmodem|Psocket, 0); } ask(in: chan of string, out: chan of string) { keys: string; T.mode = Videotex; S.setmode(Videotex); # clear(S); prompt: con "Numéroter: "; number := M.lastdialstr; S.msg(prompt); Input: for(;;) { n := len prompt + len number; # guard length must be > len prompt if (n > 30) n -= 30; else n = 0; S.msg(prompt + number[n:]); keys = <- in; if (keys == nil) return; keys = canoncmd(keys); case keys { "connect" or "send" => break Input; "correct" => if(len number > 0) number = number[0: len number -1]; "cancel" => number = ""; break Input; "repeat" or "index" or "guide" or "next" or "previous" => ; * => number += keys; } } S.msg(nil); for (;;) alt { out <- = number => return; keys = <- in => if (keys == nil) return; } } Keyb.run(k: self ref Keyb) { dontask := chan of string; askchan := dontask; askkeys := chan of string; Runloop: for(;;){ alt { ev := <- k.in => pick e := ev { Equit => break Runloop; Eproto => case e.cmd { Creset => k.reset(); Cproto => case e.a0 { START => case e.a1 { LOWERCASE => k.spec |= Invert; } STOP => case e.a1 { LOWERCASE => k.spec &= ~Invert; } } * => break; } } cmd := <- k.cmd => if(debug['k'] > 0) { fprint(stderr, "Tk %s\n", cmd); } (n, args) := sys->tokenize(cmd, " "); if(n >0) case hd args { "key" => (key, nil) := toint(hd tl args, 16); if(askchan != dontask) { s := minikey(key); if (s == nil) s[0] = key; askkeys <-= s; break; } keys := k.map(key); if(keys != nil) { send(ref Event.Edata(k.m.path, Mkeyb, keys)); } "skey" => # minitel key hit (soft key) if(hd tl args == "Exit") { if(askchan != dontask) { askchan = dontask; askkeys <-= nil; } if(T.state == Online || T.state == Connecting) { seq := keyseq("connect"); if(seq != nil) { send(ref Event.Edata(k.m.path, Mkeyb, seq)); send(ref Event.Edata(k.m.path, Mkeyb, seq)); } send(ref Event.Eproto(Pmodem, Mkeyb, Cdisconnect, "", 0,0,0)); } send(ref Event.Equit(0, 0)); break; } if(askchan != dontask) { askkeys <-= hd tl args; break; } case hd tl args { "Connect" => case T.state { Local => if(M.connect == Network) send(ref Event.Eproto(Pmodem, Mkeyb, Cconnect, "", 0,0,0)); else { askchan = chan of string; spawn ask(askkeys, askchan); } Connecting => send(ref Event.Eproto(Pmodem, Mkeyb, Cdisconnect, "", 0,0,0)); Online => seq := keyseq("connect"); if(seq != nil) send(ref Event.Edata(k.m.path, Mkeyb, seq)); } * => seq := keyseq(hd tl args); if(seq != nil) send(ref Event.Edata(k.m.path, Mkeyb, seq)); } "click" => # fetch a word from the display x := int hd tl args; y := int hd tl tl args; word := disp->GetWord(Point(x, y)); if(word != nil) { if (askchan != dontask) { askkeys <- = word; break; } if (T.state == Local) { if (canoncmd(word) == "connect") { if(M.connect == Network) send(ref Event.Eproto(Pmodem, Mkeyb, Cconnect, "", 0,0,0)); else { askchan = chan of string; spawn ask(askkeys, askchan); } break; } } seq := keyseq(word); if(seq != nil) send(ref Event.Edata(k.m.path, Mkeyb, seq)); else { send(ref Event.Edata(k.m.path, Mkeyb, array of byte word )); send(ref Event.Edata(k.m.path, Mkeyb, keyseq("send"))); } } } dialstr := <-askchan => askchan = dontask; if(dialstr != nil) { M.dialstr = dialstr; send(ref Event.Eproto(Pmodem, Mkeyb, Cconnect, "", 0,0,0)); } } } send(nil); } # Perform mode specific key translation # returns nil on invalid keypress, Keyb.map(nil: self ref Keyb, key: int): array of byte { # hardware to minitel keyboard mapping cmd := minikey(key); if (cmd != nil) { seq := keyseq(cmd); if(seq != nil) return seq; } # alphabetic (with case mapping) case T.mode { Videotex => if(key >= 'A' && key <= 'Z') return array [] of { byte ('a' + (key - 'A'))}; if(key >= 'a' && key <= 'z') return array [] of {byte ('A' + (key - 'a'))}; Mixed or Ascii => if(key >= 'A' && key <= 'Z' || key >= 'a' && key <= 'z') return array [] of {byte key}; }; # Numeric if(key >= '0' && key <= '9') return array [] of {byte key}; # Control-A -> Control-Z, Esc - columns 0 and 1 if(key >= 16r00 && key <=16r1f) case T.mode { Videotex => return nil; Mixed or Ascii => return array [] of {byte key}; } # miscellaneous key mapping case key { 16r20 => ; # space 16ra3 => return array [] of { byte 16r19, byte 16r23 }; # pound '!' or '"' or '#' or '$' or '%' or '&' or '\'' or '(' or ')' or '*' or '+' or ',' or '-' or '.' or ':' or ';' or '<' or '=' or '>' or '?' or '@' => ; KF13 => # request for error correction - usually Fnct M + C if((M.spec&Ecp) == 0 && T.state == Online && T.connect == Direct) { fprint(stderr, "requesting Ecp\n"); return array [] of { byte SEP, byte 16r4a }; } return nil; * => return nil; } return array [] of {byte key}; } Keyb.quit(k: self ref Keyb) { if(k==nil); } canoncmd(s : string) : string { s = tolower(s); case s { "connect" or "cx/fin" or "connexion" or "fin" => return "connect"; "send" or "envoi" => return "send"; "repeat" or "repetition" => return "repeat"; "index" or "sommaire" or "somm" => return "index"; "guide" => return "guide"; "correct" or "correction" => return "correct"; "cancel" or "annulation" or "annul" or "annu" => return "cancel"; "next" or "suite" => return "next"; "previous" or "retour" or "retou" => return "previous"; } return s; } # map softkey names to the appropriate byte sequences keyseq(skey: string): array of byte { b2 := 0; asterisk := 0; if(skey == nil || len skey == 0) return nil; if(skey[0] == '*') { asterisk = 1; skey = skey[1:]; } skey = canoncmd(skey); case skey { "connect" => b2 = 16r49; "send" => b2 = 16r41; "repeat" => b2 = 16r43; "index" => b2 = 16r46; "guide" => b2 = 16r44; "correct" => b2 = 16r47; "cancel" => b2 = 16r45; "next" => b2 = 16r48; "previous" => b2 = 16r42; } if(b2) { if(asterisk) return array [] of { byte '*', byte SEP, byte b2}; else return array [] of { byte SEP, byte b2}; } else return nil; } # map hardware or software keyboard presses to minitel functions minikey(key: int): string { case key { Kup or KupPC => return"previous"; Kdown or KdownPC => return "next"; Kenter => return "send"; Kback => return "correct"; Kesc => return "cancel"; KF1 => return "guide"; KF2 => return "connect"; KF3 => return "repeat"; KF4 => return "index"; * => return nil; } }