2020-05-02 10:21:10 -07:00
|
|
|
// This file is part of river, a dynamic tiling wayland compositor.
|
|
|
|
//
|
2020-11-11 11:30:21 -08:00
|
|
|
// Copyright 2020 The River Developers
|
2020-05-02 10:21:10 -07:00
|
|
|
//
|
|
|
|
// 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
|
2022-01-31 10:33:22 -08:00
|
|
|
// the Free Software Foundation, version 3.
|
2020-05-02 10:21:10 -07:00
|
|
|
//
|
|
|
|
// 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/>.
|
|
|
|
|
2020-05-02 07:47:10 -07:00
|
|
|
const Self = @This();
|
|
|
|
|
2020-05-14 10:01:17 -07:00
|
|
|
const build_options = @import("build_options");
|
2020-03-22 14:42:55 -07:00
|
|
|
const std = @import("std");
|
2021-07-14 06:32:24 -07:00
|
|
|
const assert = std.debug.assert;
|
2021-06-23 06:56:38 -07:00
|
|
|
const os = std.os;
|
2020-12-26 16:06:18 -08:00
|
|
|
const math = std.math;
|
2020-11-03 15:23:21 -08:00
|
|
|
const wlr = @import("wlroots");
|
|
|
|
const wayland = @import("wayland");
|
|
|
|
const wl = wayland.server.wl;
|
|
|
|
const zwlr = wayland.server.zwlr;
|
2020-05-02 07:47:10 -07:00
|
|
|
|
2020-03-29 10:36:15 -07:00
|
|
|
const c = @import("c.zig");
|
2021-05-13 05:35:36 -07:00
|
|
|
const server = &@import("main.zig").server;
|
2020-06-16 11:54:05 -07:00
|
|
|
const util = @import("util.zig");
|
2020-03-22 14:42:55 -07:00
|
|
|
|
2020-07-07 07:39:08 -07:00
|
|
|
const Config = @import("Config.zig");
|
2023-02-16 07:54:53 -08:00
|
|
|
const DragIcon = @import("DragIcon.zig");
|
2020-05-02 14:11:56 -07:00
|
|
|
const LayerSurface = @import("LayerSurface.zig");
|
2021-12-29 20:27:50 -08:00
|
|
|
const LockSurface = @import("LockSurface.zig");
|
2020-05-02 14:11:56 -07:00
|
|
|
const Output = @import("Output.zig");
|
2023-03-05 13:39:47 -08:00
|
|
|
const PointerConstraint = @import("PointerConstraint.zig");
|
2023-01-29 03:03:41 -08:00
|
|
|
const Root = @import("Root.zig");
|
2020-05-02 14:11:56 -07:00
|
|
|
const Seat = @import("Seat.zig");
|
|
|
|
const View = @import("View.zig");
|
2022-05-29 16:08:09 -07:00
|
|
|
const XwaylandOverrideRedirect = @import("XwaylandOverrideRedirect.zig");
|
2020-03-23 08:50:20 -07:00
|
|
|
|
2020-10-17 13:40:15 -07:00
|
|
|
const Mode = union(enum) {
|
2020-08-07 02:51:53 -07:00
|
|
|
passthrough: void,
|
2021-12-28 22:19:37 -08:00
|
|
|
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
|
|
|
|
// well and take that into account. This is currently not at all easy
|
|
|
|
// to do, but moing to the wlroots scene graph will allow us to fix this.
|
|
|
|
|
|
|
|
// Initial cursor position in layout coordinates
|
|
|
|
lx: f64,
|
|
|
|
ly: f64,
|
|
|
|
// Initial cursor position in surface-local coordinates
|
|
|
|
sx: f64,
|
|
|
|
sy: f64,
|
|
|
|
},
|
2021-10-30 10:16:31 -07:00
|
|
|
move: struct {
|
|
|
|
view: *View,
|
2023-03-04 10:17:18 -08:00
|
|
|
|
2021-10-30 10:16:31 -07:00
|
|
|
/// View coordinates are stored as i32s as they are in logical pixels.
|
|
|
|
/// However, it is possible to move the cursor by a fraction of a
|
|
|
|
/// logical pixel and this happens in practice with low dpi, high
|
|
|
|
/// polling rate mice. Therefore we must accumulate the current
|
|
|
|
/// fractional offset of the mouse to avoid rounding down tiny
|
|
|
|
/// motions to 0.
|
|
|
|
delta_x: f64 = 0,
|
|
|
|
delta_y: f64 = 0,
|
2023-03-04 10:17:18 -08:00
|
|
|
|
|
|
|
/// Offset from the left edge
|
|
|
|
offset_x: i32,
|
|
|
|
/// Offset from the top edge
|
|
|
|
offset_y: i32,
|
2021-10-30 10:16:31 -07:00
|
|
|
},
|
2020-08-07 12:34:38 -07:00
|
|
|
resize: struct {
|
|
|
|
view: *View,
|
2023-03-03 10:43:07 -08:00
|
|
|
|
2023-11-14 06:36:25 -08:00
|
|
|
delta_x: f64 = 0,
|
|
|
|
delta_y: f64 = 0,
|
|
|
|
|
|
|
|
/// Total x/y movement of the pointer device since the start of the resize,
|
|
|
|
/// clamped to the bounds of the resize as defined by the view min/max
|
|
|
|
/// dimensions and output dimensions.
|
|
|
|
/// This is not directly tied to the rendered cursor position.
|
|
|
|
x: i32 = 0,
|
|
|
|
y: i32 = 0,
|
2023-03-03 10:43:07 -08:00
|
|
|
|
|
|
|
/// Resize edges, maximum of 2 are set and they may not be opposing edges.
|
|
|
|
edges: wlr.Edges,
|
|
|
|
/// Offset from the left or right edge
|
2020-08-07 12:34:38 -07:00
|
|
|
offset_x: i32,
|
2023-03-03 10:43:07 -08:00
|
|
|
/// Offset from the top or bottom edge
|
2020-08-07 12:34:38 -07:00
|
|
|
offset_y: i32,
|
2023-10-17 10:02:46 -07:00
|
|
|
|
|
|
|
initial_width: u31,
|
|
|
|
initial_height: u31,
|
2020-08-07 12:34:38 -07:00
|
|
|
},
|
2020-08-07 02:51:53 -07:00
|
|
|
};
|
|
|
|
|
2020-07-14 08:34:29 -07:00
|
|
|
const default_size = 24;
|
|
|
|
|
2022-12-30 13:03:10 -08:00
|
|
|
const LayoutPoint = struct {
|
|
|
|
lx: f64,
|
|
|
|
ly: f64,
|
|
|
|
};
|
|
|
|
|
2021-02-05 00:46:18 -08:00
|
|
|
const log = std.log.scoped(.cursor);
|
|
|
|
|
2020-10-17 13:40:15 -07:00
|
|
|
/// Current cursor mode as well as any state needed to implement that mode
|
|
|
|
mode: Mode = .passthrough,
|
|
|
|
|
2023-03-14 13:06:44 -07:00
|
|
|
/// Set to whatever the current mode is when a transaction is started.
|
|
|
|
/// This is necessary to handle termination of move/resize modes properly
|
|
|
|
/// since the termination is not complete until a transaction completes and
|
|
|
|
/// View.resizeUpdatePosition() is called.
|
|
|
|
inflight_mode: Mode = .passthrough,
|
|
|
|
|
2020-05-02 07:47:10 -07:00
|
|
|
seat: *Seat,
|
2020-11-03 15:23:21 -08:00
|
|
|
wlr_cursor: *wlr.Cursor,
|
2021-02-14 10:32:04 -08:00
|
|
|
pointer_gestures: *wlr.PointerGesturesV1,
|
2020-05-02 07:47:10 -07:00
|
|
|
|
2023-12-01 05:57:18 -08:00
|
|
|
/// Xcursor manager for the currently configured Xcursor theme.
|
|
|
|
xcursor_manager: *wlr.XcursorManager,
|
|
|
|
/// Name of the current Xcursor shape, or null if a client has configured a
|
|
|
|
/// surface to be used as the cursor shape instead.
|
|
|
|
xcursor_name: ?[*:0]const u8 = null,
|
2021-12-20 19:50:12 -08:00
|
|
|
|
2020-07-29 07:36:46 -07:00
|
|
|
/// Number of distinct buttons currently pressed
|
2020-08-21 10:08:52 -07:00
|
|
|
pressed_count: u32 = 0,
|
2020-07-07 07:39:08 -07:00
|
|
|
|
2022-02-27 15:39:10 -08:00
|
|
|
hide_cursor_timer: *wl.EventSource,
|
|
|
|
|
|
|
|
hidden: bool = false,
|
2022-08-01 17:31:50 -07:00
|
|
|
may_need_warp: bool = false,
|
2022-02-27 15:39:10 -08:00
|
|
|
|
2023-03-05 13:39:47 -08:00
|
|
|
/// 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,
|
|
|
|
|
Cursor: keep focus_follows_cursor_target updated
This goes as close as possible to the behavior before this state was
introduced (keeping the improvement which needed it, 931405ab), fixing
various mis-interactions of keyboard and focus_follows_cursor focus
changes.
The following text is irrelevant to restoring correct basic FFC behavior
and talks about less common scenarios with regards to FFC clashing with
views' input region beyond their geometry, continuing the work done in
931405ab.
Scenario 1: the cursor traveling along a view's border in a "dead zone",
never initiating a focus change. If the focused view has an extended
input region, that area has some functionality (such as client-initiated
resizing); therefore it should be respected and even if another view's
geometry is also under the cursor, focus shouldn't change. In case of
unfocused views, it is a matter of consistency with the focused-view
case. This outcome is also easier to implement, as it doesn't require
any additional code.
Scenario 2: *clicking* such a dead zone, i.e. extended input region (of
an unfocused view). In question is not whether to focus the view (yes),
but whether the focus_follows_cursor_target should be set to the view as
well. Only one case seems relevant to me here, which is when ffc_target
is another view whose geometry is under the cursor, but covered by this
newly-focused view's input region. The most likely action following the
click is resizing the newly-focused view, where a touchpad or faulty
mouse could make the cursor move a bit farther after the button has been
released. I believe that ffc_target shouldn't have been updated, in
order to now prevent focus from skipping away.
(Another variant is me, wondering why the wrong view got focused and
trying to focus the right one using FFC. In that case, however, one
could ask if it's river that misbehaves and whether the application is
really well-integrated into the user's desktop when it provides a
feature they don't desire.)
2023-03-25 07:46:08 -07:00
|
|
|
/// View under the cursor, defined by view geometry rather than input region
|
|
|
|
focus_follows_cursor_target: ?*View = null,
|
2022-12-31 10:51:42 -08:00
|
|
|
|
2022-12-30 13:03:10 -08:00
|
|
|
/// Keeps track of the last known location of all touch points in layout coordinates.
|
|
|
|
/// This information is necessary for proper touch dnd support if there are multiple touch points.
|
|
|
|
touch_points: std.AutoHashMapUnmanaged(i32, LayoutPoint) = .{},
|
|
|
|
|
2020-12-31 06:35:35 -08:00
|
|
|
axis: wl.Listener(*wlr.Pointer.event.Axis) = wl.Listener(*wlr.Pointer.event.Axis).init(handleAxis),
|
|
|
|
frame: wl.Listener(*wlr.Cursor) = wl.Listener(*wlr.Cursor).init(handleFrame),
|
|
|
|
button: wl.Listener(*wlr.Pointer.event.Button) =
|
|
|
|
wl.Listener(*wlr.Pointer.event.Button).init(handleButton),
|
|
|
|
motion_absolute: wl.Listener(*wlr.Pointer.event.MotionAbsolute) =
|
|
|
|
wl.Listener(*wlr.Pointer.event.MotionAbsolute).init(handleMotionAbsolute),
|
|
|
|
motion: wl.Listener(*wlr.Pointer.event.Motion) =
|
|
|
|
wl.Listener(*wlr.Pointer.event.Motion).init(handleMotion),
|
2021-02-14 10:32:04 -08:00
|
|
|
pinch_begin: wl.Listener(*wlr.Pointer.event.PinchBegin) =
|
|
|
|
wl.Listener(*wlr.Pointer.event.PinchBegin).init(handlePinchBegin),
|
|
|
|
pinch_update: wl.Listener(*wlr.Pointer.event.PinchUpdate) =
|
|
|
|
wl.Listener(*wlr.Pointer.event.PinchUpdate).init(handlePinchUpdate),
|
|
|
|
pinch_end: wl.Listener(*wlr.Pointer.event.PinchEnd) =
|
|
|
|
wl.Listener(*wlr.Pointer.event.PinchEnd).init(handlePinchEnd),
|
2020-12-31 06:35:35 -08:00
|
|
|
request_set_cursor: wl.Listener(*wlr.Seat.event.RequestSetCursor) =
|
|
|
|
wl.Listener(*wlr.Seat.event.RequestSetCursor).init(handleRequestSetCursor),
|
2021-02-14 10:32:04 -08:00
|
|
|
swipe_begin: wl.Listener(*wlr.Pointer.event.SwipeBegin) =
|
|
|
|
wl.Listener(*wlr.Pointer.event.SwipeBegin).init(handleSwipeBegin),
|
|
|
|
swipe_update: wl.Listener(*wlr.Pointer.event.SwipeUpdate) =
|
|
|
|
wl.Listener(*wlr.Pointer.event.SwipeUpdate).init(handleSwipeUpdate),
|
|
|
|
swipe_end: wl.Listener(*wlr.Pointer.event.SwipeEnd) =
|
|
|
|
wl.Listener(*wlr.Pointer.event.SwipeEnd).init(handleSwipeEnd),
|
2020-05-02 07:47:10 -07:00
|
|
|
|
2022-06-21 15:34:05 -07:00
|
|
|
touch_up: wl.Listener(*wlr.Touch.event.Up) =
|
|
|
|
wl.Listener(*wlr.Touch.event.Up).init(handleTouchUp),
|
|
|
|
touch_down: wl.Listener(*wlr.Touch.event.Down) =
|
|
|
|
wl.Listener(*wlr.Touch.event.Down).init(handleTouchDown),
|
|
|
|
touch_motion: wl.Listener(*wlr.Touch.event.Motion) =
|
|
|
|
wl.Listener(*wlr.Touch.event.Motion).init(handleTouchMotion),
|
|
|
|
touch_frame: wl.Listener(void) = wl.Listener(void).init(handleTouchFrame),
|
|
|
|
|
2020-05-02 07:47:10 -07:00
|
|
|
pub fn init(self: *Self, seat: *Seat) !void {
|
2020-11-03 15:23:21 -08:00
|
|
|
const wlr_cursor = try wlr.Cursor.create();
|
|
|
|
errdefer wlr_cursor.destroy();
|
2021-05-13 05:53:08 -07:00
|
|
|
wlr_cursor.attachOutputLayout(server.root.output_layout);
|
2020-07-14 08:34:29 -07:00
|
|
|
|
2020-11-03 15:23:21 -08:00
|
|
|
// This is here so that self.xcursor_manager doesn't need to be an
|
2020-07-14 08:34:29 -07:00
|
|
|
// optional pointer. This isn't optimal as it does a needless allocation,
|
|
|
|
// but this is not a hot path.
|
2020-11-03 15:23:21 -08:00
|
|
|
const xcursor_manager = try wlr.XcursorManager.create(null, default_size);
|
|
|
|
errdefer xcursor_manager.destroy();
|
2020-08-21 10:08:52 -07:00
|
|
|
|
2022-02-27 15:39:10 -08:00
|
|
|
const event_loop = server.wl_server.getEventLoop();
|
2020-08-21 10:08:52 -07:00
|
|
|
self.* = .{
|
|
|
|
.seat = seat,
|
|
|
|
.wlr_cursor = wlr_cursor,
|
2021-05-13 05:53:08 -07:00
|
|
|
.pointer_gestures = try wlr.PointerGesturesV1.create(server.wl_server),
|
2020-11-03 15:23:21 -08:00
|
|
|
.xcursor_manager = xcursor_manager,
|
2022-02-27 15:39:10 -08:00
|
|
|
.hide_cursor_timer = try event_loop.addTimer(*Self, handleHideCursorTimeout, self),
|
2020-08-21 10:08:52 -07:00
|
|
|
};
|
2022-02-27 15:39:10 -08:00
|
|
|
errdefer self.hide_cursor_timer.remove();
|
|
|
|
try self.hide_cursor_timer.timerUpdate(server.config.cursor_hide_timeout);
|
2020-07-14 08:34:29 -07:00
|
|
|
try self.setTheme(null, null);
|
2020-05-02 07:47:10 -07:00
|
|
|
|
|
|
|
// wlr_cursor *only* displays an image on screen. It does not move around
|
|
|
|
// when the pointer moves. However, we can attach input devices to it, and
|
|
|
|
// it will generate aggregate events for all of them. In these events, we
|
|
|
|
// can choose how we want to process them, forwarding them to clients and
|
|
|
|
// moving the cursor around. See following post for more detail:
|
|
|
|
// https://drewdevault.com/2018/07/17/Input-handling-in-wlroots.html
|
2020-12-31 06:35:35 -08:00
|
|
|
wlr_cursor.events.axis.add(&self.axis);
|
|
|
|
wlr_cursor.events.button.add(&self.button);
|
|
|
|
wlr_cursor.events.frame.add(&self.frame);
|
|
|
|
wlr_cursor.events.motion_absolute.add(&self.motion_absolute);
|
|
|
|
wlr_cursor.events.motion.add(&self.motion);
|
2021-02-14 10:32:04 -08:00
|
|
|
wlr_cursor.events.swipe_begin.add(&self.swipe_begin);
|
|
|
|
wlr_cursor.events.swipe_update.add(&self.swipe_update);
|
|
|
|
wlr_cursor.events.swipe_end.add(&self.swipe_end);
|
|
|
|
wlr_cursor.events.pinch_begin.add(&self.pinch_begin);
|
|
|
|
wlr_cursor.events.pinch_update.add(&self.pinch_update);
|
|
|
|
wlr_cursor.events.pinch_end.add(&self.pinch_end);
|
2020-12-31 06:35:35 -08:00
|
|
|
seat.wlr_seat.events.request_set_cursor.add(&self.request_set_cursor);
|
2022-06-21 15:34:05 -07:00
|
|
|
|
|
|
|
wlr_cursor.events.touch_up.add(&self.touch_up);
|
|
|
|
wlr_cursor.events.touch_down.add(&self.touch_down);
|
|
|
|
wlr_cursor.events.touch_motion.add(&self.touch_motion);
|
|
|
|
wlr_cursor.events.touch_frame.add(&self.touch_frame);
|
2020-05-02 07:47:10 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
pub fn deinit(self: *Self) void {
|
2022-02-27 15:39:10 -08:00
|
|
|
self.hide_cursor_timer.remove();
|
2020-11-03 15:23:21 -08:00
|
|
|
self.xcursor_manager.destroy();
|
|
|
|
self.wlr_cursor.destroy();
|
2020-05-02 07:47:10 -07:00
|
|
|
}
|
|
|
|
|
2020-07-14 08:34:29 -07:00
|
|
|
/// Set the cursor theme for the given seat, as well as the xwayland theme if
|
2020-07-17 11:40:33 -07:00
|
|
|
/// this is the default seat. Either argument may be null, in which case a
|
|
|
|
/// default will be used.
|
2020-07-17 03:56:15 -07:00
|
|
|
pub fn setTheme(self: *Self, theme: ?[*:0]const u8, _size: ?u32) !void {
|
|
|
|
const size = _size orelse default_size;
|
2020-07-14 08:34:29 -07:00
|
|
|
|
2024-01-04 12:44:51 -08:00
|
|
|
const xcursor_manager = try wlr.XcursorManager.create(theme, size);
|
|
|
|
errdefer xcursor_manager.destroy();
|
2020-07-14 08:34:29 -07:00
|
|
|
|
|
|
|
// If this cursor belongs to the default seat, set the xcursor environment
|
2024-01-04 12:44:51 -08:00
|
|
|
// variables as well as the xwayland cursor theme.
|
2021-05-13 06:13:17 -07:00
|
|
|
if (self.seat == server.input_manager.defaultSeat()) {
|
2021-10-11 03:44:46 -07:00
|
|
|
const size_str = try std.fmt.allocPrintZ(util.gpa, "{}", .{size});
|
2020-07-14 08:34:29 -07:00
|
|
|
defer util.gpa.free(size_str);
|
2022-11-04 16:29:51 -07:00
|
|
|
if (c.setenv("XCURSOR_SIZE", size_str.ptr, 1) < 0) return error.OutOfMemory;
|
2020-07-14 08:34:29 -07:00
|
|
|
if (theme) |t| if (c.setenv("XCURSOR_THEME", t, 1) < 0) return error.OutOfMemory;
|
|
|
|
|
|
|
|
if (build_options.xwayland) {
|
2024-01-04 12:44:51 -08:00
|
|
|
try xcursor_manager.load(1);
|
|
|
|
const wlr_xcursor = xcursor_manager.getXcursor("left_ptr", 1).?;
|
2020-11-03 15:23:21 -08:00
|
|
|
const image = wlr_xcursor.images[0];
|
|
|
|
server.xwayland.setCursor(
|
|
|
|
image.buffer,
|
|
|
|
image.width * 4,
|
|
|
|
image.width,
|
|
|
|
image.height,
|
2023-10-16 07:18:36 -07:00
|
|
|
@intCast(image.hotspot_x),
|
|
|
|
@intCast(image.hotspot_y),
|
2020-11-03 15:23:21 -08:00
|
|
|
);
|
2020-07-14 08:34:29 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-01-04 12:44:51 -08:00
|
|
|
// Everything fallible is now done so the the old xcursor_manager can be destroyed.
|
|
|
|
self.xcursor_manager.destroy();
|
|
|
|
self.xcursor_manager = xcursor_manager;
|
|
|
|
|
2023-12-01 05:57:18 -08:00
|
|
|
if (self.xcursor_name) |name| {
|
2024-01-04 09:03:37 -08:00
|
|
|
self.setXcursor(name);
|
2023-12-01 05:57:18 -08:00
|
|
|
}
|
2021-12-20 19:50:12 -08:00
|
|
|
}
|
|
|
|
|
2024-01-04 09:03:37 -08:00
|
|
|
pub fn setXcursor(self: *Self, name: [*:0]const u8) void {
|
|
|
|
self.wlr_cursor.setXcursor(self.xcursor_manager, name);
|
|
|
|
self.xcursor_name = name;
|
|
|
|
}
|
|
|
|
|
2021-12-20 19:50:12 -08:00
|
|
|
fn clearFocus(self: *Self) void {
|
2024-01-04 09:03:37 -08:00
|
|
|
self.setXcursor("left_ptr");
|
2020-11-03 15:23:21 -08:00
|
|
|
self.seat.wlr_seat.pointerNotifyClearFocus();
|
2020-08-21 07:23:23 -07:00
|
|
|
}
|
|
|
|
|
2020-11-03 15:23:21 -08:00
|
|
|
/// Axis event is a scroll wheel or similiar
|
|
|
|
fn handleAxis(listener: *wl.Listener(*wlr.Pointer.event.Axis), event: *wlr.Pointer.event.Axis) void {
|
|
|
|
const self = @fieldParentPtr(Self, "axis", listener);
|
2020-05-02 07:47:10 -07:00
|
|
|
|
2020-08-13 03:22:32 -07:00
|
|
|
self.seat.handleActivity();
|
2022-02-27 15:39:10 -08:00
|
|
|
self.unhide();
|
2020-08-13 03:22:32 -07:00
|
|
|
|
2020-05-02 07:47:10 -07:00
|
|
|
// Notify the client with pointer focus of the axis event.
|
2020-11-03 15:23:21 -08:00
|
|
|
self.seat.wlr_seat.pointerNotifyAxis(
|
2020-05-02 07:47:10 -07:00
|
|
|
event.time_msec,
|
|
|
|
event.orientation,
|
|
|
|
event.delta,
|
|
|
|
event.delta_discrete,
|
|
|
|
event.source,
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2020-11-03 15:23:21 -08:00
|
|
|
fn handleButton(listener: *wl.Listener(*wlr.Pointer.event.Button), event: *wlr.Pointer.event.Button) void {
|
|
|
|
const self = @fieldParentPtr(Self, "button", listener);
|
2020-07-29 07:36:46 -07:00
|
|
|
|
2020-08-13 03:22:32 -07:00
|
|
|
self.seat.handleActivity();
|
2022-02-27 15:39:10 -08:00
|
|
|
self.unhide();
|
2020-08-13 03:22:32 -07:00
|
|
|
|
2021-12-28 22:19:37 -08:00
|
|
|
if (event.state == .released) {
|
2022-02-07 13:07:27 -08:00
|
|
|
assert(self.pressed_count > 0);
|
2020-07-29 07:36:46 -07:00
|
|
|
self.pressed_count -= 1;
|
|
|
|
if (self.pressed_count == 0 and self.mode != .passthrough) {
|
2023-03-14 13:06:44 -07:00
|
|
|
log.debug("leaving {s} mode", .{@tagName(self.mode)});
|
|
|
|
|
|
|
|
switch (self.mode) {
|
|
|
|
.passthrough => unreachable,
|
|
|
|
.down => {
|
|
|
|
// If we were in down mode, we need pass along the release event
|
|
|
|
_ = self.seat.wlr_seat.pointerNotifyButton(event.time_msec, event.button, event.state);
|
|
|
|
},
|
|
|
|
.move => {},
|
|
|
|
.resize => |data| data.view.pending.resizing = false,
|
|
|
|
}
|
|
|
|
|
|
|
|
self.mode = .passthrough;
|
|
|
|
self.passthrough(event.time_msec);
|
|
|
|
|
|
|
|
server.root.applyPending();
|
2021-12-28 22:19:37 -08:00
|
|
|
} else {
|
|
|
|
_ = self.seat.wlr_seat.pointerNotifyButton(event.time_msec, event.button, event.state);
|
2020-07-29 07:36:46 -07:00
|
|
|
}
|
2021-12-28 22:19:37 -08:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
assert(event.state == .pressed);
|
|
|
|
self.pressed_count += 1;
|
|
|
|
|
|
|
|
if (self.pressed_count > 1) {
|
|
|
|
_ = self.seat.wlr_seat.pointerNotifyButton(event.time_msec, event.button, event.state);
|
|
|
|
return;
|
2020-07-29 07:36:46 -07:00
|
|
|
}
|
|
|
|
|
2023-01-29 03:03:41 -08:00
|
|
|
if (server.root.at(self.wlr_cursor.x, self.wlr_cursor.y)) |result| {
|
2023-03-05 13:39:47 -08:00
|
|
|
if (result.data == .view and self.handlePointerMapping(event, result.data.view)) {
|
2022-06-21 15:34:05 -07:00
|
|
|
// If a mapping is triggered don't send events to clients.
|
|
|
|
return;
|
2020-04-18 03:21:43 -07:00
|
|
|
}
|
2022-06-21 15:34:05 -07:00
|
|
|
|
|
|
|
self.updateKeyboardFocus(result);
|
|
|
|
|
2020-11-03 15:23:21 -08:00
|
|
|
_ = self.seat.wlr_seat.pointerNotifyButton(event.time_msec, event.button, event.state);
|
2021-12-28 22:19:37 -08:00
|
|
|
|
2023-01-29 03:03:41 -08:00
|
|
|
if (result.surface != null) {
|
|
|
|
self.mode = .{
|
|
|
|
.down = .{
|
|
|
|
.lx = self.wlr_cursor.x,
|
|
|
|
.ly = self.wlr_cursor.y,
|
|
|
|
.sx = result.sx,
|
|
|
|
.sy = result.sy,
|
|
|
|
},
|
|
|
|
};
|
|
|
|
}
|
2022-06-21 15:34:05 -07:00
|
|
|
} else {
|
|
|
|
self.updateOutputFocus(self.wlr_cursor.x, self.wlr_cursor.y);
|
|
|
|
}
|
|
|
|
|
2023-02-24 10:28:37 -08:00
|
|
|
server.root.applyPending();
|
2022-06-21 15:34:05 -07:00
|
|
|
}
|
2021-12-28 22:19:37 -08:00
|
|
|
|
2023-03-01 01:49:44 -08:00
|
|
|
/// Requires a call to Root.applyPending()
|
2023-01-29 03:03:41 -08:00
|
|
|
fn updateKeyboardFocus(self: Self, result: Root.AtResult) void {
|
2023-03-05 13:39:47 -08:00
|
|
|
switch (result.data) {
|
2022-06-21 15:34:05 -07:00
|
|
|
.view => |view| {
|
|
|
|
self.seat.focus(view);
|
|
|
|
},
|
|
|
|
.layer_surface => |layer_surface| {
|
|
|
|
self.seat.focusOutput(layer_surface.output);
|
|
|
|
// If a keyboard inteactive layer surface has been clicked on,
|
|
|
|
// give it keyboard focus.
|
2023-02-28 09:19:37 -08:00
|
|
|
if (layer_surface.wlr_layer_surface.current.keyboard_interactive != .none) {
|
2022-06-21 15:34:05 -07:00
|
|
|
self.seat.setFocusRaw(.{ .layer = layer_surface });
|
|
|
|
}
|
|
|
|
},
|
2021-12-29 20:27:50 -08:00
|
|
|
.lock_surface => |lock_surface| {
|
2022-12-22 13:27:17 -08:00
|
|
|
assert(server.lock_manager.state != .unlocked);
|
2021-12-29 20:27:50 -08:00
|
|
|
self.seat.setFocusRaw(.{ .lock_surface = lock_surface });
|
|
|
|
},
|
2022-06-21 15:34:05 -07:00
|
|
|
.xwayland_override_redirect => |override_redirect| {
|
2023-01-31 06:47:19 -08:00
|
|
|
assert(server.lock_manager.state != .locked);
|
2023-01-12 02:57:56 -08:00
|
|
|
override_redirect.focusIfDesired();
|
2022-06-21 15:34:05 -07:00
|
|
|
},
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Focus the output at the given layout coordinates, if any
|
2023-03-01 01:49:44 -08:00
|
|
|
/// Requires a call to Root.applyPending()
|
2022-06-21 15:34:05 -07:00
|
|
|
fn updateOutputFocus(self: Self, lx: f64, ly: f64) void {
|
|
|
|
if (server.root.output_layout.outputAt(lx, ly)) |wlr_output| {
|
2023-10-16 07:18:36 -07:00
|
|
|
const output: *Output = @ptrFromInt(wlr_output.data);
|
2021-12-10 14:40:48 -08:00
|
|
|
self.seat.focusOutput(output);
|
2020-04-18 03:21:43 -07:00
|
|
|
}
|
2020-05-02 07:47:10 -07:00
|
|
|
}
|
|
|
|
|
2021-02-14 10:32:04 -08:00
|
|
|
fn handlePinchBegin(
|
|
|
|
listener: *wl.Listener(*wlr.Pointer.event.PinchBegin),
|
|
|
|
event: *wlr.Pointer.event.PinchBegin,
|
|
|
|
) void {
|
|
|
|
const self = @fieldParentPtr(Self, "pinch_begin", listener);
|
|
|
|
self.pointer_gestures.sendPinchBegin(
|
|
|
|
self.seat.wlr_seat,
|
|
|
|
event.time_msec,
|
|
|
|
event.fingers,
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
fn handlePinchUpdate(
|
|
|
|
listener: *wl.Listener(*wlr.Pointer.event.PinchUpdate),
|
|
|
|
event: *wlr.Pointer.event.PinchUpdate,
|
|
|
|
) void {
|
|
|
|
const self = @fieldParentPtr(Self, "pinch_update", listener);
|
|
|
|
self.pointer_gestures.sendPinchUpdate(
|
|
|
|
self.seat.wlr_seat,
|
|
|
|
event.time_msec,
|
|
|
|
event.dx,
|
|
|
|
event.dy,
|
|
|
|
event.scale,
|
|
|
|
event.rotation,
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
fn handlePinchEnd(
|
|
|
|
listener: *wl.Listener(*wlr.Pointer.event.PinchEnd),
|
|
|
|
event: *wlr.Pointer.event.PinchEnd,
|
|
|
|
) void {
|
|
|
|
const self = @fieldParentPtr(Self, "pinch_end", listener);
|
|
|
|
self.pointer_gestures.sendPinchEnd(
|
|
|
|
self.seat.wlr_seat,
|
|
|
|
event.time_msec,
|
|
|
|
event.cancelled,
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
fn handleSwipeBegin(
|
|
|
|
listener: *wl.Listener(*wlr.Pointer.event.SwipeBegin),
|
|
|
|
event: *wlr.Pointer.event.SwipeBegin,
|
|
|
|
) void {
|
|
|
|
const self = @fieldParentPtr(Self, "swipe_begin", listener);
|
|
|
|
self.pointer_gestures.sendSwipeBegin(
|
|
|
|
self.seat.wlr_seat,
|
|
|
|
event.time_msec,
|
|
|
|
event.fingers,
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
fn handleSwipeUpdate(
|
|
|
|
listener: *wl.Listener(*wlr.Pointer.event.SwipeUpdate),
|
|
|
|
event: *wlr.Pointer.event.SwipeUpdate,
|
|
|
|
) void {
|
|
|
|
const self = @fieldParentPtr(Self, "swipe_update", listener);
|
|
|
|
self.pointer_gestures.sendSwipeUpdate(
|
|
|
|
self.seat.wlr_seat,
|
|
|
|
event.time_msec,
|
|
|
|
event.dx,
|
|
|
|
event.dy,
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
fn handleSwipeEnd(
|
|
|
|
listener: *wl.Listener(*wlr.Pointer.event.SwipeEnd),
|
|
|
|
event: *wlr.Pointer.event.SwipeEnd,
|
|
|
|
) void {
|
|
|
|
const self = @fieldParentPtr(Self, "swipe_end", listener);
|
|
|
|
self.pointer_gestures.sendSwipeEnd(
|
|
|
|
self.seat.wlr_seat,
|
|
|
|
event.time_msec,
|
|
|
|
event.cancelled,
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2022-06-21 15:34:05 -07:00
|
|
|
fn handleTouchUp(
|
|
|
|
listener: *wl.Listener(*wlr.Touch.event.Up),
|
|
|
|
event: *wlr.Touch.event.Up,
|
|
|
|
) void {
|
|
|
|
const self = @fieldParentPtr(Self, "touch_up", listener);
|
|
|
|
|
|
|
|
self.seat.handleActivity();
|
|
|
|
|
2022-12-30 13:03:10 -08:00
|
|
|
_ = self.touch_points.remove(event.touch_id);
|
|
|
|
|
2022-06-21 15:34:05 -07:00
|
|
|
self.seat.wlr_seat.touchNotifyUp(event.time_msec, event.touch_id);
|
|
|
|
}
|
|
|
|
|
|
|
|
fn handleTouchDown(
|
|
|
|
listener: *wl.Listener(*wlr.Touch.event.Down),
|
|
|
|
event: *wlr.Touch.event.Down,
|
|
|
|
) void {
|
|
|
|
const self = @fieldParentPtr(Self, "touch_down", listener);
|
|
|
|
|
|
|
|
self.seat.handleActivity();
|
|
|
|
|
|
|
|
var lx: f64 = undefined;
|
|
|
|
var ly: f64 = undefined;
|
|
|
|
self.wlr_cursor.absoluteToLayoutCoords(event.device, event.x, event.y, &lx, &ly);
|
|
|
|
|
2022-12-30 13:03:10 -08:00
|
|
|
self.touch_points.putNoClobber(util.gpa, event.touch_id, .{ .lx = lx, .ly = ly }) catch {
|
|
|
|
log.err("out of memory", .{});
|
|
|
|
};
|
|
|
|
|
2023-01-29 03:03:41 -08:00
|
|
|
if (server.root.at(lx, ly)) |result| {
|
2022-06-21 15:34:05 -07:00
|
|
|
self.updateKeyboardFocus(result);
|
|
|
|
|
2023-01-29 03:03:41 -08:00
|
|
|
if (result.surface) |surface| {
|
|
|
|
_ = self.seat.wlr_seat.touchNotifyDown(
|
|
|
|
surface,
|
|
|
|
event.time_msec,
|
|
|
|
event.touch_id,
|
|
|
|
result.sx,
|
|
|
|
result.sy,
|
|
|
|
);
|
|
|
|
}
|
2022-06-21 15:34:05 -07:00
|
|
|
} else {
|
|
|
|
self.updateOutputFocus(lx, ly);
|
|
|
|
}
|
|
|
|
|
2023-02-24 10:28:37 -08:00
|
|
|
server.root.applyPending();
|
2022-06-21 15:34:05 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
fn handleTouchMotion(
|
|
|
|
listener: *wl.Listener(*wlr.Touch.event.Motion),
|
|
|
|
event: *wlr.Touch.event.Motion,
|
|
|
|
) void {
|
|
|
|
const self = @fieldParentPtr(Self, "touch_motion", listener);
|
|
|
|
|
|
|
|
self.seat.handleActivity();
|
|
|
|
|
|
|
|
var lx: f64 = undefined;
|
|
|
|
var ly: f64 = undefined;
|
|
|
|
self.wlr_cursor.absoluteToLayoutCoords(event.device, event.x, event.y, &lx, &ly);
|
|
|
|
|
2022-12-30 13:03:10 -08:00
|
|
|
self.touch_points.put(util.gpa, event.touch_id, .{ .lx = lx, .ly = ly }) catch {
|
|
|
|
log.err("out of memory", .{});
|
|
|
|
};
|
|
|
|
|
2023-02-16 07:54:53 -08:00
|
|
|
self.updateDragIcons();
|
|
|
|
|
2023-01-29 03:03:41 -08:00
|
|
|
if (server.root.at(lx, ly)) |result| {
|
2022-06-21 15:34:05 -07:00
|
|
|
self.seat.wlr_seat.touchNotifyMotion(event.time_msec, event.touch_id, result.sx, result.sy);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn handleTouchFrame(listener: *wl.Listener(void)) void {
|
|
|
|
const self = @fieldParentPtr(Self, "touch_frame", listener);
|
|
|
|
|
|
|
|
self.seat.handleActivity();
|
|
|
|
|
|
|
|
self.seat.wlr_seat.touchNotifyFrame();
|
|
|
|
}
|
|
|
|
|
2020-08-24 05:52:47 -07:00
|
|
|
/// Handle the mapping for the passed button if any. Returns true if there
|
|
|
|
/// was a mapping and the button was handled.
|
2020-11-03 15:23:21 -08:00
|
|
|
fn handlePointerMapping(self: *Self, event: *wlr.Pointer.event.Button, view: *View) bool {
|
|
|
|
const wlr_keyboard = self.seat.wlr_seat.getKeyboard() orelse return false;
|
|
|
|
const modifiers = wlr_keyboard.getModifiers();
|
2020-08-24 05:52:47 -07:00
|
|
|
|
|
|
|
const fullscreen = view.current.fullscreen or view.pending.fullscreen;
|
|
|
|
|
2021-05-13 05:53:08 -07:00
|
|
|
return for (server.config.modes.items[self.seat.mode_id].pointer_mappings.items) |mapping| {
|
2020-11-03 15:23:21 -08:00
|
|
|
if (event.button == mapping.event_code and std.meta.eql(modifiers, mapping.modifiers)) {
|
2020-08-24 05:52:47 -07:00
|
|
|
switch (mapping.action) {
|
2023-03-03 10:43:07 -08:00
|
|
|
.move => if (!fullscreen) self.startMove(view),
|
|
|
|
.resize => if (!fullscreen) self.startResize(view, null),
|
2022-09-12 19:05:21 -07:00
|
|
|
.command => |args| {
|
|
|
|
self.seat.focus(view);
|
|
|
|
self.seat.runCommand(args);
|
2022-11-25 04:57:35 -08:00
|
|
|
// This is mildly inefficient as running the command may have already
|
|
|
|
// started a transaction. However we need to start one after the Seat.focus()
|
|
|
|
// call in the case where it didn't.
|
2023-02-24 10:28:37 -08:00
|
|
|
server.root.applyPending();
|
2022-09-12 19:05:21 -07:00
|
|
|
},
|
2020-08-24 05:52:47 -07:00
|
|
|
}
|
|
|
|
break true;
|
|
|
|
}
|
|
|
|
} else false;
|
|
|
|
}
|
|
|
|
|
2020-11-03 15:23:21 -08:00
|
|
|
/// Frame events are sent after regular pointer events to group multiple
|
|
|
|
/// events together. For instance, two axis events may happen at the same
|
|
|
|
/// time, in which case a frame event won't be sent in between.
|
2021-10-11 03:44:46 -07:00
|
|
|
fn handleFrame(listener: *wl.Listener(*wlr.Cursor), _: *wlr.Cursor) void {
|
2020-11-03 15:23:21 -08:00
|
|
|
const self = @fieldParentPtr(Self, "frame", listener);
|
|
|
|
self.seat.wlr_seat.pointerNotifyFrame();
|
2020-05-02 07:47:10 -07:00
|
|
|
}
|
|
|
|
|
2020-11-03 15:23:21 -08:00
|
|
|
/// This event is forwarded by the cursor when a pointer emits an _absolute_
|
|
|
|
/// motion event, from 0..1 on each axis. This happens, for example, when
|
|
|
|
/// wlroots is running under a Wayland window rather than KMS+DRM, and you
|
|
|
|
/// move the mouse over the window. You could enter the window from any edge,
|
|
|
|
/// so we have to warp the mouse there. There is also some hardware which
|
|
|
|
/// emits these events.
|
|
|
|
fn handleMotionAbsolute(
|
|
|
|
listener: *wl.Listener(*wlr.Pointer.event.MotionAbsolute),
|
|
|
|
event: *wlr.Pointer.event.MotionAbsolute,
|
|
|
|
) void {
|
|
|
|
const self = @fieldParentPtr(Self, "motion_absolute", listener);
|
2020-08-07 02:51:53 -07:00
|
|
|
|
2020-08-13 03:22:32 -07:00
|
|
|
self.seat.handleActivity();
|
|
|
|
|
2020-08-07 02:51:53 -07:00
|
|
|
var lx: f64 = undefined;
|
|
|
|
var ly: f64 = undefined;
|
2020-11-03 15:23:21 -08:00
|
|
|
self.wlr_cursor.absoluteToLayoutCoords(event.device, event.x, event.y, &lx, &ly);
|
2020-08-07 02:51:53 -07:00
|
|
|
|
2021-02-15 12:07:29 -08:00
|
|
|
const dx = lx - self.wlr_cursor.x;
|
|
|
|
const dy = ly - self.wlr_cursor.y;
|
|
|
|
self.processMotion(event.device, event.time_msec, dx, dy, dx, dy);
|
2020-05-02 07:47:10 -07:00
|
|
|
}
|
|
|
|
|
2020-11-03 15:23:21 -08:00
|
|
|
/// This event is forwarded by the cursor when a pointer emits a _relative_
|
|
|
|
/// pointer motion event (i.e. a delta)
|
|
|
|
fn handleMotion(
|
|
|
|
listener: *wl.Listener(*wlr.Pointer.event.Motion),
|
|
|
|
event: *wlr.Pointer.event.Motion,
|
|
|
|
) void {
|
|
|
|
const self = @fieldParentPtr(Self, "motion", listener);
|
2020-08-07 02:51:53 -07:00
|
|
|
|
2020-08-13 03:22:32 -07:00
|
|
|
self.seat.handleActivity();
|
|
|
|
|
2021-02-15 12:07:29 -08:00
|
|
|
self.processMotion(event.device, event.time_msec, event.delta_x, event.delta_y, event.unaccel_dx, event.unaccel_dy);
|
2020-05-02 07:47:10 -07:00
|
|
|
}
|
|
|
|
|
2020-11-03 15:23:21 -08:00
|
|
|
fn handleRequestSetCursor(
|
|
|
|
listener: *wl.Listener(*wlr.Seat.event.RequestSetCursor),
|
|
|
|
event: *wlr.Seat.event.RequestSetCursor,
|
|
|
|
) void {
|
2020-05-02 07:47:10 -07:00
|
|
|
// This event is rasied by the seat when a client provides a cursor image
|
2020-11-03 15:23:21 -08:00
|
|
|
const self = @fieldParentPtr(Self, "request_set_cursor", listener);
|
2020-05-02 07:47:10 -07:00
|
|
|
const focused_client = self.seat.wlr_seat.pointer_state.focused_client;
|
|
|
|
|
|
|
|
// This can be sent by any client, so we check to make sure this one is
|
|
|
|
// actually has pointer focus first.
|
|
|
|
if (focused_client == event.seat_client) {
|
|
|
|
// Once we've vetted the client, we can tell the cursor to use the
|
|
|
|
// provided surface as the cursor image. It will set the hardware cursor
|
|
|
|
// on the output that it's currently on and continue to do so as the
|
|
|
|
// cursor moves between outputs.
|
2021-02-05 00:46:18 -08:00
|
|
|
log.debug("focused client set cursor", .{});
|
2020-11-03 15:23:21 -08:00
|
|
|
self.wlr_cursor.setSurface(event.surface, event.hotspot_x, event.hotspot_y);
|
2023-12-01 05:57:18 -08:00
|
|
|
self.xcursor_name = null;
|
2020-04-18 03:21:43 -07:00
|
|
|
}
|
2020-05-02 07:47:10 -07:00
|
|
|
}
|
|
|
|
|
2022-02-27 15:39:10 -08:00
|
|
|
pub fn hide(self: *Self) void {
|
|
|
|
if (self.pressed_count > 0) return;
|
|
|
|
self.hidden = true;
|
2023-12-01 05:57:18 -08:00
|
|
|
self.wlr_cursor.unsetImage();
|
|
|
|
self.xcursor_name = null;
|
2022-02-27 15:39:10 -08:00
|
|
|
self.seat.wlr_seat.pointerNotifyClearFocus();
|
|
|
|
self.hide_cursor_timer.timerUpdate(0) catch {
|
|
|
|
log.err("failed to update cursor hide timeout", .{});
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn unhide(self: *Self) void {
|
|
|
|
self.hide_cursor_timer.timerUpdate(server.config.cursor_hide_timeout) catch {
|
|
|
|
log.err("failed to update cursor hide timeout", .{});
|
|
|
|
};
|
|
|
|
if (!self.hidden) return;
|
|
|
|
self.hidden = false;
|
|
|
|
self.updateState();
|
|
|
|
}
|
|
|
|
|
2022-11-04 16:29:51 -07:00
|
|
|
fn handleHideCursorTimeout(self: *Self) c_int {
|
2022-02-27 15:39:10 -08:00
|
|
|
log.debug("hide cursor timeout", .{});
|
|
|
|
self.hide();
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2023-03-03 10:43:07 -08:00
|
|
|
pub fn startMove(cursor: *Self, view: *View) void {
|
2023-03-05 13:39:47 -08:00
|
|
|
if (cursor.constraint) |constraint| {
|
|
|
|
if (constraint.state == .active) constraint.deactivate();
|
|
|
|
}
|
|
|
|
|
2023-03-04 10:17:18 -08:00
|
|
|
const new_mode: Mode = .{ .move = .{
|
|
|
|
.view = view,
|
2023-10-16 07:18:36 -07:00
|
|
|
.offset_x = @as(i32, @intFromFloat(cursor.wlr_cursor.x)) - view.current.box.x,
|
|
|
|
.offset_y = @as(i32, @intFromFloat(cursor.wlr_cursor.y)) - view.current.box.y,
|
2023-03-04 10:17:18 -08:00
|
|
|
} };
|
2023-12-01 05:57:18 -08:00
|
|
|
cursor.enterMode(new_mode, view, "move");
|
2023-03-03 10:43:07 -08:00
|
|
|
}
|
2020-10-17 13:40:15 -07:00
|
|
|
|
2023-03-03 10:43:07 -08:00
|
|
|
pub fn startResize(cursor: *Self, view: *View, proposed_edges: ?wlr.Edges) void {
|
2023-03-05 13:39:47 -08:00
|
|
|
if (cursor.constraint) |constraint| {
|
|
|
|
if (constraint.state == .active) constraint.deactivate();
|
|
|
|
}
|
|
|
|
|
2023-03-03 10:43:07 -08:00
|
|
|
const edges = blk: {
|
|
|
|
if (proposed_edges) |edges| {
|
|
|
|
if (edges.top or edges.bottom or edges.left or edges.right) {
|
|
|
|
break :blk edges;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
break :blk cursor.computeEdges(view);
|
|
|
|
};
|
|
|
|
|
|
|
|
const box = &view.current.box;
|
2023-10-16 07:18:36 -07:00
|
|
|
const lx: i32 = @intFromFloat(cursor.wlr_cursor.x);
|
|
|
|
const ly: i32 = @intFromFloat(cursor.wlr_cursor.y);
|
2023-03-03 10:43:07 -08:00
|
|
|
const offset_x = if (edges.left) lx - box.x else box.x + box.width - lx;
|
|
|
|
const offset_y = if (edges.top) ly - box.y else box.y + box.height - ly;
|
|
|
|
|
|
|
|
view.pending.resizing = true;
|
|
|
|
|
|
|
|
const new_mode: Mode = .{ .resize = .{
|
|
|
|
.view = view,
|
|
|
|
.edges = edges,
|
|
|
|
.offset_x = offset_x,
|
|
|
|
.offset_y = offset_y,
|
2023-10-17 10:02:46 -07:00
|
|
|
.initial_width = @intCast(box.width),
|
|
|
|
.initial_height = @intCast(box.height),
|
2023-03-03 10:43:07 -08:00
|
|
|
} };
|
2023-12-01 05:57:18 -08:00
|
|
|
cursor.enterMode(new_mode, view, wlr.Xcursor.getResizeName(edges));
|
2023-03-03 10:43:07 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
fn computeEdges(cursor: *const Self, view: *const View) wlr.Edges {
|
|
|
|
const min_handle_size = 20;
|
|
|
|
const box = &view.current.box;
|
|
|
|
|
|
|
|
var output_box: wlr.Box = undefined;
|
|
|
|
server.root.output_layout.getBox(view.current.output.?.wlr_output, &output_box);
|
|
|
|
|
2023-10-16 07:18:36 -07:00
|
|
|
const sx = @as(i32, @intFromFloat(cursor.wlr_cursor.x)) - output_box.x - box.x;
|
|
|
|
const sy = @as(i32, @intFromFloat(cursor.wlr_cursor.y)) - output_box.y - box.y;
|
2023-03-03 10:43:07 -08:00
|
|
|
|
|
|
|
var edges: wlr.Edges = .{};
|
|
|
|
|
|
|
|
if (box.width > min_handle_size * 2) {
|
2023-10-16 07:18:36 -07:00
|
|
|
const handle = @max(min_handle_size, @divFloor(box.width, 5));
|
2023-03-03 10:43:07 -08:00
|
|
|
if (sx < handle) {
|
|
|
|
edges.left = true;
|
|
|
|
} else if (sx > box.width - handle) {
|
|
|
|
edges.right = true;
|
|
|
|
}
|
2021-12-28 22:19:37 -08:00
|
|
|
}
|
2020-10-17 13:40:15 -07:00
|
|
|
|
2023-03-03 10:43:07 -08:00
|
|
|
if (box.height > min_handle_size * 2) {
|
2023-10-16 07:18:36 -07:00
|
|
|
const handle = @max(min_handle_size, @divFloor(box.height, 5));
|
2023-03-03 10:43:07 -08:00
|
|
|
if (sy < handle) {
|
|
|
|
edges.top = true;
|
|
|
|
} else if (sy > box.height - handle) {
|
|
|
|
edges.bottom = true;
|
|
|
|
}
|
2021-12-28 22:19:37 -08:00
|
|
|
}
|
2020-10-17 13:40:15 -07:00
|
|
|
|
2023-03-03 10:43:07 -08:00
|
|
|
if (!edges.top and !edges.bottom and !edges.left and !edges.right) {
|
|
|
|
return .{ .bottom = true, .right = true };
|
|
|
|
} else {
|
|
|
|
return edges;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-12-01 05:57:18 -08:00
|
|
|
fn enterMode(cursor: *Self, mode: Mode, view: *View, xcursor_name: [*:0]const u8) void {
|
2023-03-03 11:09:20 -08:00
|
|
|
assert(cursor.mode == .passthrough or cursor.mode == .down);
|
2023-03-03 10:43:07 -08:00
|
|
|
assert(mode == .move or mode == .resize);
|
|
|
|
|
|
|
|
log.debug("enter {s} cursor mode", .{@tagName(mode)});
|
|
|
|
|
|
|
|
cursor.mode = mode;
|
2020-10-17 13:40:15 -07:00
|
|
|
|
2023-03-03 10:43:07 -08:00
|
|
|
cursor.seat.focus(view);
|
|
|
|
|
|
|
|
if (view.current.output.?.layout != null) {
|
|
|
|
view.float_box = view.current.box;
|
|
|
|
view.pending.float = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
cursor.seat.wlr_seat.pointerNotifyClearFocus();
|
2024-01-04 09:03:37 -08:00
|
|
|
cursor.setXcursor(xcursor_name);
|
2023-02-24 10:28:37 -08:00
|
|
|
|
|
|
|
server.root.applyPending();
|
2020-10-17 13:40:15 -07:00
|
|
|
}
|
|
|
|
|
2021-02-15 12:07:29 -08:00
|
|
|
fn processMotion(self: *Self, device: *wlr.InputDevice, time: u32, delta_x: f64, delta_y: f64, unaccel_dx: f64, unaccel_dy: f64) void {
|
2022-02-27 15:39:10 -08:00
|
|
|
self.unhide();
|
|
|
|
|
2021-05-13 06:13:17 -07:00
|
|
|
server.input_manager.relative_pointer_manager.sendRelativeMotion(
|
2021-02-15 12:07:29 -08:00
|
|
|
self.seat.wlr_seat,
|
|
|
|
@as(u64, time) * 1000,
|
|
|
|
delta_x,
|
|
|
|
delta_y,
|
|
|
|
unaccel_dx,
|
|
|
|
unaccel_dy,
|
|
|
|
);
|
2023-02-16 07:54:53 -08:00
|
|
|
|
2021-02-15 12:07:29 -08:00
|
|
|
var dx: f64 = delta_x;
|
|
|
|
var dy: f64 = delta_y;
|
2023-03-05 13:39:47 -08:00
|
|
|
|
|
|
|
if (self.constraint) |constraint| {
|
|
|
|
if (constraint.state == .active) {
|
|
|
|
switch (constraint.wlr_constraint.type) {
|
|
|
|
.locked => return,
|
|
|
|
.confined => constraint.confine(&dx, &dy),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-10-17 13:40:15 -07:00
|
|
|
switch (self.mode) {
|
2023-03-05 13:39:47 -08:00
|
|
|
.passthrough, .down => {
|
2021-02-15 12:07:29 -08:00
|
|
|
self.wlr_cursor.move(device, dx, dy);
|
2023-03-05 13:39:47 -08:00
|
|
|
|
|
|
|
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,
|
|
|
|
}
|
|
|
|
|
2023-02-16 07:54:53 -08:00
|
|
|
self.updateDragIcons();
|
2023-03-05 13:39:47 -08:00
|
|
|
|
|
|
|
if (self.constraint) |constraint| {
|
|
|
|
constraint.maybeActivate();
|
|
|
|
}
|
2020-10-17 13:40:15 -07:00
|
|
|
},
|
2023-10-17 10:02:46 -07:00
|
|
|
.move => |*data| {
|
2021-10-30 10:16:31 -07:00
|
|
|
dx += data.delta_x;
|
|
|
|
dy += data.delta_y;
|
|
|
|
data.delta_x = dx - @trunc(dx);
|
|
|
|
data.delta_y = dy - @trunc(dy);
|
|
|
|
|
2023-10-17 10:02:46 -07:00
|
|
|
data.view.pending.move(@intFromFloat(dx), @intFromFloat(dy));
|
|
|
|
|
|
|
|
server.root.applyPending();
|
|
|
|
},
|
|
|
|
.resize => |*data| {
|
2023-11-14 06:36:25 -08:00
|
|
|
dx += data.delta_x;
|
|
|
|
dy += data.delta_y;
|
|
|
|
data.delta_x = dx - @trunc(dx);
|
|
|
|
data.delta_y = dy - @trunc(dy);
|
|
|
|
|
|
|
|
data.x += @intFromFloat(dx);
|
|
|
|
data.y += @intFromFloat(dy);
|
2023-10-17 10:02:46 -07:00
|
|
|
|
|
|
|
// Modify width/height of the pending box, taking constraints into account
|
|
|
|
// The x/y coordinates of the view will be adjusted as needed in View.resizeCommit()
|
|
|
|
// based on the dimensions actually committed by the client.
|
|
|
|
const border_width = if (data.view.pending.ssd) server.config.border_width else 0;
|
|
|
|
|
|
|
|
var output_width: i32 = undefined;
|
|
|
|
var output_height: i32 = undefined;
|
|
|
|
data.view.current.output.?.wlr_output.effectiveResolution(&output_width, &output_height);
|
|
|
|
|
|
|
|
const constraints = &data.view.constraints;
|
|
|
|
const box = &data.view.pending.box;
|
|
|
|
|
|
|
|
if (data.edges.left) {
|
|
|
|
const x2 = box.x + box.width;
|
2023-11-14 06:36:25 -08:00
|
|
|
box.width = data.initial_width - data.x;
|
2023-10-17 10:02:46 -07:00
|
|
|
box.width = @max(box.width, constraints.min_width);
|
|
|
|
box.width = @min(box.width, constraints.max_width);
|
|
|
|
box.width = @min(box.width, x2 - border_width);
|
2023-11-14 06:36:25 -08:00
|
|
|
data.x = data.initial_width - box.width;
|
2023-10-17 10:02:46 -07:00
|
|
|
} else if (data.edges.right) {
|
2023-11-14 06:36:25 -08:00
|
|
|
box.width = data.initial_width + data.x;
|
2023-10-17 10:02:46 -07:00
|
|
|
box.width = @max(box.width, constraints.min_width);
|
|
|
|
box.width = @min(box.width, constraints.max_width);
|
|
|
|
box.width = @min(box.width, output_width - border_width - box.x);
|
2023-11-14 06:36:25 -08:00
|
|
|
data.x = box.width - data.initial_width;
|
2023-10-17 10:02:46 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
if (data.edges.top) {
|
|
|
|
const y2 = box.y + box.height;
|
2023-11-14 06:36:25 -08:00
|
|
|
box.height = data.initial_height - data.y;
|
2023-10-17 10:02:46 -07:00
|
|
|
box.height = @max(box.height, constraints.min_height);
|
|
|
|
box.height = @min(box.height, constraints.max_height);
|
|
|
|
box.height = @min(box.height, y2 - border_width);
|
2023-11-14 06:36:25 -08:00
|
|
|
data.y = data.initial_height - box.height;
|
2023-10-17 10:02:46 -07:00
|
|
|
} else if (data.edges.bottom) {
|
2023-11-14 06:36:25 -08:00
|
|
|
box.height = data.initial_height + data.y;
|
2023-10-17 10:02:46 -07:00
|
|
|
box.height = @max(box.height, constraints.min_height);
|
|
|
|
box.height = @min(box.height, constraints.max_height);
|
|
|
|
box.height = @min(box.height, output_height - border_width - box.y);
|
2023-11-14 06:36:25 -08:00
|
|
|
data.y = box.height - data.initial_height;
|
2023-03-03 10:43:07 -08:00
|
|
|
}
|
2020-10-17 13:40:15 -07:00
|
|
|
|
2023-02-24 10:28:37 -08:00
|
|
|
server.root.applyPending();
|
2020-10-17 13:40:15 -07:00
|
|
|
},
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-11-19 02:33:27 -08:00
|
|
|
pub fn checkFocusFollowsCursor(self: *Self) void {
|
2022-12-30 13:03:10 -08:00
|
|
|
// Don't do focus-follows-cursor if a pointer drag is in progress as focus
|
|
|
|
// change can't occur.
|
|
|
|
if (self.seat.drag == .pointer) return;
|
2021-11-19 02:33:27 -08:00
|
|
|
if (server.config.focus_follows_cursor == .disabled) return;
|
Cursor: keep focus_follows_cursor_target updated
This goes as close as possible to the behavior before this state was
introduced (keeping the improvement which needed it, 931405ab), fixing
various mis-interactions of keyboard and focus_follows_cursor focus
changes.
The following text is irrelevant to restoring correct basic FFC behavior
and talks about less common scenarios with regards to FFC clashing with
views' input region beyond their geometry, continuing the work done in
931405ab.
Scenario 1: the cursor traveling along a view's border in a "dead zone",
never initiating a focus change. If the focused view has an extended
input region, that area has some functionality (such as client-initiated
resizing); therefore it should be respected and even if another view's
geometry is also under the cursor, focus shouldn't change. In case of
unfocused views, it is a matter of consistency with the focused-view
case. This outcome is also easier to implement, as it doesn't require
any additional code.
Scenario 2: *clicking* such a dead zone, i.e. extended input region (of
an unfocused view). In question is not whether to focus the view (yes),
but whether the focus_follows_cursor_target should be set to the view as
well. Only one case seems relevant to me here, which is when ffc_target
is another view whose geometry is under the cursor, but covered by this
newly-focused view's input region. The most likely action following the
click is resizing the newly-focused view, where a touchpad or faulty
mouse could make the cursor move a bit farther after the button has been
released. I believe that ffc_target shouldn't have been updated, in
order to now prevent focus from skipping away.
(Another variant is me, wondering why the wrong view got focused and
trying to focus the right one using FFC. In that case, however, one
could ask if it's river that misbehaves and whether the application is
really well-integrated into the user's desktop when it provides a
feature they don't desire.)
2023-03-25 07:46:08 -07:00
|
|
|
|
|
|
|
const last_target = self.focus_follows_cursor_target;
|
|
|
|
self.updateFocusFollowsCursorTarget();
|
|
|
|
if (self.focus_follows_cursor_target) |view| {
|
|
|
|
// In .normal mode, only entering a view changes focus
|
|
|
|
if (server.config.focus_follows_cursor == .normal and
|
|
|
|
last_target == view) return;
|
|
|
|
if (self.seat.focused != .view or self.seat.focused.view != view) {
|
|
|
|
self.seat.focusOutput(view.current.output.?);
|
|
|
|
self.seat.focus(view);
|
|
|
|
server.root.applyPending();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn updateFocusFollowsCursorTarget(self: *Self) void {
|
2023-01-29 03:03:41 -08:00
|
|
|
if (server.root.at(self.wlr_cursor.x, self.wlr_cursor.y)) |result| {
|
2023-03-05 13:39:47 -08:00
|
|
|
switch (result.data) {
|
2022-12-31 10:51:42 -08:00
|
|
|
.view => |view| {
|
Cursor: keep focus_follows_cursor_target updated
This goes as close as possible to the behavior before this state was
introduced (keeping the improvement which needed it, 931405ab), fixing
various mis-interactions of keyboard and focus_follows_cursor focus
changes.
The following text is irrelevant to restoring correct basic FFC behavior
and talks about less common scenarios with regards to FFC clashing with
views' input region beyond their geometry, continuing the work done in
931405ab.
Scenario 1: the cursor traveling along a view's border in a "dead zone",
never initiating a focus change. If the focused view has an extended
input region, that area has some functionality (such as client-initiated
resizing); therefore it should be respected and even if another view's
geometry is also under the cursor, focus shouldn't change. In case of
unfocused views, it is a matter of consistency with the focused-view
case. This outcome is also easier to implement, as it doesn't require
any additional code.
Scenario 2: *clicking* such a dead zone, i.e. extended input region (of
an unfocused view). In question is not whether to focus the view (yes),
but whether the focus_follows_cursor_target should be set to the view as
well. Only one case seems relevant to me here, which is when ffc_target
is another view whose geometry is under the cursor, but covered by this
newly-focused view's input region. The most likely action following the
click is resizing the newly-focused view, where a touchpad or faulty
mouse could make the cursor move a bit farther after the button has been
released. I believe that ffc_target shouldn't have been updated, in
order to now prevent focus from skipping away.
(Another variant is me, wondering why the wrong view got focused and
trying to focus the right one using FFC. In that case, however, one
could ask if it's river that misbehaves and whether the application is
really well-integrated into the user's desktop when it provides a
feature they don't desire.)
2023-03-25 07:46:08 -07:00
|
|
|
// Some windows have an input region bigger than their window
|
|
|
|
// geometry, we only want to update this when the cursor
|
2022-12-31 10:51:42 -08:00
|
|
|
// properly enters the window (the box that we draw borders around)
|
Cursor: keep focus_follows_cursor_target updated
This goes as close as possible to the behavior before this state was
introduced (keeping the improvement which needed it, 931405ab), fixing
various mis-interactions of keyboard and focus_follows_cursor focus
changes.
The following text is irrelevant to restoring correct basic FFC behavior
and talks about less common scenarios with regards to FFC clashing with
views' input region beyond their geometry, continuing the work done in
931405ab.
Scenario 1: the cursor traveling along a view's border in a "dead zone",
never initiating a focus change. If the focused view has an extended
input region, that area has some functionality (such as client-initiated
resizing); therefore it should be respected and even if another view's
geometry is also under the cursor, focus shouldn't change. In case of
unfocused views, it is a matter of consistency with the focused-view
case. This outcome is also easier to implement, as it doesn't require
any additional code.
Scenario 2: *clicking* such a dead zone, i.e. extended input region (of
an unfocused view). In question is not whether to focus the view (yes),
but whether the focus_follows_cursor_target should be set to the view as
well. Only one case seems relevant to me here, which is when ffc_target
is another view whose geometry is under the cursor, but covered by this
newly-focused view's input region. The most likely action following the
click is resizing the newly-focused view, where a touchpad or faulty
mouse could make the cursor move a bit farther after the button has been
released. I believe that ffc_target shouldn't have been updated, in
order to now prevent focus from skipping away.
(Another variant is me, wondering why the wrong view got focused and
trying to focus the right one using FFC. In that case, however, one
could ask if it's river that misbehaves and whether the application is
really well-integrated into the user's desktop when it provides a
feature they don't desire.)
2023-03-25 07:46:08 -07:00
|
|
|
// in order to avoid clashes with cursor warping on focus change.
|
2022-12-31 10:51:42 -08:00
|
|
|
var output_layout_box: wlr.Box = undefined;
|
2023-02-24 10:28:37 -08:00
|
|
|
server.root.output_layout.getBox(view.current.output.?.wlr_output, &output_layout_box);
|
2023-10-16 07:18:36 -07:00
|
|
|
|
|
|
|
const cursor_ox = self.wlr_cursor.x - @as(f64, @floatFromInt(output_layout_box.x));
|
|
|
|
const cursor_oy = self.wlr_cursor.y - @as(f64, @floatFromInt(output_layout_box.y));
|
Cursor: keep focus_follows_cursor_target updated
This goes as close as possible to the behavior before this state was
introduced (keeping the improvement which needed it, 931405ab), fixing
various mis-interactions of keyboard and focus_follows_cursor focus
changes.
The following text is irrelevant to restoring correct basic FFC behavior
and talks about less common scenarios with regards to FFC clashing with
views' input region beyond their geometry, continuing the work done in
931405ab.
Scenario 1: the cursor traveling along a view's border in a "dead zone",
never initiating a focus change. If the focused view has an extended
input region, that area has some functionality (such as client-initiated
resizing); therefore it should be respected and even if another view's
geometry is also under the cursor, focus shouldn't change. In case of
unfocused views, it is a matter of consistency with the focused-view
case. This outcome is also easier to implement, as it doesn't require
any additional code.
Scenario 2: *clicking* such a dead zone, i.e. extended input region (of
an unfocused view). In question is not whether to focus the view (yes),
but whether the focus_follows_cursor_target should be set to the view as
well. Only one case seems relevant to me here, which is when ffc_target
is another view whose geometry is under the cursor, but covered by this
newly-focused view's input region. The most likely action following the
click is resizing the newly-focused view, where a touchpad or faulty
mouse could make the cursor move a bit farther after the button has been
released. I believe that ffc_target shouldn't have been updated, in
order to now prevent focus from skipping away.
(Another variant is me, wondering why the wrong view got focused and
trying to focus the right one using FFC. In that case, however, one
could ask if it's river that misbehaves and whether the application is
really well-integrated into the user's desktop when it provides a
feature they don't desire.)
2023-03-25 07:46:08 -07:00
|
|
|
if (view.current.box.containsPoint(cursor_ox, cursor_oy)) {
|
|
|
|
self.focus_follows_cursor_target = view;
|
2022-12-31 10:51:42 -08:00
|
|
|
}
|
|
|
|
},
|
Cursor: keep focus_follows_cursor_target updated
This goes as close as possible to the behavior before this state was
introduced (keeping the improvement which needed it, 931405ab), fixing
various mis-interactions of keyboard and focus_follows_cursor focus
changes.
The following text is irrelevant to restoring correct basic FFC behavior
and talks about less common scenarios with regards to FFC clashing with
views' input region beyond their geometry, continuing the work done in
931405ab.
Scenario 1: the cursor traveling along a view's border in a "dead zone",
never initiating a focus change. If the focused view has an extended
input region, that area has some functionality (such as client-initiated
resizing); therefore it should be respected and even if another view's
geometry is also under the cursor, focus shouldn't change. In case of
unfocused views, it is a matter of consistency with the focused-view
case. This outcome is also easier to implement, as it doesn't require
any additional code.
Scenario 2: *clicking* such a dead zone, i.e. extended input region (of
an unfocused view). In question is not whether to focus the view (yes),
but whether the focus_follows_cursor_target should be set to the view as
well. Only one case seems relevant to me here, which is when ffc_target
is another view whose geometry is under the cursor, but covered by this
newly-focused view's input region. The most likely action following the
click is resizing the newly-focused view, where a touchpad or faulty
mouse could make the cursor move a bit farther after the button has been
released. I believe that ffc_target shouldn't have been updated, in
order to now prevent focus from skipping away.
(Another variant is me, wondering why the wrong view got focused and
trying to focus the right one using FFC. In that case, however, one
could ask if it's river that misbehaves and whether the application is
really well-integrated into the user's desktop when it provides a
feature they don't desire.)
2023-03-25 07:46:08 -07:00
|
|
|
.layer_surface, .lock_surface => {
|
|
|
|
self.focus_follows_cursor_target = null;
|
|
|
|
},
|
|
|
|
.xwayland_override_redirect => {
|
|
|
|
assert(build_options.xwayland);
|
|
|
|
self.focus_follows_cursor_target = null;
|
|
|
|
},
|
2021-11-19 02:33:27 -08:00
|
|
|
}
|
2022-12-31 10:51:42 -08:00
|
|
|
} else {
|
Cursor: keep focus_follows_cursor_target updated
This goes as close as possible to the behavior before this state was
introduced (keeping the improvement which needed it, 931405ab), fixing
various mis-interactions of keyboard and focus_follows_cursor focus
changes.
The following text is irrelevant to restoring correct basic FFC behavior
and talks about less common scenarios with regards to FFC clashing with
views' input region beyond their geometry, continuing the work done in
931405ab.
Scenario 1: the cursor traveling along a view's border in a "dead zone",
never initiating a focus change. If the focused view has an extended
input region, that area has some functionality (such as client-initiated
resizing); therefore it should be respected and even if another view's
geometry is also under the cursor, focus shouldn't change. In case of
unfocused views, it is a matter of consistency with the focused-view
case. This outcome is also easier to implement, as it doesn't require
any additional code.
Scenario 2: *clicking* such a dead zone, i.e. extended input region (of
an unfocused view). In question is not whether to focus the view (yes),
but whether the focus_follows_cursor_target should be set to the view as
well. Only one case seems relevant to me here, which is when ffc_target
is another view whose geometry is under the cursor, but covered by this
newly-focused view's input region. The most likely action following the
click is resizing the newly-focused view, where a touchpad or faulty
mouse could make the cursor move a bit farther after the button has been
released. I believe that ffc_target shouldn't have been updated, in
order to now prevent focus from skipping away.
(Another variant is me, wondering why the wrong view got focused and
trying to focus the right one using FFC. In that case, however, one
could ask if it's river that misbehaves and whether the application is
really well-integrated into the user's desktop when it provides a
feature they don't desire.)
2023-03-25 07:46:08 -07:00
|
|
|
// The cursor is not above any view
|
|
|
|
self.focus_follows_cursor_target = null;
|
2021-11-19 02:33:27 -08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-06-23 06:56:38 -07:00
|
|
|
/// 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.
|
2021-07-23 10:18:49 -07:00
|
|
|
pub fn updateState(self: *Self) void {
|
2022-08-01 17:31:50 -07:00
|
|
|
if (self.may_need_warp) {
|
|
|
|
self.warp();
|
|
|
|
}
|
2023-03-05 13:39:47 -08:00
|
|
|
|
|
|
|
if (self.constraint) |constraint| {
|
|
|
|
constraint.updateState();
|
|
|
|
}
|
|
|
|
|
2021-06-23 06:56:38 -07:00
|
|
|
switch (self.mode) {
|
2021-07-23 17:30:15 -07:00
|
|
|
.passthrough => {
|
Cursor: keep focus_follows_cursor_target updated
This goes as close as possible to the behavior before this state was
introduced (keeping the improvement which needed it, 931405ab), fixing
various mis-interactions of keyboard and focus_follows_cursor focus
changes.
The following text is irrelevant to restoring correct basic FFC behavior
and talks about less common scenarios with regards to FFC clashing with
views' input region beyond their geometry, continuing the work done in
931405ab.
Scenario 1: the cursor traveling along a view's border in a "dead zone",
never initiating a focus change. If the focused view has an extended
input region, that area has some functionality (such as client-initiated
resizing); therefore it should be respected and even if another view's
geometry is also under the cursor, focus shouldn't change. In case of
unfocused views, it is a matter of consistency with the focused-view
case. This outcome is also easier to implement, as it doesn't require
any additional code.
Scenario 2: *clicking* such a dead zone, i.e. extended input region (of
an unfocused view). In question is not whether to focus the view (yes),
but whether the focus_follows_cursor_target should be set to the view as
well. Only one case seems relevant to me here, which is when ffc_target
is another view whose geometry is under the cursor, but covered by this
newly-focused view's input region. The most likely action following the
click is resizing the newly-focused view, where a touchpad or faulty
mouse could make the cursor move a bit farther after the button has been
released. I believe that ffc_target shouldn't have been updated, in
order to now prevent focus from skipping away.
(Another variant is me, wondering why the wrong view got focused and
trying to focus the right one using FFC. In that case, however, one
could ask if it's river that misbehaves and whether the application is
really well-integrated into the user's desktop when it provides a
feature they don't desire.)
2023-03-25 07:46:08 -07:00
|
|
|
self.updateFocusFollowsCursorTarget();
|
2023-03-14 04:43:59 -07:00
|
|
|
if (!self.hidden) {
|
|
|
|
var now: os.timespec = undefined;
|
|
|
|
os.clock_gettime(os.CLOCK.MONOTONIC, &now) catch @panic("CLOCK_MONOTONIC not supported");
|
2023-10-16 07:18:36 -07:00
|
|
|
const msec: u32 = @intCast(now.tv_sec * std.time.ms_per_s +
|
2023-03-14 04:43:59 -07:00
|
|
|
@divTrunc(now.tv_nsec, std.time.ns_per_ms));
|
|
|
|
self.passthrough(msec);
|
|
|
|
}
|
2021-06-23 06:56:38 -07:00
|
|
|
},
|
2023-03-14 04:43:59 -07:00
|
|
|
// TODO: Leave down mode if the target surface is no longer visible.
|
|
|
|
.down => assert(!self.hidden),
|
2023-11-03 09:06:05 -07:00
|
|
|
.move, .resize => {
|
|
|
|
// Moving and resizing of views is handled through the transaction system. Therefore,
|
|
|
|
// we must inspect the inflight_mode instead if a move or a resize is in progress.
|
|
|
|
//
|
|
|
|
// The cases when a move/resize is being started or ended and e.g. mode is resize
|
|
|
|
// while inflight_mode is passthrough or mode is passthrough while inflight_mode
|
|
|
|
// is resize shouldn't need any special handling.
|
|
|
|
//
|
|
|
|
// In the first case, a move/resize has been started along with a transaction but the
|
|
|
|
// transaction hasn't been committed yet so there is nothing to do.
|
|
|
|
//
|
|
|
|
// In the second case, a move/resize has been terminated by the user but the
|
|
|
|
// transaction carrying out the final size/position change is still inflight.
|
|
|
|
// Therefore, the user already expects the cursor to be free from the view and
|
|
|
|
// we should not warp it back to the fixed offset of the move/resize.
|
|
|
|
switch (self.inflight_mode) {
|
|
|
|
.passthrough, .down => {},
|
|
|
|
inline .move, .resize => |data, mode| {
|
|
|
|
assert(!self.hidden);
|
|
|
|
|
|
|
|
// These conditions are checked in Root.applyPending()
|
|
|
|
assert(data.view.current.tags & data.view.current.output.?.current.tags != 0);
|
|
|
|
assert(data.view.current.float or data.view.current.output.?.layout == null);
|
|
|
|
assert(!data.view.current.fullscreen);
|
|
|
|
|
|
|
|
// Keep the cursor locked to the original offset from the edges of the view.
|
|
|
|
const box = &data.view.current.box;
|
|
|
|
const new_x: f64 = blk: {
|
|
|
|
if (mode == .move or data.edges.left) {
|
|
|
|
break :blk @floatFromInt(data.offset_x + box.x);
|
|
|
|
} else if (data.edges.right) {
|
|
|
|
break :blk @floatFromInt(box.x + box.width - data.offset_x);
|
|
|
|
} else {
|
|
|
|
break :blk self.wlr_cursor.x;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
const new_y: f64 = blk: {
|
|
|
|
if (mode == .move or data.edges.top) {
|
|
|
|
break :blk @floatFromInt(data.offset_y + box.y);
|
|
|
|
} else if (data.edges.bottom) {
|
|
|
|
break :blk @floatFromInt(box.y + box.height - data.offset_y);
|
|
|
|
} else {
|
|
|
|
break :blk self.wlr_cursor.y;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
self.wlr_cursor.warpClosest(null, new_x, new_y);
|
|
|
|
},
|
|
|
|
}
|
2021-06-23 06:56:38 -07:00
|
|
|
},
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-10-17 13:40:15 -07:00
|
|
|
/// Pass an event on to the surface under the cursor, if any.
|
|
|
|
fn passthrough(self: *Self, time: u32) void {
|
2021-07-14 06:32:24 -07:00
|
|
|
assert(self.mode == .passthrough);
|
|
|
|
|
2023-01-29 03:03:41 -08:00
|
|
|
if (server.root.at(self.wlr_cursor.x, self.wlr_cursor.y)) |result| {
|
2023-03-05 13:39:47 -08:00
|
|
|
if (result.data == .lock_surface) {
|
2023-01-31 06:47:19 -08:00
|
|
|
assert(server.lock_manager.state != .unlocked);
|
|
|
|
} else {
|
|
|
|
assert(server.lock_manager.state != .locked);
|
|
|
|
}
|
|
|
|
|
2023-01-29 03:03:41 -08:00
|
|
|
if (result.surface) |surface| {
|
|
|
|
self.seat.wlr_seat.pointerNotifyEnter(surface, result.sx, result.sy);
|
|
|
|
self.seat.wlr_seat.pointerNotifyMotion(time, result.sx, result.sy);
|
|
|
|
return;
|
|
|
|
}
|
2020-10-17 13:40:15 -07:00
|
|
|
}
|
2023-01-29 03:03:41 -08:00
|
|
|
|
|
|
|
self.clearFocus();
|
2020-10-17 13:40:15 -07:00
|
|
|
}
|
2022-08-01 17:31:50 -07:00
|
|
|
|
|
|
|
fn warp(self: *Self) void {
|
|
|
|
self.may_need_warp = false;
|
2023-02-24 10:28:37 -08:00
|
|
|
|
|
|
|
const focused_output = self.seat.focused_output orelse return;
|
|
|
|
|
2022-08-01 17:31:50 -07:00
|
|
|
// Warp pointer to center of the focused view/output (In layout coordinates) if enabled.
|
|
|
|
var output_layout_box: wlr.Box = undefined;
|
2023-02-24 10:28:37 -08:00
|
|
|
server.root.output_layout.getBox(focused_output.wlr_output, &output_layout_box);
|
2022-08-01 17:31:50 -07:00
|
|
|
const target_box = switch (server.config.warp_cursor) {
|
|
|
|
.disabled => return,
|
|
|
|
.@"on-output-change" => output_layout_box,
|
|
|
|
.@"on-focus-change" => switch (self.seat.focused) {
|
|
|
|
.layer, .lock_surface, .none => output_layout_box,
|
|
|
|
.view => |view| wlr.Box{
|
|
|
|
.x = output_layout_box.x + view.current.box.x,
|
|
|
|
.y = output_layout_box.y + view.current.box.y,
|
|
|
|
.width = view.current.box.width,
|
|
|
|
.height = view.current.box.height,
|
|
|
|
},
|
2022-11-04 16:29:51 -07:00
|
|
|
.xwayland_override_redirect => |or_window| wlr.Box{
|
|
|
|
.x = or_window.xwayland_surface.x,
|
|
|
|
.y = or_window.xwayland_surface.y,
|
|
|
|
.width = or_window.xwayland_surface.width,
|
|
|
|
.height = or_window.xwayland_surface.height,
|
2022-08-01 17:31:50 -07:00
|
|
|
},
|
|
|
|
},
|
|
|
|
};
|
|
|
|
// Checking against the usable box here gives much better UX when, for example,
|
|
|
|
// a status bar allows using the pointer to change tag/view focus.
|
2023-02-24 10:28:37 -08:00
|
|
|
const usable_box = focused_output.usable_box;
|
2022-08-01 17:31:50 -07:00
|
|
|
const usable_layout_box = wlr.Box{
|
|
|
|
.x = output_layout_box.x + usable_box.x,
|
|
|
|
.y = output_layout_box.y + usable_box.y,
|
|
|
|
.width = usable_box.width,
|
|
|
|
.height = usable_box.height,
|
|
|
|
};
|
|
|
|
if (!output_layout_box.containsPoint(self.wlr_cursor.x, self.wlr_cursor.y) or
|
|
|
|
(usable_layout_box.containsPoint(self.wlr_cursor.x, self.wlr_cursor.y) and
|
|
|
|
!target_box.containsPoint(self.wlr_cursor.x, self.wlr_cursor.y)))
|
|
|
|
{
|
2023-10-16 07:18:36 -07:00
|
|
|
const lx: f64 = @floatFromInt(target_box.x + @divTrunc(target_box.width, 2));
|
|
|
|
const ly: f64 = @floatFromInt(target_box.y + @divTrunc(target_box.height, 2));
|
2022-08-01 17:31:50 -07:00
|
|
|
if (!self.wlr_cursor.warp(null, lx, ly)) {
|
|
|
|
log.err("failed to warp cursor on focus change", .{});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2023-02-16 07:54:53 -08:00
|
|
|
|
|
|
|
fn updateDragIcons(self: *Self) void {
|
2023-02-20 09:01:24 -08:00
|
|
|
var it = server.root.drag_icons.children.iterator(.forward);
|
2023-02-16 07:54:53 -08:00
|
|
|
while (it.next()) |node| {
|
2023-10-16 07:18:36 -07:00
|
|
|
const icon = @as(*DragIcon, @ptrFromInt(node.data));
|
2023-02-16 07:54:53 -08:00
|
|
|
|
2023-10-25 13:01:05 -07:00
|
|
|
if (icon.wlr_drag_icon.drag.seat == self.seat.wlr_seat) {
|
|
|
|
icon.updatePosition(self);
|
2023-02-16 07:54:53 -08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|