git.lucas.co / cce-compositor
Wayland compositor (wlroots)
git clone https://git.lucas.co/cce-compositor.git

commit1a3c0c1e68f0682876ce1854fb97782798839ae6
parentbde507c2b7
authorIsaac Freund <[email protected]>
date2026-03-30 19:38
Keyboard: work around wlroots modifiers limitation

This makes setting capslock/numlock with the river-xkb-config-v1
protocol actually work in a sensible way, at least for the libinput
backend.

 river/Keyboard.zig | 29 ++++++++++++++++++++++++-----
 1 file changed, 24 insertions(+), 5 deletions(-)

diff --git a/river/Keyboard.zig b/river/Keyboard.zig
index 2990e8e..b31892c 100644
--- a/river/Keyboard.zig
+++ b/river/Keyboard.zig
@@ -82,15 +82,32 @@ pub fn create(seat: *Seat, wlr_device: *wlr.InputDevice, virtual: bool) !*Keyboa
         wlr_keyboard.events.keymap.add(&keyboard.keymap);
     } else {
         keyboard.keymap.link.init();
-        // We must set a keymap even though this is not the wlr_keyboard river
-        // exposes to clients. If there is no keymap set, wlroots will not emit
-        // the modifiers event when using the Wayland or X11 backend.
-        _ = wlr_keyboard.setKeymap(keyboard.config.keymap);
+        if (shouldSetKeymap()) {
+            _ = wlr_keyboard.setKeymap(keyboard.config.keymap);
+        }
     }
 
     return keyboard;
 }
 
+// We don't want to ever set a keymap for backend-created wlr.Keyboards.
+// Setting a keymap means that modifiers will be buggy and LEDs will be out of sync.
+// However, if we don't set a keymap we don't get modifiers events from the backend
+// at all currently due to a wlroots bug. This is even worse, so set a keymap
+// despite the bugginess for backends that generate keyboard modifiers events.
+// TODO(wlroots) https://gitlab.freedesktop.org/wlroots/wlroots/-/merge_requests/5324
+fn shouldSetKeymap() bool {
+    var wayland_or_x11: bool = false;
+    server.backend.multiForEachBackend(*bool, shouldSetKeymapIter, &wayland_or_x11);
+    return wayland_or_x11;
+}
+
+fn shouldSetKeymapIter(backend: *wlr.Backend, wayland_or_x11: *bool) void {
+    if (backend.isWl() or (wlr.config.has_x11_backend and backend.isX11())) {
+        wayland_or_x11.* = true;
+    }
+}
+
 pub fn setGroup(keyboard: *Keyboard) void {
     assert(keyboard.group == null);
     const seat = keyboard.device.seat;
@@ -126,7 +143,9 @@ pub fn setRepeatInfo(keyboard: *Keyboard, rate: u31, delay: u31) void {
 
 pub fn setKeymap(keyboard: *Keyboard, keymap: *xkb.Keymap) void {
     assert(!keyboard.device.virtual);
-    _ = keyboard.device.wlr_device.toKeyboard().setKeymap(keymap);
+    if (shouldSetKeymap()) {
+        _ = keyboard.device.wlr_device.toKeyboard().setKeymap(keyboard.config.keymap);
+    }
     if (keyboard.config.keymap) |old| old.unref();
     keyboard.config.keymap = keymap.ref();
     if (keyboard.group) |group| {