diff --git a/src/backend/wayland/wl_backend.c b/src/backend/wayland/wl_backend.c index 7d277e7..9dceb18 100644 --- a/src/backend/wayland/wl_backend.c +++ b/src/backend/wayland/wl_backend.c @@ -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); diff --git a/tests/wl-smoke-test.sh b/tests/wl-smoke-test.sh index c85cfae..2acb0b2 100755 --- a/tests/wl-smoke-test.sh +++ b/tests/wl-smoke-test.sh @@ -123,6 +123,10 @@ # Compositor logs "XWayland DISPLAY=: (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