From 99a44990bac1ac45b15303e8a73fb2696347a8c8 Mon Sep 17 00:00:00 2001 From: akallabeth Date: Fri, 30 Jan 2026 11:08:20 +0100 Subject: [PATCH 1/5] [client,sdl] rename SdlContext::removeDisplay To match with SdlContext::addDisplayWindow rename it to SdlContext::removeDisplayWindow --- client/SDL/SDL3/sdl_context.cpp | 2 +- client/SDL/SDL3/sdl_context.hpp | 4 ++-- client/SDL/SDL3/sdl_disp.cpp | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/client/SDL/SDL3/sdl_context.cpp b/client/SDL/SDL3/sdl_context.cpp index 3f2328c46..56240c8ac 100644 --- a/client/SDL/SDL3/sdl_context.cpp +++ b/client/SDL/SDL3/sdl_context.cpp @@ -878,7 +878,7 @@ bool SdlContext::addDisplayWindow(SDL_DisplayID id) return true; } -bool SdlContext::removeDisplay(SDL_DisplayID id) +bool SdlContext::removeDisplayWindow(SDL_DisplayID id) { for (auto& w : _windows) { diff --git a/client/SDL/SDL3/sdl_context.hpp b/client/SDL/SDL3/sdl_context.hpp index 2110c4e30..393feee2c 100644 --- a/client/SDL/SDL3/sdl_context.hpp +++ b/client/SDL/SDL3/sdl_context.hpp @@ -120,7 +120,7 @@ class SdlContext [[nodiscard]] SdlWindow* getFirstWindow(); [[nodiscard]] bool addDisplayWindow(SDL_DisplayID id); - [[nodiscard]] bool removeDisplay(SDL_DisplayID id); + [[nodiscard]] bool removeDisplayWindow(SDL_DisplayID id); [[nodiscard]] sdlDispContext& getDisplayChannelContext(); [[nodiscard]] sdlInput& getInputChannelContext(); @@ -206,7 +206,7 @@ class SdlContext SdlConnectionDialogWrapper _dialog; - std::map _windows; + std::map _windows; uint32_t _windowWidth = 0; uint32_t _windowHeigth = 0; diff --git a/client/SDL/SDL3/sdl_disp.cpp b/client/SDL/SDL3/sdl_disp.cpp index 4478f070d..02ff07a45 100644 --- a/client/SDL/SDL3/sdl_disp.cpp +++ b/client/SDL/SDL3/sdl_disp.cpp @@ -308,7 +308,7 @@ bool sdlDispContext::updateMonitors(SDL_EventType type, SDL_DisplayID displayID) return false; break; case SDL_EVENT_DISPLAY_REMOVED: - if (!_sdl->removeDisplay(displayID)) + if (!_sdl->removeDisplayWindow(displayID)) return false; break; default: From efa15e1dc2a18b2e1441886eb0683a1854de38d8 Mon Sep 17 00:00:00 2001 From: akallabeth Date: Thu, 29 Jan 2026 19:51:04 +0100 Subject: [PATCH 2/5] [client,sdl] implement a static SdlWindow::query function The display settings can not be proplerly queried by SDL without a window. Create a temporary, invisible window for a requested monitor and query the required details from that window. --- client/SDL/SDL3/sdl_monitor.cpp | 57 +++-------- client/SDL/SDL3/sdl_window.cpp | 170 +++++++++++++++++++++++--------- client/SDL/SDL3/sdl_window.hpp | 7 ++ 3 files changed, 140 insertions(+), 94 deletions(-) diff --git a/client/SDL/SDL3/sdl_monitor.cpp b/client/SDL/SDL3/sdl_monitor.cpp index 9b8b78031..7fbab74e1 100644 --- a/client/SDL/SDL3/sdl_monitor.cpp +++ b/client/SDL/SDL3/sdl_monitor.cpp @@ -21,6 +21,7 @@ #include +#include #include #include #include @@ -181,10 +182,12 @@ int sdl_list_monitors([[maybe_unused]] SdlContext* sdl) [[nodiscard]] static BOOL sdl_apply_monitor_properties(rdpMonitor& monitor, SDL_DisplayID id, bool isPrimary) { + auto mode = SDL_GetCurrentDisplayMode(id); + if (!mode) + return FALSE; - float dpi = SDL_GetDisplayContentScale(id); - float hdpi = dpi; - float vdpi = dpi; + const float dpi = roundf(mode->pixel_density * 100.0f); + const float factor = mode->pixel_density; SDL_Rect rect = {}; if (!SDL_GetDisplayBounds(id, &rect)) @@ -195,58 +198,20 @@ int sdl_list_monitors([[maybe_unused]] SdlContext* sdl) bool highDpi = dpi > 100; - if (highDpi) - { - // HighDPI is problematic with SDL: We can only get native resolution by creating a - // window. Work around this by checking the supported resolutions (and keep maximum) - // Also scale the DPI - const SDL_Rect scaleRect = rect; - int count = 0; - auto modes = SDL_GetFullscreenDisplayModes(id, &count); - for (int i = 0; i < count; i++) - { - auto mode = modes[i]; - if (!mode) - break; - - if (mode->w > rect.w) - { - rect.w = mode->w; - rect.h = mode->h; - } - else if (mode->w == rect.w) - { - if (mode->h > rect.h) - { - rect.w = mode->w; - rect.h = mode->h; - } - } - } - SDL_free(static_cast(modes)); - - const float dw = 1.0f * static_cast(rect.w) / static_cast(scaleRect.w); - const float dh = 1.0f * static_cast(rect.h) / static_cast(scaleRect.h); - hdpi /= dw; - vdpi /= dh; - } - const SDL_DisplayOrientation orientation = SDL_GetCurrentDisplayOrientation(id); const UINT32 rdp_orientation = sdl::utils::orientaion_to_rdp(orientation); - /* windows uses 96 dpi as 'default' and the scale factors are in percent. */ - const auto factor = dpi / 96.0f * 100.0f; monitor.orig_screen = id; monitor.x = rect.x; monitor.y = rect.y; - monitor.width = rect.w; - monitor.height = rect.h; + monitor.width = roundf(rect.w * factor); + monitor.height = roundf(rect.h * factor); monitor.is_primary = isPrimary; - monitor.attributes.desktopScaleFactor = static_cast(factor); + monitor.attributes.desktopScaleFactor = static_cast(dpi); monitor.attributes.deviceScaleFactor = 100; monitor.attributes.orientation = rdp_orientation; - monitor.attributes.physicalWidth = scale(WINPR_ASSERTING_INT_CAST(uint32_t, rect.w), hdpi); - monitor.attributes.physicalHeight = scale(WINPR_ASSERTING_INT_CAST(uint32_t, rect.h), vdpi); + monitor.attributes.physicalWidth = WINPR_ASSERTING_INT_CAST(uint32_t, rect.w); + monitor.attributes.physicalHeight = WINPR_ASSERTING_INT_CAST(uint32_t, rect.h); return TRUE; } diff --git a/client/SDL/SDL3/sdl_window.cpp b/client/SDL/SDL3/sdl_window.cpp index 0bed3e3fe..2f1e09ef3 100644 --- a/client/SDL/SDL3/sdl_window.cpp +++ b/client/SDL/SDL3/sdl_window.cpp @@ -19,10 +19,13 @@ */ #include #include +#include #include "sdl_window.hpp" #include "sdl_utils.hpp" +#include + SdlWindow::SdlWindow(SDL_DisplayID id, const std::string& title, const SDL_Rect& rect, [[maybe_unused]] Uint32 flags) : _displayID(id) @@ -83,13 +86,7 @@ SDL_DisplayID SdlWindow::displayIndex() const SDL_Rect SdlWindow::rect() const { - SDL_Rect rect = {}; - if (_window) - { - SDL_GetWindowPosition(_window, &rect.x, &rect.y); - SDL_GetWindowSizeInPixels(_window, &rect.w, &rect.h); - } - return rect; + return rect(_window); } SDL_Rect SdlWindow::bounds() const @@ -97,8 +94,10 @@ SDL_Rect SdlWindow::bounds() const SDL_Rect rect = {}; if (_window) { - SDL_GetWindowPosition(_window, &rect.x, &rect.y); - SDL_GetWindowSize(_window, &rect.w, &rect.h); + if (!SDL_GetWindowPosition(_window, &rect.x, &rect.y)) + return {}; + if (!SDL_GetWindowSize(_window, &rect.w, &rect.h)) + return {}; } return rect; } @@ -130,42 +129,7 @@ Sint32 SdlWindow::offsetY() const rdpMonitor SdlWindow::monitor(bool isPrimary) const { - rdpMonitor mon{}; - - const auto factor = scale(); - const auto dsf = static_cast(100 * factor); - mon.attributes.desktopScaleFactor = dsf; - mon.attributes.deviceScaleFactor = 100; - - const auto r = rect(); - mon.width = r.w; - mon.height = r.h; - - mon.attributes.physicalWidth = WINPR_ASSERTING_INT_CAST(uint32_t, r.w); - mon.attributes.physicalHeight = WINPR_ASSERTING_INT_CAST(uint32_t, r.h); - - SDL_Rect rect = {}; - auto did = SDL_GetDisplayForWindow(_window); - auto rc = SDL_GetDisplayBounds(did, &rect); - - if (rc) - { - mon.x = rect.x; - mon.y = rect.y; - } - - const auto orient = orientation(); - mon.attributes.orientation = sdl::utils::orientaion_to_rdp(orient); - - auto primary = SDL_GetPrimaryDisplay(); - mon.is_primary = isPrimary || (SDL_GetWindowID(_window) == primary); - mon.orig_screen = did; - if (mon.is_primary) - { - mon.x = 0; - mon.y = 0; - } - return mon; + return query(_window, displayIndex(), isPrimary); } float SdlWindow::scale() const @@ -291,14 +255,79 @@ bool SdlWindow::drawScaledRects(SDL_Surface* surface, const SDL_FPoint& scale, bool SdlWindow::fill(Uint8 r, Uint8 g, Uint8 b, Uint8 a) { - auto surface = SDL_GetWindowSurface(_window); + return fill(_window, r, g, b, a); +} + +bool SdlWindow::fill(SDL_Window* window, Uint8 r, Uint8 g, Uint8 b, Uint8 a) +{ + auto surface = SDL_GetWindowSurface(window); if (!surface) return false; SDL_Rect rect = { 0, 0, surface->w, surface->h }; auto color = SDL_MapSurfaceRGBA(surface, r, g, b, a); - SDL_FillSurfaceRect(surface, &rect, color); - return true; + return SDL_FillSurfaceRect(surface, &rect, color); +} + +rdpMonitor SdlWindow::query(SDL_Window* window, SDL_DisplayID id, bool forceAsPrimary) +{ + if (!window) + return {}; + + const auto& r = rect(window); + const float factor = SDL_GetWindowDisplayScale(window); + const float dpi = std::roundf(factor * 100.0f); + + WINPR_ASSERT(r.w > 0); + WINPR_ASSERT(r.h > 0); + + bool highDpi = dpi > 100; + + const auto primary = SDL_GetPrimaryDisplay(); + const auto orientation = SDL_GetCurrentDisplayOrientation(id); + const auto rdp_orientation = sdl::utils::orientaion_to_rdp(orientation); + + rdpMonitor monitor{}; + monitor.orig_screen = id; + monitor.x = forceAsPrimary ? 0 : r.x; + monitor.y = forceAsPrimary ? 0 : r.y; + monitor.width = r.w; + monitor.height = r.h; + monitor.is_primary = forceAsPrimary || (id == primary); + monitor.attributes.desktopScaleFactor = static_cast(dpi); + monitor.attributes.deviceScaleFactor = 100; + monitor.attributes.orientation = rdp_orientation; + monitor.attributes.physicalWidth = WINPR_ASSERTING_INT_CAST(uint32_t, r.w); + monitor.attributes.physicalHeight = WINPR_ASSERTING_INT_CAST(uint32_t, r.h); + + SDL_Log("monitor.orig_screen %" PRIu32, monitor.orig_screen); + SDL_Log("monitor.x %" PRId32, monitor.x); + SDL_Log("monitor.y %" PRId32, monitor.y); + SDL_Log("monitor.width %" PRId32, monitor.width); + SDL_Log("monitor.height %" PRId32, monitor.height); + SDL_Log("monitor.is_primary %" PRIu32, monitor.is_primary); + SDL_Log("monitor.attributes.desktopScaleFactor %" PRIu32, + monitor.attributes.desktopScaleFactor); + SDL_Log("monitor.attributes.deviceScaleFactor %" PRIu32, monitor.attributes.deviceScaleFactor); + SDL_Log("monitor.attributes.orientation %s", + freerdp_desktop_rotation_flags_to_string(monitor.attributes.orientation)); + SDL_Log("monitor.attributes.physicalWidth %" PRIu32, monitor.attributes.physicalWidth); + SDL_Log("monitor.attributes.physicalHeight %" PRIu32, monitor.attributes.physicalHeight); + return monitor; +} + +SDL_Rect SdlWindow::rect(SDL_Window* window) +{ + SDL_Rect rect = {}; + if (!window) + return {}; + + if (!SDL_GetWindowPosition(window, &rect.x, &rect.y)) + return {}; + if (!SDL_GetWindowSizeInPixels(window, &rect.w, &rect.h)) + return {}; + + return rect; } bool SdlWindow::blit(SDL_Surface* surface, const SDL_Rect& srcRect, SDL_Rect& dstRect) @@ -347,3 +376,48 @@ SdlWindow SdlWindow::create(SDL_DisplayID id, const std::string& title, Uint32 f return window; } + +static SDL_Window* createDummy(SDL_DisplayID id) +{ + const auto x = SDL_WINDOWPOS_CENTERED_DISPLAY(id); + const auto y = SDL_WINDOWPOS_CENTERED_DISPLAY(id); + const int w = 64; + const int h = 64; + + auto props = SDL_CreateProperties(); + std::stringstream ss; + ss << "SdlWindow::query(" << id << ")"; + SDL_SetStringProperty(props, SDL_PROP_WINDOW_CREATE_TITLE_STRING, ss.str().c_str()); + SDL_SetNumberProperty(props, SDL_PROP_WINDOW_CREATE_X_NUMBER, x); + SDL_SetNumberProperty(props, SDL_PROP_WINDOW_CREATE_Y_NUMBER, y); + SDL_SetNumberProperty(props, SDL_PROP_WINDOW_CREATE_WIDTH_NUMBER, w); + SDL_SetNumberProperty(props, SDL_PROP_WINDOW_CREATE_HEIGHT_NUMBER, h); + + SDL_SetBooleanProperty(props, SDL_PROP_WINDOW_CREATE_HIGH_PIXEL_DENSITY_BOOLEAN, true); + SDL_SetBooleanProperty(props, SDL_PROP_WINDOW_CREATE_FULLSCREEN_BOOLEAN, true); + SDL_SetBooleanProperty(props, SDL_PROP_WINDOW_CREATE_BORDERLESS_BOOLEAN, true); + SDL_SetBooleanProperty(props, SDL_PROP_WINDOW_CREATE_HIDDEN_BOOLEAN, false); + + auto window = SDL_CreateWindowWithProperties(props); + SDL_DestroyProperties(props); + return window; +} + +rdpMonitor SdlWindow::query(SDL_DisplayID id, bool forceAsPrimary) +{ + std::unique_ptr window(createDummy(id), SDL_DestroyWindow); + if (!window) + return {}; + + std::unique_ptr renderer( + SDL_CreateRenderer(window.get(), nullptr), SDL_DestroyRenderer); + + if (!SDL_SyncWindow(window.get())) + return {}; + + SDL_Event event{}; + while (SDL_PollEvent(&event)) + ; + + return query(window.get(), id, forceAsPrimary); +} diff --git a/client/SDL/SDL3/sdl_window.hpp b/client/SDL/SDL3/sdl_window.hpp index 303e2a9b3..9a69d4618 100644 --- a/client/SDL/SDL3/sdl_window.hpp +++ b/client/SDL/SDL3/sdl_window.hpp @@ -31,6 +31,7 @@ class SdlWindow public: [[nodiscard]] static SdlWindow create(SDL_DisplayID id, const std::string& title, Uint32 flags, Uint32 width = 0, Uint32 height = 0); + [[nodiscard]] static rdpMonitor query(SDL_DisplayID id, bool forceAsPrimary = false); SdlWindow(SdlWindow&& other) noexcept; SdlWindow(const SdlWindow& other) = delete; @@ -82,6 +83,12 @@ class SdlWindow protected: SdlWindow(SDL_DisplayID id, const std::string& title, const SDL_Rect& rect, Uint32 flags); + [[nodiscard]] static bool fill(SDL_Window* window, Uint8 r = 0x00, Uint8 g = 0x00, + Uint8 b = 0x00, Uint8 a = 0xff); + [[nodiscard]] static rdpMonitor query(SDL_Window* window, SDL_DisplayID id, + bool forceAsPrimary = false); + [[nodiscard]] static SDL_Rect rect(SDL_Window* window); + private: SDL_Window* _window = nullptr; SDL_DisplayID _displayID = 0; From e2dd2eedab581f688f555a1078bb30278487dc26 Mon Sep 17 00:00:00 2001 From: akallabeth Date: Fri, 30 Jan 2026 11:02:07 +0100 Subject: [PATCH 3/5] [client,sdl] implement display detection handling * SdlContext::detectDisplays to create a initial list of displays and their rdpMonitor configuration * SdlContext::getDisplays to query a SDL_DisplayID at runtime * SdlContext::getDisplayIds to query available SDL_DisplayID at runtime --- client/SDL/SDL3/sdl_context.cpp | 41 +++++++++++++++++++++++++++++++++ client/SDL/SDL3/sdl_context.hpp | 7 ++++++ 2 files changed, 48 insertions(+) diff --git a/client/SDL/SDL3/sdl_context.cpp b/client/SDL/SDL3/sdl_context.cpp index 56240c8ac..0a9d65921 100644 --- a/client/SDL/SDL3/sdl_context.cpp +++ b/client/SDL/SDL3/sdl_context.cpp @@ -888,6 +888,37 @@ bool SdlContext::removeDisplayWindow(SDL_DisplayID id) return true; } +bool SdlContext::detectDisplays() +{ + int count = 0; + auto display = SDL_GetDisplays(&count); + if (!display) + return false; + for (int x = 0; x < count; x++) + { + const auto id = display[x]; + auto monitor = SdlWindow::query(id, false); + addOrUpdateDisplay(id, monitor); + } + + return true; +} + +rdpMonitor SdlContext::getDisplay(SDL_DisplayID id) const +{ + return _displays.at(id); +} + +std::vector SdlContext::getDisplayIds() const +{ + std::vector keys; + for (const auto& entry : _displays) + { + keys.push_back(entry.first); + } + return keys; +} + const SdlWindow* SdlContext::getWindowForId(SDL_WindowID id) const { auto it = _windows.find(id); @@ -1091,6 +1122,16 @@ bool SdlContext::handleEvent(const SDL_TouchFingerEvent& ev) return SdlTouch::handleEvent(this, copy.tfinger); } +void SdlContext::addOrUpdateDisplay(SDL_DisplayID id, const rdpMonitor& monitor) +{ + _displays.emplace(id, monitor); +} + +void SdlContext::deleteDisplay(SDL_DisplayID id) +{ + _displays.erase(id); +} + bool SdlContext::eventToPixelCoordinates(SDL_WindowID id, SDL_Event& ev) { auto w = getWindowForId(id); diff --git a/client/SDL/SDL3/sdl_context.hpp b/client/SDL/SDL3/sdl_context.hpp index 393feee2c..b225f19ef 100644 --- a/client/SDL/SDL3/sdl_context.hpp +++ b/client/SDL/SDL3/sdl_context.hpp @@ -121,6 +121,9 @@ class SdlContext [[nodiscard]] bool addDisplayWindow(SDL_DisplayID id); [[nodiscard]] bool removeDisplayWindow(SDL_DisplayID id); + [[nodiscard]] bool detectDisplays(); + [[nodiscard]] rdpMonitor getDisplay(SDL_DisplayID id) const; + [[nodiscard]] std::vector getDisplayIds() const; [[nodiscard]] sdlDispContext& getDisplayChannelContext(); [[nodiscard]] sdlInput& getInputChannelContext(); @@ -162,6 +165,9 @@ class SdlContext [[nodiscard]] bool handleEvent(const SDL_MouseWheelEvent& ev); [[nodiscard]] bool handleEvent(const SDL_TouchFingerEvent& ev); + void addOrUpdateDisplay(SDL_DisplayID id, const rdpMonitor& monitor); + void deleteDisplay(SDL_DisplayID id); + [[nodiscard]] bool createPrimary(); [[nodiscard]] std::string windowTitle() const; [[nodiscard]] bool waitForWindowsCreated(); @@ -206,6 +212,7 @@ class SdlContext SdlConnectionDialogWrapper _dialog; + std::map _displays; std::map _windows; uint32_t _windowWidth = 0; From 3cb88eefd644da1bb00480b44c0b3aa9d5e61473 Mon Sep 17 00:00:00 2001 From: akallabeth Date: Fri, 30 Jan 2026 11:03:40 +0100 Subject: [PATCH 4/5] [client,sdl] detect displays on startup before RDP do a simple display query on main SDL thread before starting RDP. This way the required rdpMonitor details are already available when the monitor layout or desktop resolution must be sent to the server --- client/SDL/SDL3/sdl_freerdp.cpp | 16 +++++---- client/SDL/SDL3/sdl_monitor.cpp | 60 ++++----------------------------- 2 files changed, 15 insertions(+), 61 deletions(-) diff --git a/client/SDL/SDL3/sdl_freerdp.cpp b/client/SDL/SDL3/sdl_freerdp.cpp index ef761324a..5bab3abd7 100644 --- a/client/SDL/SDL3/sdl_freerdp.cpp +++ b/client/SDL/SDL3/sdl_freerdp.cpp @@ -605,6 +605,13 @@ int main(int argc, char* argv[]) if (!SDL_Init(SDL_INIT_VIDEO | SDL_INIT_EVENTS)) return -1; + SDL_SetHint(SDL_HINT_ALLOW_ALT_TAB_WHILE_GRABBED, "0"); + SDL_SetHint(SDL_HINT_VIDEO_X11_NET_WM_BYPASS_COMPOSITOR, "0"); + SDL_SetHint(SDL_HINT_PEN_MOUSE_EVENTS, "0"); + SDL_SetHint(SDL_HINT_TOUCH_MOUSE_EVENTS, "0"); + SDL_SetHint(SDL_HINT_PEN_TOUCH_EVENTS, "1"); + SDL_SetHint(SDL_HINT_TRACKPAD_IS_TOUCH_ONLY, "1"); + /* Redirect SDL log messages to wLog */ SDL_SetLogOutputFunction(winpr_LogOutputFunction, sdl); auto level = WLog_GetLogLevel(sdl->getWLog()); @@ -614,13 +621,6 @@ int main(int argc, char* argv[]) WLog_Print(sdl->getWLog(), WLOG_DEBUG, "client is using backend '%s'", backend); sdl_dialogs_init(); - SDL_SetHint(SDL_HINT_ALLOW_ALT_TAB_WHILE_GRABBED, "0"); - SDL_SetHint(SDL_HINT_VIDEO_X11_NET_WM_BYPASS_COMPOSITOR, "0"); - SDL_SetHint(SDL_HINT_PEN_MOUSE_EVENTS, "0"); - SDL_SetHint(SDL_HINT_TOUCH_MOUSE_EVENTS, "0"); - SDL_SetHint(SDL_HINT_PEN_TOUCH_EVENTS, "1"); - SDL_SetHint(SDL_HINT_TRACKPAD_IS_TOUCH_ONLY, "1"); - /* SDL cleanup code if the client exits */ ScopeGuard guard( [&]() @@ -630,6 +630,8 @@ int main(int argc, char* argv[]) sdl_dialogs_uninit(); SDL_Quit(); }); + if (!sdl->detectDisplays()) + return -1; /* Initialize RDP */ auto context = sdl->context(); diff --git a/client/SDL/SDL3/sdl_monitor.cpp b/client/SDL/SDL3/sdl_monitor.cpp index 7fbab74e1..4664fb4f6 100644 --- a/client/SDL/SDL3/sdl_monitor.cpp +++ b/client/SDL/SDL3/sdl_monitor.cpp @@ -179,42 +179,6 @@ int sdl_list_monitors([[maybe_unused]] SdlContext* sdl) return static_cast(sval); } -[[nodiscard]] static BOOL sdl_apply_monitor_properties(rdpMonitor& monitor, SDL_DisplayID id, - bool isPrimary) -{ - auto mode = SDL_GetCurrentDisplayMode(id); - if (!mode) - return FALSE; - - const float dpi = roundf(mode->pixel_density * 100.0f); - const float factor = mode->pixel_density; - SDL_Rect rect = {}; - - if (!SDL_GetDisplayBounds(id, &rect)) - return FALSE; - - WINPR_ASSERT(rect.w > 0); - WINPR_ASSERT(rect.h > 0); - - bool highDpi = dpi > 100; - - const SDL_DisplayOrientation orientation = SDL_GetCurrentDisplayOrientation(id); - const UINT32 rdp_orientation = sdl::utils::orientaion_to_rdp(orientation); - - monitor.orig_screen = id; - monitor.x = rect.x; - monitor.y = rect.y; - monitor.width = roundf(rect.w * factor); - monitor.height = roundf(rect.h * factor); - monitor.is_primary = isPrimary; - monitor.attributes.desktopScaleFactor = static_cast(dpi); - monitor.attributes.deviceScaleFactor = 100; - monitor.attributes.orientation = rdp_orientation; - monitor.attributes.physicalWidth = WINPR_ASSERTING_INT_CAST(uint32_t, rect.w); - monitor.attributes.physicalHeight = WINPR_ASSERTING_INT_CAST(uint32_t, rect.h); - return TRUE; -} - [[nodiscard]] static BOOL sdl_apply_display_properties(SdlContext* sdl) { WINPR_ASSERT(sdl); @@ -231,9 +195,10 @@ int sdl_list_monitors([[maybe_unused]] SdlContext* sdl) if (sdl->monitorIds().empty()) return FALSE; const auto id = sdl->monitorIds().front(); - rdpMonitor monitor = {}; - if (!sdl_apply_monitor_properties(monitor, id, TRUE)) - return FALSE; + auto monitor = sdl->getDisplay(id); + monitor.is_primary = true; + monitor.x = 0; + monitor.y = 0; monitors.emplace_back(monitor); return freerdp_settings_set_monitor_def_array_sorted(settings, monitors.data(), monitors.size()); @@ -242,10 +207,7 @@ int sdl_list_monitors([[maybe_unused]] SdlContext* sdl) } for (const auto& id : sdl->monitorIds()) { - rdpMonitor monitor = {}; - const auto primary = SDL_GetPrimaryDisplay(); - if (!sdl_apply_monitor_properties(monitor, id, id == primary)) - return FALSE; + const auto monitor = sdl->getDisplay(id); monitors.emplace_back(monitor); } return freerdp_settings_set_monitor_def_array_sorted(settings, monitors.data(), @@ -297,17 +259,7 @@ BOOL sdl_detect_monitors(SdlContext* sdl, UINT32* pMaxWidth, UINT32* pMaxHeight) rdpSettings* settings = sdl->context()->settings; WINPR_ASSERT(settings); - std::vector ids; - { - int numDisplays = 0; - auto sids = SDL_GetDisplays(&numDisplays); - if (sids && (numDisplays > 0)) - ids = std::vector(sids, sids + numDisplays); - SDL_free(sids); - if (numDisplays < 0) - return FALSE; - } - + const auto& ids = sdl->getDisplayIds(); auto nr = freerdp_settings_get_uint32(settings, FreeRDP_NumMonitorIds); if (nr == 0) { From da71c28c9c11d3ee3fe10f93a8832b487b2d4942 Mon Sep 17 00:00:00 2001 From: Armin Novak Date: Fri, 13 Feb 2026 10:13:08 +0100 Subject: [PATCH 5/5] [client,sdl] create a map of pixel coordinates We need pixel coordinates for each monitor, but SDL may return logical coordinates depending on HighDPI mode used by the system. This commit does: * Detect which HighDPI mode is in use isHighDPIWindowsMode * Creates a map of pixel coordinates for each monitor * recreated whenever a monitor changes) * Updates the window rdpMonitor data for existing windows --- client/SDL/SDL3/sdl_context.cpp | 124 +++++++++++++++++++++++++++++++- client/SDL/SDL3/sdl_context.hpp | 8 ++- client/SDL/SDL3/sdl_monitor.cpp | 8 --- client/SDL/SDL3/sdl_window.cpp | 77 +++++++++++++++++--- client/SDL/SDL3/sdl_window.hpp | 15 +++- 5 files changed, 210 insertions(+), 22 deletions(-) diff --git a/client/SDL/SDL3/sdl_context.cpp b/client/SDL/SDL3/sdl_context.cpp index 0a9d65921..17c98fe4e 100644 --- a/client/SDL/SDL3/sdl_context.cpp +++ b/client/SDL/SDL3/sdl_context.cpp @@ -16,6 +16,9 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + +#include + #include "sdl_context.hpp" #include "sdl_config.hpp" #include "sdl_channels.hpp" @@ -808,6 +811,78 @@ void SdlContext::applyMonitorOffset(SDL_WindowID window, float& x, float& y) con y -= static_cast(w->offsetY()); } +static bool alignX(const SDL_Rect& a, const SDL_Rect& b) +{ + if (a.x + a.w == b.x) + return true; + if (b.x + b.w == a.x) + return true; + return false; +} + +static bool alignY(const SDL_Rect& a, const SDL_Rect& b) +{ + if (a.y + a.h == b.y) + return true; + if (b.y + b.h == a.y) + return true; + return false; +} + +std::vector +SdlContext::updateDisplayOffsetsForNeighbours(SDL_DisplayID id, + const std::vector& ignore) +{ + auto first = _offsets.at(id); + std::vector neighbours; + + for (auto& entry : _offsets) + { + if (entry.first == id) + continue; + if (std::find(ignore.begin(), ignore.end(), entry.first) != ignore.end()) + continue; + + bool neighbor = false; + if (alignX(entry.second.first, first.first)) + { + if (entry.second.first.x < first.first.x) + entry.second.second.x = first.second.x - entry.second.second.w; + else + entry.second.second.x = first.second.x + first.second.w; + neighbor = true; + } + if (alignY(entry.second.first, first.first)) + { + if (entry.second.first.y < first.first.y) + entry.second.second.y = first.second.y - entry.second.second.h; + else + entry.second.second.y = first.second.y + first.second.h; + neighbor = true; + } + + if (neighbor) + neighbours.push_back(entry.first); + } + return neighbours; +} + +void SdlContext::updateMonitorDataFromOffsets() +{ + for (auto& entry : _displays) + { + auto offsets = _offsets.at(entry.first); + entry.second.x = offsets.second.x; + entry.second.y = offsets.second.y; + } + + for (auto& entry : _windows) + { + const auto& monitor = _displays.at(entry.first); + entry.second.setMonitor(monitor); + } +} + bool SdlContext::drawToWindow(SdlWindow& window, const std::vector& rects) { if (!isConnected()) @@ -897,8 +972,7 @@ bool SdlContext::detectDisplays() for (int x = 0; x < count; x++) { const auto id = display[x]; - auto monitor = SdlWindow::query(id, false); - addOrUpdateDisplay(id, monitor); + addOrUpdateDisplay(id); } return true; @@ -912,6 +986,7 @@ rdpMonitor SdlContext::getDisplay(SDL_DisplayID id) const std::vector SdlContext::getDisplayIds() const { std::vector keys; + keys.reserve(_displays.size()); for (const auto& entry : _displays) { keys.push_back(entry.first); @@ -1122,9 +1197,52 @@ bool SdlContext::handleEvent(const SDL_TouchFingerEvent& ev) return SdlTouch::handleEvent(this, copy.tfinger); } -void SdlContext::addOrUpdateDisplay(SDL_DisplayID id, const rdpMonitor& monitor) +void SdlContext::addOrUpdateDisplay(SDL_DisplayID id) { + auto monitor = SdlWindow::query(id, false); _displays.emplace(id, monitor); + + /* Update actual display rectangles: + * + * 1. Get logical display bounds + * 2. Use already known pixel width and height + * 3. Iterate over each display and update the x and y offsets by adding all monitor + * widths/heights from the primary + */ + _offsets.clear(); + for (auto& entry : _displays) + { + SDL_Rect bounds{}; + std::ignore = SDL_GetDisplayBounds(entry.first, &bounds); + + SDL_Rect pixel{}; + pixel.w = entry.second.width; + pixel.h = entry.second.height; + _offsets.emplace(entry.first, std::pair{ bounds, pixel }); + } + + /* 1. Find primary and update all neighbors + * 2. For each neighbor update all neighbors + * 3. repeat until all displays updated. + */ + const auto primary = SDL_GetPrimaryDisplay(); + std::vector handled; + handled.push_back(primary); + + auto neighbors = updateDisplayOffsetsForNeighbours(primary); + while (!neighbors.empty()) + { + auto neighbor = *neighbors.begin(); + neighbors.pop_back(); + + if (std::find(handled.begin(), handled.end(), neighbor) != handled.end()) + continue; + handled.push_back(neighbor); + + auto next = updateDisplayOffsetsForNeighbours(neighbor, handled); + neighbors.insert(neighbors.end(), next.begin(), next.end()); + } + updateMonitorDataFromOffsets(); } void SdlContext::deleteDisplay(SDL_DisplayID id) diff --git a/client/SDL/SDL3/sdl_context.hpp b/client/SDL/SDL3/sdl_context.hpp index b225f19ef..6ea8cc774 100644 --- a/client/SDL/SDL3/sdl_context.hpp +++ b/client/SDL/SDL3/sdl_context.hpp @@ -165,7 +165,7 @@ class SdlContext [[nodiscard]] bool handleEvent(const SDL_MouseWheelEvent& ev); [[nodiscard]] bool handleEvent(const SDL_TouchFingerEvent& ev); - void addOrUpdateDisplay(SDL_DisplayID id, const rdpMonitor& monitor); + void addOrUpdateDisplay(SDL_DisplayID id); void deleteDisplay(SDL_DisplayID id); [[nodiscard]] bool createPrimary(); @@ -180,6 +180,11 @@ class SdlContext void applyMonitorOffset(SDL_WindowID window, float& x, float& y) const; + [[nodiscard]] std::vector + updateDisplayOffsetsForNeighbours(SDL_DisplayID id, + const std::vector& ignore = {}); + void updateMonitorDataFromOffsets(); + rdpContext* _context = nullptr; wLog* _log = nullptr; @@ -214,6 +219,7 @@ class SdlContext std::map _displays; std::map _windows; + std::map> _offsets; uint32_t _windowWidth = 0; uint32_t _windowHeigth = 0; diff --git a/client/SDL/SDL3/sdl_monitor.cpp b/client/SDL/SDL3/sdl_monitor.cpp index 4664fb4f6..cd890768e 100644 --- a/client/SDL/SDL3/sdl_monitor.cpp +++ b/client/SDL/SDL3/sdl_monitor.cpp @@ -172,13 +172,6 @@ int sdl_list_monitors([[maybe_unused]] SdlContext* sdl) return TRUE; } -[[nodiscard]] static Uint32 scale(Uint32 val, float scale) -{ - const auto dval = static_cast(val); - const auto sval = dval / scale; - return static_cast(sval); -} - [[nodiscard]] static BOOL sdl_apply_display_properties(SdlContext* sdl) { WINPR_ASSERT(sdl); @@ -242,7 +235,6 @@ int sdl_list_monitors([[maybe_unused]] SdlContext* sdl) sdl->setMonitorIds({ id }); } - // TODO: Fill monitor struct if (!sdl_apply_display_properties(sdl)) return FALSE; return sdl_apply_max_size(sdl, pMaxWidth, pMaxHeight); diff --git a/client/SDL/SDL3/sdl_window.cpp b/client/SDL/SDL3/sdl_window.cpp index 2f1e09ef3..913af1a10 100644 --- a/client/SDL/SDL3/sdl_window.cpp +++ b/client/SDL/SDL3/sdl_window.cpp @@ -56,6 +56,8 @@ SdlWindow::SdlWindow(SDL_DisplayID id, const std::string& title, const SDL_Rect& std::ignore = resize({ w, h }); SDL_SetHint(SDL_HINT_APP_NAME, ""); std::ignore = SDL_SyncWindow(_window); + + _monitor = query(_window, id, true); } SdlWindow::SdlWindow(SdlWindow&& other) noexcept @@ -129,7 +131,18 @@ Sint32 SdlWindow::offsetY() const rdpMonitor SdlWindow::monitor(bool isPrimary) const { - return query(_window, displayIndex(), isPrimary); + auto m = _monitor; + if (isPrimary) + { + m.x = 0; + m.y = 0; + } + return m; +} + +void SdlWindow::setMonitor(rdpMonitor monitor) +{ + _monitor = monitor; } float SdlWindow::scale() const @@ -274,23 +287,21 @@ rdpMonitor SdlWindow::query(SDL_Window* window, SDL_DisplayID id, bool forceAsPr if (!window) return {}; - const auto& r = rect(window); + const auto& r = rect(window, forceAsPrimary); const float factor = SDL_GetWindowDisplayScale(window); const float dpi = std::roundf(factor * 100.0f); WINPR_ASSERT(r.w > 0); WINPR_ASSERT(r.h > 0); - bool highDpi = dpi > 100; - const auto primary = SDL_GetPrimaryDisplay(); const auto orientation = SDL_GetCurrentDisplayOrientation(id); const auto rdp_orientation = sdl::utils::orientaion_to_rdp(orientation); rdpMonitor monitor{}; monitor.orig_screen = id; - monitor.x = forceAsPrimary ? 0 : r.x; - monitor.y = forceAsPrimary ? 0 : r.y; + monitor.x = r.x; + monitor.y = r.y; monitor.width = r.w; monitor.height = r.h; monitor.is_primary = forceAsPrimary || (id == primary); @@ -316,20 +327,49 @@ rdpMonitor SdlWindow::query(SDL_Window* window, SDL_DisplayID id, bool forceAsPr return monitor; } -SDL_Rect SdlWindow::rect(SDL_Window* window) +SDL_Rect SdlWindow::rect(SDL_Window* window, bool forceAsPrimary) { SDL_Rect rect = {}; if (!window) return {}; - if (!SDL_GetWindowPosition(window, &rect.x, &rect.y)) - return {}; + if (!forceAsPrimary) + { + if (!SDL_GetWindowPosition(window, &rect.x, &rect.y)) + return {}; + } + if (!SDL_GetWindowSizeInPixels(window, &rect.w, &rect.h)) return {}; return rect; } +SdlWindow::HighDPIMode SdlWindow::isHighDPIWindowsMode(SDL_Window* window) +{ + if (!window) + return MODE_INVALID; + + const auto id = SDL_GetDisplayForWindow(window); + if (id == 0) + return MODE_INVALID; + + const auto cs = SDL_GetDisplayContentScale(id); + const auto ds = SDL_GetWindowDisplayScale(window); + const auto pd = SDL_GetWindowPixelDensity(window); + + /* mac os x style, but no HighDPI display */ + if ((cs == 1.0f) && (ds == 1.0f) && (pd == 1.0f)) + return MODE_NONE; + + /* mac os x style HighDPI */ + if ((cs == 1.0f) && (ds > 1.0f) && (pd > 1.0f)) + return MODE_MACOS; + + /* rest is windows style */ + return MODE_WINDOWS; +} + bool SdlWindow::blit(SDL_Surface* surface, const SDL_Rect& srcRect, SDL_Rect& dstRect) { auto screen = SDL_GetWindowSurface(_window); @@ -421,3 +461,22 @@ rdpMonitor SdlWindow::query(SDL_DisplayID id, bool forceAsPrimary) return query(window.get(), id, forceAsPrimary); } + +SDL_Rect SdlWindow::rect(SDL_DisplayID id, bool forceAsPrimary) +{ + std::unique_ptr window(createDummy(id), SDL_DestroyWindow); + if (!window) + return {}; + + std::unique_ptr renderer( + SDL_CreateRenderer(window.get(), nullptr), SDL_DestroyRenderer); + + if (!SDL_SyncWindow(window.get())) + return {}; + + SDL_Event event{}; + while (SDL_PollEvent(&event)) + ; + + return rect(window.get(), forceAsPrimary); +} diff --git a/client/SDL/SDL3/sdl_window.hpp b/client/SDL/SDL3/sdl_window.hpp index 9a69d4618..0b4a4ec44 100644 --- a/client/SDL/SDL3/sdl_window.hpp +++ b/client/SDL/SDL3/sdl_window.hpp @@ -53,6 +53,7 @@ class SdlWindow [[nodiscard]] Sint32 offsetY() const; [[nodiscard]] rdpMonitor monitor(bool isPrimary) const; + void setMonitor(rdpMonitor monitor); [[nodiscard]] float scale() const; [[nodiscard]] SDL_DisplayOrientation orientation() const; @@ -87,11 +88,23 @@ class SdlWindow Uint8 b = 0x00, Uint8 a = 0xff); [[nodiscard]] static rdpMonitor query(SDL_Window* window, SDL_DisplayID id, bool forceAsPrimary = false); - [[nodiscard]] static SDL_Rect rect(SDL_Window* window); + [[nodiscard]] static SDL_Rect rect(SDL_Window* window, bool forceAsPrimary = false); + [[nodiscard]] static SDL_Rect rect(SDL_DisplayID id, bool forceAsPrimary = false); + + enum HighDPIMode + { + MODE_INVALID, + MODE_NONE, + MODE_WINDOWS, + MODE_MACOS + }; + + [[nodiscard]] static enum HighDPIMode isHighDPIWindowsMode(SDL_Window* window); private: SDL_Window* _window = nullptr; SDL_DisplayID _displayID = 0; Sint32 _offset_x = 0; Sint32 _offset_y = 0; + rdpMonitor _monitor{}; };