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