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

commita04d5341c7209632452f130947ef5276d2ad8987
parent1cd8cee1f2
authorIsaac Freund <[email protected]>
date2025-12-27 16:41
protocol: add river_seat_v1.pointer_position

The motivating use case is determining window placement by relative
cursor position in the current window at the time a new window is
opened. This will certainly support other interesting use-cases as well.

 protocol/river-window-management-v1.xml | 16 ++++++++++++++++
 river/Seat.zig                          | 13 +++++++++++++
 2 files changed, 29 insertions(+)

diff --git a/protocol/river-window-management-v1.xml b/protocol/river-window-management-v1.xml
index cdec753..cda16a5 100644
--- a/protocol/river-window-management-v1.xml
+++ b/protocol/river-window-management-v1.xml
@@ -1503,6 +1503,22 @@
       <arg name="name" type="string"/>
       <arg name="size" type="uint"/>
     </request>
+
+    <event name="pointer_position" since="2">
+      <description summary="The current position of the pointer">
+        The current position of the pointer in the compositor's logical
+        coordinate space.
+
+        This state is special in that a change in pointer position alone must
+        not cause the compositor to start a manage sequence.
+
+        Assuming the seat has a pointer, this event must be sent in every manage
+        sequence unless there is no change in x/y position since the last time this
+        event was sent.
+      </description>
+      <arg name="x" type="int"/>
+      <arg name="y" type="int"/>
+    </event>
   </interface>
 
   <interface name="river_pointer_binding_v1" version="2">
diff --git a/river/Seat.zig b/river/Seat.zig
index 0fe675e..2993724 100644
--- a/river/Seat.zig
+++ b/river/Seat.zig
@@ -19,6 +19,7 @@ const Seat = @This();
 const build_options = @import("build_options");
 const std = @import("std");
 const assert = std.debug.assert;
+const math = std.math;
 const wlr = @import("wlroots");
 const wayland = @import("wayland");
 const wl = wayland.server.wl;
@@ -129,6 +130,8 @@ wm_scheduled: struct {
 wm_sent: struct {
     /// The window entered/hovered by the pointer, if any
     hovered: ?Window.Ref = null,
+    x: i32 = 0,
+    y: i32 = 0,
 } = .{},
 link_sent: wl.list.Link,
 
@@ -361,6 +364,16 @@ pub fn manageStart(seat: *Seat) void {
             }
         }
 
+        {
+            const x = math.lossyCast(i32, seat.cursor.wlr_cursor.x);
+            const y = math.lossyCast(i32, seat.cursor.wlr_cursor.y);
+            if (new or x != seat.wm_sent.x or y != seat.wm_sent.y) {
+                if (seat_v1.getVersion() >= 2) {
+                    seat_v1.sendPointerPosition(x, y);
+                }
+            }
+        }
+
         switch (seat.wm_scheduled.interaction) {
             .none => {},
             .window => |ref| {