SDL: wayland: Enable compositor fullscreen toggling

From 707b561f971b3c0c935b64400ae1661ae8772ce6 Mon Sep 17 00:00:00 2001
From: Frank Praznik <[EMAIL REDACTED]>
Date: Sat, 3 Sep 2022 12:37:02 -0400
Subject: [PATCH] wayland: Enable compositor fullscreen toggling

The compositor can toggle the fullscreen state (via a hotkey or otherwise), so the internal SDL state must be updated accordingly when it does.

When toggling fullscreen via the compositor, SDL will attempt to use the last fullscreen flag explicitly set. If no flag was previously set, SDL_WINDOW_FULLSCREEN will be used if a window video mode was set, otherwise SDL_WINDOW_FULLSCREEN_DESKTOP will be used. If the previous flag was SDL_WINDOW_FULLSCREEN and the window video mode was cleared, it will revert to SDL_WINDOW_FULLSCREEN_DESKTOP.
---
 src/video/wayland/SDL_waylandwindow.c | 111 ++++++++++++++++++--------
 src/video/wayland/SDL_waylandwindow.h |   3 +
 2 files changed, 81 insertions(+), 33 deletions(-)

diff --git a/src/video/wayland/SDL_waylandwindow.c b/src/video/wayland/SDL_waylandwindow.c
index 78d6c1b2add..7abdcc4e598 100644
--- a/src/video/wayland/SDL_waylandwindow.c
+++ b/src/video/wayland/SDL_waylandwindow.c
@@ -44,6 +44,8 @@
 #include <libdecor.h>
 #endif
 
+#define FULLSCREEN_MASK (SDL_WINDOW_FULLSCREEN | SDL_WINDOW_FULLSCREEN_DESKTOP)
+
 SDL_FORCE_INLINE SDL_bool
 EGLTransparencyEnabled()
 {
@@ -448,6 +450,54 @@ SetFullscreen(SDL_Window *window, struct wl_output *output, SDL_bool commit)
     }
 }
 
+static void
+UpdateWindowFullscreen(SDL_Window *window, SDL_bool fullscreen)
+{
+    SDL_WindowData *wind = (SDL_WindowData*)window->driverdata;
+
+    if (fullscreen) {
+        if (!(window->flags & SDL_WINDOW_FULLSCREEN)) {
+            /*
+             * If the window was never previously made full screen, check if a particular
+             * fullscreen mode has been set for the window. If one is found, use SDL_WINDOW_FULLSCREEN,
+             * otherwise, use SDL_WINDOW_FULLSCREEN_DESKTOP.
+             *
+             * If the previous flag was SDL_WINDOW_FULLSCREEN, make sure a mode is still set,
+             * otherwise, fall back to SDL_WINDOW_FULLSCREEN_DESKTOP.
+             */
+            if (!wind->fullscreen_flags) {
+                if (window->fullscreen_mode.w && window->fullscreen_mode.h) {
+                    wind->fullscreen_flags = SDL_WINDOW_FULLSCREEN;
+                } else {
+                    wind->fullscreen_flags = SDL_WINDOW_FULLSCREEN_DESKTOP;
+                }
+            } else if (wind->fullscreen_flags != SDL_WINDOW_FULLSCREEN_DESKTOP &&
+                       (!window->fullscreen_mode.w || !window->fullscreen_mode.h)) {
+                wind->fullscreen_flags = SDL_WINDOW_FULLSCREEN_DESKTOP;
+            }
+
+            wind->is_fullscreen = SDL_TRUE;
+
+            wind->in_fullscreen_transition = SDL_TRUE;
+            SDL_SetWindowFullscreen(window, wind->fullscreen_flags);
+            wind->in_fullscreen_transition = SDL_FALSE;
+            SetMinMaxDimensions(window, SDL_FALSE);
+        }
+    } else {
+        /* Don't change the fullscreen flags if the window is hidden or being hidden. */
+        if (!window->is_hiding && !(window->flags & SDL_WINDOW_HIDDEN)) {
+            if (window->flags & SDL_WINDOW_FULLSCREEN) {
+                wind->is_fullscreen = SDL_FALSE;
+
+                wind->in_fullscreen_transition = SDL_TRUE;
+                SDL_SetWindowFullscreen(window, 0);
+                wind->in_fullscreen_transition = SDL_FALSE;
+                SetMinMaxDimensions(window, SDL_FALSE);
+            }
+        }
+    }
+}
+
 static const struct wl_callback_listener surface_damage_frame_listener;
 
 static void
