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