From 5b8ca05f1a74a2851aaa3edbdeb435f0ce232d86 Mon Sep 17 00:00:00 2001 From: Frank Praznik Date: Fri, 19 Jan 2024 10:30:07 -0500 Subject: [PATCH] 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. --- docs/README-wayland.md | 8 + include/SDL3/SDL_hints.h | 26 +++ include/SDL3/SDL_video.h | 13 ++ src/video/wayland/SDL_waylandevents.c | 3 +- src/video/wayland/SDL_waylandvideo.c | 20 +- src/video/wayland/SDL_waylandwindow.c | 280 +++++++++++++++++++------- src/video/wayland/SDL_waylandwindow.h | 2 + 7 files changed, 279 insertions(+), 73 deletions(-) diff --git a/docs/README-wayland.md b/docs/README-wayland.md index 21fd42067..d07be3b0d 100644 --- a/docs/README-wayland.md +++ b/docs/README-wayland.md @@ -6,6 +6,14 @@ encounter limitations or behavior that is different from other windowing systems ## 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 - On some desktops (i.e. GNOME), Wayland applications use a library diff --git a/include/SDL3/SDL_hints.h b/include/SDL3/SDL_hints.h index 8570b6b15..992c446c2 100644 --- a/include/SDL3/SDL_hints.h +++ b/include/SDL3/SDL_hints.h @@ -2000,6 +2000,32 @@ extern "C" { */ #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. * diff --git a/include/SDL3/SDL_video.h b/include/SDL3/SDL_video.h index 35ae37702..fe0842897 100644 --- a/include/SDL3/SDL_video.h +++ b/include/SDL3/SDL_video.h @@ -883,6 +883,18 @@ extern DECLSPEC SDL_Window *SDLCALL SDL_CreatePopupWindow(SDL_Window *parent, in * * 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 * the application wants to use the Wayland surface for a custom role and * 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_COCOA_WINDOW_POINTER "cocoa.window" #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_CREATE_EGL_WINDOW_BOOLEAN "wayland.create_egl_window" #define SDL_PROP_WINDOW_CREATE_WAYLAND_WL_SURFACE_POINTER "wayland.wl_surface" diff --git a/src/video/wayland/SDL_waylandevents.c b/src/video/wayland/SDL_waylandevents.c index b19bfdacd..5028bc111 100644 --- a/src/video/wayland/SDL_waylandevents.c +++ b/src/video/wayland/SDL_waylandevents.c @@ -507,7 +507,8 @@ static void pointer_handle_motion(void *data, struct wl_pointer *pointer, } 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); if (rc == window_data->hit_test_result) { return; diff --git a/src/video/wayland/SDL_waylandvideo.c b/src/video/wayland/SDL_waylandvideo.c index 789e50590..2a201c911 100644 --- a/src/video/wayland/SDL_waylandvideo.c +++ b/src/video/wayland/SDL_waylandvideo.c @@ -1204,8 +1204,24 @@ static int Wayland_GetDisplayBounds(SDL_VideoDevice *_this, SDL_VideoDisplay *di rect->w = display->fullscreen_window->current_fullscreen_mode.w; rect->h = display->fullscreen_window->current_fullscreen_mode.h; } else { - rect->w = display->current_mode->w; - rect->h = display->current_mode->h; + /* If the focused window is on the requested display and requires display scaling, + * 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; } diff --git a/src/video/wayland/SDL_waylandwindow.c b/src/video/wayland/SDL_waylandwindow.c index 011ddbff8..ba7b0bb73 100644 --- a/src/video/wayland/SDL_waylandwindow.c +++ b/src/video/wayland/SDL_waylandwindow.c @@ -43,6 +43,17 @@ #include #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) { @@ -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. */ if (video->viewporter) { - if (SurfaceScaleIsFractional(window)) { + if (SurfaceScaleIsFractional(window) || wind->scale_to_display) { return SDL_TRUE; } else if (window->fullscreen_exclusive) { 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) { buf_width = window->current_fullscreen_mode.w; buf_height = window->current_fullscreen_mode.h; - } else { + } else if (!data->scale_to_display) { /* Round fractional backbuffer sizes halfway away from zero. */ - buf_width = (int)SDL_lroundf(data->requested_window_width * data->windowed_scale_factor); - buf_height = (int)SDL_lroundf(data->requested_window_height * data->windowed_scale_factor); + buf_width = PointToPixel(window, data->requested_window_width); + buf_height = PointToPixel(window, data->requested_window_height); + } else { + buf_width = data->requested_window_width; + buf_height = data->requested_window_height; } if (width) { @@ -187,10 +201,23 @@ static void SetMinMaxDimensions(SDL_Window *window) max_width = 0; max_height = 0; } else if (window->flags & SDL_WINDOW_RESIZABLE) { - min_width = SDL_max(window->min_w, wind->system_min_required_width); - min_height = 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; - max_height = window->max_h ? SDL_max(window->max_h, wind->system_min_required_height) : 0; + int adj_w = SDL_max(window->min_w, wind->system_min_required_width); + int adj_h = SDL_max(window->min_h, wind->system_min_required_height); + if (wind->scale_to_display) { + 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 { min_width = wind->wl_window_width; 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 * to a protocol violation. */ - if (*x + window->driverdata->wl_window_width < 0) { + if (*x + window->w < 0) { *x = -window->w; ++adj_count; } - if (*y + window->driverdata->wl_window_height < 0) { + if (*y + window->h < 0) { *y = -window->h; ++adj_count; } - if (*x > window->parent->driverdata->wl_window_width) { - *x = window->parent->driverdata->wl_window_width; + if (*x > window->parent->w) { + *x = window->parent->w; ++adj_count; } - if (*y > window->parent->driverdata->wl_window_height) { - *y = window->parent->driverdata->wl_window_height; + if (*y > window->parent->h) { + *y = window->parent->h; ++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 */ #ifdef HAVE_LIBDECOR_H 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, - x, y, adj_x, adj_y); - } else -#endif - { - *adj_x = x; - *adj_y = y; + *x, *y, &adj_x, &adj_y); + *x = adj_x; + *y = adj_y; } +#endif } 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 && wind->shell_surface.xdg.roleobj.popup.positioner && 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 orig_y = use_current_position ? window->y : window->floating.y; - int x, y; + int x = use_current_position ? window->x : window->floating.x; + int y = use_current_position ? window->y : window->floating.y; - EnsurePopupPositionIsValid(window, &orig_x, &orig_y); - GetPopupPosition(window, orig_x, orig_y, &x, &y); + EnsurePopupPositionIsValid(window, &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_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); @@ -325,11 +354,19 @@ static void ConfigureWindowGeometry(SDL_Window *window) } if (data->is_fullscreen && window->fullscreen_exclusive) { - int output_width = data->requested_window_width; - int output_height = data->requested_window_height; + int output_width; + int output_height; window_width = window->current_fullscreen_mode.w; 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()) { case WAYLAND_MODE_SCALE_NONE: /* 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; } } else { - window_width = data->requested_window_width; - window_height = data->requested_window_height; + if (!data->scale_to_display) { + 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; @@ -395,7 +437,7 @@ static void ConfigureWindowGeometry(SDL_Window *window) wl_surface_set_buffer_scale(data->surface, 1); SetDrawSurfaceViewport(window, data->drawable_width, data->drawable_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); /* 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_height = SDL_max(window_height, data->system_min_required_height); - data->pointer_scale_x = 1.0f; - data->pointer_scale_y = 1.0f; + if (!data->scale_to_display) { + 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); /* 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); /* 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; 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 { /* If we're a fixed-size window, we know our size for sure. * Always assume the configure is wrong. */ - if (floating) { - width = window->floating.w; - height = window->floating.h; - } else { - width = window->windowed.w; - height = window->windowed.h; - } + width = window->floating.w; + height = window->floating.h; + wind->requested_logical_width = PixelToPoint(window, width); + wind->requested_logical_height = PixelToPoint(window, height); } /* 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_max(height, window->min_h); + + wind->requested_logical_width = PixelToPoint(window, width); + wind->requested_logical_height = PixelToPoint(window, height); } } else { + /* Fullscreen windows know their exact size. */ if (width == 0 || height == 0) { width = wind->requested_window_width; 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) { 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 */ - GetPopupPosition(wind->sdlwindow, 0, 0, &offset_x, &offset_y); + AdjustPopupOffset(wind->sdlwindow, &offset_x, &offset_y); x -= offset_x; 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_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)) { width = wind->requested_window_width; 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 { if (!(window->flags & SDL_WINDOW_RESIZABLE)) { - if (floating) { - width = window->floating.w; - height = window->floating.h; - } else { - width = window->windowed.w; - height = window->windowed.h; - } + /* Non-resizable windows always know their exact size. */ + width = window->floating.w; + height = window->floating.h; + + wind->requested_logical_width = PixelToPoint(window, width); + wind->requested_logical_height = PixelToPoint(window, height); OverrideLibdecorLimits(window); } else { @@ -1048,6 +1131,20 @@ static void decoration_frame_configure(struct libdecor_frame *frame, width = window->windowed.w; 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_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; - 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 */ return; } if (!FloatEqual(factor, old_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); + 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 */ 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_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_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_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 */ - EnsurePopupPositionIsValid(window, &window->x, &window->y); - GetPopupPosition(window, window->x, window->y, &position_x, &position_y); + position_x = window->x; + 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); /* 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) { 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 *)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 create_egl_window = !!(window->flags & SDL_WINDOW_OPENGL) || 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)); if (!data) { return -1; } - c = _this->driverdata; window->driverdata = data; if (window->x == SDL_WINDOWPOS_UNDEFINED) { @@ -2079,18 +2213,17 @@ int Wayland_CreateWindow(SDL_VideoDevice *_this, SDL_Window *window, SDL_Propert window->y = 0; } - if (SDL_WINDOW_IS_POPUP(window)) { - EnsurePopupPositionIsValid(window, &window->x, &window->y); - } - data->waylandData = c; data->sdlwindow = window; data->windowed_scale_factor = 1.0f; - if (window->flags & SDL_WINDOW_HIGH_PIXEL_DENSITY) { - int i; - for (i = 0; i < _this->num_displays; i++) { + if (SDL_WINDOW_IS_POPUP(window)) { + data->scale_to_display = window->parent->driverdata->scale_to_display; + 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; 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->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. */ data->app_id = SDL_strdup(SDL_GetAppID()); data->requested_window_width = window->w; 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) { 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); } - /* Don't attach a fractional scale manager to surfaces unless they are - * flagged as DPI-aware. Under non-scaled operation, the scale will - * always be 1.0, and external/custom surfaces may already have, or - * will try to attach, their own fractional scale manager, which will - * result in a protocol violation. + /* Don't attach a fractional scale manager to custom or external surfaces unless + * they are flagged as DPI-aware. Under non-scaled operation, the scale will always + * be 1.0, and external/custom surfaces may already have, or will try to attach, + * their own fractional scale manager, which will 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); wp_fractional_scale_v1_add_listener(data->fractional_scale, &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))) { wind->requested_window_width = wind->pending_size_event.width; 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); } diff --git a/src/video/wayland/SDL_waylandwindow.h b/src/video/wayland/SDL_waylandwindow.h index 0925ac6d6..3f06fdfb5 100644 --- a/src/video/wayland/SDL_waylandwindow.h +++ b/src/video/wayland/SDL_waylandwindow.h @@ -115,6 +115,7 @@ struct SDL_WindowData int last_configure_width, last_configure_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 wl_window_width, wl_window_height; int system_min_required_width; @@ -128,6 +129,7 @@ struct SDL_WindowData SDL_bool drop_fullscreen_requests; SDL_bool fullscreen_was_positioned; SDL_bool show_hide_sync_required; + SDL_bool scale_to_display; SDL_HitTestResult hit_test_result;