1{
2 system ? builtins.currentSystem,
3 config ? { },
4 pkgs ? import ../.. { inherit system config; },
5}:
6
7with import ../lib/testing-python.nix { inherit system pkgs; };
8
9let
10 readyFile = "/tmp/readerReady";
11 resultFile = "/tmp/readerResult";
12
13 testReader = pkgs.writeScript "test-input-reader" ''
14 rm -f ${resultFile} ${resultFile}.tmp
15 logger "testReader: START: expecting '$1'."
16 touch ${readyFile}
17 read -r -N ''${#1} -t 60 chars
18 rm -f ${readyFile}
19
20 if [ "$chars" == "$1" ]; then
21 logger -s "testReader: PASS: Got '$1' as expected." 2>${resultFile}.tmp
22 else
23 logger -s "testReader: FAIL: Expected '$1' but got '$chars'." 2>${resultFile}.tmp
24 fi
25 # rename after the file is written to prevent a race condition
26 mv ${resultFile}.tmp ${resultFile}
27 '';
28
29 mkKeyboardTest =
30 layout:
31 {
32 extraConfig ? { },
33 tests,
34 }:
35 with pkgs.lib;
36 makeTest {
37 name = "keymap-${layout}";
38
39 nodes.machine.console.keyMap = mkOverride 900 layout;
40 nodes.machine.services.xserver.desktopManager.xterm.enable = false;
41 nodes.machine.services.xserver.xkb.layout = mkOverride 900 layout;
42 nodes.machine.imports = [
43 ./common/x11.nix
44 extraConfig
45 ];
46
47 testScript = ''
48 import json
49 import shlex
50
51
52 def run_test_case(cmd, inputs, expected):
53 assert len(inputs) == len(expected)
54 machine.execute("rm -f ${readyFile} ${resultFile}")
55
56 # set up process that expects all the keys to be entered
57 machine.succeed(
58 "${pkgs.systemd}/bin/systemd-cat -t input-test-reader -- {} {} {} &".format(
59 cmd,
60 "${testReader}",
61 shlex.quote("".join(expected)),
62 )
63 )
64
65 # wait for reader to be ready
66 machine.wait_for_file("${readyFile}")
67
68 # send all keys
69 for key in inputs:
70 machine.send_key(key)
71
72 # wait for result and check
73 machine.wait_for_file("${resultFile}")
74 machine.succeed("grep -q 'PASS:' ${resultFile}")
75
76
77 with open("${pkgs.writeText "tests.json" (builtins.toJSON tests)}") as json_file:
78 tests = json.load(json_file)
79
80 # These environments used to run in the opposite order, causing the
81 # following error at openvt startup.
82 #
83 # openvt: Couldn't deallocate console 1
84 #
85 # This error did not appear in successful runs.
86 # I don't know the exact cause, but I it seems that openvt and X are
87 # fighting over the virtual terminal. This does not appear to be a problem
88 # when the X test runs first.
89 keymap_environments = {
90 "Xorg Keymap": "env DISPLAY=:0 xterm -title testterm -class testterm -fullscreen -e",
91 "VT Keymap": "openvt -c 2 -sw --",
92 }
93
94 machine.wait_for_x()
95
96 for test_case_name, test_data in tests.items():
97 for keymap_env_name, command in keymap_environments.items():
98 with subtest(f"{test_case_name} - {keymap_env_name}"):
99 run_test_case(
100 command,
101 test_data["qwerty"],
102 test_data["expect"],
103 )
104 '';
105 };
106
107in
108pkgs.lib.mapAttrs mkKeyboardTest {
109 azerty = {
110 tests = {
111 azqw.qwerty = [
112 "q"
113 "w"
114 ];
115 azqw.expect = [
116 "a"
117 "z"
118 ];
119 altgr.qwerty = [
120 "alt_r-2"
121 "alt_r-3"
122 "alt_r-4"
123 "alt_r-5"
124 "alt_r-6"
125 ];
126 altgr.expect = [
127 "~"
128 "#"
129 "{"
130 "["
131 "|"
132 ];
133 };
134
135 extraConfig.console.keyMap = "fr";
136 extraConfig.services.xserver.xkb.layout = "fr";
137 };
138
139 bone = {
140 tests = {
141 layer1.qwerty = [
142 "f"
143 "j"
144 ];
145 layer1.expect = [
146 "e"
147 "n"
148 ];
149 layer2.qwerty = [
150 "shift-f"
151 "shift-j"
152 "shift-6"
153 ];
154 layer2.expect = [
155 "E"
156 "N"
157 "$"
158 ];
159 layer3.qwerty = [
160 "caps_lock-d"
161 "caps_lock-f"
162 ];
163 layer3.expect = [
164 "{"
165 "}"
166 ];
167 };
168
169 extraConfig.console.keyMap = "bone";
170 extraConfig.services.xserver.xkb.layout = "de";
171 extraConfig.services.xserver.xkb.variant = "bone";
172 };
173
174 colemak = {
175 tests = {
176 homerow.qwerty = [
177 "a"
178 "s"
179 "d"
180 "f"
181 "j"
182 "k"
183 "l"
184 "semicolon"
185 ];
186 homerow.expect = [
187 "a"
188 "r"
189 "s"
190 "t"
191 "n"
192 "e"
193 "i"
194 "o"
195 ];
196 };
197
198 extraConfig.console.keyMap = "colemak";
199 extraConfig.services.xserver.xkb.layout = "us";
200 extraConfig.services.xserver.xkb.variant = "colemak";
201 };
202
203 dvorak = {
204 tests = {
205 homerow.qwerty = [
206 "a"
207 "s"
208 "d"
209 "f"
210 "j"
211 "k"
212 "l"
213 "semicolon"
214 ];
215 homerow.expect = [
216 "a"
217 "o"
218 "e"
219 "u"
220 "h"
221 "t"
222 "n"
223 "s"
224 ];
225 symbols.qwerty = [
226 "q"
227 "w"
228 "e"
229 "minus"
230 "equal"
231 ];
232 symbols.expect = [
233 "'"
234 ","
235 "."
236 "["
237 "]"
238 ];
239 };
240
241 extraConfig.console.keyMap = "dvorak";
242 extraConfig.services.xserver.xkb.layout = "us";
243 extraConfig.services.xserver.xkb.variant = "dvorak";
244 };
245
246 dvorak-programmer = {
247 tests = {
248 homerow.qwerty = [
249 "a"
250 "s"
251 "d"
252 "f"
253 "j"
254 "k"
255 "l"
256 "semicolon"
257 ];
258 homerow.expect = [
259 "a"
260 "o"
261 "e"
262 "u"
263 "h"
264 "t"
265 "n"
266 "s"
267 ];
268 numbers.qwerty = map (x: "shift-${x}") [
269 "1"
270 "2"
271 "3"
272 "4"
273 "5"
274 "6"
275 "7"
276 "8"
277 "9"
278 "0"
279 "minus"
280 ];
281 numbers.expect = [
282 "%"
283 "7"
284 "5"
285 "3"
286 "1"
287 "9"
288 "0"
289 "2"
290 "4"
291 "6"
292 "8"
293 ];
294 symbols.qwerty = [
295 "1"
296 "2"
297 "3"
298 "4"
299 "5"
300 "6"
301 "7"
302 "8"
303 "9"
304 "0"
305 "minus"
306 ];
307 symbols.expect = [
308 "&"
309 "["
310 "{"
311 "}"
312 "("
313 "="
314 "*"
315 ")"
316 "+"
317 "]"
318 "!"
319 ];
320 };
321
322 extraConfig.console.keyMap = "dvorak-programmer";
323 extraConfig.services.xserver.xkb.layout = "us";
324 extraConfig.services.xserver.xkb.variant = "dvp";
325 };
326
327 neo = {
328 tests = {
329 layer1.qwerty = [
330 "f"
331 "j"
332 ];
333 layer1.expect = [
334 "e"
335 "n"
336 ];
337 layer2.qwerty = [
338 "shift-f"
339 "shift-j"
340 "shift-6"
341 ];
342 layer2.expect = [
343 "E"
344 "N"
345 "$"
346 ];
347 layer3.qwerty = [
348 "caps_lock-d"
349 "caps_lock-f"
350 ];
351 layer3.expect = [
352 "{"
353 "}"
354 ];
355 };
356
357 extraConfig.console.keyMap = "neo";
358 extraConfig.services.xserver.xkb.layout = "de";
359 extraConfig.services.xserver.xkb.variant = "neo";
360 };
361
362 qwertz = {
363 tests = {
364 zy.qwerty = [
365 "z"
366 "y"
367 ];
368 zy.expect = [
369 "y"
370 "z"
371 ];
372 altgr.qwerty = map (x: "alt_r-${x}") [
373 "q"
374 "less"
375 "7"
376 "8"
377 "9"
378 "0"
379 ];
380 altgr.expect = [
381 "@"
382 "|"
383 "{"
384 "["
385 "]"
386 "}"
387 ];
388 };
389
390 extraConfig.console.keyMap = "de";
391 extraConfig.services.xserver.xkb.layout = "de";
392 };
393
394 custom = {
395 tests = {
396 us.qwerty = [
397 "a"
398 "b"
399 "g"
400 "d"
401 "z"
402 "shift-2"
403 "shift-3"
404 ];
405 us.expect = [
406 "a"
407 "b"
408 "g"
409 "d"
410 "z"
411 "@"
412 "#"
413 ];
414 greek.qwerty = map (x: "alt_r-${x}") [
415 "a"
416 "b"
417 "g"
418 "d"
419 "z"
420 ];
421 greek.expect = [
422 "α"
423 "β"
424 "γ"
425 "δ"
426 "ζ"
427 ];
428 };
429
430 extraConfig.console.useXkbConfig = true;
431 extraConfig.services.xserver.xkb.layout = "us-greek";
432 extraConfig.services.xserver.xkb.extraLayouts.us-greek = {
433 description = "US layout with alt-gr greek";
434 languages = [ "eng" ];
435 symbolsFile = pkgs.writeText "us-greek" ''
436 xkb_symbols "us-greek"
437 {
438 include "us(basic)"
439 include "level3(ralt_switch)"
440 key <LatA> { [ a, A, Greek_alpha ] };
441 key <LatB> { [ b, B, Greek_beta ] };
442 key <LatG> { [ g, G, Greek_gamma ] };
443 key <LatD> { [ d, D, Greek_delta ] };
444 key <LatZ> { [ z, Z, Greek_zeta ] };
445 };
446 '';
447 };
448 };
449}