feat(keyboard): per-device layout memory in-flake (no ~/.local/state)

Phase 2 of the in-flake-state philosophy, mirroring night-light: the
nomarchy-keyboard-watch daemon now remembers per-device layouts in the
git-tracked state file (settings.keyboard.devices) instead of
~/.local/state/nomarchy/keyboard-layouts.

- Reads the LIVE working-tree map (theme-sync get → jq) so a pick this
  session is honoured at once; writes instantly with --no-switch (no
  rebuild). Writes the whole map back at the dot-free parent path
  (read-modify-write) so a device name with a dot can't corrupt the
  tool's dotted set-path (Hyprland only sanitises spaces→-).
- Graduation: nomarchy.keyboard.devices now mkDefault-merges entries
  from settings.keyboard.devices, so each remembered device becomes a
  generated Hyprland device{} block on the next rebuild (reproducible,
  hotplug-applied). A hand-written keyboard.devices.<name> still wins;
  once baked, the watcher's is_declared check makes it step back.
- theme.nix seeds settings.keyboard.devices = {} (sparse state still
  evaluates); options/template descriptions updated; ROADMAP marked done.

Eval-verified (graduation + mkDefault precedence) and the set/get
writer round-trips. Still needs the on-hardware hotplug re-verify (the
picker path isn't testable in CI).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Bernardo Magri
2026-06-23 09:18:33 +01:00
parent 685126ab47
commit aed41793f8
5 changed files with 83 additions and 28 deletions

View File

@@ -485,9 +485,10 @@ how to override it. Items marked ✓ are shipped.
`custom/nightlight` indicator shows the moon while running and hides otherwise.
This is the first cut of a broader principle — **any user-settable config
gets a menu writer that lands it in the downstream flake; no state lives
outside the checkout** (next target: the per-device keyboard-layout memory
below, still in `~/.local/state`; then opt-in auto-commit of the flake on
each mutation, owned files only). Remaining (optional): geo (lat/long) auto
outside the checkout** (Phase 1 night-light + Phase 2 the per-device
keyboard-layout memory below are done — both now in the git-tracked state
file; next target: opt-in auto-commit of the flake on each mutation, owned
files only). Remaining (optional): geo (lat/long) auto
sunset/sunrise (would mean wlsunset, which schedules by location). Pending an
on-machine check (first enable rebuilds + comes on; later toggles instant; off
persists across reboot via ExecCondition; stopping hyprsunset restores gamma).
@@ -508,18 +509,27 @@ how to override it. Items marked ✓ are shipped.
hasn't been chosen before, it pops a rofi layout picker, applies the
choice as a **per-device** `hyprctl keyword device[<name>]:kb_layout` (the
runtime twin of the declarative `device{}` blocks — so it isolates that
one keyboard and never touches the built-in board), and remembers it
per-device in `~/.local/state/nomarchy/keyboard-layouts` — re-applied
silently on later reconnects. The boot-time set (incl. the built-in
keyboard) is never prompted. The runtime-remember complement to the
declarative `keyboard.devices`; a stateful runtime piece by design.
one keyboard and never touches the built-in board), and remembers it. The
boot-time set (incl. the built-in keyboard) is never prompted. The
runtime-remember complement to the declarative `keyboard.devices`.
**Picker verified on hardware (2026-06-18)**; the first cut applied the
choice with `switchxkblayout` (a global layout-index flip) and merged the
candidate pool into `input.kb_layout`, so a selection leaked onto the
laptop keyboard and stuck after unplug — fixed by the per-device keyword +
keeping the pool out of the session layout. Remaining (bonus): offer to
write a choice into `keyboard.devices` so it graduates to the reproducible
config; **re-verify the per-device isolation on hardware**.
keeping the pool out of the session layout.
-**In-flake state + graduation (2026-06-23):** the remembered picks moved
out of `~/.local/state/nomarchy/keyboard-layouts` into the git-tracked
state file (`settings.keyboard.devices`, a device-name → layout map) —
Phase 2 of the in-flake-state principle, the same path night-light took.
The watcher reads the **live** working tree (a pick is honoured at once)
and writes instantly with `--no-switch` (no rebuild), merging the whole map
back at the dot-free parent path so a device name containing a dot can't
corrupt the dotted set-path. Each remembered device **graduates** into
`nomarchy.keyboard.devices` on the next rebuild (a generated Hyprland
`device{}` block, `mkDefault` so a hand-written entry still wins), after
which the watcher sees it as declared and steps back. Eval-verified
(graduation + precedence) and the writer round-trips; **still needs the
on-hardware hotplug re-verify** (the picker path isn't testable in CI).
- Remaining: a multi-layout cycle bind (`hyprctl switchxkblayout` / xkb
`grp:` options) for switching layouts on one keyboard; add the
`hyprland/language` module to the summer whole-swap themes for parity