SDL: wayland: Remove all references to destroyed outputs from windows (c259a)

From c259a20f9674610f9537fa90b8e9d42de49fb5c0 Mon Sep 17 00:00:00 2001
From: Frank Praznik <[EMAIL REDACTED]>
Date: Tue, 27 Feb 2024 12:01:19 -0500
Subject: [PATCH] wayland: Remove all references to destroyed outputs from
 windows

The removal of a wl_output may not be accompanied by leave events for the surfaces present on it. Ensure that no window continues to hold a reference to a removed output.
---
 src/video/wayland/SDL_waylandvideo.c  |  7 ++++
 src/video/wayland/SDL_waylandwindow.c | 60 +++++++++++++++------------
 src/video/wayland/SDL_waylandwindow.h |  2 +
 3 files changed, 42 insertions(+), 27 deletions(-)

diff --git a/src/video/wayland/SDL_waylandvideo.c b/src/video/wayland/SDL_waylandvideo.c
index 0ba46f43e18e..bd7fd44f407d 100644
--- a/src/video/wayland/SDL_waylandvideo.c
+++ b/src/video/wayland/SDL_waylandvideo.c
@@ -947,6 +947,13 @@ static void Wayland_free_display(SDL_VideoDisplay *display)
     if (display) {
         SDL_DisplayData *display_data = display->driverdata;
 
+        /* A preceding surface leave event is not guaranteed when an output is removed,
+         * so ensure that no window continues to hold a reference to a removed output.
+         */
+        for (SDL_Window *window = SDL_GetVideoDevice()->windows; window; window = window->next) {
+            Wayland_RemoveOutputFromWindow(window->driverdata, display_data->output);
+        }
+
         SDL_free(display_data->wl_output_name);
 
         if (display_data->xdg_output) {
diff --git a/src/video/wayland/SDL_waylandwindow.c b/src/video/wayland/SDL_waylandwindow.c
index 09f1963d11d5..1aa64b22a301 100644
--- a/src/video/wayland/SDL_waylandwindow.c
+++ b/src/video/wayland/SDL_waylandwindow.c
@@ -1244,7 +1244,7 @@ static void Wayland_MaybeUpdateScaleFactor(SDL_WindowData *window)
             factor = SDL_max(factor, driverdata->scale_factor);
         }
     } else {
-        /* No monitor (somehow)? Just fall back. */
+        /* All outputs removed, just fall back. */
         factor = window->windowed_scale_factor;
     }
 
@@ -1304,6 +1304,37 @@ static void Wayland_move_window(SDL_Window *window, SDL_DisplayData *driverdata)
     }
 }
 
+void Wayland_RemoveOutputFromWindow(SDL_WindowData *window, struct wl_output *output)
+{
+    int i, send_move_event = 0;
+    SDL_DisplayData *driverdata = wl_output_get_user_data(output);
+
+    for (i = 0; i < window->num_outputs; i++) {
+        if (window->outputs[i] == driverdata) { /* remove this one */
+            if (i == (window->num_outputs - 1)) {
+                window->outputs[i] = NULL;
+                send_move_event = 1;
+            } else {
+                SDL_memmove(&window->outputs[i],
+                            &window->outputs[i + 1],
+                            sizeof(SDL_DisplayData *) * ((window->num_outputs - i) - 1));
+            }
+            window->num_outputs--;
+            i--;
+        }
+    }
+
+    if (window->num_outputs == 0) {
+        SDL_free(window->outputs);
+        window->outputs = NULL;
+    } else if (send_move_event) {
+        Wayland_move_window(window->sdlwindow,
+                            window->outputs[window->num_outputs - 1]);
+    }
+
+    Wayland_MaybeUpdateScaleFactor(window);
+}
+
 static void handle_surface_enter(void *data, struct wl_surface *surface,
                                  struct wl_output *output)
 {
@@ -1332,37 +1363,12 @@ static void handle_surface_leave(void *data, struct wl_surface *surface,
                                  struct wl_output *output)
 {
     SDL_WindowData *window = data;
-    int i, send_move_event = 0;
-    SDL_DisplayData *driverdata = wl_output_get_user_data(output);
 
     if (!SDL_WAYLAND_own_output(output) || !SDL_WAYLAND_own_surface(surface)) {
         return;
     }
 
-    for (i = 0; i < window->num_outputs; i++) {
-        if (window->outputs[i] == driverdata) { /* remove this one */
-            if (i == (window->num_outputs - 1)) {
-                window->outputs[i] = NULL;
-                send_move_event = 1;
-            } else {
-                SDL_memmove(&window->outputs[i],
-                            &window->outputs[i + 1],
-                            sizeof(SDL_DisplayData *) * ((window->num_outputs - i) - 1));
-            }
-            window->num_outputs--;
-            i--;
-        }
-    }
-
-    if (window->num_outputs == 0) {
-        SDL_free(window->outputs);
-        window->outputs = NULL;
-    } else if (send_move_event) {
-        Wayland_move_window(window->sdlwindow,
-                            window->outputs[window->num_outputs - 1]);
-    }
-
-    Wayland_MaybeUpdateScaleFactor(window);
+    Wayland_RemoveOutputFromWindow(window, output);
 }
 
 static void handle_preferred_buffer_scale(void *data, struct wl_surface *wl_surface, int32_t factor)
diff --git a/src/video/wayland/SDL_waylandwindow.h b/src/video/wayland/SDL_waylandwindow.h
index 1d045f3e9d19..cf8e7ea2a888 100644
--- a/src/video/wayland/SDL_waylandwindow.h
+++ b/src/video/wayland/SDL_waylandwindow.h
@@ -206,4 +206,6 @@ extern int Wayland_SetWindowHitTest(SDL_Window *window, SDL_bool enabled);
 extern int Wayland_FlashWindow(SDL_VideoDevice *_this, SDL_Window *window, SDL_FlashOperation operation);
 extern int Wayland_SyncWindow(SDL_VideoDevice *_this, SDL_Window *window);
 
+extern void Wayland_RemoveOutputFromWindow(SDL_WindowData *window, struct wl_output *output);
+
 #endif /* SDL_waylandwindow_h_ */