xref: /inferno-os/appl/wm/keyboard.b (revision 37da2899f40661e3e9631e497da8dc59b971cbd0)
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