diff --git a/src/backend/wayland/wl_backend.c b/src/backend/wayland/wl_backend.c index 35cb406..7bdfdc0 100644 --- a/src/backend/wayland/wl_backend.c +++ b/src/backend/wayland/wl_backend.c @@ -201,6 +201,9 @@ frame_timer_cb(void *data) static void wl_focus_set(WNativeWindow win, int revert_to, unsigned long timestamp); /* Phase 50: forward declaration — defined in client section below. */ static int wl_client_get_pid(WNativeWindow win); +/* Phase 13: forward declarations — defined in pointer section below. */ +static void wl_update_pointer_focus(uint32_t time_msec); +static void wl_pointer_warp_to(int x, int y); /* Phase 41: look up the view for a given synthetic WNativeWindow ID. */ static struct wl_toplevel_view * @@ -285,6 +288,16 @@ handle_toplevel_map(struct wl_listener *listener, void *data) /* Phase 62: focus delivered by wManageWindow via wSetFocusTo * (auto_focus=1 set in wl_screen_create_internals); the * direct wl_focus_set() call is no longer needed here. */ + + /* Phase 13: warp virtual pointer to the centre of the + * newly managed window so pointer focus is immediately + * delivered to its surface. The warp calls + * wl_update_pointer_focus() internally. */ + { + int cx = view->x + wwin->client.width / 2; + int cy = view->y + wwin->client.height / 2; + wl_pointer_warp_to(cx, cy); + } } } } @@ -853,6 +866,74 @@ wl_pointer_grab(WNativeWindow grab_win, WNativeWindow confine_to, static void wl_pointer_ungrab(unsigned long timestamp) { (void)timestamp; } +/* ------------------------------------------------------------------ */ +/* Phase 13: pointer focus delivery to Wayland surfaces */ +/* ------------------------------------------------------------------ */ + +/* + * Find the topmost mapped view whose surface contains screen point (px, py). + * Returns the wlr_surface and sets *sx/*sy to surface-local coordinates. + * Iterates toplevel_list in reverse-insertion order (last = topmost). + */ +static struct wlr_surface * +view_surface_at(int px, int py, double *sx_out, double *sy_out, + struct wl_toplevel_view **view_out) +{ + struct wl_toplevel_view *v; + wl_list_for_each_reverse(v, &wl_state.toplevel_list, link) { + if (!v->xdg_surface || !v->xdg_surface->mapped) + continue; + double sx = px - (double)v->x; + double sy = py - (double)v->y; + double sub_sx, sub_sy; + struct wlr_surface *sub = wlr_xdg_surface_surface_at( + v->xdg_surface, sx, sy, &sub_sx, &sub_sy); + if (sub) { + if (sx_out) *sx_out = sub_sx; + if (sy_out) *sy_out = sub_sy; + if (view_out) *view_out = v; + return sub; + } + } + return NULL; +} + +/* + * Update the seat's pointer focus to the surface at the current virtual + * pointer position. Called after every pointer position change so clients + * receive wl_pointer.enter / wl_pointer.leave events correctly. + */ +static void +wl_update_pointer_focus(uint32_t time_msec) +{ + if (!wl_state.seat) + return; + + double sx, sy; + struct wl_toplevel_view *view = NULL; + struct wlr_surface *surface = view_surface_at( + wl_state.pointer_x, wl_state.pointer_y, &sx, &sy, &view); + + static struct wlr_surface *last_focused_surface = NULL; + if (surface) { + if (surface != last_focused_surface) { + wwarning("wl_backend: pointer focus entered surface %p at %.1f,%.1f", + (void *)surface, sx, sy); + last_focused_surface = surface; + } + wlr_seat_pointer_notify_enter(wl_state.seat, surface, sx, sy); + wlr_seat_pointer_notify_motion(wl_state.seat, time_msec, sx, sy); + wlr_seat_pointer_notify_frame(wl_state.seat); + } else { + if (last_focused_surface) { + wwarning("wl_backend: pointer left all surfaces"); + last_focused_surface = NULL; + } + wlr_seat_pointer_clear_focus(wl_state.seat); + } + (void)view; /* future: WM hit-testing / raise-on-hover */ +} + static void wl_pointer_warp(int dx, int dy) { @@ -863,6 +944,8 @@ wl_pointer_warp(int dx, int dy) if (wl_state.pointer_y < 0) wl_state.pointer_y = 0; if (wl_state.pointer_x > 1919) wl_state.pointer_x = 1919; if (wl_state.pointer_y > 1079) wl_state.pointer_y = 1079; + /* Phase 13: update pointer focus after relative warp */ + wl_update_pointer_focus(0); } static void @@ -875,6 +958,8 @@ wl_pointer_warp_to(int x, int y) if (y > 1079) y = 1079; wl_state.pointer_x = x; wl_state.pointer_y = y; + /* Phase 13: update pointer focus after absolute warp */ + wl_update_pointer_focus(0); } static int @@ -1248,11 +1333,15 @@ wl_client_synth_configure(WNativeWindow client_win, int x, int y, int width, int height, int border_width, WNativeWindow above) { - /* Phase 44: send an XDG configure with the new size to the client. */ - (void)x; (void)y; (void)border_width; (void)above; + /* Phase 44: send an XDG configure with the new size to the client. + * Phase 13: also record the WM-assigned position in the view so + * pointer hit-testing (view_surface_at) uses current coordinates. */ + (void)border_width; (void)above; struct wl_toplevel_view *v = wl_find_view_by_id(client_win); if (!v || !v->xdg_surface) return; + v->x = x; + v->y = y; if (width > 0 && height > 0) wlr_xdg_toplevel_set_size(v->xdg_surface, (uint32_t)width, (uint32_t)height); wlr_xdg_surface_schedule_configure(v->xdg_surface); diff --git a/tests/wl-smoke-test.sh b/tests/wl-smoke-test.sh index 1691a9c..02ffc35 100755 --- a/tests/wl-smoke-test.sh +++ b/tests/wl-smoke-test.sh @@ -104,7 +104,13 @@ # from the headless keyboard are dispatched through the WM shortcut # handler before being forwarded to focused clients. # Verified by "keyboard key+modifiers listeners wired" log. -# 37. (Phase 11) noclip/nodock set in wl_screen_create_internals: wDockCreate +# 37. (Phase 13) Pointer focus delivered: after wManageWindow succeeds the +# virtual pointer is warped to the window centre; wl_update_pointer_focus +# calls wlr_seat_pointer_notify_enter/motion/frame so the Wayland client +# receives wl_pointer.enter events. wl_client_synth_configure now records +# the WM-assigned position (v->x/v->y) so view_surface_at() returns the +# correct surface. Verified by "pointer focus entered surface" log. +# 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 # additional workspaces. App icon creation path (wApplicationCreate → @@ -332,6 +338,16 @@ if command -v weston-simple-shm >/dev/null 2>&1; then || die "compositor did not log 'focus_clear' (wl_focus_set WNATIVE_NONE path not reached)" echo " focus_clear check: keyboard focus cleared after last window unmap" + # -- 16c. Phase 13: pointer focus delivered to mapped surface ------------ + # + # After wManageWindow succeeds, wl_backend warps the virtual pointer to + # the centre of the newly managed window and calls wl_update_pointer_focus + # which sends wl_pointer.enter to the surface. The compositor logs + # "pointer focus entered surface" when this happens. + grep -q 'pointer focus entered surface' /tmp/wl-smoke-stderr \ + || die "compositor did not deliver pointer focus to any managed surface" + echo " ptr_focus check: pointer focus delivered to managed surface" + # -- 21. Phase 63: DELETE_WINDOW=1 → kill_close=0 for Wayland toplevels -- # # PropGetProtocols now sets DELETE_WINDOW=1 for all Wayland windows so