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

commit1f1ca9d7b728bf86845009942bfbcadcc0c30ebe
parent226163f904
authorKirill Primak <[email protected]>
date2026-04-27 16:52
protocol: add "done" event to input management objects

 build.zig                              |  6 +++---
 protocol/river-input-management-v1.xml | 14 ++++++++++++--
 protocol/river-libinput-config-v1.xml  | 14 ++++++++++++--
 protocol/river-xkb-config-v1.xml       | 16 +++++++++++++---
 river/InputDevice.zig                  |  3 +++
 river/InputManager.zig                 |  2 +-
 river/LibinputConfig.zig               |  2 +-
 river/LibinputDevice.zig               | 34 ++++++++++++++++++++++++++++++++++
 river/XkbConfig.zig                    |  2 +-
 river/XkbKeyboard.zig                  | 10 ++++++++++
 10 files changed, 90 insertions(+), 13 deletions(-)

diff --git a/build.zig b/build.zig
index 39e179a..428ccc1 100644
--- a/build.zig
+++ b/build.zig
@@ -126,9 +126,9 @@ pub fn build(b: *Build) !void {
     scanner.generate("river_window_manager_v1", 4);
     scanner.generate("river_xkb_bindings_v1", 3);
     scanner.generate("river_layer_shell_v1", 1);
-    scanner.generate("river_input_manager_v1", 1);
-    scanner.generate("river_libinput_config_v1", 1);
-    scanner.generate("river_xkb_config_v1", 1);
+    scanner.generate("river_input_manager_v1", 2);
+    scanner.generate("river_libinput_config_v1", 2);
+    scanner.generate("river_xkb_config_v1", 2);
 
     scanner.generate("zwlr_output_power_manager_v1", 1);
     scanner.generate("zwlr_layer_shell_v1", 4);
diff --git a/protocol/river-input-management-v1.xml b/protocol/river-input-management-v1.xml
index 81f707c..e0f5498 100644
--- a/protocol/river-input-management-v1.xml
+++ b/protocol/river-input-management-v1.xml
@@ -32,7 +32,7 @@
     document are to be interpreted as described in IETF RFC 2119.
   </description>
 
-  <interface name="river_input_manager_v1" version="1">
+  <interface name="river_input_manager_v1" version="2">
     <description summary="input manager global interface">
       Input manager global interface.
     </description>
@@ -110,7 +110,7 @@
     </event>
   </interface>
 
-  <interface name="river_input_device_v1" version="1">
+  <interface name="river_input_device_v1" version="2">
     <description summary="an input device">
       An input device represents a physical keyboard, mouse, touchscreen, or
       drawing tablet tool. It is assigned to exactly one seat at a time.
@@ -230,5 +230,15 @@
       <arg name="width" type="int"/>
       <arg name="height" type="int"/>
     </request>
+
+    <event name="done" since="2">
+      <description summary="all information has been sent">
+        This event is sent after all information about the input device has
+        been sent.
+
+        This allows changes to one or more river_input_device_v1 properties to
+        be seen as atomic, even if they happen via multiple events.
+      </description>
+    </event>
   </interface>
 </protocol>
diff --git a/protocol/river-libinput-config-v1.xml b/protocol/river-libinput-config-v1.xml
index 5e38b08..d98a2c4 100644
--- a/protocol/river-libinput-config-v1.xml
+++ b/protocol/river-libinput-config-v1.xml
@@ -47,7 +47,7 @@
     document are to be interpreted as described in IETF RFC 2119.
   </description>
 
-  <interface name="river_libinput_config_v1" version="1">
+  <interface name="river_libinput_config_v1" version="2">
     <description summary="libinput config global interface">
       Global interface for configuring libinput devices. This global should
       only be advertised if river_input_manager_v1 is advertised as well.
@@ -115,7 +115,7 @@
     </request>
   </interface>
 
-  <interface name="river_libinput_device_v1" version="1">
+  <interface name="river_libinput_device_v1" version="2">
     <description summary="a libinput device">
       In general, *_support events will be sent exactly once directly after the
       river_libinput_device_v1 is created. *_default events will be sent after
@@ -828,6 +828,16 @@
       <arg name="result" type="new_id" interface="river_libinput_result_v1"/>
       <arg name="angle" type="uint"/>
     </request>
+
+    <event name="done" since="2">
+      <description summary="all information has been sent">
+        This event is sent after all information about the libinput device has
+        been sent.
+
+        This allows changes to one or more river_libinput_device_v1 properties
+        to be seen as atomic, even if they happen via multiple events.
+      </description>
+    </event>
   </interface>
 
   <interface name="river_libinput_accel_config_v1" version="1">
diff --git a/protocol/river-xkb-config-v1.xml b/protocol/river-xkb-config-v1.xml
index 2e3b8dd..f73ec67 100644
--- a/protocol/river-xkb-config-v1.xml
+++ b/protocol/river-xkb-config-v1.xml
@@ -33,7 +33,7 @@
     document are to be interpreted as described in IETF RFC 2119.
   </description>
 
-  <interface name="river_xkb_config_v1" version="1">
+  <interface name="river_xkb_config_v1" version="2">
     <description summary="xkb config global interface">
       Global interface for configuring xkb devices.
 
@@ -107,7 +107,7 @@
     </event>
   </interface>
 
-  <interface name="river_xkb_keymap_v1" version="1">
+  <interface name="river_xkb_keymap_v1" version="2">
     <description summary="xkbcommon keymap">
       This object is the result of attempting to create an xkbcommon keymap.
     </description>
@@ -137,7 +137,7 @@
     </event>
   </interface>
 
-  <interface name="river_xkb_keyboard_v1" version="1">
+  <interface name="river_xkb_keyboard_v1" version="2">
     <description summary="xkbcommon keyboard device">
       This object represent a physical keyboard which has its configuration and
       state managed by xkbcommon.
@@ -273,5 +273,15 @@
         again whenever the numlock state changes.
       </description>
     </event>
+
+    <event name="done" since="2">
+      <description summary="all information has been sent">
+        This event is sent after all information about the keyboard has been
+        sent.
+
+        This allows changes to one or more river_xkb_keyboard_v1 properties to
+        be seen as atomic, even if they happen via multiple events.
+      </description>
+    </event>
   </interface>
 </protocol>
diff --git a/river/InputDevice.zig b/river/InputDevice.zig
index 060e595..63e68bd 100644
--- a/river/InputDevice.zig
+++ b/river/InputDevice.zig
@@ -120,6 +120,9 @@ pub fn createObject(device: *InputDevice, im_v1: *river.InputManagerV1) void {
     object.setHandler(*InputDevice, handleRequest, handleDestroy, device);
     object.sendType(device_type);
     object.sendName(device.wlr_device.name orelse "");
+    if (object.getVersion() >= 2) {
+        object.sendDone();
+    }
 }
 
 pub fn deinit(device: *InputDevice) void {
diff --git a/river/InputManager.zig b/river/InputManager.zig
index b5bb9e3..960b36e 100644
--- a/river/InputManager.zig
+++ b/river/InputManager.zig
@@ -51,7 +51,7 @@ new_text_input: wl.Listener(*wlr.TextInputV3) = .init(handleNewTextInput),
 
 pub fn init(input_manager: *InputManager) !void {
     input_manager.* = .{
-        .global = try wl.Global.create(server.wl_server, river.InputManagerV1, 1, *InputManager, input_manager, bind),
+        .global = try wl.Global.create(server.wl_server, river.InputManagerV1, 2, *InputManager, input_manager, bind),
         // These are automatically freed when the display is destroyed
         .idle_notifier = try wlr.IdleNotifierV1.create(server.wl_server),
         .relative_pointer_manager = try wlr.RelativePointerManagerV1.create(server.wl_server),
diff --git a/river/LibinputConfig.zig b/river/LibinputConfig.zig
index fb20a22..4759fc8 100644
--- a/river/LibinputConfig.zig
+++ b/river/LibinputConfig.zig
@@ -24,7 +24,7 @@ server_destroy: wl.Listener(*wl.Server) = .init(handleServerDestroy),
 
 pub fn init(config: *LibinputConfig) !void {
     config.* = .{
-        .global = try wl.Global.create(server.wl_server, river.LibinputConfigV1, 1, *LibinputConfig, config, bind),
+        .global = try wl.Global.create(server.wl_server, river.LibinputConfigV1, 2, *LibinputConfig, config, bind),
         .objects = undefined,
         .devices = undefined,
     };
diff --git a/river/LibinputDevice.zig b/river/LibinputDevice.zig
index 183d7ee..507368f 100644
--- a/river/LibinputDevice.zig
+++ b/river/LibinputDevice.zig
@@ -168,6 +168,9 @@ pub fn createObject(device: *LibinputDevice, config_v1: *river.LibinputConfigV1)
         object.sendRotationDefault(c.libinput_device_config_rotation_get_default_angle(device.libinput));
         object.sendRotationCurrent(c.libinput_device_config_rotation_get_angle(device.libinput));
     }
+    if (object.getVersion() >= 2) {
+        object.sendDone();
+    }
 }
 
 pub fn deinit(device: *LibinputDevice) void {
@@ -200,6 +203,7 @@ fn handleRequest(
     request: river.LibinputDeviceV1.Request,
     device: *LibinputDevice,
 ) void {
+    var send_done = false;
     switch (request) {
         .destroy => object.destroy(),
         .set_send_events => |args| {
@@ -209,6 +213,7 @@ fn handleRequest(
                 const current = c.libinput_device_config_send_events_get_mode(device.libinput);
                 var it = device.objects.iterator(.forward);
                 while (it.next()) |o| o.sendSendEventsCurrent(@bitCast(current));
+                send_done = true;
             }
         },
         .set_tap => |args| {
@@ -225,6 +230,7 @@ fn handleRequest(
                 const current = c.libinput_device_config_tap_get_enabled(device.libinput);
                 var it = device.objects.iterator(.forward);
                 while (it.next()) |o| o.sendTapCurrent(@enumFromInt(current));
+                send_done = true;
             }
         },
         .set_tap_button_map => |args| {
@@ -241,6 +247,7 @@ fn handleRequest(
                 const current = c.libinput_device_config_tap_get_button_map(device.libinput);
                 var it = device.objects.iterator(.forward);
                 while (it.next()) |o| o.sendTapButtonMapCurrent(@enumFromInt(current));
+                send_done = true;
             }
         },
         .set_drag => |args| {
@@ -257,6 +264,7 @@ fn handleRequest(
                 const current = c.libinput_device_config_tap_get_drag_enabled(device.libinput);
                 var it = device.objects.iterator(.forward);
                 while (it.next()) |o| o.sendDragCurrent(@enumFromInt(current));
+                send_done = true;
             }
         },
         .set_drag_lock => |args| {
@@ -274,6 +282,7 @@ fn handleRequest(
                 const current = c.libinput_device_config_tap_get_drag_lock_enabled(device.libinput);
                 var it = device.objects.iterator(.forward);
                 while (it.next()) |o| o.sendDragLockCurrent(@enumFromInt(current));
+                send_done = true;
             }
         },
         .set_three_finger_drag => |args| {
@@ -291,6 +300,7 @@ fn handleRequest(
                 const current = c.libinput_device_config_3fg_drag_get_enabled(device.libinput);
                 var it = device.objects.iterator(.forward);
                 while (it.next()) |o| o.sendThreeFingerDragCurrent(@enumFromInt(current));
+                send_done = true;
             }
         },
         .set_calibration_matrix => |args| {
@@ -308,6 +318,7 @@ fn handleRequest(
                 _ = c.libinput_device_config_calibration_get_matrix(device.libinput, &current);
                 var it = device.objects.iterator(.forward);
                 while (it.next()) |o| o.sendCalibrationMatrixCurrent(&array);
+                send_done = true;
             }
         },
         .set_accel_profile => |args| {
@@ -326,6 +337,7 @@ fn handleRequest(
                 const current = c.libinput_device_config_accel_get_profile(device.libinput);
                 var it = device.objects.iterator(.forward);
                 while (it.next()) |o| o.sendAccelProfileCurrent(@enumFromInt(current));
+                send_done = true;
             }
         },
         .set_accel_speed => |args| {
@@ -342,6 +354,7 @@ fn handleRequest(
                 var array: wl.Array = .{ .size = bytes.len, .alloc = bytes.len, .data = bytes.ptr };
                 var it = device.objects.iterator(.forward);
                 while (it.next()) |o| o.sendAccelSpeedCurrent(&array);
+                send_done = true;
             }
         },
         .apply_accel_config => |args| {
@@ -356,6 +369,7 @@ fn handleRequest(
                 const current = c.libinput_device_config_accel_get_profile(device.libinput);
                 var it = device.objects.iterator(.forward);
                 while (it.next()) |o| o.sendAccelProfileCurrent(@enumFromInt(current));
+                send_done = true;
             }
         },
         .set_natural_scroll => |args| {
@@ -372,6 +386,7 @@ fn handleRequest(
                 const current = c.libinput_device_config_scroll_get_natural_scroll_enabled(device.libinput);
                 var it = device.objects.iterator(.forward);
                 while (it.next()) |o| o.sendNaturalScrollCurrent(if (current != 0) .enabled else .disabled);
+                send_done = true;
             }
         },
         .set_left_handed => |args| {
@@ -388,6 +403,7 @@ fn handleRequest(
                 const current = c.libinput_device_config_left_handed_get(device.libinput);
                 var it = device.objects.iterator(.forward);
                 while (it.next()) |o| o.sendLeftHandedCurrent(if (current != 0) .enabled else .disabled);
+                send_done = true;
             }
         },
         .set_click_method => |args| {
@@ -405,6 +421,7 @@ fn handleRequest(
                 const current = c.libinput_device_config_click_get_method(device.libinput);
                 var it = device.objects.iterator(.forward);
                 while (it.next()) |o| o.sendClickMethodCurrent(@enumFromInt(current));
+                send_done = true;
             }
         },
         .set_clickfinger_button_map => |args| {
@@ -421,6 +438,7 @@ fn handleRequest(
                 const current = c.libinput_device_config_click_get_clickfinger_button_map(device.libinput);
                 var it = device.objects.iterator(.forward);
                 while (it.next()) |o| o.sendClickfingerButtonMapCurrent(@enumFromInt(current));
+                send_done = true;
             }
         },
         .set_middle_emulation => |args| {
@@ -437,6 +455,7 @@ fn handleRequest(
                 const current = c.libinput_device_config_middle_emulation_get_enabled(device.libinput);
                 var it = device.objects.iterator(.forward);
                 while (it.next()) |o| o.sendMiddleEmulationCurrent(@enumFromInt(current));
+                send_done = true;
             }
         },
         .set_scroll_method => |args| {
@@ -455,6 +474,7 @@ fn handleRequest(
                 const current = c.libinput_device_config_scroll_get_method(device.libinput);
                 var it = device.objects.iterator(.forward);
                 while (it.next()) |o| o.sendScrollMethodCurrent(@enumFromInt(current));
+                send_done = true;
             }
         },
         .set_scroll_button => |args| {
@@ -464,6 +484,7 @@ fn handleRequest(
                 const current = c.libinput_device_config_scroll_get_button(device.libinput);
                 var it = device.objects.iterator(.forward);
                 while (it.next()) |o| o.sendScrollButtonCurrent(current);
+                send_done = true;
             }
         },
         .set_scroll_button_lock => |args| {
@@ -480,6 +501,7 @@ fn handleRequest(
                 const current = c.libinput_device_config_scroll_get_button_lock(device.libinput);
                 var it = device.objects.iterator(.forward);
                 while (it.next()) |o| o.sendScrollButtonLockCurrent(@enumFromInt(current));
+                send_done = true;
             }
         },
         .set_dwt => |args| {
@@ -496,6 +518,7 @@ fn handleRequest(
                 const current = c.libinput_device_config_dwt_get_enabled(device.libinput);
                 var it = device.objects.iterator(.forward);
                 while (it.next()) |o| o.sendDwtCurrent(if (current != 0) .enabled else .disabled);
+                send_done = true;
             }
         },
         .set_dwtp => |args| {
@@ -512,6 +535,7 @@ fn handleRequest(
                 const current = c.libinput_device_config_dwtp_get_enabled(device.libinput);
                 var it = device.objects.iterator(.forward);
                 while (it.next()) |o| o.sendDwtpCurrent(if (current != 0) .enabled else .disabled);
+                send_done = true;
             }
         },
         .set_rotation => |args| {
@@ -521,9 +545,19 @@ fn handleRequest(
                 const current = c.libinput_device_config_rotation_get_angle(device.libinput);
                 var it = device.objects.iterator(.forward);
                 while (it.next()) |o| o.sendRotationCurrent(current);
+                send_done = true;
             }
         },
     }
+
+    if (send_done) {
+        var it = device.objects.iterator(.forward);
+        while (it.next()) |o| {
+            if (o.getVersion() >= 2) {
+                o.sendDone();
+            }
+        }
+    }
 }
 
 const Result = struct {
diff --git a/river/XkbConfig.zig b/river/XkbConfig.zig
index 159604e..2107a84 100644
--- a/river/XkbConfig.zig
+++ b/river/XkbConfig.zig
@@ -36,7 +36,7 @@ pub fn init(config: *XkbConfig) !void {
     defer default_keymap.unref();
 
     config.* = .{
-        .global = try wl.Global.create(server.wl_server, river.XkbConfigV1, 1, *XkbConfig, config, bind),
+        .global = try wl.Global.create(server.wl_server, river.XkbConfigV1, 2, *XkbConfig, config, bind),
         .context = context.ref(),
         .default_keymap = default_keymap.ref(),
         .objects = undefined,
diff --git a/river/XkbKeyboard.zig b/river/XkbKeyboard.zig
index 0921bc6..2fff5d6 100644
--- a/river/XkbKeyboard.zig
+++ b/river/XkbKeyboard.zig
@@ -83,6 +83,9 @@ pub fn createObject(xkb_keyboard: *XkbKeyboard, config_v1: *river.XkbConfigV1) v
             object.sendNumlockDisabled();
         }
     }
+    if (object.getVersion() >= 2) {
+        object.sendDone();
+    }
 }
 
 pub fn deinit(xkb_keyboard: *XkbKeyboard) void {
@@ -179,11 +182,13 @@ pub fn sendState(
     const sent = &xkb_keyboard.sent;
     var it = xkb_keyboard.objects.iterator(.forward);
     while (it.next()) |object| {
+        var send_done = false;
         if (sent.layout_index != layout_index or
             (sent.layout_name == null) != (layout_name == null) or
             (layout_name != null and mem.orderZ(u8, layout_name.?, sent.layout_name.?) != .eq))
         {
             object.sendLayout(layout_index, layout_name);
+            send_done = true;
         }
         if (sent.capslock != capslock) {
             if (capslock) {
@@ -191,6 +196,7 @@ pub fn sendState(
             } else {
                 object.sendCapslockDisabled();
             }
+            send_done = true;
         }
         if (sent.numlock != numlock) {
             if (numlock) {
@@ -198,6 +204,10 @@ pub fn sendState(
             } else {
                 object.sendNumlockDisabled();
             }
+            send_done = true;
+        }
+        if (send_done and object.getVersion() >= 2) {
+            object.sendDone();
         }
     }
     sent.layout_index = layout_index;