wayland: Add a forced screen space scaling mode

Add a mode that forces Wayland windows to output with scaling that forces 1:1 pixel mapping.

This is intended to allow legacy applications to be displayed without desktop scaling being applied, and may have issues with some display configurations, as this forces the window to behave in a way that Wayland desktops were not designed to accommodate (rounding errors can result from certain combinations of window/scale values, the window may be unusably small, jump in size at times, or appear to be larger than the desktop space, and cursor precision may be reduced).

Windows flagged as DPI-aware are not affected by this.

The automated video test suite passes with the hint turned on.
main
Frank Praznik 2024-01-19 10:30:07 -05:00
parent 9f7eb6c4c1
commit 5b8ca05f1a
7 changed files with 279 additions and 73 deletions

View File

@ -6,6 +6,14 @@ encounter limitations or behavior that is different from other windowing systems
## Common issues: ## Common issues:
### Legacy, DPI-unaware applications are blurry
- Wayland handles high-DPI displays by scaling the desktop, which causes applications that are not designed to be
DPI-aware to be automatically scaled by the window manager, which results in them being blurry. SDL can _attempt_ to
scale these applications such that they will be output with a 1:1 pixel aspect, however this may be buggy, especially
with odd-sized windows and/or scale factors that aren't quarter-increments (125%, 150%, etc...). To enable this, set
the environment variable `SDL_HINT_VIDEO_WAYLAND_SCALE_TO_DISPLAY=1`
### Window decorations are missing, or the decorations look strange ### Window decorations are missing, or the decorations look strange
- On some desktops (i.e. GNOME), Wayland applications use a library - On some desktops (i.e. GNOME), Wayland applications use a library

View File

@ -2000,6 +2000,32 @@ extern "C" {
*/ */
#define SDL_HINT_VIDEO_WAYLAND_MODE_SCALING "SDL_VIDEO_WAYLAND_MODE_SCALING" #define SDL_HINT_VIDEO_WAYLAND_MODE_SCALING "SDL_VIDEO_WAYLAND_MODE_SCALING"
/**
* A variable forcing non-DPI-aware Wayland windows to output at 1:1 scaling.
*
* When this hint is set, Wayland windows that are not flagged as being DPI-aware will be
* output with scaling designed to force 1:1 pixel mapping.
*
* This is intended to allow legacy applications to be displayed without desktop scaling
* being applied, and has issues with certain display configurations, as this forces the
* window to behave in a way that Wayland desktops were not designed to accommodate:
*
* - Rounding errors can result with odd window sizes and/or desktop scales.
* - The window may be unusably small.
* - The window may jump in size at times.
* - The window may appear to be larger than the desktop size to the application.
* - Possible loss of cursor precision.
*
* New applications should be designed with proper DPI awareness handling instead of enabling this.
*
* This variable can be set to the following values:
* "0" - Windows will be scaled normally.
* "1" - Windows will be forced to scale to achieve 1:1 output.
*
* By default, scaling to the display is disabled.
*/
#define SDL_HINT_VIDEO_WAYLAND_SCALE_TO_DISPLAY "SDL_VIDEO_WAYLAND_SCALE_TO_DISPLAY"
/** /**
* Enable or disable mouse pointer warp emulation, needed by some older games. * Enable or disable mouse pointer warp emulation, needed by some older games.
* *

View File

@ -883,6 +883,18 @@ extern DECLSPEC SDL_Window *SDLCALL SDL_CreatePopupWindow(SDL_Window *parent, in
* *
* These are additional supported properties on Wayland: * These are additional supported properties on Wayland:
* *
* - `SDL_PROP_WINDOW_CREATE_WAYLAND_SCALE_TO_DISPLAY` - true if the window
* should use forced scaling designed to produce 1:1 pixel mapping if not
* flagged as being DPI-aware. This is intended to allow legacy applications
* to be displayed without desktop scaling being applied, and has issues with
* certain display configurations, as this forces the window to behave in a
* way that Wayland desktops were not designed to accommodate. Potential
* issues include, but are not limited to: rounding errors can result when
* odd window sizes/scales are used, the window may be unusably small, the
* window may jump in visible size at times, the window may appear to be
* larger than the desktop space, and possible loss of cursor precision can
* occur. New applications should be designed with proper DPI awareness and
* handling instead of enabling this.
* - `SDL_PROP_WINDOW_CREATE_WAYLAND_SURFACE_ROLE_CUSTOM_BOOLEAN` - true if * - `SDL_PROP_WINDOW_CREATE_WAYLAND_SURFACE_ROLE_CUSTOM_BOOLEAN` - true if
* the application wants to use the Wayland surface for a custom role and * the application wants to use the Wayland surface for a custom role and
* does not want it attached to an XDG toplevel window. See * does not want it attached to an XDG toplevel window. See
@ -947,6 +959,7 @@ extern DECLSPEC SDL_Window *SDLCALL SDL_CreateWindowWithProperties(SDL_Propertie
#define SDL_PROP_WINDOW_CREATE_Y_NUMBER "y" #define SDL_PROP_WINDOW_CREATE_Y_NUMBER "y"
#define SDL_PROP_WINDOW_CREATE_COCOA_WINDOW_POINTER "cocoa.window" #define SDL_PROP_WINDOW_CREATE_COCOA_WINDOW_POINTER "cocoa.window"
#define SDL_PROP_WINDOW_CREATE_COCOA_VIEW_POINTER "cocoa.view" #define SDL_PROP_WINDOW_CREATE_COCOA_VIEW_POINTER "cocoa.view"
#define SDL_PROP_WINDOW_CREATE_WAYLAND_SCALE_TO_DISPLAY "wayland.scale_to_display"
#define SDL_PROP_WINDOW_CREATE_WAYLAND_SURFACE_ROLE_CUSTOM_BOOLEAN "wayland.surface_role_custom" #define SDL_PROP_WINDOW_CREATE_WAYLAND_SURFACE_ROLE_CUSTOM_BOOLEAN "wayland.surface_role_custom"
#define SDL_PROP_WINDOW_CREATE_WAYLAND_CREATE_EGL_WINDOW_BOOLEAN "wayland.create_egl_window" #define SDL_PROP_WINDOW_CREATE_WAYLAND_CREATE_EGL_WINDOW_BOOLEAN "wayland.create_egl_window"
#define SDL_PROP_WINDOW_CREATE_WAYLAND_WL_SURFACE_POINTER "wayland.wl_surface" #define SDL_PROP_WINDOW_CREATE_WAYLAND_WL_SURFACE_POINTER "wayland.wl_surface"

View File

@ -507,7 +507,8 @@ static void pointer_handle_motion(void *data, struct wl_pointer *pointer,
} }
if (window && window->hit_test) { if (window && window->hit_test) {
const SDL_Point point = { wl_fixed_to_int(sx_w), wl_fixed_to_int(sy_w) }; const SDL_Point point = { (int)SDL_floor(wl_fixed_to_double(sx_w) * window_data->pointer_scale_x),
(int)SDL_floor(wl_fixed_to_double(sy_w) * window_data->pointer_scale_y) };
SDL_HitTestResult rc = window->hit_test(window, &point, window->hit_test_data); SDL_HitTestResult rc = window->hit_test(window, &point, window->hit_test_data);
if (rc == window_data->hit_test_result) { if (rc == window_data->hit_test_result) {
return; return;

View File

@ -1204,8 +1204,24 @@ static int Wayland_GetDisplayBounds(SDL_VideoDevice *_this, SDL_VideoDisplay *di
rect->w = display->fullscreen_window->current_fullscreen_mode.w; rect->w = display->fullscreen_window->current_fullscreen_mode.w;
rect->h = display->fullscreen_window->current_fullscreen_mode.h; rect->h = display->fullscreen_window->current_fullscreen_mode.h;
} else { } else {
rect->w = display->current_mode->w; /* If the focused window is on the requested display and requires display scaling,
rect->h = display->current_mode->h; * return the physical dimensions in pixels.
*/
SDL_Window *kb = SDL_GetKeyboardFocus();
SDL_Window *m = SDL_GetMouseFocus();
SDL_bool scale_output = (kb && kb->driverdata->scale_to_display && (kb->last_displayID == display->id)) ||
(m && m->driverdata->scale_to_display && (m->last_displayID == display->id));
if (!scale_output) {
rect->w = display->current_mode->w;
rect->h = display->current_mode->h;
} else if (driverdata->transform & WL_OUTPUT_TRANSFORM_90) {
rect->w = driverdata->pixel_height;
rect->h = driverdata->pixel_width;
} else {
rect->w = driverdata->pixel_width;
rect->h = driverdata->pixel_height;
}
} }
return 0; return 0;
} }

