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

commita99b0518ab79ad64b409fec5d7362cb2e8d5b489
parent939dc6094e
authorIsaac Freund <[email protected]>
date2025-06-30 12:44
Seat: ignore virtual keyboards until keymap set

The wlr-virtual-keyboard-v1 protocol fails to make keyboard creation
atomic. There is nothing a client can do with a virtual keyboard that
has no keymap except for destroy it, so we can completely ignore them
until a keymap is set.

This fixes a regression with fcitx caused by river sending the null
keymap of a new virtual keyboard to fcitx.

(cherry picked from commit d08c170571722348eca3c665612704c6e7e3d800)

 river/InputManager.zig | 40 ++++++++++++++++++++++++++++++++++++++--
 1 file changed, 38 insertions(+), 2 deletions(-)

diff --git a/river/InputManager.zig b/river/InputManager.zig
index ec8ea50..c8c2102 100644
--- a/river/InputManager.zig
+++ b/river/InputManager.zig
@@ -176,10 +176,46 @@ fn handleNewVirtualKeyboard(
     _: *wl.Listener(*wlr.VirtualKeyboardV1),
     virtual_keyboard: *wlr.VirtualKeyboardV1,
 ) void {
-    const seat: *Seat = @alignCast(@ptrCast(virtual_keyboard.seat.data));
-    seat.addDevice(&virtual_keyboard.keyboard.base, true);
+    const no_keymap = util.gpa.create(NoKeymapVirtKeyboard) catch {
+        log.err("out of memory", .{});
+        return;
+    };
+    errdefer util.gpa.destroy(no_keymap);
+
+    no_keymap.* = .{
+        .virtual_keyboard = virtual_keyboard,
+    };
+    virtual_keyboard.keyboard.base.events.destroy.add(&no_keymap.destroy);
+    virtual_keyboard.keyboard.events.keymap.add(&no_keymap.keymap);
 }
 
+/// Ignore virtual keyboards completely until the client sets a keymap
+/// Yes, wlroots should probably do this for us.
+const NoKeymapVirtKeyboard = struct {
+    virtual_keyboard: *wlr.VirtualKeyboardV1,
+    destroy: wl.Listener(*wlr.InputDevice) = .init(handleDestroy),
+    keymap: wl.Listener(*wlr.Keyboard) = .init(handleKeymap),
+
+    fn handleDestroy(listener: *wl.Listener(*wlr.InputDevice), _: *wlr.InputDevice) void {
+        const no_keymap: *NoKeymapVirtKeyboard = @fieldParentPtr("destroy", listener);
+
+        no_keymap.destroy.link.remove();
+        no_keymap.keymap.link.remove();
+
+        util.gpa.destroy(no_keymap);
+    }
+
+    fn handleKeymap(listener: *wl.Listener(*wlr.Keyboard), _: *wlr.Keyboard) void {
+        const no_keymap: *NoKeymapVirtKeyboard = @fieldParentPtr("keymap", listener);
+        const virtual_keyboard = no_keymap.virtual_keyboard;
+
+        handleDestroy(&no_keymap.destroy, &virtual_keyboard.keyboard.base);
+
+        const seat: *Seat = @alignCast(@ptrCast(virtual_keyboard.seat.data));
+        seat.addDevice(&virtual_keyboard.keyboard.base, true);
+    }
+};
+
 fn handleNewConstraint(
     _: *wl.Listener(*wlr.PointerConstraintV1),
     wlr_constraint: *wlr.PointerConstraintV1,