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

commit6db5a3e332c034721b2dd127a82fb3b88a68951c
parent62589a5897
authorIsaac Freund <[email protected]>
date2023-03-05 22:39
pointer-constraints: implement protocol

Now with 50% less pointer warping!

The new implementation requires the user to move the cursor into the
constraint region before the constraint is activated in order to keep
behavior more predictable.

 build.zig                   |   1 +
 river/Cursor.zig            |  70 ++++++++++----
 river/InputManager.zig      |  20 ++++
 river/PointerConstraint.zig | 225 ++++++++++++++++++++++++++++++++++++++++++++
 river/Root.zig              |  26 ++---
 river/SceneNodeData.zig     |   2 +-
 river/Seat.zig              |  34 +++++--
 7 files changed, 335 insertions(+), 43 deletions(-)

diff --git a/build.zig b/build.zig
index e72d1ff..65c2db3 100644
--- a/build.zig
+++ b/build.zig
@@ -108,6 +108,7 @@ pub fn build(b: *zbs.Builder) !void {
 
     scanner.generate("xdg_wm_base", 2);
     scanner.generate("zwp_pointer_gestures_v1", 3);
+    scanner.generate("zwp_pointer_constraints_v1", 1);
     scanner.generate("ext_session_lock_manager_v1", 1);
 
     scanner.generate("zriver_control_v1", 1);
diff --git a/river/Cursor.zig b/river/Cursor.zig
index 5ac6076..e0949c5 100644
--- a/river/Cursor.zig
+++ b/river/Cursor.zig
@@ -35,6 +35,7 @@ const DragIcon = @import("DragIcon.zig");
 const LayerSurface = @import("LayerSurface.zig");
 const LockSurface = @import("LockSurface.zig");
 const Output = @import("Output.zig");
+const PointerConstraint = @import("PointerConstraint.zig");
 const Root = @import("Root.zig");
 const Seat = @import("Seat.zig");
 const View = @import("View.zig");
@@ -145,6 +146,11 @@ hide_cursor_timer: *wl.EventSource,
 hidden: bool = false,
 may_need_warp: bool = false,
 
+/// The pointer constraint for the surface that currently has keyboard focus, if any.
+/// This constraint is not necessarily active, activation only occurs once the cursor
+/// has been moved inside the constraint region.
+constraint: ?*PointerConstraint = null,
+
 last_focus_follows_cursor_target: ?*View = null,
 
 /// Keeps track of the last known location of all touch points in layout coordinates.
@@ -344,7 +350,7 @@ fn handleButton(listener: *wl.Listener(*wlr.Pointer.event.Button), event: *wlr.P
     }
 
     if (server.root.at(self.wlr_cursor.x, self.wlr_cursor.y)) |result| {
-        if (result.node == .view and self.handlePointerMapping(event, result.node.view)) {
+        if (result.data == .view and self.handlePointerMapping(event, result.data.view)) {
             // If a mapping is triggered don't send events to clients.
             return;
         }
@@ -372,7 +378,7 @@ fn handleButton(listener: *wl.Listener(*wlr.Pointer.event.Button), event: *wlr.P
 
 /// Requires a call to Root.applyPending()
 fn updateKeyboardFocus(self: Self, result: Root.AtResult) void {
-    switch (result.node) {
+    switch (result.data) {
         .view => |view| {
             self.seat.focus(view);
         },
@@ -678,6 +684,10 @@ fn handleHideCursorTimeout(self: *Self) c_int {
 }
 
 pub fn startMove(cursor: *Self, view: *View) void {
+    if (cursor.constraint) |constraint| {
+        if (constraint.state == .active) constraint.deactivate();
+    }
+
     const new_mode: Mode = .{ .move = .{
         .view = view,
         .offset_x = @floatToInt(i32, cursor.wlr_cursor.x) - view.current.box.x,
@@ -687,6 +697,10 @@ pub fn startMove(cursor: *Self, view: *View) void {
 }
 
 pub fn startResize(cursor: *Self, view: *View, proposed_edges: ?wlr.Edges) void {
+    if (cursor.constraint) |constraint| {
+        if (constraint.state == .active) constraint.deactivate();
+    }
+
     const edges = blk: {
         if (proposed_edges) |edges| {
             if (edges.top or edges.bottom or edges.left or edges.right) {
@@ -803,21 +817,40 @@ fn processMotion(self: *Self, device: *wlr.InputDevice, time: u32, delta_x: f64,
 
     var dx: f64 = delta_x;
     var dy: f64 = delta_y;
+
+    if (self.constraint) |constraint| {
+        if (constraint.state == .active) {
+            switch (constraint.wlr_constraint.type) {
+                .locked => return,
+                .confined => constraint.confine(&dx, &dy),
+            }
+        }
+    }
+
     switch (self.mode) {
-        .passthrough => {
+        .passthrough, .down => {
             self.wlr_cursor.move(device, dx, dy);
-            self.checkFocusFollowsCursor();
-            self.passthrough(time);
-            self.updateDragIcons();
-        },
-        .down => |down| {
-            self.wlr_cursor.move(device, dx, dy);
-            self.seat.wlr_seat.pointerNotifyMotion(
-                time,
-                down.sx + (self.wlr_cursor.x - down.lx),
-                down.sy + (self.wlr_cursor.y - down.ly),
-            );
+
+            switch (self.mode) {
+                .passthrough => {
+                    self.checkFocusFollowsCursor();
+                    self.passthrough(time);
+                },
+                .down => |data| {
+                    self.seat.wlr_seat.pointerNotifyMotion(
+                        time,
+                        data.sx + (self.wlr_cursor.x - data.lx),
+                        data.sy + (self.wlr_cursor.y - data.ly),
+                    );
+                },
+                else => unreachable,
+            }
+
             self.updateDragIcons();
+
+            if (self.constraint) |constraint| {
+                constraint.maybeActivate();
+            }
         },
         .move => |*data| {
             dx += data.delta_x;
@@ -904,7 +937,7 @@ pub fn checkFocusFollowsCursor(self: *Self) void {
     if (self.seat.drag == .pointer) return;
     if (server.config.focus_follows_cursor == .disabled) return;
     if (server.root.at(self.wlr_cursor.x, self.wlr_cursor.y)) |result| {
-        switch (result.node) {
+        switch (result.data) {
             .view => |view| {
                 // Don't re-focus the last focused view when the mode is .normal
                 if (server.config.focus_follows_cursor == .normal and
@@ -941,6 +974,11 @@ pub fn updateState(self: *Self) void {
     if (self.may_need_warp) {
         self.warp();
     }
+
+    if (self.constraint) |constraint| {
+        constraint.updateState();
+    }
+
     if (self.shouldPassthrough()) {
         self.mode = .passthrough;
         var now: os.timespec = undefined;
@@ -986,7 +1024,7 @@ fn passthrough(self: *Self, time: u32) void {
     assert(self.mode == .passthrough);
 
     if (server.root.at(self.wlr_cursor.x, self.wlr_cursor.y)) |result| {
-        if (result.node == .lock_surface) {
+        if (result.data == .lock_surface) {
             assert(server.lock_manager.state != .unlocked);
         } else {
             assert(server.lock_manager.state != .locked);
diff --git a/river/InputManager.zig b/river/InputManager.zig
index 2992f78..0d0eb72 100644
--- a/river/InputManager.zig
+++ b/river/InputManager.zig
@@ -29,6 +29,7 @@ const util = @import("util.zig");
 const InputConfig = @import("InputConfig.zig");
 const InputDevice = @import("InputDevice.zig");
 const Keyboard = @import("Keyboard.zig");
+const PointerConstraint = @import("PointerConstraint.zig");
 const Seat = @import("Seat.zig");
 const Switch = @import("Switch.zig");
 
@@ -42,6 +43,7 @@ idle_notifier: *wlr.IdleNotifierV1,
 relative_pointer_manager: *wlr.RelativePointerManagerV1,
 virtual_pointer_manager: *wlr.VirtualPointerManagerV1,
 virtual_keyboard_manager: *wlr.VirtualKeyboardManagerV1,
+pointer_constraints: *wlr.PointerConstraintsV1,
 
 configs: std.ArrayList(InputConfig),
 devices: wl.list.Head(InputDevice, .link),
@@ -53,6 +55,8 @@ new_virtual_pointer: wl.Listener(*wlr.VirtualPointerManagerV1.event.NewPointer)
     wl.Listener(*wlr.VirtualPointerManagerV1.event.NewPointer).init(handleNewVirtualPointer),
 new_virtual_keyboard: wl.Listener(*wlr.VirtualKeyboardV1) =
     wl.Listener(*wlr.VirtualKeyboardV1).init(handleNewVirtualKeyboard),
+new_constraint: wl.Listener(*wlr.PointerConstraintV1) =
+    wl.Listener(*wlr.PointerConstraintV1).init(handleNewConstraint),
 
 pub fn init(self: *Self) !void {
     const seat_node = try util.gpa.create(std.TailQueue(Seat).Node);
@@ -64,6 +68,7 @@ pub fn init(self: *Self) !void {
         .relative_pointer_manager = try wlr.RelativePointerManagerV1.create(server.wl_server),
         .virtual_pointer_manager = try wlr.VirtualPointerManagerV1.create(server.wl_server),
         .virtual_keyboard_manager = try wlr.VirtualKeyboardManagerV1.create(server.wl_server),
+        .pointer_constraints = try wlr.PointerConstraintsV1.create(server.wl_server),
         .configs = std.ArrayList(InputConfig).init(util.gpa),
 
         .devices = undefined,
@@ -78,12 +83,17 @@ pub fn init(self: *Self) !void {
     server.backend.events.new_input.add(&self.new_input);
     self.virtual_pointer_manager.events.new_virtual_pointer.add(&self.new_virtual_pointer);
     self.virtual_keyboard_manager.events.new_virtual_keyboard.add(&self.new_virtual_keyboard);
+    self.pointer_constraints.events.new_constraint.add(&self.new_constraint);
 }
 
 pub fn deinit(self: *Self) void {
     // This function must be called after the backend has been destroyed
     assert(self.devices.empty());
 
+    self.new_virtual_pointer.link.remove();
+    self.new_virtual_keyboard.link.remove();
+    self.new_constraint.link.remove();
+
     while (self.seats.pop()) |seat_node| {
         seat_node.data.deinit();
         util.gpa.destroy(seat_node);
@@ -138,3 +148,13 @@ fn handleNewVirtualKeyboard(
     const seat = @intToPtr(*Seat, virtual_keyboard.seat.data);
     seat.addDevice(&virtual_keyboard.keyboard.base);
 }
+
+fn handleNewConstraint(
+    _: *wl.Listener(*wlr.PointerConstraintV1),
+    wlr_constraint: *wlr.PointerConstraintV1,
+) void {
+    PointerConstraint.create(wlr_constraint) catch {
+        log.err("out of memory", .{});
+        wlr_constraint.resource.postNoMemory();
+    };
+}
diff --git a/river/PointerConstraint.zig b/river/PointerConstraint.zig
new file mode 100644
index 0000000..59fd4cf
--- /dev/null
+++ b/river/PointerConstraint.zig
@@ -0,0 +1,225 @@
+// This file is part of river, a dynamic tiling wayland compositor.
+//
+// Copyright 2023 The River Developers
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, version 3.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <https://www.gnu.org/licenses/>.
+
+const PointerConstraint = @This();
+
+const std = @import("std");
+const assert = std.debug.assert;
+const wlr = @import("wlroots");
+const wl = @import("wayland").server.wl;
+
+const server = &@import("main.zig").server;
+const util = @import("util.zig");
+
+const Seat = @import("Seat.zig");
+
+const log = std.log.scoped(.pointer_constraint);
+
+wlr_constraint: *wlr.PointerConstraintV1,
+
+state: union(enum) {
+    inactive,
+    active: struct {
+        /// Node of the active constraint surface in the scene graph.
+        node: *wlr.SceneNode,
+        /// Coordinates of the pointer on activation in the surface coordinate system.
+        sx: f64,
+        sy: f64,
+    },
+} = .inactive,
+
+destroy: wl.Listener(*wlr.PointerConstraintV1) = wl.Listener(*wlr.PointerConstraintV1).init(handleDestroy),
+set_region: wl.Listener(void) = wl.Listener(void).init(handleSetRegion),
+
+node_destroy: wl.Listener(void) = wl.Listener(void).init(handleNodeDestroy),
+
+pub fn create(wlr_constraint: *wlr.PointerConstraintV1) error{OutOfMemory}!void {
+    const seat = @intToPtr(*Seat, wlr_constraint.seat.data);
+
+    const constraint = try util.gpa.create(PointerConstraint);
+    errdefer util.gpa.destroy(constraint);
+
+    constraint.* = .{
+        .wlr_constraint = wlr_constraint,
+    };
+    wlr_constraint.data = @ptrToInt(constraint);
+
+    wlr_constraint.events.destroy.add(&constraint.destroy);
+    wlr_constraint.events.set_region.add(&constraint.set_region);
+
+    if (seat.wlr_seat.keyboard_state.focused_surface) |surface| {
+        if (surface == wlr_constraint.surface) {
+            assert(seat.cursor.constraint == null);
+            seat.cursor.constraint = constraint;
+            constraint.maybeActivate();
+        }
+    }
+}
+
+pub fn maybeActivate(constraint: *PointerConstraint) void {
+    const seat = @intToPtr(*Seat, constraint.wlr_constraint.seat.data);
+
+    assert(seat.cursor.constraint == constraint);
+    assert(seat.wlr_seat.keyboard_state.focused_surface == constraint.wlr_constraint.surface);
+
+    if (constraint.state == .active) return;
+
+    if (seat.cursor.mode == .move or seat.cursor.mode == .resize) return;
+
+    const result = server.root.at(seat.cursor.wlr_cursor.x, seat.cursor.wlr_cursor.y) orelse return;
+    if (result.surface != constraint.wlr_constraint.surface) return;
+
+    const sx = @floatToInt(i32, result.sx);
+    const sy = @floatToInt(i32, result.sy);
+    if (!constraint.wlr_constraint.region.containsPoint(sx, sy, null)) return;
+
+    assert(constraint.state == .inactive);
+    constraint.state = .{
+        .active = .{
+            .node = result.node,
+            .sx = result.sx,
+            .sy = result.sy,
+        },
+    };
+    result.node.events.destroy.add(&constraint.node_destroy);
+
+    log.info("activating pointer constraint", .{});
+
+    constraint.wlr_constraint.sendActivated();
+}
+
+/// Called when the cursor position or content in the scene graph changes
+pub fn updateState(constraint: *PointerConstraint) void {
+    const seat = @intToPtr(*Seat, constraint.wlr_constraint.seat.data);
+
+    assert(seat.wlr_seat.keyboard_state.focused_surface == constraint.wlr_constraint.surface);
+
+    constraint.maybeActivate();
+
+    if (constraint.state != .active) return;
+
+    var lx: i32 = undefined;
+    var ly: i32 = undefined;
+    if (!constraint.state.active.node.coords(&lx, &ly)) {
+        log.info("deactivating pointer constraint, scene node disabled", .{});
+        constraint.deactivate();
+        return;
+    }
+
+    const warp_lx = @intToFloat(f64, lx) + constraint.state.active.sx;
+    const warp_ly = @intToFloat(f64, ly) + constraint.state.active.sy;
+    if (!seat.cursor.wlr_cursor.warp(null, warp_lx, warp_ly)) {
+        log.info("deactivating pointer constraint, could not warp cursor", .{});
+        constraint.deactivate();
+        return;
+    }
+}
+
+pub fn confine(constraint: *PointerConstraint, dx: *f64, dy: *f64) void {
+    assert(constraint.state == .active);
+    assert(constraint.wlr_constraint.type == .confined);
+
+    const region = &constraint.wlr_constraint.region;
+    const sx = constraint.state.active.sx;
+    const sy = constraint.state.active.sy;
+    var new_sx: f64 = undefined;
+    var new_sy: f64 = undefined;
+    assert(wlr.region.confine(region, sx, sy, sx + dx.*, sy + dy.*, &new_sx, &new_sy));
+
+    dx.* = new_sx - sx;
+    dy.* = new_sy - sy;
+
+    constraint.state.active.sx = new_sx;
+    constraint.state.active.sy = new_sy;
+}
+
+pub fn deactivate(constraint: *PointerConstraint) void {
+    const seat = @intToPtr(*Seat, constraint.wlr_constraint.seat.data);
+
+    assert(seat.cursor.constraint == constraint);
+    assert(constraint.state == .active);
+
+    constraint.warpToHintIfSet();
+
+    constraint.state = .inactive;
+    constraint.node_destroy.link.remove();
+    constraint.wlr_constraint.sendDeactivated();
+}
+
+fn warpToHintIfSet(constraint: *PointerConstraint) void {
+    const seat = @intToPtr(*Seat, constraint.wlr_constraint.seat.data);
+
+    if (constraint.wlr_constraint.current.committed.cursor_hint) {
+        var lx: i32 = undefined;
+        var ly: i32 = undefined;
+        _ = constraint.state.active.node.coords(&lx, &ly);
+
+        const sx = constraint.wlr_constraint.current.cursor_hint.x;
+        const sy = constraint.wlr_constraint.current.cursor_hint.y;
+        _ = seat.cursor.wlr_cursor.warp(null, @intToFloat(f64, lx) + sx, @intToFloat(f64, ly) + sy);
+        _ = seat.wlr_seat.pointerWarp(sx, sy);
+    }
+}
+
+fn handleNodeDestroy(listener: *wl.Listener(void)) void {
+    const constraint = @fieldParentPtr(PointerConstraint, "node_destroy", listener);
+
+    log.info("deactivating pointer constraint, scene node destroyed", .{});
+    constraint.deactivate();
+}
+
+fn handleDestroy(listener: *wl.Listener(*wlr.PointerConstraintV1), _: *wlr.PointerConstraintV1) void {
+    const constraint = @fieldParentPtr(PointerConstraint, "destroy", listener);
+    const seat = @intToPtr(*Seat, constraint.wlr_constraint.seat.data);
+
+    if (constraint.state == .active) {
+        // We can't simply call deactivate() here as it calls sendDeactivated(),
+        // which could in the case of a oneshot constraint lifetime recursively
+        // destroy the constraint.
+        constraint.warpToHintIfSet();
+        constraint.node_destroy.link.remove();
+    }
+
+    constraint.destroy.link.remove();
+    constraint.set_region.link.remove();
+
+    if (seat.cursor.constraint == constraint) {
+        seat.cursor.constraint = null;
+    }
+
+    util.gpa.destroy(constraint);
+}
+
+fn handleSetRegion(listener: *wl.Listener(void)) void {
+    const constraint = @fieldParentPtr(PointerConstraint, "set_region", listener);
+    const seat = @intToPtr(*Seat, constraint.wlr_constraint.seat.data);
+
+    switch (constraint.state) {
+        .active => |state| {
+            const sx = @floatToInt(i32, state.sx);
+            const sy = @floatToInt(i32, state.sy);
+            if (!constraint.wlr_constraint.region.containsPoint(sx, sy, null)) {
+                log.info("deactivating pointer constraint, region change left pointer outside constraint", .{});
+                constraint.deactivate();
+            }
+        },
+        .inactive => {
+            if (seat.cursor.constraint == constraint) {
+                constraint.maybeActivate();
+            }
+        },
+    }
+}
diff --git a/river/Root.zig b/river/Root.zig
index a528345..752a510 100644
--- a/river/Root.zig
+++ b/river/Root.zig
@@ -170,15 +170,11 @@ pub fn deinit(self: *Self) void {
 }
 
 pub const AtResult = struct {
+    node: *wlr.SceneNode,
     surface: ?*wlr.Surface,
     sx: f64,
     sy: f64,
-    node: union(enum) {
-        view: *View,
-        layer_surface: *LayerSurface,
-        lock_surface: *LockSurface,
-        xwayland_override_redirect: if (build_options.xwayland) *XwaylandOverrideRedirect else noreturn,
-    },
+    data: SceneNodeData.Data,
 };
 
 /// Return information about what is currently rendered in the interactive_content
@@ -186,11 +182,11 @@ pub const AtResult = struct {
 pub fn at(self: Self, lx: f64, ly: f64) ?AtResult {
     var sx: f64 = undefined;
     var sy: f64 = undefined;
-    const node_at = self.interactive_content.node.at(lx, ly, &sx, &sy) orelse return null;
+    const node = self.interactive_content.node.at(lx, ly, &sx, &sy) orelse return null;
 
     const surface: ?*wlr.Surface = blk: {
-        if (node_at.type == .buffer) {
-            const scene_buffer = wlr.SceneBuffer.fromNode(node_at);
+        if (node.type == .buffer) {
+            const scene_buffer = wlr.SceneBuffer.fromNode(node);
             if (wlr.SceneSurface.fromBuffer(scene_buffer)) |scene_surface| {
                 break :blk scene_surface.surface;
             }
@@ -198,19 +194,13 @@ pub fn at(self: Self, lx: f64, ly: f64) ?AtResult {
         break :blk null;
     };
 
-    if (SceneNodeData.fromNode(node_at)) |scene_node_data| {
+    if (SceneNodeData.fromNode(node)) |scene_node_data| {
         return .{
+            .node = node,
             .surface = surface,
             .sx = sx,
             .sy = sy,
-            .node = switch (scene_node_data.data) {
-                .view => |view| .{ .view = view },
-                .layer_surface => |layer_surface| .{ .layer_surface = layer_surface },
-                .lock_surface => |lock_surface| .{ .lock_surface = lock_surface },
-                .xwayland_override_redirect => |xwayland_override_redirect| .{
-                    .xwayland_override_redirect = xwayland_override_redirect,
-                },
-            },
+            .data = scene_node_data.data,
         };
     } else {
         return null;
diff --git a/river/SceneNodeData.zig b/river/SceneNodeData.zig
index 782351f..4fda92b 100644
--- a/river/SceneNodeData.zig
+++ b/river/SceneNodeData.zig
@@ -27,7 +27,7 @@ const LockSurface = @import("LockSurface.zig");
 const View = @import("View.zig");
 const XwaylandOverrideRedirect = @import("XwaylandOverrideRedirect.zig");
 
-const Data = union(enum) {
+pub const Data = union(enum) {
     view: *View,
     lock_surface: *LockSurface,
     layer_surface: *LayerSurface,
diff --git a/river/Seat.zig b/river/Seat.zig
index d00ea12..685d4cc 100644
--- a/river/Seat.zig
+++ b/river/Seat.zig
@@ -38,6 +38,7 @@ const LayerSurface = @import("LayerSurface.zig");
 const LockSurface = @import("LockSurface.zig");
 const Mapping = @import("Mapping.zig");
 const Output = @import("Output.zig");
+const PointerConstraint = @import("PointerConstraint.zig");
 const SeatStatus = @import("SeatStatus.zig");
 const Switch = @import("Switch.zig");
 const View = @import("View.zig");
@@ -245,8 +246,33 @@ pub fn setFocusRaw(self: *Self, new_focus: FocusTarget) void {
     }
     self.focused = new_focus;
 
+    if (self.cursor.constraint) |constraint| {
+        if (constraint.wlr_constraint.surface != target_surface) {
+            if (constraint.state == .active) {
+                log.info("deactivating pointer constraint for surface, keyboard focus lost", .{});
+                constraint.deactivate();
+            }
+            self.cursor.constraint = null;
+        }
+    }
+
     self.keyboardEnterOrLeave(target_surface);
 
+    if (target_surface) |surface| {
+        const pointer_constraints = server.input_manager.pointer_constraints;
+        if (pointer_constraints.constraintForSurface(surface, self.wlr_seat)) |wlr_constraint| {
+            if (self.cursor.constraint) |constraint| {
+                assert(constraint.wlr_constraint == wlr_constraint);
+            } else {
+                self.cursor.constraint = @intToPtr(*PointerConstraint, wlr_constraint.data);
+            }
+        }
+    }
+
+    // Depending on configuration and cursor position, changing keyboard focus
+    // may cause the cursor to be warped.
+    self.cursor.may_need_warp = true;
+
     // Inform any clients tracking status of the change
     var it = self.status_trackers.first;
     while (it) |node| : (it = node.next) node.data.sendFocusedView();
@@ -258,16 +284,8 @@ pub fn setFocusRaw(self: *Self, new_focus: FocusTarget) void {
 pub fn keyboardEnterOrLeave(self: *Self, target_surface: ?*wlr.Surface) void {
     if (target_surface) |wlr_surface| {
         self.keyboardNotifyEnter(wlr_surface);
-
-        // Depending on configuration and cursor position, changing keyboard focus
-        // may cause the cursor to be warped.
-        self.cursor.may_need_warp = true;
     } else {
         self.wlr_seat.keyboardNotifyClearFocus();
-
-        // Depending on configuration and cursor position, changing keyboard focus
-        // may cause the cursor to be warped.
-        self.cursor.may_need_warp = true;
     }
 }