Skip to content

Commit 83d4dce

Browse files
committed
wayland: Remove all window references from seats when destroying a window
Compositors typically send keyboard, pointer, touch, and tablet leave events when a window is destroyed, however, this is not guaranteed behavior, and at least one compositor in widespread use doesn't always send pointer leave events immediately upon destroying a window. Ensure that all references held by seats to a focused window are removed before the underlying window surface and structs are destroyed to prevent potential segfaults if the seats are immediately destroyed after the window.
1 parent 0a34279 commit 83d4dce

File tree

4 files changed

+84
-61
lines changed

4 files changed

+84
-61
lines changed

src/video/wayland/SDL_waylandevents.c

Lines changed: 75 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -90,19 +90,19 @@
9090
// Scoped function declarations
9191
static void Wayland_SeatUpdateKeyboardGrab(SDL_WaylandSeat *seat);
9292

93-
struct SDL_WaylandTouchPoint
93+
typedef struct
9494
{
9595
SDL_TouchID id;
9696
wl_fixed_t fx;
9797
wl_fixed_t fy;
9898
struct wl_surface *surface;
9999

100100
struct wl_list link;
101-
};
101+
} SDL_WaylandTouchPoint;
102102

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

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

116+
static void Wayland_SeatCancelTouch(SDL_WaylandSeat *seat, SDL_WaylandTouchPoint *tp)
117+
{
118+
if (tp->surface) {
119+
SDL_WindowData *window_data = (SDL_WindowData *)wl_surface_get_user_data(tp->surface);
120+
121+
if (window_data) {
122+
const float x = (float)(wl_fixed_to_double(tp->fx) / window_data->current.logical_width);
123+
const float y = (float)(wl_fixed_to_double(tp->fy) / window_data->current.logical_height);
124+
125+
SDL_SendTouch(0, (SDL_TouchID)(uintptr_t)seat->touch.wl_touch,
126+
(SDL_FingerID)(tp->id + 1), window_data->sdlwindow, SDL_EVENT_FINGER_CANCELED, x, y, 0.0f);
127+
128+
--window_data->active_touch_count;
129+
130+
/* If the window currently has mouse focus and has no currently active keyboards, pointers,
131+
* or touch events, then consider mouse focus to be lost.
132+
*/
133+
if (SDL_GetMouseFocus() == window_data->sdlwindow && !window_data->keyboard_focus_count &&
134+
!window_data->pointer_focus_count && !window_data->active_touch_count) {
135+
SDL_SetMouseFocus(NULL);
136+
}
137+
}
138+
}
139+
140+
WAYLAND_wl_list_remove(&tp->link);
141+
SDL_free(tp);
142+
}
143+
116144
static void Wayland_SeatUpdateTouch(SDL_WaylandSeat *seat, SDL_TouchID id, wl_fixed_t fx, wl_fixed_t fy, struct wl_surface **surface)
117145
{
118-
struct SDL_WaylandTouchPoint *tp;
146+
SDL_WaylandTouchPoint *tp;
119147

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

132160
static void Wayland_SeatRemoveTouch(SDL_WaylandSeat *seat, SDL_TouchID id, wl_fixed_t *fx, wl_fixed_t *fy, struct wl_surface **surface)
133161
{
134-
struct SDL_WaylandTouchPoint *tp;
162+
SDL_WaylandTouchPoint *tp;
135163

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

155-
static bool Wayland_SurfaceHasActiveTouches(SDL_VideoData *display, struct wl_surface *surface)
156-
{
157-
struct SDL_WaylandTouchPoint *tp;
158-
SDL_WaylandSeat *seat;
159-
160-
// Check all seats for active touches on the surface.
161-
wl_list_for_each (seat, &display->seat_list, link) {
162-
wl_list_for_each (tp, &seat->touch.points, link) {
163-
if (tp->surface == surface) {
164-
return true;
165-
}
166-
}
167-
}
168-
169-
return false;
170-
}
171-
172183
static void Wayland_GetScaledMouseRect(SDL_Window *window, SDL_Rect *scaled_rect)
173184
{
174185
SDL_WindowData *window_data = window->internal;
@@ -751,7 +762,7 @@ static void pointer_handle_leave(void *data, struct wl_pointer *pointer,
751762
*/
752763
SDL_Window *mouse_focus = SDL_GetMouseFocus();
753764
const bool had_focus = mouse_focus && window->sdlwindow == mouse_focus;
754-
if (!--window->pointer_focus_count && had_focus && !Wayland_SurfaceHasActiveTouches(seat->display, surface)) {
765+
if (!--window->pointer_focus_count && had_focus && !window->active_touch_count) {
755766
SDL_SetMouseFocus(NULL);
756767
}
757768

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

1257+
++window_data->active_touch_count;
12461258
SDL_SetMouseFocus(window_data->sdlwindow);
12471259

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

1272-
/* If the window currently has mouse focus, the keyboard focus is another window or NULL, the window has no
1273-
* pointers active on it, and the surface has no active touch events, then consider mouse focus to be lost.
1284+
--window_data->active_touch_count;
1285+
1286+
/* If the window currently has mouse focus and has no currently active keyboards, pointers,
1287+
* or touch events, then consider mouse focus to be lost.
12741288
*/
1275-
if (SDL_GetMouseFocus() == window_data->sdlwindow && seat->keyboard.focus != window_data &&
1276-
!window_data->pointer_focus_count && !Wayland_SurfaceHasActiveTouches(seat->display, surface)) {
1289+
if (SDL_GetMouseFocus() == window_data->sdlwindow && !window_data->keyboard_focus_count &&
1290+
!window_data->pointer_focus_count && !window_data->active_touch_count) {
12771291
SDL_SetMouseFocus(NULL);
12781292
}
12791293
}
@@ -1308,39 +1322,11 @@ static void touch_handler_frame(void *data, struct wl_touch *touch)
13081322
static void touch_handler_cancel(void *data, struct wl_touch *touch)
13091323
{
13101324
SDL_WaylandSeat *seat = (SDL_WaylandSeat *)data;
1311-
struct SDL_WaylandTouchPoint *tp, *temp;
1325+
SDL_WaylandTouchPoint *tp, *temp;
13121326

1327+
// Need the safe loop variant here as cancelling a touch point removes it from the list.
13131328
wl_list_for_each_safe (tp, temp, &seat->touch.points, link) {
1314-
bool removed = false;
1315-
1316-
if (tp->surface) {
1317-
SDL_WindowData *window_data = (SDL_WindowData *)wl_surface_get_user_data(tp->surface);
1318-
1319-
if (window_data) {
1320-
const float x = (float)(wl_fixed_to_double(tp->fx) / window_data->current.logical_width);
1321-
const float y = (float)(wl_fixed_to_double(tp->fy) / window_data->current.logical_height);
1322-
1323-
SDL_SendTouch(0, (SDL_TouchID)(uintptr_t)touch,
1324-
(SDL_FingerID)(tp->id + 1), window_data->sdlwindow, SDL_EVENT_FINGER_CANCELED, x, y, 0.0f);
1325-
1326-
// Remove the touch from the list before checking for still-active touches on the surface.
1327-
WAYLAND_wl_list_remove(&tp->link);
1328-
removed = true;
1329-
1330-
/* If the window currently has mouse focus, the keyboard focus is another window or NULL, the window has no
1331-
* pointers active on it, and the surface has no active touch events, then consider mouse focus to be lost.
1332-
*/
1333-
if (SDL_GetMouseFocus() == window_data->sdlwindow && seat->keyboard.focus != window_data &&
1334-
!window_data->pointer_focus_count && !Wayland_SurfaceHasActiveTouches(seat->display, tp->surface)) {
1335-
SDL_SetMouseFocus(NULL);
1336-
}
1337-
}
1338-
}
1339-
1340-
if (!removed) {
1341-
WAYLAND_wl_list_remove(&tp->link);
1342-
}
1343-
SDL_free(tp);
1329+
Wayland_SeatCancelTouch(seat, tp);
13441330
}
13451331
}
13461332

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

3404+
void Wayland_DisplayRemoveWindowReferencesFromSeats(SDL_VideoData *display, SDL_WindowData *window)
3405+
{
3406+
SDL_WaylandSeat *seat;
3407+
wl_list_for_each (seat, &display->seat_list, link)
3408+
{
3409+
if (seat->keyboard.focus == window) {
3410+
keyboard_handle_leave(seat, seat->keyboard.wl_keyboard, 0, window->surface);
3411+
}
3412+
3413+
if (seat->pointer.focus == window) {
3414+
pointer_handle_leave(seat, seat->pointer.wl_pointer, 0, window->surface);
3415+
}
3416+
3417+
// Need the safe loop variant here as cancelling a touch point removes it from the list.
3418+
SDL_WaylandTouchPoint *tp, *temp;
3419+
wl_list_for_each_safe (tp, temp, &seat->touch.points, link) {
3420+
if (tp->surface == window->surface) {
3421+
Wayland_SeatCancelTouch(seat, tp);
3422+
}
3423+
}
3424+
3425+
SDL_WaylandPenTool *tool;
3426+
wl_list_for_each (tool, &seat->tablet.tool_list, link) {
3427+
if (tool->tool_focus == window->sdlwindow) {
3428+
tablet_tool_handle_proximity_out(tool, tool->wltool);
3429+
}
3430+
}
3431+
}
3432+
}
3433+
34193434
void Wayland_SeatDestroy(SDL_WaylandSeat *seat, bool send_events)
34203435
{
34213436
if (!seat) {

src/video/wayland/SDL_waylandevents_c.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -205,6 +205,7 @@ extern bool Wayland_SeatHasRelativePointerFocus(SDL_WaylandSeat *seat);
205205
extern void Wayland_SeatUpdatePointerGrab(SDL_WaylandSeat *seat);
206206
extern void Wayland_DisplayUpdatePointerGrabs(SDL_VideoData *display, SDL_WindowData *window);
207207
extern void Wayland_DisplayUpdateKeyboardGrabs(SDL_VideoData *display, SDL_WindowData *window);
208+
extern void Wayland_DisplayRemoveWindowReferencesFromSeats(SDL_VideoData *display, SDL_WindowData *window);
208209

209210
/* The implicit grab serial needs to be updated on:
210211
* - Keyboard key down/up

src/video/wayland/SDL_waylandwindow.c

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3093,6 +3093,12 @@ void Wayland_DestroyWindow(SDL_VideoDevice *_this, SDL_Window *window)
30933093
WAYLAND_wl_display_roundtrip(data->display);
30943094
}
30953095

3096+
/* The compositor should have relinquished keyboard, pointer, touch, and tablet tool focus when the toplevel
3097+
* window was destroyed upon being hidden, but there is no guarantee of this, so ensure that all references
3098+
* to the window held by seats are released before destroying the underlying surface and struct.
3099+
*/
3100+
Wayland_DisplayRemoveWindowReferencesFromSeats(data, wind);
3101+
30963102
#ifdef SDL_VIDEO_OPENGL_EGL
30973103
if (wind->egl_surface) {
30983104
SDL_EGL_DestroySurface(_this, wind->egl_surface);

src/video/wayland/SDL_waylandwindow.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -132,9 +132,10 @@ struct SDL_WindowData
132132
struct Wayland_SHMBuffer *icon_buffers;
133133
int icon_buffer_count;
134134

135-
// Keyboard and pointer focus refcount.
135+
// Keyboard, pointer, and touch focus refcount.
136136
int keyboard_focus_count;
137137
int pointer_focus_count;
138+
int active_touch_count;
138139

139140
struct
140141
{

0 commit comments

Comments
 (0)