From a63c97638d216b6ee2db4e35e2d71b91c29171c9 Mon Sep 17 00:00:00 2001 From: lkn Date: Sun, 7 Jun 2026 13:31:16 +0200 Subject: [PATCH] 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. --- WINGs/wclient_wl.c | 63 +++++++++++++++++++++++++++++++++------------- 1 file changed, 46 insertions(+), 17 deletions(-) diff --git a/WINGs/wclient_wl.c b/WINGs/wclient_wl.c index 508805d..78f86a0 100644 --- a/WINGs/wclient_wl.c +++ b/WINGs/wclient_wl.c @@ -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); }