1{ pkgs, lib, ... }:
2{
3 name = "sway";
4 meta = {
5 maintainers = with lib.maintainers; [
6 synthetica
7 ];
8 };
9
10 # testScriptWithTypes:49: error: Cannot call function of unknown type
11 # (machine.succeed if succeed else machine.execute)(
12 # ^
13 # Found 1 error in 1 file (checked 1 source file)
14 skipTypeCheck = true;
15
16 nodes.machine =
17 { config, ... }:
18 {
19 # Automatically login on tty1 as a normal user:
20 imports = [ ./common/user-account.nix ];
21 services.getty.autologinUser = "alice";
22
23 environment = {
24 # For glinfo and wayland-info:
25 systemPackages = with pkgs; [
26 mesa-demos
27 wayland-utils
28 alacritty
29 ];
30 # Use a fixed SWAYSOCK path (for swaymsg):
31 variables = {
32 "SWAYSOCK" = "/tmp/sway-ipc.sock";
33 # TODO: Investigate if we can get hardware acceleration to work (via
34 # virtio-gpu and Virgil). We currently have to use the Pixman software
35 # renderer since the GLES2 renderer doesn't work inside the VM (even
36 # with WLR_RENDERER_ALLOW_SOFTWARE):
37 # "WLR_RENDERER_ALLOW_SOFTWARE" = "1";
38 "WLR_RENDERER" = "pixman";
39 };
40 # For convenience:
41 shellAliases = {
42 test-x11 = "glinfo | tee /tmp/test-x11.out && touch /tmp/test-x11-exit-ok";
43 test-wayland = "wayland-info | tee /tmp/test-wayland.out && touch /tmp/test-wayland-exit-ok";
44 };
45
46 # To help with OCR:
47 etc."xdg/foot/foot.ini".text = lib.generators.toINI { } {
48 main = {
49 font = "inconsolata:size=14";
50 };
51 colors = rec {
52 foreground = "000000";
53 background = "ffffff";
54 regular2 = foreground;
55 };
56 };
57
58 etc."gpg-agent.conf".text = ''
59 pinentry-timeout 86400
60 '';
61 };
62
63 fonts.packages = [ pkgs.inconsolata ];
64
65 # Automatically configure and start Sway when logging in on tty1:
66 programs.bash.loginShellInit = ''
67 if [ "$(tty)" = "/dev/tty1" ]; then
68 set -e
69
70 mkdir -p ~/.config/sway
71 sed s/Mod4/Mod1/ /etc/sway/config > ~/.config/sway/config
72
73 sway --validate
74 sway && touch /tmp/sway-exit-ok
75 fi
76 '';
77
78 programs.sway.enable = true;
79
80 # To test pinentry via gpg-agent:
81 programs.gnupg.agent.enable = true;
82
83 # Need to switch to a different GPU driver than the default one (-vga std) so that Sway can launch:
84 virtualisation.qemu.options = [ "-vga none -device virtio-gpu-pci" ];
85 };
86
87 testScript =
88 { nodes, ... }:
89 ''
90 import shlex
91 import json
92
93 q = shlex.quote
94 NODE_GROUPS = ["nodes", "floating_nodes"]
95
96
97 def swaymsg(command: str = "", succeed=True, type="command"):
98 assert command != "" or type != "command", "Must specify command or type"
99 shell = q(f"swaymsg -t {q(type)} -- {q(command)}")
100 with machine.nested(
101 f"sending swaymsg {shell!r}" + " (allowed to fail)" * (not succeed)
102 ):
103 ret = (machine.succeed if succeed else machine.execute)(
104 f"su - alice -c {shell}"
105 )
106
107 # execute also returns a status code, but disregard.
108 if not succeed:
109 _, ret = ret
110
111 if not succeed and not ret:
112 return None
113
114 parsed = json.loads(ret)
115 return parsed
116
117
118 def walk(tree):
119 yield tree
120 for group in NODE_GROUPS:
121 for node in tree.get(group, []):
122 yield from walk(node)
123
124
125 def wait_for_window(pattern):
126 def func(last_chance):
127 nodes = (node["name"] for node in walk(swaymsg(type="get_tree")))
128
129 if last_chance:
130 nodes = list(nodes)
131 machine.log(f"Last call! Current list of windows: {nodes}")
132
133 return any(pattern in name for name in nodes)
134
135 retry(func)
136
137 start_all()
138 machine.wait_for_unit("multi-user.target")
139
140 # To check the version:
141 print(machine.succeed("sway --version"))
142
143 # Wait for Sway to complete startup:
144 machine.wait_for_file("/run/user/1000/wayland-1")
145 machine.wait_for_file("/tmp/sway-ipc.sock")
146
147 # Test XWayland (foot does not support X):
148 swaymsg("exec WINIT_UNIX_BACKEND=x11 WAYLAND_DISPLAY= alacritty")
149 wait_for_window("alice@machine")
150 machine.send_chars("test-x11\n")
151 machine.wait_for_file("/tmp/test-x11-exit-ok")
152 print(machine.succeed("cat /tmp/test-x11.out"))
153 machine.copy_from_vm("/tmp/test-x11.out")
154 machine.screenshot("alacritty_glinfo")
155 machine.succeed("pkill alacritty")
156
157 # Start a terminal (foot) on workspace 3:
158 machine.send_key("alt-3")
159 machine.sleep(3)
160 machine.send_key("alt-ret")
161 wait_for_window("alice@machine")
162 machine.send_chars("test-wayland\n")
163 machine.wait_for_file("/tmp/test-wayland-exit-ok")
164 print(machine.succeed("cat /tmp/test-wayland.out"))
165 machine.copy_from_vm("/tmp/test-wayland.out")
166 machine.screenshot("foot_wayland_info")
167 machine.send_key("alt-shift-q")
168 machine.wait_until_fails("pgrep foot")
169
170 # Test gpg-agent starting pinentry-gnome3 via D-Bus (tests if
171 # $WAYLAND_DISPLAY is correctly imported into the D-Bus user env):
172 swaymsg("exec mkdir -p ~/.gnupg")
173 swaymsg("exec cp /etc/gpg-agent.conf ~/.gnupg")
174
175 swaymsg("exec DISPLAY=INVALID gpg --no-tty --yes --quick-generate-key test", succeed=False)
176 machine.wait_until_succeeds("pgrep --exact gpg")
177 wait_for_window("gpg")
178 machine.succeed("pgrep --exact gpg")
179 machine.screenshot("gpg_pinentry")
180 machine.send_key("alt-shift-q")
181 machine.wait_until_fails("pgrep --exact gpg")
182
183
184 ${lib.optionalString pkgs.stdenv.hostPlatform.isx86_64 ''
185 # Test swaynag:
186 # Broken on aarch64-linux, see https://github.com/NixOS/nixpkgs/issues/416217
187 def get_height():
188 return [node['rect']['height'] for node in walk(swaymsg(type="get_tree")) if node['focused']][0]
189
190 before = get_height()
191 machine.send_key("alt-shift-e")
192 retry(lambda _: get_height() < before)
193 machine.screenshot("sway_exit")
194 ''}
195
196 swaymsg("exec swaylock")
197 machine.wait_until_succeeds("pgrep -xf swaylock")
198 machine.sleep(3)
199 machine.send_chars("${nodes.machine.config.users.users.alice.password}")
200 machine.send_key("ret")
201 machine.wait_until_fails("pgrep -xf swaylock")
202
203 # Exit Sway and verify process exit status 0:
204 swaymsg("exit", succeed=False)
205 machine.wait_until_fails("pgrep -xf sway")
206 machine.wait_for_file("/tmp/sway-exit-ok")
207 '';
208}