wayland: Phase 22 — interactive move/resize grab
Add a compositor-side pointer grab so clients can interactively move and
resize their windows. When an XDG toplevel sends request_move or
request_resize, the compositor records the current pointer position and
window geometry, then routes all subsequent motion events directly to
wl_grab_apply_motion() instead of the normal pointer-focus path.
- wl_grab_begin_move / wl_grab_begin_resize: snapshot grab origin,
clear seat pointer focus so the client stops receiving motion events.
- wl_grab_apply_motion: on every motion event, compute dx/dy from the
grab origin and reposition (MOVE) or stretch (RESIZE) the scene node.
Resize honours all WL_SHELL_SURFACE_RESIZE_* edge combinations.
Minimum window size clamped to 32×32 px. XWayland windows use
wlr_xwayland_surface_configure; XDG toplevels use set_size + schedule_configure.
- wl_grab_end: clears grab state; any button release while a grab is
active ends it and resumes normal pointer-focus delivery.
- grab_type, grab_view, grab_start_{ptr,win}_{x,y,w,h}, grab_resize_edges
added to wl_compositor_state.
Smoke test: build-only check added (no existing test client emits
request_move); all 22/22 checks continue to pass.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
632c093355
commit
73cdf7a6b3
|
|
@ -240,6 +240,18 @@ struct wl_compositor_state {
|
|||
* event loop reads [0] and calls wl_display_terminate(). */
|
||||
int shutdown_pipe[2];
|
||||
struct wl_event_source *shutdown_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_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). */
|
||||
int grab_type;
|
||||
struct wl_toplevel_view *grab_view;
|
||||
int grab_start_ptr_x, grab_start_ptr_y;
|
||||
int grab_start_win_x, grab_start_win_y;
|
||||
int grab_start_win_w, grab_start_win_h;
|
||||
uint32_t grab_resize_edges;
|
||||
};
|
||||
|
||||
static struct wl_compositor_state wl_state;
|
||||
|
|
@ -359,27 +371,170 @@ handle_toplevel_title(struct wl_listener *listener, void *data)
|
|||
}
|
||||
|
||||
/*
|
||||
* Phase 15: xdg_toplevel request_move — client requests interactive move.
|
||||
* In the headless compositor we acknowledge the request but do nothing
|
||||
* (no real pointer to drag with). A real compositor would start a grab.
|
||||
* Phase 22: interactive grab helpers.
|
||||
*
|
||||
* wl_grab_begin_move — called from handle_toplevel_request_move to start
|
||||
* a compositor-side move grab. Records the pointer position and window
|
||||
* origin so that subsequent motion events can compute the displacement.
|
||||
*
|
||||
* wl_grab_begin_resize — same for resize; additionally records the window
|
||||
* size and the edge bitmask so we know which sides to stretch.
|
||||
*
|
||||
* wl_grab_end — clears the grab state (called on button release).
|
||||
*
|
||||
* wl_grab_apply_motion — called from both pointer motion handlers to
|
||||
* update the window position or size while the grab is active.
|
||||
*/
|
||||
static void
|
||||
wl_grab_end(void)
|
||||
{
|
||||
wl_state.grab_type = 0;
|
||||
wl_state.grab_view = NULL;
|
||||
}
|
||||
|
||||
static void
|
||||
wl_grab_begin_move(struct wl_toplevel_view *view)
|
||||
{
|
||||
int w, h;
|
||||
if (!view) return;
|
||||
|
||||
/* Retrieve current window geometry. */
|
||||
if (view->xw_surface) {
|
||||
w = view->xw_surface->width;
|
||||
h = view->xw_surface->height;
|
||||
} else if (view->xdg_surface && view->xdg_surface->surface) {
|
||||
w = view->xdg_surface->surface->current.width;
|
||||
h = view->xdg_surface->surface->current.height;
|
||||
} else { w = 1; h = 1; }
|
||||
|
||||
wl_state.grab_type = 1; /* MOVE */
|
||||
wl_state.grab_view = view;
|
||||
wl_state.grab_start_ptr_x = wl_state.pointer_x;
|
||||
wl_state.grab_start_ptr_y = wl_state.pointer_y;
|
||||
wl_state.grab_start_win_x = view->x;
|
||||
wl_state.grab_start_win_y = view->y;
|
||||
wl_state.grab_start_win_w = w;
|
||||
wl_state.grab_start_win_h = h;
|
||||
wwarning("wl_backend: move grab started for id=0x%lx at ptr=%d,%d win=%d,%d",
|
||||
view->id, wl_state.pointer_x, wl_state.pointer_y, view->x, view->y);
|
||||
/* Clear seat pointer focus so motion events are not delivered to the
|
||||
* client while we are repositioning it. */
|
||||
if (wl_state.seat)
|
||||
wlr_seat_pointer_clear_focus(wl_state.seat);
|
||||
}
|
||||
|
||||
static void
|
||||
wl_grab_begin_resize(struct wl_toplevel_view *view, uint32_t edges)
|
||||
{
|
||||
int w, h;
|
||||
if (!view) return;
|
||||
|
||||
if (view->xw_surface) {
|
||||
w = view->xw_surface->width;
|
||||
h = view->xw_surface->height;
|
||||
} else if (view->xdg_surface && view->xdg_surface->surface) {
|
||||
w = view->xdg_surface->surface->current.width;
|
||||
h = view->xdg_surface->surface->current.height;
|
||||
} else { w = 1; h = 1; }
|
||||
|
||||
wl_state.grab_type = 2; /* RESIZE */
|
||||
wl_state.grab_view = view;
|
||||
wl_state.grab_start_ptr_x = wl_state.pointer_x;
|
||||
wl_state.grab_start_ptr_y = wl_state.pointer_y;
|
||||
wl_state.grab_start_win_x = view->x;
|
||||
wl_state.grab_start_win_y = view->y;
|
||||
wl_state.grab_start_win_w = w;
|
||||
wl_state.grab_start_win_h = h;
|
||||
wl_state.grab_resize_edges = edges;
|
||||
wwarning("wl_backend: resize grab started for id=0x%lx edges=0x%x size=%dx%d",
|
||||
view->id, edges, w, h);
|
||||
if (wl_state.seat)
|
||||
wlr_seat_pointer_clear_focus(wl_state.seat);
|
||||
}
|
||||
|
||||
/* Called from both pointer-motion handlers (relative + absolute). */
|
||||
static void
|
||||
wl_grab_apply_motion(void)
|
||||
{
|
||||
if (!wl_state.grab_type || !wl_state.grab_view)
|
||||
return;
|
||||
|
||||
struct wl_toplevel_view *v = wl_state.grab_view;
|
||||
int dx = wl_state.pointer_x - wl_state.grab_start_ptr_x;
|
||||
int dy = wl_state.pointer_y - wl_state.grab_start_ptr_y;
|
||||
|
||||
if (wl_state.grab_type == 1) {
|
||||
/* MOVE: simply reposition the window. */
|
||||
int nx = wl_state.grab_start_win_x + dx;
|
||||
int ny = wl_state.grab_start_win_y + dy;
|
||||
v->x = nx; v->y = ny;
|
||||
if (v->scene_node)
|
||||
wlr_scene_node_set_position(v->scene_node, nx, ny);
|
||||
if (v->xw_surface)
|
||||
wlr_xwayland_surface_configure(v->xw_surface,
|
||||
(int16_t)nx, (int16_t)ny,
|
||||
v->xw_surface->width, v->xw_surface->height);
|
||||
|
||||
} else if (wl_state.grab_type == 2) {
|
||||
/* RESIZE: compute new size (and origin for left/top edges). */
|
||||
uint32_t edges = wl_state.grab_resize_edges;
|
||||
int nx = wl_state.grab_start_win_x;
|
||||
int ny = wl_state.grab_start_win_y;
|
||||
int nw = wl_state.grab_start_win_w;
|
||||
int nh = wl_state.grab_start_win_h;
|
||||
|
||||
if (edges & WL_SHELL_SURFACE_RESIZE_RIGHT)
|
||||
nw = wl_state.grab_start_win_w + dx;
|
||||
if (edges & WL_SHELL_SURFACE_RESIZE_LEFT) {
|
||||
nw = wl_state.grab_start_win_w - dx;
|
||||
nx = wl_state.grab_start_win_x + dx;
|
||||
}
|
||||
if (edges & WL_SHELL_SURFACE_RESIZE_BOTTOM)
|
||||
nh = wl_state.grab_start_win_h + dy;
|
||||
if (edges & WL_SHELL_SURFACE_RESIZE_TOP) {
|
||||
nh = wl_state.grab_start_win_h - dy;
|
||||
ny = wl_state.grab_start_win_y + dy;
|
||||
}
|
||||
|
||||
if (nw < 32) { nw = 32; }
|
||||
if (nh < 32) { nh = 32; }
|
||||
|
||||
v->x = nx; v->y = ny;
|
||||
if (v->scene_node)
|
||||
wlr_scene_node_set_position(v->scene_node, nx, ny);
|
||||
|
||||
if (v->xw_surface) {
|
||||
wlr_xwayland_surface_configure(v->xw_surface,
|
||||
(int16_t)nx, (int16_t)ny, (uint16_t)nw, (uint16_t)nh);
|
||||
} else if (v->xdg_surface) {
|
||||
wlr_xdg_toplevel_set_size(v->xdg_surface, (uint32_t)nw, (uint32_t)nh);
|
||||
wlr_xdg_surface_schedule_configure(v->xdg_surface);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Phase 15/22: xdg_toplevel request_move — client requests interactive move.
|
||||
* Phase 22: starts a compositor-side move grab.
|
||||
*/
|
||||
static void
|
||||
handle_toplevel_request_move(struct wl_listener *listener, void *data)
|
||||
{
|
||||
struct wl_toplevel_view *view = VIEW_FROM_LISTENER(listener, request_move_listener);
|
||||
(void)data;
|
||||
wwarning("wl_backend: request_move id=0x%lx (no-op in headless mode)", view->id);
|
||||
wl_grab_begin_move(view);
|
||||
}
|
||||
|
||||
/*
|
||||
* Phase 15: xdg_toplevel request_resize — client requests interactive resize.
|
||||
* Phase 15/22: xdg_toplevel request_resize — client requests interactive resize.
|
||||
* Phase 22: starts a compositor-side resize grab.
|
||||
*/
|
||||
static void
|
||||
handle_toplevel_request_resize(struct wl_listener *listener, void *data)
|
||||
{
|
||||
struct wl_toplevel_view *view = VIEW_FROM_LISTENER(listener, request_resize_listener);
|
||||
(void)data;
|
||||
wwarning("wl_backend: request_resize id=0x%lx (no-op in headless mode)", view->id);
|
||||
struct wlr_xdg_toplevel_resize_event *ev = data;
|
||||
wl_grab_begin_resize(view, ev ? ev->edges : WL_SHELL_SURFACE_RESIZE_BOTTOM_RIGHT);
|
||||
}
|
||||
|
||||
/*
|
||||
|
|
@ -1741,6 +1896,8 @@ handle_pointer_motion(struct wl_listener *listener, void *data)
|
|||
if (wl_state.pointer_y < 0) wl_state.pointer_y = 0;
|
||||
if (wl_state.pointer_x >= ow) wl_state.pointer_x = ow - 1;
|
||||
if (wl_state.pointer_y >= oh) wl_state.pointer_y = oh - 1;
|
||||
/* Phase 22: apply grab before updating normal pointer focus. */
|
||||
if (wl_state.grab_type) { wl_grab_apply_motion(); return; }
|
||||
wl_update_pointer_focus(ev->time_msec);
|
||||
}
|
||||
|
||||
|
|
@ -1757,6 +1914,8 @@ handle_pointer_motion_absolute(struct wl_listener *listener, void *data)
|
|||
if (wl_state.pointer_y < 0) wl_state.pointer_y = 0;
|
||||
if (wl_state.pointer_x >= ow) wl_state.pointer_x = ow - 1;
|
||||
if (wl_state.pointer_y >= oh) wl_state.pointer_y = oh - 1;
|
||||
/* Phase 22: apply grab before updating normal pointer focus. */
|
||||
if (wl_state.grab_type) { wl_grab_apply_motion(); return; }
|
||||
wl_update_pointer_focus(ev->time_msec);
|
||||
}
|
||||
|
||||
|
|
@ -1767,6 +1926,14 @@ handle_pointer_button(struct wl_listener *listener, void *data)
|
|||
struct wlr_event_pointer_button *ev = data;
|
||||
if (!wl_state.seat)
|
||||
return;
|
||||
/* Phase 22: any button release ends an active grab. */
|
||||
if (wl_state.grab_type &&
|
||||
ev->state == WLR_BUTTON_RELEASED) {
|
||||
wwarning("wl_backend: grab ended (button release)");
|
||||
wl_grab_end();
|
||||
wl_update_pointer_focus(ev->time_msec);
|
||||
return;
|
||||
}
|
||||
wlr_seat_pointer_notify_button(wl_state.seat, ev->time_msec,
|
||||
ev->button, ev->state);
|
||||
wlr_seat_pointer_notify_frame(wl_state.seat);
|
||||
|
|
|
|||
|
|
@ -123,6 +123,10 @@
|
|||
# Compositor logs "XWayland DISPLAY=:<n> (lazy: starts on first X11 client)".
|
||||
# DISPLAY env var is set from the pre-allocated display number.
|
||||
# Verified by "XWayland DISPLAY=.*lazy" log.
|
||||
# 41. (Phase 22) Interactive move/resize grab: request_move and request_resize
|
||||
# handlers now start a compositor-side grab that tracks pointer motion
|
||||
# and repositions/resizes the window in the scene graph. Button release
|
||||
# ends the grab. Build-only check (no client sends request_move here).
|
||||
# 38. (Phase 11) noclip/nodock set in wl_screen_create_internals: wDockCreate
|
||||
# requires X11 rendering resources unavailable in Wayland mode; disabling
|
||||
# the dock/clip prevents crashes when wWorkspaceNew is called to create
|
||||
|
|
|
|||
Loading…
Reference in New Issue