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 = monitor->width;
128  *pMaxHeight = 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 = rect.w;
135  *pMaxHeight = 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 = rect.w;
143  *pMaxHeight = rect.h;
144 
145  if (freerdp_settings_get_bool(settings, FreeRDP_PercentScreenUseWidth))
146  *pMaxWidth =
147  (rect.w * freerdp_settings_get_uint32(settings, FreeRDP_PercentScreen)) / 100;
148 
149  if (freerdp_settings_get_bool(settings, FreeRDP_PercentScreenUseHeight))
150  *pMaxHeight =
151  (rect.h * freerdp_settings_get_uint32(settings, FreeRDP_PercentScreen)) / 100;
152  }
153  else if (freerdp_settings_get_uint32(settings, FreeRDP_DesktopWidth) &&
154  freerdp_settings_get_uint32(settings, FreeRDP_DesktopHeight))
155  {
156  *pMaxWidth = freerdp_settings_get_uint32(settings, FreeRDP_DesktopWidth);
157  *pMaxHeight = freerdp_settings_get_uint32(settings, FreeRDP_DesktopHeight);
158  }
159  }
160  return TRUE;
161 }
162 
163 static UINT32 sdl_orientaion_to_rdp(SDL_DisplayOrientation orientation)
164 {
165  switch (orientation)
166  {
167  case SDL_ORIENTATION_LANDSCAPE:
168  return ORIENTATION_LANDSCAPE;
169  case SDL_ORIENTATION_LANDSCAPE_FLIPPED:
170  return ORIENTATION_LANDSCAPE_FLIPPED;
171  case SDL_ORIENTATION_PORTRAIT_FLIPPED:
172  return ORIENTATION_PORTRAIT_FLIPPED;
173  case SDL_ORIENTATION_PORTRAIT:
174  default:
175  return ORIENTATION_PORTRAIT;
176  }
177 }
178 
179 static Uint32 scale(Uint32 val, float scale)
180 {
181  const auto dval = static_cast<float>(val);
182  const auto sval = dval / scale;
183  return static_cast<Uint32>(sval);
184 }
185 
186 static BOOL sdl_apply_display_properties(SdlContext* sdl)
187 {
188  WINPR_ASSERT(sdl);
189 
190  rdpSettings* settings = sdl->context()->settings;
191  WINPR_ASSERT(settings);
192  if (!freerdp_settings_get_bool(settings, FreeRDP_Fullscreen) &&
193  !freerdp_settings_get_bool(settings, FreeRDP_UseMultimon))
194  return TRUE;
195 
196  const UINT32 numIds = freerdp_settings_get_uint32(settings, FreeRDP_NumMonitorIds);
197  if (!freerdp_settings_set_pointer_len(settings, FreeRDP_MonitorDefArray, nullptr, numIds))
198  return FALSE;
199  if (!freerdp_settings_set_uint32(settings, FreeRDP_MonitorCount, numIds))
200  return FALSE;
201 
202  for (UINT32 x = 0; x < numIds; x++)
203  {
204  auto id = static_cast<const int*>(
205  freerdp_settings_get_pointer_array(settings, FreeRDP_MonitorIds, x));
206  WINPR_ASSERT(id);
207 
208  float dpi = SDL_GetDisplayContentScale(*id);
209  float hdpi = dpi;
210  float vdpi = dpi;
211  SDL_Rect rect = {};
212 
213  if (!SDL_GetDisplayBounds(*id, &rect))
214  return FALSE;
215 
216  WINPR_ASSERT(rect.w > 0);
217  WINPR_ASSERT(rect.h > 0);
218 
219  bool highDpi = dpi > 100;
220 
221  if (highDpi)
222  {
223  // HighDPI is problematic with SDL: We can only get native resolution by creating a
224  // window. Work around this by checking the supported resolutions (and keep maximum)
225  // Also scale the DPI
226  const SDL_Rect scaleRect = rect;
227  int count = 0;
228  auto modes = SDL_GetFullscreenDisplayModes(x, &count);
229  for (int i = 0; i < count; i++)
230  {
231  auto mode = modes[i];
232  if (!mode)
233  break;
234 
235  if (mode->w > rect.w)
236  {
237  rect.w = mode->w;
238  rect.h = mode->h;
239  }
240  else if (mode->w == rect.w)
241  {
242  if (mode->h > rect.h)
243  {
244  rect.w = mode->w;
245  rect.h = mode->h;
246  }
247  }
248  }
249  SDL_free(modes);
250 
251  const float dw = 1.0f * static_cast<float>(rect.w) / static_cast<float>(scaleRect.w);
252  const float dh = 1.0f * static_cast<float>(rect.h) / static_cast<float>(scaleRect.h);
253  hdpi /= dw;
254  vdpi /= dh;
255  }
256 
257  const SDL_DisplayOrientation orientation = SDL_GetCurrentDisplayOrientation(*id);
258  const UINT32 rdp_orientation = sdl_orientaion_to_rdp(orientation);
259 
260  auto monitor = static_cast<rdpMonitor*>(
261  freerdp_settings_get_pointer_array_writable(settings, FreeRDP_MonitorDefArray, x));
262  WINPR_ASSERT(monitor);
263 
264  /* windows uses 96 dpi as 'default' and the scale factors are in percent. */
265  const auto factor = dpi / 96.0f * 100.0f;
266  monitor->orig_screen = x;
267  monitor->x = rect.x;
268  monitor->y = rect.y;
269  monitor->width = rect.w;
270  monitor->height = rect.h;
271  monitor->is_primary = x == 0;
272  monitor->attributes.desktopScaleFactor = static_cast<UINT32>(factor);
273  monitor->attributes.deviceScaleFactor = 100;
274  monitor->attributes.orientation = rdp_orientation;
275  monitor->attributes.physicalWidth = scale(rect.w, hdpi);
276  monitor->attributes.physicalHeight = scale(rect.h, vdpi);
277  }
278  return TRUE;
279 }
280 
281 static BOOL sdl_detect_single_window(SdlContext* sdl, UINT32* pMaxWidth, UINT32* pMaxHeight)
282 {
283  WINPR_ASSERT(sdl);
284  WINPR_ASSERT(pMaxWidth);
285  WINPR_ASSERT(pMaxHeight);
286 
287  rdpSettings* settings = sdl->context()->settings;
288  WINPR_ASSERT(settings);
289 
290  if ((!freerdp_settings_get_bool(settings, FreeRDP_UseMultimon) &&
291  !freerdp_settings_get_bool(settings, FreeRDP_SpanMonitors)) ||
292  (freerdp_settings_get_bool(settings, FreeRDP_Workarea) &&
293  !freerdp_settings_get_bool(settings, FreeRDP_RemoteApplicationMode)))
294  {
295  /* If no monitors were specified on the command-line then set the current monitor as active
296  */
297  if (freerdp_settings_get_uint32(settings, FreeRDP_NumMonitorIds) == 0)
298  {
299  const size_t id =
300  (!sdl->windows.empty()) ? sdl->windows.begin()->second.displayIndex() : 0;
301  if (!freerdp_settings_set_pointer_len(settings, FreeRDP_MonitorIds, &id, 1))
302  return FALSE;
303  }
304  else
305  {
306 
307  /* Always sets number of monitors from command-line to just 1.
308  * If the monitor is invalid then we will default back to current monitor
309  * later as a fallback. So, there is no need to validate command-line entry here.
310  */
311  if (!freerdp_settings_set_uint32(settings, FreeRDP_NumMonitorIds, 1))
312  return FALSE;
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 TRUE;
321 }
322 
323 BOOL 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_set_pointer_len(settings, FreeRDP_MonitorIds, nullptr, ids.size()))
347  return FALSE;
348 
349  for (size_t x = 0; x < ids.size(); x++)
350  {
351  auto id = ids[x];
352  if (!freerdp_settings_set_pointer_array(settings, FreeRDP_MonitorIds, x, &id))
353  return FALSE;
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<UINT32> 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  auto 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(*cur);
390  }
391  }
392 
393  if (!sdl_apply_display_properties(sdl))
394  return FALSE;
395 
396  return sdl_detect_single_window(sdl, pMaxWidth, pMaxHeight);
397 }
398 
399 INT64 sdl_monitor_id_for_index(SdlContext* sdl, UINT32 index)
400 {
401  WINPR_ASSERT(sdl);
402  auto settings = sdl->context()->settings;
403 
404  auto nr = freerdp_settings_get_uint32(settings, FreeRDP_NumMonitorIds);
405  if (nr == 0)
406  return index;
407 
408  if (nr <= index)
409  return -1;
410 
411  auto cur = static_cast<const UINT32*>(
412  freerdp_settings_get_pointer_array(settings, FreeRDP_MonitorIds, index));
413  WINPR_ASSERT(cur);
414  return *cur;
415 }
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.