From 5f8eb445419c5e9c4388ac414722d7baee2a9655 Mon Sep 17 00:00:00 2001 From: Frank Praznik Date: Fri, 22 May 2026 11:42:53 -0400 Subject: [PATCH] wayland: Handle captured pointer movements over a subsurface Some compositors will send pointer enter/leave event while moving between surfaces that are part of the same window while mouse capture is active. Maintain window focus in this case, and adjust the coordinates relative to the content surface by the subsurface offset, if necessary. --- src/video/wayland/SDL_waylandevents.c | 34 ++++++++++++++++++++----- src/video/wayland/SDL_waylandevents_c.h | 1 + 2 files changed, 29 insertions(+), 6 deletions(-) diff --git a/src/video/wayland/SDL_waylandevents.c b/src/video/wayland/SDL_waylandevents.c index b38ef441db..e828dc9f69 100644 --- a/src/video/wayland/SDL_waylandevents.c +++ b/src/video/wayland/SDL_waylandevents.c @@ -685,8 +685,16 @@ static void pointer_dispatch_absolute_motion(SDL_WaylandSeat *seat) SDL_Window *window = window_data ? window_data->sdlwindow : NULL; if (window_data) { - const float sx = (float)(wl_fixed_to_double(seat->pointer.pending_frame.absolute.sx) * window_data->pointer_scale.x); - const float sy = (float)(wl_fixed_to_double(seat->pointer.pending_frame.absolute.sy) * window_data->pointer_scale.y); + double sx = wl_fixed_to_double(seat->pointer.pending_frame.absolute.sx); + double sy = wl_fixed_to_double(seat->pointer.pending_frame.absolute.sy); + + if (seat->pointer.focus_surface == window_data->mask.surface) { + sx += (double)window_data->mask.offset_x; + sy += (double)window_data->mask.offset_y; + } + + sx *= window_data->pointer_scale.x; + sy *= window_data->pointer_scale.y; SDL_SendMouseMotion(seat->pointer.pending_frame.timestamp_ns, window_data->sdlwindow, seat->pointer.sdl_id, false, sx, sy); seat->pointer.last_motion.x = (int)SDL_floorf(sx); @@ -816,6 +824,7 @@ static void pointer_dispatch_enter(SDL_WaylandSeat *seat) } seat->pointer.focus = window; + seat->pointer.focus_surface = seat->pointer.pending_frame.enter_surface; ++window->pointer_focus_count; SDL_SetMouseFocus(window->sdlwindow); @@ -874,6 +883,7 @@ static void pointer_dispatch_leave(SDL_WaylandSeat *seat, bool update_pointer) window->sdlwindow->flags &= ~SDL_WINDOW_MOUSE_CAPTURE; seat->pointer.focus = NULL; + seat->pointer.focus_surface = NULL; for (Uint8 i = 1; seat->pointer.buttons_pressed; ++i) { if (seat->pointer.buttons_pressed & SDL_BUTTON_MASK(i)) { SDL_SendMouseButton(0, window->sdlwindow, seat->pointer.sdl_id, i, false); @@ -1283,12 +1293,24 @@ static void pointer_handle_frame(void *data, struct wl_pointer *pointer) if (seat->pointer.pending_frame.enter_surface) { if (seat->pointer.pending_frame.leave_surface) { - // Leaving the previous surface before entering a new surface. - pointer_dispatch_leave(seat, false); - seat->pointer.pending_frame.leave_surface = NULL; + SDL_WindowData *window_data = seat->pointer.focus; + SDL_WindowData *new_focus = Wayland_GetWindowDataForOwnedSurface(seat->pointer.pending_frame.enter_surface); + + if (window_data && (window_data->sdlwindow->flags & SDL_WINDOW_MOUSE_CAPTURE) && window_data == new_focus) { + // The mouse is captured and moving between owned window surfaces. Just change the focused surface. + seat->pointer.focus_surface = seat->pointer.pending_frame.enter_surface; + seat->pointer.pending_frame.enter_surface = NULL; + seat->pointer.pending_frame.leave_surface = NULL; + } else { + // Leaving the previous surface before entering a new surface. + pointer_dispatch_leave(seat, false); + seat->pointer.pending_frame.leave_surface = NULL; + } } - pointer_dispatch_enter(seat); + if (seat->pointer.pending_frame.enter_surface) { + pointer_dispatch_enter(seat); + } } if (seat->pointer.pending_frame.have_absolute) { diff --git a/src/video/wayland/SDL_waylandevents_c.h b/src/video/wayland/SDL_waylandevents_c.h index 1a597a35f8..9e3d573b6c 100644 --- a/src/video/wayland/SDL_waylandevents_c.h +++ b/src/video/wayland/SDL_waylandevents_c.h @@ -189,6 +189,7 @@ typedef struct SDL_WaylandSeat struct zwp_pointer_gesture_pinch_v1 *gesture_pinch; SDL_WindowData *focus; + struct wl_surface *focus_surface; // According to the spec, a seat can only have one active gesture of any type at a time. SDL_WindowData *gesture_focus;