SDL: wayland: Cleanup work to aid reconnect support

From 9c8b1fd8b69717e5978105c6733e6c6a87ed40c1 Mon Sep 17 00:00:00 2001
From: Ethan Lee <[EMAIL REDACTED]>
Date: Sat, 29 Oct 2022 22:34:05 -0400
Subject: [PATCH] wayland: Cleanup work to aid reconnect support

Co-authored-by: David Edmundson <kde@davidedmundson.co.uk>
---
 src/events/SDL_mouse.c                |  24 +++++
 src/events/SDL_mouse_c.h              |   5 +
 src/video/wayland/SDL_waylanddyn.h    |   1 +
 src/video/wayland/SDL_waylandevents.c |  17 +++-
 src/video/wayland/SDL_waylandsym.h    |   6 ++
 src/video/wayland/SDL_waylandvideo.c  | 128 +++++++++++++++++++++-----
 src/video/wayland/SDL_waylandvideo.h  |   2 +
 7 files changed, 157 insertions(+), 26 deletions(-)

diff --git a/src/events/SDL_mouse.c b/src/events/SDL_mouse.c
index f315674ff02e..994b6a6db21c 100644
--- a/src/events/SDL_mouse.c
+++ b/src/events/SDL_mouse.c
@@ -266,6 +266,30 @@ SDL_GetMouseFocus(void)
     return mouse->focus;
 }
 
