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

commitf65f08b6d806f6a5c59b5b44f7371e03608bba5b
parentcc1a8599c9
authorIsaac Freund <[email protected]>
date2025-07-10 14:48
rwm: use global names for wl_output/wl_seat references

This simplifies the code and prevents possible races. Clients need to
keep track of wl_output/wl_seat names anyways to handle global_remove
properly.

 protocol/river-window-management-v1.xml | 24 +++++++++++-----------
 river/Output.zig                        | 35 ++++++---------------------------
 river/Seat.zig                          |  9 +--------
 3 files changed, 19 insertions(+), 49 deletions(-)

diff --git a/protocol/river-window-management-v1.xml b/protocol/river-window-management-v1.xml
index 9f2e34d..80eb7b3 100644
--- a/protocol/river-window-management-v1.xml
+++ b/protocol/river-window-management-v1.xml
@@ -1129,16 +1129,16 @@
 
     <event name="wl_output">
       <description summary="corresponding wl_output">
-        The wl_output object corresponding to the river_output_v1.
+        The wl_output object corresponding to the river_output_v1. The argument
+        is the global name of the wl_output advertised with wl_registry.global.
+
+        It is guaranteed that the corresponding wl_output is advertised before
+        this event is sent.
 
         This event is sent exactly once. The wl_output associated with a
         river_output_v1 cannot change. It is guaranteed that there is a 1-to-1
         mapping between wl_output and river_output_v1 objects.
 
-        Note that this event will only be sent after the client binds the
-        wl_output global. It is guaranteed that the corresponding wl_output is
-        advertised before the river_window_manager_v1.output event is sent.
-
         The global_remove event for the corresponding wl_output may be sent
         before the river_output_v1.remove event. This is due to the fact that
         river_output_v1 state changes are synced to the river window management
@@ -1148,7 +1148,7 @@
         wl_output interface such as the name/description. It also may need the
         wl_output object to start screencopy for example.
       </description>
-      <arg name="wl_output" type="object" interface="wl_output"/>
+      <arg name="name" type="uint" summary="name of the wl_output global"/>
     </event>
 
     <event name="position">
@@ -1231,16 +1231,16 @@
 
     <event name="wl_seat">
       <description summary="corresponding wl_seat">
-        The wl_seat object corresponding to the river_seat_v1.
+        The wl_seat object corresponding to the river_seat_v1. The argument is
+        the global name of the wl_seat advertised with wl_registry.global.
+
+        It is guaranteed that the corresponding wl_seat is advertised before
+        this event is sent.
 
         This event is sent exactly once. The wl_seat associated with a
         river_seat_v1 cannot change. It is guaranteed that there is a 1-to-1
         mapping between wl_seat and river_seat_v1 objects.
 
-        Note that this event will only be sent after the client binds the
-        wl_seat global. It is guaranteed that the corresponding wl_seat is
-        advertised before the river_window_manager_v1.seat event is sent.
-
         The global_remove event for the corresponding wl_seat may be sent before
         the river_seat_v1.remove event. This is due to the fact that
         river_seat_v1 state changes are synced to the river window management
@@ -1250,7 +1250,7 @@
         state changes based on normal input events received by its shell
         surfaces for example.
       </description>
-      <arg name="wl_seat" type="object" interface="wl_seat"/>
+      <arg name="name" type="uint" summary="name of the wl_seat global"/>
     </event>
 
     <request name="focus_window">
diff --git a/river/Output.zig b/river/Output.zig
index aea90d2..18be1e5 100644
--- a/river/Output.zig
+++ b/river/Output.zig
@@ -132,7 +132,6 @@ wlr_output: ?*wlr.Output,
 scene_output: ?*wlr.SceneOutput,
 
 object: ?*river.OutputV1 = null,
-sent_wl_output: bool = false,
 
 /// Tracks the currently presented frame on the output as it pertains to ext-session-lock.
 /// The output is initially considered blanked:
@@ -168,7 +167,6 @@ link_sent: wl.list.Link,
 current: State,
 
 destroy: wl.Listener(*wlr.Output) = .init(handleDestroy),
