fix(keyboard): isolate new-keyboard layout to its own device
The interactive new-keyboard picker applied the chosen layout with `hyprctl switchxkblayout <device> <index>`, which flips the *shared* layout for every keyboard without its own per-device config — so picking a layout for an external board dragged the built-in keyboard along, and the change stuck after unplug (it's global state, not device-bound). Compounding it, the candidate `keyboard.layouts` pool was merged into `input.kb_layout`, loading those layouts onto the session keyboard too. Apply the choice as a per-device `hyprctl keyword device[<name>]:kb_layout` instead — the runtime twin of the declarative `keyboard.devices` blocks — so it isolates that one keyboard and never touches the built-in board. Keep the candidate pool out of `input.kb_layout` (session keyboard carries only `keyboard.layout`); the pool is for external boards, applied per-device. Also put `nomarchy-keyboard-watch` on PATH when enabled (it was store-path only via exec-once, so undiscoverable) for debugging. Found on the Latitude 5410 hardware sweep; picker itself verified working. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -55,10 +55,14 @@ let
|
||||
++ lib.optional (m.extra != "") m.extra
|
||||
);
|
||||
|
||||
# Keyboard layout candidates: the primary nomarchy.keyboard.layout (split on
|
||||
# commas) plus any nomarchy.keyboard.layouts. Drives input.kb_layout so all
|
||||
# of them are switchable, and feeds the watcher's picker.
|
||||
kbLayouts = lib.unique (
|
||||
# Candidate layouts offered by the new-keyboard picker: the session
|
||||
# layout(s) (nomarchy.keyboard.layout, comma-split for a multi-layout
|
||||
# session) plus the extra nomarchy.keyboard.layouts pool. The pool is
|
||||
# deliberately NOT merged into input.kb_layout — those candidates are for
|
||||
# *external* keyboards and get applied per-device by the watcher's apply().
|
||||
# Loading them onto the session keyboard is what let a global switch flip
|
||||
# the built-in board to the wrong layout.
|
||||
pickerLayouts = lib.unique (
|
||||
(lib.splitString "," config.nomarchy.keyboard.layout)
|
||||
++ config.nomarchy.keyboard.layouts
|
||||
);
|
||||
@@ -68,18 +72,20 @@ let
|
||||
# keyboard.devices, not already remembered), ask for a layout and persist it
|
||||
# per-device, re-applying silently on later reconnects. Stateful runtime
|
||||
# piece — the complement to the declarative keyboard.devices. Reliable
|
||||
# primitives only: poll hyprctl devices, apply via switchxkblayout (an index
|
||||
# into kbLayouts). NOTE: needs an on-hardware test (hotplug isn't verifiable
|
||||
# in CI).
|
||||
# primitives only: poll hyprctl devices, apply with a per-device
|
||||
# `device[<name>]:kb_layout` keyword — the runtime twin of the declarative
|
||||
# device blocks, so it isolates that one keyboard and never disturbs the
|
||||
# built-in board (switchxkblayout flipped the *shared* layout, which leaked
|
||||
# onto the laptop and stuck after unplug). NOTE: needs an on-hardware test
|
||||
# (hotplug isn't verifiable in CI).
|
||||
keyboardWatch = pkgs.writeShellScriptBin "nomarchy-keyboard-watch" ''
|
||||
set -u
|
||||
layouts="${lib.concatStringsSep " " kbLayouts}"
|
||||
layouts="${lib.concatStringsSep " " pickerLayouts}"
|
||||
declared="${lib.concatStringsSep " " (builtins.attrNames config.nomarchy.keyboard.devices)}"
|
||||
state="''${XDG_STATE_HOME:-$HOME/.local/state}/nomarchy/keyboard-layouts"
|
||||
mkdir -p "$(dirname "$state")"; [ -f "$state" ] || : > "$state"
|
||||
|
||||
layout_index() { i=0; for l in $layouts; do [ "$l" = "$1" ] && { printf %s "$i"; return; }; i=$((i + 1)); done; printf %s -1; }
|
||||
apply() { idx=$(layout_index "$2"); [ "$idx" -ge 0 ] && hyprctl switchxkblayout "$1" "$idx" >/dev/null 2>&1; }
|
||||
apply() { hyprctl keyword "device[$1]:kb_layout" "$2" >/dev/null 2>&1; }
|
||||
keyboards() { hyprctl devices -j | jq -r '.keyboards[].name'; }
|
||||
is_declared() { case " $declared " in *" $1 "*) return 0 ;; *) return 1 ;; esac; }
|
||||
saved_for() { while IFS='=' read -r k v; do [ "$k" = "$1" ] && { printf '%s' "$v"; return; }; done < "$state"; }
|
||||
@@ -201,8 +207,10 @@ in
|
||||
|
||||
input = {
|
||||
# kb_layout/variant come from nomarchy.keyboard.* — set that
|
||||
# option (the installer writes it; it also drives tty/LUKS).
|
||||
kb_layout = lib.concatStringsSep "," kbLayouts;
|
||||
# option (the installer writes it; it also drives tty/LUKS). Only the
|
||||
# session layout(s) land here; the keyboard.layouts pool is applied
|
||||
# per-device to external boards, never onto the session keyboard.
|
||||
kb_layout = config.nomarchy.keyboard.layout;
|
||||
kb_variant = config.nomarchy.keyboard.variant;
|
||||
follow_mouse = lib.mkDefault 1;
|
||||
touchpad.natural_scroll = lib.mkDefault true;
|
||||
@@ -270,7 +278,10 @@ in
|
||||
# writes a config file). A helper to find values for nomarchy.monitors —
|
||||
# the declarative config stays the source of truth (we don't source its
|
||||
# output). Gated on its toggle; pointless without the Hyprland session.
|
||||
home.packages = lib.optionals
|
||||
(config.nomarchy.hyprland.enable && config.nomarchy.displays.enable)
|
||||
[ pkgs.nwg-displays ];
|
||||
home.packages =
|
||||
lib.optionals (config.nomarchy.hyprland.enable && config.nomarchy.displays.enable)
|
||||
[ pkgs.nwg-displays ]
|
||||
# The new-keyboard watcher on PATH (when enabled) so it's discoverable and
|
||||
# runnable by name for debugging — Hyprland still exec-once's it by store path.
|
||||
++ lib.optional (config.nomarchy.hyprland.enable && kbAutoSwitch) keyboardWatch;
|
||||
}
|
||||
|
||||
@@ -105,10 +105,11 @@ in
|
||||
picker. When non-empty, a small watcher runs in the session: when a
|
||||
keyboard connects that isn't covered by nomarchy.keyboard.devices and
|
||||
hasn't been chosen before, it pops a rofi picker (these layouts plus
|
||||
the primary nomarchy.keyboard.layout), applies the choice, and
|
||||
remembers it per-device (~/.local/state) — re-applying automatically
|
||||
on later reconnects. The runtime-remember complement to the
|
||||
declarative keyboard.devices; a stateful runtime piece by design.
|
||||
the primary nomarchy.keyboard.layout), applies the choice to that
|
||||
keyboard only (a per-device kb_layout, so the built-in board is left
|
||||
alone), and remembers it per-device (~/.local/state) — re-applying
|
||||
automatically on later reconnects. The runtime-remember complement to
|
||||
the declarative keyboard.devices; a stateful runtime piece by design.
|
||||
'';
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user