Skip to content

wayland: Remove all window references from seats when destroying a wi… #13028

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
May 13, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
135 changes: 75 additions & 60 deletions src/video/wayland/SDL_waylandevents.c
Original file line number Diff line number Diff line change
Expand Up @@ -90,19 +90,19 @@
// Scoped function declarations
static void Wayland_SeatUpdateKeyboardGrab(SDL_WaylandSeat *seat);

struct SDL_WaylandTouchPoint
typedef struct
{
SDL_TouchID id;
wl_fixed_t fx;
wl_fixed_t fy;
struct wl_surface *surface;

struct wl_list link;
};
} SDL_WaylandTouchPoint;

static void Wayland_SeatAddTouch(SDL_WaylandSeat *seat, SDL_TouchID id, wl_fixed_t fx, wl_fixed_t fy, struct wl_surface *surface)
{
struct SDL_WaylandTouchPoint *tp = SDL_malloc(sizeof(struct SDL_WaylandTouchPoint));
SDL_WaylandTouchPoint *tp = SDL_malloc(sizeof(SDL_WaylandTouchPoint));

SDL_zerop(tp);
tp->id = id;
Expand All @@ -113,9 +113,37 @@ static void Wayland_SeatAddTouch(SDL_WaylandSeat *seat, SDL_TouchID id, wl_fixed
WAYLAND_wl_list_insert(&seat->touch.points, &tp->link);
}

static void Wayland_SeatCancelTouch(SDL_WaylandSeat *seat, SDL_WaylandTouchPoint *tp)
{
if (tp->surface) {
SDL_WindowData *window_data = (SDL_WindowData *)wl_surface_get_user_data(tp->surface);

if (window_data) {
const float x = (float)(wl_fixed_to_double(tp->fx) / window_data->current.logical_width);
const float y = (float)(wl_fixed_to_double(tp->fy) / window_data->current.logical_height);

SDL_SendTouch(0, (SDL_TouchID)(uintptr_t)seat->touch.wl_touch,
(SDL_FingerID)(tp->id + 1), window_data->sdlwindow, SDL_EVENT_FINGER_CANCELED, x, y, 0.0f);

--window_data->active_touch_count;

/* If the window currently has mouse focus and has no currently active keyboards, pointers,
* or touch events, then consider mouse focus to be lost.
*/
if (SDL_GetMouseFocus() == window_data->sdlwindow && !window_data->keyboard_focus_count &&
!window_data->pointer_focus_count && !window_data->active_touch_count) {
SDL_SetMouseFocus(NULL);
}
}
}

WAYLAND_wl_list_remove(&tp->link);
SDL_free(tp);
}

static void Wayland_SeatUpdateTouch(SDL_WaylandSeat *seat, SDL_TouchID id, wl_fixed_t fx, wl_fixed_t fy, struct wl_surface **surface)
{
struct SDL_WaylandTouchPoint *tp;
SDL_WaylandTouchPoint *tp;

wl_list_for_each (tp, &seat->touch.points, link) {
if (tp->id == id) {
Expand All @@ -131,7 +159,7 @@ static void Wayland_SeatUpdateTouch(SDL_WaylandSeat *seat, SDL_TouchID id, wl_fi

static void Wayland_SeatRemoveTouch(SDL_WaylandSeat *seat, SDL_TouchID id, wl_fixed_t *fx, wl_fixed_t *fy, struct wl_surface **surface)
{
struct SDL_WaylandTouchPoint *tp;
SDL_WaylandTouchPoint *tp;

wl_list_for_each (tp, &seat->touch.points, link) {
if (tp->id == id) {
Expand All @@ -152,23 +180,6 @@ static void Wayland_SeatRemoveTouch(SDL_WaylandSeat *seat, SDL_TouchID id, wl_fi
}
}

static bool Wayland_SurfaceHasActiveTouches(SDL_VideoData *display, struct wl_surface *surface)
{
struct SDL_WaylandTouchPoint *tp;
SDL_WaylandSeat *seat;

// Check all seats for active touches on the surface.
wl_list_for_each (seat, &display->seat_list, link) {
wl_list_for_each (tp, &seat->touch.points, link) {
if (tp->surface == surface) {
return true;
}
}
}

return false;
}

static void Wayland_GetScaledMouseRect(SDL_Window *window, SDL_Rect *scaled_rect)
{
SDL_WindowData *window_data = window->internal;
Expand Down Expand Up @@ -751,7 +762,7 @@ static void pointer_handle_leave(void *data, struct wl_pointer *pointer,
*/
SDL_Window *mouse_focus = SDL_GetMouseFocus();
const bool had_focus = mouse_focus && window->sdlwindow == mouse_focus;
if (!--window->pointer_focus_count && had_focus && !Wayland_SurfaceHasActiveTouches(seat->display, surface)) {
if (!--window->pointer_focus_count && had_focus && !window->active_touch_count) {
SDL_SetMouseFocus(NULL);
}

Expand Down Expand Up @@ -1243,6 +1254,7 @@ static void touch_handler_down(void *data, struct wl_touch *touch, uint32_t seri
y = (float)wl_fixed_to_double(fy) / (window_data->current.logical_height - 1);
}

++window_data->active_touch_count;
SDL_SetMouseFocus(window_data->sdlwindow);

SDL_SendTouch(Wayland_GetTouchTimestamp(seat, timestamp), (SDL_TouchID)(uintptr_t)touch,
Expand All @@ -1269,11 +1281,13 @@ static void touch_handler_up(void *data, struct wl_touch *touch, uint32_t serial
SDL_SendTouch(Wayland_GetTouchTimestamp(seat, timestamp), (SDL_TouchID)(uintptr_t)touch,
(SDL_FingerID)(id + 1), window_data->sdlwindow, SDL_EVENT_FINGER_UP, x, y, 0.0f);

/* If the window currently has mouse focus, the keyboard focus is another window or NULL, the window has no
* pointers active on it, and the surface has no active touch events, then consider mouse focus to be lost.
--window_data->active_touch_count;

/* If the window currently has mouse focus and has no currently active keyboards, pointers,
* or touch events, then consider mouse focus to be lost.
*/
if (SDL_GetMouseFocus() == window_data->sdlwindow && seat->keyboard.focus != window_data &&
!window_data->pointer_focus_count && !Wayland_SurfaceHasActiveTouches(seat->display, surface)) {
if (SDL_GetMouseFocus() == window_data->sdlwindow && !window_data->keyboard_focus_count &&
!window_data->pointer_focus_count && !window_data->active_touch_count) {
SDL_SetMouseFocus(NULL);
}
}
Expand Down Expand Up @@ -1308,39 +1322,11 @@ static void touch_handler_frame(void *data, struct wl_touch *touch)
static void touch_handler_cancel(void *data, struct wl_touch *touch)
{
SDL_WaylandSeat *seat = (SDL_WaylandSeat *)data;
struct SDL_WaylandTouchPoint *tp, *temp;
SDL_WaylandTouchPoint *tp, *temp;

// Need the safe loop variant here as cancelling a touch point removes it from the list.
wl_list_for_each_safe (tp, temp, &seat->touch.points, link) {
bool removed = false;

if (tp->surface) {
SDL_WindowData *window_data = (SDL_WindowData *)wl_surface_get_user_data(tp->surface);

if (window_data) {
const float x = (float)(wl_fixed_to_double(tp->fx) / window_data->current.logical_width);
const float y = (float)(wl_fixed_to_double(tp->fy) / window_data->current.logical_height);

SDL_SendTouch(0, (SDL_TouchID)(uintptr_t)touch,
(SDL_FingerID)(tp->id + 1), window_data->sdlwindow, SDL_EVENT_FINGER_CANCELED, x, y, 0.0f);

// Remove the touch from the list before checking for still-active touches on the surface.
WAYLAND_wl_list_remove(&tp->link);
removed = true;

/* If the window currently has mouse focus, the keyboard focus is another window or NULL, the window has no
* pointers active on it, and the surface has no active touch events, then consider mouse focus to be lost.
*/
if (SDL_GetMouseFocus() == window_data->sdlwindow && seat->keyboard.focus != window_data &&
!window_data->pointer_focus_count && !Wayland_SurfaceHasActiveTouches(seat->display, tp->surface)) {
SDL_SetMouseFocus(NULL);
}
}
}

if (!removed) {
WAYLAND_wl_list_remove(&tp->link);
}
SDL_free(tp);
Wayland_SeatCancelTouch(seat, tp);
}
}

Expand Down Expand Up @@ -1924,8 +1910,7 @@ static void keyboard_handle_leave(void *data, struct wl_keyboard *keyboard,
/* If the window has mouse focus, has no pointers within it, and no active touches, consider
* mouse focus to be lost.
*/
if (SDL_GetMouseFocus() == window->sdlwindow && !window->pointer_focus_count &&
!Wayland_SurfaceHasActiveTouches(seat->display, surface)) {
if (SDL_GetMouseFocus() == window->sdlwindow && !window->pointer_focus_count && !window->active_touch_count) {
SDL_SetMouseFocus(NULL);
}
}
Expand Down Expand Up @@ -3416,6 +3401,36 @@ void Wayland_DisplayCreateSeat(SDL_VideoData *display, struct wl_seat *wl_seat,
WAYLAND_wl_display_flush(display->display);
}

void Wayland_DisplayRemoveWindowReferencesFromSeats(SDL_VideoData *display, SDL_WindowData *window)
{
SDL_WaylandSeat *seat;
wl_list_for_each (seat, &display->seat_list, link)
{
if (seat->keyboard.focus == window) {
keyboard_handle_leave(seat, seat->keyboard.wl_keyboard, 0, window->surface);
}

if (seat->pointer.focus == window) {
pointer_handle_leave(seat, seat->pointer.wl_pointer, 0, window->surface);
}

// Need the safe loop variant here as cancelling a touch point removes it from the list.
SDL_WaylandTouchPoint *tp, *temp;
wl_list_for_each_safe (tp, temp, &seat->touch.points, link) {
if (tp->surface == window->surface) {
Wayland_SeatCancelTouch(seat, tp);
}
}

SDL_WaylandPenTool *tool;
wl_list_for_each (tool, &seat->tablet.tool_list, link) {
if (tool->tool_focus == window->sdlwindow) {
tablet_tool_handle_proximity_out(tool, tool->wltool);
}
}
}
}

void Wayland_SeatDestroy(SDL_WaylandSeat *seat, bool send_events)
{
if (!seat) {
Expand Down
1 change: 1 addition & 0 deletions src/video/wayland/SDL_waylandevents_c.h
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,7 @@ extern bool Wayland_SeatHasRelativePointerFocus(SDL_WaylandSeat *seat);
extern void Wayland_SeatUpdatePointerGrab(SDL_WaylandSeat *seat);
extern void Wayland_DisplayUpdatePointerGrabs(SDL_VideoData *display, SDL_WindowData *window);
extern void Wayland_DisplayUpdateKeyboardGrabs(SDL_VideoData *display, SDL_WindowData *window);
extern void Wayland_DisplayRemoveWindowReferencesFromSeats(SDL_VideoData *display, SDL_WindowData *window);

/* The implicit grab serial needs to be updated on:
* - Keyboard key down/up
Expand Down
6 changes: 6 additions & 0 deletions src/video/wayland/SDL_waylandwindow.c
Original file line number Diff line number Diff line change
Expand Up @@ -3093,6 +3093,12 @@ void Wayland_DestroyWindow(SDL_VideoDevice *_this, SDL_Window *window)
WAYLAND_wl_display_roundtrip(data->display);
}

/* The compositor should have relinquished keyboard, pointer, touch, and tablet tool focus when the toplevel
* window was destroyed upon being hidden, but there is no guarantee of this, so ensure that all references
* to the window held by seats are released before destroying the underlying surface and struct.
*/
Wayland_DisplayRemoveWindowReferencesFromSeats(data, wind);

#ifdef SDL_VIDEO_OPENGL_EGL
if (wind->egl_surface) {
SDL_EGL_DestroySurface(_this, wind->egl_surface);
Expand Down
3 changes: 2 additions & 1 deletion src/video/wayland/SDL_waylandwindow.h
Original file line number Diff line number Diff line change
Expand Up @@ -132,9 +132,10 @@ struct SDL_WindowData
struct Wayland_SHMBuffer *icon_buffers;
int icon_buffer_count;

// Keyboard and pointer focus refcount.
// Keyboard, pointer, and touch focus refcount.
int keyboard_focus_count;
int pointer_focus_count;
int active_touch_count;

struct
{
Expand Down
Loading