From 37ecea7c0765dfc9f8dffba49108f76e042b6d3c Mon Sep 17 00:00:00 2001 From: Window Maker Date: Fri, 15 May 2026 23:36:17 +0200 Subject: [PATCH] wayland: implement pointer grab for icon drag-and-move MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Implement wl_pointer_grab (type 3 WM policy grab) that suppresses client pointer focus delivery during WM-internal drags while letting WME_MOTION events flow to WMMaskEvent. Implement wl_pointer_ungrab and wl_pointer_change_cursor (maps WCUR_* to xcursor names). Fix button release handler to not terminate type 3 grabs early — the policy code calls pointer_ungrab itself. Fix overlay surface blocking hit-testing: the iconify animation's full-screen overlay (WL_LAYER_OVERLAY) was left enabled after the animation completed, causing wlr_scene_node_at to hit it instead of frame_bufs. Now server_ungrab disables the overlay node after flush. --- src/appicon.c | 1 + src/backend/wayland/wl_backend.c | 75 +++++++++++++++++++++++++++----- src/dock.c | 4 ++ 3 files changed, 68 insertions(+), 12 deletions(-) diff --git a/src/appicon.c b/src/appicon.c index 7961882..f8adde6 100644 --- a/src/appicon.c +++ b/src/appicon.c @@ -806,6 +806,7 @@ Bool wHandleAppIconMove(WAppIcon *aicon, WMEvent *event) while (1) { WMMaskEvent(scr->wmscreen, PointerMotionMask | ButtonReleaseMask | ButtonPressMask | ButtonMotionMask | ExposureMask | EnterWindowMask, &ev); + wwarning("wHandleAppIconMove: got event type=%d", ev.type); switch (ev.type) { case WME_EXPOSE: WMHandleEvent(&ev); diff --git a/src/backend/wayland/wl_backend.c b/src/backend/wayland/wl_backend.c index 7b76a8b..f8c0138 100644 --- a/src/backend/wayland/wl_backend.c +++ b/src/backend/wayland/wl_backend.c @@ -409,8 +409,12 @@ struct wl_compositor_state { int sigfd; struct wl_event_source *sigfd_source; /* Phase 22: interactive move/resize grab state. - * grab_type: 0=none, 1=move, 2=resize. - * grab_view: the toplevel being moved/resized (NULL when grab_type==0). + * grab_type: 0=none, 1=move, 2=resize, 3=WM policy grab. + * Type 3 is used by wl_pointer_grab for WM-internal drags (dock icons, + * app icons, menus). It suppresses wl_update_pointer_focus (so client + * surfaces don't steal pointer events) but does NOT intercept motion — + * WME_MOTION events flow normally to the WMMaskEvent loop. + * grab_view: the toplevel being moved/resized (NULL when grab_type==0 or 3). * grab_start_ptr_{x,y}: pointer position when grab started. * grab_start_win_{x,y,w,h}: window geometry when grab started. * grab_resize_edges: WL_SHELL_SURFACE_RESIZE_* bitmask (resize only). */ @@ -5130,10 +5134,12 @@ handle_pointer_button(struct wl_listener *listener, void *data) struct wlr_pointer_button_event *ev = data; if (!wl_state.seat) return; - /* Phase 22: any button release ends an active grab. */ - if (wl_state.grab_type && + /* Phase 22: any button release ends an active compositor grab (move/resize). + * Type 3 (WM policy grab) is NOT ended here — the policy code calls + * pointer_ungrab itself after processing the button release event. */ + if (wl_state.grab_type && wl_state.grab_type != 3 && ev->state == WLR_BUTTON_RELEASED) { - wwarning("wl_backend: grab ended (button release)"); + wwarning("wl_backend: grab ended (button release) grab_type=%d", wl_state.grab_type); wl_grab_end(); wl_update_pointer_focus(ev->time_msec); return; @@ -5233,12 +5239,33 @@ wl_pointer_grab(WNativeWindow grab_win, WNativeWindow confine_to, unsigned int event_mask, int cursor_id, int owner_events, unsigned long timestamp) { - (void)grab_win; (void)confine_to; (void)event_mask; - (void)cursor_id; (void)owner_events; (void)timestamp; - return 0; + (void)confine_to; (void)event_mask; + (void)timestamp; (void)grab_win; + wwarning("wl_pointer_grab: win=0x%lx owner_events=%d cursor_id=%d grab_type_before=%d", + grab_win, owner_events, cursor_id, wl_state.grab_type); + /* Only set up a WM policy grab (type 3) for actual drags (owner_events=True). + * Brief protective grabs (e.g., during icon creation) don't need to suppress + * pointer focus — they just need to return success. */ + if (owner_events) { + wl_state.grab_type = 3; + wl_state.grab_view = NULL; + if (wl_state.seat) + wlr_seat_pointer_clear_focus(wl_state.seat); + } + if (cursor_id > 0) + wl_set_cursor_image("left_ptr"); + return 1; /* success */ } -static void wl_pointer_ungrab(unsigned long timestamp) { (void)timestamp; } +static void wl_pointer_ungrab(unsigned long timestamp) +{ + (void)timestamp; + wwarning("wl_pointer_ungrab: grab_type=%d", wl_state.grab_type); + if (wl_state.grab_type == 3) { + wl_state.grab_type = 0; + wl_state.grab_view = NULL; + } +} /* ------------------------------------------------------------------ */ /* Phase 13: pointer focus delivery to Wayland surfaces */ @@ -5690,6 +5717,8 @@ overlay_flush(void) wm_shm_surface_commit(s, _op, 0, 0); } if (s->scene_buf) { + /* Ensure the node is visible for the animation frame. */ + wlr_scene_node_set_enabled(&s->scene_buf->node, true); /* Notify wlroots that the pixel data has changed. */ wlr_scene_buffer_set_buffer(s->scene_buf, &s->wlr_buf); } @@ -5704,7 +5733,16 @@ overlay_flush(void) /* ------------------------------------------------------------------ */ static void wl_server_grab(void) {} /* no-op: Wayland double-buffers for us */ -static void wl_server_ungrab(void) { overlay_flush(); } +static void wl_server_ungrab(void) +{ + overlay_flush(); + /* Disable the overlay scene node so it doesn't intercept hit-testing. + * The XOR animation has already erased its pixels; leaving the node + * enabled blocks frame_buf_at (wlr_scene_node_at hits the overlay + * before any frame_buf). */ + if (wl_state.overlay_surf && wl_state.overlay_surf->scene_buf) + wlr_scene_node_set_enabled(&wl_state.overlay_surf->scene_buf->node, false); +} /* ------------------------------------------------------------------ */ /* Stacking */ @@ -7931,7 +7969,20 @@ static int wl_noop_cursor_create_from_files(WScreen *scr, const char *bitmap_pat { (void)scr; (void)bitmap_path; (void)mask_path; (void)cursor_out; return 0; } static void wl_noop_cursor_free(Cursor cursor) { (void)cursor; } static void wl_noop_cursor_set(WNativeWindow window, Cursor cursor) { (void)window; (void)cursor; } -static void wl_noop_pointer_change_cursor(int cursor_id) { (void)cursor_id; } +static void wl_pointer_change_cursor(int cursor_id) +{ + const char *name; + switch (cursor_id) { + case 1: /* WCUR_MOVE */ name = "fleur"; break; + case 2: /* WCUR_RESIZE */ name = "bottom_right_corner"; break; + case 7: /* WCUR_WAIT */ name = "watch"; break; + case 8: /* WCUR_ARROW */ name = "left_ptr"; break; + case 9: /* WCUR_QUESTION */name = "question_arrow"; break; + case 10:/* WCUR_TEXT */ name = "xterm"; break; + default: name = "left_ptr"; break; + } + wl_set_cursor_image(name); +} static void wl_noop_appicon_paint_dock_dots(WAppIcon *aicon, void *wapp) { (void)aicon; (void)wapp; } @@ -8217,7 +8268,7 @@ const WMBackend wl_backend = { .cursor_create_from_files = wl_noop_cursor_create_from_files, .cursor_free = wl_noop_cursor_free, .cursor_set = wl_noop_cursor_set, - .pointer_change_cursor = wl_noop_pointer_change_cursor, + .pointer_change_cursor = wl_pointer_change_cursor, .appicon_paint_dock_dots = wl_noop_appicon_paint_dock_dots, .pixmap_get_size = wl_noop_pixmap_get_size, .icon_paint_title_bg = wl_noop_icon_paint_title_bg, diff --git a/src/dock.c b/src/dock.c index a6cab4d..0ef0476 100644 --- a/src/dock.c +++ b/src/dock.c @@ -3899,6 +3899,10 @@ static void iconMouseDown(WObjDescriptor *desc, WBackendEvent *event) WDock *dock = aicon->dock; WScreen *scr = aicon->icon->core->screen_ptr; + wwarning("iconMouseDown: aicon=%p dock=%p button=%d xindex=%d yindex=%d", + (void*)aicon, (void*)dock, event->u.button.button, + aicon->xindex, aicon->yindex); + if (aicon->editing || WCHECK_STATE(WSTATE_MODAL)) return;