FreeRDP
Loading...
Searching...
No Matches
SDL3/sdl_monitor.cpp
1
22#include <freerdp/config.h>
23
24#include <cstdio>
25#include <cstdlib>
26#include <cstring>
27#include <algorithm>
28
29#include <SDL3/SDL.h>
30
31#include <winpr/assert.h>
32#include <winpr/crt.h>
33
34#include <freerdp/log.h>
35
36#define TAG CLIENT_TAG("sdl")
37
38#include "sdl_monitor.hpp"
39#include "sdl_context.hpp"
40
41typedef struct
42{
43 RECTANGLE_16 area;
44 RECTANGLE_16 workarea;
45 BOOL primary;
46} MONITOR_INFO;
47
48typedef struct
49{
50 int nmonitors;
51 RECTANGLE_16 area;
52 RECTANGLE_16 workarea;
53 MONITOR_INFO* monitors;
54} VIRTUAL_SCREEN;
55
56/* See MSDN Section on Multiple Display Monitors: http://msdn.microsoft.com/en-us/library/dd145071
57 */
58
59int sdl_list_monitors([[maybe_unused]] SdlContext* sdl)
60{
61 SDL_Init(SDL_INIT_VIDEO);
62
63 int nmonitors = 0;
64 auto ids = SDL_GetDisplays(&nmonitors);
65
66 printf("listing %d monitors:\n", nmonitors);
67 for (int i = 0; i < nmonitors; i++)
68 {
69 SDL_Rect rect = {};
70 auto id = ids[i];
71 const auto brc = SDL_GetDisplayBounds(id, &rect);
72 const char* name = SDL_GetDisplayName(id);
73
74 if (!brc)
75 continue;
76 printf(" %s [%u] [%s] %dx%d\t+%d+%d\n", (i == 0) ? "*" : " ", id, name, rect.w, rect.h,
77 rect.x, rect.y);
78 }
79 SDL_free(ids);
80 SDL_Quit();
81 return 0;
82}
83
84[[nodiscard]] static BOOL sdl_apply_mon_max_size(SdlContext* sdl, UINT32* pMaxWidth,
85 UINT32* pMaxHeight)
86{
87 int32_t left = 0;
88 int32_t right = 0;
89 int32_t top = 0;
90 int32_t bottom = 0;
91
92 WINPR_ASSERT(pMaxWidth);
93 WINPR_ASSERT(pMaxHeight);
94
95 auto settings = sdl->context()->settings;
96 WINPR_ASSERT(settings);
97
98 for (size_t x = 0; x < freerdp_settings_get_uint32(settings, FreeRDP_MonitorCount); x++)
99 {
100 auto monitor = static_cast<const rdpMonitor*>(
101 freerdp_settings_get_pointer_array(settings, FreeRDP_MonitorDefArray, x));
102
103 left = std::min(monitor->x, left);
104 top = std::min(monitor->y, top);
105 right = std::max(monitor->x + monitor->width, right);
106 bottom = std::max(monitor->y + monitor->height, bottom);
107 }
108
109 const int32_t w = right - left;
110 const int32_t h = bottom - top;
111
112 *pMaxWidth = WINPR_ASSERTING_INT_CAST(uint32_t, w);
113 *pMaxHeight = WINPR_ASSERTING_INT_CAST(uint32_t, h);
114 return TRUE;
115}
116
117[[nodiscard]] static BOOL sdl_apply_max_size(SdlContext* sdl, UINT32* pMaxWidth, UINT32* pMaxHeight)
118{
119 WINPR_ASSERT(sdl);
120 WINPR_ASSERT(pMaxWidth);
121 WINPR_ASSERT(pMaxHeight);
122
123 auto settings = sdl->context()->settings;
124 WINPR_ASSERT(settings);
125
126 *pMaxWidth = 0;
127 *pMaxHeight = 0;
128
129 for (size_t x = 0; x < freerdp_settings_get_uint32(settings, FreeRDP_MonitorCount); x++)
130 {
131 auto monitor = static_cast<const rdpMonitor*>(
132 freerdp_settings_get_pointer_array(settings, FreeRDP_MonitorDefArray, x));
133
134 if (freerdp_settings_get_bool(settings, FreeRDP_Fullscreen))
135 {
136 *pMaxWidth = WINPR_ASSERTING_INT_CAST(uint32_t, monitor->width);
137 *pMaxHeight = WINPR_ASSERTING_INT_CAST(uint32_t, monitor->height);
138 }
139 else if (freerdp_settings_get_bool(settings, FreeRDP_Workarea))
140 {
141 SDL_Rect rect = {};
142 SDL_GetDisplayUsableBounds(monitor->orig_screen, &rect);
143 *pMaxWidth = WINPR_ASSERTING_INT_CAST(uint32_t, rect.w);
144 *pMaxHeight = WINPR_ASSERTING_INT_CAST(uint32_t, rect.h);
145 }
146 else if (freerdp_settings_get_uint32(settings, FreeRDP_PercentScreen) > 0)
147 {
148 SDL_Rect rect = {};
149 SDL_GetDisplayUsableBounds(monitor->orig_screen, &rect);
150
151 *pMaxWidth = WINPR_ASSERTING_INT_CAST(uint32_t, rect.w);
152 *pMaxHeight = WINPR_ASSERTING_INT_CAST(uint32_t, rect.h);
153
154 if (freerdp_settings_get_bool(settings, FreeRDP_PercentScreenUseWidth))
155 *pMaxWidth = (WINPR_ASSERTING_INT_CAST(uint32_t, rect.w) *
156 freerdp_settings_get_uint32(settings, FreeRDP_PercentScreen)) /
157 100;
158
159 if (freerdp_settings_get_bool(settings, FreeRDP_PercentScreenUseHeight))
160 *pMaxHeight = (WINPR_ASSERTING_INT_CAST(uint32_t, rect.h) *
161 freerdp_settings_get_uint32(settings, FreeRDP_PercentScreen)) /
162 100;
163 }
164 else if (freerdp_settings_get_uint32(settings, FreeRDP_DesktopWidth) &&
165 freerdp_settings_get_uint32(settings, FreeRDP_DesktopHeight))
166 {
167 *pMaxWidth = freerdp_settings_get_uint32(settings, FreeRDP_DesktopWidth);
168 *pMaxHeight = freerdp_settings_get_uint32(settings, FreeRDP_DesktopHeight);
169 }
170 }
171 return TRUE;
172}
173
174[[nodiscard]] static Uint32 scale(Uint32 val, float scale)
175{
176 const auto dval = static_cast<float>(val);
177 const auto sval = dval / scale;
178 return static_cast<Uint32>(sval);
179}
180
181[[nodiscard]] static BOOL sdl_apply_monitor_properties(rdpMonitor& monitor, SDL_DisplayID id,
182 bool isPrimary)
183{
184
185 float dpi = SDL_GetDisplayContentScale(id);
186 float hdpi = dpi;
187 float vdpi = dpi;
188 SDL_Rect rect = {};
189
190 if (!SDL_GetDisplayBounds(id, &rect))
191 return FALSE;
192
193 WINPR_ASSERT(rect.w > 0);
194 WINPR_ASSERT(rect.h > 0);
195
196 bool highDpi = dpi > 100;
197
198 if (highDpi)
199 {
200 // HighDPI is problematic with SDL: We can only get native resolution by creating a
201 // window. Work around this by checking the supported resolutions (and keep maximum)
202 // Also scale the DPI
203 const SDL_Rect scaleRect = rect;
204 int count = 0;
205 auto modes = SDL_GetFullscreenDisplayModes(id, &count);
206 for (int i = 0; i < count; i++)
207 {
208 auto mode = modes[i];
209 if (!mode)
210 break;
211
212 if (mode->w > rect.w)
213 {
214 rect.w = mode->w;
215 rect.h = mode->h;
216 }
217 else if (mode->w == rect.w)
218 {
219 if (mode->h > rect.h)
220 {
221 rect.w = mode->w;
222 rect.h = mode->h;
223 }
224 }
225 }
226 SDL_free(static_cast<void*>(modes));
227
228 const float dw = 1.0f * static_cast<float>(rect.w) / static_cast<float>(scaleRect.w);
229 const float dh = 1.0f * static_cast<float>(rect.h) / static_cast<float>(scaleRect.h);
230 hdpi /= dw;
231 vdpi /= dh;
232 }
233
234 const SDL_DisplayOrientation orientation = SDL_GetCurrentDisplayOrientation(id);
235 const UINT32 rdp_orientation = sdl::utils::orientaion_to_rdp(orientation);
236
237 /* windows uses 96 dpi as 'default' and the scale factors are in percent. */
238 const auto factor = dpi / 96.0f * 100.0f;
239 monitor.orig_screen = id;
240 monitor.x = rect.x;
241 monitor.y = rect.y;
242 monitor.width = rect.w;
243 monitor.height = rect.h;
244 monitor.is_primary = isPrimary;
245 monitor.attributes.desktopScaleFactor = static_cast<UINT32>(factor);
246 monitor.attributes.deviceScaleFactor = 100;
247 monitor.attributes.orientation = rdp_orientation;
248 monitor.attributes.physicalWidth = scale(WINPR_ASSERTING_INT_CAST(uint32_t, rect.w), hdpi);
249 monitor.attributes.physicalHeight = scale(WINPR_ASSERTING_INT_CAST(uint32_t, rect.h), vdpi);
250 return TRUE;
251}
252
253[[nodiscard]] static BOOL sdl_apply_display_properties(SdlContext* sdl)
254{
255 WINPR_ASSERT(sdl);
256
257 rdpSettings* settings = sdl->context()->settings;
258 WINPR_ASSERT(settings);
259
260 std::vector<rdpMonitor> monitors;
261 if (!freerdp_settings_get_bool(settings, FreeRDP_Fullscreen) &&
262 !freerdp_settings_get_bool(settings, FreeRDP_UseMultimon))
263 {
264 if (freerdp_settings_get_bool(settings, FreeRDP_Workarea))
265 {
266 if (sdl->monitorIds().empty())
267 return FALSE;
268 const auto id = sdl->monitorIds().front();
269 rdpMonitor monitor = {};
270 if (!sdl_apply_monitor_properties(monitor, id, TRUE))
271 return FALSE;
272 monitors.emplace_back(monitor);
273 return freerdp_settings_set_monitor_def_array_sorted(settings, monitors.data(),
274 monitors.size());
275 }
276 return TRUE;
277 }
278 for (const auto& id : sdl->monitorIds())
279 {
280 rdpMonitor monitor = {};
281 const auto primary = SDL_GetPrimaryDisplay();
282 if (!sdl_apply_monitor_properties(monitor, id, id == primary))
283 return FALSE;
284 monitors.emplace_back(monitor);
285 }
286 return freerdp_settings_set_monitor_def_array_sorted(settings, monitors.data(),
287 monitors.size());
288}
289
290[[nodiscard]] static BOOL sdl_detect_single_window(SdlContext* sdl, UINT32* pMaxWidth,
291 UINT32* pMaxHeight)
292{
293 WINPR_ASSERT(sdl);
294 WINPR_ASSERT(pMaxWidth);
295 WINPR_ASSERT(pMaxHeight);
296
297 rdpSettings* settings = sdl->context()->settings;
298 WINPR_ASSERT(settings);
299
300 if ((!freerdp_settings_get_bool(settings, FreeRDP_UseMultimon) &&
301 !freerdp_settings_get_bool(settings, FreeRDP_SpanMonitors)) ||
302 (freerdp_settings_get_bool(settings, FreeRDP_Workarea) &&
303 !freerdp_settings_get_bool(settings, FreeRDP_RemoteApplicationMode)))
304 {
305 /* If no monitors were specified on the command-line then set the current monitor as active
306 */
307 if (freerdp_settings_get_uint32(settings, FreeRDP_NumMonitorIds) == 0)
308 {
309 SDL_DisplayID id = 0;
310 const auto& ids = sdl->monitorIds();
311 if (!ids.empty())
312 {
313 id = ids.front();
314 }
315 sdl->setMonitorIds({ id });
316 }
317
318 // TODO: Fill monitor struct
319 if (!sdl_apply_display_properties(sdl))
320 return FALSE;
321 return sdl_apply_max_size(sdl, pMaxWidth, pMaxHeight);
322 }
323 return sdl_apply_mon_max_size(sdl, pMaxWidth, pMaxHeight);
324}
325
326BOOL sdl_detect_monitors(SdlContext* sdl, UINT32* pMaxWidth, UINT32* pMaxHeight)
327{
328 WINPR_ASSERT(sdl);
329 WINPR_ASSERT(pMaxWidth);
330 WINPR_ASSERT(pMaxHeight);
331
332 rdpSettings* settings = sdl->context()->settings;
333 WINPR_ASSERT(settings);
334
335 std::vector<SDL_DisplayID> ids;
336 {
337 int numDisplays = 0;
338 auto sids = SDL_GetDisplays(&numDisplays);
339 if (sids && (numDisplays > 0))
340 ids = std::vector<SDL_DisplayID>(sids, sids + numDisplays);
341 SDL_free(sids);
342 if (numDisplays < 0)
343 return FALSE;
344 }
345
346 auto nr = freerdp_settings_get_uint32(settings, FreeRDP_NumMonitorIds);
347 if (nr == 0)
348 {
349 if (freerdp_settings_get_bool(settings, FreeRDP_UseMultimon))
350 sdl->setMonitorIds(ids);
351 else
352 {
353 sdl->setMonitorIds({ ids.front() });
354 }
355 }
356 else
357 {
358 /* There were more IDs supplied than there are monitors */
359 if (nr > ids.size())
360 {
361 WLog_ERR(TAG,
362 "Found %" PRIu32 " monitor IDs, but only have %" PRIuz " monitors connected",
363 nr, ids.size());
364 return FALSE;
365 }
366
367 std::vector<SDL_DisplayID> used;
368 for (size_t x = 0; x < nr; x++)
369 {
370 auto cur = static_cast<const UINT32*>(
371 freerdp_settings_get_pointer_array(settings, FreeRDP_MonitorIds, x));
372 WINPR_ASSERT(cur);
373
374 SDL_DisplayID id = *cur;
375
376 /* the ID is no valid monitor index */
377 if (std::find(ids.begin(), ids.end(), id) == ids.end())
378 {
379 WLog_ERR(TAG, "Supplied monitor ID[%" PRIuz "]=%" PRIu32 " is invalid", x, id);
380 return FALSE;
381 }
382
383 /* The ID is already taken */
384 if (std::find(used.begin(), used.end(), id) != used.end())
385 {
386 WLog_ERR(TAG, "Duplicate monitor ID[%" PRIuz "]=%" PRIu32 " detected", x, id);
387 return FALSE;
388 }
389 used.push_back(id);
390 }
391 sdl->setMonitorIds(used);
392 }
393
394 if (!sdl_apply_display_properties(sdl))
395 return FALSE;
396
397 auto size = static_cast<uint32_t>(sdl->monitorIds().size());
398 if (!freerdp_settings_set_uint32(settings, FreeRDP_NumMonitorIds, size))
399 return FALSE;
400
401 return sdl_detect_single_window(sdl, pMaxWidth, pMaxHeight);
402}
WINPR_ATTR_NODISCARD FREERDP_API UINT32 freerdp_settings_get_uint32(const rdpSettings *settings, FreeRDP_Settings_Keys_UInt32 id)
Returns a UINT32 settings value.
WINPR_ATTR_NODISCARD FREERDP_API BOOL freerdp_settings_set_monitor_def_array_sorted(rdpSettings *settings, const rdpMonitor *monitors, size_t count)
Sort monitor array according to:
FREERDP_API BOOL freerdp_settings_set_uint32(rdpSettings *settings, FreeRDP_Settings_Keys_UInt32 id, UINT32 param)
Sets a UINT32 settings value.
WINPR_ATTR_NODISCARD FREERDP_API BOOL freerdp_settings_get_bool(const rdpSettings *settings, FreeRDP_Settings_Keys_Bool id)
Returns a boolean settings value.