-bind: wl.Listener(*wlr.Output.event.Bind) = .init(handleBind),
 request_state: wl.Listener(*wlr.Output.event.RequestState) = .init(handleRequestState),
 frame: wl.Listener(*wlr.Output) = .init(handleFrame),
 present: wl.Listener(*wlr.Output.event.Present) = .init(handlePresent),
@@ -208,7 +206,6 @@ pub fn create(wlr_output: *wlr.Output) !void {
     output.link_sent.init();
 
     wlr_output.events.destroy.add(&output.destroy);
-    wlr_output.events.bind.add(&output.bind);
     wlr_output.events.request_state.add(&output.request_state);
     wlr_output.events.frame.add(&output.frame);
     wlr_output.events.present.add(&output.present);
@@ -229,7 +226,6 @@ fn handleDestroy(listener: *wl.Listener(*wlr.Output), wlr_output: *wlr.Output) v
     output.link.remove();
 
     output.destroy.link.remove();
-    output.bind.link.remove();
     output.request_state.link.remove();
     output.frame.link.remove();
     output.present.link.remove();
@@ -243,23 +239,11 @@ fn handleDestroy(listener: *wl.Listener(*wlr.Output), wlr_output: *wlr.Output) v
     server.wm.dirtyWindowing();
 }
 
-fn handleBind(listener: *wl.Listener(*wlr.Output.event.Bind), event: *wlr.Output.event.Bind) void {
-    const output: *Output = @fieldParentPtr("bind", listener);
-    // Guard against a client binding the same wl_output global more than once.
-    if (output.sent_wl_output) {
-        return;
-    }
-    if (output.object) |output_v1| {
-        if (output_v1.getClient() == event.resource.getClient()) {
-            output_v1.sendWlOutput(event.resource);
-            output.sent_wl_output = true;
-        }
-    }
-}
-
 pub fn manageStart(output: *Output) void {
     switch (output.scheduled.state) {
         .enabled, .disabled_soft => {
+            const wlr_output = output.wlr_output.?;
+
             if (server.wm.object) |wm_v1| {
                 const new = output.object == null;
                 const output_v1 = output.object orelse blk: {
@@ -280,17 +264,10 @@ pub fn manageStart(output: *Output) void {
                 const sent = &output.sent;
 
                 if (new) {
-                    const client = output_v1.getClient();
-                    var it = output.wlr_output.?.resources.iterator(.forward);
-                    while (it.next()) |wl_output| {
-                        if (client == wl_output.getClient()) {
-                            output_v1.sendWlOutput(wl_output);
-                            output.sent_wl_output = true;
-                            break;
-                        }
-                    } else {
-                        output.sent_wl_output = false;
-                    }
+                    // Ensure that the wl_output global has been created,
+                    // this is a noop if it already has been.
+                    wlr_output.createGlobal(server.wl_server);
+                    output_v1.sendWlOutput(wlr_output.global.?.getName(output_v1.getClient()));
                 }
 
                 if (new or pending.width() != sent.width() or pending.height() != sent.height()) {
diff --git a/river/Seat.zig b/river/Seat.zig
index 0f95fdf..5814da2 100644
--- a/river/Seat.zig
+++ b/river/Seat.zig
@@ -315,14 +315,7 @@ pub fn manageStart(seat: *Seat) void {
         errdefer comptime unreachable;
 
         if (new) {
-            // TODO(wlroots): send on bind if wl_seat global not yet bound:
-            // https://gitlab.freedesktop.org/wlroots/wlroots/-/merge_requests/5099
-            const client = seat_v1.getClient();
-            if (seat.wlr_seat.clientForWlClient(client)) |seat_client| {
-                if (seat_client.resources.first()) |wl_seat| {
-                    seat_v1.sendWlSeat(wl_seat);
-                }
-            }
+            seat_v1.sendWlSeat(seat.wlr_seat.global.getName(seat_v1.getClient()));
         }
 
         if (new) {