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_freerdp.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
84static BOOL sdl_apply_mon_max_size(SdlContext* sdl, UINT32* pMaxWidth, UINT32* pMaxHeight)
85{
86 int32_t left = 0;
87 int32_t right = 0;
88 int32_t top = 0;
89 int32_t bottom = 0;
90
91 WINPR_ASSERT(pMaxWidth);
92 WINPR_ASSERT(pMaxHeight);
93
94 auto settings = sdl->context()->settings;
95 WINPR_ASSERT(settings);
96
97 for (size_t x = 0; x < freerdp_settings_get_uint32(settings, FreeRDP_MonitorCount); x++)
98 {
99 auto monitor = static_cast<const rdpMonitor*>(
100 freerdp_settings_get_pointer_array(settings, FreeRDP_MonitorDefArray, x));
101
102 left = std::min(monitor->x, left);
103 top = std::min(monitor->y, top);
104 right = std::max(monitor->x + monitor->width, right);
105 bottom = std::max(monitor->y + monitor->height, bottom);
106 }
107
108 const int32_t w = right - left;
109 const int32_t h = bottom - top;
110
111 *pMaxWidth = WINPR_ASSERTING_INT_CAST(uint32_t, w);
112 *pMaxHeight = WINPR_ASSERTING_INT_CAST(uint32_t, h);
113 return TRUE;
114}
115
116static BOOL sdl_apply_max_size(SdlContext* sdl, UINT32* pMaxWidth, UINT32* pMaxHeight)
117{
118 WINPR_ASSERT(sdl);
119 WINPR_ASSERT(pMaxWidth);
120 WINPR_ASSERT(pMaxHeight);
121
122 auto settings = sdl->context()->settings;
123 WINPR_ASSERT(settings);
124
125 *pMaxWidth = 0;
126 *pMaxHeight = 0;
127
128 for (size_t x = 0; x < freerdp_settings_get_uint32(settings, FreeRDP_MonitorCount); x++)
129 {
130 auto monitor = static_cast<const rdpMonitor*>(
131 freerdp_settings_get_pointer_array(settings, FreeRDP_MonitorDefArray, x));
132
133 if (freerdp_settings_get_bool(settings, FreeRDP_Fullscreen))
134 {
135 *pMaxWidth = WINPR_ASSERTING_INT_CAST(uint32_t, monitor->width);
136 *pMaxHeight = WINPR_ASSERTING_INT_CAST(uint32_t, monitor->height);
137 }
138 else if (freerdp_settings_get_bool(settings, FreeRDP_Workarea))
139 {
140 SDL_Rect rect = {};
141 SDL_GetDisplayUsableBounds(monitor->orig_screen, &rect);
142 *pMaxWidth = WINPR_ASSERTING_INT_CAST(uint32_t, rect.w);
143 *pMaxHeight = WINPR_ASSERTING_INT_CAST(uint32_t, rect.h);
144 }
145 else if (freerdp_settings_get_uint32(settings, FreeRDP_PercentScreen) > 0)
146 {
147 SDL_Rect rect = {};
148 SDL_GetDisplayUsableBounds(monitor->orig_screen, &rect);
149
150 *pMaxWidth = WINPR_ASSERTING_INT_CAST(uint32_t, rect.w);
151 *pMaxHeight = WINPR_ASSERTING_INT_CAST(uint32_t, rect.h);
152
153 if (freerdp_settings_get_bool(settings, FreeRDP_PercentScreenUseWidth))
154 *pMaxWidth = (WINPR_ASSERTING_INT_CAST(uint32_t, rect.w) *
155 freerdp_settings_get_uint32(settings, FreeRDP_PercentScreen)) /
156 100;
157
158 if (freerdp_settings_get_bool(settings, FreeRDP_PercentScreenUseHeight))
159 *pMaxHeight = (WINPR_ASSERTING_INT_CAST(uint32_t, rect.h) *
160 freerdp_settings_get_uint32(settings, FreeRDP_PercentScreen)) /
161 100;
162 }
163 else if (freerdp_settings_get_uint32(settings, FreeRDP_DesktopWidth) &&
164 freerdp_settings_get_uint32(settings, FreeRDP_DesktopHeight))
165 {
166 *pMaxWidth = freerdp_settings_get_uint32(settings, FreeRDP_DesktopWidth);
167 *pMaxHeight = freerdp_settings_get_uint32(settings, FreeRDP_DesktopHeight);
168 }
169 }
170 return TRUE;
171}
172
173static Uint32 scale(Uint32 val, float scale)
174{
175 const auto dval = static_cast<float>(val);
176 const auto sval = dval / scale;
177 return static_cast<Uint32>(sval);
178}
179
180static BOOL sdl_apply_monitor_properties(rdpMonitor& monitor, SDL_DisplayID id, bool isPrimary)
181{
182
183 float dpi = SDL_GetDisplayContentScale(id);
184 float hdpi = dpi;
185 float vdpi = dpi;
186 SDL_Rect rect = {};
187
188 if (!SDL_GetDisplayBounds(id, &rect))
189 return FALSE;
190
191 WINPR_ASSERT(rect.w > 0);
192 WINPR_ASSERT(rect.h > 0);
193
194 bool highDpi = dpi > 100;
195
196 if (highDpi)
197 {
198 // HighDPI is problematic with SDL: We can only get native resolution by creating a
199 // window. Work around this by checking the supported resolutions (and keep maximum)
200 // Also scale the DPI
201 const SDL_Rect scaleRect = rect;
202 int count = 0;
203 auto modes = SDL_GetFullscreenDisplayModes(id, &count);
204 for (int i = 0; i < count; i++)
205 {
206 auto mode = modes[i];
207 if (!mode)
208 break;
209
210 if (mode->w > rect.w)
211 {
212 rect.w = mode->w;
213 rect.h = mode->h;
214 }
215 else if (mode->w == rect.w)
216 {
217 if (mode->h > rect.h)
218 {
219 rect.w = mode->w;
220 rect.h = mode->h;
221 }
222 }
223 }
224 SDL_free(static_cast<void*>(modes));
225
226 const float dw = 1.0f * static_cast<float>(rect.w) / static_cast<float>(scaleRect.w);
227 const float dh = 1.0f * static_cast<float>(rect.h) / static_cast<float>(scaleRect.h);
228 hdpi /= dw;
229 vdpi /= dh;
230 }
231
232 const SDL_DisplayOrientation orientation = SDL_GetCurrentDisplayOrientation(id);
233 const UINT32 rdp_orientation = sdl::utils::orientaion_to_rdp(orientation);
234
235 /* windows uses 96 dpi as 'default' and the scale factors are in percent. */
236 const auto factor = dpi / 96.0f * 100.0f;
237 monitor.orig_screen = id;
238 monitor.x = rect.x;
239 monitor.y = rect.y;
240 monitor.width = rect.w;
241 monitor.height = rect.h;
242 monitor.is_primary = isPrimary;
243 monitor.attributes.desktopScaleFactor = static_cast<UINT32>(factor);
244 monitor.attributes.deviceScaleFactor = 100;
245 monitor.attributes.orientation = rdp_orientation;
246 monitor.attributes.physicalWidth = scale(WINPR_ASSERTING_INT_CAST(uint32_t, rect.w), hdpi);
247 monitor.attributes.physicalHeight = scale(WINPR_ASSERTING_INT_CAST(uint32_t, rect.h), vdpi);
248 return TRUE;
249}
250
251static BOOL sdl_apply_display_properties(SdlContext* sdl)
252{
253 WINPR_ASSERT(sdl);
254
255 rdpSettings* settings = sdl->context()->settings;
256 WINPR_ASSERT(settings);
257
258 std::vector<rdpMonitor> monitors;
259 if (!freerdp_settings_get_bool(settings, FreeRDP_Fullscreen) &&
260 !freerdp_settings_get_bool(settings, FreeRDP_UseMultimon))
261 {
262 if (freerdp_settings_get_bool(settings, FreeRDP_Workarea))
263 {
264 if (sdl->monitorIds().empty())
265 return FALSE;
266 const auto id = sdl->monitorIds().front();
267 rdpMonitor monitor = {};
268 if (!sdl_apply_monitor_properties(monitor, id, TRUE))
269 return FALSE;
270 monitors.emplace_back(monitor);
271 return freerdp_settings_set_monitor_def_array_sorted(settings, monitors.data(),
272 monitors.size());
273 }
274 return TRUE;
275 }
276 for (const auto& id : sdl->monitorIds())
277 {
278 rdpMonitor monitor = {};
279 const auto primary = SDL_GetPrimaryDisplay();
280 if (!sdl_apply_monitor_properties(monitor, id, id == primary))
281 return FALSE;
282 monitors.emplace_back(monitor);
283 }
284 return freerdp_settings_set_monitor_def_array_sorted(settings, monitors.data(),
285 monitors.size());
286}
287
288static BOOL sdl_detect_single_window(SdlContext* sdl, UINT32* pMaxWidth, UINT32* pMaxHeight)
289{
290 WINPR_ASSERT(sdl);
291 WINPR_ASSERT(pMaxWidth);
292 WINPR_ASSERT(pMaxHeight);
293
294 rdpSettings* settings = sdl->context()->settings;
295 WINPR_ASSERT(settings);
296
297 if ((!freerdp_settings_get_bool(settings, FreeRDP_UseMultimon) &&
298 !freerdp_settings_get_bool(settings, FreeRDP_SpanMonitors)) ||
299 (freerdp_settings_get_bool(settings, FreeRDP_Workarea) &&
300 !freerdp_settings_get_bool(settings, FreeRDP_RemoteApplicationMode)))
301 {
302 /* If no monitors were specified on the command-line then set the current monitor as active
303 */
304 if (freerdp_settings_get_uint32(settings, FreeRDP_NumMonitorIds) == 0)
305 {
306 SDL_DisplayID id = 0;
307 const auto& ids = sdl->monitorIds();
308 if (!ids.empty())
309 {
310 id = ids.front();
311 }
312 sdl->setMonitorIds({ id });
313 }
314
315 // TODO: Fill monitor struct
316 if (!sdl_apply_display_properties(sdl))
317 return FALSE;
318 return sdl_apply_max_size(sdl, pMaxWidth, pMaxHeight);
319 }
320 return sdl_apply_mon_max_size(sdl, pMaxWidth, pMaxHeight);
321}
322
323BOOL sdl_detect_monitors(SdlContext* sdl, UINT32* pMaxWidth, UINT32* pMaxHeight)
324{
325 WINPR_ASSERT(sdl);
326 WINPR_ASSERT(pMaxWidth);
327 WINPR_ASSERT(pMaxHeight);
328
329 rdpSettings* settings = sdl->context()->settings;
330 WINPR_ASSERT(settings);
331
332 std::vector<SDL_DisplayID> ids;
333 {
334 int numDisplays = 0;
335 auto sids = SDL_GetDisplays(&numDisplays);
336 if (sids && (numDisplays > 0))
337 ids = std::vector<SDL_DisplayID>(sids, sids + numDisplays);
338 SDL_free(sids);
339 if (numDisplays < 0)
340 return FALSE;
341 }
342
343 auto nr = freerdp_settings_get_uint32(settings, FreeRDP_NumMonitorIds);
344 if (nr == 0)
345 {
346 if (freerdp_settings_get_bool(settings, FreeRDP_UseMultimon))
347 sdl->setMonitorIds(ids);
348 else
349 {
350 sdl->setMonitorIds({ ids.front() });
351 }
352 }
353 else
354 {
355 /* There were more IDs supplied than there are monitors */
356 if (nr > ids.size())
357 {
358 WLog_ERR(TAG,
359 "Found %" PRIu32 " monitor IDs, but only have %" PRIuz " monitors connected",
360 nr, ids.size());
361 return FALSE;
362 }
363
364 std::vector<SDL_DisplayID> used;
365 for (size_t x = 0; x < nr; x++)
366 {
367 auto cur = static_cast<const UINT32*>(
368 freerdp_settings_get_pointer_array(settings, FreeRDP_MonitorIds, x));
369 WINPR_ASSERT(cur);
370
371 SDL_DisplayID id = *cur;
372
373 /* the ID is no valid monitor index */
374 if (std::find(ids.begin(), ids.end(), id) == ids.end())
375 {
376 WLog_ERR(TAG, "Supplied monitor ID[%" PRIuz "]=%" PRIu32 " is invalid", x, id);
377 return FALSE;
378 }
379
380 /* The ID is already taken */
381 if (std::find(used.begin(), used.end(), id) != used.end())
382 {
383 WLog_ERR(TAG, "Duplicate monitor ID[%" PRIuz "]=%" PRIu32 " detected", x, id);
384 return FALSE;
385 }
386 used.push_back(id);
387 }
388 sdl->setMonitorIds(used);
389 }
390
391 if (!sdl_apply_display_properties(sdl))
392 return FALSE;
393
394 auto size = static_cast<uint32_t>(sdl->monitorIds().size());
395 if (!freerdp_settings_set_uint32(settings, FreeRDP_NumMonitorIds, size))
396 return FALSE;
397
398 return sdl_detect_single_window(sdl, pMaxWidth, pMaxHeight);
399}
FREERDP_API UINT32 freerdp_settings_get_uint32(const rdpSettings *settings, FreeRDP_Settings_Keys_UInt32 id)
Returns a UINT32 settings value.
FREERDP_API BOOL freerdp_settings_get_bool(const rdpSettings *settings, FreeRDP_Settings_Keys_Bool id)
Returns a boolean settings value.
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.