From e91cf43387e5bde7528ccf094123f312c689955b Mon Sep 17 00:00:00 2001 From: Quentin Cazier Date: Fri, 11 Sep 2026 17:46:36 +0200 Subject: [PATCH 1/2] feat(sway): bind shortcuts by keycode with --to-code The sway configuration binds shortcuts by keysym, so on a keyboard using another layout than fr the workspace keys and letter shortcuts land on different physical keys. Using bindsym --to-code translates each keysym to its keycode from the first configured layout (fr), so the same physical keys work on both fr and en keyboards without carrying the bindings in an extra config. Closes #84 Signed-off-by: Quentin Cazier --- .../graphical-interface/sway/sway-config.nix | 133 +++++++++--------- 1 file changed, 68 insertions(+), 65 deletions(-) diff --git a/modules/graphical-interface/sway/sway-config.nix b/modules/graphical-interface/sway/sway-config.nix index 0c964e13..360e31da 100644 --- a/modules/graphical-interface/sway/sway-config.nix +++ b/modules/graphical-interface/sway/sway-config.nix @@ -90,19 +90,22 @@ in ### Key bindings # + # --to-code binds the physical key rather than the keysym, so the + # shortcuts stay the same on fr and en keyboards. + # # Basics: # # Start a terminal - bindsym $mod+Return exec $term + bindsym --to-code $mod+Return exec $term # Kill focused window - bindsym $mod+Shift+q kill + bindsym --to-code $mod+Shift+q kill # Start your launcher - bindsym $mod+d exec $menu + bindsym --to-code $mod+d exec $menu # Lock the session - bindsym $mod+Control+l exec --no-startup-id swaylock -c 000000 -e + bindsym --to-code $mod+Control+l exec --no-startup-id swaylock -c 000000 -e # Drag floating windows by holding down $mod and left mouse button. # Resize them with right mouse button + $mod. @@ -112,88 +115,88 @@ in floating_modifier $mod normal # Reload the configuration file - bindsym $mod+Shift+c reload + bindsym --to-code $mod+Shift+c reload # Exit sway (logs you out of your Wayland session) - bindsym $mod+Shift+e exec swaynag -t warning -m 'You pressed the exit shortcut. Do you really want to exit sway? This will end your Wayland session.' -B 'Yes, exit sway' 'swaymsg exit' + bindsym --to-code $mod+Shift+e exec swaynag -t warning -m 'You pressed the exit shortcut. Do you really want to exit sway? This will end your Wayland session.' -B 'Yes, exit sway' 'swaymsg exit' # # Moving around: # # Move your focus around - bindsym $mod+$left focus left - bindsym $mod+$down focus down - bindsym $mod+$up focus up - bindsym $mod+$right focus right + bindsym --to-code $mod+$left focus left + bindsym --to-code $mod+$down focus down + bindsym --to-code $mod+$up focus up + bindsym --to-code $mod+$right focus right # Or use $mod+[up|down|left|right] - bindsym $mod+Left focus left - bindsym $mod+Down focus down - bindsym $mod+Up focus up - bindsym $mod+Right focus right + bindsym --to-code $mod+Left focus left + bindsym --to-code $mod+Down focus down + bindsym --to-code $mod+Up focus up + bindsym --to-code $mod+Right focus right # Move the focused window with the same, but add Shift - bindsym $mod+Shift+$left move left - bindsym $mod+Shift+$down move down - bindsym $mod+Shift+$up move up - bindsym $mod+Shift+$right move right + bindsym --to-code $mod+Shift+$left move left + bindsym --to-code $mod+Shift+$down move down + bindsym --to-code $mod+Shift+$up move up + bindsym --to-code $mod+Shift+$right move right # Ditto, with arrow keys - bindsym $mod+Shift+Left move left - bindsym $mod+Shift+Down move down - bindsym $mod+Shift+Up move up - bindsym $mod+Shift+Right move right + bindsym --to-code $mod+Shift+Left move left + bindsym --to-code $mod+Shift+Down move down + bindsym --to-code $mod+Shift+Up move up + bindsym --to-code $mod+Shift+Right move right # # Workspaces: # # Switch to workspace - bindsym $mod+ampersand workspace number 1 - bindsym $mod+eacute workspace number 2 - bindsym $mod+quotedbl workspace number 3 - bindsym $mod+apostrophe workspace number 4 - bindsym $mod+parenleft workspace number 5 - bindsym $mod+minus workspace number 6 # +section on Apple keyboards - bindsym $mod+egrave workspace number 7 - bindsym $mod+underscore workspace number 8 # +exclam on Apple keyboards - bindsym $mod+ccedilla workspace number 9 - bindsym $mod+agrave workspace number 10 + bindsym --to-code $mod+ampersand workspace number 1 + bindsym --to-code $mod+eacute workspace number 2 + bindsym --to-code $mod+quotedbl workspace number 3 + bindsym --to-code $mod+apostrophe workspace number 4 + bindsym --to-code $mod+parenleft workspace number 5 + bindsym --to-code $mod+minus workspace number 6 # +section on Apple keyboards + bindsym --to-code $mod+egrave workspace number 7 + bindsym --to-code $mod+underscore workspace number 8 # +exclam on Apple keyboards + bindsym --to-code $mod+ccedilla workspace number 9 + bindsym --to-code $mod+agrave workspace number 10 # Move focused container to workspace - bindsym $mod+Shift+ampersand move container to workspace number 1 - bindsym $mod+Shift+eacute move container to workspace number 2 - bindsym $mod+Shift+quotedbl move container to workspace number 3 - bindsym $mod+Shift+apostrophe move container to workspace number 4 - bindsym $mod+Shift+parenleft move container to workspace number 5 - bindsym $mod+Shift+minus move container to workspace number 6 - bindsym $mod+Shift+egrave move container to workspace number 7 - bindsym $mod+Shift+underscore move container to workspace number 8 - bindsym $mod+Shift+ccedilla move container to workspace number 9 - bindsym $mod+Shift+agrave move container to workspace number 10 + bindsym --to-code $mod+Shift+ampersand move container to workspace number 1 + bindsym --to-code $mod+Shift+eacute move container to workspace number 2 + bindsym --to-code $mod+Shift+quotedbl move container to workspace number 3 + bindsym --to-code $mod+Shift+apostrophe move container to workspace number 4 + bindsym --to-code $mod+Shift+parenleft move container to workspace number 5 + bindsym --to-code $mod+Shift+minus move container to workspace number 6 + bindsym --to-code $mod+Shift+egrave move container to workspace number 7 + bindsym --to-code $mod+Shift+underscore move container to workspace number 8 + bindsym --to-code $mod+Shift+ccedilla move container to workspace number 9 + bindsym --to-code $mod+Shift+agrave move container to workspace number 10 # Note: workspaces can have any name you want, not just numbers. # We just use 1-10 as the default. # For multi-screens. - bindsym $mod+m move workspace to output left + bindsym --to-code $mod+m move workspace to output left # # Layout stuff: # # You can "split" the current object of your focus with # $mod+b or $mod+v, for horizontal and vertical splits # respectively. - bindsym $mod+b splith - bindsym $mod+v splitv + bindsym --to-code $mod+b splith + bindsym --to-code $mod+v splitv # Switch the current container between different layout styles - bindsym $mod+s layout stacking - bindsym $mod+w layout tabbed - bindsym $mod+e layout toggle split + bindsym --to-code $mod+s layout stacking + bindsym --to-code $mod+w layout tabbed + bindsym --to-code $mod+e layout toggle split # Make the current focus fullscreen - bindsym $mod+f fullscreen + bindsym --to-code $mod+f fullscreen # Toggle the current focus between tiling and floating mode - bindsym $mod+Shift+space floating toggle + bindsym --to-code $mod+Shift+space floating toggle # Swap focus between the tiling area and the floating area - bindsym $mod+space focus mode_toggle + bindsym --to-code $mod+space focus mode_toggle # Move focus to the parent container - bindsym $mod+a focus parent + bindsym --to-code $mod+a focus parent # # Scratchpad: # @@ -201,11 +204,11 @@ in # You can send windows there and get them back later. # Move the currently focused window to the scratchpad - bindsym $mod+Shift+p move scratchpad + bindsym --to-code $mod+Shift+p move scratchpad # Show the next scratchpad window or hide the focused scratchpad window. # If there are multiple scratchpad windows, this command cycles through them. - bindsym $mod+p scratchpad show + bindsym --to-code $mod+p scratchpad show # # Resizing containers: # @@ -214,22 +217,22 @@ in # right will grow the containers width # up will shrink the containers height # down will grow the containers height - bindsym $left resize shrink width 10px - bindsym $down resize grow height 10px - bindsym $up resize shrink height 10px - bindsym $right resize grow width 10px + bindsym --to-code $left resize shrink width 10px + bindsym --to-code $down resize grow height 10px + bindsym --to-code $up resize shrink height 10px + bindsym --to-code $right resize grow width 10px # Ditto, with arrow keys - bindsym Left resize shrink width 10px - bindsym Down resize grow height 10px - bindsym Up resize shrink height 10px - bindsym Right resize grow width 10px + bindsym --to-code Left resize shrink width 10px + bindsym --to-code Down resize grow height 10px + bindsym --to-code Up resize shrink height 10px + bindsym --to-code Right resize grow width 10px # Return to default mode - bindsym Return mode "default" - bindsym Escape mode "default" + bindsym --to-code Return mode "default" + bindsym --to-code Escape mode "default" } - bindsym $mod+r mode "resize" + bindsym --to-code $mod+r mode "resize" # # Status Bar: From a4e08752a009a2521e8ca0bff1051c8b4b75004e Mon Sep 17 00:00:00 2001 From: Quentin Cazier Date: Fri, 11 Sep 2026 18:47:35 +0200 Subject: [PATCH 2/2] tests: add a VM test for the sway shortcuts Boots the Securix sway configuration and sends key events through QEMU. The shortcuts must react on the same physical keys with the default fr layout and once the keyboard is switched to a us layout. Signed-off-by: Quentin Cazier --- tests/default.nix | 1 + tests/sway-keybindings.nix | 100 +++++++++++++++++++++++++++++++++++++ 2 files changed, 101 insertions(+) create mode 100644 tests/sway-keybindings.nix diff --git a/tests/default.nix b/tests/default.nix index 3f6f74b8..84c91de3 100644 --- a/tests/default.nix +++ b/tests/default.nix @@ -8,5 +8,6 @@ anssi-minimal = import ./anssi-minimal.nix { inherit pkgs libSecurix; }; idempotent-autoinstall = import ./idempotent-autoinstall.nix { inherit pkgs libSecurix; }; portail = import ./portail.nix { inherit pkgs libSecurix; }; + sway-keybindings = import ./sway-keybindings.nix { inherit pkgs libSecurix; }; tools = import ./tools.nix { inherit pkgs libSecurix; }; } diff --git a/tests/sway-keybindings.nix b/tests/sway-keybindings.nix new file mode 100644 index 00000000..258ad3ee --- /dev/null +++ b/tests/sway-keybindings.nix @@ -0,0 +1,100 @@ +# SPDX-FileCopyrightText: 2026 Quentin Cazier +# +# SPDX-License-Identifier: MIT +# +# Regression test for https://github.com/cloud-gouv/securix/issues/84 +# +# Boots the Securix sway configuration and drives it with key events sent +# by QEMU. The shortcuts must keep working on the same physical keys once +# the keyboard is switched from the default fr layout to a us layout. + +{ pkgs, libSecurix }: + +let + lib = pkgs.lib; +in +pkgs.testers.nixosTest { + name = "sway-keybindings"; + + nodes.machine = { + imports = [ ../modules/graphical-interface ]; + + securix.graphical-interface = { + enable = true; + variant = "sway"; + }; + + # Log in on tty1 directly instead of going through tuigreet. + services.greetd.enable = lib.mkForce false; + services.getty.autologinUser = "alice"; + + users.users.alice = { + isNormalUser = true; + password = "test"; + uid = 1000; + }; + + environment.variables = { + SWAYSOCK = "/tmp/sway-ipc.sock"; + WLR_RENDERER = "pixman"; + }; + + programs.bash.loginShellInit = '' + if [ "$(tty)" = "/dev/tty1" ]; then + sway + fi + ''; + + virtualisation.qemu.options = [ "-vga none -device virtio-gpu-pci" ]; + }; + + testScript = '' + import json + import shlex + + def swaymsg(command, msg_type="command"): + shell = "swaymsg -t " + shlex.quote(msg_type) + " -- " + shlex.quote(command) + out = machine.succeed("su - alice -c " + shlex.quote(shell)) + return json.loads(out) + + def walk(node): + yield node + for group in ("nodes", "floating_nodes"): + for child in node.get(group, []): + yield from walk(child) + + def app_ids(): + return [node.get("app_id") for node in walk(swaymsg("", "get_tree"))] + + def focused_workspace(): + return [ws["num"] for ws in swaymsg("", "get_workspaces") if ws["focused"]][0] + + def keyboards(): + return [i for i in swaymsg("", "get_inputs") if i["type"] == "keyboard"] + + # QEMU names keys by their us position, so the fr "q" key is sent as "a". + machine.wait_for_unit("multi-user.target") + machine.wait_for_file("/tmp/sway-ipc.sock") + retry(lambda _: len(keyboards()) > 0, timeout_seconds=120) + + with subtest("fr layout: shortcuts work"): + assert keyboards()[0]["xkb_active_layout_name"].startswith("French") + machine.send_key("meta_l-ret") + retry(lambda _: "foot" in app_ids(), timeout_seconds=120) + machine.send_key("meta_l-shift-a") + machine.wait_until_fails("pgrep foot") + machine.send_key("meta_l-2") + retry(lambda _: focused_workspace() == 2, timeout_seconds=120) + + with subtest("us layout: same physical keys"): + swaymsg('input type:keyboard xkb_layout "fr,us"') + swaymsg("input type:keyboard xkb_switch_layout 1") + retry(lambda _: keyboards()[0]["xkb_active_layout_name"].startswith("English"), timeout_seconds=120) + machine.send_key("meta_l-3") + retry(lambda _: focused_workspace() == 3, timeout_seconds=120) + machine.send_key("meta_l-ret") + retry(lambda _: "foot" in app_ids(), timeout_seconds=120) + machine.send_key("meta_l-shift-a") + machine.wait_until_fails("pgrep foot") + ''; +}