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 = monitor->width;
125  *pMaxHeight = monitor->height;
126  }
127  else if (freerdp_settings_get_bool(settings, FreeRDP_Workarea))
128  {
129  SDL_Rect rect = {};
130  SDL_GetDisplayUsableBounds(monitor->orig_screen, &rect);
131  *pMaxWidth = rect.w;
132  *pMaxHeight = rect.h;
133  }
134  else if (freerdp_settings_get_uint32(settings, FreeRDP_PercentScreen) > 0)
135  {
136  SDL_Rect rect = {};
137  SDL_GetDisplayUsableBounds(monitor->orig_screen, &rect);
138 
139  *pMaxWidth = rect.w;
140  *pMaxHeight = rect.h;
141 
142  if (freerdp_settings_get_bool(settings, FreeRDP_PercentScreenUseWidth))
143  *pMaxWidth =
144  (rect.w * freerdp_settings_get_uint32(settings, FreeRDP_PercentScreen)) / 100;
145 
146  if (freerdp_settings_get_bool(settings, FreeRDP_PercentScreenUseHeight))
147  *pMaxHeight =
148  (rect.h * freerdp_settings_get_uint32(settings, FreeRDP_PercentScreen)) / 100;
149  }
150  else if (freerdp_settings_get_uint32(settings, FreeRDP_DesktopWidth) &&
151  freerdp_settings_get_uint32(settings, FreeRDP_DesktopHeight))
152  {
153  *pMaxWidth = freerdp_settings_get_uint32(settings, FreeRDP_DesktopWidth);
154  *pMaxHeight = freerdp_settings_get_uint32(settings, FreeRDP_DesktopHeight);
155  }
156  }
157  return TRUE;
158 }
159 
160 #if SDL_VERSION_ATLEAST(2, 0, 10)
161 static UINT32 sdl_orientaion_to_rdp(SDL_DisplayOrientation orientation)
162 {
163  switch (orientation)
164  {
165  case SDL_ORIENTATION_LANDSCAPE:
166  return ORIENTATION_LANDSCAPE;
167  case SDL_ORIENTATION_LANDSCAPE_FLIPPED:
168  return ORIENTATION_LANDSCAPE_FLIPPED;
169  case SDL_ORIENTATION_PORTRAIT_FLIPPED:
170  return ORIENTATION_PORTRAIT_FLIPPED;
171  case SDL_ORIENTATION_PORTRAIT:
172  default:
173  return ORIENTATION_PORTRAIT;
174  }
175 }
176 #endif
177 
178 static Uint32 scale(Uint32 val, float scale)
179 {
180  const auto dval = static_cast<float>(val);
181  const auto sval = dval / scale;
182  return static_cast<Uint32>(sval);
183 }
184 
185 static BOOL sdl_apply_display_properties(SdlContext* sdl)
186 {
187  WINPR_ASSERT(sdl);
188 
189  rdpSettings* settings = sdl->context()->settings;
190  WINPR_ASSERT(settings);
191 
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 ddpi = 1.0f;
209  float hdpi = 1.0f;
210  float vdpi = 1.0f;
211  SDL_Rect rect = {};
212 
213  if (SDL_GetDisplayBounds(*id, &rect) < 0)
214  return FALSE;
215 
216  if (SDL_GetDisplayDPI(*id, &ddpi, &hdpi, &vdpi) < 0)
217  return FALSE;
218 
219  WINPR_ASSERT(rect.w > 0);
220  WINPR_ASSERT(rect.h > 0);
221  WINPR_ASSERT(ddpi > 0);
222  WINPR_ASSERT(hdpi > 0);
223  WINPR_ASSERT(vdpi > 0);
224 
225  bool highDpi = hdpi > 100;
226 
227  if (highDpi)
228  {
229  // HighDPI is problematic with SDL: We can only get native resolution by creating a
230  // window. Work around this by checking the supported resolutions (and keep maximum)
231  // Also scale the DPI
232  const SDL_Rect scaleRect = rect;
233  for (int i = 0; i < SDL_GetNumDisplayModes(*id); i++)
234  {
235  SDL_DisplayMode mode = {};
236  SDL_GetDisplayMode(x, i, &mode);
237 
238  if (mode.w > rect.w)
239  {
240  rect.w = mode.w;
241  rect.h = mode.h;
242  }
243  else if (mode.w == rect.w)
244  {
245  if (mode.h > rect.h)
246  {
247  rect.w = mode.w;
248  rect.h = mode.h;
249  }
250  }
251  }
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 #if SDL_VERSION_ATLEAST(2, 0, 10)
260  const SDL_DisplayOrientation orientation = SDL_GetDisplayOrientation(*id);
261  const UINT32 rdp_orientation = sdl_orientaion_to_rdp(orientation);
262 #else
263  const UINT32 rdp_orientation = ORIENTATION_LANDSCAPE;
264 #endif
265 
266  auto monitor = static_cast<rdpMonitor*>(
267  freerdp_settings_get_pointer_array_writable(settings, FreeRDP_MonitorDefArray, x));
268  WINPR_ASSERT(monitor);
269 
270  /* windows uses 96 dpi as 'default' and the scale factors are in percent. */
271  const auto factor = ddpi / 96.0f * 100.0f;
272  monitor->orig_screen = x;
273  monitor->x = rect.x;
274  monitor->y = rect.y;
275  monitor->width = rect.w;
276  monitor->height = rect.h;
277  monitor->is_primary = x == 0;
278  monitor->attributes.desktopScaleFactor = static_cast<UINT32>(factor);
279  monitor->attributes.deviceScaleFactor = 100;
280  monitor->attributes.orientation = rdp_orientation;
281  monitor->attributes.physicalWidth = scale(rect.w, hdpi);
282  monitor->attributes.physicalHeight = scale(rect.h, vdpi);
283  }
284  return TRUE;
285 }
286 
287 static BOOL sdl_detect_single_window(SdlContext* sdl, UINT32* pMaxWidth, UINT32* pMaxHeight)
288 {
289  WINPR_ASSERT(sdl);
290  WINPR_ASSERT(pMaxWidth);
291  WINPR_ASSERT(pMaxHeight);
292 
293  rdpSettings* settings = sdl->context()->settings;
294  WINPR_ASSERT(settings);
295 
296  if ((!freerdp_settings_get_bool(settings, FreeRDP_UseMultimon) &&
297  !freerdp_settings_get_bool(settings, FreeRDP_SpanMonitors)) ||
298  (freerdp_settings_get_bool(settings, FreeRDP_Workarea) &&
299  !freerdp_settings_get_bool(settings, FreeRDP_RemoteApplicationMode)))
300  {
301  /* If no monitors were specified on the command-line then set the current monitor as active
302  */
303  if (freerdp_settings_get_uint32(settings, FreeRDP_NumMonitorIds) == 0)
304  {
305  const size_t id =
306  (!sdl->windows.empty()) ? sdl->windows.begin()->second.displayIndex() : 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  const int numDisplays = SDL_GetNumVideoDisplays();
339  auto nr = freerdp_settings_get_uint32(settings, FreeRDP_NumMonitorIds);
340  if (numDisplays < 0)
341  return FALSE;
342 
343  if (nr == 0)
344  {
345  if (!freerdp_settings_set_pointer_len(settings, FreeRDP_MonitorIds, nullptr,
346  static_cast<size_t>(numDisplays)))
347  return FALSE;
348  for (size_t x = 0; x < static_cast<size_t>(numDisplays); x++)
349  {
350  if (!freerdp_settings_set_pointer_array(settings, FreeRDP_MonitorIds, x, &x))
351  return FALSE;
352  }
353  }
354  else
355  {
356 
357  /* There were more IDs supplied than there are monitors */
358  if (nr > static_cast<UINT32>(numDisplays))
359  {
360  WLog_ERR(TAG,
361  "Found %" PRIu32 " monitor IDs, but only have %" PRIu32 " monitors connected",
362  nr, numDisplays);
363  return FALSE;
364  }
365 
366  std::vector<UINT32> used;
367  for (size_t x = 0; x < nr; x++)
368  {
369  auto cur = static_cast<const UINT32*>(
370  freerdp_settings_get_pointer_array(settings, FreeRDP_MonitorIds, x));
371  WINPR_ASSERT(cur);
372 
373  auto id = *cur;
374 
375  /* the ID is no valid monitor index */
376  if (id >= nr)
377  {
378  WLog_ERR(TAG,
379  "Supplied monitor ID[%" PRIuz "]=%" PRIu32 " is invalid, only [0-%" PRIu32
380  "] are allowed",
381  x, id, nr - 1);
382  return FALSE;
383  }
384 
385  /* The ID is already taken */
386  if (std::find(used.begin(), used.end(), id) != used.end())
387  {
388  WLog_ERR(TAG, "Duplicate monitor ID[%" PRIuz "]=%" PRIu32 " detected", x, id);
389  return FALSE;
390  }
391  used.push_back(*cur);
392  }
393  }
394 
395  if (!sdl_apply_display_properties(sdl))
396  return FALSE;
397 
398  return sdl_detect_single_window(sdl, pMaxWidth, pMaxHeight);
399 }
400 
401 INT64 sdl_monitor_id_for_index(SdlContext* sdl, UINT32 index)
402 {
403  WINPR_ASSERT(sdl);
404  auto settings = sdl->context()->settings;
405 
406  auto nr = freerdp_settings_get_uint32(settings, FreeRDP_NumMonitorIds);
407  if (nr == 0)
408  return index;
409 
410  if (nr <= index)
411  return -1;
412 
413  auto cur = static_cast<const UINT32*>(
414  freerdp_settings_get_pointer_array(settings, FreeRDP_MonitorIds, index));
415  WINPR_ASSERT(cur);
416  return *cur;
417 }
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.