wclient_wl: fix cross-surface motion/release during implicit grab

When a popup menu is opened during a button-held drag, the compositor
changes pointer focus to the popup surface as the pointer enters it.
But pointer_handle_motion always delivered events to the originally
grabbed view (wlc_ptr_grab), never hit-testing inside the popup.
This meant wpopupbutton.c received motion events targeted at the
main button view with coordinates relative to that view, not the menu.

Fix: in both pointer_handle_motion and pointer_handle_button (release),
check whether the grab target is within the currently focused toplevel.
If not (the pointer entered a different surface), use wlc_hit_test
against wlc_ptr_focused to find the correct child view with proper
local coordinates.

Also enable enter/leave synthesis during grabs when target changes,
so child views receive proper WME_ENTER/WME_LEAVE as the pointer
moves between them across the cross-surface boundary.
This commit is contained in:
lkn 2026-06-07 13:31:16 +02:00
parent d2c9a59344
commit a63c97638d
1 changed files with 46 additions and 17 deletions

View File

@ -758,22 +758,39 @@ pointer_handle_motion(void *data, struct wl_pointer *p, uint32_t time,
int lx, ly;
W_View *target;
/* During implicit grab (button held), deliver to grabbed view */
/* During implicit grab (button held), deliver to grabbed view.
* If the pointer has entered a different toplevel surface (e.g. a
* popup menu opened during the drag), hit-test against the focused
* surface instead so motion events reach the correct child view
* (e.g. menuView) with proper local coordinates. */
if (wlc_ptr_grab) {
target = wlc_ptr_grab;
int ox = 0, oy = 0;
for (W_View *v = target; v && v != wlc_ptr_focused->view; v = v->parent) {
ox += v->pos.x;
oy += v->pos.y;
/* Check if grab target is within the currently focused toplevel */
int grab_in_focused = 0;
for (W_View *v = wlc_ptr_grab; v; v = v->parent) {
if (v == wlc_ptr_focused->view) {
grab_in_focused = 1;
break;
}
}
if (grab_in_focused) {
target = wlc_ptr_grab;
int ox = 0, oy = 0;
for (W_View *v = target; v && v != wlc_ptr_focused->view; v = v->parent) {
ox += v->pos.x;
oy += v->pos.y;
}
lx = (int)wlc_ptr_x - ox;
ly = (int)wlc_ptr_y - oy;
} else {
/* Pointer entered a different toplevel — hit-test there */
target = wlc_hit_test(wlc_ptr_focused, (int)wlc_ptr_x, (int)wlc_ptr_y, &lx, &ly);
}
lx = (int)wlc_ptr_x - ox;
ly = (int)wlc_ptr_y - oy;
} else {
target = wlc_hit_test(wlc_ptr_focused, (int)wlc_ptr_x, (int)wlc_ptr_y, &lx, &ly);
}
/* Synthesize enter/leave when pointer moves between child views */
if (!wlc_ptr_grab && target != wlc_ptr_child_focus) {
if (target != wlc_ptr_child_focus) {
if (wlc_ptr_child_focus) {
WMEvent lev;
memset(&lev, 0, sizeof(lev));
@ -859,17 +876,29 @@ pointer_handle_button(void *data, struct wl_pointer *p, uint32_t serial,
target = wlc_hit_test(wlc_ptr_focused, (int)wlc_ptr_x, (int)wlc_ptr_y, &lx, &ly);
wlc_ptr_grab = target;
} else {
/* Deliver release to the grabbed view */
/* Deliver release to the grabbed view. If the pointer has moved
* to a different toplevel (e.g. popup menu), hit-test there. */
target = wlc_ptr_grab;
if (target) {
/* Compute local coords relative to grabbed view */
int ox = 0, oy = 0;
for (W_View *v = target; v && v != wlc_ptr_focused->view; v = v->parent) {
ox += v->pos.x;
oy += v->pos.y;
int grab_in_focused = 0;
for (W_View *v = target; v; v = v->parent) {
if (v == wlc_ptr_focused->view) {
grab_in_focused = 1;
break;
}
}
if (grab_in_focused) {
int ox = 0, oy = 0;
for (W_View *v = target; v && v != wlc_ptr_focused->view; v = v->parent) {
ox += v->pos.x;
oy += v->pos.y;
}
lx = (int)wlc_ptr_x - ox;
ly = (int)wlc_ptr_y - oy;
} else {
/* Release on a different surface — hit-test there */
target = wlc_hit_test(wlc_ptr_focused, (int)wlc_ptr_x, (int)wlc_ptr_y, &lx, &ly);
}
lx = (int)wlc_ptr_x - ox;
ly = (int)wlc_ptr_y - oy;
} else {
target = wlc_hit_test(wlc_ptr_focused, (int)wlc_ptr_x, (int)wlc_ptr_y, &lx, &ly);
}