View File

@ -43,6 +43,17 @@
#include <libdecor.h> #include <libdecor.h>
#endif #endif
/* These are *NOT* roundtrip safe! */
static int PointToPixel(SDL_Window *window, int point)
{
/* Rounds halfway away from zero as per the Wayland fractional scaling protocol spec. */
return (int)SDL_lroundf((float)point * window->driverdata->windowed_scale_factor);
}
static int PixelToPoint(SDL_Window *window, int pixel)
{
return (int)SDL_lroundf((float)pixel / window->driverdata->windowed_scale_factor);
}
static SDL_bool FloatEqual(float a, float b) static SDL_bool FloatEqual(float a, float b)
{ {
@ -115,7 +126,7 @@ static SDL_bool WindowNeedsViewport(SDL_Window *window)
* - An exclusive fullscreen mode is being emulated and the mode does not match the requested output size. * - An exclusive fullscreen mode is being emulated and the mode does not match the requested output size.
*/ */
if (video->viewporter) { if (video->viewporter) {
if (SurfaceScaleIsFractional(window)) { if (SurfaceScaleIsFractional(window) || wind->scale_to_display) {
return SDL_TRUE; return SDL_TRUE;
} else if (window->fullscreen_exclusive) { } else if (window->fullscreen_exclusive) {
if (window->current_fullscreen_mode.w != wind->requested_window_width || window->current_fullscreen_mode.h != wind->requested_window_height) { if (window->current_fullscreen_mode.w != wind->requested_window_width || window->current_fullscreen_mode.h != wind->requested_window_height) {
@ -137,10 +148,13 @@ static void GetBufferSize(SDL_Window *window, int *width, int *height)
if (data->is_fullscreen && window->fullscreen_exclusive) { if (data->is_fullscreen && window->fullscreen_exclusive) {
buf_width = window->current_fullscreen_mode.w; buf_width = window->current_fullscreen_mode.w;
buf_height = window->current_fullscreen_mode.h; buf_height = window->current_fullscreen_mode.h;
} else { } else if (!data->scale_to_display) {
/* Round fractional backbuffer sizes halfway away from zero. */ /* Round fractional backbuffer sizes halfway away from zero. */
buf_width = (int)SDL_lroundf(data->requested_window_width * data->windowed_scale_factor); buf_width = PointToPixel(window, data->requested_window_width);
buf_height = (int)SDL_lroundf(data->requested_window_height * data->windowed_scale_factor); buf_height = PointToPixel(window, data->requested_window_height);
} else {
buf_width = data->requested_window_width;
buf_height = data->requested_window_height;
} }
if (width) { if (width) {
@ -187,10 +201,23 @@ static void SetMinMaxDimensions(SDL_Window *window)
max_width = 0; max_width = 0;
max_height = 0; max_height = 0;
} else if (window->flags & SDL_WINDOW_RESIZABLE) { } else if (window->flags & SDL_WINDOW_RESIZABLE) {
min_width = SDL_max(window->min_w, wind->system_min_required_width); int adj_w = SDL_max(window->min_w, wind->system_min_required_width);
min_height = SDL_max(window->min_h, wind->system_min_required_height); int adj_h = SDL_max(window->min_h, wind->system_min_required_height);
max_width = window->max_w ? SDL_max(window->max_w, wind->system_min_required_width) : 0; if (wind->scale_to_display) {
max_height = window->max_h ? SDL_max(window->max_h, wind->system_min_required_height) : 0; adj_w = PixelToPoint(window, adj_w);
adj_h = PixelToPoint(window, adj_h);
}
min_width = adj_w;
min_height = adj_h;
adj_w = window->max_w ? SDL_max(window->max_w, wind->system_min_required_width) : 0;
adj_h = window->max_h ? SDL_max(window->max_h, wind->system_min_required_height) : 0;
if (wind->scale_to_display) {
adj_w = PixelToPoint(window, adj_w);
adj_h = PixelToPoint(window, adj_h);
}
max_width = adj_w;
max_height = adj_h;
} else { } else {
min_width = wind->wl_window_width; min_width = wind->wl_window_width;
min_height = wind->wl_window_height; min_height = wind->wl_window_height;
@ -240,20 +267,20 @@ static void EnsurePopupPositionIsValid(SDL_Window *window, int *x, int *y)
* can result in behavior ranging from the window being spuriously closed * can result in behavior ranging from the window being spuriously closed
* to a protocol violation. * to a protocol violation.
*/ */
if (*x + window->driverdata->wl_window_width < 0) { if (*x + window->w < 0) {
*x = -window->w; *x = -window->w;
++adj_count; ++adj_count;
} }
if (*y + window->driverdata->wl_window_height < 0) { if (*y + window->h < 0) {
*y = -window->h; *y = -window->h;
++adj_count; ++adj_count;
} }
if (*x > window->parent->driverdata->wl_window_width) { if (*x > window->parent->w) {
*x = window->parent->driverdata->wl_window_width; *x = window->parent->w;
++adj_count; ++adj_count;
} }
if (*y > window->parent->driverdata->wl_window_height) { if (*y > window->parent->h) {
*y = window->parent->driverdata->wl_window_height; *y = window->parent->h;
++adj_count; ++adj_count;
} }
@ -266,19 +293,18 @@ static void EnsurePopupPositionIsValid(SDL_Window *window, int *x, int *y)
} }
} }
static void GetPopupPosition(SDL_Window *popup, int x, int y, int *adj_x, int *adj_y) static void AdjustPopupOffset(SDL_Window *popup, int *x, int *y)
{ {
/* Adjust the popup positioning, if necessary */ /* Adjust the popup positioning, if necessary */
#ifdef HAVE_LIBDECOR_H #ifdef HAVE_LIBDECOR_H
if (popup->parent->driverdata->shell_surface_type == WAYLAND_SURFACE_LIBDECOR) { if (popup->parent->driverdata->shell_surface_type == WAYLAND_SURFACE_LIBDECOR) {
int adj_x, adj_y;
libdecor_frame_translate_coordinate(popup->parent->driverdata->shell_surface.libdecor.frame, libdecor_frame_translate_coordinate(popup->parent->driverdata->shell_surface.libdecor.frame,
x, y, adj_x, adj_y); *x, *y, &adj_x, &adj_y);
} else *x = adj_x;
#endif *y = adj_y;
{
*adj_x = x;
*adj_y = y;
} }
#endif
} }
static void RepositionPopup(SDL_Window *window, SDL_bool use_current_position) static void RepositionPopup(SDL_Window *window, SDL_bool use_current_position)
@ -288,12 +314,15 @@ static void RepositionPopup(SDL_Window *window, SDL_bool use_current_position)
if (wind->shell_surface_type == WAYLAND_SURFACE_XDG_POPUP && if (wind->shell_surface_type == WAYLAND_SURFACE_XDG_POPUP &&
wind->shell_surface.xdg.roleobj.popup.positioner && wind->shell_surface.xdg.roleobj.popup.positioner &&
xdg_popup_get_version(wind->shell_surface.xdg.roleobj.popup.popup) >= XDG_POPUP_REPOSITION_SINCE_VERSION) { xdg_popup_get_version(wind->shell_surface.xdg.roleobj.popup.popup) >= XDG_POPUP_REPOSITION_SINCE_VERSION) {
int orig_x = use_current_position ? window->x : window->floating.x; int x = use_current_position ? window->x : window->floating.x;
int orig_y = use_current_position ? window->y : window->floating.y; int y = use_current_position ? window->y : window->floating.y;
int x, y;
EnsurePopupPositionIsValid(window, &orig_x, &orig_y); EnsurePopupPositionIsValid(window, &x, &y);
GetPopupPosition(window, orig_x, orig_y, &x, &y); if (wind->scale_to_display) {
x = PixelToPoint(window->parent, x);
y = PixelToPoint(window->parent, y);
}
AdjustPopupOffset(window, &x, &y);
xdg_positioner_set_anchor_rect(wind->shell_surface.xdg.roleobj.popup.positioner, 0, 0, window->parent->driverdata->wl_window_width, window->parent->driverdata->wl_window_height); xdg_positioner_set_anchor_rect(wind->shell_surface.xdg.roleobj.popup.positioner, 0, 0, window->parent->driverdata->wl_window_width, window->parent->driverdata->wl_window_height);
xdg_positioner_set_size(wind->shell_surface.xdg.roleobj.popup.positioner, wind->wl_window_width, wind->wl_window_height); xdg_positioner_set_size(wind->shell_surface.xdg.roleobj.popup.positioner, wind->wl_window_width, wind->wl_window_height);
xdg_positioner_set_offset(wind->shell_surface.xdg.roleobj.popup.positioner, x, y); xdg_positioner_set_offset(wind->shell_surface.xdg.roleobj.popup.positioner, x, y);
@ -325,11 +354,19 @@ static void ConfigureWindowGeometry(SDL_Window *window)
} }
if (data->is_fullscreen && window->fullscreen_exclusive) { if (data->is_fullscreen && window->fullscreen_exclusive) {
int output_width = data->requested_window_width; int output_width;
int output_height = data->requested_window_height; int output_height;
window_width = window->current_fullscreen_mode.w; window_width = window->current_fullscreen_mode.w;
window_height = window->current_fullscreen_mode.h; window_height = window->current_fullscreen_mode.h;
if (!data->scale_to_display) {
output_width = data->requested_window_width;
output_height = data->requested_window_height;
} else {
output_width = data->requested_logical_width;
output_height = data->requested_logical_height;
}
switch (GetModeScaleMethod()) { switch (GetModeScaleMethod()) {
case WAYLAND_MODE_SCALE_NONE: case WAYLAND_MODE_SCALE_NONE:
/* The Wayland spec states that the advertised fullscreen dimensions are a maximum. /* The Wayland spec states that the advertised fullscreen dimensions are a maximum.
@ -385,8 +422,13 @@ static void ConfigureWindowGeometry(SDL_Window *window)
data->pointer_scale_y = (float)window_height / (float)data->wl_window_height; data->pointer_scale_y = (float)window_height / (float)data->wl_window_height;
} }
} else { } else {
window_width = data->requested_window_width; if (!data->scale_to_display) {
window_height = data->requested_window_height; window_width = data->requested_window_width;
window_height = data->requested_window_height;
} else {
window_width = data->requested_logical_width;
window_height = data->requested_logical_height;
}
window_size_changed = window_width != data->wl_window_width || window_height != data->wl_window_height; window_size_changed = window_width != data->wl_window_width || window_height != data->wl_window_height;
@ -395,7 +437,7 @@ static void ConfigureWindowGeometry(SDL_Window *window)
wl_surface_set_buffer_scale(data->surface, 1); wl_surface_set_buffer_scale(data->surface, 1);
SetDrawSurfaceViewport(window, data->drawable_width, data->drawable_height, SetDrawSurfaceViewport(window, data->drawable_width, data->drawable_height,
window_width, window_height); window_width, window_height);
} else if (window->flags & SDL_WINDOW_HIGH_PIXEL_DENSITY) { } else if ((window->flags & SDL_WINDOW_HIGH_PIXEL_DENSITY) || data->scale_to_display) {
UnsetDrawSurfaceViewport(window); UnsetDrawSurfaceViewport(window);
/* Don't change this if DPI awareness flag is unset, as an application may have set this manually. */ /* Don't change this if DPI awareness flag is unset, as an application may have set this manually. */
@ -406,8 +448,13 @@ static void ConfigureWindowGeometry(SDL_Window *window)
data->wl_window_width = SDL_max(window_width, data->system_min_required_width); 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->wl_window_height = SDL_max(window_height, data->system_min_required_height);
data->pointer_scale_x = 1.0f; if (!data->scale_to_display) {
data->pointer_scale_y = 1.0f; data->pointer_scale_x = 1.0f;
data->pointer_scale_y = 1.0f;
} else {
data->pointer_scale_x = data->windowed_scale_factor;
data->pointer_scale_y = data->windowed_scale_factor;
}
} }
} }
@ -447,7 +494,11 @@ static void ConfigureWindowGeometry(SDL_Window *window)
SetMinMaxDimensions(window); SetMinMaxDimensions(window);
/* Unconditionally send the window and drawable size, the video core will deduplicate when required. */ /* 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); if (!data->scale_to_display) {
SDL_SendWindowEvent(window, SDL_EVENT_WINDOW_RESIZED, window_width, window_height);
} else {
SDL_SendWindowEvent(window, SDL_EVENT_WINDOW_RESIZED, data->drawable_width, data->drawable_height);
}
SDL_SendWindowEvent(window, SDL_EVENT_WINDOW_PIXEL_SIZE_CHANGED, data->drawable_width, data->drawable_height); SDL_SendWindowEvent(window, SDL_EVENT_WINDOW_PIXEL_SIZE_CHANGED, data->drawable_width, data->drawable_height);
/* Send an exposure event if the window is in the shown state and the size has changed, /* Send an exposure event if the window is in the shown state and the size has changed,
@ -736,18 +787,29 @@ static void handle_configure_xdg_toplevel(void *data,
*/ */
width = window->floating.w; width = window->floating.w;
height = window->floating.h; height = window->floating.h;
wind->requested_logical_width = PixelToPoint(window, width);
wind->requested_logical_height = PixelToPoint(window, height);
} else if (wind->scale_to_display) {
/* Don't convert if the size hasn't changed to avoid rounding errors. */
if (width != wind->wl_window_width || height != wind->wl_window_height) {
/* Initially assume that the compositor is telling us exactly what size the logical window size must be. */
wind->requested_logical_width = width;
wind->requested_logical_height = height;
width = PointToPixel(window, width);
height = PointToPixel(window, height);
} else {
width = wind->requested_window_width;
height = wind->requested_window_height;
}
} }
} else { } else {
/* If we're a fixed-size window, we know our size for sure. /* If we're a fixed-size window, we know our size for sure.
* Always assume the configure is wrong. * Always assume the configure is wrong.
*/ */
if (floating) { width = window->floating.w;
width = window->floating.w; height = window->floating.h;
height = window->floating.h; wind->requested_logical_width = PixelToPoint(window, width);
} else { wind->requested_logical_height = PixelToPoint(window, height);
width = window->windowed.w;
height = window->windowed.h;
}
} }
/* The content limits are only a hint, which the compositor is free to ignore, /* The content limits are only a hint, which the compositor is free to ignore,
@ -766,11 +828,20 @@ static void handle_configure_xdg_toplevel(void *data,
height = SDL_min(height, window->max_h); height = SDL_min(height, window->max_h);
} }
height = SDL_max(height, window->min_h); height = SDL_max(height, window->min_h);
wind->requested_logical_width = PixelToPoint(window, width);
wind->requested_logical_height = PixelToPoint(window, height);
} }
} else { } else {
/* Fullscreen windows know their exact size. */
if (width == 0 || height == 0) { if (width == 0 || height == 0) {
width = wind->requested_window_width; width = wind->requested_window_width;
height = wind->requested_window_height; height = wind->requested_window_height;
} else if (wind->scale_to_display) {
wind->requested_logical_width = width;
wind->requested_logical_height = height;
width = PointToPixel(window, width);
height = PointToPixel(window, height);
} }
} }
@ -829,13 +900,20 @@ static void handle_configure_xdg_popup(void *data,
int32_t height) int32_t height)
{ {
SDL_WindowData *wind = (SDL_WindowData *)data; SDL_WindowData *wind = (SDL_WindowData *)data;
int offset_x, offset_y; int offset_x = 0, offset_y = 0;
/* Adjust the position if it was offset for libdecor */ /* Adjust the position if it was offset for libdecor */
GetPopupPosition(wind->sdlwindow, 0, 0, &offset_x, &offset_y); AdjustPopupOffset(wind->sdlwindow, &offset_x, &offset_y);
x -= offset_x; x -= offset_x;
y -= offset_y; y -= offset_y;
if (wind->scale_to_display) {
x = PointToPixel(wind->sdlwindow->parent, x);
y = PointToPixel(wind->sdlwindow->parent, y);
width = PointToPixel(wind->sdlwindow, width);
height = PointToPixel(wind->sdlwindow, height);
}
wind->requested_window_width = width; wind->requested_window_width = width;
wind->requested_window_height = height; wind->requested_window_height = height;
@ -1009,16 +1087,21 @@ static void decoration_frame_configure(struct libdecor_frame *frame,
if (!libdecor_configuration_get_content_size(configuration, frame, &width, &height)) { if (!libdecor_configuration_get_content_size(configuration, frame, &width, &height)) {
width = wind->requested_window_width; width = wind->requested_window_width;
height = wind->requested_window_height; height = wind->requested_window_height;
} else if (wind->scale_to_display) {
/* Fullscreen windows know their exact size. */
wind->requested_logical_width = width;
wind->requested_logical_height = height;
width = PointToPixel(window, width);
height = PointToPixel(window, height);
} }
} else { } else {
if (!(window->flags & SDL_WINDOW_RESIZABLE)) { if (!(window->flags & SDL_WINDOW_RESIZABLE)) {
if (floating) { /* Non-resizable windows always know their exact size. */
width = window->floating.w; width = window->floating.w;
height = window->floating.h; height = window->floating.h;
} else {
width = window->windowed.w; wind->requested_logical_width = PixelToPoint(window, width);
height = window->windowed.h; wind->requested_logical_height = PixelToPoint(window, height);
}
OverrideLibdecorLimits(window); OverrideLibdecorLimits(window);
} else { } else {
@ -1048,6 +1131,20 @@ static void decoration_frame_configure(struct libdecor_frame *frame,
width = window->windowed.w; width = window->windowed.w;
height = window->windowed.h; height = window->windowed.h;
} }
wind->requested_logical_width = PixelToPoint(window, width);
wind->requested_logical_height = PixelToPoint(window, height);
} else if (wind->scale_to_display) {
/* Don't convert if the size hasn't changed to avoid rounding errors. */
if (width != wind->wl_window_width || height != wind->wl_window_height) {
wind->requested_logical_width = width;
wind->requested_logical_height = height;
width = PointToPixel(window, width);
height = PointToPixel(window, height);
} else {
width = wind->requested_window_width;
height = wind->requested_window_height;
}
} }
} }
@ -1067,6 +1164,9 @@ static void decoration_frame_configure(struct libdecor_frame *frame,
height = SDL_min(height, window->max_h); height = SDL_min(height, window->max_h);
} }
height = SDL_max(height, window->min_h); height = SDL_max(height, window->min_h);
wind->requested_logical_width = PixelToPoint(window, width);
wind->requested_logical_height = PixelToPoint(window, height);
} }
} }
@ -1141,14 +1241,30 @@ static void Wayland_HandlePreferredScaleChanged(SDL_WindowData *window_data, flo
{ {
const float old_factor = window_data->windowed_scale_factor; const float old_factor = window_data->windowed_scale_factor;
if (!(window_data->sdlwindow->flags & SDL_WINDOW_HIGH_PIXEL_DENSITY)) { if (!(window_data->sdlwindow->flags & SDL_WINDOW_HIGH_PIXEL_DENSITY) && !window_data->scale_to_display) {
/* Scale will always be 1, just ignore this */ /* Scale will always be 1, just ignore this */
return; return;
} }
if (!FloatEqual(factor, old_factor)) { if (!FloatEqual(factor, old_factor)) {
window_data->windowed_scale_factor = factor; window_data->windowed_scale_factor = factor;
if (window_data->scale_to_display) {
/* If the window is in the floating state with a user/application specified size, calculate the new
* logical size from the backbuffer size. Otherwise, use the fixed underlying logical size to calculate
* the new backbuffer dimensions.
*/
if (window_data->floating) {
window_data->requested_logical_width = PixelToPoint(window_data->sdlwindow, window_data->requested_window_width);
window_data->requested_logical_height = PixelToPoint(window_data->sdlwindow, window_data->requested_window_height);
} else {
window_data->requested_window_width = PointToPixel(window_data->sdlwindow, window_data->requested_logical_width);
window_data->requested_window_height = PointToPixel(window_data->sdlwindow, window_data->requested_logical_height);
}
}
ConfigureWindowGeometry(window_data->sdlwindow); ConfigureWindowGeometry(window_data->sdlwindow);
CommitLibdecorFrame(window_data->sdlwindow);
} }
} }
@ -1507,15 +1623,21 @@ void Wayland_ShowWindow(SDL_VideoDevice *_this, SDL_Window *window)
/* Set up the positioner for the popup and configure the constraints */ /* Set up the positioner for the popup and configure the constraints */
data->shell_surface.xdg.roleobj.popup.positioner = xdg_wm_base_create_positioner(c->shell.xdg); data->shell_surface.xdg.roleobj.popup.positioner = xdg_wm_base_create_positioner(c->shell.xdg);
xdg_positioner_set_anchor(data->shell_surface.xdg.roleobj.popup.positioner, XDG_POSITIONER_ANCHOR_TOP_LEFT); xdg_positioner_set_anchor(data->shell_surface.xdg.roleobj.popup.positioner, XDG_POSITIONER_ANCHOR_TOP_LEFT);
xdg_positioner_set_anchor_rect(data->shell_surface.xdg.roleobj.popup.positioner, 0, 0, parent->w, parent->h); xdg_positioner_set_anchor_rect(data->shell_surface.xdg.roleobj.popup.positioner, 0, 0, parent->driverdata->wl_window_width, parent->driverdata->wl_window_width);
xdg_positioner_set_constraint_adjustment(data->shell_surface.xdg.roleobj.popup.positioner, xdg_positioner_set_constraint_adjustment(data->shell_surface.xdg.roleobj.popup.positioner,
XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_SLIDE_X | XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_SLIDE_Y); XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_SLIDE_X | XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_SLIDE_Y);
xdg_positioner_set_gravity(data->shell_surface.xdg.roleobj.popup.positioner, XDG_POSITIONER_GRAVITY_BOTTOM_RIGHT); xdg_positioner_set_gravity(data->shell_surface.xdg.roleobj.popup.positioner, XDG_POSITIONER_GRAVITY_BOTTOM_RIGHT);
xdg_positioner_set_size(data->shell_surface.xdg.roleobj.popup.positioner, window->w, window->h); xdg_positioner_set_size(data->shell_surface.xdg.roleobj.popup.positioner, data->wl_window_width, data->wl_window_height);
/* Set the popup initial position */ /* Set the popup initial position */
EnsurePopupPositionIsValid(window, &window->x, &window->y); position_x = window->x;
GetPopupPosition(window, window->x, window->y, &position_x, &position_y); position_y = window->y;
EnsurePopupPositionIsValid(window, &position_x, &position_y);
if (data->scale_to_display) {
position_x = PixelToPoint(window->parent, position_x);
position_y = PixelToPoint(window->parent, position_y);
}
AdjustPopupOffset(window, &position_x, &position_y);
xdg_positioner_set_offset(data->shell_surface.xdg.roleobj.popup.positioner, position_x, position_y); xdg_positioner_set_offset(data->shell_surface.xdg.roleobj.popup.positioner, position_x, position_y);
/* Assign the popup role */ /* Assign the popup role */
@ -2056,20 +2178,32 @@ void Wayland_SetWindowKeyboardGrab(SDL_VideoDevice *_this, SDL_Window *window, S
int Wayland_CreateWindow(SDL_VideoDevice *_this, SDL_Window *window, SDL_PropertiesID create_props) int Wayland_CreateWindow(SDL_VideoDevice *_this, SDL_Window *window, SDL_PropertiesID create_props)
{ {
SDL_WindowData *data; SDL_WindowData *data;
SDL_VideoData *c; SDL_VideoData *c = _this->driverdata;
struct wl_surface *external_surface = (struct wl_surface *)SDL_GetProperty(create_props, SDL_PROP_WINDOW_CREATE_WAYLAND_WL_SURFACE_POINTER, struct wl_surface *external_surface = (struct wl_surface *)SDL_GetProperty(create_props, SDL_PROP_WINDOW_CREATE_WAYLAND_WL_SURFACE_POINTER,
(struct wl_surface *)SDL_GetProperty(create_props, "sdl2-compat.external_window", NULL)); (struct wl_surface *)SDL_GetProperty(create_props, "sdl2-compat.external_window", NULL));
const SDL_bool custom_surface_role = (external_surface != NULL) || SDL_GetBooleanProperty(create_props, SDL_PROP_WINDOW_CREATE_WAYLAND_SURFACE_ROLE_CUSTOM_BOOLEAN, SDL_FALSE); const SDL_bool custom_surface_role = (external_surface != NULL) || SDL_GetBooleanProperty(create_props, SDL_PROP_WINDOW_CREATE_WAYLAND_SURFACE_ROLE_CUSTOM_BOOLEAN, SDL_FALSE);
const SDL_bool create_egl_window = !!(window->flags & SDL_WINDOW_OPENGL) || const SDL_bool create_egl_window = !!(window->flags & SDL_WINDOW_OPENGL) ||
SDL_GetBooleanProperty(create_props, SDL_PROP_WINDOW_CREATE_WAYLAND_CREATE_EGL_WINDOW_BOOLEAN, SDL_FALSE); SDL_GetBooleanProperty(create_props, SDL_PROP_WINDOW_CREATE_WAYLAND_CREATE_EGL_WINDOW_BOOLEAN, SDL_FALSE);
SDL_bool scale_to_display = !(window->flags & SDL_WINDOW_HIGH_PIXEL_DENSITY) && !custom_surface_role &&
SDL_GetBooleanProperty(create_props, SDL_PROP_WINDOW_CREATE_WAYLAND_SCALE_TO_DISPLAY,
SDL_GetHintBoolean(SDL_HINT_VIDEO_WAYLAND_SCALE_TO_DISPLAY, SDL_FALSE));
/* Require viewports for display scaling. */
if (scale_to_display && !c->viewporter) {
SDL_LogError(SDL_LOG_CATEGORY_VIDEO, "wayland: Display scaling requires the missing 'wp_viewporter' protocol: disabling");
scale_to_display = SDL_FALSE;
}
/* Require popups to have the same scaling mode as the parent. */
if (SDL_WINDOW_IS_POPUP(window) && scale_to_display != window->parent->driverdata->scale_to_display) {
return SDL_SetError("wayland: Popup windows must use the same scaling as their parent");
}
data = SDL_calloc(1, sizeof(*data)); data = SDL_calloc(1, sizeof(*data));
if (!data) { if (!data) {
return -1; return -1;
} }
c = _this->driverdata;
window->driverdata = data; window->driverdata = data;
if (window->x == SDL_WINDOWPOS_UNDEFINED) { if (window->x == SDL_WINDOWPOS_UNDEFINED) {
@ -2079,18 +2213,17 @@ int Wayland_CreateWindow(SDL_VideoDevice *_this, SDL_Window *window, SDL_Propert
window->y = 0; window->y = 0;
} }
if (SDL_WINDOW_IS_POPUP(window)) {
EnsurePopupPositionIsValid(window, &window->x, &window->y);
}
data->waylandData = c; data->waylandData = c;
data->sdlwindow = window; data->sdlwindow = window;
data->windowed_scale_factor = 1.0f; data->windowed_scale_factor = 1.0f;
if (window->flags & SDL_WINDOW_HIGH_PIXEL_DENSITY) { if (SDL_WINDOW_IS_POPUP(window)) {
int i; data->scale_to_display = window->parent->driverdata->scale_to_display;
for (i = 0; i < _this->num_displays; i++) { data->windowed_scale_factor = window->parent->driverdata->windowed_scale_factor;
EnsurePopupPositionIsValid(window, &window->x, &window->y);
} else if ((window->flags & SDL_WINDOW_HIGH_PIXEL_DENSITY) || scale_to_display) {
for (int i = 0; i < _this->num_displays; i++) {
float scale = _this->displays[i]->driverdata->scale_factor; float scale = _this->displays[i]->driverdata->scale_factor;
data->windowed_scale_factor = SDL_max(data->windowed_scale_factor, scale); data->windowed_scale_factor = SDL_max(data->windowed_scale_factor, scale);
} }
@ -2098,12 +2231,15 @@ int Wayland_CreateWindow(SDL_VideoDevice *_this, SDL_Window *window, SDL_Propert
data->outputs = NULL; data->outputs = NULL;
data->num_outputs = 0; data->num_outputs = 0;
data->scale_to_display = scale_to_display;
/* Cache the app_id at creation time, as it may change before the window is mapped. */ /* Cache the app_id at creation time, as it may change before the window is mapped. */
data->app_id = SDL_strdup(SDL_GetAppID()); data->app_id = SDL_strdup(SDL_GetAppID());
data->requested_window_width = window->w; data->requested_window_width = window->w;
data->requested_window_height = window->h; data->requested_window_height = window->h;
data->requested_logical_width = PixelToPoint(window, window->w);
data->requested_logical_height = PixelToPoint(window, window->h);
if (!external_surface) { if (!external_surface) {
data->surface = wl_compositor_create_surface(c->compositor); data->surface = wl_compositor_create_surface(c->compositor);
@ -2168,13 +2304,13 @@ int Wayland_CreateWindow(SDL_VideoDevice *_this, SDL_Window *window, SDL_Propert
Wayland_input_lock_pointer(c->input); Wayland_input_lock_pointer(c->input);
} }
/* Don't attach a fractional scale manager to surfaces unless they are /* Don't attach a fractional scale manager to custom or external surfaces unless
* flagged as DPI-aware. Under non-scaled operation, the scale will * they are flagged as DPI-aware. Under non-scaled operation, the scale will always
* always be 1.0, and external/custom surfaces may already have, or * be 1.0, and external/custom surfaces may already have, or will try to attach,
* will try to attach, their own fractional scale manager, which will * their own fractional scale manager, which will result in a protocol violation.
* result in a protocol violation.
*/ */
if (c->fractional_scale_manager && (window->flags & SDL_WINDOW_HIGH_PIXEL_DENSITY)) { if (c->fractional_scale_manager &&
((!custom_surface_role && !external_surface) || (window->flags & SDL_WINDOW_HIGH_PIXEL_DENSITY))) {
data->fractional_scale = wp_fractional_scale_manager_v1_get_fractional_scale(c->fractional_scale_manager, data->surface); data->fractional_scale = wp_fractional_scale_manager_v1_get_fractional_scale(c->fractional_scale_manager, data->surface);
wp_fractional_scale_v1_add_listener(data->fractional_scale, wp_fractional_scale_v1_add_listener(data->fractional_scale,
&fractional_scale_listener, data); &fractional_scale_listener, data);
@ -2289,6 +2425,10 @@ static void size_event_handler(void *data, struct wl_callback *callback, uint32_
if (!(window->flags & (SDL_WINDOW_FULLSCREEN | SDL_WINDOW_MAXIMIZED))) { if (!(window->flags & (SDL_WINDOW_FULLSCREEN | SDL_WINDOW_MAXIMIZED))) {
wind->requested_window_width = wind->pending_size_event.width; wind->requested_window_width = wind->pending_size_event.width;
wind->requested_window_height = wind->pending_size_event.height; wind->requested_window_height = wind->pending_size_event.height;
if (wind->scale_to_display) {
wind->requested_logical_width = PixelToPoint(window, wind->pending_size_event.width);
wind->requested_logical_height = PixelToPoint(window, wind->pending_size_event.height);
}
ConfigureWindowGeometry(window); ConfigureWindowGeometry(window);
} }

View File

@ -115,6 +115,7 @@ struct SDL_WindowData
int last_configure_width, last_configure_height; int last_configure_width, last_configure_height;
int requested_window_width, requested_window_height; int requested_window_width, requested_window_height;
int requested_logical_width, requested_logical_height; /* Only used for screen space scaling. */
int drawable_width, drawable_height; int drawable_width, drawable_height;
int wl_window_width, wl_window_height; int wl_window_width, wl_window_height;
int system_min_required_width; int system_min_required_width;
@ -128,6 +129,7 @@ struct SDL_WindowData
SDL_bool drop_fullscreen_requests; SDL_bool drop_fullscreen_requests;
SDL_bool fullscreen_was_positioned; SDL_bool fullscreen_was_positioned;
SDL_bool show_hide_sync_required; SDL_bool show_hide_sync_required;
SDL_bool scale_to_display;
SDL_HitTestResult hit_test_result; SDL_HitTestResult hit_test_result;