1{ system ? builtins.currentSystem }:
2
3with import ../lib/testing.nix { inherit system; };
4
5let
6 testReader = pkgs.writeScript "test-input-reader" ''
7 #!${pkgs.stdenv.shell}
8 readInput() {
9 touch /tmp/reader.ready
10 echo "Waiting for '$1' to be typed"
11 read -r -n1 c
12 if [ "$c" = "$2" ]; then
13 echo "SUCCESS: Got back '$c' as expected."
14 echo 0 >&2
15 else
16 echo "FAIL: Expected '$2' but got '$c' instead."
17 echo 1 >&2
18 fi
19 }
20
21 main() {
22 error=0
23 while [ $# -gt 0 ]; do
24 ret="$((readInput "$2" "$3" | systemd-cat -t "$1") 2>&1)"
25 if [ $ret -ne 0 ]; then error=1; fi
26 shift 3
27 done
28 return $error
29 }
30
31 main "$@"; echo -n $? > /tmp/reader.exit
32 '';
33
34 mkReaderInput = testname: { qwerty, expect }: with pkgs.lib; let
35 lq = length qwerty;
36 le = length expect;
37 msg = "`qwerty' (${lq}) and `expect' (${le}) lists"
38 + " need to be of the same length!";
39 result = flatten (zipListsWith (a: b: [testname a b]) qwerty expect);
40 in if lq != le then throw msg else result;
41
42 mkKeyboardTest = layout: { extraConfig ? {}, tests }: with pkgs.lib; let
43 readerInput = flatten (mapAttrsToList mkReaderInput tests);
44 perlStr = val: "'${escape ["'" "\\"] val}'";
45 perlReaderInput = concatMapStringsSep ", " perlStr readerInput;
46 in makeTest {
47 name = "keymap-${layout}";
48
49 machine.services.xserver.desktopManager.xterm.enable = false;
50 machine.i18n.consoleKeyMap = mkOverride 900 layout;
51 machine.services.xserver.layout = mkOverride 900 layout;
52 machine.imports = [ ./common/x11.nix extraConfig ];
53 machine.services.xserver.displayManager.slim = {
54 enable = true;
55
56 # Use a custom theme in order to get best OCR results
57 theme = pkgs.runCommand "slim-theme-ocr" {
58 nativeBuildInputs = [ pkgs.imagemagick ];
59 } ''
60 mkdir "$out"
61 convert -size 1x1 xc:white "$out/background.jpg"
62 convert -size 200x100 xc:white "$out/panel.jpg"
63 cat > "$out/slim.theme" <<EOF
64 background_color #ffffff
65 background_style tile
66
67 input_fgcolor #000000
68 msg_color #000000
69
70 session_color #000000
71 session_font Verdana:size=16:bold
72
73 username_msg Username:
74 username_font Verdana:size=16:bold
75 username_color #000000
76 username_x 50%
77 username_y 40%
78
79 password_msg Password:
80 password_x 50%
81 password_y 40%
82 EOF
83 '';
84 };
85
86 testScript = ''
87 sub waitCatAndDelete ($) {
88 return $machine->succeed(
89 "for i in \$(seq 600); do if [ -e '$_[0]' ]; then ".
90 "cat '$_[0]' && rm -f '$_[0]' && exit 0; ".
91 "fi; sleep 0.1; done; echo timed out after 60 seconds >&2; exit 1"
92 );
93 };
94
95 sub mkTest ($$) {
96 my ($desc, $cmd) = @_;
97
98 my @testdata = (${perlReaderInput});
99 my $shellTestdata = join ' ', map { "'".s/'/'\\'''/gr."'" } @testdata;
100
101 subtest $desc, sub {
102 $machine->succeed("$cmd ${testReader} $shellTestdata &");
103 while (my ($testname, $qwerty, $expect) = splice(@testdata, 0, 3)) {
104 waitCatAndDelete "/tmp/reader.ready";
105 $machine->sendKeys($qwerty);
106 };
107 my $exitcode = waitCatAndDelete "/tmp/reader.exit";
108 die "tests for $desc failed" if $exitcode ne 0;
109 };
110 }
111
112 $machine->waitForX;
113
114 mkTest "VT keymap", "openvt -sw --";
115 mkTest "Xorg keymap", "DISPLAY=:0 xterm -fullscreen -e";
116 '';
117 };
118
119in pkgs.lib.mapAttrs mkKeyboardTest {
120 azerty = {
121 tests = {
122 azqw.qwerty = [ "q" "w" ];
123 azqw.expect = [ "a" "z" ];
124 altgr.qwerty = [ "alt_r-2" "alt_r-3" "alt_r-4" "alt_r-5" "alt_r-6" ];
125 altgr.expect = [ "~" "#" "{" "[" "|" ];
126 };
127
128 extraConfig.i18n.consoleKeyMap = "azerty/fr";
129 extraConfig.services.xserver.layout = "fr";
130 };
131
132 colemak = {
133 tests = {
134 homerow.qwerty = [ "a" "s" "d" "f" "j" "k" "l" "semicolon" ];
135 homerow.expect = [ "a" "r" "s" "t" "n" "e" "i" "o" ];
136 };
137
138 extraConfig.i18n.consoleKeyMap = "en-latin9";
139 extraConfig.services.xserver.layout = "us";
140 extraConfig.services.xserver.xkbVariant = "colemak";
141 };
142
143 dvorak = {
144 tests = {
145 homerow.qwerty = [ "a" "s" "d" "f" "j" "k" "l" "semicolon" ];
146 homerow.expect = [ "a" "o" "e" "u" "h" "t" "n" "s" ];
147 symbols.qwerty = [ "q" "w" "e" "minus" "equal" ];
148 symbols.expect = [ "'" "," "." "[" "]" ];
149 };
150 };
151
152 dvp = {
153 tests = {
154 homerow.qwerty = [ "a" "s" "d" "f" "j" "k" "l" "semicolon" ];
155 homerow.expect = [ "a" "o" "e" "u" "h" "t" "n" "s" ];
156 numbers.qwerty = map (x: "shift-${x}")
157 [ "1" "2" "3" "4" "5" "6" "7" "8" "9" "0" "minus" ];
158 numbers.expect = [ "%" "7" "5" "3" "1" "9" "0" "2" "4" "6" "8" ];
159 symbols.qwerty = [ "1" "2" "3" "4" "5" "6" "7" "8" "9" "0" "minus" ];
160 symbols.expect = [ "&" "[" "{" "}" "(" "=" "*" ")" "+" "]" "!" ];
161 };
162
163 extraConfig.services.xserver.layout = "us";
164 extraConfig.services.xserver.xkbVariant = "dvp";
165 };
166
167 neo = {
168 tests = {
169 layer1.qwerty = [ "f" "j" ];
170 layer1.expect = [ "e" "n" ];
171 layer2.qwerty = [ "shift-f" "shift-j" "shift-6" ];
172 layer2.expect = [ "E" "N" "$" ];
173 layer3.qwerty = [ "caps_lock-d" "caps_lock-f" ];
174 layer3.expect = [ "{" "}" ];
175 };
176
177 extraConfig.services.xserver.layout = "de";
178 extraConfig.services.xserver.xkbVariant = "neo";
179 };
180
181 qwertz = {
182 tests = {
183 zy.qwerty = [ "z" "y" ];
184 zy.expect = [ "y" "z" ];
185 altgr.qwerty = map (x: "alt_r-${x}")
186 [ "q" "less" "7" "8" "9" "0" ];
187 altgr.expect = [ "@" "|" "{" "[" "]" "}" ];
188 };
189
190 extraConfig.i18n.consoleKeyMap = "de";
191 extraConfig.services.xserver.layout = "de";
192 };
193}