FreeRDP
xf_window.c
1 
23 #include <freerdp/config.h>
24 
25 #include <stdarg.h>
26 #include <stdint.h>
27 #include <unistd.h>
28 #include <sys/types.h>
29 
30 #include <X11/Xlib.h>
31 #include <X11/Xutil.h>
32 #include <X11/Xatom.h>
33 
34 #include <sys/mman.h>
35 #include <sys/stat.h>
36 #include <fcntl.h>
37 
38 #include <winpr/assert.h>
39 #include <winpr/thread.h>
40 #include <winpr/crt.h>
41 #include <winpr/string.h>
42 
43 #include <freerdp/rail.h>
44 #include <freerdp/log.h>
45 
46 #ifdef WITH_XEXT
47 #include <X11/extensions/shape.h>
48 #endif
49 
50 #ifdef WITH_XI
51 #include <X11/extensions/XInput2.h>
52 #endif
53 
54 #include "xf_gfx.h"
55 #include "xf_rail.h"
56 #include "xf_input.h"
57 #include "xf_keyboard.h"
58 #include "xf_utils.h"
59 
60 #define TAG CLIENT_TAG("x11")
61 
62 #ifdef WITH_DEBUG_X11
63 #define DEBUG_X11(...) WLog_DBG(TAG, __VA_ARGS__)
64 #else
65 #define DEBUG_X11(...) \
66  do \
67  { \
68  } while (0)
69 #endif
70 
71 #include <FreeRDP_Icon_256px.h>
72 #define xf_icon_prop FreeRDP_Icon_256px_prop
73 
74 #include "xf_window.h"
75 
76 /* Extended Window Manager Hints: http://standards.freedesktop.org/wm-spec/wm-spec-1.3.html */
77 
78 /* bit definitions for MwmHints.flags */
79 #define MWM_HINTS_FUNCTIONS (1L << 0)
80 #define MWM_HINTS_DECORATIONS (1L << 1)
81 // #define MWM_HINTS_INPUT_MODE (1L << 2)
82 // #define MWM_HINTS_STATUS (1L << 3)
83 
84 /* bit definitions for MwmHints.functions */
85 #define MWM_FUNC_ALL (1L << 0)
86 // #define MWM_FUNC_RESIZE (1L << 1)
87 // #define MWM_FUNC_MOVE (1L << 2)
88 // #define MWM_FUNC_MINIMIZE (1L << 3)
89 // #define MWM_FUNC_MAXIMIZE (1L << 4)
90 // #define MWM_FUNC_CLOSE (1L << 5)
91 
92 /* bit definitions for MwmHints.decorations */
93 #define MWM_DECOR_ALL (1L << 0)
94 // #define MWM_DECOR_BORDER (1L << 1)
95 // #define MWM_DECOR_RESIZEH (1L << 2)
96 // #define MWM_DECOR_TITLE (1L << 3)
97 // #define MWM_DECOR_MENU (1L << 4)
98 // #define MWM_DECOR_MINIMIZE (1L << 5)
99 // #define MWM_DECOR_MAXIMIZE (1L << 6)
100 
101 #define PROP_MOTIF_WM_HINTS_ELEMENTS 5
102 
103 #define ENTRY(x) \
104  case x: \
105  return #x
106 
107 typedef struct
108 {
109  unsigned long flags;
110  unsigned long functions;
111  unsigned long decorations;
112  long inputMode;
113  unsigned long status;
114 } PropMotifWmHints;
115 
116 static void xf_XSetTransientForHint(xfContext* xfc, xfAppWindow* window);
117 
118 static const char* window_style_to_string(UINT32 style)
119 {
120  switch (style)
121  {
122  ENTRY(WS_NONE);
123  ENTRY(WS_BORDER);
124  ENTRY(WS_CAPTION);
125  ENTRY(WS_CHILD);
126  ENTRY(WS_CLIPCHILDREN);
127  ENTRY(WS_CLIPSIBLINGS);
128  ENTRY(WS_DISABLED);
129  ENTRY(WS_DLGFRAME);
130  ENTRY(WS_GROUP);
131  ENTRY(WS_HSCROLL);
132  ENTRY(WS_MAXIMIZE);
133  ENTRY(WS_MAXIMIZEBOX);
134  ENTRY(WS_MINIMIZE);
135  ENTRY(WS_OVERLAPPEDWINDOW);
136  ENTRY(WS_POPUP);
137  ENTRY(WS_POPUPWINDOW);
138  ENTRY(WS_SIZEBOX);
139  ENTRY(WS_SYSMENU);
140  ENTRY(WS_VISIBLE);
141  ENTRY(WS_VSCROLL);
142  default:
143  return NULL;
144  }
145 }
146 
147 const char* window_styles_to_string(UINT32 style, char* buffer, size_t length)
148 {
149  (void)_snprintf(buffer, length, "style[0x%08" PRIx32 "] {", style);
150  const char* sep = "";
151  for (size_t x = 0; x < 32; x++)
152  {
153  const UINT32 val = 1 << x;
154  if ((style & val) != 0)
155  {
156  const char* str = window_style_to_string(val);
157  if (str)
158  {
159  winpr_str_append(str, buffer, length, sep);
160  sep = "|";
161  }
162  }
163  }
164  winpr_str_append("}", buffer, length, "");
165 
166  return buffer;
167 }
168 
169 static const char* window_style_ex_to_string(UINT32 styleEx)
170 {
171  switch (styleEx)
172  {
173  ENTRY(WS_EX_ACCEPTFILES);
174  ENTRY(WS_EX_APPWINDOW);
175  ENTRY(WS_EX_CLIENTEDGE);
176  ENTRY(WS_EX_COMPOSITED);
177  ENTRY(WS_EX_CONTEXTHELP);
178  ENTRY(WS_EX_CONTROLPARENT);
179  ENTRY(WS_EX_DLGMODALFRAME);
180  ENTRY(WS_EX_LAYERED);
181  ENTRY(WS_EX_LAYOUTRTL);
182  ENTRY(WS_EX_LEFTSCROLLBAR);
183  ENTRY(WS_EX_MDICHILD);
184  ENTRY(WS_EX_NOACTIVATE);
185  ENTRY(WS_EX_NOINHERITLAYOUT);
186  ENTRY(WS_EX_NOPARENTNOTIFY);
187  ENTRY(WS_EX_OVERLAPPEDWINDOW);
188  ENTRY(WS_EX_PALETTEWINDOW);
189  ENTRY(WS_EX_RIGHT);
190  ENTRY(WS_EX_RIGHTSCROLLBAR);
191  ENTRY(WS_EX_RTLREADING);
192  ENTRY(WS_EX_STATICEDGE);
193  ENTRY(WS_EX_TOOLWINDOW);
194  ENTRY(WS_EX_TOPMOST);
195  ENTRY(WS_EX_TRANSPARENT);
196  ENTRY(WS_EX_WINDOWEDGE);
197  default:
198  return NULL;
199  }
200 }
201 
202 const char* window_styles_ex_to_string(UINT32 styleEx, char* buffer, size_t length)
203 {
204  (void)_snprintf(buffer, length, "styleEx[0x%08" PRIx32 "] {", styleEx);
205  const char* sep = "";
206  for (size_t x = 0; x < 32; x++)
207  {
208  const UINT32 val = (UINT32)(1UL << x);
209  if ((styleEx & val) != 0)
210  {
211  const char* str = window_style_ex_to_string(val);
212  if (str)
213  {
214  winpr_str_append(str, buffer, length, sep);
215  sep = "|";
216  }
217  }
218  }
219  winpr_str_append("}", buffer, length, "");
220 
221  return buffer;
222 }
223 
224 static void xf_SetWindowTitleText(xfContext* xfc, Window window, const char* name)
225 {
226  WINPR_ASSERT(xfc);
227  WINPR_ASSERT(name);
228 
229  const size_t i = strnlen(name, MAX_PATH);
230  XStoreName(xfc->display, window, name);
231  Atom wm_Name = xfc->_NET_WM_NAME;
232  Atom utf8Str = xfc->UTF8_STRING;
233  LogTagAndXChangeProperty(TAG, xfc->display, window, wm_Name, utf8Str, 8, PropModeReplace,
234  (const unsigned char*)name, (int)i);
235 }
236 
240 void xf_SendClientEvent(xfContext* xfc, Window window, Atom atom, unsigned int numArgs, ...)
241 {
242  XEvent xevent = { 0 };
243  va_list argp;
244  va_start(argp, numArgs);
245 
246  xevent.xclient.type = ClientMessage;
247  xevent.xclient.serial = 0;
248  xevent.xclient.send_event = False;
249  xevent.xclient.display = xfc->display;
250  xevent.xclient.window = window;
251  xevent.xclient.message_type = atom;
252  xevent.xclient.format = 32;
253 
254  for (size_t i = 0; i < numArgs; i++)
255  {
256  xevent.xclient.data.l[i] = va_arg(argp, int);
257  }
258 
259  DEBUG_X11("Send ClientMessage Event: wnd=0x%04lX", (unsigned long)xevent.xclient.window);
260  XSendEvent(xfc->display, RootWindowOfScreen(xfc->screen), False,
261  SubstructureRedirectMask | SubstructureNotifyMask, &xevent);
262  XSync(xfc->display, False);
263  va_end(argp);
264 }
265 
266 void xf_SetWindowMinimized(xfContext* xfc, xfWindow* window)
267 {
268  XIconifyWindow(xfc->display, window->handle, xfc->screen_number);
269 }
270 
271 void xf_SetWindowFullscreen(xfContext* xfc, xfWindow* window, BOOL fullscreen)
272 {
273  const rdpSettings* settings = NULL;
274  int startX = 0;
275  int startY = 0;
276  UINT32 width = window->width;
277  UINT32 height = window->height;
278 
279  WINPR_ASSERT(xfc);
280 
281  settings = xfc->common.context.settings;
282  WINPR_ASSERT(settings);
283 
284  /* xfc->decorations is set by caller depending on settings and whether it is fullscreen or not
285  */
286  window->decorations = xfc->decorations;
287  /* show/hide decorations (e.g. title bar) as guided by xfc->decorations */
288  xf_SetWindowDecorations(xfc, window->handle, window->decorations);
289  DEBUG_X11(TAG, "X window decoration set to %d", (int)window->decorations);
290  xf_floatbar_toggle_fullscreen(xfc->window->floatbar, fullscreen);
291 
292  if (fullscreen)
293  {
294  xfc->savedWidth = xfc->window->width;
295  xfc->savedHeight = xfc->window->height;
296  xfc->savedPosX = xfc->window->left;
297  xfc->savedPosY = xfc->window->top;
298 
299  startX = (freerdp_settings_get_uint32(settings, FreeRDP_DesktopPosX) != UINT32_MAX)
300  ? freerdp_settings_get_uint32(settings, FreeRDP_DesktopPosX)
301  : 0;
302  startY = (freerdp_settings_get_uint32(settings, FreeRDP_DesktopPosY) != UINT32_MAX)
303  ? freerdp_settings_get_uint32(settings, FreeRDP_DesktopPosY)
304  : 0;
305  }
306  else
307  {
308  width = xfc->savedWidth;
309  height = xfc->savedHeight;
310  startX = xfc->savedPosX;
311  startY = xfc->savedPosY;
312  }
313 
314  /* Determine the x,y starting location for the fullscreen window */
315  if (fullscreen)
316  {
317  const rdpMonitor* firstMonitor =
318  freerdp_settings_get_pointer_array(settings, FreeRDP_MonitorDefArray, 0);
319  /* Initialize startX and startY with reasonable values */
320  startX = firstMonitor->x;
321  startY = firstMonitor->y;
322 
323  /* Search all monitors to find the lowest startX and startY values */
324  for (size_t i = 0; i < freerdp_settings_get_uint32(settings, FreeRDP_MonitorCount); i++)
325  {
326  const rdpMonitor* monitor =
327  freerdp_settings_get_pointer_array(settings, FreeRDP_MonitorDefArray, i);
328  startX = MIN(startX, monitor->x);
329  startY = MIN(startY, monitor->y);
330  }
331 
332  /* Lastly apply any monitor shift(translation from remote to local coordinate system)
333  * to startX and startY values
334  */
335  startX += freerdp_settings_get_uint32(settings, FreeRDP_MonitorLocalShiftX);
336  startY += freerdp_settings_get_uint32(settings, FreeRDP_MonitorLocalShiftY);
337  }
338 
339  /*
340  It is safe to proceed with simply toogling _NET_WM_STATE_FULLSCREEN window state on the
341  following conditions:
342  - The window manager supports multiple monitor full screen
343  - The user requested to use a single monitor to render the remote desktop
344  */
345  if (xfc->_NET_WM_FULLSCREEN_MONITORS != None ||
346  freerdp_settings_get_uint32(settings, FreeRDP_MonitorCount) == 1)
347  {
348  xf_ResizeDesktopWindow(xfc, window, width, height);
349 
350  if (fullscreen)
351  {
352  /* enter full screen: move the window before adding NET_WM_STATE_FULLSCREEN */
353  XMoveWindow(xfc->display, window->handle, startX, startY);
354  }
355 
356  /* Set the fullscreen state */
357  xf_SendClientEvent(xfc, window->handle, xfc->_NET_WM_STATE, 4,
358  fullscreen ? _NET_WM_STATE_ADD : _NET_WM_STATE_REMOVE,
359  xfc->_NET_WM_STATE_FULLSCREEN, 0, 0);
360 
361  if (!fullscreen)
362  {
363  /* leave full screen: move the window after removing NET_WM_STATE_FULLSCREEN
364  * Resize the window again, the previous call to xf_SendClientEvent might have
365  * changed the window size (borders, ...)
366  */
367  xf_ResizeDesktopWindow(xfc, window, width, height);
368  XMoveWindow(xfc->display, window->handle, startX, startY);
369  }
370 
371  /* Set monitor bounds */
372  if (freerdp_settings_get_uint32(settings, FreeRDP_MonitorCount) > 1)
373  {
374  xf_SendClientEvent(xfc, window->handle, xfc->_NET_WM_FULLSCREEN_MONITORS, 5,
375  xfc->fullscreenMonitors.top, xfc->fullscreenMonitors.bottom,
376  xfc->fullscreenMonitors.left, xfc->fullscreenMonitors.right, 1);
377  }
378  }
379  else
380  {
381  if (fullscreen)
382  {
383  xf_SetWindowDecorations(xfc, window->handle, FALSE);
384 
385  if (xfc->fullscreenMonitors.top)
386  {
387  xf_SendClientEvent(xfc, window->handle, xfc->_NET_WM_STATE, 4, _NET_WM_STATE_ADD,
388  xfc->fullscreenMonitors.top, 0, 0);
389  }
390  else
391  {
392  XSetWindowAttributes xswa;
393  xswa.override_redirect = True;
394  XChangeWindowAttributes(xfc->display, window->handle, CWOverrideRedirect, &xswa);
395  XRaiseWindow(xfc->display, window->handle);
396  xswa.override_redirect = False;
397  XChangeWindowAttributes(xfc->display, window->handle, CWOverrideRedirect, &xswa);
398  }
399 
400  /* if window is in maximized state, save and remove */
401  if (xfc->_NET_WM_STATE_MAXIMIZED_VERT != None)
402  {
403  BYTE state = 0;
404  unsigned long nitems = 0;
405  unsigned long bytes = 0;
406  BYTE* prop = NULL;
407 
408  if (xf_GetWindowProperty(xfc, window->handle, xfc->_NET_WM_STATE, 255, &nitems,
409  &bytes, &prop))
410  {
411  const Atom* aprop = (const Atom*)prop;
412  state = 0;
413 
414  for (size_t x = 0; x < nitems; x++)
415  {
416  if (aprop[x] == xfc->_NET_WM_STATE_MAXIMIZED_VERT)
417  state |= 0x01;
418 
419  if (aprop[x] == xfc->_NET_WM_STATE_MAXIMIZED_HORZ)
420  state |= 0x02;
421  }
422 
423  if (state)
424  {
425  xf_SendClientEvent(xfc, window->handle, xfc->_NET_WM_STATE, 4,
426  _NET_WM_STATE_REMOVE, xfc->_NET_WM_STATE_MAXIMIZED_VERT,
427  0, 0);
428  xf_SendClientEvent(xfc, window->handle, xfc->_NET_WM_STATE, 4,
429  _NET_WM_STATE_REMOVE, xfc->_NET_WM_STATE_MAXIMIZED_HORZ,
430  0, 0);
431  xfc->savedMaximizedState = state;
432  }
433 
434  XFree(prop);
435  }
436  }
437 
438  width = xfc->vscreen.area.right - xfc->vscreen.area.left + 1;
439  height = xfc->vscreen.area.bottom - xfc->vscreen.area.top + 1;
440  DEBUG_X11("X window move and resize %dx%d@%dx%d", startX, startY, width, height);
441  xf_ResizeDesktopWindow(xfc, window, width, height);
442  XMoveWindow(xfc->display, window->handle, startX, startY);
443  }
444  else
445  {
446  xf_SetWindowDecorations(xfc, window->handle, window->decorations);
447  xf_ResizeDesktopWindow(xfc, window, width, height);
448  XMoveWindow(xfc->display, window->handle, startX, startY);
449 
450  if (xfc->fullscreenMonitors.top)
451  {
452  xf_SendClientEvent(xfc, window->handle, xfc->_NET_WM_STATE, 4, _NET_WM_STATE_REMOVE,
453  xfc->fullscreenMonitors.top, 0, 0);
454  }
455 
456  /* restore maximized state, if the window was maximized before setting fullscreen */
457  if (xfc->savedMaximizedState & 0x01)
458  {
459  xf_SendClientEvent(xfc, window->handle, xfc->_NET_WM_STATE, 4, _NET_WM_STATE_ADD,
460  xfc->_NET_WM_STATE_MAXIMIZED_VERT, 0, 0);
461  }
462 
463  if (xfc->savedMaximizedState & 0x02)
464  {
465  xf_SendClientEvent(xfc, window->handle, xfc->_NET_WM_STATE, 4, _NET_WM_STATE_ADD,
466  xfc->_NET_WM_STATE_MAXIMIZED_HORZ, 0, 0);
467  }
468 
469  xfc->savedMaximizedState = 0;
470  }
471  }
472 }
473 
474 /* http://tronche.com/gui/x/xlib/window-information/XGetWindowProperty.html */
475 
476 BOOL xf_GetWindowProperty(xfContext* xfc, Window window, Atom property, int length,
477  unsigned long* nitems, unsigned long* bytes, BYTE** prop)
478 {
479  int status = 0;
480  Atom actual_type = None;
481  int actual_format = 0;
482 
483  if (property == None)
484  return FALSE;
485 
486  status = LogTagAndXGetWindowProperty(TAG, xfc->display, window, property, 0, length, False,
487  AnyPropertyType, &actual_type, &actual_format, nitems,
488  bytes, prop);
489 
490  if (status != Success)
491  return FALSE;
492 
493  if (actual_type == None)
494  {
495  WLog_DBG(TAG, "Property %lu does not exist", (unsigned long)property);
496  return FALSE;
497  }
498 
499  return TRUE;
500 }
501 
502 static BOOL xf_GetNumberOfDesktops(xfContext* xfc, Window root, unsigned* pval)
503 {
504  unsigned long nitems = 0;
505  unsigned long bytes = 0;
506  BYTE* bprop = NULL;
507 
508  WINPR_ASSERT(xfc);
509  WINPR_ASSERT(pval);
510 
511  const BOOL rc =
512  xf_GetWindowProperty(xfc, root, xfc->_NET_NUMBER_OF_DESKTOPS, 1, &nitems, &bytes, &bprop);
513 
514  long* prop = (long*)bprop;
515  *pval = 0;
516  if (!rc)
517  return FALSE;
518 
519  BOOL res = FALSE;
520  if ((*prop >= 0) && (*prop <= UINT32_MAX))
521  {
522  *pval = (UINT32)*prop;
523  res = TRUE;
524  }
525  XFree(prop);
526  return res;
527 }
528 
529 static BOOL xf_GetCurrentDesktop(xfContext* xfc, Window root)
530 {
531  unsigned long nitems = 0;
532  unsigned long bytes = 0;
533  BYTE* bprop = NULL;
534  unsigned max = 0;
535 
536  if (!xf_GetNumberOfDesktops(xfc, root, &max))
537  return FALSE;
538  if (max < 1)
539  return FALSE;
540 
541  const BOOL rc =
542  xf_GetWindowProperty(xfc, root, xfc->_NET_CURRENT_DESKTOP, 1, &nitems, &bytes, &bprop);
543 
544  long* prop = (long*)bprop;
545  xfc->current_desktop = 0;
546  if (!rc)
547  return FALSE;
548 
549  xfc->current_desktop = (int)MIN(max - 1, *prop);
550  XFree(prop);
551  return TRUE;
552 }
553 
554 static BOOL xf_GetWorkArea_NET_WORKAREA(xfContext* xfc, Window root)
555 {
556  BOOL rc = FALSE;
557  unsigned long nitems = 0;
558  unsigned long bytes = 0;
559  BYTE* bprop = NULL;
560 
561  const BOOL status =
562  xf_GetWindowProperty(xfc, root, xfc->_NET_WORKAREA, INT_MAX, &nitems, &bytes, &bprop);
563  long* prop = (long*)bprop;
564 
565  if (!status)
566  goto fail;
567 
568  if ((xfc->current_desktop * 4 + 3) >= (INT64)nitems)
569  goto fail;
570 
571  xfc->workArea.x = (UINT32)MIN(UINT32_MAX, prop[xfc->current_desktop * 4 + 0]);
572  xfc->workArea.y = (UINT32)MIN(UINT32_MAX, prop[xfc->current_desktop * 4 + 1]);
573  xfc->workArea.width = (UINT32)MIN(UINT32_MAX, prop[xfc->current_desktop * 4 + 2]);
574  xfc->workArea.height = (UINT32)MIN(UINT32_MAX, prop[xfc->current_desktop * 4 + 3]);
575 
576  rc = TRUE;
577 fail:
578  XFree(prop);
579  return rc;
580 }
581 
582 BOOL xf_GetWorkArea(xfContext* xfc)
583 {
584  WINPR_ASSERT(xfc);
585 
586  Window root = DefaultRootWindow(xfc->display);
587  (void)xf_GetCurrentDesktop(xfc, root);
588  return xf_GetWorkArea_NET_WORKAREA(xfc, root);
589 }
590 
591 void xf_SetWindowDecorations(xfContext* xfc, Window window, BOOL show)
592 {
593  PropMotifWmHints hints = { .decorations = (show) ? MWM_DECOR_ALL : 0,
594  .functions = MWM_FUNC_ALL,
595  .flags = MWM_HINTS_DECORATIONS | MWM_HINTS_FUNCTIONS,
596  .inputMode = 0,
597  .status = 0 };
598  WINPR_ASSERT(xfc);
599  LogTagAndXChangeProperty(TAG, xfc->display, window, xfc->_MOTIF_WM_HINTS, xfc->_MOTIF_WM_HINTS,
600  32, PropModeReplace, (BYTE*)&hints, PROP_MOTIF_WM_HINTS_ELEMENTS);
601 }
602 
603 void xf_SetWindowUnlisted(xfContext* xfc, Window window)
604 {
605  WINPR_ASSERT(xfc);
606  Atom window_state[] = { xfc->_NET_WM_STATE_SKIP_PAGER, xfc->_NET_WM_STATE_SKIP_TASKBAR };
607  LogTagAndXChangeProperty(TAG, xfc->display, window, xfc->_NET_WM_STATE, XA_ATOM, 32,
608  PropModeReplace, (BYTE*)window_state, 2);
609 }
610 
611 static void xf_SetWindowPID(xfContext* xfc, Window window, pid_t pid)
612 {
613  Atom am_wm_pid = 0;
614 
615  WINPR_ASSERT(xfc);
616  if (!pid)
617  pid = getpid();
618 
619  am_wm_pid = xfc->_NET_WM_PID;
620  LogTagAndXChangeProperty(TAG, xfc->display, window, am_wm_pid, XA_CARDINAL, 32, PropModeReplace,
621  (BYTE*)&pid, 1);
622 }
623 
624 static const char* get_shm_id(void)
625 {
626  static char shm_id[64];
627  (void)sprintf_s(shm_id, sizeof(shm_id), "/com.freerdp.xfreerdp.tsmf_%016X",
628  GetCurrentProcessId());
629  return shm_id;
630 }
631 
632 Window xf_CreateDummyWindow(xfContext* xfc)
633 {
634  return XCreateWindow(xfc->display, RootWindowOfScreen(xfc->screen), xfc->workArea.x,
635  xfc->workArea.y, 1, 1, 0, xfc->depth, InputOutput, xfc->visual,
636  xfc->attribs_mask, &xfc->attribs);
637 }
638 
639 void xf_DestroyDummyWindow(xfContext* xfc, Window window)
640 {
641  if (window)
642  XDestroyWindow(xfc->display, window);
643 }
644 
645 xfWindow* xf_CreateDesktopWindow(xfContext* xfc, char* name, int width, int height)
646 {
647  XEvent xevent = { 0 };
648  int input_mask = 0;
649  XClassHint* classHints = NULL;
650  xfWindow* window = (xfWindow*)calloc(1, sizeof(xfWindow));
651 
652  if (!window)
653  return NULL;
654 
655  rdpSettings* settings = xfc->common.context.settings;
656  WINPR_ASSERT(settings);
657 
658  Window parentWindow = (Window)freerdp_settings_get_uint64(settings, FreeRDP_ParentWindowId);
659  window->width = width;
660  window->height = height;
661  window->decorations = xfc->decorations;
662  window->is_mapped = FALSE;
663  window->is_transient = FALSE;
664 
665  WINPR_ASSERT(xfc->depth != 0);
666  window->handle =
667  XCreateWindow(xfc->display, RootWindowOfScreen(xfc->screen), xfc->workArea.x,
668  xfc->workArea.y, xfc->workArea.width, xfc->workArea.height, 0, xfc->depth,
669  InputOutput, xfc->visual, xfc->attribs_mask, &xfc->attribs);
670  window->shmid = shm_open(get_shm_id(), (O_CREAT | O_RDWR), (S_IREAD | S_IWRITE));
671 
672  if (window->shmid < 0)
673  {
674  DEBUG_X11("xf_CreateDesktopWindow: failed to get access to shared memory - shmget()\n");
675  }
676  else
677  {
678  int rc = ftruncate(window->shmid, sizeof(window->handle));
679  if (rc != 0)
680  {
681 #ifdef WITH_DEBUG_X11
682  char ebuffer[256] = { 0 };
683  DEBUG_X11("ftruncate failed with %s [%d]", winpr_strerror(rc, ebuffer, sizeof(ebuffer)),
684  rc);
685 #endif
686  }
687  else
688  {
689  void* mem = mmap(0, sizeof(window->handle), PROT_READ | PROT_WRITE, MAP_SHARED,
690  window->shmid, 0);
691 
692  if (mem == MAP_FAILED)
693  {
694  DEBUG_X11(
695  "xf_CreateDesktopWindow: failed to assign pointer to the memory address - "
696  "shmat()\n");
697  }
698  else
699  {
700  window->xfwin = mem;
701  *window->xfwin = window->handle;
702  }
703  }
704  }
705 
706  classHints = XAllocClassHint();
707 
708  if (classHints)
709  {
710  classHints->res_name = "xfreerdp";
711 
712  char* res_class = NULL;
713  const char* WmClass = freerdp_settings_get_string(settings, FreeRDP_WmClass);
714  if (WmClass)
715  res_class = _strdup(WmClass);
716  else
717  res_class = _strdup("xfreerdp");
718 
719  classHints->res_class = res_class;
720  XSetClassHint(xfc->display, window->handle, classHints);
721  XFree(classHints);
722  free(res_class);
723  }
724 
725  xf_ResizeDesktopWindow(xfc, window, width, height);
726  xf_SetWindowDecorations(xfc, window->handle, window->decorations);
727  xf_SetWindowPID(xfc, window->handle, 0);
728  input_mask = KeyPressMask | KeyReleaseMask | ButtonPressMask | ButtonReleaseMask |
729  VisibilityChangeMask | FocusChangeMask | StructureNotifyMask | PointerMotionMask |
730  ExposureMask | PropertyChangeMask;
731 
732  if (xfc->grab_keyboard)
733  input_mask |= EnterWindowMask | LeaveWindowMask;
734 
735  LogTagAndXChangeProperty(TAG, xfc->display, window->handle, xfc->_NET_WM_ICON, XA_CARDINAL, 32,
736  PropModeReplace, (BYTE*)xf_icon_prop, ARRAYSIZE(xf_icon_prop));
737 
738  if (parentWindow)
739  XReparentWindow(xfc->display, window->handle, parentWindow, 0, 0);
740 
741  XSelectInput(xfc->display, window->handle, input_mask);
742  XClearWindow(xfc->display, window->handle);
743  xf_SetWindowTitleText(xfc, window->handle, name);
744  XMapWindow(xfc->display, window->handle);
745  xf_input_init(xfc, window->handle);
746 
747  /*
748  * NOTE: This must be done here to handle reparenting the window,
749  * so that we don't miss the event and hang waiting for the next one
750  */
751  do
752  {
753  XMaskEvent(xfc->display, VisibilityChangeMask, &xevent);
754  } while (xevent.type != VisibilityNotify);
755 
756  /*
757  * The XCreateWindow call will start the window in the upper-left corner of our current
758  * monitor instead of the upper-left monitor for remote app mode (which uses all monitors).
759  * This extra call after the window is mapped will position the login window correctly
760  */
761  if (freerdp_settings_get_bool(settings, FreeRDP_RemoteApplicationMode))
762  {
763  XMoveWindow(xfc->display, window->handle, 0, 0);
764  }
765  else if ((freerdp_settings_get_uint32(settings, FreeRDP_DesktopPosX) != UINT32_MAX) &&
766  (freerdp_settings_get_uint32(settings, FreeRDP_DesktopPosY) != UINT32_MAX))
767  {
768  XMoveWindow(xfc->display, window->handle,
769  freerdp_settings_get_uint32(settings, FreeRDP_DesktopPosX),
770  freerdp_settings_get_uint32(settings, FreeRDP_DesktopPosY));
771  }
772 
773  window->floatbar = xf_floatbar_new(xfc, window->handle, name,
774  freerdp_settings_get_uint32(settings, FreeRDP_Floatbar));
775 
776  if (xfc->_XWAYLAND_MAY_GRAB_KEYBOARD)
777  xf_SendClientEvent(xfc, window->handle, xfc->_XWAYLAND_MAY_GRAB_KEYBOARD, 1, 1);
778 
779  return window;
780 }
781 
782 void xf_ResizeDesktopWindow(xfContext* xfc, xfWindow* window, int width, int height)
783 {
784  XSizeHints* size_hints = NULL;
785  rdpSettings* settings = NULL;
786 
787  if (!xfc || !window)
788  return;
789 
790  settings = xfc->common.context.settings;
791  WINPR_ASSERT(settings);
792 
793  if (!(size_hints = XAllocSizeHints()))
794  return;
795 
796  size_hints->flags = PMinSize | PMaxSize | PWinGravity;
797  size_hints->win_gravity = NorthWestGravity;
798  size_hints->min_width = size_hints->min_height = 1;
799  size_hints->max_width = size_hints->max_height = 16384;
800  XResizeWindow(xfc->display, window->handle, width, height);
801 #ifdef WITH_XRENDER
802 
803  if (!freerdp_settings_get_bool(settings, FreeRDP_SmartSizing) &&
804  !freerdp_settings_get_bool(settings, FreeRDP_DynamicResolutionUpdate))
805 #endif
806  {
807  if (!xfc->fullscreen)
808  {
809  /* min == max is an hint for the WM to indicate that the window should
810  * not be resizable */
811  size_hints->min_width = size_hints->max_width = width;
812  size_hints->min_height = size_hints->max_height = height;
813  }
814  }
815 
816  XSetWMNormalHints(xfc->display, window->handle, size_hints);
817  XFree(size_hints);
818 }
819 
820 void xf_DestroyDesktopWindow(xfContext* xfc, xfWindow* window)
821 {
822  if (!window)
823  return;
824 
825  if (xfc->window == window)
826  xfc->window = NULL;
827 
828  xf_floatbar_free(window->floatbar);
829 
830  if (window->gc)
831  XFreeGC(xfc->display, window->gc);
832 
833  if (window->handle)
834  {
835  XUnmapWindow(xfc->display, window->handle);
836  XDestroyWindow(xfc->display, window->handle);
837  }
838 
839  if (window->xfwin)
840  munmap(0, sizeof(*window->xfwin));
841 
842  if (window->shmid >= 0)
843  close(window->shmid);
844 
845  shm_unlink(get_shm_id());
846  window->xfwin = (Window*)-1;
847  window->shmid = -1;
848  free(window);
849 }
850 
851 void xf_SetWindowStyle(xfContext* xfc, xfAppWindow* appWindow, UINT32 style, UINT32 ex_style)
852 {
853  Atom window_type = 0;
854  BOOL redirect = FALSE;
855 
856  window_type = xfc->_NET_WM_WINDOW_TYPE_NORMAL;
857 
858  if ((ex_style & WS_EX_NOACTIVATE) || (ex_style & WS_EX_TOOLWINDOW))
859  {
860  redirect = TRUE;
861  appWindow->is_transient = TRUE;
862  xf_SetWindowUnlisted(xfc, appWindow->handle);
863  window_type = xfc->_NET_WM_WINDOW_TYPE_DROPDOWN_MENU;
864  }
865  /*
866  * TOPMOST window that is not a tool window is treated like a regular window (i.e. task
867  * manager). Want to do this here, since the window may have type WS_POPUP
868  */
869  else if (ex_style & WS_EX_TOPMOST)
870  {
871  window_type = xfc->_NET_WM_WINDOW_TYPE_NORMAL;
872  }
873 
874  if (style & WS_POPUP)
875  {
876  window_type = xfc->_NET_WM_WINDOW_TYPE_DIALOG;
877  /* this includes dialogs, popups, etc, that need to be full-fledged windows */
878 
879  if (!((ex_style & WS_EX_DLGMODALFRAME) || (ex_style & WS_EX_LAYERED) ||
880  (style & WS_SYSMENU)))
881  {
882  appWindow->is_transient = TRUE;
883  redirect = TRUE;
884 
885  xf_SetWindowUnlisted(xfc, appWindow->handle);
886  }
887  }
888 
889  if (!(style == 0 && ex_style == 0))
890  {
891  xf_SetWindowActions(xfc, appWindow);
892  }
893 
894  {
895  /*
896  * Tooltips and menu items should be unmanaged windows
897  * (called "override redirect" in X windows parlance)
898  * If they are managed, there are issues with window focus that
899  * cause the windows to behave improperly. For example, a mouse
900  * press will dismiss a drop-down menu because the RDP server
901  * sees that as a focus out event from the window owning the
902  * dropdown.
903  */
904  XSetWindowAttributes attrs = { 0 };
905  attrs.override_redirect = redirect ? True : False;
906  XChangeWindowAttributes(xfc->display, appWindow->handle, CWOverrideRedirect, &attrs);
907  }
908 
909  LogTagAndXChangeProperty(TAG, xfc->display, appWindow->handle, xfc->_NET_WM_WINDOW_TYPE,
910  XA_ATOM, 32, PropModeReplace, (BYTE*)&window_type, 1);
911 
912  if (ex_style & (WS_EX_CONTROLPARENT | WS_EX_TOOLWINDOW | WS_EX_DLGMODALFRAME))
913  xf_XSetTransientForHint(xfc, appWindow);
914 
915  if (((ex_style & WS_EX_TOPMOST) != 0) && ((ex_style & WS_EX_TOOLWINDOW) == 0))
916  {
917  xf_SendClientEvent(xfc, appWindow->handle, xfc->_NET_WM_STATE, 4, _NET_WM_STATE_ADD,
918  xfc->_NET_WM_STATE_ABOVE, 0, 0);
919  }
920  else
921  {
922  xf_SendClientEvent(xfc, appWindow->handle, xfc->_NET_WM_STATE, 4, _NET_WM_STATE_REMOVE,
923  xfc->_NET_WM_STATE_ABOVE, 0, 0);
924  }
925 }
926 
927 void xf_SetWindowActions(xfContext* xfc, xfAppWindow* appWindow)
928 {
929  Atom allowed_actions[] = {
930  xfc->NET_WM_ACTION_CLOSE, xfc->NET_WM_ACTION_MINIMIZE,
931  xfc->NET_WM_ACTION_MOVE, xfc->NET_WM_ACTION_RESIZE,
932  xfc->NET_WM_ACTION_MAXIMIZE_HORZ, xfc->NET_WM_ACTION_MAXIMIZE_VERT,
933  xfc->NET_WM_ACTION_FULLSCREEN, xfc->NET_WM_ACTION_CHANGE_DESKTOP
934  };
935 
936  if (!(appWindow->dwStyle & WS_SYSMENU))
937  allowed_actions[0] = 0;
938 
939  if (!(appWindow->dwStyle & WS_MINIMIZEBOX))
940  allowed_actions[1] = 0;
941 
942  if (!(appWindow->dwStyle & WS_SIZEBOX))
943  allowed_actions[3] = 0;
944 
945  if (!(appWindow->dwStyle & WS_MAXIMIZEBOX))
946  {
947  allowed_actions[4] = 0;
948  allowed_actions[5] = 0;
949  allowed_actions[6] = 0;
950  }
951 
952  XChangeProperty(xfc->display, appWindow->handle, xfc->NET_WM_ALLOWED_ACTIONS, XA_ATOM, 32,
953  PropModeReplace, (unsigned char*)&allowed_actions, 8);
954 }
955 
956 void xf_SetWindowText(xfContext* xfc, xfAppWindow* appWindow, const char* name)
957 {
958  xf_SetWindowTitleText(xfc, appWindow->handle, name);
959 }
960 
961 static void xf_FixWindowCoordinates(xfContext* xfc, int* x, int* y, int* width, int* height)
962 {
963  int vscreen_width = 0;
964  int vscreen_height = 0;
965  vscreen_width = xfc->vscreen.area.right - xfc->vscreen.area.left + 1;
966  vscreen_height = xfc->vscreen.area.bottom - xfc->vscreen.area.top + 1;
967 
968  if (*x < xfc->vscreen.area.left)
969  {
970  *width += *x;
971  *x = xfc->vscreen.area.left;
972  }
973 
974  if (*y < xfc->vscreen.area.top)
975  {
976  *height += *y;
977  *y = xfc->vscreen.area.top;
978  }
979 
980  if (*width > vscreen_width)
981  {
982  *width = vscreen_width;
983  }
984 
985  if (*height > vscreen_height)
986  {
987  *height = vscreen_height;
988  }
989 
990  if (*width < 1)
991  {
992  *width = 1;
993  }
994 
995  if (*height < 1)
996  {
997  *height = 1;
998  }
999 }
1000 
1001 int xf_AppWindowInit(xfContext* xfc, xfAppWindow* appWindow)
1002 {
1003  if (!xfc || !appWindow)
1004  return -1;
1005 
1006  xf_SetWindowDecorations(xfc, appWindow->handle, appWindow->decorations);
1007  xf_SetWindowStyle(xfc, appWindow, appWindow->dwStyle, appWindow->dwExStyle);
1008  xf_SetWindowPID(xfc, appWindow->handle, 0);
1009  xf_ShowWindow(xfc, appWindow, WINDOW_SHOW);
1010  XClearWindow(xfc->display, appWindow->handle);
1011  XMapWindow(xfc->display, appWindow->handle);
1012  /* Move doesn't seem to work until window is mapped. */
1013  xf_MoveWindow(xfc, appWindow, appWindow->x, appWindow->y, appWindow->width, appWindow->height);
1014  xf_SetWindowText(xfc, appWindow, appWindow->title);
1015  return 1;
1016 }
1017 
1018 BOOL xf_AppWindowCreate(xfContext* xfc, xfAppWindow* appWindow)
1019 {
1020  XGCValues gcv = { 0 };
1021  int input_mask = 0;
1022  XWMHints* InputModeHint = NULL;
1023  XClassHint* class_hints = NULL;
1024  const rdpSettings* settings = NULL;
1025 
1026  WINPR_ASSERT(xfc);
1027  WINPR_ASSERT(appWindow);
1028 
1029  settings = xfc->common.context.settings;
1030  WINPR_ASSERT(settings);
1031 
1032  xf_FixWindowCoordinates(xfc, &appWindow->x, &appWindow->y, &appWindow->width,
1033  &appWindow->height);
1034  appWindow->shmid = -1;
1035  appWindow->decorations = FALSE;
1036  appWindow->fullscreen = FALSE;
1037  appWindow->local_move.state = LMS_NOT_ACTIVE;
1038  appWindow->is_mapped = FALSE;
1039  appWindow->is_transient = FALSE;
1040  appWindow->rail_state = 0;
1041  appWindow->maxVert = FALSE;
1042  appWindow->maxHorz = FALSE;
1043  appWindow->minimized = FALSE;
1044  appWindow->rail_ignore_configure = FALSE;
1045 
1046  WINPR_ASSERT(xfc->depth != 0);
1047  appWindow->handle =
1048  XCreateWindow(xfc->display, RootWindowOfScreen(xfc->screen), appWindow->x, appWindow->y,
1049  appWindow->width, appWindow->height, 0, xfc->depth, InputOutput, xfc->visual,
1050  xfc->attribs_mask, &xfc->attribs);
1051 
1052  if (!appWindow->handle)
1053  return FALSE;
1054 
1055  appWindow->gc = XCreateGC(xfc->display, appWindow->handle, GCGraphicsExposures, &gcv);
1056 
1057  if (!xf_AppWindowResize(xfc, appWindow))
1058  return FALSE;
1059 
1060  class_hints = XAllocClassHint();
1061 
1062  if (class_hints)
1063  {
1064  char* strclass = NULL;
1065 
1066  const char* WmClass = freerdp_settings_get_string(settings, FreeRDP_WmClass);
1067  if (WmClass)
1068  strclass = _strdup(WmClass);
1069  else
1070  {
1071  size_t size = 0;
1072  winpr_asprintf(&strclass, &size, "RAIL:%08" PRIX64 "", appWindow->windowId);
1073  }
1074  class_hints->res_class = strclass;
1075  class_hints->res_name = "RAIL";
1076  XSetClassHint(xfc->display, appWindow->handle, class_hints);
1077  XFree(class_hints);
1078  free(strclass);
1079  }
1080 
1081  /* Set the input mode hint for the WM */
1082  InputModeHint = XAllocWMHints();
1083  InputModeHint->flags = (1L << 0);
1084  InputModeHint->input = True;
1085  XSetWMHints(xfc->display, appWindow->handle, InputModeHint);
1086  XFree(InputModeHint);
1087  XSetWMProtocols(xfc->display, appWindow->handle, &(xfc->WM_DELETE_WINDOW), 1);
1088  input_mask = KeyPressMask | KeyReleaseMask | ButtonPressMask | ButtonReleaseMask |
1089  EnterWindowMask | LeaveWindowMask | PointerMotionMask | Button1MotionMask |
1090  Button2MotionMask | Button3MotionMask | Button4MotionMask | Button5MotionMask |
1091  ButtonMotionMask | KeymapStateMask | ExposureMask | VisibilityChangeMask |
1092  StructureNotifyMask | SubstructureNotifyMask | SubstructureRedirectMask |
1093  FocusChangeMask | PropertyChangeMask | ColormapChangeMask | OwnerGrabButtonMask;
1094  XSelectInput(xfc->display, appWindow->handle, input_mask);
1095 
1096  if (xfc->_XWAYLAND_MAY_GRAB_KEYBOARD)
1097  xf_SendClientEvent(xfc, appWindow->handle, xfc->_XWAYLAND_MAY_GRAB_KEYBOARD, 1, 1);
1098 
1099  return TRUE;
1100 }
1101 
1102 void xf_SetWindowMinMaxInfo(xfContext* xfc, xfAppWindow* appWindow, int maxWidth, int maxHeight,
1103  int maxPosX, int maxPosY, int minTrackWidth, int minTrackHeight,
1104  int maxTrackWidth, int maxTrackHeight)
1105 {
1106  XSizeHints* size_hints = NULL;
1107  size_hints = XAllocSizeHints();
1108 
1109  if (size_hints)
1110  {
1111  size_hints->flags = PMinSize | PMaxSize | PResizeInc;
1112  size_hints->min_width = minTrackWidth;
1113  size_hints->min_height = minTrackHeight;
1114  size_hints->max_width = maxTrackWidth;
1115  size_hints->max_height = maxTrackHeight;
1116  /* to speedup window drawing we need to select optimal value for sizing step. */
1117  size_hints->width_inc = size_hints->height_inc = 1;
1118  XSetWMNormalHints(xfc->display, appWindow->handle, size_hints);
1119  XFree(size_hints);
1120  }
1121 }
1122 
1123 void xf_StartLocalMoveSize(xfContext* xfc, xfAppWindow* appWindow, int direction, int x, int y)
1124 {
1125  if (appWindow->local_move.state != LMS_NOT_ACTIVE)
1126  return;
1127 
1128  /*
1129  * Save original mouse location relative to root. This will be needed
1130  * to end local move to RDP server and/or X server
1131  */
1132  appWindow->local_move.root_x = x;
1133  appWindow->local_move.root_y = y;
1134  appWindow->local_move.state = LMS_STARTING;
1135  appWindow->local_move.direction = direction;
1136 
1137  xf_ungrab(xfc);
1138 
1139  xf_SendClientEvent(
1140  xfc, appWindow->handle,
1141  xfc->_NET_WM_MOVERESIZE, /* request X window manager to initiate a local move */
1142  5, /* 5 arguments to follow */
1143  x, /* x relative to root window */
1144  y, /* y relative to root window */
1145  direction, /* extended ICCM direction flag */
1146  1, /* simulated mouse button 1 */
1147  1); /* 1 == application request per extended ICCM */
1148 }
1149 
1150 void xf_EndLocalMoveSize(xfContext* xfc, xfAppWindow* appWindow)
1151 {
1152  if (appWindow->local_move.state == LMS_NOT_ACTIVE)
1153  return;
1154 
1155  if (appWindow->local_move.state == LMS_STARTING)
1156  {
1157  /*
1158  * The move never was property started. This can happen due to race
1159  * conditions between the mouse button up and the communications to the
1160  * RDP server for local moves. We must cancel the X window manager move.
1161  * Per ICCM, the X client can ask to cancel an active move.
1162  */
1163  xf_SendClientEvent(
1164  xfc, appWindow->handle,
1165  xfc->_NET_WM_MOVERESIZE, /* request X window manager to abort a local move */
1166  5, /* 5 arguments to follow */
1167  appWindow->local_move.root_x, /* x relative to root window */
1168  appWindow->local_move.root_y, /* y relative to root window */
1169  _NET_WM_MOVERESIZE_CANCEL, /* extended ICCM direction flag */
1170  1, /* simulated mouse button 1 */
1171  1); /* 1 == application request per extended ICCM */
1172  }
1173 
1174  appWindow->local_move.state = LMS_NOT_ACTIVE;
1175 }
1176 
1177 void xf_MoveWindow(xfContext* xfc, xfAppWindow* appWindow, int x, int y, int width, int height)
1178 {
1179  BOOL resize = FALSE;
1180 
1181  if ((width * height) < 1)
1182  return;
1183 
1184  if ((appWindow->width != width) || (appWindow->height != height))
1185  resize = TRUE;
1186 
1187  if (appWindow->local_move.state == LMS_STARTING || appWindow->local_move.state == LMS_ACTIVE)
1188  return;
1189 
1190  appWindow->x = x;
1191  appWindow->y = y;
1192  appWindow->width = width;
1193  appWindow->height = height;
1194 
1195  if (resize)
1196  XMoveResizeWindow(xfc->display, appWindow->handle, x, y, width, height);
1197  else
1198  XMoveWindow(xfc->display, appWindow->handle, x, y);
1199 
1200  xf_UpdateWindowArea(xfc, appWindow, 0, 0, width, height);
1201 }
1202 
1203 void xf_ShowWindow(xfContext* xfc, xfAppWindow* appWindow, BYTE state)
1204 {
1205  WINPR_ASSERT(xfc);
1206  WINPR_ASSERT(appWindow);
1207 
1208  switch (state)
1209  {
1210  case WINDOW_HIDE:
1211  XWithdrawWindow(xfc->display, appWindow->handle, xfc->screen_number);
1212  break;
1213 
1214  case WINDOW_SHOW_MINIMIZED:
1215  appWindow->minimized = TRUE;
1216  XIconifyWindow(xfc->display, appWindow->handle, xfc->screen_number);
1217  break;
1218 
1219  case WINDOW_SHOW_MAXIMIZED:
1220  /* Set the window as maximized */
1221  appWindow->maxHorz = TRUE;
1222  appWindow->maxVert = TRUE;
1223  xf_SendClientEvent(xfc, appWindow->handle, xfc->_NET_WM_STATE, 4, _NET_WM_STATE_ADD,
1224  xfc->_NET_WM_STATE_MAXIMIZED_VERT, xfc->_NET_WM_STATE_MAXIMIZED_HORZ,
1225  0);
1226 
1227  /*
1228  * This is a workaround for the case where the window is maximized locally before the
1229  * rail server is told to maximize the window, this appears to be a race condition where
1230  * the local window with incomplete data and once the window is actually maximized on
1231  * the server
1232  * - an update of the new areas may not happen. So, we simply to do a full update of the
1233  * entire window once the rail server notifies us that the window is now maximized.
1234  */
1235  if (appWindow->rail_state == WINDOW_SHOW_MAXIMIZED)
1236  {
1237  xf_UpdateWindowArea(xfc, appWindow, 0, 0, appWindow->windowWidth,
1238  appWindow->windowHeight);
1239  }
1240 
1241  break;
1242 
1243  case WINDOW_SHOW:
1244  /* Ensure the window is not maximized */
1245  xf_SendClientEvent(xfc, appWindow->handle, xfc->_NET_WM_STATE, 4, _NET_WM_STATE_REMOVE,
1246  xfc->_NET_WM_STATE_MAXIMIZED_VERT, xfc->_NET_WM_STATE_MAXIMIZED_HORZ,
1247  0);
1248 
1249  /*
1250  * Ignore configure requests until both the Maximized properties have been processed
1251  * to prevent condition where WM overrides size of request due to one or both of these
1252  * properties still being set - which causes a position adjustment to be sent back to
1253  * the server thus causing the window to not return to its original size
1254  */
1255  if (appWindow->rail_state == WINDOW_SHOW_MAXIMIZED)
1256  appWindow->rail_ignore_configure = TRUE;
1257 
1258  if (appWindow->is_transient)
1259  xf_SetWindowUnlisted(xfc, appWindow->handle);
1260 
1261  XMapWindow(xfc->display, appWindow->handle);
1262  break;
1263  default:
1264  break;
1265  }
1266 
1267  /* Save the current rail state of this window */
1268  appWindow->rail_state = state;
1269  XFlush(xfc->display);
1270 }
1271 
1272 void xf_SetWindowRects(xfContext* xfc, xfAppWindow* appWindow, RECTANGLE_16* rects, int nrects)
1273 {
1274  XRectangle* xrects = NULL;
1275 
1276  if (nrects < 1)
1277  return;
1278 
1279 #ifdef WITH_XEXT
1280  xrects = (XRectangle*)calloc(nrects, sizeof(XRectangle));
1281 
1282  for (int i = 0; i < nrects; i++)
1283  {
1284  xrects[i].x = rects[i].left;
1285  xrects[i].y = rects[i].top;
1286  xrects[i].width = rects[i].right - rects[i].left;
1287  xrects[i].height = rects[i].bottom - rects[i].top;
1288  }
1289 
1290  XShapeCombineRectangles(xfc->display, appWindow->handle, ShapeBounding, 0, 0, xrects, nrects,
1291  ShapeSet, 0);
1292  free(xrects);
1293 #endif
1294 }
1295 
1296 void xf_SetWindowVisibilityRects(xfContext* xfc, xfAppWindow* appWindow, UINT32 rectsOffsetX,
1297  UINT32 rectsOffsetY, RECTANGLE_16* rects, int nrects)
1298 {
1299  XRectangle* xrects = NULL;
1300 
1301  if (nrects < 1)
1302  return;
1303 
1304 #ifdef WITH_XEXT
1305  xrects = (XRectangle*)calloc(nrects, sizeof(XRectangle));
1306 
1307  for (int i = 0; i < nrects; i++)
1308  {
1309  xrects[i].x = rects[i].left;
1310  xrects[i].y = rects[i].top;
1311  xrects[i].width = rects[i].right - rects[i].left;
1312  xrects[i].height = rects[i].bottom - rects[i].top;
1313  }
1314 
1315  XShapeCombineRectangles(xfc->display, appWindow->handle, ShapeBounding, rectsOffsetX,
1316  rectsOffsetY, xrects, nrects, ShapeSet, 0);
1317  free(xrects);
1318 #endif
1319 }
1320 
1321 void xf_UpdateWindowArea(xfContext* xfc, xfAppWindow* appWindow, int x, int y, int width,
1322  int height)
1323 {
1324  int ax = 0;
1325  int ay = 0;
1326  const rdpSettings* settings = NULL;
1327 
1328  WINPR_ASSERT(xfc);
1329 
1330  settings = xfc->common.context.settings;
1331  WINPR_ASSERT(settings);
1332 
1333  if (appWindow == NULL)
1334  return;
1335 
1336  if (appWindow->surfaceId < UINT16_MAX)
1337  return;
1338 
1339  ax = x + appWindow->windowOffsetX;
1340  ay = y + appWindow->windowOffsetY;
1341 
1342  if (ax + width > appWindow->windowOffsetX + appWindow->width)
1343  width = (appWindow->windowOffsetX + appWindow->width - 1) - ax;
1344 
1345  if (ay + height > appWindow->windowOffsetY + appWindow->height)
1346  height = (appWindow->windowOffsetY + appWindow->height - 1) - ay;
1347 
1348  xf_lock_x11(xfc);
1349 
1350  if (freerdp_settings_get_bool(settings, FreeRDP_SoftwareGdi))
1351  {
1352  XPutImage(xfc->display, appWindow->pixmap, appWindow->gc, xfc->image, ax, ay, x, y, width,
1353  height);
1354  }
1355 
1356  XCopyArea(xfc->display, appWindow->pixmap, appWindow->handle, appWindow->gc, x, y, width,
1357  height, x, y);
1358  XFlush(xfc->display);
1359  xf_unlock_x11(xfc);
1360 }
1361 
1362 static void xf_AppWindowDestroyImage(xfAppWindow* appWindow)
1363 {
1364  WINPR_ASSERT(appWindow);
1365  if (appWindow->image)
1366  {
1367  appWindow->image->data = NULL;
1368  XDestroyImage(appWindow->image);
1369  appWindow->image = NULL;
1370  }
1371 }
1372 
1373 void xf_DestroyWindow(xfContext* xfc, xfAppWindow* appWindow)
1374 {
1375  if (!appWindow)
1376  return;
1377 
1378  if (xfc->appWindow == appWindow)
1379  xfc->appWindow = NULL;
1380 
1381  if (appWindow->gc)
1382  XFreeGC(xfc->display, appWindow->gc);
1383 
1384  if (appWindow->pixmap)
1385  XFreePixmap(xfc->display, appWindow->pixmap);
1386 
1387  xf_AppWindowDestroyImage(appWindow);
1388 
1389  if (appWindow->handle)
1390  {
1391  XUnmapWindow(xfc->display, appWindow->handle);
1392  XDestroyWindow(xfc->display, appWindow->handle);
1393  }
1394 
1395  if (appWindow->xfwin)
1396  munmap(0, sizeof(*appWindow->xfwin));
1397 
1398  if (appWindow->shmid >= 0)
1399  close(appWindow->shmid);
1400 
1401  shm_unlink(get_shm_id());
1402  appWindow->xfwin = (Window*)-1;
1403  appWindow->shmid = -1;
1404  free(appWindow->title);
1405  free(appWindow->windowRects);
1406  free(appWindow->visibilityRects);
1407  free(appWindow);
1408 }
1409 
1410 xfAppWindow* xf_AppWindowFromX11Window(xfContext* xfc, Window wnd)
1411 {
1412  ULONG_PTR* pKeys = NULL;
1413 
1414  WINPR_ASSERT(xfc);
1415  if (!xfc->railWindows)
1416  return NULL;
1417 
1418  size_t count = HashTable_GetKeys(xfc->railWindows, &pKeys);
1419 
1420  for (size_t index = 0; index < count; index++)
1421  {
1422  xfAppWindow* appWindow = xf_rail_get_window(xfc, *(UINT64*)pKeys[index]);
1423 
1424  if (!appWindow)
1425  {
1426  free(pKeys);
1427  return NULL;
1428  }
1429 
1430  if (appWindow->handle == wnd)
1431  {
1432  free(pKeys);
1433  return appWindow;
1434  }
1435  }
1436 
1437  free(pKeys);
1438  return NULL;
1439 }
1440 
1441 UINT xf_AppUpdateWindowFromSurface(xfContext* xfc, gdiGfxSurface* surface)
1442 {
1443  XImage* image = NULL;
1444  UINT rc = ERROR_INTERNAL_ERROR;
1445 
1446  WINPR_ASSERT(xfc);
1447  WINPR_ASSERT(surface);
1448 
1449  xfAppWindow* appWindow = xf_rail_get_window(xfc, surface->windowId);
1450  if (!appWindow)
1451  {
1452  WLog_VRB(TAG, "Failed to find a window for id=0x%08" PRIx64, surface->windowId);
1453  return CHANNEL_RC_OK;
1454  }
1455 
1456  const BOOL swGdi = freerdp_settings_get_bool(xfc->common.context.settings, FreeRDP_SoftwareGdi);
1457  UINT32 nrects = 0;
1458  const RECTANGLE_16* rects = region16_rects(&surface->invalidRegion, &nrects);
1459 
1460  xf_lock_x11(xfc);
1461  if (swGdi)
1462  {
1463  if (appWindow->surfaceId != surface->surfaceId)
1464  {
1465  xf_AppWindowDestroyImage(appWindow);
1466  appWindow->surfaceId = surface->surfaceId;
1467  }
1468  if (appWindow->width != (INT64)surface->width)
1469  xf_AppWindowDestroyImage(appWindow);
1470  if (appWindow->height != (INT64)surface->height)
1471  xf_AppWindowDestroyImage(appWindow);
1472 
1473  if (!appWindow->image)
1474  {
1475  WINPR_ASSERT(xfc->depth != 0);
1476  appWindow->image = XCreateImage(xfc->display, xfc->visual, xfc->depth, ZPixmap, 0,
1477  (char*)surface->data, surface->width, surface->height,
1478  xfc->scanline_pad, surface->scanline);
1479  if (!appWindow->image)
1480  {
1481  WLog_WARN(TAG,
1482  "Failed create a XImage[%" PRIu32 "x%" PRIu32 ", scanline=%" PRIu32
1483  ", bpp=%" PRIu32 "] for window id=0x%08" PRIx64,
1484  surface->width, surface->height, surface->scanline, xfc->depth,
1485  surface->windowId);
1486  goto fail;
1487  }
1488  appWindow->image->byte_order = LSBFirst;
1489  appWindow->image->bitmap_bit_order = LSBFirst;
1490  }
1491 
1492  image = appWindow->image;
1493  }
1494  else
1495  {
1496  xfGfxSurface* xfSurface = (xfGfxSurface*)surface;
1497  image = xfSurface->image;
1498  }
1499 
1500  for (UINT32 x = 0; x < nrects; x++)
1501  {
1502  const RECTANGLE_16* rect = &rects[x];
1503  const UINT32 width = rect->right - rect->left;
1504  const UINT32 height = rect->bottom - rect->top;
1505 
1506  XPutImage(xfc->display, appWindow->pixmap, appWindow->gc, image, rect->left, rect->top,
1507  rect->left, rect->top, width, height);
1508 
1509  XCopyArea(xfc->display, appWindow->pixmap, appWindow->handle, appWindow->gc, rect->left,
1510  rect->top, width, height, rect->left, rect->top);
1511  }
1512 
1513  rc = CHANNEL_RC_OK;
1514 fail:
1515  XFlush(xfc->display);
1516  xf_unlock_x11(xfc);
1517  return rc;
1518 }
1519 
1520 BOOL xf_AppWindowResize(xfContext* xfc, xfAppWindow* appWindow)
1521 {
1522  WINPR_ASSERT(xfc);
1523  WINPR_ASSERT(appWindow);
1524 
1525  if (appWindow->pixmap != 0)
1526  XFreePixmap(xfc->display, appWindow->pixmap);
1527 
1528  WINPR_ASSERT(xfc->depth != 0);
1529  appWindow->pixmap =
1530  XCreatePixmap(xfc->display, xfc->drawable, appWindow->width, appWindow->height, xfc->depth);
1531  xf_AppWindowDestroyImage(appWindow);
1532 
1533  return appWindow->pixmap != 0;
1534 }
1535 
1536 void xf_XSetTransientForHint(xfContext* xfc, xfAppWindow* window)
1537 {
1538  WINPR_ASSERT(xfc);
1539  WINPR_ASSERT(window);
1540 
1541  if (window->ownerWindowId == 0)
1542  return;
1543 
1544  xfAppWindow* parent = xf_rail_get_window(xfc, window->ownerWindowId);
1545  if (!parent)
1546  return;
1547 
1548  const int rc = XSetTransientForHint(xfc->display, window->handle, parent->handle);
1549  if (rc)
1550  {
1551  char buffer[128] = { 0 };
1552  WLog_WARN(TAG, "XSetTransientForHint [%d]{%s}", rc,
1553  x11_error_to_string(xfc, rc, buffer, sizeof(buffer)));
1554  }
1555 }
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 UINT64 freerdp_settings_get_uint64(const rdpSettings *settings, FreeRDP_Settings_Keys_UInt64 id)
Returns a UINT64 settings value.
FREERDP_API const char * freerdp_settings_get_string(const rdpSettings *settings, FreeRDP_Settings_Keys_String id)
Returns a immutable string settings value.