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

commit94abafc0359c5977164bc6da506ca18a476a9d0a
parent3a9fe7b93d
authorIsaac Freund <[email protected]>
date2025-08-19 16:38
Cursor: fix DnD related assertion failure

On drag and drop end:

thread 3498 panic: reached unreachable code
/river/river/Cursor.zig:486:33: 0x1145fb2 in processButton (river)
                .passthrough => unreachable,
                                ^
/river/river/Seat.zig:269:62: 0x1132465 in processEvents (river)
            .pointer_button => |ev| seat.cursor.processButton(&ev),
                                                             ^
/river/river/Seat.zig:246:27: 0x1131f2e in queueEvent (river)
        seat.processEvents();
                          ^
/river/river/Cursor.zig:759:27: 0x112325c in wrapper (river)
    cursor.seat.queueEvent(.{ .pointer_button = event.* }) catch {};
                          ^

 protocol/river-window-management-v1.xml |  2 +-
 river/Cursor.zig                        | 24 ++++++++++++++++++------
 river/Seat.zig                          |  2 +-
 3 files changed, 20 insertions(+), 8 deletions(-)

diff --git a/protocol/river-window-management-v1.xml b/protocol/river-window-management-v1.xml
index 1942c9f..31a48ca 100644
--- a/protocol/river-window-management-v1.xml
+++ b/protocol/river-window-management-v1.xml
@@ -80,7 +80,7 @@
       manage sequence in between, for example during interactive move/resize of
       windows or if a window independently changes its own dimensions.
 
-      To summarize the main loop of this protocol is as follows:
+      To summarize, the main loop of this protocol is as follows:
 
       1. The server sends events indicating all changes since the last
          manage sequence followed by the manage_start event.
diff --git a/river/Cursor.zig b/river/Cursor.zig
index b32f24d..997eef8 100644
--- a/river/Cursor.zig
+++ b/river/Cursor.zig
@@ -51,6 +51,8 @@ const Mode = union(enum) {
     /// This mode is entered when a binding is triggered and exited when there
     /// are no longer any buttons pressed.
     ignore,
+    /// A drag and drop is in progress.
+    drag,
     down: struct {
         // TODO: To handle the surface with pointer focus being moved during
         // down mode we need to store the starting location of the surface as
@@ -330,11 +332,11 @@ pub fn processMotionRelative(cursor: *Cursor, event: *const wlr.Pointer.event.Mo
     }
 
     switch (cursor.mode) {
-        .passthrough, .ignore, .down => {
+        .passthrough, .drag, .ignore, .down => {
             cursor.wlr_cursor.move(event.device, dx, dy);
 
             switch (cursor.mode) {
-                .passthrough => {
+                .passthrough, .drag => {
                     cursor.updateHovered();
                     cursor.passthrough(event.time_msec);
                 },
@@ -467,6 +469,16 @@ pub fn processButton(cursor: *Cursor, event: *const wlr.Pointer.event.Button) vo
                 cursor.clearFocus();
                 return;
             },
+            .drag => {
+                if (server.scene.at(cursor.wlr_cursor.x, cursor.wlr_cursor.y)) |at| {
+                    cursor.interact(at);
+                    if (at.surface != null) {
+                        _ = cursor.seat.wlr_seat.pointerNotifyButton(event.time_msec, event.button, event.state);
+                        return;
+                    }
+                }
+                cursor.clearFocus();
+            },
             // Pointer focus does not change while in down mode.
             .down => {
                 _ = cursor.seat.wlr_seat.pointerNotifyButton(event.time_msec, event.button, event.state);
@@ -484,8 +496,8 @@ pub fn processButton(cursor: *Cursor, event: *const wlr.Pointer.event.Button) vo
 
             switch (cursor.mode) {
                 .passthrough => unreachable,
-                .down, .ignore => {
-                    if (cursor.mode == .down) {
+                .drag, .down, .ignore => {
+                    if (cursor.mode != .ignore) {
                         _ = cursor.seat.wlr_seat.pointerNotifyButton(event.time_msec, event.button, event.state);
                     }
                     if (cursor.pressed.count() == 0) {
@@ -699,7 +711,7 @@ pub fn updateState(cursor: *Cursor) void {
     }
 
     switch (cursor.mode) {
-        .passthrough => {
+        .passthrough, .drag => {
             cursor.updateHovered();
 
             const now = posix.clock_gettime(posix.CLOCK.MONOTONIC) catch @panic("CLOCK_MONOTONIC not supported");
@@ -714,7 +726,7 @@ pub fn updateState(cursor: *Cursor) void {
 
 /// Pass an event on to the surface under the cursor, if any.
 fn passthrough(cursor: *Cursor, time: u32) void {
-    assert(cursor.mode == .passthrough);
+    assert(cursor.mode == .passthrough or cursor.mode == .drag);
 
     if (server.scene.at(cursor.wlr_cursor.x, cursor.wlr_cursor.y)) |result| {
         if (result.data == .lock_surface) {
diff --git a/river/Seat.zig b/river/Seat.zig
index 8ad11a3..b2e9e0f 100644
--- a/river/Seat.zig
+++ b/river/Seat.zig
@@ -802,7 +802,7 @@ fn handleStartDrag(listener: *wl.Listener(*wlr.Drag), wlr_drag: *wlr.Drag) void
     switch (wlr_drag.grab_type) {
         .keyboard_pointer => {
             seat.drag = .pointer;
-            seat.cursor.mode = .passthrough;
+            seat.cursor.mode = .drag;
         },
         .keyboard_touch => seat.drag = .touch,
         .keyboard => unreachable,