diff --git a/include/SDL3/SDL_hints.h b/include/SDL3/SDL_hints.h index 671a841b64..5aa31e8d3a 100644 --- a/include/SDL3/SDL_hints.h +++ b/include/SDL3/SDL_hints.h @@ -3093,6 +3093,7 @@ extern "C" { /** * A variable forcing non-DPI-aware Wayland windows to output at 1:1 scaling. + * This must be set before initializing the video subsystem. * * 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. @@ -3102,12 +3103,12 @@ extern "C" { * 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. + * - Rounding errors can result with odd window sizes and/or desktop scales, + * which can cause the window contents to appear slightly blurry. + * - The window may be unusably small on scaled desktops. + * - The window may jump in size when moving between displays of different scale factors. + * - Displays may appear to overlap when using a multi-monitor setup with scaling enabled. + * - Possible loss of cursor precision due to the logical size of the window being reduced. * * New applications should be designed with proper DPI awareness handling * instead of enabling this. diff --git a/include/SDL3/SDL_video.h b/include/SDL3/SDL_video.h index 485544e698..e84717bc21 100644 --- a/include/SDL3/SDL_video.h +++ b/include/SDL3/SDL_video.h @@ -958,18 +958,6 @@ 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_BOOLEAN` - 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 @@ -1038,7 +1026,6 @@ 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_BOOLEAN "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/SDL_sysvideo.h b/src/video/SDL_sysvideo.h index 065d11b8ea..bfc85a5105 100644 --- a/src/video/SDL_sysvideo.h +++ b/src/video/SDL_sysvideo.h @@ -157,7 +157,8 @@ typedef enum VIDEO_DEVICE_CAPS_MODE_SWITCHING_EMULATED = 0x01, VIDEO_DEVICE_CAPS_HAS_POPUP_WINDOW_SUPPORT = 0x02, VIDEO_DEVICE_CAPS_SENDS_FULLSCREEN_DIMENSIONS = 0x04, - VIDEO_DEVICE_CAPS_FULLSCREEN_ONLY = 0x08 + VIDEO_DEVICE_CAPS_FULLSCREEN_ONLY = 0x08, + VIDEO_DEVICE_CAPS_SENDS_DISPLAY_CHANGES = 0x10 } DeviceCaps; struct SDL_VideoDevice diff --git a/src/video/SDL_video.c b/src/video/SDL_video.c index cf752dbf1f..fdff61e285 100644 --- a/src/video/SDL_video.c +++ b/src/video/SDL_video.c @@ -185,6 +185,11 @@ static SDL_bool IsFullscreenOnly(SDL_VideoDevice *_this) return !!(_this->device_caps & VIDEO_DEVICE_CAPS_FULLSCREEN_ONLY); } +static SDL_bool SDL_SendsDisplayChanges(SDL_VideoDevice *_this) +{ + return !!(_this->device_caps & VIDEO_DEVICE_CAPS_SENDS_DISPLAY_CHANGES); +} + /* Hint to treat all window ops as synchronous */ static SDL_bool syncHint; @@ -1545,6 +1550,10 @@ SDL_DisplayID SDL_GetDisplayForWindow(SDL_Window *window) static void SDL_CheckWindowDisplayChanged(SDL_Window *window) { + if (SDL_SendsDisplayChanges(_this)) { + return; + } + SDL_DisplayID displayID = SDL_GetDisplayForWindowPosition(window); if (displayID != window->last_displayID) { diff --git a/src/video/wayland/SDL_waylandvideo.c b/src/video/wayland/SDL_waylandvideo.c index 173fc7f7c9..f8db42975c 100644 --- a/src/video/wayland/SDL_waylandvideo.c +++ b/src/video/wayland/SDL_waylandvideo.c @@ -423,6 +423,7 @@ static SDL_VideoDevice *Wayland_CreateDevice(void) data->display = display; data->input = input; data->display_externally_owned = display_is_external; + data->scale_to_display_enabled = SDL_GetHintBoolean(SDL_HINT_VIDEO_WAYLAND_SCALE_TO_DISPLAY, SDL_FALSE); WAYLAND_wl_list_init(&data->output_list); WAYLAND_wl_list_init(&data->output_order); WAYLAND_wl_list_init(&external_window_list); @@ -488,6 +489,7 @@ static SDL_VideoDevice *Wayland_CreateDevice(void) device->SetWindowModalFor = Wayland_SetWindowModalFor; device->SetWindowTitle = Wayland_SetWindowTitle; device->GetWindowSizeInPixels = Wayland_GetWindowSizeInPixels; + device->GetDisplayForWindow = Wayland_GetDisplayForWindow; device->DestroyWindow = Wayland_DestroyWindow; device->SetWindowHitTest = Wayland_SetWindowHitTest; device->FlashWindow = Wayland_FlashWindow; @@ -519,7 +521,8 @@ static SDL_VideoDevice *Wayland_CreateDevice(void) device->device_caps = VIDEO_DEVICE_CAPS_MODE_SWITCHING_EMULATED | VIDEO_DEVICE_CAPS_HAS_POPUP_WINDOW_SUPPORT | - VIDEO_DEVICE_CAPS_SENDS_FULLSCREEN_DIMENSIONS; + VIDEO_DEVICE_CAPS_SENDS_FULLSCREEN_DIMENSIONS | + VIDEO_DEVICE_CAPS_SENDS_DISPLAY_CHANGES; return device; } @@ -831,9 +834,16 @@ static void display_handle_done(void *data, SDL_zero(desktop_mode); desktop_mode.format = SDL_PIXELFORMAT_XRGB8888; - desktop_mode.w = driverdata->screen_width; - desktop_mode.h = driverdata->screen_height; - desktop_mode.pixel_density = driverdata->scale_factor; + if (!video->scale_to_display_enabled) { + desktop_mode.w = driverdata->screen_width; + desktop_mode.h = driverdata->screen_height; + desktop_mode.pixel_density = driverdata->scale_factor; + } else { + desktop_mode.w = native_mode.w; + desktop_mode.h = native_mode.h; + desktop_mode.pixel_density = 1.0f; + } + desktop_mode.refresh_rate = ((100 * driverdata->refresh) / 1000) / 100.0f; /* mHz to Hz */ if (driverdata->display > 0) { @@ -842,6 +852,10 @@ static void display_handle_done(void *data, dpy = &driverdata->placeholder; } + if (video->scale_to_display_enabled) { + SDL_SetDisplayContentScale(dpy, driverdata->scale_factor); + } + /* Set the desktop display mode. */ SDL_SetDesktopDisplayMode(dpy, &desktop_mode); @@ -1176,6 +1190,12 @@ int Wayland_VideoInit(SDL_VideoDevice *_this) // First roundtrip to receive all registry objects. WAYLAND_wl_display_roundtrip(data->display); + // Require viewports for display scaling. + if (data->scale_to_display_enabled && !data->viewporter) { + SDL_LogError(SDL_LOG_CATEGORY_VIDEO, "wayland: Display scaling requires the missing 'wp_viewporter' protocol: disabling"); + data->scale_to_display_enabled = SDL_FALSE; + } + /* Now that we have all the protocols, load libdecor if applicable */ Wayland_LoadLibdecor(data, SDL_FALSE); @@ -1203,6 +1223,7 @@ int Wayland_VideoInit(SDL_VideoDevice *_this) static int Wayland_GetDisplayBounds(SDL_VideoDevice *_this, SDL_VideoDisplay *display, SDL_Rect *rect) { + SDL_VideoData *viddata = _this->driverdata; SDL_DisplayData *driverdata = display->driverdata; rect->x = driverdata->x; rect->y = driverdata->y; @@ -1216,15 +1237,7 @@ 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 { - /* 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) { + if (!viddata->scale_to_display_enabled) { rect->w = display->current_mode->w; rect->h = display->current_mode->h; } else if (driverdata->transform & WL_OUTPUT_TRANSFORM_90) { diff --git a/src/video/wayland/SDL_waylandvideo.h b/src/video/wayland/SDL_waylandvideo.h index 7228636edd..abf33d614b 100644 --- a/src/video/wayland/SDL_waylandvideo.h +++ b/src/video/wayland/SDL_waylandvideo.h @@ -93,6 +93,8 @@ struct SDL_VideoData int relative_mouse_mode; SDL_bool display_externally_owned; + + SDL_bool scale_to_display_enabled; }; struct SDL_DisplayData diff --git a/src/video/wayland/SDL_waylandwindow.c b/src/video/wayland/SDL_waylandwindow.c index bfbde9e926..4bab51b3b7 100644 --- a/src/video/wayland/SDL_waylandwindow.c +++ b/src/video/wayland/SDL_waylandwindow.c @@ -538,6 +538,7 @@ static void Wayland_move_window(SDL_Window *window) */ FlushFullscreenEvents(window); SDL_SendWindowEvent(window, SDL_EVENT_WINDOW_MOVED, display->x, display->y); + SDL_SendWindowEvent(window, SDL_EVENT_WINDOW_DISPLAY_CHANGED, wind->last_displayID, 0); } } break; @@ -2212,20 +2213,6 @@ int Wayland_CreateWindow(SDL_VideoDevice *_this, SDL_Window *window, SDL_Propert 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_BOOLEAN, - 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) { @@ -2250,7 +2237,7 @@ int Wayland_CreateWindow(SDL_VideoDevice *_this, SDL_Window *window, SDL_Propert 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) { + } else if ((window->flags & SDL_WINDOW_HIGH_PIXEL_DENSITY) || c->scale_to_display_enabled) { 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); @@ -2259,7 +2246,7 @@ 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; + data->scale_to_display = c->scale_to_display_enabled; /* Cache the app_id at creation time, as it may change before the window is mapped. */ data->app_id = SDL_strdup(SDL_GetAppID()); @@ -2509,6 +2496,17 @@ void Wayland_GetWindowSizeInPixels(SDL_VideoDevice *_this, SDL_Window *window, i *h = data->current.drawable_height; } +SDL_DisplayID Wayland_GetDisplayForWindow(SDL_VideoDevice *_this, SDL_Window *window) +{ + SDL_WindowData *wind = window->driverdata; + + if (wind) { + return wind->last_displayID; + } + + return 0; +} + void Wayland_SetWindowTitle(SDL_VideoDevice *_this, SDL_Window *window) { SDL_WindowData *wind = window->driverdata; diff --git a/src/video/wayland/SDL_waylandwindow.h b/src/video/wayland/SDL_waylandwindow.h index 24245cff55..b490aa206d 100644 --- a/src/video/wayland/SDL_waylandwindow.h +++ b/src/video/wayland/SDL_waylandwindow.h @@ -200,6 +200,7 @@ extern void Wayland_SetWindowSize(SDL_VideoDevice *_this, SDL_Window *window); extern void Wayland_SetWindowMinimumSize(SDL_VideoDevice *_this, SDL_Window *window); extern void Wayland_SetWindowMaximumSize(SDL_VideoDevice *_this, SDL_Window *window); extern void Wayland_GetWindowSizeInPixels(SDL_VideoDevice *_this, SDL_Window *window, int *w, int *h); +extern SDL_DisplayID Wayland_GetDisplayForWindow(SDL_VideoDevice *_this, SDL_Window *window); extern int Wayland_SetWindowModalFor(SDL_VideoDevice *_this, SDL_Window *modal_window, SDL_Window *parent_window); extern void Wayland_SetWindowTitle(SDL_VideoDevice *_this, SDL_Window *window); extern void Wayland_ShowWindowSystemMenu(SDL_Window *window, int x, int y); diff --git a/test/testdisplayinfo.c b/test/testdisplayinfo.c index 0e45b54a80..81c6f709fd 100644 --- a/test/testdisplayinfo.c +++ b/test/testdisplayinfo.c @@ -71,7 +71,7 @@ int main(int argc, char *argv[]) SDL_GetDisplayBounds(dpy, &rect); modes = SDL_GetFullscreenDisplayModes(dpy, &num_modes); - SDL_Log("%" SDL_PRIu32 ": \"%s\" (%dx%d at %d,%d), content scale %.1f, %d fullscreen modes.\n", dpy, SDL_GetDisplayName(dpy), rect.w, rect.h, rect.x, rect.y, SDL_GetDisplayContentScale(dpy), num_modes); + SDL_Log("%" SDL_PRIu32 ": \"%s\" (%dx%d at %d,%d), content scale %.2f, %d fullscreen modes.\n", dpy, SDL_GetDisplayName(dpy), rect.w, rect.h, rect.x, rect.y, SDL_GetDisplayContentScale(dpy), num_modes); mode = SDL_GetCurrentDisplayMode(dpy); if (mode) {