@@ -545,30 +595,9 @@ handle_configure_xdg_toplevel(void *data,
 
     driverdata = (SDL_WaylandOutputData *) SDL_GetDisplayForWindow(window)->driverdata;
 
-    if (!fullscreen) {
-        if (window->flags & SDL_WINDOW_FULLSCREEN) {
-            /* Foolishly do what the compositor says here. If it's wrong, don't
-             * blame us, we were explicitly instructed to do this.
-             *
-             * UPDATE: Nope, we can't actually do that, the compositor may give
-             * us a completely stateless, sizeless configure, with which we have
-             * to enforce our own state anyway.
-             */
-            if (width != 0 && height != 0 && (window->w != width || window->h != height)) {
-                window->w = width;
-                window->h = height;
-                wind->needs_resize_event = SDL_TRUE;
-            }
-
-            /* This part is good though. */
-            if ((window->flags & SDL_WINDOW_ALLOW_HIGHDPI) && !FloatEqual(wind->scale_factor, driverdata->scale_factor)) {
-                wind->scale_factor = driverdata->scale_factor;
-                wind->needs_resize_event = SDL_TRUE;
-            }
-
-            return;
-        }
+    UpdateWindowFullscreen(window, fullscreen);
 
+    if (!fullscreen) {
         if (width == 0 || height == 0) {
             /* This usually happens when we're being restored from a
              * non-floating state, so use the cached floating size here.
@@ -781,19 +810,19 @@ decoration_frame_configure(struct libdecor_frame *frame,
 
     driverdata = (SDL_WaylandOutputData *) SDL_GetDisplayForWindow(window)->driverdata;
 
+    UpdateWindowFullscreen(window, fullscreen);
+
     if (!fullscreen) {
         /* Always send a maximized/restore event; if the event is redundant it will
          * automatically be discarded (see src/events/SDL_windowevents.c)
          *
          * No, we do not get minimize events from libdecor.
          */
-        if (!fullscreen) {
-            SDL_SendWindowEvent(window,
-                                maximized ?
-                                    SDL_WINDOWEVENT_MAXIMIZED :
-                                    SDL_WINDOWEVENT_RESTORED,
-                                0, 0);
-        }
+        SDL_SendWindowEvent(window,
+                            maximized ?
+                                SDL_WINDOWEVENT_MAXIMIZED :
+                                SDL_WINDOWEVENT_RESTORED,
+                            0, 0);
     }
 
     /* Similar to maximized/restore events above, send focus events too! */
@@ -1630,12 +1659,28 @@ void
 Wayland_SetWindowFullscreen(_THIS, SDL_Window * window,
                             SDL_VideoDisplay * _display, SDL_bool fullscreen)
 {
+    SDL_WindowData *wind = (SDL_WindowData*) window->driverdata;
     struct wl_output *output = ((SDL_WaylandOutputData*) _display->driverdata)->output;
     SDL_VideoData *viddata = (SDL_VideoData *) _this->driverdata;
-    SetFullscreen(window, fullscreen ? output : NULL, SDL_TRUE);
 
-    /* Roundtrip required to receive the updated window dimensions */
-    WAYLAND_wl_display_roundtrip(viddata->display);
+    /* Called from within a configure event, drop it. */
+    if (wind->in_fullscreen_transition) {
+        return;
+    }
+
+    /* Save the last fullscreen flags for future requests by the compositor. */
+    if (fullscreen) {
+        wind->fullscreen_flags = window->flags & FULLSCREEN_MASK;
+    }
+
+    /* Don't send redundant fullscreen set/unset events. */
+    if (wind->is_fullscreen != fullscreen) {
+        wind->is_fullscreen = fullscreen;
+        SetFullscreen(window, fullscreen ? output : NULL, SDL_TRUE);
+
+        /* Roundtrip required to receive the updated window dimensions */
+        WAYLAND_wl_display_roundtrip(viddata->display);
+    }
 }
 
 void
diff --git a/src/video/wayland/SDL_waylandwindow.h b/src/video/wayland/SDL_waylandwindow.h
index 5413c95bd8c..a1ba0fe5ae3 100644
--- a/src/video/wayland/SDL_waylandwindow.h
+++ b/src/video/wayland/SDL_waylandwindow.h
@@ -107,6 +107,9 @@ typedef struct {
     SDL_Rect viewport_rect;
     SDL_bool needs_resize_event;
     SDL_bool floating_resize_pending;
+    SDL_bool is_fullscreen;
+    SDL_bool in_fullscreen_transition;
+    Uint32 fullscreen_flags;
 } SDL_WindowData;
 
 extern void Wayland_ShowWindow(_THIS, SDL_Window *window);