Browse Source

wayland: Refactor for video core changes

Accommodate the new video core changes.

The new video core changes allow for some window geometry calculation refactoring that simplify the system:

- Removal of helper functions
- Eliminate some discrepancies between the libdecor and xdg-toplevel paths
- No need to short-circuit the video core window size event deduplication check
- Exclusive fullscreen windows will always end up on the correct output, even when fullscreen is initiated from the compositor
- Better handling of cases where the desktop is scaled, but does not expose the viewport protocol
- Return the display bounds for the emulated mode if an exclusive fullscreen window has focus
- Fixed cases where changing display properties during runtime wouldn't update the display mode lists
- General cleanup
Frank Praznik 2 years ago
parent
commit
7def1438c3

+ 80 - 68
src/video/wayland/SDL_waylandvideo.c

@@ -361,7 +361,7 @@ static const struct zxdg_output_v1_listener xdg_output_listener = {
     xdg_output_handle_description,
 };
 
-static void AddEmulatedModes(SDL_VideoDisplay *dpy, SDL_bool rot_90)
+static void AddEmulatedModes(SDL_DisplayData *dispdata, SDL_bool rot_90)
 {
     struct EmulatedMode
     {
@@ -413,8 +413,9 @@ static void AddEmulatedModes(SDL_VideoDisplay *dpy, SDL_bool rot_90)
 
     int i;
     SDL_DisplayMode mode;
-    const int native_width = dpy->desktop_mode.pixel_w;
-    const int native_height = dpy->desktop_mode.pixel_h;
+    SDL_VideoDisplay *dpy = dispdata->display ? SDL_GetVideoDisplay(dispdata->display) : &dispdata->placeholder;
+    const int native_width = dispdata->pixel_width;
+    const int native_height = dispdata->pixel_height;
 
     for (i = 0; i < SDL_arraysize(mode_list); ++i) {
         SDL_zero(mode);
@@ -423,18 +424,19 @@ static void AddEmulatedModes(SDL_VideoDisplay *dpy, SDL_bool rot_90)
         mode.refresh_rate = dpy->desktop_mode.refresh_rate;
         mode.driverdata = dpy->desktop_mode.driverdata;
 
-        if (rot_90) {
-            mode.pixel_w = mode_list[i].h;
-            mode.pixel_h = mode_list[i].w;
-        } else {
-            mode.pixel_w = mode_list[i].w;
-            mode.pixel_h = mode_list[i].h;
-        }
-
         /* Only add modes that are smaller than the native mode. */
-        if ((mode.pixel_w < native_width && mode.pixel_h < native_height) ||
-            (mode.pixel_w < native_width && mode.pixel_h == native_height) ||
-            (mode.pixel_w == native_width && mode.pixel_h < native_height)) {
+        if ((mode_list[i].w < native_width && mode_list[i].h < native_height) ||
+            (mode_list[i].w < native_width && mode_list[i].h == native_height) ||
+            (mode_list[i].w == native_width && mode_list[i].h < native_height)) {
+
+            if (rot_90) {
+                mode.pixel_w = mode_list[i].h;
+                mode.pixel_h = mode_list[i].w;
+            } else {
+                mode.pixel_w = mode_list[i].w;
+                mode.pixel_h = mode_list[i].h;
+            }
+
             SDL_AddFullscreenDisplayMode(dpy, &mode);
         }
     }
@@ -452,22 +454,6 @@ static void display_handle_geometry(void *data,
 
 {
     SDL_DisplayData *driverdata = (SDL_DisplayData *)data;
-    SDL_VideoDisplay *display;
-    int i;
-
-    if (driverdata->wl_output_done_count) {
-        /* Clear the wl_output ref so Reset doesn't free it */
-        display = SDL_GetVideoDisplay(driverdata->display);
-        for (i = 0; i < display->num_fullscreen_modes; ++i) {
-            display->fullscreen_modes[i].driverdata = NULL;
-        }
-
-        /* Okay, now it's safe to reset */
-        SDL_ResetFullscreenDisplayModes(display);
-
-        /* The display has officially started over. */
-        driverdata->wl_output_done_count = 0;
-    }
 
     /* Apply the change from wl-output only if xdg-output is not supported */
     if (!driverdata->has_logical_position) {
@@ -558,14 +544,29 @@ static void display_handle_done(void *data,
 
     driverdata->wl_output_done_count = SDL_min(driverdata->wl_output_done_count + 1, event_await_count + 1);
 
-    if (driverdata->wl_output_done_count != event_await_count) {
+    if (driverdata->wl_output_done_count < event_await_count) {
         return;
     }
 
+    /* If the display was already created, reset and rebuild the mode list. */
+    if (driverdata->display != 0) {
+        int i;
+        dpy = SDL_GetVideoDisplay(driverdata->display);
+
+        /* Clear the wl_output ref so Reset doesn't free it */
+        for (i = 0; i < dpy->num_fullscreen_modes; ++i) {
+            dpy->fullscreen_modes[i].driverdata = NULL;
+        }
+
+        /* Okay, now it's safe to reset */
+        SDL_ResetFullscreenDisplayModes(dpy);
+    }
+
     /* The native display resolution */
     SDL_zero(native_mode);
     native_mode.format = SDL_PIXELFORMAT_RGB888;
 
+    /* Transform the pixel values, if necessary. */
     if (driverdata->transform & WL_OUTPUT_TRANSFORM_90) {
         native_mode.pixel_w = driverdata->pixel_height;
         native_mode.pixel_h = driverdata->pixel_width;
@@ -577,27 +578,29 @@ static void display_handle_done(void *data,
     native_mode.refresh_rate = ((100 * driverdata->refresh) / 1000) / 100.0f; /* mHz to Hz */
     native_mode.driverdata = driverdata->output;
 
-    /* The scaled desktop mode */
-    SDL_zero(desktop_mode);
-    desktop_mode.format = SDL_PIXELFORMAT_RGB888;
-
-    if (driverdata->has_logical_size) { /* If xdg-output is present, calculate the true scale of the desktop */
+    if (driverdata->has_logical_size) { /* If xdg-output is present... */
         if (video->viewporter) {
+            /* ...and viewports are supported, calculate the true scale of the output. */
             driverdata->scale_factor = (float)native_mode.pixel_w / (float)driverdata->screen_width;
+        } else {
+            /* ...otherwise, the 'native' pixel values are a multiple of the logical screen size. */
+            driverdata->pixel_width = driverdata->screen_width * (int)driverdata->scale_factor;
+            driverdata->pixel_height = driverdata->screen_height * (int)driverdata->scale_factor;
         }
-    } else { /* Scale the desktop coordinates, if xdg-output isn't present */
-        driverdata->screen_width /= driverdata->scale_factor;
-        driverdata->screen_height /= driverdata->scale_factor;
-    }
-
-    /* xdg-output dimensions are already transformed, so no need to rotate. */
-    if (driverdata->has_logical_size || !(driverdata->transform & WL_OUTPUT_TRANSFORM_90)) {
-        desktop_mode.pixel_w = driverdata->pixel_width;
-        desktop_mode.pixel_h = driverdata->pixel_height;
     } else {
-        desktop_mode.pixel_w = driverdata->pixel_height;
-        desktop_mode.pixel_h = driverdata->pixel_width;
+        /* Calculate the screen coordinates from the pixel values, if xdg-output isn't present.
+         * Use the native mode pixel values since they are pre-transformed.
+         */
+        driverdata->screen_width = native_mode.pixel_w / (int)driverdata->scale_factor;
+        driverdata->screen_height = native_mode.pixel_h / (int)driverdata->scale_factor;
     }
+
+    /* The scaled desktop mode */
+    SDL_zero(desktop_mode);
+    desktop_mode.format = SDL_PIXELFORMAT_RGB888;
+
+    desktop_mode.screen_w = driverdata->screen_width;
+    desktop_mode.screen_h = driverdata->screen_height;
     desktop_mode.display_scale = driverdata->scale_factor;
     desktop_mode.refresh_rate = ((100 * driverdata->refresh) / 1000) / 100.0f; /* mHz to Hz */
     desktop_mode.driverdata = driverdata->output;
@@ -609,25 +612,23 @@ static void display_handle_done(void *data,
     }
 
     /* Set the desktop display mode. */
-    SDL_memcpy(&dpy->desktop_mode, &desktop_mode, sizeof(&dpy->desktop_mode));
+    SDL_SetDesktopDisplayMode(dpy, &desktop_mode);
 
-    /* If the desktop is scaled... */
-    if (driverdata->scale_factor > 1.0f) {
-        /* ...expose the native resolution if viewports are available... */
-        if (video->viewporter != NULL) {
-            SDL_AddFullscreenDisplayMode(dpy, &native_mode);
-        } else {
-            /* ...if not, expose some smaller, integer scaled resolutions. */
-            int i;
-            const int base_pixel_w = desktop_mode.pixel_w / (int)desktop_mode.display_scale;
-            const int base_pixel_h = desktop_mode.pixel_h / (int)desktop_mode.display_scale;
-            for (i = 1; i < (int)desktop_mode.display_scale; ++i) {
-                desktop_mode.pixel_w = base_pixel_w * i;
-                desktop_mode.pixel_h = base_pixel_h * i;
-                desktop_mode.display_scale = (float)i;
-
-                SDL_AddFullscreenDisplayMode(dpy, &desktop_mode);
-            }
+    /* ...expose the unscaled, native resolution if the scale is 1.0 or viewports are available... */
+    if (driverdata->scale_factor == 1.0f || video->viewporter != NULL) {
+        SDL_AddFullscreenDisplayMode(dpy, &native_mode);
+    } else {
+        /* ...if not, expose the integer scaled variants of the desktop resolution down to 1. */
+        int i;
+
+        desktop_mode.pixel_w = 0;
+        desktop_mode.pixel_h = 0;
+        desktop_mode.screen_w = driverdata->screen_width;
+        desktop_mode.screen_h = driverdata->screen_height;
+
+        for (i = (int)driverdata->scale_factor; i > 0; --i) {
+            desktop_mode.display_scale = (float)i;
+            SDL_AddFullscreenDisplayMode(dpy, &desktop_mode);
         }
     }
 
@@ -635,7 +636,7 @@ static void display_handle_done(void *data,
     if (video->viewporter && mode_emulation_enabled) {
         const SDL_bool rot_90 = ((driverdata->transform & WL_OUTPUT_TRANSFORM_90) != 0) ||
                                 (driverdata->screen_width < driverdata->screen_height);
-        AddEmulatedModes(dpy, rot_90);
+        AddEmulatedModes(driverdata, rot_90);
     }
 
     /* Calculate the display DPI */
@@ -974,8 +975,19 @@ static int Wayland_GetDisplayBounds(_THIS, SDL_VideoDisplay *display, SDL_Rect *
     SDL_DisplayData *driverdata = display->driverdata;
     rect->x = driverdata->x;
     rect->y = driverdata->y;
-    rect->w = display->current_mode->screen_w;
-    rect->h = display->current_mode->screen_h;
+
+    /* When an emulated, exclusive fullscreen window has focus, treat the mode dimensions as the display bounds. */
+    if (display->fullscreen_window &&
+        display->fullscreen_window->fullscreen_exclusive &&
+        display->fullscreen_window == SDL_GetFocusWindow() &&
+        display->fullscreen_window->fullscreen_mode.screen_w != 0 &&
+        display->fullscreen_window->fullscreen_mode.screen_h != 0) {
+        rect->w = display->fullscreen_window->fullscreen_mode.screen_w;
+        rect->h = display->fullscreen_window->fullscreen_mode.screen_h;
+    } else {
+        rect->w = display->current_mode->screen_w;
+        rect->h = display->current_mode->screen_h;
+    }
     return 0;
 }
 

+ 151 - 234
src/video/wayland/SDL_waylandwindow.c

@@ -53,70 +53,10 @@ SDL_FORCE_INLINE SDL_bool FloatEqual(float a, float b)
     return diff <= largest * SDL_FLT_EPSILON;
 }
 
-static void GetFullScreenDimensions(SDL_Window *window, int *width, int *height, int *drawable_width, int *drawable_height)
-{
-    SDL_WindowData *wind = window->driverdata;
-    SDL_VideoDisplay *disp = SDL_GetVideoDisplayForWindow(window);
-    SDL_DisplayData *output = disp->driverdata;
-
-    int fs_width, fs_height;
-    int buf_width, buf_height;
-    const int output_width = wind->fs_output_width ? wind->fs_output_width : output->screen_width;
-    const int output_height = wind->fs_output_height ? wind->fs_output_height : output->screen_height;
-
-    if (window->fullscreen_exclusive) {
-        /* If a mode was set, use it, otherwise use the native resolution. */
-        const SDL_DisplayMode *mode = SDL_GetWindowFullscreenMode(window);
-        if (!mode) {
-            mode = &disp->desktop_mode;
-        }
-        fs_width = mode->screen_w;
-        fs_height = mode->screen_h;
-        buf_width = mode->pixel_w;
-        buf_height = mode->pixel_h;
-    } else {
-        /*
-         * Fullscreen desktop mandates a desktop sized window, so that's what
-         * applications will get. If the application is DPI aware, it will need
-         * to handle the transformations between the differently sized window
-         * and backbuffer spaces on its own.
-         */
-        fs_width = output_width;
-        fs_height = output_height;
-
-        /* If the application is DPI aware, we can expose the true backbuffer size */
-        if (window->flags & SDL_WINDOW_ALLOW_HIGHDPI) {
-            buf_width = output->pixel_width;
-            buf_height = output->pixel_height;
-        } else {
-            buf_width = fs_width;
-            buf_height = fs_height;
-        }
-    }
-
-    if (width) {
-        *width = fs_width;
-    }
-    if (height) {
-        *height = fs_height;
-    }
-    if (drawable_width) {
-        *drawable_width = buf_width;
-    }
-    if (drawable_height) {
-        *drawable_height = buf_height;
-    }
-}
-
-SDL_FORCE_INLINE SDL_bool FullscreenModeEmulation(SDL_Window *window)
-{
-    return window->fullscreen_exclusive;
-}
-
-SDL_bool SurfaceScaleIsFractional(SDL_Window *window)
+static SDL_bool SurfaceScaleIsFractional(SDL_Window *window)
 {
     SDL_WindowData *data = window->driverdata;
-    const float scale_value = !FullscreenModeEmulation(window) ? data->windowed_scale_factor : window->fullscreen_mode.display_scale;
+    const float scale_value = !(window->fullscreen_exclusive) ? data->windowed_scale_factor : window->fullscreen_mode.display_scale;
     return !FloatEqual(SDL_roundf(scale_value), scale_value);
 }
 
@@ -125,9 +65,8 @@ static SDL_bool WindowNeedsViewport(SDL_Window *window)
     SDL_WindowData *wind = window->driverdata;
     SDL_VideoData *video = wind->waylandData;
     SDL_DisplayData *output = SDL_GetDisplayDriverDataForWindow(window);
-    const int output_width = wind->fs_output_width ? wind->fs_output_width : output->screen_width;
-    const int output_height = wind->fs_output_height ? wind->fs_output_height : output->screen_height;
-    int fs_width, fs_height;
+    const int output_width = wind->requested_window_width ? wind->requested_window_width : output->screen_width;
+    const int output_height = wind->requested_window_height ? wind->requested_window_height : output->screen_height;
 
     /*
      * A viewport is only required when scaling is enabled and:
@@ -137,9 +76,8 @@ static SDL_bool WindowNeedsViewport(SDL_Window *window)
     if (video->viewporter != NULL) {
         if (SurfaceScaleIsFractional(window)) {
             return SDL_TRUE;
-        } else if (FullscreenModeEmulation(window)) {
-            GetFullScreenDimensions(window, &fs_width, &fs_height, NULL, NULL);
-            if (fs_width != output_width || fs_height != output_height) {
+        } else if (window->fullscreen_exclusive) {
+            if (window->fullscreen_mode.screen_w != output_width || window->fullscreen_mode.screen_h != output_height) {
                 return SDL_TRUE;
             }
         }
@@ -154,16 +92,13 @@ static void GetBufferSize(SDL_Window *window, int *width, int *height)
     int buf_width;
     int buf_height;
 
-    if (FullscreenModeEmulation(window)) {
-        GetFullScreenDimensions(window, NULL, NULL, &buf_width, &buf_height);
-    } else if (WindowNeedsViewport(window)) {
-        /* Round fractional backbuffer sizes halfway away from zero. */
-        buf_width = (int)SDL_lroundf((float)window->w * data->windowed_scale_factor);
-        buf_height = (int)SDL_lroundf((float)window->h * data->windowed_scale_factor);
+    if (window->fullscreen_exclusive) {
+        buf_width = window->fullscreen_mode.pixel_w;
+        buf_height = window->fullscreen_mode.pixel_h;
     } else {
-        /* Integer scaled windowed or fullscreen with no viewport */
-        buf_width = window->w * (int)data->windowed_scale_factor;
-        buf_height = window->h * (int)data->windowed_scale_factor;
+        /* Round fractional backbuffer sizes halfway away from zero. */
+        buf_width = (int)SDL_lroundf((float)data->requested_window_width * data->windowed_scale_factor);
+        buf_height = (int)SDL_lroundf((float)data->requested_window_height * data->windowed_scale_factor);
     }
 
     if (width) {
@@ -204,9 +139,9 @@ static void ConfigureWindowGeometry(SDL_Window *window)
     SDL_WindowData *data = window->driverdata;
     SDL_VideoData *viddata = data->waylandData;
     SDL_DisplayData *output = SDL_GetDisplayDriverDataForWindow(window);
-    struct wl_region *region;
     const int old_dw = data->drawable_width;
     const int old_dh = data->drawable_height;
+    int window_width, window_height;
     SDL_bool window_size_changed;
     SDL_bool drawable_size_changed;
 
@@ -221,52 +156,56 @@ static void ConfigureWindowGeometry(SDL_Window *window)
                                      0, 0);
     }
 
-    if (FullscreenModeEmulation(window)) {
-        int fs_width, fs_height;
-        const int output_width = data->fs_output_width ? data->fs_output_width : output->screen_width;
-        const int output_height = data->fs_output_height ? data->fs_output_height : output->screen_height;
+    if (window->fullscreen_exclusive) {
+        /* If the compositor supplied fullscreen dimensions, use them, otherwise fall back to the display dimensions. */
+        const int output_width = data->requested_window_width ? data->requested_window_width : output->screen_width;
+        const int output_height = data->requested_window_height ? data->requested_window_height : output->screen_height;
+        window_width = window->fullscreen_mode.screen_w;
+        window_height = window->fullscreen_mode.screen_h;
 
-        window_size_changed = data->window_width != output_width || data->window_height != output_height;
+        window_size_changed = window_width != window->w || window_height != window->h ||
+            data->wl_window_width != output_width || data->wl_window_height != output_height;
 
         if (window_size_changed || drawable_size_changed) {
-            GetFullScreenDimensions(window, &fs_width, &fs_height, NULL, NULL);
-
             if (WindowNeedsViewport(window)) {
                 /* Set the buffer scale to 1 since a viewport will be used. */
                 wl_surface_set_buffer_scale(data->surface, 1);
                 SetDrawSurfaceViewport(window, data->drawable_width, data->drawable_height,
                                        output_width, output_height);
 
-                data->window_width = output_width;
-                data->window_height = output_height;
-
-                data->pointer_scale_x = (float)fs_width / (float)output_width;
-                data->pointer_scale_y = (float)fs_height / (float)output_height;
+                data->wl_window_width = output_width;
+                data->wl_window_height = output_height;
             } else {
+                /* Always use the mode dimensions for integer scaling. */
+                UnsetDrawSurfaceViewport(window);
                 wl_surface_set_buffer_scale(data->surface, (int32_t)window->fullscreen_mode.display_scale);
 
-                data->window_width = fs_width;
-                data->window_height = fs_height;
-
-                data->pointer_scale_x = 1.0f;
-                data->pointer_scale_y = 1.0f;
+                data->wl_window_width = window->fullscreen_mode.screen_w;
+                data->wl_window_height = window->fullscreen_mode.screen_h;
             }
+
+            data->pointer_scale_x = (float)window_width / (float)data->wl_window_width;
+            data->pointer_scale_y = (float)window_height / (float)data->wl_window_height;
         }
     } else {
-        window_size_changed = data->window_width != window->w || data->window_height != window->h;
+        window_width = data->requested_window_width;
+        window_height = data->requested_window_height;
+
+        window_size_changed = window_width != window->w || window_height != window->h;
 
         if (window_size_changed || drawable_size_changed) {
             if (WindowNeedsViewport(window)) {
                 wl_surface_set_buffer_scale(data->surface, 1);
-                SetDrawSurfaceViewport(window, data->drawable_width, data->drawable_height, window->w, window->h);
+                SetDrawSurfaceViewport(window, data->drawable_width, data->drawable_height,
+                                       window_width, window_height);
             } else {
                 UnsetDrawSurfaceViewport(window);
                 wl_surface_set_buffer_scale(data->surface, (int32_t)data->windowed_scale_factor);
             }
 
             /* Clamp the physical window size to the system minimum required size. */
-            data->window_width = SDL_max(window->w, data->system_min_required_width);
-            data->window_height = SDL_max(window->h, data->system_min_required_height);
+            data->wl_window_width = SDL_max(window_width, data->system_min_required_width);
+            data->wl_window_height = SDL_max(window_height, data->system_min_required_height);
 
             data->pointer_scale_x = 1.0f;
             data->pointer_scale_y = 1.0f;
@@ -278,16 +217,18 @@ static void ConfigureWindowGeometry(SDL_Window *window)
      * need to be recalculated if the output size has changed.
      */
     if (window_size_changed) {
+        struct wl_region *region;
+
         /* libdecor does this internally on frame commits, so it's only needed for xdg surfaces. */
         if (data->shell_surface_type != WAYLAND_SURFACE_LIBDECOR &&
             viddata->shell.xdg && data->shell_surface.xdg.surface != NULL) {
-            xdg_surface_set_window_geometry(data->shell_surface.xdg.surface, 0, 0, data->window_width, data->window_height);
+            xdg_surface_set_window_geometry(data->shell_surface.xdg.surface, 0, 0, data->wl_window_width, data->wl_window_height);
         }
 
         if (!viddata->egl_transparency_enabled) {
             region = wl_compositor_create_region(viddata->compositor);
             wl_region_add(region, 0, 0,
-                          data->window_width, data->window_height);
+                          data->wl_window_width, data->wl_window_height);
             wl_surface_set_opaque_region(data->surface, region);
             wl_region_destroy(region);
         }
@@ -296,6 +237,10 @@ static void ConfigureWindowGeometry(SDL_Window *window)
             Wayland_input_confine_pointer(viddata->input, window);
         }
     }
+
+    /* Unconditionally send the window and drawable size, the video core will deduplicate when required. */
+    SDL_SendWindowEvent(window, SDL_EVENT_WINDOW_RESIZED, window_width, window_height);
+    SDL_SendWindowEvent(window, SDL_EVENT_WINDOW_PIXEL_SIZE_CHANGED, data->drawable_width, data->drawable_height);
 }
 
 static void CommitLibdecorFrame(SDL_Window *window)
@@ -304,7 +249,7 @@ static void CommitLibdecorFrame(SDL_Window *window)
     SDL_WindowData *wind = window->driverdata;
 
     if (wind->shell_surface_type == WAYLAND_SURFACE_LIBDECOR && wind->shell_surface.libdecor.frame) {
-        struct libdecor_state *state = libdecor_state_new(wind->window_width, wind->window_height);
+        struct libdecor_state *state = libdecor_state_new(wind->wl_window_width, wind->wl_window_height);
         libdecor_frame_commit(wind->shell_surface.libdecor.frame, state, NULL);
         libdecor_state_free(state);
     }
@@ -326,7 +271,7 @@ static void SetMinMaxDimensions(SDL_Window *window, SDL_bool commit)
         return;
     }
 
-    if (window->fullscreen_exclusive) {
+    if (window->flags & SDL_WINDOW_FULLSCREEN) {
         min_width = 0;
         min_height = 0;
         max_width = 0;
@@ -439,22 +384,23 @@ static void UpdateWindowFullscreen(SDL_Window *window, SDL_bool fullscreen)
     SDL_WindowData *wind = window->driverdata;
 
     if (fullscreen) {
-        if ((window->flags & SDL_WINDOW_FULLSCREEN) == 0) {
+        if (!(window->flags & SDL_WINDOW_FULLSCREEN)) {
             wind->is_fullscreen = SDL_TRUE;
+
             wind->in_fullscreen_transition = SDL_TRUE;
             SDL_SetWindowFullscreen(window, SDL_TRUE);
             wind->in_fullscreen_transition = 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) != 0) {
-                wind->is_fullscreen = SDL_FALSE;
-                wind->in_fullscreen_transition = SDL_TRUE;
-                SDL_SetWindowFullscreen(window, SDL_FALSE);
-                wind->in_fullscreen_transition = SDL_FALSE;
-                SetMinMaxDimensions(window, SDL_FALSE);
-            }
+        if ((window->flags & SDL_WINDOW_FULLSCREEN) && !window->is_hiding && !(window->flags & SDL_WINDOW_HIDDEN)) {
+            wind->is_fullscreen = SDL_FALSE;
+
+            wind->in_fullscreen_transition = SDL_TRUE;
+            SDL_SetWindowFullscreen(window, SDL_FALSE);
+            wind->in_fullscreen_transition = SDL_FALSE;
+
+            SetMinMaxDimensions(window, SDL_FALSE);
         }
     }
 }
@@ -474,7 +420,7 @@ static void surface_damage_frame_done(void *data, struct wl_callback *cb, uint32
                                  wind->drawable_width, wind->drawable_height);
     } else {
         wl_surface_damage(wind->surface, 0, 0,
-                          wind->window_width, wind->window_height);
+                          wind->wl_window_width, wind->wl_window_height);
     }
 
     wl_callback_destroy(cb);
@@ -503,14 +449,12 @@ static const struct wl_callback_listener gles_swap_frame_listener = {
     gles_swap_frame_done
 };
 
-static void Wayland_HandleResize(SDL_Window *window, int width, int height);
-
 static void handle_configure_xdg_shell_surface(void *data, struct xdg_surface *xdg, uint32_t serial)
 {
     SDL_WindowData *wind = (SDL_WindowData *)data;
     SDL_Window *window = wind->sdlwindow;
 
-    Wayland_HandleResize(window, window->w, window->h);
+    ConfigureWindowGeometry(window);
     xdg_surface_ack_configure(xdg, serial);
 
     wind->shell_surface.xdg.initial_configure_seen = SDL_TRUE;
@@ -533,6 +477,7 @@ static void handle_configure_xdg_toplevel(void *data,
     SDL_bool fullscreen = SDL_FALSE;
     SDL_bool maximized = SDL_FALSE;
     SDL_bool floating = SDL_TRUE;
+    SDL_bool focused = SDL_FALSE;
     wl_array_for_each (state, states) {
         switch (*state) {
         case XDG_TOPLEVEL_STATE_FULLSCREEN:
@@ -543,6 +488,9 @@ static void handle_configure_xdg_toplevel(void *data,
             maximized = SDL_TRUE;
             floating = SDL_FALSE;
             break;
+        case XDG_TOPLEVEL_STATE_ACTIVATED:
+            focused = SDL_TRUE;
+            break;
         case XDG_TOPLEVEL_STATE_TILED_LEFT:
         case XDG_TOPLEVEL_STATE_TILED_RIGHT:
         case XDG_TOPLEVEL_STATE_TILED_TOP:
@@ -557,18 +505,18 @@ static void handle_configure_xdg_toplevel(void *data,
     UpdateWindowFullscreen(window, fullscreen);
 
     if (!fullscreen) {
-        if ((floating && !wind->was_floating) || width == 0 || height == 0) {
-            /* This usually happens when we're being restored from a
-             * non-floating state, so use the cached floating size here.
-             */
-            width = wind->floating_width;
-            height = wind->floating_height;
-        }
-
         /* xdg_toplevel spec states that this is a suggestion.
-           Ignore if less than or greater than max/min size. */
-
+         * Ignore if less than or greater than max/min size.
+         */
         if ((window->flags & SDL_WINDOW_RESIZABLE)) {
+            if ((floating && !wind->floating) || width == 0 || height == 0) {
+                /* This happens when we're being restored from a
+                 * non-floating state, so use the cached floating size here.
+                 */
+                width = wind->floating_width;
+                height = wind->floating_height;
+            }
+
             if (window->max_w > 0) {
                 width = SDL_min(width, window->max_w);
             }
@@ -586,6 +534,12 @@ static void handle_configure_xdg_toplevel(void *data,
             height = window->windowed.h;
         }
 
+        /* Store current floating dimensions for restoring */
+        if (floating) {
+            wind->floating_width = width;
+            wind->floating_height = height;
+        }
+
         /* Always send a maximized/restore event; if the event is redundant it will
          * automatically be discarded (see src/events/SDL_windowevents.c)
          *
@@ -594,45 +548,25 @@ static void handle_configure_xdg_toplevel(void *data,
         SDL_SendWindowEvent(window,
                             maximized ? SDL_EVENT_WINDOW_MAXIMIZED : SDL_EVENT_WINDOW_RESTORED,
                             0, 0);
-
-        /* Store current floating dimensions for restoring */
-        if (floating) {
-            wind->floating_width = width;
-            wind->floating_height = height;
-        }
-
-        /* Store this now so the xdg_surface configure knows what to resize to */
-        if (window->w != width || window->h != height) {
-            window->w = width;
-            window->h = height;
-            wind->needs_resize_event = SDL_TRUE;
-        }
     } else {
-        /* For fullscreen, foolishly do what the compositor says. If it's wrong,
-         * don't blame us, we were explicitly instructed to do this.
-         *
-         * UPDATE: Nope, sure enough a compositor sends 0,0. This is a known bug:
-         * https://bugs.kde.org/show_bug.cgi?id=444962
-         */
-        if (width && height) {
-            wind->fs_output_width = width;
-            wind->fs_output_height = height;
-        } else {
-            wind->fs_output_width = 0;
-            wind->fs_output_height = 0;
-        }
-
-        if (FullscreenModeEmulation(window)) {
-            GetFullScreenDimensions(window, &width, &height, NULL, NULL);
-        }
-        if (width != 0 && height != 0 && (window->w != width || window->h != height)) {
-            window->w = width;
-            window->h = height;
-            wind->needs_resize_event = SDL_TRUE;
+        /* If an exclusive fullscreen mode was requested, ensure it is placed on the appropriate output. */
+        if (window->fullscreen_exclusive && wind->fullscreen_display != window->fullscreen_mode.displayID) {
+            SDL_VideoDisplay *disp = SDL_GetVideoDisplay(window->fullscreen_mode.displayID);
+            if (disp) {
+                wind->fullscreen_display = disp->id;
+                xdg_toplevel_set_fullscreen(xdg_toplevel, disp->driverdata->output);
+            }
         }
     }
 
-    wind->was_floating = floating;
+    /* Similar to maximized/restore events above, send focus events too! */
+    SDL_SendWindowEvent(window,
+                        focused ? SDL_EVENT_WINDOW_FOCUS_GAINED : SDL_EVENT_WINDOW_FOCUS_LOST,
+                        0, 0);
+
+    wind->requested_window_width = width;
+    wind->requested_window_height = height;
+    wind->floating = floating;
 }
 
 static void handle_close_xdg_toplevel(void *data, struct xdg_toplevel *xdg_toplevel)
@@ -826,29 +760,27 @@ static void decoration_frame_configure(struct libdecor_frame *frame,
      * Always assume the configure is wrong.
      */
     if (fullscreen) {
+        /* If an exclusive fullscreen mode was requested, ensure it is placed on the appropriate output. */
+        if (window->fullscreen_exclusive && wind->fullscreen_display != window->fullscreen_mode.displayID) {
+            SDL_VideoDisplay *disp = SDL_GetVideoDisplay(window->fullscreen_mode.displayID);
+            if (disp) {
+                wind->fullscreen_display = disp->id;
+                libdecor_frame_set_fullscreen(frame, disp->driverdata->output);
+            }
+        }
+
         /* FIXME: We have been explicitly told to respect the fullscreen size
          * parameters here, even though they are known to be wrong on GNOME at
          * bare minimum. If this is wrong, don't blame us, we were explicitly
          * told to do this.
          */
-        if (libdecor_configuration_get_content_size(configuration, frame,
-                                                    &width, &height)) {
-            wind->fs_output_width = width;
-            wind->fs_output_height = height;
-        } else {
-            width = window->w;
-            height = window->h;
-            wind->fs_output_width = 0;
-            wind->fs_output_height = 0;
-        }
-
-        if (FullscreenModeEmulation(window)) {
-            GetFullScreenDimensions(window, &width, &height, NULL, NULL);
+        if (!libdecor_configuration_get_content_size(configuration, frame, &width, &height)) {
+            width = 0;
+            height = 0;
         }
-    } else if (!(window->flags & SDL_WINDOW_RESIZABLE) || (floating && wind->floating_resize_pending)) {
+    } else if (!(window->flags & SDL_WINDOW_RESIZABLE)) {
         width = window->windowed.w;
         height = window->windowed.h;
-        wind->floating_resize_pending = SDL_FALSE;
 
         OverrideLibdecorLimits(window);
     } else {
@@ -862,7 +794,7 @@ static void decoration_frame_configure(struct libdecor_frame *frame,
          *
          *      https://gitlab.gnome.org/jadahl/libdecor/-/issues/40
          */
-        const SDL_bool use_cached_size = (floating && !wind->was_floating) ||
+        const SDL_bool use_cached_size = (floating != wind->floating) ||
                                          (window->is_hiding || !!(window->flags & SDL_WINDOW_HIDDEN));
 
         /* This will never set 0 for width/height unless the function returns false */
@@ -886,13 +818,16 @@ static void decoration_frame_configure(struct libdecor_frame *frame,
         wind->floating_height = height;
     }
 
-    wind->was_floating = floating;
+    /* Store the new floating state. */
+    wind->floating = floating;
 
-    /* Do the resize on the SDL side (this will set window->w/h)... */
-    Wayland_HandleResize(window, width, height);
+    /* Calculate the new window geometry */
+    wind->requested_window_width = width;
+    wind->requested_window_height = height;
+    ConfigureWindowGeometry(window);
 
     /* ... then commit the changes on the libdecor side. */
-    state = libdecor_state_new(wind->window_width, wind->window_height);
+    state = libdecor_state_new(wind->wl_window_width, wind->wl_window_height);
     libdecor_frame_commit(frame, state, configuration);
     libdecor_state_free(state);
 
@@ -979,7 +914,7 @@ static void update_scale_factor(SDL_WindowData *window)
 
     if (!FloatEqual(new_factor, old_factor)) {
         window->windowed_scale_factor = new_factor;
-        Wayland_HandleResize(window->sdlwindow, window->sdlwindow->w, window->sdlwindow->h);
+        ConfigureWindowGeometry(window->sdlwindow);
     }
 }
 
@@ -1296,7 +1231,7 @@ void Wayland_ShowWindow(_THIS, SDL_Window *window)
         }
 
         /* Set the geometry */
-        xdg_surface_set_window_geometry(data->shell_surface.xdg.surface, 0, 0, data->window_width, data->window_height);
+        xdg_surface_set_window_geometry(data->shell_surface.xdg.surface, 0, 0, data->wl_window_width, data->wl_window_height);
     } else {
         /* Nothing to see here, just commit. */
         wl_surface_commit(data->surface);
@@ -1324,8 +1259,8 @@ void Wayland_ShowWindow(_THIS, SDL_Window *window)
                         "Window dimensions (%i, %i) are smaller than the system enforced minimum (%i, %i); window borders will be larger than the content surface.",
                         window->windowed.w, window->windowed.h, data->system_min_required_width, data->system_min_required_height);
 
-            data->window_width = SDL_max(window->windowed.w, data->system_min_required_width);
-            data->window_height = SDL_max(window->windowed.h, data->system_min_required_height);
+            data->wl_window_width = SDL_max(window->windowed.w, data->system_min_required_width);
+            data->wl_window_height = SDL_max(window->windowed.h, data->system_min_required_height);
             CommitLibdecorFrame(window);
         }
     } else
@@ -1546,7 +1481,7 @@ void handle_preferred_scale_changed(void *data,
 
     if (!FloatEqual(new_factor, old_factor)) {
         window->windowed_scale_factor = new_factor;
-        Wayland_HandleResize(window->sdlwindow, window->sdlwindow->w, window->sdlwindow->h);
+        ConfigureWindowGeometry(window->sdlwindow);
     }
 }
 
@@ -1647,12 +1582,17 @@ void Wayland_SetWindowFullscreen(_THIS, SDL_Window *window,
 
     /* Called from within a configure event or the window is a popup, drop it. */
     if (wind->in_fullscreen_transition || wind->shell_surface_type == WAYLAND_SURFACE_XDG_POPUP) {
+        if (!fullscreen) {
+            /* Clear the display ID so it will be set next time. */
+            wind->fullscreen_display = 0;
+        }
         return;
     }
 
     /* Don't send redundant fullscreen set/unset events. */
     if (wind->is_fullscreen != fullscreen) {
         wind->is_fullscreen = fullscreen;
+        wind->fullscreen_display = fullscreen ? display->id : 0;
         SetFullscreen(window, fullscreen ? output : NULL);
 
         /* Roundtrip required to receive the updated window dimensions */
@@ -1660,11 +1600,16 @@ void Wayland_SetWindowFullscreen(_THIS, SDL_Window *window,
     } else if (wind->is_fullscreen) {
         /*
          * If the window is already fullscreen, this is likely a request to switch between
-         * fullscreen and fullscreen desktop, or to change the video mode. Update the
-         * geometry and trigger a commit.
+         * fullscreen and fullscreen desktop, change outputs, or change the video mode.
+         * Update the geometry and trigger a commit.
          */
-        ConfigureWindowGeometry(window);
-        CommitLibdecorFrame(window);
+        if (wind->fullscreen_display != display->id) {
+            wind->fullscreen_display = display->id;
+            SetFullscreen(window, output);
+        } else {
+            ConfigureWindowGeometry(window);
+            CommitLibdecorFrame(window);
+        }
 
         /* Roundtrip required to receive the updated window dimensions */
         WAYLAND_wl_display_roundtrip(viddata->display);
@@ -1894,6 +1839,8 @@ int Wayland_CreateWindow(_THIS, SDL_Window *window)
     data->outputs = NULL;
     data->num_outputs = 0;
 
+    data->requested_window_width = window->w;
+    data->requested_window_height = window->h;
     data->floating_width = window->windowed.w;
     data->floating_height = window->windowed.h;
 
@@ -1989,30 +1936,6 @@ int Wayland_CreateWindow(_THIS, SDL_Window *window)
     return 0;
 }
 
-static void Wayland_HandleResize(SDL_Window *window, int width, int height)
-{
-    SDL_WindowData *data = window->driverdata;
-    const int old_w = window->w, old_h = window->h;
-
-    /* Update the window geometry. */
-    window->w = width;
-    window->h = height;
-    ConfigureWindowGeometry(window);
-
-    if (data->needs_resize_event || old_w != width || old_h != height) {
-        /* We have already updated window w/h, so we must override the deduplication logic in the video core */
-        window->w = 0;
-        window->h = 0;
-        SDL_SendWindowEvent(window, SDL_EVENT_WINDOW_RESIZED, width, height);
-        window->w = width;
-        window->h = height;
-        data->needs_resize_event = SDL_FALSE;
-    }
-
-    SDL_SendWindowEvent(window, SDL_EVENT_WINDOW_PIXEL_SIZE_CHANGED,
-                        data->drawable_width, data->drawable_height);
-}
-
 void Wayland_SetWindowMinimumSize(_THIS, SDL_Window *window)
 {
     SetMinMaxDimensions(window, SDL_TRUE);
@@ -2027,27 +1950,21 @@ void Wayland_SetWindowSize(_THIS, SDL_Window *window)
 {
     SDL_WindowData *wind = window->driverdata;
 
-#ifdef HAVE_LIBDECOR_H
-    /* we must not resize the window while we have a static (non-floating) size */
-    if (wind->shell_surface_type == WAYLAND_SURFACE_LIBDECOR) {
-        if (wind->shell_surface.libdecor.frame &&
-            !libdecor_frame_is_floating(wind->shell_surface.libdecor.frame)) {
-            /* Commit the resize when we re-enter floating state */
-            wind->floating_resize_pending = SDL_TRUE;
-            return;
-        }
-
-        OverrideLibdecorLimits(window);
-    }
-#endif
-
-    /* Update the window geometry. */
-    ConfigureWindowGeometry(window);
-    CommitLibdecorFrame(window);
-
-    /* windowed is unconditionally set, so we can trust it here */
+    /*
+     * Unconditionally store the floating size, as it will need
+     * to be applied when returning from a non-floating state.
+     */
     wind->floating_width = window->windowed.w;
     wind->floating_height = window->windowed.h;
+
+    /* Don't change the size of static (non-floating) windows. */
+    if (wind->floating) {
+        wind->requested_window_width = window->windowed.w;
+        wind->requested_window_height = window->windowed.h;
+
+        ConfigureWindowGeometry(window);
+        CommitLibdecorFrame(window);
+    }
 }
 
 void Wayland_GetWindowSizeInPixels(_THIS, SDL_Window *window, int *w, int *h)
@@ -2201,7 +2118,7 @@ static void EGLTransparencyChangedCallback(void *userdata, const char *name, con
 
             if (!newval) {
                 struct wl_region *region = wl_compositor_create_region(wind->waylandData->compositor);
-                wl_region_add(region, 0, 0, wind->window_width, wind->window_height);
+                wl_region_add(region, 0, 0, wind->wl_window_width, wind->wl_window_height);
                 wl_surface_set_opaque_region(wind->surface, region);
                 wl_region_destroy(region);
             } else {

+ 4 - 5
src/video/wayland/SDL_waylandwindow.h

@@ -106,14 +106,13 @@ struct SDL_WindowData
     float windowed_scale_factor;
     float pointer_scale_x;
     float pointer_scale_y;
+    int requested_window_width, requested_window_height;
     int drawable_width, drawable_height;
-    int fs_output_width, fs_output_height;
-    int window_width, window_height;
+    int wl_window_width, wl_window_height;
     int system_min_required_width;
     int system_min_required_height;
-    SDL_bool needs_resize_event;
-    SDL_bool floating_resize_pending;
-    SDL_bool was_floating;
+    SDL_DisplayID fullscreen_display;
+    SDL_bool floating;
     SDL_bool is_fullscreen;
     SDL_bool in_fullscreen_transition;
 };