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{}; };