diff --git a/river/Cursor.zig b/river/Cursor.zig index 17b752e..e67e7d5 100644 --- a/river/Cursor.zig +++ b/river/Cursor.zig @@ -750,24 +750,7 @@ fn processMotion(self: *Self, device: *wlr.InputDevice, time: u32, delta_x: f64, switch (self.mode) { .passthrough => { self.wlr_cursor.move(device, dx, dy); - - if (self.surfaceAt()) |result| { - const focus_change = self.seat.wlr_seat.pointer_state.focused_surface != result.surface; - if (server.config.focus_follows_cursor == .normal and focus_change) { - switch (result.parent) { - .view => |view| { - if (self.seat.focused != .view or self.seat.focused.view != view) { - self.seat.focusOutput(view.output); - self.seat.focus(view); - server.root.startTransaction(); - } - }, - .layer_surface => {}, - .xwayland_unmanaged => assert(build_options.xwayland), - } - } - } - + self.checkFocusFollowsCursor(); self.passthrough(time); }, .down => |view| { @@ -826,6 +809,27 @@ fn processMotion(self: *Self, device: *wlr.InputDevice, time: u32, delta_x: f64, } } +pub fn checkFocusFollowsCursor(self: *Self) void { + // Don't do focus-follows-cursor if a drag is in progress as focus change can't occur + if (self.seat.pointer_drag) return; + if (server.config.focus_follows_cursor == .disabled) return; + if (self.surfaceAt()) |result| { + if (self.seat.wlr_seat.pointer_state.focused_surface != result.surface) { + switch (result.parent) { + .view => |view| { + if (self.seat.focused != .view or self.seat.focused.view != view) { + self.seat.focusOutput(view.output); + self.seat.focus(view); + server.root.startTransaction(); + } + }, + .layer_surface => {}, + .xwayland_unmanaged => assert(build_options.xwayland), + } + } + } +} + /// Handle potential change in location of views on the output, as well as /// the target view of a cursor operation potentially being moved to a non-visible tag, /// becoming fullscreen, etc. diff --git a/river/Seat.zig b/river/Seat.zig index 966263d..ad94fc8 100644 --- a/river/Seat.zig +++ b/river/Seat.zig @@ -19,6 +19,7 @@ const Self = @This(); const build_options = @import("build_options"); const std = @import("std"); +const assert = std.debug.assert; const wlr = @import("wlroots"); const wl = @import("wayland").server.wl; const xkb = @import("xkbcommon"); @@ -81,11 +82,15 @@ focus_stack: ViewStack(*View) = .{}, /// List of status tracking objects relaying changes to this seat to clients. status_trackers: std.SinglyLinkedList(SeatStatus) = .{}, +/// True if a pointer drag is currently in progress +pointer_drag: bool = false, + request_set_selection: wl.Listener(*wlr.Seat.event.RequestSetSelection) = wl.Listener(*wlr.Seat.event.RequestSetSelection).init(handleRequestSetSelection), request_start_drag: wl.Listener(*wlr.Seat.event.RequestStartDrag) = wl.Listener(*wlr.Seat.event.RequestStartDrag).init(handleRequestStartDrag), start_drag: wl.Listener(*wlr.Drag) = wl.Listener(*wlr.Drag).init(handleStartDrag), +pointer_drag_destroy: wl.Listener(*wlr.Drag) = wl.Listener(*wlr.Drag).init(handlePointerDragDestroy), request_set_primary_selection: wl.Listener(*wlr.Seat.event.RequestSetPrimarySelection) = wl.Listener(*wlr.Seat.event.RequestSetPrimarySelection).init(handleRequestSetPrimarySelection), @@ -443,7 +448,15 @@ fn handleRequestStartDrag( const self = @fieldParentPtr(Self, "request_start_drag", listener); if (!self.wlr_seat.validatePointerGrabSerial(event.origin, event.serial)) { - log.debug("ignoring request to start drag, failed to validate serial {}", .{event.serial}); + log.debug("ignoring request to start drag, " ++ + "failed to validate pointer serial {}", .{event.serial}); + if (event.drag.source) |source| source.destroy(); + return; + } + + if (self.pointer_drag) { + log.debug("ignoring request to start pointer drag, " ++ + "another pointer drag is already in progress", .{}); if (event.drag.source) |source| source.destroy(); return; } @@ -452,12 +465,14 @@ fn handleRequestStartDrag( self.wlr_seat.startPointerDrag(event.drag, event.serial); } -fn handleStartDrag( - listener: *wl.Listener(*wlr.Drag), - wlr_drag: *wlr.Drag, -) void { +fn handleStartDrag(listener: *wl.Listener(*wlr.Drag), wlr_drag: *wlr.Drag) void { const self = @fieldParentPtr(Self, "start_drag", listener); + assert(wlr_drag.grab_type == .keyboard_pointer); + + self.pointer_drag = true; + wlr_drag.events.destroy.add(&self.pointer_drag_destroy); + if (wlr_drag.icon) |wlr_drag_icon| { const node = util.gpa.create(std.SinglyLinkedList(DragIcon).Node) catch { log.crit("out of memory", .{}); @@ -469,6 +484,14 @@ fn handleStartDrag( self.cursor.mode = .passthrough; } +fn handlePointerDragDestroy(listener: *wl.Listener(*wlr.Drag), wlr_drag: *wlr.Drag) void { + const self = @fieldParentPtr(Self, "pointer_drag_destroy", listener); + self.pointer_drag = false; + self.pointer_drag_destroy.link.remove(); + self.cursor.checkFocusFollowsCursor(); + self.cursor.updateState(); +} + fn handleRequestSetPrimarySelection( listener: *wl.Listener(*wlr.Seat.event.RequestSetPrimarySelection), event: *wlr.Seat.event.RequestSetPrimarySelection,