1 /**
2 * FreeRDP: A Remote Desktop Protocol Implementation
3 * X11 Monitor Handling
4 *
5 * Copyright 2011 Marc-Andre Moreau <marcandre.moreau@gmail.com>
6 * Copyright 2017 David Fort <contact@hardening-consulting.com>
7 * Copyright 2018 Kai Harms <kharms@rangee.com>
8 *
9 * Licensed under the Apache License, Version 2.0 (the "License");
10 * you may not use this file except in compliance with the License.
11 * You may obtain a copy of the License at
12 *
13 * http://www.apache.org/licenses/LICENSE-2.0
14 *
15 * Unless required by applicable law or agreed to in writing, software
16 * distributed under the License is distributed on an "AS IS" BASIS,
17 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
18 * See the License for the specific language governing permissions and
19 * limitations under the License.
20 */
21
22 #ifdef HAVE_CONFIG_H
23 #include "config.h"
24 #endif
25
26 #include <stdio.h>
27 #include <stdlib.h>
28 #include <string.h>
29 #include <X11/Xlib.h>
30 #include <X11/Xutil.h>
31
32 #include <winpr/crt.h>
33
34 #include <freerdp/log.h>
35
36 #define TAG CLIENT_TAG("x11")
37
38 #ifdef WITH_XINERAMA
39 #include <X11/extensions/Xinerama.h>
40 #endif
41
42 #ifdef WITH_XRANDR
43 #include <X11/extensions/Xrandr.h>
44 #include <X11/extensions/randr.h>
45
46 #if (RANDR_MAJOR * 100 + RANDR_MINOR) >= 105
47 #define USABLE_XRANDR
48 #endif
49
50 #endif
51
52 #include "xf_monitor.h"
53
54 /* See MSDN Section on Multiple Display Monitors: http://msdn.microsoft.com/en-us/library/dd145071
55 */
56
xf_list_monitors(xfContext * xfc)57 int xf_list_monitors(xfContext* xfc)
58 {
59 Display* display;
60 int major, minor;
61 int i, nmonitors = 0;
62 display = XOpenDisplay(NULL);
63
64 if (!display)
65 {
66 WLog_ERR(TAG, "failed to open X display");
67 return -1;
68 }
69
70 #if defined(USABLE_XRANDR)
71
72 if (XRRQueryExtension(xfc->display, &major, &minor) &&
73 (XRRQueryVersion(xfc->display, &major, &minor) == True) && (major * 100 + minor >= 105))
74 {
75 XRRMonitorInfo* monitors =
76 XRRGetMonitors(xfc->display, DefaultRootWindow(xfc->display), 1, &nmonitors);
77
78 for (i = 0; i < nmonitors; i++)
79 {
80 printf(" %s [%d] %dx%d\t+%d+%d\n", monitors[i].primary ? "*" : " ", i,
81 monitors[i].width, monitors[i].height, monitors[i].x, monitors[i].y);
82 }
83
84 XRRFreeMonitors(monitors);
85 }
86 else
87 #endif
88 #ifdef WITH_XINERAMA
89 if (XineramaQueryExtension(display, &major, &minor))
90 {
91 if (XineramaIsActive(display))
92 {
93 XineramaScreenInfo* screen = XineramaQueryScreens(display, &nmonitors);
94
95 for (i = 0; i < nmonitors; i++)
96 {
97 printf(" %s [%d] %hdx%hd\t+%hd+%hd\n", (i == 0) ? "*" : " ", i,
98 screen[i].width, screen[i].height, screen[i].x_org, screen[i].y_org);
99 }
100
101 XFree(screen);
102 }
103 }
104 else
105 #else
106 {
107 Screen* screen = ScreenOfDisplay(display, DefaultScreen(display));
108 printf(" * [0] %dx%d\t+0+0\n", WidthOfScreen(screen), HeightOfScreen(screen));
109 }
110
111 #endif
112 XCloseDisplay(display);
113 return 0;
114 }
115
xf_is_monitor_id_active(xfContext * xfc,UINT32 id)116 static BOOL xf_is_monitor_id_active(xfContext* xfc, UINT32 id)
117 {
118 UINT32 index;
119 rdpSettings* settings = xfc->context.settings;
120
121 if (!settings->NumMonitorIds)
122 return TRUE;
123
124 for (index = 0; index < settings->NumMonitorIds; index++)
125 {
126 if (settings->MonitorIds[index] == id)
127 return TRUE;
128 }
129
130 return FALSE;
131 }
132
xf_detect_monitors(xfContext * xfc,UINT32 * pMaxWidth,UINT32 * pMaxHeight)133 BOOL xf_detect_monitors(xfContext* xfc, UINT32* pMaxWidth, UINT32* pMaxHeight)
134 {
135 int nmonitors = 0;
136 int monitor_index = 0;
137 BOOL primaryMonitorFound = FALSE;
138 VIRTUAL_SCREEN* vscreen;
139 rdpSettings* settings;
140 int mouse_x, mouse_y, _dummy_i;
141 Window _dummy_w;
142 int current_monitor = 0;
143 Screen* screen;
144 MONITOR_INFO* monitor;
145 #if defined WITH_XINERAMA || defined WITH_XRANDR
146 int major, minor;
147 #endif
148 #if defined(USABLE_XRANDR)
149 XRRMonitorInfo* rrmonitors = NULL;
150 BOOL useXRandr = FALSE;
151 #endif
152
153 if (!xfc || !pMaxWidth || !pMaxHeight || !xfc->context.settings)
154 return FALSE;
155
156 settings = xfc->context.settings;
157 vscreen = &xfc->vscreen;
158 *pMaxWidth = settings->DesktopWidth;
159 *pMaxHeight = settings->DesktopHeight;
160
161 /* get mouse location */
162 if (!XQueryPointer(xfc->display, DefaultRootWindow(xfc->display), &_dummy_w, &_dummy_w,
163 &mouse_x, &mouse_y, &_dummy_i, &_dummy_i, (void*)&_dummy_i))
164 mouse_x = mouse_y = 0;
165
166 #if defined(USABLE_XRANDR)
167
168 if (XRRQueryExtension(xfc->display, &major, &minor) &&
169 (XRRQueryVersion(xfc->display, &major, &minor) == True) && (major * 100 + minor >= 105))
170 {
171 XRRMonitorInfo* rrmonitors =
172 XRRGetMonitors(xfc->display, DefaultRootWindow(xfc->display), 1, &vscreen->nmonitors);
173
174 if (vscreen->nmonitors > 16)
175 vscreen->nmonitors = 0;
176
177 if (vscreen->nmonitors)
178 {
179 int i;
180
181 for (i = 0; i < vscreen->nmonitors; i++)
182 {
183 vscreen->monitors[i].area.left = rrmonitors[i].x;
184 vscreen->monitors[i].area.top = rrmonitors[i].y;
185 vscreen->monitors[i].area.right = rrmonitors[i].x + rrmonitors[i].width - 1;
186 vscreen->monitors[i].area.bottom = rrmonitors[i].y + rrmonitors[i].height - 1;
187 vscreen->monitors[i].primary = rrmonitors[i].primary > 0;
188 }
189 }
190
191 XRRFreeMonitors(rrmonitors);
192 useXRandr = TRUE;
193 }
194 else
195 #endif
196 #ifdef WITH_XINERAMA
197 if (XineramaQueryExtension(xfc->display, &major, &minor) && XineramaIsActive(xfc->display))
198 {
199 XineramaScreenInfo* screenInfo = XineramaQueryScreens(xfc->display, &vscreen->nmonitors);
200
201 if (vscreen->nmonitors > 16)
202 vscreen->nmonitors = 0;
203
204 if (vscreen->nmonitors)
205 {
206 int i;
207
208 for (i = 0; i < vscreen->nmonitors; i++)
209 {
210 vscreen->monitors[i].area.left = screenInfo[i].x_org;
211 vscreen->monitors[i].area.top = screenInfo[i].y_org;
212 vscreen->monitors[i].area.right = screenInfo[i].x_org + screenInfo[i].width - 1;
213 vscreen->monitors[i].area.bottom = screenInfo[i].y_org + screenInfo[i].height - 1;
214 }
215 }
216
217 XFree(screenInfo);
218 }
219
220 #endif
221 xfc->fullscreenMonitors.top = xfc->fullscreenMonitors.bottom = xfc->fullscreenMonitors.left =
222 xfc->fullscreenMonitors.right = 0;
223
224 /* Determine which monitor that the mouse cursor is on */
225 if (vscreen->monitors)
226 {
227 int i;
228
229 for (i = 0; i < vscreen->nmonitors; i++)
230 {
231 if ((mouse_x >= vscreen->monitors[i].area.left) &&
232 (mouse_x <= vscreen->monitors[i].area.right) &&
233 (mouse_y >= vscreen->monitors[i].area.top) &&
234 (mouse_y <= vscreen->monitors[i].area.bottom))
235 {
236 current_monitor = i;
237 break;
238 }
239 }
240 }
241
242 /*
243 Even for a single monitor, we need to calculate the virtual screen to support
244 window managers that do not implement all X window state hints.
245
246 If the user did not request multiple monitor or is using workarea
247 without remote app, we force the number of monitors be 1 so later
248 the rest of the client don't end up using more monitors than the user desires.
249 */
250 if ((!settings->UseMultimon && !settings->SpanMonitors) ||
251 (settings->Workarea && !settings->RemoteApplicationMode))
252 {
253 /* If no monitors were specified on the command-line then set the current monitor as active
254 */
255 if (!settings->NumMonitorIds)
256 {
257 settings->MonitorIds[0] = current_monitor;
258 }
259
260 /* Always sets number of monitors from command-line to just 1.
261 * If the monitor is invalid then we will default back to current monitor
262 * later as a fallback. So, there is no need to validate command-line entry here.
263 */
264 settings->NumMonitorIds = 1;
265 }
266
267 /* WORKAROUND: With Remote Application Mode - using NET_WM_WORKAREA
268 * causes issues with the ability to fully size the window vertically
269 * (the bottom of the window area is never updated). So, we just set
270 * the workArea to match the full Screen width/height.
271 */
272 if (settings->RemoteApplicationMode || !xf_GetWorkArea(xfc))
273 {
274 /*
275 if only 1 monitor is enabled, use monitor area
276 this is required in case of a screen composed of more than one monitor
277 but user did not enable multimonitor
278 */
279 if ((settings->NumMonitorIds == 1) && (vscreen->nmonitors > current_monitor))
280 {
281 monitor = vscreen->monitors + current_monitor;
282
283 if (!monitor)
284 return FALSE;
285
286 xfc->workArea.x = monitor->area.left;
287 xfc->workArea.y = monitor->area.top;
288 xfc->workArea.width = monitor->area.right - monitor->area.left + 1;
289 xfc->workArea.height = monitor->area.bottom - monitor->area.top + 1;
290 }
291 else
292 {
293 xfc->workArea.x = 0;
294 xfc->workArea.y = 0;
295 xfc->workArea.width = WidthOfScreen(xfc->screen);
296 xfc->workArea.height = HeightOfScreen(xfc->screen);
297 }
298 }
299
300 if (settings->Fullscreen)
301 {
302 *pMaxWidth = WidthOfScreen(xfc->screen);
303 *pMaxHeight = HeightOfScreen(xfc->screen);
304 }
305 else if (settings->Workarea)
306 {
307 *pMaxWidth = xfc->workArea.width;
308 *pMaxHeight = xfc->workArea.height;
309 }
310 else if (settings->PercentScreen)
311 {
312 /* If we have specific monitor information then limit the PercentScreen value
313 * to only affect the current monitor vs. the entire desktop
314 */
315 if (vscreen->nmonitors > 0)
316 {
317 if (!vscreen->monitors)
318 return FALSE;
319
320 *pMaxWidth = vscreen->monitors[current_monitor].area.right -
321 vscreen->monitors[current_monitor].area.left + 1;
322 *pMaxHeight = vscreen->monitors[current_monitor].area.bottom -
323 vscreen->monitors[current_monitor].area.top + 1;
324
325 if (settings->PercentScreenUseWidth)
326 *pMaxWidth = ((vscreen->monitors[current_monitor].area.right -
327 vscreen->monitors[current_monitor].area.left + 1) *
328 settings->PercentScreen) /
329 100;
330
331 if (settings->PercentScreenUseHeight)
332 *pMaxHeight = ((vscreen->monitors[current_monitor].area.bottom -
333 vscreen->monitors[current_monitor].area.top + 1) *
334 settings->PercentScreen) /
335 100;
336 }
337 else
338 {
339 *pMaxWidth = xfc->workArea.width;
340 *pMaxHeight = xfc->workArea.height;
341
342 if (settings->PercentScreenUseWidth)
343 *pMaxWidth = (xfc->workArea.width * settings->PercentScreen) / 100;
344
345 if (settings->PercentScreenUseHeight)
346 *pMaxHeight = (xfc->workArea.height * settings->PercentScreen) / 100;
347 }
348 }
349 else if (settings->DesktopWidth && settings->DesktopHeight)
350 {
351 *pMaxWidth = settings->DesktopWidth;
352 *pMaxHeight = settings->DesktopHeight;
353 }
354
355 /* Create array of all active monitors by taking into account monitors requested on the
356 * command-line */
357 {
358 int i;
359
360 for (i = 0; i < vscreen->nmonitors; i++)
361 {
362 MONITOR_ATTRIBUTES* attrs;
363
364 if (!xf_is_monitor_id_active(xfc, (UINT32)i))
365 continue;
366
367 if (!vscreen->monitors)
368 return FALSE;
369
370 settings->MonitorDefArray[nmonitors].x =
371 (vscreen->monitors[i].area.left *
372 (settings->PercentScreenUseWidth ? settings->PercentScreen : 100)) /
373 100;
374 settings->MonitorDefArray[nmonitors].y =
375 (vscreen->monitors[i].area.top *
376 (settings->PercentScreenUseHeight ? settings->PercentScreen : 100)) /
377 100;
378 settings->MonitorDefArray[nmonitors].width =
379 ((vscreen->monitors[i].area.right - vscreen->monitors[i].area.left + 1) *
380 (settings->PercentScreenUseWidth ? settings->PercentScreen : 100)) /
381 100;
382 settings->MonitorDefArray[nmonitors].height =
383 ((vscreen->monitors[i].area.bottom - vscreen->monitors[i].area.top + 1) *
384 (settings->PercentScreenUseWidth ? settings->PercentScreen : 100)) /
385 100;
386 settings->MonitorDefArray[nmonitors].orig_screen = i;
387 #ifdef USABLE_XRANDR
388
389 if (useXRandr && rrmonitors)
390 {
391 Rotation rot, ret;
392 attrs = &settings->MonitorDefArray[nmonitors].attributes;
393 attrs->physicalWidth = rrmonitors[i].mwidth;
394 attrs->physicalHeight = rrmonitors[i].mheight;
395 ret = XRRRotations(xfc->display, i, &rot);
396 attrs->orientation = rot;
397 }
398
399 #endif
400
401 if ((UINT32)i == settings->MonitorIds[0])
402 {
403 settings->MonitorDefArray[nmonitors].is_primary = TRUE;
404 settings->MonitorLocalShiftX = settings->MonitorDefArray[nmonitors].x;
405 settings->MonitorLocalShiftY = settings->MonitorDefArray[nmonitors].y;
406 primaryMonitorFound = TRUE;
407 }
408
409 nmonitors++;
410 }
411 }
412
413 /* If no monitor is active(bogus command-line monitor specification) - then lets try to fallback
414 * to go fullscreen on the current monitor only */
415 if (nmonitors == 0 && vscreen->nmonitors > 0)
416 {
417 INT32 width, height;
418 if (!vscreen->monitors)
419 return FALSE;
420
421 width = vscreen->monitors[current_monitor].area.right -
422 vscreen->monitors[current_monitor].area.left + 1L;
423 height = vscreen->monitors[current_monitor].area.bottom -
424 vscreen->monitors[current_monitor].area.top + 1L;
425
426 settings->MonitorDefArray[0].x = vscreen->monitors[current_monitor].area.left;
427 settings->MonitorDefArray[0].y = vscreen->monitors[current_monitor].area.top;
428 settings->MonitorDefArray[0].width = MIN(width, (INT64)(*pMaxWidth));
429 settings->MonitorDefArray[0].height = MIN(height, (INT64)(*pMaxHeight));
430 settings->MonitorDefArray[0].orig_screen = current_monitor;
431 nmonitors = 1;
432 }
433
434 settings->MonitorCount = nmonitors;
435
436 /* If we have specific monitor information */
437 if (settings->MonitorCount)
438 {
439 UINT32 i;
440 /* Initialize bounding rectangle for all monitors */
441 int vX = settings->MonitorDefArray[0].x;
442 int vY = settings->MonitorDefArray[0].y;
443 int vR = vX + settings->MonitorDefArray[0].width;
444 int vB = vY + settings->MonitorDefArray[0].height;
445 xfc->fullscreenMonitors.top = xfc->fullscreenMonitors.bottom =
446 xfc->fullscreenMonitors.left = xfc->fullscreenMonitors.right =
447 settings->MonitorDefArray[0].orig_screen;
448
449 /* Calculate bounding rectangle around all monitors to be used AND
450 * also set the Xinerama indices which define left/top/right/bottom monitors.
451 */
452 for (i = 1; i < settings->MonitorCount; i++)
453 {
454 /* does the same as gdk_rectangle_union */
455 int destX = MIN(vX, settings->MonitorDefArray[i].x);
456 int destY = MIN(vY, settings->MonitorDefArray[i].y);
457 int destR =
458 MAX(vR, settings->MonitorDefArray[i].x + settings->MonitorDefArray[i].width);
459 int destB =
460 MAX(vB, settings->MonitorDefArray[i].y + settings->MonitorDefArray[i].height);
461
462 if (vX != destX)
463 xfc->fullscreenMonitors.left = settings->MonitorDefArray[i].orig_screen;
464
465 if (vY != destY)
466 xfc->fullscreenMonitors.top = settings->MonitorDefArray[i].orig_screen;
467
468 if (vR != destR)
469 xfc->fullscreenMonitors.right = settings->MonitorDefArray[i].orig_screen;
470
471 if (vB != destB)
472 xfc->fullscreenMonitors.bottom = settings->MonitorDefArray[i].orig_screen;
473
474 vX = destX / ((settings->PercentScreenUseWidth ? settings->PercentScreen : 100) / 100.);
475 vY =
476 destY / ((settings->PercentScreenUseHeight ? settings->PercentScreen : 100) / 100.);
477 vR = destR / ((settings->PercentScreenUseWidth ? settings->PercentScreen : 100) / 100.);
478 vB =
479 destB / ((settings->PercentScreenUseHeight ? settings->PercentScreen : 100) / 100.);
480 }
481
482 vscreen->area.left = 0;
483 vscreen->area.right = vR - vX - 1;
484 vscreen->area.top = 0;
485 vscreen->area.bottom = vB - vY - 1;
486
487 if (settings->Workarea)
488 {
489 vscreen->area.top = xfc->workArea.y;
490 vscreen->area.bottom = xfc->workArea.height + xfc->workArea.y - 1;
491 }
492
493 if (!primaryMonitorFound)
494 {
495 /* If we have a command line setting we should use it */
496 if (settings->NumMonitorIds)
497 {
498 /* The first monitor is the first in the setting which should be used */
499 monitor_index = settings->MonitorIds[0];
500 }
501 else
502 {
503 /* This is the same as when we would trust the Xinerama results..
504 and set the monitor index to zero.
505 The monitor listed with /monitor-list on index zero is always the primary
506 */
507 screen = DefaultScreenOfDisplay(xfc->display);
508 monitor_index = XScreenNumberOfScreen(screen);
509 }
510
511 int j = monitor_index;
512
513 /* If the "default" monitor is not 0,0 use it */
514 if (settings->MonitorDefArray[j].x != 0 || settings->MonitorDefArray[j].y != 0)
515 {
516 settings->MonitorDefArray[j].is_primary = TRUE;
517 settings->MonitorLocalShiftX = settings->MonitorDefArray[j].x;
518 settings->MonitorLocalShiftY = settings->MonitorDefArray[j].y;
519 primaryMonitorFound = TRUE;
520 }
521 else
522 {
523 /* Lets try to see if there is a monitor with a 0,0 coordinate and use it as a
524 * fallback*/
525 for (i = 0; i < settings->MonitorCount; i++)
526 {
527 if (!primaryMonitorFound && settings->MonitorDefArray[i].x == 0 &&
528 settings->MonitorDefArray[i].y == 0)
529 {
530 settings->MonitorDefArray[i].is_primary = TRUE;
531 settings->MonitorLocalShiftX = settings->MonitorDefArray[i].x;
532 settings->MonitorLocalShiftY = settings->MonitorDefArray[i].y;
533 primaryMonitorFound = TRUE;
534 }
535 }
536 }
537 }
538
539 /* Subtract monitor shift from monitor variables for server-side use.
540 * We maintain monitor shift value as Window requires the primary monitor to have a
541 * coordinate of 0,0 In some X configurations, no monitor may have a coordinate of 0,0. This
542 * can also be happen if the user requests specific monitors from the command-line as well.
543 * So, we make sure to translate our primary monitor's upper-left corner to 0,0 on the
544 * server.
545 */
546 for (i = 0; i < settings->MonitorCount; i++)
547 {
548 settings->MonitorDefArray[i].x =
549 settings->MonitorDefArray[i].x - settings->MonitorLocalShiftX;
550 settings->MonitorDefArray[i].y =
551 settings->MonitorDefArray[i].y - settings->MonitorLocalShiftY;
552 }
553
554 /* Set the desktop width and height according to the bounding rectangle around the active
555 * monitors */
556 *pMaxWidth = MIN(*pMaxWidth, (UINT32)vscreen->area.right - vscreen->area.left + 1);
557 *pMaxHeight = MIN(*pMaxHeight, (UINT32)vscreen->area.bottom - vscreen->area.top + 1);
558 }
559
560 /* some 2008 server freeze at logon if we announce support for monitor layout PDU with
561 * #monitors < 2. So let's announce it only if we have more than 1 monitor.
562 */
563 if (settings->MonitorCount)
564 settings->SupportMonitorLayoutPdu = TRUE;
565
566 #ifdef USABLE_XRANDR
567
568 if (rrmonitors)
569 XRRFreeMonitors(rrmonitors);
570
571 #endif
572 return TRUE;
573 }
574