input: stash current node and surface in cursor

If an actively constrained surface is partially occluded by another node
(say, by a floating node), the cursor should remain constrained and pass
"under" the floating node.

Previously, this did not always happen, as `node_at_coords` is a simple
top-down hit test that doesn't (and shouldn't) cover constraints.

Furthermore, whether the cursor should remain constrained is a function
of the input device type: only pointer devices should be constrained.

This commit addresses this issue by stashing the surface and node that
currently have cursor input focus, and using the stashed values
everywhere.

When `wlr_cursor_move` and friends are called, one must now take care to
also call `cursor_update_current_node` with the `wlr_input_device` that
initiated the movement.

Fixes #6073.
This commit is contained in:
Tudor Brindus 2021-03-02 23:55:10 -05:00
parent 1afedcb94c
commit bfc22725b8
5 changed files with 82 additions and 48 deletions

View file

@ -22,6 +22,15 @@ struct sway_cursor {
double x, y;
struct sway_node *node;
} previous;
// Not to be confused with `previous` above; they store entirely unrelated
// data.
struct {
double sx, sy;
struct sway_node *node;
struct wlr_surface *surface;
} current;
struct wlr_xcursor_manager *xcursor_manager;
struct wl_list tablets;
struct wl_list tablet_pads;
@ -79,10 +88,6 @@ struct sway_cursor {
struct sway_node;
struct sway_node *node_at_coords(
struct sway_seat *seat, double lx, double ly,
struct wlr_surface **surface, double *sx, double *sy);
void sway_cursor_destroy(struct sway_cursor *cursor);
struct sway_cursor *sway_cursor_create(struct sway_seat *seat);

View file

@ -618,11 +618,7 @@ void seat_execute_command(struct sway_seat *seat, struct sway_binding *binding)
struct sway_container *con = NULL;
if (binding->type == BINDING_MOUSESYM
|| binding->type == BINDING_MOUSECODE) {
struct wlr_surface *surface = NULL;
double sx, sy;
struct sway_node *node = node_at_coords(seat,
seat->cursor->cursor->x, seat->cursor->cursor->y,
&surface, &sx, &sy);
struct sway_node *node = seat->cursor->current.node;
if (node && node->type == N_CONTAINER) {
con = node->sway_container;
}

View file

@ -74,7 +74,7 @@ static struct wlr_surface *layer_surface_popup_at(struct sway_output *output,
* Returns the node at the cursor's position. If there is a surface at that
* location, it is stored in **surface (it may not be a view).
*/
struct sway_node *node_at_coords(
static struct sway_node *node_at_coords(
struct sway_seat *seat, double lx, double ly,
struct wlr_surface **surface, double *sx, double *sy) {
// check for unmanaged views first
@ -340,6 +340,50 @@ void cursor_unhide(struct sway_cursor *cursor) {
wl_event_source_timer_update(cursor->hide_source, cursor_get_timeout(cursor));
}
static bool cursor_motion_is_constrained(struct sway_cursor *cursor,
enum wlr_input_device_type type) {
// Only apply pointer constraints to real pointer input.
return cursor->active_constraint && type == WLR_INPUT_DEVICE_POINTER;
}
static void cursor_update_current_node(struct sway_cursor *cursor,
enum wlr_input_device_type type) {
struct sway_node *node = NULL;
struct wlr_surface *surface = NULL;
double sx = 0, sy = 0;
if (cursor_motion_is_constrained(cursor, type)) {
struct wlr_pointer_constraint_v1 *constraint = cursor->active_constraint;
struct sway_view *view = view_from_wlr_surface(constraint->surface);
if (view) {
struct sway_container *con = view->container;
sx = cursor->cursor->x - con->pending.content_x + view->geometry.x;
sy = cursor->cursor->y - con->pending.content_y + view->geometry.y;
if (pixman_region32_contains_point(&constraint->region, floor(sx),
floor(sy), NULL)) {
node = &view->container->node;
surface = constraint->surface;
}
}
}
// This is subtle: this means that if an actively constrained surface is
// partially occluded by another node (say, by a floating node), the cursor
// shall remain constrained and pass "under" the floating node. Need to
// check both `node` and `surface` since neither implies the other.
if (!node && !surface) {
node = node_at_coords(cursor->seat, cursor->cursor->x, cursor->cursor->y,
&surface, &sx, &sy);
}
cursor->current.sx = sx;
cursor->current.sy = sy;
cursor->current.node = node;
cursor->current.surface = surface;
}
static void pointer_motion(struct sway_cursor *cursor, uint32_t time_msec,
struct wlr_input_device *device, double dx, double dy,
double dx_unaccel, double dy_unaccel) {
@ -348,17 +392,12 @@ static void pointer_motion(struct sway_cursor *cursor, uint32_t time_msec,
cursor->seat->wlr_seat, (uint64_t)time_msec * 1000,
dx, dy, dx_unaccel, dy_unaccel);
// Only apply pointer constraints to real pointer input.
if (cursor->active_constraint && device->type == WLR_INPUT_DEVICE_POINTER) {
struct wlr_surface *surface = NULL;
double sx, sy;
node_at_coords(cursor->seat,
cursor->cursor->x, cursor->cursor->y, &surface, &sx, &sy);
cursor_update_current_node(cursor, device->type);
if (cursor->active_constraint->surface != surface) {
return;
}
if (cursor_motion_is_constrained(cursor, device->type)) {
assert(cursor->active_constraint->surface == cursor->current.surface);
double sx = cursor->current.sx, sy = cursor->current.sy;
double sx_confined, sy_confined;
if (!wlr_region_confine(&cursor->confine, sx, sy, sx + dx, sy + dy,
&sx_confined, &sy_confined)) {
@ -370,6 +409,7 @@ static void pointer_motion(struct sway_cursor *cursor, uint32_t time_msec,
}
wlr_cursor_move(cursor->cursor, device, dx, dy);
cursor_update_current_node(cursor, device->type);
seatop_pointer_motion(cursor->seat, time_msec);
}
@ -608,10 +648,8 @@ static void handle_tablet_tool_position(struct sway_cursor *cursor,
break;
}
double sx, sy;
struct wlr_surface *surface = NULL;
struct wlr_surface *surface = cursor->current.surface;
struct sway_seat *seat = cursor->seat;
node_at_coords(seat, cursor->cursor->x, cursor->cursor->y, &surface, &sx, &sy);
// The logic for whether we should send a tablet event or an emulated pointer
// event is tricky. It comes down to:
@ -626,6 +664,7 @@ static void handle_tablet_tool_position(struct sway_cursor *cursor,
if (!cursor->simulating_pointer_from_tool_tip &&
((surface && wlr_surface_accepts_tablet_v2(tablet->tablet_v2, surface)) ||
wlr_tablet_tool_v2_has_implicit_grab(tool->tablet_v2_tool))) {
cursor_update_current_node(cursor, input_device->wlr_device->type);
seatop_tablet_tool_motion(seat, tool, time_msec);
} else {
wlr_tablet_v2_tablet_tool_notify_proximity_out(tool->tablet_v2_tool);
@ -839,8 +878,11 @@ static void check_constraint_region(struct sway_cursor *cursor) {
sx + con->pending.content_x - view->geometry.x,
sy + con->pending.content_y - view->geometry.y);
cursor_update_current_node(cursor, WLR_INPUT_DEVICE_POINTER);
cursor_rebase(cursor);
}
} else {
cursor_update_current_node(cursor, WLR_INPUT_DEVICE_POINTER);
}
}
@ -1279,6 +1321,7 @@ static void warp_to_constraint_cursor_hint(struct sway_cursor *cursor) {
// Warp the pointer as well, so that on the next pointer rebase we don't
// send an unexpected synthetic motion event to clients.
wlr_seat_pointer_warp(constraint->seat, sx, sy);
cursor_update_current_node(cursor, WLR_INPUT_DEVICE_POINTER);
}
}

View file

@ -213,10 +213,9 @@ static void handle_tablet_tool_tip(struct sway_seat *seat,
}
struct sway_cursor *cursor = seat->cursor;
struct wlr_surface *surface = NULL;
double sx, sy;
struct sway_node *node = node_at_coords(seat,
cursor->cursor->x, cursor->cursor->y, &surface, &sx, &sy);
struct wlr_surface *surface = cursor->current.surface;
double sx = cursor->current.sx, sy = cursor->current.sy;
struct sway_node *node = cursor->current.node;
if (!sway_assert(surface,
"Expected null-surface tablet input to route through pointer emulation")) {
@ -328,10 +327,9 @@ static void handle_button(struct sway_seat *seat, uint32_t time_msec,
struct sway_cursor *cursor = seat->cursor;
// Determine what's under the cursor
struct wlr_surface *surface = NULL;
double sx, sy;
struct sway_node *node = node_at_coords(seat,
cursor->cursor->x, cursor->cursor->y, &surface, &sx, &sy);
struct wlr_surface *surface = cursor->current.surface;
double sx = cursor->current.sx, sy = cursor->current.sy;
struct sway_node *node = cursor->current.node;
struct sway_container *cont = node && node->type == N_CONTAINER ?
node->sway_container : NULL;
@ -574,10 +572,9 @@ static void handle_pointer_motion(struct sway_seat *seat, uint32_t time_msec) {
struct seatop_default_event *e = seat->seatop_data;
struct sway_cursor *cursor = seat->cursor;
struct wlr_surface *surface = NULL;
double sx, sy;
struct sway_node *node = node_at_coords(seat,
cursor->cursor->x, cursor->cursor->y, &surface, &sx, &sy);
struct wlr_surface *surface = cursor->current.surface;
double sx = cursor->current.sx, sy = cursor->current.sy;
struct sway_node *node = cursor->current.node;
if (config->focus_follows_mouse != FOLLOWS_NO) {
check_focus_follows_mouse(seat, e, node);
@ -608,10 +605,9 @@ static void handle_tablet_tool_motion(struct sway_seat *seat,
struct seatop_default_event *e = seat->seatop_data;
struct sway_cursor *cursor = seat->cursor;
struct wlr_surface *surface = NULL;
double sx, sy;
struct sway_node *node = node_at_coords(seat,
cursor->cursor->x, cursor->cursor->y, &surface, &sx, &sy);
struct wlr_surface *surface = cursor->current.surface;
double sx = cursor->current.sx, sy = cursor->current.sy;
struct sway_node *node = cursor->current.node;
if (config->focus_follows_mouse != FOLLOWS_NO) {
check_focus_follows_mouse(seat, e, node);
@ -664,10 +660,8 @@ static void handle_pointer_axis(struct sway_seat *seat,
struct seatop_default_event *e = seat->seatop_data;
// Determine what's under the cursor
struct wlr_surface *surface = NULL;
double sx, sy;
struct sway_node *node = node_at_coords(seat,
cursor->cursor->x, cursor->cursor->y, &surface, &sx, &sy);
struct wlr_surface *surface = cursor->current.surface;
struct sway_node *node = cursor->current.node;
struct sway_container *cont = node && node->type == N_CONTAINER ?
node->sway_container : NULL;
enum wlr_edges edge = cont ? find_edge(cont, surface, cursor) : WLR_EDGE_NONE;
@ -756,8 +750,7 @@ static void handle_rebase(struct sway_seat *seat, uint32_t time_msec) {
struct sway_cursor *cursor = seat->cursor;
struct wlr_surface *surface = NULL;
double sx = 0.0, sy = 0.0;
e->previous_node = node_at_coords(seat,
cursor->cursor->x, cursor->cursor->y, &surface, &sx, &sy);
e->previous_node = cursor->current.node;
if (surface) {
if (seat_is_input_allowed(seat, surface)) {

View file

@ -94,11 +94,8 @@ static void resize_box(struct wlr_box *box, enum wlr_edges edge,
static void handle_motion_postthreshold(struct sway_seat *seat) {
struct seatop_move_tiling_event *e = seat->seatop_data;
struct wlr_surface *surface = NULL;
double sx, sy;
struct sway_cursor *cursor = seat->cursor;
struct sway_node *node = node_at_coords(seat,
cursor->cursor->x, cursor->cursor->y, &surface, &sx, &sy);
struct sway_node *node = cursor->current.node;
// Damage the old location
desktop_damage_box(&e->drop_box);