1implement Keybd; 2 3# 4# extensive revision of code originally by N. W. Knauft 5# 6# Copyright © 1997 Lucent Technologies Inc. All rights reserved. 7# Revisions Copyright © 1998 Vita Nuova Limited. All rights reserved. 8# Rewritten code Copyright © 2001 Vita Nuova Holdings Limited. All rights reserved. 9# 10# To do: 11# input from file 12# calculate size 13 14include "sys.m"; 15 sys: Sys; 16 17include "draw.m"; 18 draw: Draw; 19 Rect, Point: import draw; 20 21include "tk.m"; 22 tk: Tk; 23 24include "tkclient.m"; 25 tkclient: Tkclient; 26 27include "arg.m"; 28 29include "keyboard.m"; 30 31Keybd: module 32{ 33 init: fn(nil: ref Draw->Context, nil: list of string); 34}; 35 36FONT: con "/fonts/lucidasans/boldlatin1.6.font"; 37SPECFONT: con "/fonts/lucidasans/unicode.6.font"; 38 39# size in pixels 40#KEYSIZE: con 16; 41KEYSIZE: con 13; 42KEYSPACE: con 2; 43KEYBORDER: con 1; 44KEYGAP: con KEYSPACE - (2 * KEYBORDER); 45#ENDGAP: con 2 - KEYBORDER; 46ENDGAP: con 0; 47 48Key: adt { 49 name: string; 50 val: int; 51 size: int; 52 x: list of int; 53 on: int; 54}; 55 56background: con "#dddddd"; 57 58Backspace, Tab, Backslash, CapsLock, Return, Shift, Ctrl, Esc, Alt, Space: con iota; 59 60specials := array[] of { 61Backspace => Key("<-", '\b', 28, nil, 0), 62Tab => Key("Tab", '\t', 26, nil, 0), 63Backslash => Key("\\\\", '\\', KEYSIZE, nil, 0), 64CapsLock => Key("Caps", Keyboard->Caps, 40, nil, 0), 65Return => Key("Enter", '\n', 36, nil, 0), 66Shift => Key("Shift", Keyboard->LShift, 45, nil, 0), 67Esc => Key("Esc", 8r33, 21, nil, 0), 68Ctrl => Key("Ctrl", Keyboard->LCtrl, 36, nil, 0), 69Alt => Key("Alt", Keyboard->LAlt, 22, nil, 0), 70Space => Key(" ", ' ', 140, nil, 0), 71Space+1 => Key("Return", '\n', 36, nil, 0), 72}; 73 74keys:= array[] of { 75 # unshifted 76 array[] of { 77 "Esc", "1", "2", "3", "4", "5", "6", "7", "8", "9", "0", "-", "=", "\\\\", "`", nil, 78 "Tab", "q", "w", "e", "r", "t", "y", "u", "i", "o", "p", "[", "]", "<-", nil, 79 "Ctrl", "a", "s", "d", "f", "g", "h", "j", "k", "l", ";", "'", "Enter", nil, 80 "Shift", "z", "x", "c", "v", "b", "n", "m", ",", ".", "/", "Shift", nil, 81 "Caps", "Alt", " ", "Alt", nil, 82 }, 83 84 # shifted 85 array[] of { 86 "Esc", "!", "@", "#", "$", "%", "^", "&", "*", "(", ")", "_", "+", "|", "~", nil, 87 "Tab", "Q", "W", "E", "R", "T", "Y", "U", "I", "O", "P", "\\{", "\\}", "<-", nil, 88 "Ctrl", "A", "S", "D", "F", "G", "H", "J", "K", "L", ":", "\"", "Return", nil, 89 "Shift", "Z", "X", "C", "V", "B", "N", "M", "<", ">", "?", "Shift", nil, 90 "Caps", "Alt", " ", "Alt", nil, 91 }, 92}; 93 94keyvals: array of array of int; 95noexit := 0; 96 97init(ctxt: ref Draw->Context, args: list of string) 98{ 99 sys = load Sys Sys->PATH; 100 if (ctxt == nil) { 101 sys->fprint(sys->fildes(2), "keyboard: no window context\n"); 102 raise "fail:bad context"; 103 } 104 draw = load Draw Draw->PATH; 105 tk = load Tk Tk->PATH; 106 tkclient = load Tkclient Tkclient->PATH; 107 arg := load Arg Arg->PATH; 108 109 taskbar := 0; 110 winopts := Tkclient->Hide; 111 arg->init(args); 112 while ((opt := arg->opt()) != 0) { 113 case opt { 114 't' => 115 taskbar = 1; 116 'e' => 117 noexit = 1; 118 winopts = 0; 119 * => 120 sys->fprint(sys->fildes(2), "usage: keyboard [-et]\n"); 121 raise "fail:usage"; 122 } 123 } 124 125 sys->pctl(Sys->NEWPGRP, nil); 126 tkclient->init(); 127 128 keyvals = array[] of { 129 array[len keys[0]] of int, 130 array[len keys[1]] of int, 131 }; 132 setindex(keys[0], keyvals[0], specials); 133 setindex(keys[1], keyvals[1], specials); 134 135 136 (t, wcmd) := tkclient->toplevel(ctxt, "", "Kbd", winopts); 137 cmd(t, ". configure -bd 0 -relief flat"); 138 139 for(i := 0; i < len keys[0]; i++) 140 if(keys[0][i] != nil) 141 cmd(t, sys->sprint("button .b%d -takefocus 0 -font %s -width %d -height %d -bd %d -activebackground %s -text {%s} -command 'send keypress %d", 142 i, FONT, KEYSIZE, KEYSIZE, KEYBORDER, background, keys[0][i], keyvals[0][i])); 143 144 for(i = 0; i < len specials; i++) { 145 k := specials[i]; 146 for(xl := k.x; xl != nil; xl = tl xl) 147 cmd(t, sys->sprint(".b%d configure -font %s -width %d", hd xl, SPECFONT, k.size)); 148 } 149 150 # pack buttons in rows 151 i = 0; 152 for(j:=0; i < len keys[0]; j++){ 153 rowf := sys->sprint(".f%d", j); 154 cmd(t, "frame "+rowf); 155 cmd(t, sys->sprint("frame .pad%d -height %d", j, KEYGAP)); 156 if(ENDGAP){ 157 cmd(t, rowf + ".pad -width " + string ENDGAP); 158 cmd(t, "pack " + rowf + ".pad -side left"); 159 } 160 for(; keys[0][i] != nil; i++){ 161 label := keys[0][i]; 162 expand := label != "\\\\" && len label > 1; 163 cmd(t, "pack .b" + string i + " -in "+ rowf + " -side left -fill x -expand "+string expand); 164 if(keys[0][i+1] != nil && KEYGAP > 0){ 165 padf := sys->sprint("%s.pad%d", rowf, i); 166 cmd(t, "frame " + padf + " -width " + string KEYGAP); 167 cmd(t, "pack " + padf + " -side left"); 168 } 169 } 170 if(ENDGAP){ 171 padf := sys->sprint("%s.pad%d", rowf, i); 172 cmd(t, "frame " + padf + " -width " + string ENDGAP); 173 cmd(t, "pack " + padf + " -side left"); 174 } 175 i++; 176 } 177 nrow := j; 178 179 # pack rows in frame 180 for(j = 0; j < nrow; j++) 181 cmd(t, sys->sprint("pack .f%d .pad%d -fill x -in .", j, j)); 182 183 (w, h) := (int cmd(t, ". cget -width"), int cmd(t, ". cget -height")); 184 r := t.screenr; 185 off := (r.dx()-w)/2; 186 cmd(t, sys->sprint(". configure -x %d -y %d", r.min.x+off, r.max.y-h)); 187 tkclient->onscreen(t, nil); 188 tkclient->startinput(t, "ptr" :: nil); 189 190 spawn handle_keyclicks(t, wcmd, taskbar); 191} 192 193setindex(keys: array of string, keyvals: array of int, spec: array of Key) 194{ 195 for(i := 0; i < len keys; i++){ 196 if(keys[i] == nil) 197 continue; 198 val := keys[i][0]; 199 if(len keys[i] > 1 && val == '\\') 200 val = keys[i][1]; 201 for(j := 0; j < len spec; j++) 202 if(spec[j].name == keys[i]){ 203 if(!inlist(i, spec[j].x)) 204 spec[j].x = i :: spec[j].x; 205 val = spec[j].val; 206 break; 207 } 208 keyvals[i] = val; 209 } 210} 211 212inlist(i: int, l: list of int): int 213{ 214 for(; l != nil; l = tl l) 215 if(hd l == i) 216 return 1; 217 return 0; 218} 219 220handle_keyclicks(t: ref Tk->Toplevel, wcmd: chan of string, taskbar: int) 221{ 222 keypress := chan of string; 223 tk->namechan(t, keypress, "keypress"); 224 225 if(taskbar) 226 tkclient->wmctl(t, "task"); 227 228 cmd(t,"update"); 229 230 collecting := 0; 231 collected := ""; 232 for(;;)alt { 233 k := <-keypress => 234 c := int k; 235 case c { 236 Keyboard->Caps => 237 active(t, Ctrl, 0); 238 active(t, Shift, 0); 239 active(t, Alt, 0); 240 active(t, CapsLock, -1); 241 redraw(t); 242 Keyboard->LShift => 243 active(t, Shift, -1); 244 redraw(t); 245 Keyboard->LCtrl => 246 active(t, Alt, 0); 247 active(t, Ctrl, -1); 248 active(t, Shift, 0); 249 redraw(t); 250 Keyboard->LAlt => 251 active(t, Alt, -1); 252 active(t, Ctrl, 0); 253 active(t, Shift, 0); 254 redraw(t); 255 if(specials[Alt].on){ 256 collecting = 1; 257 collected = ""; 258 }else 259 collecting = 0; 260 * => 261 if(collecting){ 262 collected[len collected] = c; 263 c = latin1(collected); 264 if(c < -1) 265 continue; 266 collecting = 0; 267 if(c == -1){ 268 for(i := 0; i < len collected; i++) 269 sendkey(t, collected[i]); 270 continue; 271 } 272 } 273 show := specials[Ctrl].on | specials[Alt].on | specials[Shift].on; 274 if(specials[Ctrl].on) 275 c &= 16r1F; 276 active(t, Ctrl, 0); 277 active(t, Alt, 0); 278 active(t, Shift, 0); 279 if(show) 280 redraw(t); 281 sendkey(t, c); 282 } 283 m := <-t.ctxt.ptr => 284 tk->pointer(t, *m); 285 s := <-t.ctxt.ctl or 286 s = <-t.wreq or 287 s = <-wcmd => 288 if (s == "exit" && noexit) 289 s = "task"; 290 tkclient->wmctl(t, s); 291 } 292} 293 294sendkey(t: ref Tk->Toplevel, c: int) 295{ 296 sys->fprint(t.ctxt.connfd, "key %d", c); 297} 298 299active(t: ref Tk->Toplevel, keyno: int, on: int) 300{ 301 key := specials[keyno:]; 302 if(on < 0) 303 key[0].on ^= 1; 304 else 305 key[0].on = on; 306 for(xl := key[0].x; xl != nil; xl = tl xl){ 307 col := background; 308 if(key[0].on) 309 col = "white"; 310 cmd(t, ".b"+string hd xl+" configure -bg "+col+ " -activebackground "+col); 311 } 312} 313 314redraw(t: ref Tk->Toplevel) 315{ 316 shifted := specials[Shift].on; 317 bank := keys[shifted]; 318 vals := keyvals[shifted]; 319 for(i:=0; i<len bank; i++) { 320 key := bank[i]; 321 val := vals[i]; 322 if(key != nil){ 323 if(specials[CapsLock].on && len key == 1){ 324 if(key[0]>='A' && key[0]<='Z') # true if also shifted 325 key[0] += 'a'-'A'; 326 else if(key[0] >= 'a' && key[0]<='z') 327 key[0] += 'A'-'a'; 328 val = key[0]; 329 } 330 cmd(t, ".b" + string i + " configure -text {" + key + "} -command 'send keypress " + string val); 331 } 332 } 333 cmd(t, "update"); 334} 335 336# 337# The code makes two assumptions: strlen(ld) is 1 or 2; latintab[i].ld can be a 338# prefix of latintab[j].ld only when j<i. 339# 340Cvlist: adt 341{ 342 ld: string; # must be seen before using this conversion 343 si: string; # options for last input characters 344 so: string; # the corresponding Rune for each si entry 345}; 346latintab: array of Cvlist = array[] of { 347 (" ", " i", "␣ı"), 348 ("!~", "-=~", "≄≇≉"), 349 ("!", "!<=>?bmp", "¡≮≠≯‽⊄∉⊅"), 350 ("\"*", "IUiu", "ΪΫϊϋ"), 351 ("\"", "\"AEIOUYaeiouy", "¨ÄËÏÖÜŸäëïöüÿ"), 352 ("$*", "fhk", "ϕϑϰ"), 353 ("$", "BEFHILMRVaefglopv", "ℬℰℱℋℐℒℳℛƲɑℯƒℊℓℴ℘ʋ"), 354 ("\'\"", "Uu", "Ǘǘ"), 355 ("\'", "\'ACEILNORSUYZacegilnorsuyz", "´ÁĆÉÍĹŃÓŔŚÚÝŹáćéģíĺńóŕśúýź"), 356 ("*", "*ABCDEFGHIKLMNOPQRSTUWXYZabcdefghiklmnopqrstuwxyz", "∗ΑΒΞΔΕΦΓΘΙΚΛΜΝΟΠΨΡΣΤΥΩΧΗΖαβξδεφγθικλμνοπψρστυωχηζ"), 357 ("+", "-O", "±⊕"), 358 (",", ",ACEGIKLNORSTUacegiklnorstu", "¸ĄÇĘĢĮĶĻŅǪŖŞŢŲąçęģįķļņǫŗşţų"), 359 ("-*", "l", "ƛ"), 360 ("-", "+-2:>DGHILOTZbdghiltuz~", "∓ƻ÷→ÐǤĦƗŁ⊖ŦƵƀðǥℏɨłŧʉƶ≂"), 361 (".", ".CEGILOZceglz", "·ĊĖĠİĿ⊙Żċėġŀż"), 362 ("/", "Oo", "Øø"), 363 ("1", "234568", "½⅓¼⅕⅙⅛"), 364 ("2", "-35", "ƻ⅔⅖"), 365 ("3", "458", "¾⅗⅜"), 366 ("4", "5", "⅘"), 367 ("5", "68", "⅚⅝"), 368 ("7", "8", "⅞"), 369 (":", ")-=", "☺÷≔"), 370 ("<!", "=~", "≨⋦"), 371 ("<", "-<=>~", "←«≤≶≲"), 372 ("=", ":<=>OV", "≕⋜≡⋝⊜⇒"), 373 (">!", "=~", "≩⋧"), 374 (">", "<=>~", "≷≥»≳"), 375 ("?", "!?", "‽¿"), 376 ("@\'", "\'", "ъ"), 377 ("@@", "\'EKSTYZekstyz", "ьЕКСТЫЗекстыз"), 378 ("@C", "Hh", "ЧЧ"), 379 ("@E", "Hh", "ЭЭ"), 380 ("@K", "Hh", "ХХ"), 381 ("@S", "CHch", "ЩШЩШ"), 382 ("@T", "Ss", "ЦЦ"), 383 ("@Y", "AEOUaeou", "ЯЕЁЮЯЕЁЮ"), 384 ("@Z", "Hh", "ЖЖ"), 385 ("@c", "h", "ч"), 386 ("@e", "h", "э"), 387 ("@k", "h", "х"), 388 ("@s", "ch", "щш"), 389 ("@t", "s", "ц"), 390 ("@y", "aeou", "яеёю"), 391 ("@z", "h", "ж"), 392 ("@", "ABDFGIJLMNOPRUVXabdfgijlmnopruvx", "АБДФГИЙЛМНОПРУВХабдфгийлмнопрувх"), 393 ("A", "E", "Æ"), 394 ("C", "ACU", "⋂ℂ⋃"), 395 ("Dv", "Zz", "DŽDž"), 396 ("D", "-e", "Ð∆"), 397 ("G", "-", "Ǥ"), 398 ("H", "-H", "Ħℍ"), 399 ("I", "-J", "ƗIJ"), 400 ("L", "&-Jj|", "⋀ŁLJLj⋁"), 401 ("N", "JNj", "NJℕNj"), 402 ("O", "*+-./=EIcoprx", "⊛⊕⊖⊙⊘⊜ŒƢ©⊚℗®⊗"), 403 ("P", "P", "ℙ"), 404 ("Q", "Q", "ℚ"), 405 ("R", "R", "ℝ"), 406 ("S", "123S", "¹²³§"), 407 ("T", "-u", "Ŧ⊨"), 408 ("V", "=", "⇐"), 409 ("Y", "R", "Ʀ"), 410 ("Z", "-ACSZ", "Ƶℤ"), 411 ("^", "ACEGHIJOSUWYaceghijosuwy", "ÂĈÊĜĤÎĴÔŜÛŴŶâĉêĝĥîĵôŝûŵŷ"), 412 ("_\"", "AUau", "ǞǕǟǖ"), 413 ("_,", "Oo", "Ǭǭ"), 414 ("_.", "Aa", "Ǡǡ"), 415 ("_", "AEIOU_aeiou", "ĀĒĪŌŪ¯āēīōū"), 416 ("`\"", "Uu", "Ǜǜ"), 417 ("`", "AEIOUaeiou", "ÀÈÌÒÙàèìòù"), 418 ("a", "ben", "↔æ∠"), 419 ("b", "()+-0123456789=bknpqru", "₍₎₊₋₀₁₂₃₄₅₆₇₈₉₌♝♚♞♟♛♜•"), 420 ("c", "$Oagu", "¢©∩≅∪"), 421 ("dv", "z", "dž"), 422 ("d", "-adegz", "ð↓‡°†ʣ"), 423 ("e", "$lmns", "€⋯—–∅"), 424 ("f", "a", "∀"), 425 ("g", "$-r", "¤ǥ∇"), 426 ("h", "-v", "ℏƕ"), 427 ("i", "-bfjps", "ɨ⊆∞ij⊇∫"), 428 ("l", "\"$&\'-jz|", "“£∧‘łlj⋄∨"), 429 ("m", "iou", "µ∈×"), 430 ("n", "jo", "nj¬"), 431 ("o", "AOUaeiu", "Å⊚Ůåœƣů"), 432 ("p", "Odgrt", "℗∂¶∏∝"), 433 ("r", "\"\'O", "”’®"), 434 ("s", "()+-0123456789=abnoprstu", "⁽⁾⁺⁻⁰ⁱ⁴⁵⁶⁷⁸⁹⁼ª⊂ⁿº⊃√ß∍∑"), 435 ("t", "-efmsu", "ŧ∃∴™ς⊢"), 436 ("u", "-AEGIOUaegiou", "ʉĂĔĞĬŎŬ↑ĕğĭŏŭ"), 437 ("v\"", "Uu", "Ǚǚ"), 438 ("v", "ACDEGIKLNORSTUZacdegijklnorstuz", "ǍČĎĚǦǏǨĽŇǑŘŠŤǓŽǎčďěǧǐǰǩľňǒřšťǔž"), 439 ("w", "bknpqr", "♗♔♘♙♕♖"), 440 ("x", "O", "⊗"), 441 ("y", "$", "¥"), 442 ("z", "-", "ƶ"), 443 ("|", "Pp|", "Þþ¦"), 444 ("~!", "=", "≆"), 445 ("~", "-=AINOUainou~", "≃≅ÃĨÑÕŨãĩñõũ≈"), 446}; 447 448# 449# Given 5 characters k[0]..k[4], find the rune or return -1 for failure. 450# 451unicode(k: string): int 452{ 453 c := 0; 454 for(i:=1; i<5; i++){ 455 r := k[i]; 456 c <<= 4; 457 if('0'<=r && r<='9') 458 c += r-'0'; 459 else if('a'<=r && r<='f') 460 c += 10 + r-'a'; 461 else if('A'<=r && r<='F') 462 c += 10 + r-'A'; 463 else 464 return -1; 465 } 466 return c; 467} 468 469# 470# Given n characters k[0]..k[n-1], find the corresponding rune or return -1 for 471# failure, or something < -1 if n is too small. In the latter case, the result 472# is minus the required n. 473# 474latin1(k: string): int 475{ 476 n := len k; 477 if(k[0] == 'X' || n>1 && k[0] == 'x' && k[1]!='O') # 'x' to avoid having to Shift as well 478 if(n>=5) 479 return unicode(k); 480 else 481 return -5; 482 for(i := 0; i < len latintab; i++){ 483 l := latintab[i]; 484 if(k[0] == l.ld[0]){ 485 if(n == 1) 486 return -2; 487 c := 0; 488 if(len l.ld == 1) 489 c = k[1]; 490 else if(l.ld[1] != k[1]) 491 continue; 492 else if(n == 2) 493 return -3; 494 else 495 c = k[2]; 496 for(p:=0; p < len l.si; p++) 497 if(l.si[p] == c && p < len l.so) 498 return l.so[p]; 499 return -1; 500 } 501 } 502 return -1; 503} 504 505cmd(top: ref Tk->Toplevel, c: string): string 506{ 507 e := tk->cmd(top, c); 508 if (e != nil && e[0] == '!') 509 sys->fprint(sys->fildes(2), "keyboard: tk error on '%s': %s\n", c, e); 510 return e; 511} 512