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.
This commit is contained in:
Frank Praznik
2026-05-22 11:42:53 -04:00
parent 9fe200c215
commit 5f8eb44541
2 changed files with 29 additions and 6 deletions

View File

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

View File

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