[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
This commit is contained in:
Armin Novak
2026-02-13 10:13:08 +01:00
parent 3cb88eefd6
commit da71c28c9c
5 changed files with 210 additions and 22 deletions

View File

@@ -16,6 +16,9 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <algorithm>
#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<float>(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<SDL_DisplayID>
SdlContext::updateDisplayOffsetsForNeighbours(SDL_DisplayID id,
const std::vector<SDL_DisplayID>& ignore)
{
auto first = _offsets.at(id);
std::vector<SDL_DisplayID> 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<SDL_Rect>& 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<SDL_DisplayID> SdlContext::getDisplayIds() const
{
std::vector<SDL_DisplayID> 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<SDL_DisplayID> 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)

View File

@@ -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<SDL_DisplayID>
updateDisplayOffsetsForNeighbours(SDL_DisplayID id,
const std::vector<SDL_DisplayID>& ignore = {});
void updateMonitorDataFromOffsets();
rdpContext* _context = nullptr;
wLog* _log = nullptr;
@@ -214,6 +219,7 @@ class SdlContext
std::map<SDL_DisplayID, rdpMonitor> _displays;
std::map<SDL_WindowID, SdlWindow> _windows;
std::map<SDL_DisplayID, std::pair<SDL_Rect, SDL_Rect>> _offsets;
uint32_t _windowWidth = 0;
uint32_t _windowHeigth = 0;

View File

@@ -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<float>(val);
const auto sval = dval / scale;
return static_cast<Uint32>(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);

View File

@@ -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<SDL_Window, void (*)(SDL_Window*)> window(createDummy(id), SDL_DestroyWindow);
if (!window)
return {};
std::unique_ptr<SDL_Renderer, void (*)(SDL_Renderer*)> 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);
}

View File

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