Persistent Wayland client that applies a custom XKB keymap to every keyboard in River 0.4+.
River no longer accepts a keymap file through riverctl. Full keymaps
(especially ones with custom types / compat) must be set through the
river-xkb-config-v1 protocol. A one-shot client is not enough: after a TTY
(VT) switch, suspend/resume, or keyboard hotplug, libinput re-opens devices and
River recreates them with the default keymap. This daemon stays connected
and re-applies your keymap on every new keyboard.
Works with any compiled keymap file — not tied to a specific layout.
- River 0.4+ (advertises
river-xkb-config-v1) libwayland-clientwayland-scanner(build only)- A compiled XKB keymap file (see below)
git clone https://github.com/PaulBunch/river-xkb-load.git
cd river-xkb-load
make
make install # PREFIX=/usr/local by default
# or: make install PREFIX=$HOME/.localNotes:
Protocol XML files (river-xkb-config-v1, river-input-management-v1) are
vendored from River (MIT). The input-management protocol is required at build
time because keyboards expose a river_input_device_v1 object that we destroy.
In ~/.config/river/init:
river-xkb-load /path/to/your/compiled.xkb &The process must keep running for the whole session. After a TTY switch you should see log lines like:
2026-08-03T09:22:41Z river-xkb-load: keyboard removed
2026-08-03T09:22:55Z river-xkb-load: new keyboard appeared
2026-08-03T09:22:55Z river-xkb-load: keymap assigned to keyboard
Check that it is alive:
pgrep -a river-xkb-loadReload after changing the keymap file:
pkill river-xkb-load
river-xkb-load /path/to/your/compiled.xkb &When the River session ends cleanly, the compositor signals the init process
group and the daemon should exit. If the session is killed abruptly, a leftover
process may remain until the next wl_display_dispatch failure — send
SIGTERM/pkill river-xkb-load or start it under a systemd user unit bound to
the graphical session.
river-xkb-load expects a flat keymap text file (with a trailing NUL added
internally). Example with xkbcli:
xkbcli compile-keymap \
--include "$HOME/.config/xkb" \
--include-defaults /path/to/your-layout.xkb \
> "$HOME/.config/xkb/my-layout"Any valid keymap produced by libxkbcommon is fine.
Messages go to stderr with UTC ISO 8601 timestamps:
2026-08-03T09:18:02Z river-xkb-load: watching for keyboards (keymap: ...)
2026-08-03T09:18:02Z river-xkb-load: new keyboard appeared
2026-08-03T09:18:02Z river-xkb-load: keymap assigned to keyboard
Redirect if needed:
river-xkb-load ~/.config/xkb/my-layout >~/.config/xkb/river-xkb.log 2>&1 &- Local Wayland client only (Unix socket to the compositor). No listening port.
- Runs as your user, same trust boundary as any other session client.
- Does not require root.
- Reads one keymap file and sends it to the compositor; no shell, no network.
- Connects to the Wayland display and binds
river_xkb_config_v1. - Caches the keymap file in memory (appends a NUL byte required by xkbcommon).
- On each
xkb_keyboardevent, creates a keymap object and callsset_keymap. - On
removed, destroys the keyboard object. - Stays in the event loop until
SIGINT/SIGTERMor display disconnect.
Example consumer with a multi-layer custom layout:
Protocol definitions are taken from River (MIT).
Semantic Versioning (0.y.z until 1.0.0). See VERSIONING.md.
river-xkb-load --version # print version and exitMIT. See LICENSE.
Protocol XML files retain their original copyright (© Isaac Freund, MIT).