From e0b323890be80097a7ec094117c278c8217590b2 Mon Sep 17 00:00:00 2001
From: Frank Praznik <[EMAIL REDACTED]>
Date: Tue, 16 Jun 2026 16:40:37 -0400
Subject: [PATCH] wayland: Rework aspect ratio correction
Try harder to keep a valid aspect ratio, considering the window size limits, and in cases where the window size limits prevent achieving the desired aspect ratio, the window size limits should win overall.
---
src/video/wayland/SDL_waylandwindow.c | 215 +++++++++++++++-----------
src/video/wayland/SDL_waylandwindow.h | 8 +
2 files changed, 136 insertions(+), 87 deletions(-)
diff --git a/src/video/wayland/SDL_waylandwindow.c b/src/video/wayland/SDL_waylandwindow.c
index f5c7b4bb9a2dc..cfa1c7ecc7645 100644
--- a/src/video/wayland/SDL_waylandwindow.c
+++ b/src/video/wayland/SDL_waylandwindow.c
@@ -398,7 +398,7 @@ static void ConfigureWindowGeometry(SDL_Window *window)
viewport_height = data->requested.pixel_height;
}
- if (data->viewport && data->waylandData->subcompositor && !data->is_fullscreen) {
+ if (data->viewport && data->waylandData->subcompositor && !data->floating && !data->is_fullscreen) {
if (window->min_w) {
viewport_width = SDL_max(viewport_width, window->min_w);
}
@@ -915,6 +915,124 @@ static const struct xdg_surface_listener _xdg_surface_listener = {
handle_xdg_surface_configure
};
+static void ApplyGeometryLimits(SDL_Window *window, int req_w, int req_h, int *out_w, int *out_h)
+{
+ SDL_WindowData *wind = window->internal;
+
+ int w = req_w;
+ int h = req_h;
+ const float aspect = (float)req_w / (float)req_h;
+
+ /* If adjusting for aspect causes the adjusted dimension to exceed the min/max limit, clamp to the limit
+ * and attempt to adjust the opposite dimension to compensate.
+ */
+ if (!wind->resizing || (wind->resize_edge & WAYLAND_RESIZE_EDGE_CORNER) == WAYLAND_RESIZE_EDGE_CORNER) {
+ if (window->min_aspect != 0.f && aspect < window->min_aspect) {
+ h = SDL_max(SDL_lroundf((float)req_w / window->min_aspect), 1);
+
+ if (h < window->min_h) {
+ w = SDL_max(SDL_lroundf((float)window->min_h * window->min_aspect), 1);
+ }
+ if (window->max_h && h > window->max_h) {
+ w = SDL_max(SDL_lroundf((float)window->max_h * window->min_aspect), 1);
+ }
+ } else if (window->max_aspect != 0.f && aspect > window->max_aspect) {
+ w = SDL_max(SDL_lroundf((float)req_h * window->max_aspect), 1);
+
+ if (w < window->min_w) {
+ h = SDL_max(SDL_lroundf((float)window->min_w / window->max_aspect), 1);
+ }
+ if (window->max_w && w > window->max_w) {
+ h = SDL_max(SDL_lroundf((float)window->max_w / window->max_aspect), 1);
+ }
+ }
+ } else if (wind->resize_edge & WAYLAND_RESIZE_EDGE_LR) {
+ if (window->min_aspect != 0.f && aspect < window->min_aspect) {
+ h = SDL_max(SDL_lroundf((float)req_w / window->min_aspect), 1);
+
+ if (h < window->min_h) {
+ w = SDL_max(SDL_lroundf((float)window->min_h * window->min_aspect), 1);
+ }
+ if (window->max_h && h > window->max_h) {
+ w = SDL_max(SDL_lroundf((float)window->max_h * window->min_aspect), 1);
+ }
+ } else if (window->max_aspect != 0.f && aspect > window->max_aspect) {
+ h = SDL_max(SDL_lroundf((float)req_w / window->max_aspect), 1);
+
+ if (h < window->min_h) {
+ w = SDL_max(SDL_lroundf((float)window->min_h * window->max_aspect), 1);
+ }
+ if (window->max_h && h > window->max_h) {
+ w = SDL_max(SDL_lroundf((float)window->max_h * window->max_aspect), 1);
+ }
+ }
+ } else if (wind->resize_edge & WAYLAND_RESIZE_EDGE_TB) {
+ if (window->min_aspect != 0.f && aspect < window->min_aspect) {
+ w = SDL_max(SDL_lroundf((float)req_h * window->min_aspect), 1);
+
+ if (w < window->min_w) {
+ h = SDL_max(SDL_lroundf((float)window->min_w / window->min_aspect), 1);
+ }
+ if (window->max_w && w > window->max_w) {
+ h = SDL_max(SDL_lroundf((float)window->max_w / window->min_aspect), 1);
+ }
+ } else if (window->max_aspect != 0.f && aspect > window->max_aspect) {
+ w = SDL_max(SDL_lroundf((float)req_h * window->max_aspect), 1);
+
+ if (w < window->min_w) {
+ h = SDL_max(SDL_lroundf((float)window->min_w / window->max_aspect), 1);
+ }
+ if (window->max_w && w > window->max_w) {
+ h = SDL_max(SDL_lroundf((float)window->max_w / window->max_aspect), 1);
+ }
+ }
+ }
+
+ if (window->max_w) {
+ w = SDL_min(w, window->max_w);
+ }
+ *out_w = SDL_max(w, window->min_w);
+
+ if (window->max_h) {
+ h = SDL_min(h, window->max_h);
+ }
+ *out_h = SDL_max(h, window->min_h);
+
+}
+
+static void DetermineResizeAxis(SDL_WindowData *wind, int width, int height, bool resizing)
+{
+ /* Try to determine the axis along which the window is being resized.
+ *
+ * Every compositor is different, and some will start sending strange values once the resize
+ * begins, so only the first change is safe to try and determine the resize edge.
+ *
+ * Some compositors don't send a configure event with the resize flag cleared when the resize ends,
+ * so a reset timer is required.
+ */
+ if (resizing) {
+ const Uint64 now = SDL_GetTicksNS();
+ if (now >= wind->last_resize_event_time_ns + SDL_MS_TO_NS(500)) {
+ wind->resize_edge = 0;
+ }
+
+ wind->last_resize_event_time_ns = now;
+
+ if (!wind->resize_edge) {
+ if (width != wind->last_configure.width || !height) {
+ wind->resize_edge |= WAYLAND_RESIZE_EDGE_LR;
+ }
+ if (height != wind->last_configure.height || !width) {
+ wind->resize_edge |= WAYLAND_RESIZE_EDGE_TB;
+ }
+ }
+ } else {
+ wind->resize_edge = 0;
+ }
+
+ wind->resizing = resizing;
+}
+
static void handle_xdg_toplevel_configure(void *data,
struct xdg_toplevel *xdg_toplevel,
int32_t width,
@@ -979,6 +1097,7 @@ static void handle_xdg_toplevel_configure(void *data,
// When resizing, dimensions other than 0 are a maximum.
const bool new_configure_size = width != wind->last_configure.width || height != wind->last_configure.height;
+ DetermineResizeAxis(wind, width, height, resizing);
UpdateWindowFullscreen(window, fullscreen);
/* Always send a maximized/restore event; if the event is redundant it will
@@ -1090,54 +1209,15 @@ static void handle_xdg_toplevel_configure(void *data,
* - Only floating windows are truly safe to resize: maximized windows must have
* their exact dimensions respected, or a protocol violation can occur, and tiled
* windows can technically use dimensions smaller than the ones supplied by the
- * compositor, but doing so can cause odd behavior. In these cases it's best to use
- * the supplied dimensions and use a viewport + mask to enforce the size limits and/or
- * aspect ratio.
- *
- * - When resizing a window, the width/height are maximum values, so aspect ratio
- * correction can't resize beyond the existing dimensions, or a protocol violation
- * can occur. However, in practice, nothing seems to kill clients that do this, but
- * doing so can cause certain compositors to glitch out.
+ * compositor, but doing so can cause visual glitches and odd behavior. In these cases
+ * it's best to use the supplied dimensions and use a viewport + mask to enforce the
+ * size limits and/or aspect ratio.
*/
if (floating) {
if (!wind->scale_to_display) {
- if (window->max_w > 0) {
- wind->requested.logical_width = SDL_min(wind->requested.logical_width, window->max_w);
- }
- wind->requested.logical_width = SDL_max(wind->requested.logical_width, window->min_w);
-
- if (window->max_h > 0) {
- wind->requested.logical_height = SDL_min(wind->requested.logical_height, window->max_h);
- }
- wind->requested.logical_height = SDL_max(wind->requested.logical_height, window->min_h);
-
- // Aspect correction.
- const float aspect = (float)wind->requested.logical_width / (float)wind->requested.logical_height;
-
- if (window->min_aspect != 0.f && aspect < window->min_aspect) {
- wind->requested.logical_height = SDL_max(SDL_lroundf((float)wind->requested.logical_width / window->min_aspect), 1);
- } else if (window->max_aspect != 0.f && aspect > window->max_aspect) {
- wind->requested.logical_width = SDL_max(SDL_lroundf((float)wind->requested.logical_height * window->max_aspect), 1);
- }
+ ApplyGeometryLimits(window, wind->requested.logical_width, wind->requested.logical_height, &wind->requested.logical_width, &wind->requested.logical_height);
} else {
- if (window->max_w > 0) {
- wind->requested.pixel_width = SDL_min(wind->requested.pixel_width, window->max_w);
- }
- wind->requested.pixel_width = SDL_max(wind->requested.pixel_width, window->min_w);
-
- if (window->max_h > 0) {
- wind->requested.pixel_height = SDL_min(wind->requested.pixel_height, window->max_h);
- }
- wind->requested.pixel_height = SDL_max(wind->requested.pixel_height, window->min_h);
-
- // Aspect correction.
- const float aspect = (float)wind->requested.pixel_width / (float)wind->requested.pixel_height;
-
- if (window->min_aspect != 0.f && aspect < window->min_aspect) {
- wind->requested.pixel_height = SDL_max(SDL_lroundf((float)wind->requested.pixel_width / window->min_aspect), 1);
- } else if (window->max_aspect != 0.f && aspect > window->max_aspect) {
- wind->requested.pixel_width = SDL_max(SDL_lroundf((float)wind->requested.pixel_height * window->max_aspect), 1);
- }
+ ApplyGeometryLimits(window, wind->requested.pixel_width, wind->requested.pixel_height, &wind->requested.pixel_width, &wind->requested.pixel_height);
wind->requested.logical_width = PixelToPoint(window, wind->requested.pixel_width);
wind->requested.logical_height = PixelToPoint(window, wind->requested.pixel_height);
@@ -1165,7 +1245,6 @@ static void handle_xdg_toplevel_configure(void *data,
wind->suspended = suspended;
wind->active = active;
window->tiled = tiled;
- wind->resizing = resizing;
}
static void handle_xdg_toplevel_close(void *data, struct xdg_toplevel *xdg_toplevel)
@@ -1487,6 +1566,7 @@ static void decoration_frame_configure(struct libdecor_frame *frame,
}
const bool new_configure_size = width != wind->last_configure.width || height != wind->last_configure.height;
+ DetermineResizeAxis(wind, width, height, resizing);
if (!width) {
/* This happens when we're being restored from a non-floating state,
@@ -1580,51 +1660,12 @@ static void decoration_frame_configure(struct libdecor_frame *frame,
* compositor, but doing so can cause odd behavior. In these cases it's best to use
* the supplied dimensions and use a viewport + mask to enforce the size limits and/or
* aspect ratio.
- *
- * - When resizing a window, the width/height are maximum values, so aspect ratio
- * correction can't resize beyond the existing dimensions, or a protocol violation
- * can occur. However, in practice, nothing seems to kill clients that do this, but
- * doing so can cause certain compositors to glitch out.
*/
if (floating) {
if (!wind->scale_to_display) {
- if (window->max_w > 0) {
- wind->requested.logical_width = SDL_min(wind->requested.logical_width, window->max_w);
- }
- wind->requested.logical_width = SDL_max(wind->requested.logical_width, window->min_w);
-
- if (window->max_h > 0) {
- wind->requested.logical_height = SDL_min(wind->requested.logical_height, window->max_h);
- }
- wind->requested.logical_height = SDL_max(wind->requested.logical_height, window->min_h);
-
- // Aspect correction.
- const float aspect = (float)wind->requested.logical_width / (float)wind->requested.logical_height;
-
- if (window->min_aspect != 0.f && aspect < window->min_aspect) {
- wind->requested.logical_height = SDL_max(SDL_lroundf((float)wind->requested.logical_width / window->min_aspect), 1);
- } else if (window->max_aspect != 0.f && aspect > window->max_aspect) {
- wind->requested.logical_width = SDL_max(SDL_lroundf((float)wind->requested.logical_height * window->max_aspect), 1);
- }
+ ApplyGeometryLimits(window, wind->requested.logical_width, wind->requested.logical_height, &wind->requested.logical_width, &wind->requested.logical_height);
} else {
- if (window->max_w > 0) {
- wind->requested.pixel_width = SDL_min(wind->requested.pixel_width, window->max_w);
- }
- wind->requested.pixel_width = SDL_max(wind->requested.pixel_width, window->min_w);
-
- if (window->max_h > 0) {
- wind->requested.pixel_height = SDL_min(wind->requested.pixel_height, window->max_h);
- }
- wind->requested.pixel_height = SDL_max(wind->requested.pixel_height, window->min_h);
-
- // Aspect correction.
- const float aspect = (float)wind->requested.pixel_width / (float)wind->requested.pixel_height;
-
- if (window->min_aspect != 0.f && aspect < window->min_aspect) {
- wind->requested.pixel_height = SDL_max(SDL_lroundf((float)wind->requested.pixel_width / window->min_aspect), 1);
- } else if (window->max_aspect != 0.f && aspect > window->max_aspect) {
- wind->requested.pixel_width = SDL_max(SDL_lroundf((float)wind->requested.pixel_height * window->max_aspect), 1);
- }
+ ApplyGeometryLimits(window, wind->requested.pixel_width, wind->requested.pixel_height, &wind->requested.pixel_width, &wind->requested.pixel_height);
wind->requested.logical_width = PixelToPoint(window, wind->requested.pixel_width);
wind->requested.logical_height = PixelToPoint(window, wind->requested.pixel_height);
diff --git a/src/video/wayland/SDL_waylandwindow.h b/src/video/wayland/SDL_waylandwindow.h
index bd6cd174ce8c0..427c42713e568 100644
--- a/src/video/wayland/SDL_waylandwindow.h
+++ b/src/video/wayland/SDL_waylandwindow.h
@@ -103,6 +103,13 @@ struct SDL_WindowData
WAYLAND_TOPLEVEL_CONSTRAINED_BOTTOM = 0x08
} toplevel_constraints;
+ enum
+ {
+ WAYLAND_RESIZE_EDGE_LR = 0x01,
+ WAYLAND_RESIZE_EDGE_TB = 0x02,
+ WAYLAND_RESIZE_EDGE_CORNER = WAYLAND_RESIZE_EDGE_LR | WAYLAND_RESIZE_EDGE_TB,
+ } resize_edge;
+
struct wl_egl_window *egl_window;
#ifdef SDL_VIDEO_OPENGL_EGL
EGLSurface egl_surface;
@@ -218,6 +225,7 @@ struct SDL_WindowData
SDL_DisplayID last_displayID;
int pending_state_deadline_count;
Uint64 last_focus_event_time_ns;
+ Uint64 last_resize_event_time_ns;
int icc_fd;
Uint32 icc_size;
bool floating;