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