cursor: Allow leave/enter events within the same XDG toplevel

Attempting to open a GTK3 menu and activate a menu item in it,
using a single mouse motion (press-move-release), was broken due
to GTK apparently expecting to receive leave/enter events when the
cursor enters the menu (XDG popup).

To fix the issue, allow leave/enter events when the cursor is
moved between an XDG toplevel and popups of the same.

v2:
 - Use (struct view *) as proxy for toplevel in comparisons
 - Update seat->pressed.surface when entering/leaving popups
v3:
 - Go back to using get_toplevel() rather than (struct view *)
This commit is contained in:
John Lindgren 2022-09-10 01:57:39 -04:00
parent a8fbe1aac2
commit e18f7a32ba
3 changed files with 70 additions and 14 deletions

View file

@ -115,6 +115,7 @@ struct seat {
struct view *view; struct view *view;
struct wlr_scene_node *node; struct wlr_scene_node *node;
struct wlr_surface *surface; struct wlr_surface *surface;
struct wlr_surface *toplevel;
} pressed; } pressed;
struct wl_client *active_client_while_inhibited; struct wl_client *active_client_while_inhibited;
@ -563,7 +564,8 @@ void seat_reconfigure(struct server *server);
void seat_focus_surface(struct seat *seat, struct wlr_surface *surface); void seat_focus_surface(struct seat *seat, struct wlr_surface *surface);
void seat_set_focus_layer(struct seat *seat, struct wlr_layer_surface_v1 *layer); void seat_set_focus_layer(struct seat *seat, struct wlr_layer_surface_v1 *layer);
void seat_set_pressed(struct seat *seat, struct view *view, void seat_set_pressed(struct seat *seat, struct view *view,
struct wlr_scene_node *node, struct wlr_surface *surface); struct wlr_scene_node *node, struct wlr_surface *surface,
struct wlr_surface *toplevel);
void seat_reset_pressed(struct seat *seat); void seat_reset_pressed(struct seat *seat);
void interactive_begin(struct view *view, enum input_mode mode, void interactive_begin(struct view *view, enum input_mode mode,

View file

@ -45,6 +45,32 @@ is_surface(enum ssd_part_type view_area)
; ;
} }
static struct wlr_surface *
get_toplevel(struct wlr_surface *surface)
{
while (surface && wlr_surface_is_xdg_surface(surface)) {
struct wlr_xdg_surface *xdg_surface =
wlr_xdg_surface_from_wlr_surface(surface);
if (!xdg_surface) {
return NULL;
}
switch (xdg_surface->role) {
case WLR_XDG_SURFACE_ROLE_NONE:
return NULL;
case WLR_XDG_SURFACE_ROLE_TOPLEVEL:
return surface;
case WLR_XDG_SURFACE_ROLE_POPUP:
surface = xdg_surface->popup->parent;
continue;
}
}
if (surface && wlr_surface_is_layer_surface(surface)) {
return surface;
}
return NULL;
}
static void static void
request_cursor_notify(struct wl_listener *listener, void *data) request_cursor_notify(struct wl_listener *listener, void *data)
{ {
@ -227,6 +253,28 @@ input_inhibit_blocks_surface(struct seat *seat, struct wl_resource *resource)
&& inhibiting_client != wl_resource_get_client(resource); && inhibiting_client != wl_resource_get_client(resource);
} }
static bool
update_pressed_surface(struct seat *seat, struct view *view,
struct wlr_scene_node *node, struct wlr_surface *surface)
{
/*
* In most cases, we don't want to leave one surface and enter
* another while a button is pressed. However, GTK/Wayland
* menus (implemented as XDG popups) do need the leave/enter
* events to work properly. To cover this case, we allow
* leave/enter events between XDG popups and their toplevel.
*/
if (seat->pressed.surface && surface != seat->pressed.surface) {
struct wlr_surface *toplevel = get_toplevel(surface);
if (toplevel && toplevel == seat->pressed.toplevel) {
seat_set_pressed(seat, view, node,
surface, toplevel);
return true;
}
}
return false;
}
static void static void
process_cursor_motion_out_of_surface(struct server *server, uint32_t time) process_cursor_motion_out_of_surface(struct server *server, uint32_t time)
{ {
@ -267,10 +315,10 @@ process_cursor_motion_out_of_surface(struct server *server, uint32_t time)
* Common logic shared by cursor_update_focus() and process_cursor_motion() * Common logic shared by cursor_update_focus() and process_cursor_motion()
*/ */
static void static void
cursor_update_common(struct server *server, struct wlr_scene_node *node, cursor_update_common(struct server *server, struct view *view,
struct wlr_surface *surface, double sx, double sy, struct wlr_scene_node *node, struct wlr_surface *surface,
enum ssd_part_type view_area, uint32_t time_msec, double sx, double sy, enum ssd_part_type view_area,
bool cursor_has_moved) uint32_t time_msec, bool cursor_has_moved)
{ {
struct seat *seat = &server->seat; struct seat *seat = &server->seat;
struct wlr_seat *wlr_seat = seat->seat; struct wlr_seat *wlr_seat = seat->seat;
@ -287,6 +335,7 @@ cursor_update_common(struct server *server, struct wlr_scene_node *node,
/* TODO: verify drag_icon logic */ /* TODO: verify drag_icon logic */
if (seat->pressed.surface && surface != seat->pressed.surface if (seat->pressed.surface && surface != seat->pressed.surface
&& !update_pressed_surface(seat, view, node, surface)
&& !seat->drag_icon) { && !seat->drag_icon) {
if (cursor_has_moved) { if (cursor_has_moved) {
/* /*
@ -398,8 +447,8 @@ process_cursor_motion(struct server *server, uint32_t time)
} }
} }
cursor_update_common(server, node, surface, sx, sy, view_area, time, cursor_update_common(server, view, node, surface, sx, sy, view_area,
/*cursor_has_moved*/ true); time, /*cursor_has_moved*/ true);
} }
static uint32_t static uint32_t
@ -420,15 +469,15 @@ cursor_update_focus(struct server *server)
clock_gettime(CLOCK_MONOTONIC, &now); clock_gettime(CLOCK_MONOTONIC, &now);
struct seat *seat = &server->seat; struct seat *seat = &server->seat;
desktop_node_and_view_at(seat->server, seat->cursor->x, struct view *view = desktop_node_and_view_at(seat->server,
seat->cursor->y, &node, &sx, &sy, &view_area); seat->cursor->x, seat->cursor->y, &node, &sx, &sy, &view_area);
if (is_surface(view_area)) { if (is_surface(view_area)) {
surface = lab_wlr_surface_from_node(node); surface = lab_wlr_surface_from_node(node);
} }
/* Focus surface under cursor if it isn't already focused */ /* Focus surface under cursor if it isn't already focused */
cursor_update_common(server, node, surface, sx, sy, view_area, cursor_update_common(server, view, node, surface, sx, sy, view_area,
msec(&now), /*cursor_has_moved*/ false); msec(&now), /*cursor_has_moved*/ false);
} }
@ -798,8 +847,9 @@ cursor_button(struct wl_listener *listener, void *data)
if (server->input_mode == LAB_INPUT_STATE_MENU) { if (server->input_mode == LAB_INPUT_STATE_MENU) {
if (close_menu) { if (close_menu) {
menu_close_root(server); menu_close_root(server);
cursor_update_common(server, node, surface, sx, sy, cursor_update_common(server, view, node,
view_area, event->time_msec, false); surface, sx, sy, view_area,
event->time_msec, false);
close_menu = false; close_menu = false;
} }
return; return;
@ -814,7 +864,8 @@ cursor_button(struct wl_listener *listener, void *data)
/* Handle _press */ /* Handle _press */
if (surface) { if (surface) {
seat_set_pressed(seat, view, node, surface); seat_set_pressed(seat, view, node, surface,
get_toplevel(surface));
} }
if (server->input_mode == LAB_INPUT_STATE_MENU) { if (server->input_mode == LAB_INPUT_STATE_MENU) {

View file

@ -383,7 +383,8 @@ pressed_surface_destroy(struct wl_listener *listener, void *data)
void void
seat_set_pressed(struct seat *seat, struct view *view, seat_set_pressed(struct seat *seat, struct view *view,
struct wlr_scene_node *node, struct wlr_surface *surface) struct wlr_scene_node *node, struct wlr_surface *surface,
struct wlr_surface *toplevel)
{ {
assert(surface); assert(surface);
seat_reset_pressed(seat); seat_reset_pressed(seat);
@ -391,6 +392,7 @@ seat_set_pressed(struct seat *seat, struct view *view,
seat->pressed.view = view; seat->pressed.view = view;
seat->pressed.node = node; seat->pressed.node = node;
seat->pressed.surface = surface; seat->pressed.surface = surface;
seat->pressed.toplevel = toplevel;
seat->pressed_surface_destroy.notify = pressed_surface_destroy; seat->pressed_surface_destroy.notify = pressed_surface_destroy;
wl_signal_add(&surface->events.destroy, &seat->pressed_surface_destroy); wl_signal_add(&surface->events.destroy, &seat->pressed_surface_destroy);
} }
@ -402,6 +404,7 @@ seat_reset_pressed(struct seat *seat)
seat->pressed.view = NULL; seat->pressed.view = NULL;
seat->pressed.node = NULL; seat->pressed.node = NULL;
seat->pressed.surface = NULL; seat->pressed.surface = NULL;
seat->pressed.toplevel = NULL;
wl_list_remove(&seat->pressed_surface_destroy.link); wl_list_remove(&seat->pressed_surface_destroy.link);
} }
} }