Skip to content

Per-keyboard-device xkb and repeat configuration (carries upstream niri#4459) - #44

Merged
barrulus merged 7 commits into
mainfrom
per-keyboard-config
Sep 17, 2026
Merged

barrulus merged 7 commits into
mainfrom
per-keyboard-config

Conversation

@barrulus

Copy link
Copy Markdown
Owner

Part of #43.

Carries upstream niri-wm#4459 (three commits, cherry-picked as-is) onto main, plus two fork commits on top.

What it does

keyboard "device name" { … } blocks inside input {} configure one physical keyboard. A named block inherits any setting it leaves unset (repeat-rate, repeat-delay, numlock, track-layout) from the unnamed block; xkb {} is one setting and replaces as a whole. Device names match case-insensitively against the libinput device name. A Wayland seat has a single keymap, so the resolved config is applied when a key event arrives from a different device than the last one. Resolved configs are cached per device and the cache is dropped whenever the keyboard blocks change on reload.

Fork changes on top of the upstream PR

  • input: keep num lock, locale1 fallback and IPC event when switching keyboards. The upstream switch path applied the keymap directly through the Smithay handle, which resets num lock on every switch, ignored the locale1 settings that reload_config() uses when xkb is left unset, and never sent KeyboardLayoutsChanged over IPC. The switch now goes through State::set_xkb_config() with the same chain as a reload and notifies IPC. The switch logic lives in switch_keyboard_config() so the headless fixture can drive it.
  • docs. The PR told readers to run niri msg keyboard-layouts to find device names; that command lists layouts, not devices. The wiki page and the default config now point at libinput list-devices and /proc/bus/input/devices.

Tests

  • niri-config: six parse tests (inheritance from the unnamed block, xkb replacing wholesale, case-insensitive matching, unknown device falling back, named-only config using the defaults, an included input section replacing all keyboard blocks). Written before the cherry-pick; they failed to compile until the config commit landed.
  • niri: three fixture tests in src/tests/keyboard_config.rs (named layout applied, num lock kept, locale1 used when switching back to an unset xkb). The last two failed against the upstream code and pass with the fix.
  • cargo test -p niri -p niri-config -p niri-ipc: 316 and 81 pass. cargo fmt --check clean. cargo clippy on those crates adds no warnings on changed lines. Workspace-wide clippy fails in niri-visual-tests on a missing gdk-pixbuf, unrelated to this branch.

Hardware test on sixseven (pending)

  • With keyboard "Logitech USB Receiver" { xkb { … } } next to the unnamed block, typing on each keyboard produces its own layout from the first keystroke
  • Num lock state survives switching between the two keyboards
  • niri msg event-stream reports KeyboardLayoutsChanged when the active device changes
  • Editing a named block and reloading takes effect on the next keystroke
  • Media keys from the receiver's separate "Consumer Control" device do not disturb typing

Known limitations carried from upstream: track-layout "window" with several named keyboards keeps one layout per surface, not per (keyboard, surface); XWayland clients see a single X11 keymap.

barrulus and others added 6 commits September 17, 2026 13:33
Adds an optional device name argument to the keyboard block, e.g.
`keyboard "My External Keyboard" { ... }`, collected into
Input::keyboards. Named blocks inherit unset settings from the unnamed
keyboard block in the same input section, then fall back to the
regular defaults.

Input::fallback_keyboard() and Input::keyboard_named() resolve the
config for a given device name, used by the following commit to
actually apply per-device settings.
The Wayland protocol only allows one active keymap per seat, so
per-device configuration is implemented by resolving and re-applying
the keyboard config for whichever physical device sent the most
recent key event, caching the resolved config by device so idle
devices are free. This reuses the same set_xkb_config()/
change_repeat_info() calls already used for the layout-switch bind
and config hot-reload.

The resolved-config cache is cleared and the tracked active keyboard
reset to the fallback whenever the keyboard configs change on reload,
so edits take effect on the next keystroke instead of sticking to
stale per-device settings.

Also switches bind key repeat and the accessibility repeat-delay
heuristic to use the currently tracked keyboard instead of always the
fallback one.
…eyboards

The per-device switch path applied the new keymap directly through the
Smithay keyboard handle instead of State::set_xkb_config(), so every
switch between keyboards reset num lock. It also ignored the locale1
settings that a config reload uses when xkb is left unset, and never
told IPC listeners that the layout list had changed.

Split the switch out of the device resolution into
switch_keyboard_config() so it can be driven from the headless fixture,
and make it follow the same chain as reload_config(): xkb file first,
then the rules-based config with locale1 filling in the defaults, via
the num-lock-preserving wrapper, followed by the keyboard-layouts IPC
event.
niri msg keyboard-layouts lists layout names, not devices.
@barrulus barrulus added the theme: input-binds Binds, chords and modes, multi-action binds, gestures, runtime device toggles label Sep 17, 2026
@github-project-automation github-project-automation Bot moved this to Todo in Biri Sep 17, 2026
@barrulus
barrulus merged commit fcac9ec into main Sep 17, 2026
26 checks passed
@github-project-automation github-project-automation Bot moved this from Todo to Done in Biri Sep 17, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

theme: input-binds Binds, chords and modes, multi-action binds, gestures, runtime device toggles

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

2 participants