+/* TODO RECONNECT: Hello from the Wayland video driver!
+ * This was once removed from SDL, but it's been added back in comment form
+ * because we will need it when Wayland adds compositor reconnect support.
+ * If you need this before we do, great! Otherwise, leave this alone, we'll
+ * uncomment it at the right time.
+ * -flibit
+ */
+#if 0
+void
+SDL_ResetMouse(void)
+{
+    SDL_Mouse *mouse = SDL_GetMouse();
+    Uint32 buttonState = GetButtonState(mouse, SDL_FALSE);
+    int i;
+
+    for (i = 1; i <= sizeof(buttonState)*8; ++i) {
+        if (buttonState & SDL_BUTTON(i)) {
+            SDL_SendMouseButton(mouse->focus, mouse->mouseID, SDL_RELEASED, i);
+        }
+    }
+    SDL_assert(GetButtonState(mouse, SDL_FALSE) == 0);
+}
+#endif /* 0 */
+
 void
 SDL_SetMouseFocus(SDL_Window * window)
 {
diff --git a/src/events/SDL_mouse_c.h b/src/events/SDL_mouse_c.h
index e9dda276bf41..6dd023041297 100644
--- a/src/events/SDL_mouse_c.h
+++ b/src/events/SDL_mouse_c.h
@@ -164,6 +164,11 @@ extern int SDL_SendMouseWheel(SDL_Window * window, SDL_MouseID mouseID, float x,
 /* Warp the mouse within the window, potentially overriding relative mode */
 extern void SDL_PerformWarpMouseInWindow(SDL_Window *window, int x, int y, SDL_bool ignore_relative_mode);
 
+/* TODO RECONNECT: Set mouse state to "zero" */
+#if 0
+extern void SDL_ResetMouse(void);
+#endif /* 0 */
+
 /* Shutdown the mouse subsystem */
 extern void SDL_MouseQuit(void);
 
diff --git a/src/video/wayland/SDL_waylanddyn.h b/src/video/wayland/SDL_waylanddyn.h
index 1346e9814c06..12341e16f763 100644
--- a/src/video/wayland/SDL_waylanddyn.h
+++ b/src/video/wayland/SDL_waylanddyn.h
@@ -102,6 +102,7 @@ void SDL_WAYLAND_UnloadSymbols(void);
 #define wl_proxy_get_tag (*WAYLAND_wl_proxy_get_tag)
 #define wl_proxy_marshal_flags (*WAYLAND_wl_proxy_marshal_flags)
 #define wl_proxy_marshal_array_flags (*WAYLAND_wl_proxy_marshal_array_flags)
+#define wl_display_reconnect (*WAYLAND_wl_display_reconnect)
 
 #define wl_seat_interface (*WAYLAND_wl_seat_interface)
 #define wl_surface_interface (*WAYLAND_wl_surface_interface)
diff --git a/src/video/wayland/SDL_waylandevents.c b/src/video/wayland/SDL_waylandevents.c
index 65e829c10aaa..01e5ad404887 100644
--- a/src/video/wayland/SDL_waylandevents.c
+++ b/src/video/wayland/SDL_waylandevents.c
@@ -420,11 +420,18 @@ Wayland_PumpEvents(_THIS)
     if (err < 0 && !d->display_disconnected) {
         /* Something has failed with the Wayland connection -- for example,
          * the compositor may have shut down and closed its end of the socket,
-         * or there is a library-specific error. No recovery is possible. */
-        d->display_disconnected = 1;
-        /* Only send a single quit message, as application shutdown might call
-         * SDL_PumpEvents */
-        SDL_SendQuit();
+         * or there is a library-specific error.
+         *
+         * Try to recover once, then quit.
+         */
+        if (!Wayland_VideoReconnect(_this)) {
+            d->display_disconnected = 1;
+
+            /* Only send a single quit message, as application shutdown might call
+             * SDL_PumpEvents
+             */
+            SDL_SendQuit();
+        }
     }
 }
 
diff --git a/src/video/wayland/SDL_waylandsym.h b/src/video/wayland/SDL_waylandsym.h
index 0473fa1b179d..987bc499d7a0 100644
--- a/src/video/wayland/SDL_waylandsym.h
+++ b/src/video/wayland/SDL_waylandsym.h
@@ -86,6 +86,12 @@ SDL_WAYLAND_SYM(struct wl_proxy*, wl_proxy_marshal_flags, (struct wl_proxy *prox
 SDL_WAYLAND_SYM(struct wl_proxy*, wl_proxy_marshal_array_flags, (struct wl_proxy *proxy, uint32_t opcode, const struct wl_interface *interface, uint32_t version,  uint32_t flags, union wl_argument *args))
 #endif
 
+#if 0 /* TODO RECONNECT: See waylandvideo.c for more information! */
+#if SDL_WAYLAND_CHECK_VERSION(broken, on, purpose)
+SDL_WAYLAND_SYM(int, wl_display_reconnect, (struct wl_display*));
+#endif
+#endif /* 0 */
+
 SDL_WAYLAND_INTERFACE(wl_seat_interface)
 SDL_WAYLAND_INTERFACE(wl_surface_interface)
 SDL_WAYLAND_INTERFACE(wl_shm_pool_interface)
diff --git a/src/video/wayland/SDL_waylandvideo.c b/src/video/wayland/SDL_waylandvideo.c
index 666e4efb0b7b..318d96e0884f 100644
--- a/src/video/wayland/SDL_waylandvideo.c
+++ b/src/video/wayland/SDL_waylandvideo.c
@@ -1028,7 +1028,7 @@ Wayland_GetDisplayDPI(_THIS, SDL_VideoDisplay * sdl_display, float * ddpi, float
 }
 
 void
-Wayland_VideoQuit(_THIS)
+Wayland_VideoCleanup(_THIS)
 {
     SDL_VideoData *data = _this->driverdata;
     int i, j;
@@ -1036,7 +1036,7 @@ Wayland_VideoQuit(_THIS)
     Wayland_QuitWin(data);
     Wayland_FiniMouse(data);
 
-    for (i = 0; i < _this->num_displays; ++i) {
+    for (i = _this->num_displays - 1; i >= 0; --i) {
         SDL_VideoDisplay *display = &_this->displays[i];
 
         if (((SDL_WaylandOutputData*)display->driverdata)->xdg_output) {
@@ -1051,79 +1051,165 @@ Wayland_VideoQuit(_THIS)
             display->display_modes[j].driverdata = NULL;
         }
         display->desktop_mode.driverdata = NULL;
+        SDL_DelVideoDisplay(i);
     }
 
     Wayland_display_destroy_input(data);
     Wayland_display_destroy_pointer_constraints(data);
     Wayland_display_destroy_relative_pointer_manager(data);
 
-    if (data->activation_manager)
+    if (data->activation_manager) {
         xdg_activation_v1_destroy(data->activation_manager);
+        data->activation_manager = NULL;
+    }
 
-    if (data->idle_inhibit_manager)
+    if (data->idle_inhibit_manager) {
         zwp_idle_inhibit_manager_v1_destroy(data->idle_inhibit_manager);
+        data->idle_inhibit_manager = NULL;
+    }
 
-    if (data->key_inhibitor_manager)
+    if (data->key_inhibitor_manager) {
         zwp_keyboard_shortcuts_inhibit_manager_v1_destroy(data->key_inhibitor_manager);
+        data->key_inhibitor_manager = NULL;
+    }
 
     Wayland_QuitKeyboard(_this);
 
-    if (data->text_input_manager)
+    if (data->text_input_manager) {
         zwp_text_input_manager_v3_destroy(data->text_input_manager);
+        data->text_input_manager = NULL;
+    }
 
     if (data->xkb_context) {
         WAYLAND_xkb_context_unref(data->xkb_context);
         data->xkb_context = NULL;
     }
 #ifdef SDL_VIDEO_DRIVER_WAYLAND_QT_TOUCH
-    if (data->windowmanager)
+    if (data->windowmanager) {
         qt_windowmanager_destroy(data->windowmanager);
+        data->windowmanager = NULL;
+    }
 
-    if (data->surface_extension)
+    if (data->surface_extension) {
         qt_surface_extension_destroy(data->surface_extension);
+        data->surface_extension = NULL;
+    }
 
     Wayland_touch_destroy(data);
 #endif /* SDL_VIDEO_DRIVER_WAYLAND_QT_TOUCH */
 
-    if (data->tablet_manager)
+    if (data->tablet_manager) {
         zwp_tablet_manager_v2_destroy((struct zwp_tablet_manager_v2*)data->tablet_manager);
+        data->tablet_manager = NULL;
+    }
 
-    if (data->data_device_manager)
+    if (data->data_device_manager) {
         wl_data_device_manager_destroy(data->data_device_manager);
+        data->data_device_manager = NULL;
+    }
 
-    if (data->shm)
+    if (data->shm) {
         wl_shm_destroy(data->shm);
+        data->shm = NULL;
+    }
 
-    if (data->shell.xdg)
+    if (data->shell.xdg) {
         xdg_wm_base_destroy(data->shell.xdg);
+        data->shell.xdg = NULL;
+    }
 
-    if (data->decoration_manager)
+    if (data->decoration_manager) {
         zxdg_decoration_manager_v1_destroy(data->decoration_manager);
-
-#ifdef HAVE_LIBDECOR_H
-    if (data->shell.libdecor) {
-        libdecor_unref(data->shell.libdecor);
-        data->shell.libdecor = NULL;
+        data->decoration_manager = NULL;
     }
-#endif
 
     if (data->xdg_output_manager) {
         zxdg_output_manager_v1_destroy(data->xdg_output_manager);
+        data->xdg_output_manager = NULL;
     }
 
     if (data->viewporter) {
         wp_viewporter_destroy(data->viewporter);
+        data->viewporter = NULL;
     }
 
     if (data->primary_selection_device_manager) {
         zwp_primary_selection_device_manager_v1_destroy(data->primary_selection_device_manager);
+        data->primary_selection_device_manager = NULL;
     }
 
-    if (data->compositor)
+    if (data->compositor) {
         wl_compositor_destroy(data->compositor);
+        data->compositor = NULL;
+    }
 
-    if (data->registry)
+    if (data->registry) {
         wl_registry_destroy(data->registry);
+        data->registry = NULL;
+    }
+}
+
+SDL_bool
+Wayland_VideoReconnect(_THIS)
+{
+#if 0 /* TODO RECONNECT: Uncomment all when https://invent.kde.org/plasma/kwin/-/wikis/Restarting is completed */
+    SDL_VideoData *data = _this->driverdata;
+
+    SDL_Window *window = NULL;
+
+    SDL_GLContext current_ctx = SDL_GL_GetCurrentContext();
+    SDL_Window *current_window = SDL_GL_GetCurrentWindow();
+
+    Wayland_FiniMouse(data);
+
+    SDL_GL_MakeCurrent(NULL, NULL);
+    Wayland_VideoCleanup(_this);
+
+    SDL_ResetKeyboard();
+    SDL_ResetMouse();
+    if (WAYLAND_wl_display_reconnect(data->display) < 0) {
+        return SDL_FALSE;
+    }
+
+    Wayland_VideoInit(_this);
+
+    window = _this->windows;
+    while (window) {
+        /* We're going to cheat _just_ for a second and strip the OpenGL flag.
+         * The Wayland driver actually forces it in CreateWindow, and
+         * RecreateWindow does a bunch of unloading/loading of libGL, so just
+         * strip the flag so RecreateWindow doesn't mess with the GL context,
+         * and CreateWindow will add it right back!
+         * -flibit
+         */
+        window->flags &= ~SDL_WINDOW_OPENGL;
+
+        SDL_RecreateWindow(window, window->flags);
+        window = window->next;
+    }
+
+    if (current_window && current_ctx) {
+        SDL_GL_MakeCurrent (current_window, current_ctx);
+    }
+    return SDL_TRUE;
+#else
+    return SDL_FALSE;
+#endif /* 0 */
+}
+
+void
+Wayland_VideoQuit(_THIS)
+{
+    SDL_VideoData *data = _this->driverdata;
+
+    Wayland_VideoCleanup(_this);
+
+#ifdef HAVE_LIBDECOR_H
+    if (data->shell.libdecor) {
+        libdecor_unref(data->shell.libdecor);
+        data->shell.libdecor = NULL;
+    }
+#endif
 
     SDL_free(data->classname);
 }
diff --git a/src/video/wayland/SDL_waylandvideo.h b/src/video/wayland/SDL_waylandvideo.h
index e1825cbae53c..e7f66fe2dea1 100644
--- a/src/video/wayland/SDL_waylandvideo.h
+++ b/src/video/wayland/SDL_waylandvideo.h
@@ -126,6 +126,8 @@ extern SDL_bool SDL_WAYLAND_own_output(struct wl_output *output);
 
 extern SDL_bool Wayland_LoadLibdecor(SDL_VideoData *data, SDL_bool ignore_xdg);
 
+extern SDL_bool Wayland_VideoReconnect(_THIS);
+
 #endif /* SDL_waylandvideo_h_ */
 
 /* vi: set ts=4 sw=4 expandtab: */