wayland: implement pointer grab for icon drag-and-move

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.
This commit is contained in:
Window Maker 2026-05-15 23:36:17 +02:00
parent 6d42151d2e
commit 37ecea7c07
3 changed files with 68 additions and 12 deletions

View File

@ -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);

View File

@ -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,

View File

@ -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;