1 /**
2  * FreeRDP: A Remote Desktop Protocol Implementation
3  * X11 Display Control channel
4  *
5  * Copyright 2017 David Fort <contact@hardening-consulting.com>
6  *
7  * Licensed under the Apache License, Version 2.0 (the "License");
8  * you may not use this file except in compliance with the License.
9  * You may obtain a copy of the License at
10  *
11  *     http://www.apache.org/licenses/LICENSE-2.0
12  *
13  * Unless required by applicable law or agreed to in writing, software
14  * distributed under the License is distributed on an "AS IS" BASIS,
15  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16  * See the License for the specific language governing permissions and
17  * limitations under the License.
18  */
19 
20 #include <winpr/sysinfo.h>
21 #include <X11/Xutil.h>
22 
23 #ifdef WITH_XRANDR
24 #include <X11/extensions/Xrandr.h>
25 #include <X11/extensions/randr.h>
26 
27 #if (RANDR_MAJOR * 100 + RANDR_MINOR) >= 105
28 #define USABLE_XRANDR
29 #endif
30 
31 #endif
32 
33 #include "xf_disp.h"
34 #include "xf_monitor.h"
35 
36 #define TAG CLIENT_TAG("x11disp")
37 #define RESIZE_MIN_DELAY 200 /* minimum delay in ms between two resizes */
38 
39 struct _xfDispContext
40 {
41 	xfContext* xfc;
42 	DispClientContext* disp;
43 	BOOL haveXRandr;
44 	int eventBase, errorBase;
45 	int lastSentWidth, lastSentHeight;
46 	UINT64 lastSentDate;
47 	int targetWidth, targetHeight;
48 	BOOL activated;
49 	BOOL fullscreen;
50 	UINT16 lastSentDesktopOrientation;
51 	UINT32 lastSentDesktopScaleFactor;
52 	UINT32 lastSentDeviceScaleFactor;
53 };
54 
55 static UINT xf_disp_sendLayout(DispClientContext* disp, rdpMonitor* monitors, int nmonitors);
56 
xf_disp_settings_changed(xfDispContext * xfDisp)57 static BOOL xf_disp_settings_changed(xfDispContext* xfDisp)
58 {
59 	rdpSettings* settings = xfDisp->xfc->context.settings;
60 
61 	if (xfDisp->lastSentWidth != xfDisp->targetWidth)
62 		return TRUE;
63 
64 	if (xfDisp->lastSentHeight != xfDisp->targetHeight)
65 		return TRUE;
66 
67 	if (xfDisp->lastSentDesktopOrientation != settings->DesktopOrientation)
68 		return TRUE;
69 
70 	if (xfDisp->lastSentDesktopScaleFactor != settings->DesktopScaleFactor)
71 		return TRUE;
72 
73 	if (xfDisp->lastSentDeviceScaleFactor != settings->DeviceScaleFactor)
74 		return TRUE;
75 
76 	if (xfDisp->fullscreen != xfDisp->xfc->fullscreen)
77 		return TRUE;
78 
79 	return FALSE;
80 }
81 
xf_update_last_sent(xfDispContext * xfDisp)82 static BOOL xf_update_last_sent(xfDispContext* xfDisp)
83 {
84 	rdpSettings* settings = xfDisp->xfc->context.settings;
85 	xfDisp->lastSentWidth = xfDisp->targetWidth;
86 	xfDisp->lastSentHeight = xfDisp->targetHeight;
87 	xfDisp->lastSentDesktopOrientation = settings->DesktopOrientation;
88 	xfDisp->lastSentDesktopScaleFactor = settings->DesktopScaleFactor;
89 	xfDisp->lastSentDeviceScaleFactor = settings->DeviceScaleFactor;
90 	xfDisp->fullscreen = xfDisp->xfc->fullscreen;
91 	return TRUE;
92 }
93 
xf_disp_sendResize(xfDispContext * xfDisp)94 static BOOL xf_disp_sendResize(xfDispContext* xfDisp)
95 {
96 	DISPLAY_CONTROL_MONITOR_LAYOUT layout;
97 	xfContext* xfc;
98 	rdpSettings* settings;
99 
100 	if (!xfDisp || !xfDisp->xfc)
101 		return FALSE;
102 
103 	xfc = xfDisp->xfc;
104 	settings = xfc->context.settings;
105 
106 	if (!settings)
107 		return FALSE;
108 
109 	if (!xfDisp->activated || !xfDisp->disp)
110 		return TRUE;
111 
112 	if (GetTickCount64() - xfDisp->lastSentDate < RESIZE_MIN_DELAY)
113 		return TRUE;
114 
115 	if (!xf_disp_settings_changed(xfDisp))
116 		return TRUE;
117 
118 	xfDisp->lastSentDate = GetTickCount64();
119 	if (xfc->fullscreen && (settings->MonitorCount > 0))
120 	{
121 		if (xf_disp_sendLayout(xfDisp->disp, settings->MonitorDefArray, settings->MonitorCount) !=
122 		    CHANNEL_RC_OK)
123 			return FALSE;
124 	}
125 	else
126 	{
127 		layout.Flags = DISPLAY_CONTROL_MONITOR_PRIMARY;
128 		layout.Top = layout.Left = 0;
129 		layout.Width = xfDisp->targetWidth;
130 		layout.Height = xfDisp->targetHeight;
131 		layout.Orientation = settings->DesktopOrientation;
132 		layout.DesktopScaleFactor = settings->DesktopScaleFactor;
133 		layout.DeviceScaleFactor = settings->DeviceScaleFactor;
134 		layout.PhysicalWidth = xfDisp->targetWidth / 75 * 25.4f;
135 		layout.PhysicalHeight = xfDisp->targetHeight / 75 * 25.4f;
136 
137 		if (IFCALLRESULT(CHANNEL_RC_OK, xfDisp->disp->SendMonitorLayout, xfDisp->disp, 1,
138 		                 &layout) != CHANNEL_RC_OK)
139 			return FALSE;
140 	}
141 
142 	return xf_update_last_sent(xfDisp);
143 }
144 
xf_disp_queueResize(xfDispContext * xfDisp,UINT32 width,UINT32 height)145 static BOOL xf_disp_queueResize(xfDispContext* xfDisp, UINT32 width, UINT32 height)
146 {
147 	if ((xfDisp->targetWidth == width) && (xfDisp->targetHeight == height))
148 		return TRUE;
149 	xfDisp->targetWidth = width;
150 	xfDisp->targetHeight = height;
151 	xfDisp->lastSentDate = GetTickCount64();
152 	return xf_disp_sendResize(xfDisp);
153 }
154 
xf_disp_set_window_resizable(xfDispContext * xfDisp)155 static BOOL xf_disp_set_window_resizable(xfDispContext* xfDisp)
156 {
157 	XSizeHints* size_hints;
158 
159 	if (!(size_hints = XAllocSizeHints()))
160 		return FALSE;
161 
162 	size_hints->flags = PMinSize | PMaxSize | PWinGravity;
163 	size_hints->win_gravity = NorthWestGravity;
164 	size_hints->min_width = size_hints->min_height = 320;
165 	size_hints->max_width = size_hints->max_height = 8192;
166 
167 	if (xfDisp->xfc->window)
168 		XSetWMNormalHints(xfDisp->xfc->display, xfDisp->xfc->window->handle, size_hints);
169 
170 	XFree(size_hints);
171 	return TRUE;
172 }
173 
xf_disp_check_context(void * context,xfContext ** ppXfc,xfDispContext ** ppXfDisp,rdpSettings ** ppSettings)174 static BOOL xf_disp_check_context(void* context, xfContext** ppXfc, xfDispContext** ppXfDisp,
175                                   rdpSettings** ppSettings)
176 {
177 	xfContext* xfc;
178 
179 	if (!context)
180 		return FALSE;
181 
182 	xfc = (xfContext*)context;
183 
184 	if (!(xfc->xfDisp))
185 		return FALSE;
186 
187 	if (!xfc->context.settings)
188 		return FALSE;
189 
190 	*ppXfc = xfc;
191 	*ppXfDisp = xfc->xfDisp;
192 	*ppSettings = xfc->context.settings;
193 	return TRUE;
194 }
195 
xf_disp_OnActivated(void * context,ActivatedEventArgs * e)196 static void xf_disp_OnActivated(void* context, ActivatedEventArgs* e)
197 {
198 	xfContext* xfc;
199 	xfDispContext* xfDisp;
200 	rdpSettings* settings;
201 
202 	if (!xf_disp_check_context(context, &xfc, &xfDisp, &settings))
203 		return;
204 
205 	if (xfDisp->activated && !xfc->fullscreen)
206 	{
207 		xf_disp_set_window_resizable(xfDisp);
208 
209 		if (e->firstActivation)
210 			return;
211 
212 		xf_disp_sendResize(xfDisp);
213 	}
214 }
215 
xf_disp_OnGraphicsReset(void * context,GraphicsResetEventArgs * e)216 static void xf_disp_OnGraphicsReset(void* context, GraphicsResetEventArgs* e)
217 {
218 	xfContext* xfc;
219 	xfDispContext* xfDisp;
220 	rdpSettings* settings;
221 
222 	WINPR_UNUSED(e);
223 
224 	if (!xf_disp_check_context(context, &xfc, &xfDisp, &settings))
225 		return;
226 
227 	if (xfDisp->activated && !settings->Fullscreen)
228 	{
229 		xf_disp_set_window_resizable(xfDisp);
230 		xf_disp_sendResize(xfDisp);
231 	}
232 }
233 
xf_disp_OnTimer(void * context,TimerEventArgs * e)234 static void xf_disp_OnTimer(void* context, TimerEventArgs* e)
235 {
236 	xfContext* xfc;
237 	xfDispContext* xfDisp;
238 	rdpSettings* settings;
239 
240 	WINPR_UNUSED(e);
241 
242 	if (!xf_disp_check_context(context, &xfc, &xfDisp, &settings))
243 		return;
244 
245 	if (!xfDisp->activated || settings->Fullscreen)
246 		return;
247 
248 	xf_disp_sendResize(xfDisp);
249 }
250 
xf_disp_new(xfContext * xfc)251 xfDispContext* xf_disp_new(xfContext* xfc)
252 {
253 	xfDispContext* ret;
254 
255 	if (!xfc || !xfc->context.settings || !xfc->context.pubSub)
256 		return NULL;
257 
258 	ret = calloc(1, sizeof(xfDispContext));
259 
260 	if (!ret)
261 		return NULL;
262 
263 	ret->xfc = xfc;
264 #ifdef USABLE_XRANDR
265 
266 	if (XRRQueryExtension(xfc->display, &ret->eventBase, &ret->errorBase))
267 	{
268 		ret->haveXRandr = TRUE;
269 	}
270 
271 #endif
272 	ret->lastSentWidth = ret->targetWidth = xfc->context.settings->DesktopWidth;
273 	ret->lastSentHeight = ret->targetHeight = xfc->context.settings->DesktopHeight;
274 	PubSub_SubscribeActivated(xfc->context.pubSub, xf_disp_OnActivated);
275 	PubSub_SubscribeGraphicsReset(xfc->context.pubSub, xf_disp_OnGraphicsReset);
276 	PubSub_SubscribeTimer(xfc->context.pubSub, xf_disp_OnTimer);
277 	return ret;
278 }
279 
xf_disp_free(xfDispContext * disp)280 void xf_disp_free(xfDispContext* disp)
281 {
282 	if (!disp)
283 		return;
284 
285 	if (disp->xfc)
286 	{
287 		PubSub_UnsubscribeActivated(disp->xfc->context.pubSub, xf_disp_OnActivated);
288 		PubSub_UnsubscribeGraphicsReset(disp->xfc->context.pubSub, xf_disp_OnGraphicsReset);
289 		PubSub_UnsubscribeTimer(disp->xfc->context.pubSub, xf_disp_OnTimer);
290 	}
291 
292 	free(disp);
293 }
294 
xf_disp_sendLayout(DispClientContext * disp,rdpMonitor * monitors,int nmonitors)295 UINT xf_disp_sendLayout(DispClientContext* disp, rdpMonitor* monitors, int nmonitors)
296 {
297 	UINT ret = CHANNEL_RC_OK;
298 	DISPLAY_CONTROL_MONITOR_LAYOUT* layouts;
299 	int i;
300 	xfDispContext* xfDisp = (xfDispContext*)disp->custom;
301 	rdpSettings* settings = xfDisp->xfc->context.settings;
302 	layouts = calloc(nmonitors, sizeof(DISPLAY_CONTROL_MONITOR_LAYOUT));
303 
304 	if (!layouts)
305 		return CHANNEL_RC_NO_MEMORY;
306 
307 	for (i = 0; i < nmonitors; i++)
308 	{
309 		layouts[i].Flags = (monitors[i].is_primary ? DISPLAY_CONTROL_MONITOR_PRIMARY : 0);
310 		layouts[i].Left = monitors[i].x;
311 		layouts[i].Top = monitors[i].y;
312 		layouts[i].Width = monitors[i].width;
313 		layouts[i].Height = monitors[i].height;
314 		layouts[i].Orientation = ORIENTATION_LANDSCAPE;
315 		layouts[i].PhysicalWidth = monitors[i].attributes.physicalWidth;
316 		layouts[i].PhysicalHeight = monitors[i].attributes.physicalHeight;
317 
318 		switch (monitors[i].attributes.orientation)
319 		{
320 			case 90:
321 				layouts[i].Orientation = ORIENTATION_PORTRAIT;
322 				break;
323 
324 			case 180:
325 				layouts[i].Orientation = ORIENTATION_LANDSCAPE_FLIPPED;
326 				break;
327 
328 			case 270:
329 				layouts[i].Orientation = ORIENTATION_PORTRAIT_FLIPPED;
330 				break;
331 
332 			case 0:
333 			default:
334 				/* MS-RDPEDISP - 2.2.2.2.1:
335 				 * Orientation (4 bytes): A 32-bit unsigned integer that specifies the
336 				 * orientation of the monitor in degrees. Valid values are 0, 90, 180
337 				 * or 270
338 				 *
339 				 * So we default to ORIENTATION_LANDSCAPE
340 				 */
341 				layouts[i].Orientation = ORIENTATION_LANDSCAPE;
342 				break;
343 		}
344 
345 		layouts[i].DesktopScaleFactor = settings->DesktopScaleFactor;
346 		layouts[i].DeviceScaleFactor = settings->DeviceScaleFactor;
347 	}
348 
349 	ret = IFCALLRESULT(CHANNEL_RC_OK, disp->SendMonitorLayout, disp, nmonitors, layouts);
350 	free(layouts);
351 	return ret;
352 }
353 
xf_disp_handle_xevent(xfContext * xfc,const XEvent * event)354 BOOL xf_disp_handle_xevent(xfContext* xfc, const XEvent* event)
355 {
356 	xfDispContext* xfDisp;
357 	rdpSettings* settings;
358 	UINT32 maxWidth, maxHeight;
359 
360 	if (!xfc || !event)
361 		return FALSE;
362 
363 	xfDisp = xfc->xfDisp;
364 
365 	if (!xfDisp)
366 		return FALSE;
367 
368 	settings = xfc->context.settings;
369 
370 	if (!settings)
371 		return FALSE;
372 
373 	if (!xfDisp->haveXRandr || !xfDisp->disp)
374 		return TRUE;
375 
376 #ifdef USABLE_XRANDR
377 
378 	if (event->type != xfDisp->eventBase + RRScreenChangeNotify)
379 		return TRUE;
380 
381 #endif
382 	xf_detect_monitors(xfc, &maxWidth, &maxHeight);
383 	return xf_disp_sendLayout(xfDisp->disp, settings->MonitorDefArray, settings->MonitorCount) ==
384 	       CHANNEL_RC_OK;
385 }
386 
xf_disp_handle_configureNotify(xfContext * xfc,int width,int height)387 BOOL xf_disp_handle_configureNotify(xfContext* xfc, int width, int height)
388 {
389 	xfDispContext* xfDisp;
390 
391 	if (!xfc)
392 		return FALSE;
393 
394 	xfDisp = xfc->xfDisp;
395 
396 	if (!xfDisp)
397 		return FALSE;
398 
399 	return xf_disp_queueResize(xfDisp, width, height);
400 }
401 
xf_DisplayControlCaps(DispClientContext * disp,UINT32 maxNumMonitors,UINT32 maxMonitorAreaFactorA,UINT32 maxMonitorAreaFactorB)402 static UINT xf_DisplayControlCaps(DispClientContext* disp, UINT32 maxNumMonitors,
403                                   UINT32 maxMonitorAreaFactorA, UINT32 maxMonitorAreaFactorB)
404 {
405 	/* we're called only if dynamic resolution update is activated */
406 	xfDispContext* xfDisp = (xfDispContext*)disp->custom;
407 	rdpSettings* settings = xfDisp->xfc->context.settings;
408 	WLog_DBG(TAG,
409 	         "DisplayControlCapsPdu: MaxNumMonitors: %" PRIu32 " MaxMonitorAreaFactorA: %" PRIu32
410 	         " MaxMonitorAreaFactorB: %" PRIu32 "",
411 	         maxNumMonitors, maxMonitorAreaFactorA, maxMonitorAreaFactorB);
412 	xfDisp->activated = TRUE;
413 
414 	if (settings->Fullscreen)
415 		return CHANNEL_RC_OK;
416 
417 	WLog_DBG(TAG, "DisplayControlCapsPdu: setting the window as resizable");
418 	return xf_disp_set_window_resizable(xfDisp) ? CHANNEL_RC_OK : CHANNEL_RC_NO_MEMORY;
419 }
420 
xf_disp_init(xfDispContext * xfDisp,DispClientContext * disp)421 BOOL xf_disp_init(xfDispContext* xfDisp, DispClientContext* disp)
422 {
423 	rdpSettings* settings;
424 
425 	if (!xfDisp || !xfDisp->xfc || !disp)
426 		return FALSE;
427 
428 	settings = xfDisp->xfc->context.settings;
429 
430 	if (!settings)
431 		return FALSE;
432 
433 	xfDisp->disp = disp;
434 	disp->custom = (void*)xfDisp;
435 
436 	if (settings->DynamicResolutionUpdate)
437 	{
438 		disp->DisplayControlCaps = xf_DisplayControlCaps;
439 #ifdef USABLE_XRANDR
440 
441 		if (settings->Fullscreen)
442 		{
443 			/* ask X11 to notify us of screen changes */
444 			XRRSelectInput(xfDisp->xfc->display, DefaultRootWindow(xfDisp->xfc->display),
445 			               RRScreenChangeNotifyMask);
446 		}
447 
448 #endif
449 	}
450 
451 	return TRUE;
452 }
453 
xf_disp_uninit(xfDispContext * xfDisp,DispClientContext * disp)454 BOOL xf_disp_uninit(xfDispContext* xfDisp, DispClientContext* disp)
455 {
456 	if (!xfDisp || !disp)
457 		return FALSE;
458 
459 	xfDisp->disp = NULL;
460 	return TRUE;
461 }
462