From 0a45525242516772f8dfa6e07b123dedf1439372 Mon Sep 17 00:00:00 2001
From: Frank Praznik <[EMAIL REDACTED]>
Date: Fri, 7 Nov 2025 12:32:24 -0500
Subject: [PATCH] wayland: Handle min/max sizes in fixed-size windows with
viewports
Wayland is sometimes at-odds with clients that want to enforce an aspect ratio or min/max window size, as certain window states have dimensions that either must be obeyed (maximized), or will give terrible results if they aren't (tiled). Use a viewport and a masking subsurface to handle cases where surfaces are unable to match the exact window size.
The changes made to accommodate this also catches some additional windowing related edge-cases, simplifies synchronization, and prevents commits before a buffer has been attached to the surface.
---
src/video/wayland/SDL_waylandevents.c | 114 ++--
src/video/wayland/SDL_waylandevents_c.h | 4 +-
src/video/wayland/SDL_waylandmouse.c | 17 +
src/video/wayland/SDL_waylandmouse.h | 2 +
src/video/wayland/SDL_waylandshmbuffer.c | 25 +
src/video/wayland/SDL_waylandshmbuffer.h | 2 +
src/video/wayland/SDL_waylandvideo.c | 16 +
src/video/wayland/SDL_waylandvideo.h | 2 +
src/video/wayland/SDL_waylandwindow.c | 655 +++++++++++++------
src/video/wayland/SDL_waylandwindow.h | 24 +-
wayland-protocols/single-pixel-buffer-v1.xml | 76 +++
11 files changed, 666 insertions(+), 271 deletions(-)
create mode 100644 wayland-protocols/single-pixel-buffer-v1.xml
diff --git a/src/video/wayland/SDL_waylandevents.c b/src/video/wayland/SDL_waylandevents.c
index b8fb8e0a42472..30b4002701f0d 100644
--- a/src/video/wayland/SDL_waylandevents.c
+++ b/src/video/wayland/SDL_waylandevents.c
@@ -802,7 +802,20 @@ static void pointer_handle_motion(void *data, struct wl_pointer *pointer,
static void pointer_dispatch_enter(SDL_WaylandSeat *seat)
{
- SDL_WindowData *window = seat->pointer.pending_frame.enter_window;
+ SDL_WindowData *window = Wayland_GetWindowDataForOwnedSurface(seat->pointer.pending_frame.enter_surface);
+ if (!window) {
+ // Entering a surface not managed by SDL; just set the cursor reset flag.
+ Wayland_SeatResetCursor(seat);
+ return;
+ }
+
+ if (window->surface != seat->pointer.pending_frame.enter_surface) {
+ /* This surface is part of the window managed by SDL, but it is not the main content
+ * surface and doesn't get focus. Just set the default cursor and leave.
+ */
+ Wayland_SeatSetDefaultCursor(seat);
+ return;
+ }
seat->pointer.focus = window;
++window->pointer_focus_count;
@@ -834,14 +847,8 @@ static void pointer_handle_enter(void *data, struct wl_pointer *pointer,
return;
}
- SDL_WindowData *window = Wayland_GetWindowDataForOwnedSurface(surface);
- if (!window) {
- // Not a surface owned by SDL.
- return;
- }
-
SDL_WaylandSeat *seat = (SDL_WaylandSeat *)data;
- seat->pointer.pending_frame.enter_window = window;
+ seat->pointer.pending_frame.enter_surface = surface;
seat->pointer.enter_serial = serial;
/* In the case of e.g. a pointer confine warp, we may receive an enter
@@ -860,32 +867,39 @@ static void pointer_handle_enter(void *data, struct wl_pointer *pointer,
static void pointer_dispatch_leave(SDL_WaylandSeat *seat, bool update_pointer)
{
- SDL_WindowData *window = seat->pointer.pending_frame.leave_window;
+ SDL_WindowData *window = Wayland_GetWindowDataForOwnedSurface(seat->pointer.pending_frame.leave_surface);
if (window) {
- // Clear the capture flag and raise all buttons
- window->sdlwindow->flags &= ~SDL_WINDOW_MOUSE_CAPTURE;
-
- seat->pointer.focus = NULL;
- for (Uint8 i = 1; seat->pointer.buttons_pressed; ++i) {
- if (seat->pointer.buttons_pressed & SDL_BUTTON_MASK(i)) {
- SDL_SendMouseButton(0, window->sdlwindow, seat->pointer.sdl_id, i, false);
- seat->pointer.buttons_pressed &= ~SDL_BUTTON_MASK(i);
- }
- }
+ if (seat->pointer.focus) {
+ if (seat->pointer.focus->surface == seat->pointer.pending_frame.leave_surface) {
+ // Clear the capture flag and raise all buttons
+ window->sdlwindow->flags &= ~SDL_WINDOW_MOUSE_CAPTURE;
- /* A pointer leave event may be emitted if the compositor hides the pointer in response to receiving a touch event.
- * Don't relinquish focus if the surface has active touches, as the compositor is just transitioning from mouse to touch mode.
- */
- SDL_Window *mouse_focus = SDL_GetMouseFocus();
- const bool had_focus = mouse_focus && window->sdlwindow == mouse_focus;
- if (!--window->pointer_focus_count && had_focus && !window->active_touch_count) {
- SDL_SetMouseFocus(NULL);
- }
+ seat->pointer.focus = NULL;
+ for (Uint8 i = 1; seat->pointer.buttons_pressed; ++i) {
+ if (seat->pointer.buttons_pressed & SDL_BUTTON_MASK(i)) {
+ SDL_SendMouseButton(0, window->sdlwindow, seat->pointer.sdl_id, i, false);
+ seat->pointer.buttons_pressed &= ~SDL_BUTTON_MASK(i);
+ }
+ }
- if (update_pointer) {
- Wayland_SeatUpdatePointerGrab(seat);
- Wayland_SeatUpdatePointerCursor(seat);
+ /* A pointer leave event may be emitted if the compositor hides the pointer in response to receiving a touch event.
+ * Don't relinquish focus if the surface has active touches, as the compositor is just transitioning from mouse to touch mode.
+ */
+ SDL_Window *mouse_focus = SDL_GetMouseFocus();
+ const bool had_focus = mouse_focus && window->sdlwindow == mouse_focus;
+ if (!--window->pointer_focus_count && had_focus && !window->active_touch_count) {
+ SDL_SetMouseFocus(NULL);
+ }
+
+ if (update_pointer) {
+ Wayland_SeatUpdatePointerGrab(seat);
+ Wayland_SeatUpdatePointerCursor(seat);
+ }
+ }
+ } else if (update_pointer) {
+ // Leaving a non-content surface managed by SDL; just set the cursor reset flag.
+ Wayland_SeatResetCursor(seat);
}
}
}
@@ -898,15 +912,9 @@ static void pointer_handle_leave(void *data, struct wl_pointer *pointer,
return;
}
- SDL_WindowData *window = Wayland_GetWindowDataForOwnedSurface(surface);
- if (!window) {
- // Not a surface owned by SDL.
- return;
- }
-
SDL_WaylandSeat *seat = (SDL_WaylandSeat *)data;
- seat->pointer.pending_frame.leave_window = window;
- if (wl_pointer_get_version(seat->pointer.wl_pointer) < WL_POINTER_FRAME_SINCE_VERSION && window == seat->pointer.focus) {
+ seat->pointer.pending_frame.leave_surface = surface;
+ if (wl_pointer_get_version(seat->pointer.wl_pointer) < WL_POINTER_FRAME_SINCE_VERSION) {
pointer_dispatch_leave(seat, true);
}
}
@@ -1277,11 +1285,13 @@ static void pointer_handle_frame(void *data, struct wl_pointer *pointer)
{
SDL_WaylandSeat *seat = data;
- if (seat->pointer.pending_frame.enter_window) {
- if (seat->pointer.focus && seat->pointer.pending_frame.leave_window == seat->pointer.focus) {
+ if (seat->pointer.pending_frame.enter_surface) {
+ if (seat->pointer.pending_frame.leave_surface) {
// Leaving the previous surface before entering a new surface.
pointer_dispatch_leave(seat, false);
+ seat->pointer.pending_frame.leave_surface = NULL;
}
+
pointer_dispatch_enter(seat);
}
@@ -1309,7 +1319,7 @@ static void pointer_handle_frame(void *data, struct wl_pointer *pointer)
pointer_dispatch_axis(seat);
}
- if (seat->pointer.focus && seat->pointer.pending_frame.leave_window == seat->pointer.focus) {
+ if (seat->pointer.pending_frame.leave_surface) {
pointer_dispatch_leave(seat, true);
}
@@ -1435,7 +1445,7 @@ static void touch_handler_down(void *data, struct wl_touch *touch, uint32_t seri
Wayland_UpdateImplicitGrabSerial(seat, serial);
window_data = Wayland_GetWindowDataForOwnedSurface(surface);
- if (window_data) {
+ if (window_data && window_data->surface == surface) {
float x, y;
if (window_data->current.logical_width <= 1) {
@@ -1457,8 +1467,7 @@ static void touch_handler_down(void *data, struct wl_touch *touch, uint32_t seri
}
}
-static void touch_handler_up(void *data, struct wl_touch *touch, uint32_t serial,
- uint32_t timestamp, int id)
+static void touch_handler_up(void *data, struct wl_touch *touch, uint32_t serial, uint32_t timestamp, int id)
{
SDL_WaylandSeat *seat = (SDL_WaylandSeat *)data;
wl_fixed_t fx = 0, fy = 0;
@@ -1469,7 +1478,7 @@ static void touch_handler_up(void *data, struct wl_touch *touch, uint32_t serial
if (surface) {
SDL_WindowData *window_data = Wayland_GetWindowDataForOwnedSurface(surface);
- if (window_data) {
+ if (window_data && window_data->surface == surface) {
const float x = (float)wl_fixed_to_double(fx) / window_data->current.logical_width;
const float y = (float)wl_fixed_to_double(fy) / window_data->current.logical_height;
@@ -1489,8 +1498,7 @@ static void touch_handler_up(void *data, struct wl_touch *touch, uint32_t serial
}
}
-static void touch_handler_motion(void *data, struct wl_touch *touch, uint32_t timestamp,
- int id, wl_fixed_t fx, wl_fixed_t fy)
+static void touch_handler_motion(void *data, struct wl_touch *touch, uint32_t timestamp, int id, wl_fixed_t fx, wl_fixed_t fy)
{
SDL_WaylandSeat *seat = (SDL_WaylandSeat *)data;
struct wl_surface *surface = NULL;
@@ -1500,7 +1508,7 @@ static void touch_handler_motion(void *data, struct wl_touch *touch, uint32_t ti
if (surface) {
SDL_WindowData *window_data = Wayland_GetWindowDataForOwnedSurface(surface);
- if (window_data) {
+ if (window_data && window_data->surface == surface) {
const float x = (float)wl_fixed_to_double(fx) / window_data->current.logical_width;
const float y = (float)wl_fixed_to_double(fy) / window_data->current.logical_height;
@@ -2395,9 +2403,9 @@ static void Wayland_SeatDestroyPointer(SDL_WaylandSeat *seat)
// Make sure focus is removed from a surface before the pointer is destroyed.
if (seat->pointer.focus) {
- seat->pointer.pending_frame.leave_window = seat->pointer.focus;
+ seat->pointer.pending_frame.leave_surface = seat->pointer.focus->surface;
pointer_dispatch_leave(seat, false);
- seat->pointer.pending_frame.leave_window = NULL;
+ seat->pointer.pending_frame.leave_surface = NULL;
}
SDL_RemoveMouse(seat->pointer.sdl_id);
@@ -3349,7 +3357,7 @@ static void tablet_tool_handle_proximity_in(void *data, struct zwp_tablet_tool_v
{
SDL_WaylandPenTool *sdltool = (SDL_WaylandPenTool *) data;
SDL_WindowData *windowdata = surface ? Wayland_GetWindowDataForOwnedSurface(surface) : NULL;
- sdltool->focus = windowdata;
+ sdltool->focus = windowdata && windowdata->surface == surface ? windowdata : NULL;
sdltool->proximity_serial = serial;
sdltool->frame.have_proximity = true;
sdltool->frame.in_proximity = true;
@@ -3664,9 +3672,9 @@ void Wayland_DisplayRemoveWindowReferencesFromSeats(SDL_VideoData *display, SDL_
}
if (seat->pointer.focus == window) {
- seat->pointer.pending_frame.leave_window = seat->pointer.focus;
+ seat->pointer.pending_frame.leave_surface = seat->pointer.focus->surface;
pointer_dispatch_leave(seat, true);
- seat->pointer.pending_frame.leave_window = NULL;
+ seat->pointer.pending_frame.leave_surface = NULL;
}
// Need the safe loop variant here as cancelling a touch point removes it from the list.
diff --git a/src/video/wayland/SDL_waylandevents_c.h b/src/video/wayland/SDL_waylandevents_c.h
index 4e98e6f69f7be..1a597a35f8b10 100644
--- a/src/video/wayland/SDL_waylandevents_c.h
+++ b/src/video/wayland/SDL_waylandevents_c.h
@@ -236,8 +236,8 @@ typedef struct SDL_WaylandSeat
SDL_MouseWheelDirection direction;
} axis;
- SDL_WindowData *enter_window;
- SDL_WindowData *leave_window;
+ struct wl_surface *enter_surface;
+ struct wl_surface *leave_surface;
// Event timestamp in nanoseconds
Uint64 timestamp_ns;
diff --git a/src/video/wayland/SDL_waylandmouse.c b/src/video/wayland/SDL_waylandmouse.c
index ec54994d87c2d..72b9d14d5e372 100644
--- a/src/video/wayland/SDL_waylandmouse.c
+++ b/src/video/wayland/SDL_waylandmouse.c
@@ -1523,6 +1523,23 @@ void Wayland_FiniMouse(SDL_VideoData *data)
#endif
}
+void Wayland_SeatResetCursor(SDL_WaylandSeat *seat)
+{
+ Wayland_CursorStateResetCursor(&seat->pointer.cursor_state);
+}
+
+void Wayland_SeatSetDefaultCursor(SDL_WaylandSeat *seat)
+{
+ SDL_Mouse *mouse = SDL_GetMouse();
+ SDL_WindowData *pointer_focus = seat->pointer.focus;
+ const Wayland_PointerObject obj = {
+ .wl_pointer = seat->pointer.wl_pointer,
+ .is_pointer = true
+ };
+
+ Wayland_CursorStateSetCursor(&seat->pointer.cursor_state, &obj, pointer_focus, seat->pointer.enter_serial, mouse->def_cursor);
+}
+
void Wayland_SeatUpdatePointerCursor(SDL_WaylandSeat *seat)
{
SDL_Mouse *mouse = SDL_GetMouse();
diff --git a/src/video/wayland/SDL_waylandmouse.h b/src/video/wayland/SDL_waylandmouse.h
index 481cae10739eb..85ba5eef24d40 100644
--- a/src/video/wayland/SDL_waylandmouse.h
+++ b/src/video/wayland/SDL_waylandmouse.h
@@ -27,6 +27,8 @@
extern void Wayland_InitMouse(SDL_VideoData *data);
extern void Wayland_FiniMouse(SDL_VideoData *data);
extern void Wayland_SeatUpdatePointerCursor(SDL_WaylandSeat *seat);
+extern void Wayland_SeatSetDefaultCursor(SDL_WaylandSeat *seat);
+extern void Wayland_SeatResetCursor(SDL_WaylandSeat *seat);
extern void Wayland_TabletToolUpdateCursor(SDL_WaylandPenTool *tool);
extern void Wayland_SeatWarpMouse(SDL_WaylandSeat *seat, SDL_WindowData *window, float x, float y);
extern void Wayland_CursorStateSetFrameCallback(SDL_WaylandCursorState *state, void *userdata);
diff --git a/src/video/wayland/SDL_waylandshmbuffer.c b/src/video/wayland/SDL_waylandshmbuffer.c
index 88da2e9324185..9e355fd77a302 100644
--- a/src/video/wayland/SDL_waylandshmbuffer.c
+++ b/src/video/wayland/SDL_waylandshmbuffer.c
@@ -32,6 +32,7 @@
#include "SDL_waylandshmbuffer.h"
#include "SDL_waylandvideo.h"
+#include "single-pixel-buffer-v1-client-protocol.h"
static bool SetTempFileSize(int fd, off_t size)
{
@@ -186,4 +187,28 @@ void Wayland_ReleaseSHMPool(Wayland_SHMPool *shmPool)
}
}
+struct wl_buffer *Wayland_CreateSinglePixelBuffer(Uint32 r, Uint32 g, Uint32 b, Uint32 a)
+{
+ SDL_VideoData *viddata = SDL_GetVideoDevice()->internal;
+
+ // The single-pixel buffer protocol is preferred, as the compositor can choose an optimal format.
+ if (viddata->single_pixel_buffer_manager) {
+ return wp_single_pixel_buffer_manager_v1_create_u32_rgba_buffer(viddata->single_pixel_buffer_manager, r, g, b, a);
+ } else {
+ Wayland_SHMPool *pool = Wayland_AllocSHMPool(4);
+ if (!pool) {
+ return NULL;
+ }
+
+ void *mem;
+ struct wl_buffer *wl_buffer = Wayland_AllocBufferFromPool(pool, 1, 1, &mem);
+
+ const Uint8 pixel[4] = { r >> 24, g >> 24, b >> 24, a >> 24 };
+ SDL_memcpy(mem, pixel, sizeof(pixel));
+
+ Wayland_ReleaseSHMPool(pool);
+ return wl_buffer;
+ }
+}
+
#endif
diff --git a/src/video/wayland/SDL_waylandshmbuffer.h b/src/video/wayland/SDL_waylandshmbuffer.h
index 019793e7edc33..44caa6ed09c4a 100644
--- a/src/video/wayland/SDL_waylandshmbuffer.h
+++ b/src/video/wayland/SDL_waylandshmbuffer.h
@@ -30,4 +30,6 @@ extern Wayland_SHMPool *Wayland_AllocSHMPool(int size);
extern struct wl_buffer *Wayland_AllocBufferFromPool(Wayland_SHMPool *shmPool, int width, int height, void **data);
extern void Wayland_ReleaseSHMPool(Wayland_SHMPool *shmPool);
+extern struct wl_buffer *Wayland_CreateSinglePixelBuffer(Uint32 r, Uint32 g, Uint32 b, Uint32 a);
+
#endif
diff --git a/src/video/wayland/SDL_waylandvideo.c b/src/video/wayland/SDL_waylandvideo.c
index 2b0cfad3d3c9c..bcefeb74dd98a 100644
--- a/src/video/wayland/SDL_waylandvideo.c
+++ b/src/video/wayland/SDL_waylandvideo.c
@@ -69,6 +69,7 @@
#include "color-management-v1-client-protocol.h"
#include "pointer-warp-v1-client-protocol.h"
#include "pointer-gestures-unstable-v1-client-protocol.h"
+#include "single-pixel-buffer-v1-client-protocol.h"
#ifdef HAVE_LIBDECOR_H
#include <libdecor.h>
@@ -653,6 +654,7 @@ static SDL_VideoDevice *Wayland_CreateDevice(bool require_preferred_protocols)
device->SetWindowResizable = Wayland_SetWindowResizable;
device->SetWindowPosition = Wayland_SetWindowPosition;
device->SetWindowSize = Wayland_SetWindowSize;
+ device->SetWindowAspectRatio = Wayland_SetWindowAspectRatio;
device->SetWindowMinimumSize = Wayland_SetWindowMinimumSize;
device->SetWindowMaximumSize = Wayland_SetWindowMaximumSize;
device->SetWindowParent = Wayland_SetWindowParent;
@@ -1278,6 +1280,8 @@ static void handle_registry_global(void *data, struct wl_registry *registry, uin
if (SDL_strcmp(interface, "wl_compositor") == 0) {
d->compositor = wl_registry_bind(d->registry, id, &wl_compositor_interface, SDL_min(SDL_WL_COMPOSITOR_VERSION, version));
+ } else if (SDL_strcmp(interface, "wl_subcompositor") == 0) {
+ d->subcompositor = wl_registry_bind(d->registry, id, &wl_subcompositor_interface, 1);
} else if (SDL_strcmp(interface, "wl_output") == 0) {
Wayland_add_display(d, id, SDL_min(version, SDL_WL_OUTPUT_VERSION));
} else if (SDL_strcmp(interface, "wl_seat") == 0) {
@@ -1344,6 +1348,8 @@ static void handle_registry_global(void *data, struct wl_registry *registry, uin
} else if (SDL_strcmp(interface, "zwp_pointer_gestures_v1") == 0) {
d->zwp_pointer_gestures = wl_registry_bind(d->registry, id, &zwp_pointer_gestures_v1_interface, SDL_min(version, 3));
Wayland_DisplayInitPointerGestureManager(d);
+ } else if (SDL_strcmp(interface, "wp_single_pixel_buffer_manager_v1") == 0) {
+ d->single_pixel_buffer_manager = wl_registry_bind(d->registry, id, &wp_single_pixel_buffer_manager_v1_interface, 1);
}
#ifdef SDL_WL_FIXES_VERSION
else if (SDL_strcmp(interface, "wl_fixes") == 0) {
@@ -1692,6 +1698,16 @@ static void Wayland_VideoCleanup(SDL_VideoDevice *_this)
data->zwp_pointer_gestures = NULL;
}
+ if (data->single_pixel_buffer_manager) {
+ wp_single_pixel_buffer_manager_v1_destroy(data->single_pixel_buffer_manager);
+ data->single_pixel_buffer_manager = NULL;
+ }
+
+ if (data->subcompositor) {
+ wl_subcompositor_destroy(data->subcompositor);
+ data->subcompositor = NULL;
+ }
+
if (data->compositor) {
wl_compositor_destroy(data->compositor);
data->compositor = NULL;
diff --git a/src/video/wayland/SDL_waylandvideo.h b/src/video/wayland/SDL_waylandvideo.h
index 3542b48afa04a..8578aa7650a67 100644
--- a/src/video/wayland/SDL_waylandvideo.h
+++ b/src/video/wayland/SDL_waylandvideo.h
@@ -61,6 +61,7 @@ struct SDL_VideoData
struct libdecor *libdecor;
#endif
} shell;
+ struct wl_subcompositor *subcompositor;
struct zwp_relative_pointer_manager_v1 *relative_pointer_manager;
struct zwp_pointer_constraints_v1 *pointer_constraints;
struct wp_pointer_warp_v1 *wp_pointer_warp_v1;
@@ -85,6 +86,7 @@ struct SDL_VideoData
struct zwp_tablet_manager_v2 *tablet_manager;
struct wl_fixes *wl_fixes;
struct zwp_pointer_gestures_v1 *zwp_pointer_gestures;
+ struct wp_single_pixel_buffer_manager_v1 *single_pixel_buffer_manager;
struct xkb_context *xkb_context;
diff --git a/src/video/wayland/SDL_waylandwindow.c b/src/video/wayland/SDL_waylandwindow.c
index 5dac6f22a84c5..7fdaa52b83a01 100644
--- a/src/video/wayland/SDL_waylandwindow.c
+++ b/src/video/wayland/SDL_waylandwindow.c
@@ -117,39 +117,15 @@ static enum WaylandModeScale GetModeScaleMethod(void)
return scale_mode;
}
-static void GetBufferSize(SDL_Window *window, int *width, int *height)
-{
- SDL_WindowData *data = window->internal;
- int buf_width;
- int buf_height;
-
- // Exclusive fullscreen modes always have a pixel density of 1
- if (data->is_fullscreen && window->fullscreen_exclusive) {
- buf_width = window->current_fullscreen_mode.w;
- buf_height = window->current_fullscreen_mode.h;
- } else if (!data->scale_to_display) {
- // Round fractional backbuffer sizes halfway away from zero.
- buf_width = PointToPixel(window, data->requested.logical_width);
- buf_height = PointToPixel(window, data->requested.logical_height);
- } else {
- buf_width = data->requested.pixel_width;
- buf_height = data->requested.pixel_height;
- }
-
- if (width) {
- *width = buf_width;
- }
- if (height) {
- *height = buf_height;
- }
-}
-
static void SetMinMaxDimensions(SDL_Window *window)
{
SDL_WindowData *wind = window->internal;
int min_width, min_height, max_width, max_height;
- if ((window->flags & SDL_WINDOW_FULLSCREEN) || wind->fullscreen_deadline_count) {
+ /* Keep the limits off while the window is in a fixed-size state, or the controls
+ * to exit that state may be disabled.
+ */
+ if (window->flags & (SDL_WINDOW_FULLSCREEN | SDL_WINDOW_MAXIMIZED)) {
min_width = 0;
min_height = 0;
max_width = 0;
@@ -184,6 +160,13 @@ static void SetMinMaxDimensions(SDL_Window *window)
if (!wind->shell_surface.libdecor.frame) {
return; // Can't do anything yet, wait for ShowWindow
}
+
+ if (min_width && min_height && min_width == max_width && min_height == max_height) {
+ libdecor_frame_unset_capabilities(wind->shell_surface.libdecor.frame, LIBDECOR_ACTION_RESIZE);
+ } else {
+ libdecor_frame_set_capabilities(wind->shell_surface.libdecor.frame, LIBDECOR_ACTION_RESIZE);
+ }
+
/* No need to change these values if the window is non-resizable,
* as libdecor will just overwrite them internally.
*/
@@ -286,18 +269,17 @@ static void RepositionPopup(SDL_Window *window, bool use_current_position)
}
}
-static void SetSurfaceOpaqueRegion(SDL_WindowData *wind, bool is_opaque)
+static void SetSurfaceOpaqueRegion(struct wl_surface *surface, int width, int height)
{
- SDL_VideoData *viddata = wind->waylandData;
+ SDL_VideoData *viddata = SDL_GetVideoDevice()->internal;
- if (is_opaque) {
+ if (width && height) {
struct wl_region *region = wl_compositor_create_region(viddata->compositor);
- wl_region_add(region, 0, 0,
- wind->current.logical_width, wind->current.logical_height);
- wl_surface_set_opaque_region(wind->surface, region);
+ wl_region_add(region, 0, 0, width, height);
+ wl_surface_set_opaque_region(surface, region);
wl_region_destroy(region);
} else {
- wl_surface_set_opaque_region(wind->surface, NULL);
+ wl_surface_set_opaque_region(surface, NULL);
}
}
@@ -307,29 +289,19 @@ static void ConfigureWindowGeometry(SDL_Window *window)
const double scale_factor = GetWindowScale(window);
const int old_pixel_width = data->current.pixel_width;
const int old_pixel_height = data->current.pixel_height;
- int window_width, window_height;
+ int window_width = 0;
+ int window_height = 0;
+ int viewport_width, viewport_height;
bool window_size_changed;
-
- // Set the drawable backbuffer size.
- GetBufferSize(window, &data->current.pixel_width, &data->current.pixel_height);
- const bool buffer_size_changed = data->current.pixel_width != old_pixel_width ||
- data->current.pixel_height != old_pixel_height;
-
- if (data->egl_window && buffer_size_changed) {
- WAYLAND_wl_egl_window_resize(data->egl_window,
- data->current.pixel_width,
- data->current.pixel_height,
- 0, 0);
- }
+ bool buffer_size_changed;
+ const bool is_opaque = !(window->flags & SDL_WINDOW_TRANSPARENT) && window->opacity == 1.0f;
if (data->is_fullscreen && window->fullscreen_exclusive) {
- int output_width;
- int output_height;
window_width = window->current_fullscreen_mode.w;
window_height = window->current_fullscreen_mode.h;
- output_width = data->requested.logical_width;
- output_height = data->requested.logical_height;
+ viewport_width = data->requested.logical_width;
+ viewport_height = data->requested.logical_height;
switch (GetModeScaleMethod()) {
case WAYLAND_MODE_SCALE_NONE:
@@ -337,40 +309,50 @@ static void ConfigureWindowGeometry(SDL_Window *window)
* Windows can request a smaller size, but exceeding these dimensions is a protocol violation,
* thus, modes that exceed the output size still need to be scaled with a viewport.
*/
- if (window_width <= output_width && window_height <= output_height) {
- output_width = window_width;
- output_height = window_height;
+ if (window_width <= viewport_width && window_height <= viewport_height) {
+ viewport_width = window_width;
+ viewport_height = window_height;
break;
}
SDL_FALLTHROUGH;
case WAYLAND_MODE_SCALE_ASPECT:
{
- const float output_ratio = (float)output_width / (float)output_height;
+ const float output_ratio = (float)viewport_width / (float)viewport_height;
const float mode_ratio = (float)window_width / (float)window_height;
if (output_ratio > mode_ratio) {
- output_width = SDL_lroundf((float)window_width * ((float)output_height / (float)window_height));
+ viewport_width = SDL_lroundf((float)window_width * ((float)viewport_height / (float)window_height));
} else if (output_ratio < mode_ratio) {
- output_height = SDL_lroundf((float)window_height * ((float)output_width / (float)window_width));
+ viewport_height = SDL_lroundf((float)window_height * ((float)viewport_width / (float)window_width));
}
} break;
default:
break;
}
- window_size_changed = window_width != window->w || window_height != window->h ||
- data->current.logical_width != output_width || data->current.logical_height != output_height;
+ window_size_changed = window_width != window->w ||
+ window_height != window->h ||
+ data->current.viewport_width != viewport_width ||
+ data->current.viewport_height != viewport_height;
+
+ // Exclusive fullscreen window sizes are always in pixel units.
+ data->current.pixel_width = window_width;
+ data->current.pixel_height = window_height;
+ buffer_size_changed = data->current.pixel_width != old_pixel_width ||
+ data->current.pixel_height != old_pixel_height;
if (window_size_changed || buffer_size_changed) {
if (data->viewport) {
- wp_viewport_set_destination(data->viewport, output_width, output_height);
+ wp_viewport_set_destination(data->viewport, viewport_width, viewport_height);
- data->current.logical_width = output_width;
- data->current.logical_height = output_height;
+ data->current.logical_width = data->requested.logical_width;
+ data->current.logical_height = data->requested.logical_height;
+ data->current.viewport_width = viewport_width;
+ data->current.viewport_height = viewport_height;
} else {
// Calculate the integer scale from the mode and output.
- const int32_t int_scale = SDL_max(window->current_fullscreen_mode.w / output_width, 1);
+ const int32_t int_scale = SDL_max(window->current_fullscreen_mode.w / viewport_width, 1);
wl_surface_set_buffer_scale(data->surface, int_scale);
data->current.logical_width = window->current_fullscreen_mode.w;
@@ -381,33 +363,184 @@ static void ConfigureWindowGeometry(SDL_Window *window)
data->pointer_scale.y = (double)window_height / (double)data->current.logical_height;
}
} else {
- window_width = data->requested.logical_width;
- window_height = data->requested.logical_height;
+ if (!data->scale_to_display) {
+ viewport_width = data->requested.logical_width;
+ viewport_height = data->requested.logical_height;
+ } else {
+ viewport_width = data->requested.pixel_width;
+ viewport_height = data->requested.pixel_height;
+ }
+
+ if (data->viewport && data->waylandData->subcompositor && !data->is_fullscreen) {
+ if (window->min_w) {
+ window_width = viewport_width = SDL_max(viewport_width, window->min_w);
+ }
+ if (window->min_h) {
+ window_height = viewport_height = SDL_max(viewport_height, window->min_h);
+ }
+ if (window->max_w) {
+ window_width = viewport_width = SDL_min(viewport_width, window->max_w);
+ }
+ if (window->max_h) {
+ window_height = viewport_height = SDL_min(viewport_height, window->max_h);
+ }
+
+ float aspect = (float)viewport_width / (float)viewport_height;
+ if (window->min_aspect != 0.f && aspect < window->min_aspect) {
+ viewport_height = SDL_lroundf((float)viewport_width / window->min_aspect);
+ } else if (window->max_aspect != 0.f && aspect > window->max_aspect) {
+ viewport_width = SDL_lroundf((float)viewport_height * window->max_aspect);
+ }
+
+ // At this point, the viewport matches the window dimensions, but the viewport might be clamped to window dimensions beyond here.
+ window_width = viewport_width;
+ window_height = viewport_height;
+
+ // If the viewport bounds exceed the window size, scale them while maintaining the aspect ratio.
+ if (!data->scale_to_display) {
+ if (viewport_width > data->requested.logical_width || viewport_height > data->requested.logical_height) {
+ aspect = (float)viewport_width / (float)viewport_height;
+ const float window_ratio = (float)data->requested.logical_width / (float)data->requested.logical_height;
+ if (aspect >= window_ratio) {
+ viewport_width = data->requested.logical_wid
(Patch may be truncated, please check the link at the top of this post.)