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

commit0b85e2c059aef5b7043c194db96a89d60370d610
parent698c165ae8
authorIsaac Freund <[email protected]>
date2026-01-03 15:12
InputDevice: don't advertise if virtual

Currently river incorrectly creates river_input_device_v1 objects
for virtual input devices when a river_input_manager_v1 is bound.

Fix this and add more asserts/cleanup.

 river/InputDevice.zig    | 16 +++++++++++-----
 river/InputManager.zig   |  6 +++++-
 river/Keyboard.zig       | 32 +++++++++++---------------------
 river/LibinputDevice.zig |  1 +
 4 files changed, 28 insertions(+), 27 deletions(-)

diff --git a/river/InputDevice.zig b/river/InputDevice.zig
index 758d086..0846b71 100644
--- a/river/InputDevice.zig
+++ b/river/InputDevice.zig
@@ -23,6 +23,7 @@ const log = std.log.scoped(.input);
 
 seat: *Seat,
 wlr_device: *wlr.InputDevice,
+virtual: bool,
 objects: wl.list.Head(river.InputDeviceV1, null),
 
 libinput: LibinputDevice,
@@ -47,6 +48,7 @@ pub fn init(
     device.* = .{
         .seat = seat,
         .wlr_device = wlr_device,
+        .virtual = virtual,
         .libinput = undefined,
         .objects = undefined,
         .link = undefined,
@@ -57,17 +59,20 @@ pub fn init(
     wlr_device.data = device;
     wlr_device.events.destroy.add(&device.remove);
 
-    log.debug("new input device: {s}-{s}", .{
+    log.debug("new {s}input device: {s}-{s}", .{
+        if (virtual) "virtual " else "",
         @tagName(wlr_device.type),
         wlr_device.name orelse "unknown",
     });
 
     if (!virtual) {
         var it = server.input_manager.objects.safeIterator(.forward);
-        while (it.next()) |im_v1| device.createObject(im_v1);
-    }
-    if (wlr_device.getLibinputDevice()) |handle| {
-        device.libinput.init(@ptrCast(handle));
+        while (it.next()) |im_v1| {
+            device.createObject(im_v1);
+        }
+        if (wlr_device.getLibinputDevice()) |handle| {
+            device.libinput.init(@ptrCast(handle));
+        }
     }
 
     // The wlroots Wayland and X11 backends support multiple outputs
@@ -91,6 +96,7 @@ pub fn init(
 }
 
 pub fn createObject(device: *InputDevice, im_v1: *river.InputManagerV1) void {
+    assert(!device.virtual);
     const device_type: river.InputDeviceV1.Type = switch (device.wlr_device.type) {
         .keyboard => .keyboard,
         .pointer => .pointer,
diff --git a/river/InputManager.zig b/river/InputManager.zig
index e3a165b..c7852a1 100644
--- a/river/InputManager.zig
+++ b/river/InputManager.zig
@@ -115,7 +115,11 @@ fn bind(client: *wl.Client, im: *InputManager, version: u32, id: u32) void {
     im.objects.append(im_v1);
     {
         var it = im.devices.iterator(.forward);
-        while (it.next()) |device| device.createObject(im_v1);
+        while (it.next()) |device| {
+            if (!device.virtual) {
+                device.createObject(im_v1);
+            }
+        }
     }
 }
 
diff --git a/river/Keyboard.zig b/river/Keyboard.zig
index e857e7b..4c59d2c 100644
--- a/river/Keyboard.zig
+++ b/river/Keyboard.zig
@@ -37,7 +37,6 @@ device: InputDevice,
 device_destroyed: bool = false,
 queued_events: u32 = 0,
 
-virtual: bool,
 config: Config,
 /// Only null during initialization or due to allocation failure.
 group: ?*KeyboardGroup = null,
@@ -53,7 +52,6 @@ pub fn create(seat: *Seat, wlr_device: *wlr.InputDevice, virtual: bool) !*Keyboa
     errdefer util.gpa.destroy(keyboard);
 
     keyboard.* = .{
-        .virtual = virtual,
         .config = .{
             .keymap = if (virtual) wlr_keyboard.keymap else server.config.keymap,
         },
@@ -74,35 +72,27 @@ pub fn create(seat: *Seat, wlr_device: *wlr.InputDevice, virtual: bool) !*Keyboa
 pub fn setGroup(keyboard: *Keyboard) void {
     assert(keyboard.group == null);
     const seat = keyboard.device.seat;
-    if (keyboard.virtual) {
-        // Virtual keyboards set their own keymap and require independent modifier state.
-        // Therefore, they are always placed in their own group of one.
-        keyboard.group = KeyboardGroup.create(seat, keyboard.config, true) catch |err| switch (err) {
-            error.OutOfMemory => blk: {
-                log.err("out of memory", .{});
-                break :blk null;
-            },
-        };
-    } else {
+    // Virtual keyboards set their own keymap and require independent modifier state.
+    // Therefore, they are always placed in their own group of one.
+    if (!keyboard.device.virtual) {
         var it = seat.keyboard_groups.iterator(.forward);
         while (it.next()) |group| {
             if (keyboard.config.eql(&group.config)) {
                 keyboard.group = group.ref();
-                break;
+                return;
             }
-        } else {
-            keyboard.group = KeyboardGroup.create(seat, keyboard.config, false) catch |err| switch (err) {
-                error.OutOfMemory => blk: {
-                    log.err("out of memory", .{});
-                    break :blk null;
-                },
-            };
         }
     }
+    keyboard.group = KeyboardGroup.create(seat, keyboard.config, keyboard.device.virtual) catch |err| switch (err) {
+        error.OutOfMemory => {
+            log.err("out of memory", .{});
+            return;
+        },
+    };
 }
 
 pub fn setRepeatInfo(keyboard: *Keyboard, rate: u31, delay: u31) void {
-    assert(!keyboard.virtual);
+    assert(!keyboard.device.virtual);
     keyboard.config.repeat_rate = rate;
     keyboard.config.repeat_delay = delay;
     if (keyboard.group) |group| {
diff --git a/river/LibinputDevice.zig b/river/LibinputDevice.zig
index 2f4b9c4..a2a15ef 100644
--- a/river/LibinputDevice.zig
+++ b/river/LibinputDevice.zig
@@ -50,6 +50,7 @@ pub fn createObject(device: *LibinputDevice, config_v1: *river.LibinputConfigV1)
     config_v1.sendLibinputDevice(object);
     {
         const base: *InputDevice = @fieldParentPtr("libinput", device);
+        assert(!base.virtual);
         var it = base.objects.iterator(.forward);
         while (it.next()) |input_device_v1| {
             if (object.getClient() == input_device_v1.getClient()) {