aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorIsaac Freund <mail@isaacfreund.com>2023-03-05 22:39:47 +0100
committerIsaac Freund <mail@isaacfreund.com>2023-03-05 23:11:54 +0100
commitb7b371cb4fe61c5c224fea0e99897f5dedab6780 (patch)
tree2b4ddab07691a545af163552d478219c58422ce0
parent1602b34f4fe8df2ca2a9b35e23a923d0dbc6fda7 (diff)
downloadriver-b7b371cb4fe61c5c224fea0e99897f5dedab6780.tar.gz
river-b7b371cb4fe61c5c224fea0e99897f5dedab6780.tar.xz
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.
-rw-r--r--build.zig1
-rw-r--r--river/Cursor.zig70
-rw-r--r--river/InputManager.zig20
-rw-r--r--river/PointerConstraint.zig225
-rw-r--r--river/Root.zig26
-rw-r--r--river/SceneNodeData.zig2
-rw-r--r--river/Seat.zig34
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;
}
}