FreeRDP
Loading...
Searching...
No Matches
xf_rail.c
1
20#include <freerdp/config.h>
21
22#include <X11/Xlib.h>
23#include <X11/Xatom.h>
24#include <X11/Xutil.h>
25
26#include <winpr/cast.h>
27#include <winpr/assert.h>
28#include <winpr/wlog.h>
29#include <winpr/print.h>
30#include <winpr/sysinfo.h>
31
32#include <freerdp/client/rail.h>
33
34#include "xf_window.h"
35#include "xf_rail.h"
36#include "xf_utils.h"
37
38#include <freerdp/log.h>
39
40WINPR_ATTR_NODISCARD
41static const char* error_code2str(UINT32 code)
42{
43#define EVCASE(x) \
44 case x: \
45 return #x
46 switch (code)
47 {
48 EVCASE(RAIL_EXEC_S_OK);
49 EVCASE(RAIL_EXEC_E_HOOK_NOT_LOADED);
50 EVCASE(RAIL_EXEC_E_DECODE_FAILED);
51 EVCASE(RAIL_EXEC_E_NOT_IN_ALLOWLIST);
52 EVCASE(RAIL_EXEC_E_FILE_NOT_FOUND);
53 EVCASE(RAIL_EXEC_E_FAIL);
54 EVCASE(RAIL_EXEC_E_SESSION_LOCKED);
55 default:
56 return "RAIL_EXEC_E_UNKNOWN";
57 }
58#undef EVCASE
59}
60
61WINPR_ATTR_NODISCARD
62static const char* movetype2str(UINT32 code)
63{
64#define EVCASE(x) \
65 case x: \
66 return #x
67
68 switch (code)
69 {
70
71 EVCASE(RAIL_WMSZ_LEFT);
72 EVCASE(RAIL_WMSZ_RIGHT);
73 EVCASE(RAIL_WMSZ_TOP);
74 EVCASE(RAIL_WMSZ_TOPLEFT);
75 EVCASE(RAIL_WMSZ_TOPRIGHT);
76 EVCASE(RAIL_WMSZ_BOTTOM);
77 EVCASE(RAIL_WMSZ_BOTTOMLEFT);
78 EVCASE(RAIL_WMSZ_BOTTOMRIGHT);
79 EVCASE(RAIL_WMSZ_MOVE);
80 EVCASE(RAIL_WMSZ_KEYMOVE);
81 EVCASE(RAIL_WMSZ_KEYSIZE);
82 default:
83 return "RAIL_WMSZ_INVALID";
84 }
85#undef EVCASE
86}
87
88struct xf_rail_icon
89{
90 long* data;
91 int length;
92};
93typedef struct xf_rail_icon xfRailIcon;
94
95struct xf_rail_icon_cache
96{
97 xfRailIcon* entries;
98 UINT32 numCaches;
99 UINT32 numCacheEntries;
100 xfRailIcon scratch;
101};
102
103typedef struct
104{
105 xfContext* xfc;
106 const RECTANGLE_16* rect;
107} rail_paint_fn_arg_t;
108
109BOOL xf_rail_enable_remoteapp_mode(xfContext* xfc)
110{
111 WINPR_ASSERT(xfc);
112 if (!xfc->remote_app)
113 {
114 rdpGdi* gdi = xfc->common.context.gdi;
115 WINPR_ASSERT(gdi);
116
117 const BOOL old = gdi->suppressOutput;
118 gdi->suppressOutput = TRUE;
119 xfc->remote_app = TRUE;
120 xfc->drawable = xf_CreateDummyWindow(xfc);
121 xf_DestroyDesktopWindow(xfc, xfc->window);
122 xfc->window = nullptr;
123
124 gdi->suppressOutput = old;
125 }
126 return TRUE;
127}
128
129BOOL xf_rail_disable_remoteapp_mode(xfContext* xfc)
130{
131 WINPR_ASSERT(xfc);
132 if (xfc->remote_app)
133 {
134 rdpGdi* gdi = xfc->common.context.gdi;
135 WINPR_ASSERT(gdi);
136
137 const BOOL old = gdi->suppressOutput;
138 gdi->suppressOutput = TRUE;
139
140 xfc->remote_app = FALSE;
141 xf_DestroyDummyWindow(xfc, xfc->drawable);
142 xf_destroy_window(xfc);
143 xf_create_window(xfc);
144 xf_create_image(xfc);
145
146 gdi->suppressOutput = old;
147 }
148 return TRUE;
149}
150
151WINPR_ATTR_NODISCARD
152static BOOL xf_rail_select_workarea_events(xfContext* xfc)
153{
154 WINPR_ASSERT(xfc);
155
156 XWindowAttributes attributes = WINPR_C_ARRAY_INIT;
157 const Window root = DefaultRootWindow(xfc->display);
158 const int rc = LogDynAndXGetWindowAttributes(xfc->log, xfc->display, root, &attributes);
159 if (rc != 1)
160 return FALSE;
161
162 LogDynAndXSelectInput(xfc->log, xfc->display, root,
163 attributes.your_event_mask | PropertyChangeMask);
164 return TRUE;
165}
166
167WINPR_ATTR_NODISCARD
168static BOOL xf_rail_get_workarea(xfContext* xfc, xfWorkArea* workArea)
169{
170 WINPR_ASSERT(xfc);
171 WINPR_ASSERT(workArea);
172
173 /* xf_GetWorkArea overwrites xfc->workArea; restore it (RAIL keeps it full). */
174 const xfWorkArea saved = xfc->workArea;
175 const BOOL rc = xf_GetWorkArea(xfc);
176 *workArea = xfc->workArea;
177 xfc->workArea = saved;
178 return rc;
179}
180
181/* Report the real work area so the server maximizes RemoteApp windows within the panel. */
182WINPR_ATTR_NODISCARD
183static UINT xf_rail_send_workarea(xfContext* xfc)
184{
185 WINPR_ASSERT(xfc);
186
187 if (!xfc->remote_app || !xfc->rail)
188 return CHANNEL_RC_OK;
189
190 xfWorkArea current = WINPR_C_ARRAY_INIT;
191 if (!xf_rail_get_workarea(xfc, &current))
192 return CHANNEL_RC_OK;
193
194 if ((current.x == xfc->railWorkArea.x) && (current.y == xfc->railWorkArea.y) &&
195 (current.width == xfc->railWorkArea.width) && (current.height == xfc->railWorkArea.height))
196 {
197 WLog_Print(xfc->log, WLOG_DEBUG,
198 "Suppressing work area %" PRId32 ",%" PRId32 " %" PRIu32 "x%" PRIu32
199 ": unchanged",
200 current.x, current.y, current.width, current.height);
201 return CHANNEL_RC_OK;
202 }
203
204 const INT64 right = (INT64)current.x + current.width;
205 const INT64 bottom = (INT64)current.y + current.height;
206 const RAIL_SYSPARAM_ORDER sysparam = { .params = SPI_MASK_SET_WORK_AREA,
207 .workArea.left =
208 WINPR_CXX_COMPAT_CAST(UINT16, current.x),
209 .workArea.top = WINPR_CXX_COMPAT_CAST(UINT16, current.y),
210 .workArea.right = WINPR_CXX_COMPAT_CAST(UINT16, right),
211 .workArea.bottom =
212 WINPR_CXX_COMPAT_CAST(UINT16, bottom) };
213
214 WLog_Print(xfc->log, WLOG_DEBUG,
215 "Sending work area %" PRId32 ",%" PRId32 " %" PRIu32 "x%" PRIu32, current.x,
216 current.y, current.width, current.height);
217 const UINT rc = xfc->rail->ClientSystemParam(xfc->rail, &sysparam);
218 if (rc == CHANNEL_RC_OK)
219 xfc->railWorkArea = current;
220
221 return rc;
222}
223
224BOOL xf_rail_schedule_workarea(xfContext* xfc)
225{
226 WINPR_ASSERT(xfc);
227
228 xfWorkArea current = WINPR_C_ARRAY_INIT;
229 if (!xf_rail_get_workarea(xfc, &current))
230 {
231 WLog_Print(xfc->log, WLOG_DEBUG, "Suppressing work area update: current value unavailable");
232 return TRUE;
233 }
234
235 return xf_rail_send_workarea(xfc) == CHANNEL_RC_OK;
236}
237
238BOOL xf_rail_send_activate(xfContext* xfc, Window xwindow, BOOL enabled)
239{
240 RAIL_ACTIVATE_ORDER activate = WINPR_C_ARRAY_INIT;
241 xfAppWindow* appWindow = xf_AppWindowFromX11Window(xfc, xwindow);
242
243 if (!appWindow)
244 {
245 WLog_Print(xfc->log, WLOG_DEBUG, "xf_rail_send_activate: ignoring unknown window 0x%08lx",
246 xwindow);
247 return TRUE;
248 }
249
250 if (enabled)
251 xf_SetWindowStyle(xfc, appWindow, appWindow->dwStyle, appWindow->dwExStyle);
252
253 WINPR_ASSERT(appWindow->windowId <= UINT32_MAX);
254 activate.windowId = (UINT32)appWindow->windowId;
255 xf_rail_return_window(appWindow, FALSE);
256
257 activate.enabled = enabled;
258 const UINT rc = xfc->rail->ClientActivate(xfc->rail, &activate);
259 return rc == CHANNEL_RC_OK;
260}
261
262BOOL xf_rail_send_client_system_command(xfContext* xfc, UINT64 windowId, UINT16 command)
263{
264 WINPR_ASSERT(xfc);
265 WINPR_ASSERT(xfc->rail);
266 WINPR_ASSERT(xfc->rail->ClientSystemCommand);
267 if (windowId > UINT32_MAX)
268 return FALSE;
269
270 const RAIL_SYSCOMMAND_ORDER syscommand = { .windowId = (UINT32)windowId, .command = command };
271 const UINT rc = xfc->rail->ClientSystemCommand(xfc->rail, &syscommand);
272 return rc == CHANNEL_RC_OK;
273}
274
281BOOL xf_rail_adjust_position(xfContext* xfc, xfAppWindow* appWindow)
282{
283 RAIL_WINDOW_MOVE_ORDER windowMove = WINPR_C_ARRAY_INIT;
284
285 WINPR_ASSERT(xfc);
286 WINPR_ASSERT(appWindow);
287
288 if (!appWindow->is_mapped || appWindow->local_move.state != LMS_NOT_ACTIVE)
289 return FALSE;
290
291 /* NOTE: this geometry report is required - if it is dropped the server no
292 * longer knows where the window manager placed the window and all mouse
293 * events are routed to the wrong coordinates (buttons stop responding).
294 * The window size feedback loop is handled in xf_rail_window_common(). */
295 if (appWindow->x != appWindow->windowOffsetX || appWindow->y != appWindow->windowOffsetY ||
296 appWindow->width != (INT64)appWindow->windowWidth ||
297 appWindow->height != (INT64)appWindow->windowHeight)
298 {
299 WINPR_ASSERT(appWindow->windowId <= UINT32_MAX);
300 windowMove.windowId = (UINT32)appWindow->windowId;
301 /*
302 * Calculate new size/position for the rail window(new values for
303 * windowOffsetX/windowOffsetY/windowWidth/windowHeight) on the server
304 */
305 const INT16 left = WINPR_ASSERTING_INT_CAST(INT16, appWindow->resizeMarginLeft);
306 const INT16 right = WINPR_ASSERTING_INT_CAST(INT16, appWindow->resizeMarginRight);
307 const INT16 top = WINPR_ASSERTING_INT_CAST(INT16, appWindow->resizeMarginTop);
308 const INT16 bottom = WINPR_ASSERTING_INT_CAST(INT16, appWindow->resizeMarginBottom);
309 windowMove.left = WINPR_ASSERTING_INT_CAST(INT16, appWindow->x - left);
310 windowMove.top = WINPR_ASSERTING_INT_CAST(INT16, appWindow->y - top);
311 windowMove.right = WINPR_ASSERTING_INT_CAST(INT16, appWindow->x + appWindow->width + right);
312 windowMove.bottom =
313 WINPR_ASSERTING_INT_CAST(INT16, appWindow->y + appWindow->height + bottom);
314 const UINT rc = xfc->rail->ClientWindowMove(xfc->rail, &windowMove);
315 return rc == CHANNEL_RC_OK;
316 }
317 return TRUE;
318}
319
320BOOL xf_rail_end_local_move(xfContext* xfc, xfAppWindow* appWindow)
321{
322 int x = 0;
323 int y = 0;
324 int child_x = 0;
325 int child_y = 0;
326 unsigned int mask = 0;
327 Window root_window = 0;
328 Window child_window = 0;
329
330 WINPR_ASSERT(xfc);
331 WINPR_ASSERT(appWindow);
332
333 if ((appWindow->local_move.direction == NET_WM_MOVERESIZE_MOVE_KEYBOARD) ||
334 (appWindow->local_move.direction == NET_WM_MOVERESIZE_SIZE_KEYBOARD))
335 {
336 RAIL_WINDOW_MOVE_ORDER windowMove = WINPR_C_ARRAY_INIT;
337
338 /*
339 * For keyboard moves send and explicit update to RDP server
340 */
341 WINPR_ASSERT(appWindow->windowId <= UINT32_MAX);
342 windowMove.windowId = (UINT32)appWindow->windowId;
343 /*
344 * Calculate new size/position for the rail window(new values for
345 * windowOffsetX/windowOffsetY/windowWidth/windowHeight) on the server
346 *
347 */
348 const INT16 left = WINPR_ASSERTING_INT_CAST(INT16, appWindow->resizeMarginLeft);
349 const INT16 right = WINPR_ASSERTING_INT_CAST(INT16, appWindow->resizeMarginRight);
350 const INT16 top = WINPR_ASSERTING_INT_CAST(INT16, appWindow->resizeMarginTop);
351 const INT16 bottom = WINPR_ASSERTING_INT_CAST(INT16, appWindow->resizeMarginBottom);
352 const INT16 w = WINPR_ASSERTING_INT_CAST(INT16, appWindow->width + right);
353 const INT16 h = WINPR_ASSERTING_INT_CAST(INT16, appWindow->height + bottom);
354 windowMove.left = WINPR_ASSERTING_INT_CAST(INT16, appWindow->x - left);
355 windowMove.top = WINPR_ASSERTING_INT_CAST(INT16, appWindow->y - top);
356 windowMove.right = WINPR_ASSERTING_INT_CAST(INT16, appWindow->x + w); /* In the update to
357 RDP the position is one past the window */
358 windowMove.bottom = WINPR_ASSERTING_INT_CAST(INT16, appWindow->y + h);
359 const UINT rc = xfc->rail->ClientWindowMove(xfc->rail, &windowMove);
360 if (rc != CHANNEL_RC_OK)
361 return FALSE;
362 }
363
364 /*
365 * Simulate button up at new position to end the local move (per RDP spec)
366 */
367 XQueryPointer(xfc->display, appWindow->handle, &root_window, &child_window, &x, &y, &child_x,
368 &child_y, &mask);
369
370 /* only send the mouse coordinates if not a keyboard move or size */
371 if ((appWindow->local_move.direction != NET_WM_MOVERESIZE_MOVE_KEYBOARD) &&
372 (appWindow->local_move.direction != NET_WM_MOVERESIZE_SIZE_KEYBOARD))
373 {
374 if (!freerdp_client_send_button_event(&xfc->common, FALSE, PTR_FLAGS_BUTTON1, x, y))
375 return FALSE;
376 }
377
378 /*
379 * Proactively update the RAIL window dimensions. There is a race condition where
380 * we can start to receive GDI orders for the new window dimensions before we
381 * receive the RAIL ORDER for the new window size. This avoids that race condition.
382 */
383 appWindow->windowOffsetX = appWindow->x;
384 appWindow->windowOffsetY = appWindow->y;
385 appWindow->windowWidth = WINPR_ASSERTING_INT_CAST(uint32_t, appWindow->width);
386 appWindow->windowHeight = WINPR_ASSERTING_INT_CAST(uint32_t, appWindow->height);
387 appWindow->local_move.state = LMS_TERMINATING;
388 return TRUE;
389}
390
391BOOL xf_rail_paint_surface(xfContext* xfc, UINT64 windowId, const RECTANGLE_16* rect)
392{
393 BOOL rc = FALSE;
394 BOOL redraw = FALSE;
395 REGION16 windowInvalidRegion = WINPR_C_ARRAY_INIT;
396 xfAppWindow* appWindow = xf_rail_get_window(xfc, windowId, FALSE);
397
398 WINPR_ASSERT(rect);
399
400 if (!appWindow)
401 return FALSE;
402
403 const RECTANGLE_16 windowRect = {
404 .left = WINPR_ASSERTING_INT_CAST(UINT16, MAX(appWindow->x, 0)),
405 .top = WINPR_ASSERTING_INT_CAST(UINT16, MAX(appWindow->y, 0)),
406 .right = WINPR_ASSERTING_INT_CAST(UINT16, MAX(appWindow->x + appWindow->width, 0)),
407 .bottom = WINPR_ASSERTING_INT_CAST(UINT16, MAX(appWindow->y + appWindow->height, 0))
408 };
409
410 region16_init(&windowInvalidRegion);
411 if (!region16_union_rect(&windowInvalidRegion, &windowInvalidRegion, &windowRect))
412 goto fail;
413 if (!region16_intersect_rect(&windowInvalidRegion, &windowInvalidRegion, rect))
414 goto fail;
415
416 if (!region16_is_empty(&windowInvalidRegion))
417 {
418 const RECTANGLE_16* extents = region16_extents(&windowInvalidRegion);
419
420 const RECTANGLE_16 updateRect = {
421 .left = WINPR_ASSERTING_INT_CAST(UINT16, extents->left - appWindow->x),
422 .top = WINPR_ASSERTING_INT_CAST(UINT16, extents->top - appWindow->y),
423 .right = WINPR_ASSERTING_INT_CAST(UINT16, extents->right - appWindow->x),
424 .bottom = WINPR_ASSERTING_INT_CAST(UINT16, extents->bottom - appWindow->y)
425 };
426
427 redraw = xf_UpdateWindowArea(xfc, appWindow, updateRect.left, updateRect.top,
428 updateRect.right - updateRect.left,
429 updateRect.bottom - updateRect.top);
430 }
431 rc = TRUE;
432fail:
433{
434 const UINT32 surfaceId = appWindow->surfaceId;
435 region16_uninit(&windowInvalidRegion);
436 xf_rail_return_window(appWindow, FALSE);
437 if (redraw)
438 xf_AppWindowRepaintFromSurface(xfc, windowId, surfaceId);
439}
440 return rc;
441}
442
443WINPR_ATTR_NODISCARD
444static BOOL rail_paint_fn(const void* pvkey, WINPR_ATTR_UNUSED void* value, void* pvarg)
445{
446 rail_paint_fn_arg_t* arg = pvarg;
447 WINPR_ASSERT(pvkey);
448 WINPR_ASSERT(arg);
449
450 const UINT64 key = *(const UINT64*)pvkey;
451 return xf_rail_paint_surface(arg->xfc, key, arg->rect);
452}
453
454BOOL xf_rail_paint(xfContext* xfc, const RECTANGLE_16* rect)
455{
456 rail_paint_fn_arg_t arg = { .xfc = xfc, .rect = rect };
457
458 WINPR_ASSERT(xfc);
459 WINPR_ASSERT(rect);
460
461 if (!xfc->railWindows)
462 return TRUE;
463
464 return HashTable_Foreach(xfc->railWindows, rail_paint_fn, &arg);
465}
466
467#define window_state_log_style(log, windowState) \
468 window_state_log_style_int((log), (windowState), __FILE__, __func__, __LINE__)
469static void window_state_log_style_int(wLog* log, const WINDOW_STATE_ORDER* windowState,
470 const char* file, const char* fkt, size_t line)
471{
472 const DWORD log_level = WLOG_DEBUG;
473
474 WINPR_ASSERT(log);
475 WINPR_ASSERT(windowState);
476 if (WLog_IsLevelActive(log, log_level))
477 {
478 char buffer1[128] = WINPR_C_ARRAY_INIT;
479 char buffer2[128] = WINPR_C_ARRAY_INIT;
480
481 window_styles_to_string(windowState->style, buffer1, sizeof(buffer1));
482 window_styles_ex_to_string(windowState->extendedStyle, buffer2, sizeof(buffer2));
483 WLog_PrintTextMessage(log, log_level, line, file, fkt, "windowStyle={%s, %s}", buffer1,
484 buffer2);
485 }
486}
487
488/* RemoteApp Core Protocol Extension */
489WINPR_ATTR_NODISCARD
490static BOOL xf_rail_window_common(rdpContext* context, const WINDOW_ORDER_INFO* orderInfo,
491 const WINDOW_STATE_ORDER* windowState)
492{
493 BOOL rc = FALSE;
494 BOOL redraw = FALSE;
495 UINT32 surfaceId = 0;
496 UINT64 windowId = 0;
497 xfContext* xfc = (xfContext*)context;
498
499 WINPR_ASSERT(xfc);
500 WINPR_ASSERT(orderInfo);
501 WINPR_ASSERT(windowState);
502
503 UINT32 fieldFlags = orderInfo->fieldFlags;
504 BOOL position_or_size_updated = FALSE;
505 xfAppWindow* appWindow = xf_rail_get_window(xfc, orderInfo->windowId, FALSE);
506
507 if (fieldFlags & WINDOW_ORDER_STATE_NEW)
508 {
509 if (!appWindow)
510 appWindow = xf_rail_add_window(xfc, orderInfo->windowId, windowState->windowOffsetX,
511 windowState->windowOffsetY, windowState->windowWidth,
512 windowState->windowHeight, 0xFFFFFFFF);
513
514 if (!appWindow)
515 goto fail;
516
517 appWindow->dwStyle = windowState->style;
518 appWindow->dwExStyle = windowState->extendedStyle;
519 window_state_log_style(xfc->log, windowState);
520
521 /* Ensure window always gets a window title */
522 if (fieldFlags & WINDOW_ORDER_FIELD_TITLE)
523 {
524 char* title = rail_string_to_utf8_string(&windowState->titleInfo);
525 if (!title)
526 {
527 WLog_Print(xfc->log, WLOG_ERROR, "failed to duplicate window title string");
528 /* error handled below */
529 }
530
531 appWindow->title = title;
532 }
533 else
534 {
535 if (!(appWindow->title = _strdup("RdpRailWindow")))
536 WLog_Print(xfc->log, WLOG_ERROR, "failed to duplicate default window title string");
537 }
538
539 if (!appWindow->title)
540 goto fail;
541
542 const BOOL hidden = ((fieldFlags & WINDOW_ORDER_FIELD_SHOW) != 0) &&
543 (windowState->showState == WINDOW_HIDE);
544 if (xf_AppWindowInit(xfc, appWindow, hidden))
545 redraw = TRUE;
546 }
547
548 if (!appWindow)
549 return FALSE;
550
551 surfaceId = appWindow->surfaceId;
552 windowId = appWindow->windowId;
553
554 /* Keep track of any position/size update so that we can force a refresh of the window */
555 if ((fieldFlags & WINDOW_ORDER_FIELD_WND_OFFSET) ||
556 (fieldFlags & WINDOW_ORDER_FIELD_WND_SIZE) ||
557 (fieldFlags & WINDOW_ORDER_FIELD_CLIENT_AREA_OFFSET) ||
558 (fieldFlags & WINDOW_ORDER_FIELD_CLIENT_AREA_SIZE) ||
559 (fieldFlags & WINDOW_ORDER_FIELD_WND_CLIENT_DELTA) ||
560 (fieldFlags & WINDOW_ORDER_FIELD_VIS_OFFSET) ||
561 (fieldFlags & WINDOW_ORDER_FIELD_VISIBILITY))
562 {
563 position_or_size_updated = TRUE;
564 }
565
566 /* Update Parameters */
567
568 if (fieldFlags & WINDOW_ORDER_FIELD_WND_OFFSET)
569 {
570 appWindow->windowOffsetX = windowState->windowOffsetX;
571 appWindow->windowOffsetY = windowState->windowOffsetY;
572 }
573
574 if (fieldFlags & WINDOW_ORDER_FIELD_WND_SIZE)
575 {
576 /* Coalesce bursts of server side window size updates.
577 *
578 * When a RemoteApp dialog opens, the server sends a sequence of window
579 * sizes (~45 frames, ~15ms apart) - a window open animation performed
580 * by rdpshell. The client applies every frame and reports the resulting
581 * geometry back, which makes the server shrink the window again by its
582 * frame size. That feedback loop runs away and collapses the dialog to
583 * a 1px wide strip: it "flashes and disappears".
584 *
585 * Keep only the first update of such a burst (which is close to the
586 * real size) and ignore the rest for a second. Positions are still
587 * reported, so mouse input keeps working. */
588 const UINT64 now = winpr_GetTickCount64NS() / 1000000ULL;
589 const BOOL burst =
590 (appWindow->lastWndSizeUpdate != 0) && ((now - appWindow->lastWndSizeUpdate) < 1000);
591 const UINT32 newW = windowState->windowWidth;
592 const UINT32 newH = windowState->windowHeight;
593 const BOOL degenerate = (newW < 32) || (newH < 32);
594 const BOOL hadSane = (appWindow->windowWidth >= 32) && (appWindow->windowHeight >= 32);
595
596 if ((degenerate && hadSane) || burst)
597 {
598 WLog_Print(xfc->log, WLOG_DEBUG,
599 "coalescing RAIL window size %" PRIu32 "x%" PRIu32
600 " (burst=%d degenerate=%d)",
601 newW, newH, burst, degenerate);
602 }
603 else
604 {
605 appWindow->windowWidth = newW;
606 appWindow->windowHeight = newH;
607 appWindow->lastWndSizeUpdate = now;
608 }
609 }
610
611 if (fieldFlags & WINDOW_ORDER_FIELD_RESIZE_MARGIN_X)
612 {
613 appWindow->resizeMarginLeft = windowState->resizeMarginLeft;
614 appWindow->resizeMarginRight = windowState->resizeMarginRight;
615 }
616
617 if (fieldFlags & WINDOW_ORDER_FIELD_RESIZE_MARGIN_Y)
618 {
619 appWindow->resizeMarginTop = windowState->resizeMarginTop;
620 appWindow->resizeMarginBottom = windowState->resizeMarginBottom;
621 }
622
623 if (fieldFlags & WINDOW_ORDER_FIELD_OWNER)
624 {
625 appWindow->ownerWindowId = windowState->ownerWindowId;
626 }
627
628 if (fieldFlags & WINDOW_ORDER_FIELD_STYLE)
629 {
630 appWindow->dwStyle = windowState->style;
631 appWindow->dwExStyle = windowState->extendedStyle;
632 window_state_log_style(xfc->log, windowState);
633 }
634
635 if (fieldFlags & WINDOW_ORDER_FIELD_SHOW)
636 {
637 appWindow->showState = windowState->showState;
638 }
639
640 if (fieldFlags & WINDOW_ORDER_FIELD_TITLE)
641 {
642 char* title = rail_string_to_utf8_string(&windowState->titleInfo);
643 if (!title)
644 {
645 WLog_Print(xfc->log, WLOG_ERROR, "failed to duplicate window title string");
646 goto fail;
647 }
648
649 free(appWindow->title);
650 appWindow->title = title;
651 }
652
653 if (fieldFlags & WINDOW_ORDER_FIELD_CLIENT_AREA_OFFSET)
654 {
655 appWindow->clientOffsetX = windowState->clientOffsetX;
656 appWindow->clientOffsetY = windowState->clientOffsetY;
657 }
658
659 if (fieldFlags & WINDOW_ORDER_FIELD_CLIENT_AREA_SIZE)
660 {
661 appWindow->clientAreaWidth = windowState->clientAreaWidth;
662 appWindow->clientAreaHeight = windowState->clientAreaHeight;
663 }
664
665 if (fieldFlags & WINDOW_ORDER_FIELD_WND_CLIENT_DELTA)
666 {
667 appWindow->windowClientDeltaX = windowState->windowClientDeltaX;
668 appWindow->windowClientDeltaY = windowState->windowClientDeltaY;
669 }
670
671 if (fieldFlags & WINDOW_ORDER_FIELD_WND_RECTS)
672 {
673 if (appWindow->windowRects)
674 {
675 free(appWindow->windowRects);
676 appWindow->windowRects = nullptr;
677 }
678
679 appWindow->numWindowRects = windowState->numWindowRects;
680
681 if (appWindow->numWindowRects)
682 {
683 appWindow->windowRects =
684 (RECTANGLE_16*)calloc(appWindow->numWindowRects, sizeof(RECTANGLE_16));
685
686 if (!appWindow->windowRects)
687 goto fail;
688
689 CopyMemory(appWindow->windowRects, windowState->windowRects,
690 appWindow->numWindowRects * sizeof(RECTANGLE_16));
691 }
692 }
693
694 if (fieldFlags & WINDOW_ORDER_FIELD_VIS_OFFSET)
695 {
696 appWindow->visibleOffsetX = windowState->visibleOffsetX;
697 appWindow->visibleOffsetY = windowState->visibleOffsetY;
698 }
699
700 if (fieldFlags & WINDOW_ORDER_FIELD_VISIBILITY)
701 {
702 if (appWindow->visibilityRects)
703 {
704 free(appWindow->visibilityRects);
705 appWindow->visibilityRects = nullptr;
706 }
707
708 appWindow->numVisibilityRects = windowState->numVisibilityRects;
709
710 if (appWindow->numVisibilityRects)
711 {
712 appWindow->visibilityRects =
713 (RECTANGLE_16*)calloc(appWindow->numVisibilityRects, sizeof(RECTANGLE_16));
714
715 if (!appWindow->visibilityRects)
716 goto fail;
717
718 CopyMemory(appWindow->visibilityRects, windowState->visibilityRects,
719 appWindow->numVisibilityRects * sizeof(RECTANGLE_16));
720 }
721 }
722
723 /* Update Window */
724
725 if (fieldFlags & WINDOW_ORDER_FIELD_STYLE)
726 {
727 }
728
729 if (fieldFlags & WINDOW_ORDER_FIELD_SHOW)
730 {
731 if (xf_ShowWindow(xfc, appWindow, WINPR_ASSERTING_INT_CAST(UINT8, appWindow->showState)))
732 redraw = TRUE;
733 }
734
735 if (fieldFlags & WINDOW_ORDER_FIELD_TITLE)
736 {
737 if (appWindow->title)
738 xf_SetWindowText(xfc, appWindow, appWindow->title);
739 }
740
741 if (position_or_size_updated)
742 {
743 const INT32 visibilityRectsOffsetX =
744 (appWindow->visibleOffsetX -
745 (appWindow->clientOffsetX - appWindow->windowClientDeltaX));
746 const INT32 visibilityRectsOffsetY =
747 (appWindow->visibleOffsetY -
748 (appWindow->clientOffsetY - appWindow->windowClientDeltaY));
749
750 /*
751 * The rail server like to set the window to a small size when it is minimized even though
752 * it is hidden in some cases this can cause the window not to restore back to its original
753 * size. Therefore we don't update our local window when that rail window state is minimized
754 */
755 if (appWindow->rail_state != WINDOW_SHOW_MINIMIZED)
756 {
757 const BOOL maximized = (appWindow->dwStyle & WS_MAXIMIZE) != 0;
758 /* Leave maximized windows to the WM, which honors the work area. */
759 if (maximized || (appWindow->x == (INT64)appWindow->windowOffsetX &&
760 appWindow->y == (INT64)appWindow->windowOffsetY &&
761 appWindow->width == (INT64)appWindow->windowWidth &&
762 appWindow->height == (INT64)appWindow->windowHeight))
763 {
764 if (xf_UpdateWindowArea(xfc, appWindow, 0, 0,
765 WINPR_ASSERTING_INT_CAST(int, appWindow->windowWidth),
766 WINPR_ASSERTING_INT_CAST(int, appWindow->windowHeight)))
767 redraw = TRUE;
768 }
769 else
770 {
771 if (xf_MoveWindow(xfc, appWindow, appWindow->windowOffsetX,
772 appWindow->windowOffsetY,
773 WINPR_ASSERTING_INT_CAST(int, appWindow->windowWidth),
774 WINPR_ASSERTING_INT_CAST(int, appWindow->windowHeight)))
775 redraw = TRUE;
776 }
777
778 /* Show a maximized window fully; its frame-inset visibility rects would clip it. */
779 if (maximized)
780 xf_ClearWindowVisibilityRects(xfc, appWindow);
781 else
782 xf_SetWindowVisibilityRects(
783 xfc, appWindow, WINPR_ASSERTING_INT_CAST(uint32_t, visibilityRectsOffsetX),
784 WINPR_ASSERTING_INT_CAST(uint32_t, visibilityRectsOffsetY),
785 appWindow->visibilityRects,
786 WINPR_ASSERTING_INT_CAST(int, appWindow->numVisibilityRects));
787 }
788
789 if (appWindow->rail_state == WINDOW_SHOW_MAXIMIZED)
790 {
791 xf_SendClientEvent(xfc, appWindow->handle, xfc->NET_WM_STATE, 4, NET_WM_STATE_ADD,
792 xfc->NET_WM_STATE_MAXIMIZED_VERT, xfc->NET_WM_STATE_MAXIMIZED_HORZ,
793 0, 0);
794 }
795 }
796
797 if (fieldFlags & (WINDOW_ORDER_STATE_NEW | WINDOW_ORDER_FIELD_STYLE))
798 xf_SetWindowStyle(xfc, appWindow, appWindow->dwStyle, appWindow->dwExStyle);
799
800 /* We should only be using the visibility rects for shaping the window */
801 /*if (fieldFlags & WINDOW_ORDER_FIELD_WND_RECTS)
802 {
803 xf_SetWindowRects(xfc, appWindow, appWindow->windowRects, appWindow->numWindowRects);
804 }*/
805 rc = TRUE;
806fail:
807 xf_rail_return_window(appWindow, FALSE);
808 if (redraw)
809 xf_AppWindowRepaintFromSurface(xfc, windowId, surfaceId);
810 return rc;
811}
812
813WINPR_ATTR_NODISCARD
814static BOOL xf_rail_window_delete(rdpContext* context, const WINDOW_ORDER_INFO* orderInfo)
815{
816 xfContext* xfc = (xfContext*)context;
817 WINPR_ASSERT(xfc);
818 return xf_rail_del_window(xfc, orderInfo->windowId);
819}
820
821static void RailIconCache_Free(xfRailIconCache* cache)
822{
823 if (!cache)
824 return;
825
826 for (UINT32 i = 0; i < cache->numCaches * cache->numCacheEntries; i++)
827 {
828 xfRailIcon* cur = &cache->entries[i];
829 free(cur->data);
830 }
831
832 free(cache->scratch.data);
833 free(cache->entries);
834 free(cache);
835}
836
837WINPR_ATTR_MALLOC(RailIconCache_Free, 1)
838static xfRailIconCache* RailIconCache_New(wLog* log, rdpSettings* settings)
839{
840 xfRailIconCache* cache = calloc(1, sizeof(xfRailIconCache));
841
842 if (!cache)
843 return nullptr;
844
845 cache->numCaches = freerdp_settings_get_uint32(settings, FreeRDP_RemoteAppNumIconCaches);
846 cache->numCacheEntries =
847 freerdp_settings_get_uint32(settings, FreeRDP_RemoteAppNumIconCacheEntries);
848 cache->entries = calloc(1ull * cache->numCaches * cache->numCacheEntries, sizeof(xfRailIcon));
849
850 if (!cache->entries)
851 {
852 WLog_Print(log, WLOG_ERROR,
853 "failed to allocate icon cache %" PRIu32 " x %" PRIu32 " entries",
854 cache->numCaches, cache->numCacheEntries);
855 free(cache);
856 return nullptr;
857 }
858
859 return cache;
860}
861
862WINPR_ATTR_NODISCARD
863static xfRailIcon* RailIconCache_Lookup(xfRailIconCache* cache, UINT8 cacheId, UINT16 cacheEntry)
864{
865 WINPR_ASSERT(cache);
866 /*
867 * MS-RDPERP 2.2.1.2.3 Icon Info (TS_ICON_INFO)
868 *
869 * CacheId (1 byte):
870 * If the value is 0xFFFF, the icon SHOULD NOT be cached.
871 *
872 * Yes, the spec says "0xFFFF" in the 2018-03-16 revision,
873 * but the actual protocol field is 1-byte wide.
874 */
875 if (cacheId == 0xFF)
876 return &cache->scratch;
877
878 if (cacheId >= cache->numCaches)
879 return nullptr;
880
881 if (cacheEntry >= cache->numCacheEntries)
882 return nullptr;
883
884 return &cache->entries[cache->numCacheEntries * cacheId + cacheEntry];
885}
886
887/*
888 * _NET_WM_ICON format is defined as "array of CARDINAL" values which for
889 * Xlib must be represented with an array of C's "long" values. Note that
890 * "long" != "INT32" on 64-bit systems. Therefore we can't simply cast
891 * the bitmap data as (unsigned char*), we have to copy all the pixels.
892 *
893 * The first two values are width and height followed by actual color data
894 * in ARGB format (e.g., 0xFFFF0000L is opaque red), pixels are in normal,
895 * left-to-right top-down order.
896 */
897WINPR_ATTR_NODISCARD
898static BOOL convert_rail_icon(const ICON_INFO* iconInfo, xfRailIcon* railIcon)
899{
900 WINPR_ASSERT(iconInfo);
901 WINPR_ASSERT(railIcon);
902
903 BYTE* nextPixel = nullptr;
904 long* pixels = nullptr;
905 BYTE* argbPixels = calloc(1ull * iconInfo->width * iconInfo->height, 4);
906
907 if (!argbPixels)
908 goto error;
909
910 if (!freerdp_image_copy_from_icon_data(
911 argbPixels, PIXEL_FORMAT_ARGB32, 0, 0, 0,
912 WINPR_ASSERTING_INT_CAST(UINT16, iconInfo->width),
913 WINPR_ASSERTING_INT_CAST(UINT16, iconInfo->height), iconInfo->bitsColor,
914 WINPR_ASSERTING_INT_CAST(UINT16, iconInfo->cbBitsColor), iconInfo->bitsMask,
915 WINPR_ASSERTING_INT_CAST(UINT16, iconInfo->cbBitsMask), iconInfo->colorTable,
916 WINPR_ASSERTING_INT_CAST(UINT16, iconInfo->cbColorTable), iconInfo->bpp))
917 goto error;
918
919 {
920 const UINT32 nelements = 2 + iconInfo->width * iconInfo->height;
921 pixels = realloc(railIcon->data, nelements * sizeof(long));
922
923 if (!pixels)
924 goto error;
925
926 railIcon->data = pixels;
927
928 railIcon->length = WINPR_ASSERTING_INT_CAST(int, nelements);
929 pixels[0] = iconInfo->width;
930 pixels[1] = iconInfo->height;
931 nextPixel = argbPixels;
932
933 for (UINT32 i = 2; i < nelements; i++)
934 {
935 pixels[i] = FreeRDPReadColor(nextPixel, PIXEL_FORMAT_BGRA32);
936 nextPixel += 4;
937 }
938 }
939
940 free(argbPixels);
941 return TRUE;
942error:
943 free(argbPixels);
944 return FALSE;
945}
946
947static void xf_rail_set_window_icon(xfContext* xfc, xfAppWindow* railWindow, xfRailIcon* icon,
948 BOOL replace)
949{
950 WINPR_ASSERT(xfc);
951
952 LogDynAndXChangeProperty(xfc->log, xfc->display, railWindow->handle, xfc->NET_WM_ICON,
953 XA_CARDINAL, 32, replace ? PropModeReplace : PropModeAppend,
954 (unsigned char*)icon->data, icon->length);
955 LogDynAndXFlush(xfc->log, xfc->display);
956}
957
958WINPR_ATTR_NODISCARD
959static BOOL xf_rail_window_icon(rdpContext* context, const WINDOW_ORDER_INFO* orderInfo,
960 const WINDOW_ICON_ORDER* windowIcon)
961{
962 BOOL rc = FALSE;
963 xfContext* xfc = (xfContext*)context;
964 BOOL replaceIcon = 0;
965 xfAppWindow* railWindow = xf_rail_get_window(xfc, orderInfo->windowId, FALSE);
966
967 if (!railWindow)
968 return TRUE;
969
970 WINPR_ASSERT(windowIcon);
971 WINPR_ASSERT(windowIcon->iconInfo);
972 xfRailIcon* icon = RailIconCache_Lookup(
973 xfc->railIconCache, WINPR_ASSERTING_INT_CAST(UINT8, windowIcon->iconInfo->cacheId),
974 WINPR_ASSERTING_INT_CAST(UINT16, windowIcon->iconInfo->cacheEntry));
975
976 if (!icon)
977 {
978 WLog_Print(xfc->log, WLOG_WARN, "failed to get icon from cache %02X:%04X",
979 windowIcon->iconInfo->cacheId, windowIcon->iconInfo->cacheEntry);
980 }
981 else if (!convert_rail_icon(windowIcon->iconInfo, icon))
982 {
983 WLog_Print(xfc->log, WLOG_WARN, "failed to convert icon for window %08X",
984 orderInfo->windowId);
985 }
986 else
987 {
988 replaceIcon = !!(orderInfo->fieldFlags & WINDOW_ORDER_STATE_NEW);
989 xf_rail_set_window_icon(xfc, railWindow, icon, replaceIcon);
990 rc = TRUE;
991 }
992 xf_rail_return_window(railWindow, FALSE);
993 return rc;
994}
995
996WINPR_ATTR_NODISCARD
997static BOOL xf_rail_window_cached_icon(rdpContext* context, const WINDOW_ORDER_INFO* orderInfo,
998 const WINDOW_CACHED_ICON_ORDER* windowCachedIcon)
999{
1000 BOOL rc = FALSE;
1001 xfContext* xfc = (xfContext*)context;
1002 WINPR_ASSERT(orderInfo);
1003
1004 BOOL replaceIcon = 0;
1005 xfAppWindow* railWindow = xf_rail_get_window(xfc, orderInfo->windowId, FALSE);
1006
1007 if (!railWindow)
1008 return TRUE;
1009
1010 WINPR_ASSERT(windowCachedIcon);
1011
1012 xfRailIcon* icon = RailIconCache_Lookup(
1013 xfc->railIconCache, WINPR_ASSERTING_INT_CAST(UINT8, windowCachedIcon->cachedIcon.cacheId),
1014 WINPR_ASSERTING_INT_CAST(UINT16, windowCachedIcon->cachedIcon.cacheEntry));
1015
1016 if (!icon)
1017 {
1018 WLog_Print(xfc->log, WLOG_WARN, "failed to get icon from cache %02X:%04X",
1019 windowCachedIcon->cachedIcon.cacheId, windowCachedIcon->cachedIcon.cacheEntry);
1020 }
1021 else
1022 {
1023 replaceIcon = !!(orderInfo->fieldFlags & WINDOW_ORDER_STATE_NEW);
1024 xf_rail_set_window_icon(xfc, railWindow, icon, replaceIcon);
1025 rc = TRUE;
1026 }
1027 xf_rail_return_window(railWindow, FALSE);
1028 return rc;
1029}
1030
1031WINPR_ATTR_NODISCARD
1032static BOOL
1033xf_rail_notify_icon_common(WINPR_ATTR_UNUSED rdpContext* context,
1034 const WINDOW_ORDER_INFO* orderInfo,
1035 WINPR_ATTR_UNUSED const NOTIFY_ICON_STATE_ORDER* notifyIconState)
1036{
1037 xfContext* xfc = (xfContext*)context;
1038 WINPR_ASSERT(xfc);
1039
1040 WLog_Print(xfc->log, WLOG_ERROR, "TODO: implement");
1041 if (orderInfo->fieldFlags & WINDOW_ORDER_FIELD_NOTIFY_VERSION)
1042 {
1043 }
1044
1045 if (orderInfo->fieldFlags & WINDOW_ORDER_FIELD_NOTIFY_TIP)
1046 {
1047 }
1048
1049 if (orderInfo->fieldFlags & WINDOW_ORDER_FIELD_NOTIFY_INFO_TIP)
1050 {
1051 }
1052
1053 if (orderInfo->fieldFlags & WINDOW_ORDER_FIELD_NOTIFY_STATE)
1054 {
1055 }
1056
1057 if (orderInfo->fieldFlags & WINDOW_ORDER_ICON)
1058 {
1059 }
1060
1061 if (orderInfo->fieldFlags & WINDOW_ORDER_CACHED_ICON)
1062 {
1063 }
1064
1065 return TRUE;
1066}
1067
1068WINPR_ATTR_NODISCARD
1069static BOOL xf_rail_notify_icon_create(rdpContext* context, const WINDOW_ORDER_INFO* orderInfo,
1070 const NOTIFY_ICON_STATE_ORDER* notifyIconState)
1071{
1072 return xf_rail_notify_icon_common(context, orderInfo, notifyIconState);
1073}
1074
1075WINPR_ATTR_NODISCARD
1076static BOOL xf_rail_notify_icon_update(rdpContext* context, const WINDOW_ORDER_INFO* orderInfo,
1077 const NOTIFY_ICON_STATE_ORDER* notifyIconState)
1078{
1079 return xf_rail_notify_icon_common(context, orderInfo, notifyIconState);
1080}
1081
1082WINPR_ATTR_NODISCARD
1083static BOOL xf_rail_notify_icon_delete(WINPR_ATTR_UNUSED rdpContext* context,
1084 WINPR_ATTR_UNUSED const WINDOW_ORDER_INFO* orderInfo)
1085{
1086 xfContext* xfc = (xfContext*)context;
1087 WINPR_ASSERT(xfc);
1088
1089 WLog_Print(xfc->log, WLOG_ERROR, "TODO: implement");
1090 return TRUE;
1091}
1092
1093WINPR_ATTR_NODISCARD
1094static BOOL
1095xf_rail_monitored_desktop(WINPR_ATTR_UNUSED rdpContext* context,
1096 WINPR_ATTR_UNUSED const WINDOW_ORDER_INFO* orderInfo,
1097 WINPR_ATTR_UNUSED const MONITORED_DESKTOP_ORDER* monitoredDesktop)
1098{
1099 const UINT32 mask = WINDOW_ORDER_TYPE_DESKTOP | WINDOW_ORDER_FIELD_DESKTOP_HOOKED |
1100 WINDOW_ORDER_FIELD_DESKTOP_ARC_BEGAN |
1101 WINDOW_ORDER_FIELD_DESKTOP_ARC_COMPLETED |
1102 WINDOW_ORDER_FIELD_DESKTOP_ACTIVE_WND | WINDOW_ORDER_FIELD_DESKTOP_ZORDER;
1103 xfContext* xfc = (xfContext*)context;
1104
1105 if (!context || !orderInfo || !monitoredDesktop)
1106 return FALSE;
1107
1108 if ((orderInfo->fieldFlags & WINDOW_ORDER_TYPE_DESKTOP) == 0)
1109 {
1110 WLog_Print(xfc->log, WLOG_WARN, "WINDOW_ORDER_TYPE_DESKTOP flag missing!");
1111 return FALSE;
1112 }
1113
1114 if ((orderInfo->fieldFlags & WINDOW_ORDER_FIELD_DESKTOP_ARC_BEGAN) &&
1115 (orderInfo->fieldFlags & WINDOW_ORDER_FIELD_DESKTOP_HOOKED))
1116 {
1117 // discard all windows/notify icons
1118 WLog_Print(xfc->log, WLOG_WARN,
1119 "TODO: implement WINDOW_ORDER_FIELD_DESKTOP_ARC_BEGAN && "
1120 "WINDOW_ORDER_FIELD_DESKTOP_HOOKED");
1121 }
1122 else if (orderInfo->fieldFlags & WINDOW_ORDER_FIELD_DESKTOP_HOOKED)
1123 {
1124 WLog_Print(xfc->log, WLOG_WARN, "TODO: implement WINDOW_ORDER_FIELD_DESKTOP_HOOKED");
1125 }
1126 if (orderInfo->fieldFlags & WINDOW_ORDER_FIELD_DESKTOP_ARC_COMPLETED)
1127 {
1128 WLog_Print(xfc->log, WLOG_DEBUG,
1129 "WINDOW_ORDER_FIELD_DESKTOP_ARC_COMPLETED -> switch to RAILS mode");
1130 if (!xf_rail_enable_remoteapp_mode(xfc))
1131 return FALSE;
1132
1133 if (!xf_rail_select_workarea_events(xfc))
1134 return FALSE;
1135
1136 const char* app =
1137 freerdp_settings_get_string(context->settings, FreeRDP_RemoteApplicationProgram);
1138 if ((app != nullptr) && (strnlen(app, 1) > 0))
1139 {
1140 if (client_rail_server_start_cmd(xfc->rail) != CHANNEL_RC_OK)
1141 return FALSE;
1142 if (xf_rail_send_workarea(xfc) != CHANNEL_RC_OK)
1143 return FALSE;
1144 }
1145 }
1146 if (orderInfo->fieldFlags & WINDOW_ORDER_FIELD_DESKTOP_ACTIVE_WND)
1147 {
1148 WLog_Print(xfc->log, WLOG_WARN, "TODO: implement WINDOW_ORDER_FIELD_DESKTOP_ACTIVE_WND");
1149 }
1150 if (orderInfo->fieldFlags & WINDOW_ORDER_FIELD_DESKTOP_ZORDER)
1151 {
1152 WLog_Print(xfc->log, WLOG_WARN, "TODO: implement WINDOW_ORDER_FIELD_DESKTOP_ZORDER");
1153 }
1154 if (orderInfo->fieldFlags & ~mask)
1155 {
1156 WLog_Print(xfc->log, WLOG_WARN, "unknown flags 0x%08" PRIx32 "!", orderInfo->fieldFlags);
1157 }
1158 return TRUE;
1159}
1160
1161WINPR_ATTR_NODISCARD
1162static BOOL xf_rail_non_monitored_desktop(rdpContext* context,
1163 WINPR_ATTR_UNUSED const WINDOW_ORDER_INFO* orderInfo)
1164{
1165 xfContext* xfc = (xfContext*)context;
1166 const UINT32 mask = WINDOW_ORDER_TYPE_DESKTOP | WINDOW_ORDER_FIELD_DESKTOP_NONE;
1167
1168 if (!context || !orderInfo)
1169 return FALSE;
1170
1171 if ((orderInfo->fieldFlags & WINDOW_ORDER_TYPE_DESKTOP) == 0)
1172 {
1173 WLog_Print(xfc->log, WLOG_WARN, "TODO: implement WINDOW_ORDER_TYPE_DESKTOP");
1174 return FALSE;
1175 }
1176 if (orderInfo->fieldFlags & WINDOW_ORDER_FIELD_DESKTOP_NONE)
1177 {
1178 WLog_Print(xfc->log, WLOG_WARN, "TODO: implement WINDOW_ORDER_FIELD_DESKTOP_NONE");
1179 }
1180 if (orderInfo->fieldFlags & ~mask)
1181 {
1182 WLog_Print(xfc->log, WLOG_WARN, "unknown flags 0x%08" PRIx32 "!", orderInfo->fieldFlags);
1183 }
1184
1185 return xf_rail_disable_remoteapp_mode(xfc);
1186}
1187
1188static void xf_rail_register_update_callbacks(rdpUpdate* update)
1189{
1190 WINPR_ASSERT(update);
1191
1192 rdpWindowUpdate* window = update->window;
1193 WINPR_ASSERT(window);
1194
1195 window->WindowCreate = xf_rail_window_common;
1196 window->WindowUpdate = xf_rail_window_common;
1197 window->WindowDelete = xf_rail_window_delete;
1198 window->WindowIcon = xf_rail_window_icon;
1199 window->WindowCachedIcon = xf_rail_window_cached_icon;
1200 window->NotifyIconCreate = xf_rail_notify_icon_create;
1201 window->NotifyIconUpdate = xf_rail_notify_icon_update;
1202 window->NotifyIconDelete = xf_rail_notify_icon_delete;
1203 window->MonitoredDesktop = xf_rail_monitored_desktop;
1204 window->NonMonitoredDesktop = xf_rail_non_monitored_desktop;
1205}
1206
1207/* RemoteApp Virtual Channel Extension */
1208
1214WINPR_ATTR_NODISCARD
1215static UINT xf_rail_server_execute_result(RailClientContext* context,
1216 const RAIL_EXEC_RESULT_ORDER* execResult)
1217{
1218 WINPR_ASSERT(context);
1219 WINPR_ASSERT(execResult);
1220
1221 xfContext* xfc = (xfContext*)context->custom;
1222 WINPR_ASSERT(xfc);
1223
1224 if (execResult->execResult != RAIL_EXEC_S_OK)
1225 {
1226 WLog_Print(
1227 xfc->log, WLOG_ERROR, "RAIL exec error: execResult=%s [0x%08" PRIx32 "] NtError=0x%X\n",
1228 error_code2str(execResult->execResult), execResult->execResult, execResult->rawResult);
1229 freerdp_abort_connect_context(&xfc->common.context);
1230 }
1231
1232 return CHANNEL_RC_OK;
1233}
1234
1240WINPR_ATTR_NODISCARD
1241static UINT xf_rail_server_system_param(WINPR_ATTR_UNUSED RailClientContext* context,
1242 WINPR_ATTR_UNUSED const RAIL_SYSPARAM_ORDER* sysparam)
1243{
1244 WINPR_ASSERT(context);
1245
1246 xfContext* xfc = (xfContext*)context->custom;
1247 WINPR_ASSERT(xfc);
1248
1249 // TODO: Actually apply param
1250 WLog_Print(xfc->log, WLOG_ERROR, "TODO: implement");
1251 return CHANNEL_RC_OK;
1252}
1253
1259WINPR_ATTR_NODISCARD
1260static UINT xf_rail_server_local_move_size(RailClientContext* context,
1261 const RAIL_LOCALMOVESIZE_ORDER* localMoveSize)
1262{
1263 int x = 0;
1264 int y = 0;
1265 int direction = 0;
1266 Window child_window = 0;
1267 WINPR_ASSERT(context);
1268 WINPR_ASSERT(localMoveSize);
1269
1270 xfContext* xfc = (xfContext*)context->custom;
1271 xfAppWindow* appWindow = xf_rail_get_window(xfc, localMoveSize->windowId, FALSE);
1272
1273 if (!appWindow)
1274 return ERROR_INTERNAL_ERROR;
1275
1276 WLog_Print(xfc->log, WLOG_TRACE, "%s [0x%08" PRIx32 "]",
1277 movetype2str(localMoveSize->moveSizeType), localMoveSize->moveSizeType);
1278 switch (localMoveSize->moveSizeType)
1279 {
1280 case RAIL_WMSZ_LEFT:
1281 direction = NET_WM_MOVERESIZE_SIZE_LEFT;
1282 x = localMoveSize->posX;
1283 y = localMoveSize->posY;
1284 break;
1285
1286 case RAIL_WMSZ_RIGHT:
1287 direction = NET_WM_MOVERESIZE_SIZE_RIGHT;
1288 x = localMoveSize->posX;
1289 y = localMoveSize->posY;
1290 break;
1291
1292 case RAIL_WMSZ_TOP:
1293 direction = NET_WM_MOVERESIZE_SIZE_TOP;
1294 x = localMoveSize->posX;
1295 y = localMoveSize->posY;
1296 break;
1297
1298 case RAIL_WMSZ_TOPLEFT:
1299 direction = NET_WM_MOVERESIZE_SIZE_TOPLEFT;
1300 x = localMoveSize->posX;
1301 y = localMoveSize->posY;
1302 break;
1303
1304 case RAIL_WMSZ_TOPRIGHT:
1305 direction = NET_WM_MOVERESIZE_SIZE_TOPRIGHT;
1306 x = localMoveSize->posX;
1307 y = localMoveSize->posY;
1308 break;
1309
1310 case RAIL_WMSZ_BOTTOM:
1311 direction = NET_WM_MOVERESIZE_SIZE_BOTTOM;
1312 x = localMoveSize->posX;
1313 y = localMoveSize->posY;
1314 break;
1315
1316 case RAIL_WMSZ_BOTTOMLEFT:
1317 direction = NET_WM_MOVERESIZE_SIZE_BOTTOMLEFT;
1318 x = localMoveSize->posX;
1319 y = localMoveSize->posY;
1320 break;
1321
1322 case RAIL_WMSZ_BOTTOMRIGHT:
1323 direction = NET_WM_MOVERESIZE_SIZE_BOTTOMRIGHT;
1324 x = localMoveSize->posX;
1325 y = localMoveSize->posY;
1326 break;
1327
1328 case RAIL_WMSZ_MOVE:
1329 direction = NET_WM_MOVERESIZE_MOVE;
1330 XTranslateCoordinates(xfc->display, appWindow->handle, RootWindowOfScreen(xfc->screen),
1331 localMoveSize->posX, localMoveSize->posY, &x, &y, &child_window);
1332 break;
1333
1334 case RAIL_WMSZ_KEYMOVE:
1335 direction = NET_WM_MOVERESIZE_MOVE_KEYBOARD;
1336 x = localMoveSize->posX;
1337 y = localMoveSize->posY;
1338 /* FIXME: local keyboard moves not working */
1339 break;
1340
1341 case RAIL_WMSZ_KEYSIZE:
1342 direction = NET_WM_MOVERESIZE_SIZE_KEYBOARD;
1343 x = localMoveSize->posX;
1344 y = localMoveSize->posY;
1345 /* FIXME: local keyboard moves not working */
1346 break;
1347 default:
1348 break;
1349 }
1350
1351 if (localMoveSize->isMoveSizeStart)
1352 xf_StartLocalMoveSize(xfc, appWindow, direction, x, y);
1353 else
1354 xf_EndLocalMoveSize(xfc, appWindow);
1355
1356 xf_rail_return_window(appWindow, FALSE);
1357 return CHANNEL_RC_OK;
1358}
1359
1365WINPR_ATTR_NODISCARD
1366static UINT xf_rail_server_min_max_info(RailClientContext* context,
1367 const RAIL_MINMAXINFO_ORDER* minMaxInfo)
1368{
1369 WINPR_ASSERT(context);
1370 WINPR_ASSERT(minMaxInfo);
1371
1372 xfContext* xfc = (xfContext*)context->custom;
1373 xfAppWindow* appWindow = xf_rail_get_window(xfc, minMaxInfo->windowId, FALSE);
1374
1375 if (appWindow)
1376 {
1377 xf_SetWindowMinMaxInfo(xfc, appWindow, minMaxInfo->maxWidth, minMaxInfo->maxHeight,
1378 minMaxInfo->maxPosX, minMaxInfo->maxPosY, minMaxInfo->minTrackWidth,
1379 minMaxInfo->minTrackHeight, minMaxInfo->maxTrackWidth,
1380 minMaxInfo->maxTrackHeight);
1381 }
1382 xf_rail_return_window(appWindow, FALSE);
1383
1384 return CHANNEL_RC_OK;
1385}
1386
1392WINPR_ATTR_NODISCARD
1393static UINT
1394xf_rail_server_language_bar_info(WINPR_ATTR_UNUSED RailClientContext* context,
1395 WINPR_ATTR_UNUSED const RAIL_LANGBAR_INFO_ORDER* langBarInfo)
1396{
1397 WINPR_ASSERT(context);
1398
1399 xfContext* xfc = (xfContext*)context->custom;
1400 WINPR_ASSERT(xfc);
1401
1402 WLog_Print(xfc->log, WLOG_ERROR, "TODO: implement");
1403 return CHANNEL_RC_OK;
1404}
1405
1411WINPR_ATTR_NODISCARD
1412static UINT
1413xf_rail_server_get_appid_response(WINPR_ATTR_UNUSED RailClientContext* context,
1414 WINPR_ATTR_UNUSED const RAIL_GET_APPID_RESP_ORDER* getAppIdResp)
1415{
1416 WINPR_ASSERT(context);
1417
1418 xfContext* xfc = (xfContext*)context->custom;
1419 WINPR_ASSERT(xfc);
1420
1421 WLog_Print(xfc->log, WLOG_ERROR, "TODO: implement");
1422 return CHANNEL_RC_OK;
1423}
1424
1425WINPR_ATTR_NODISCARD
1426static BOOL rail_window_key_equals(const void* key1, const void* key2)
1427{
1428 const UINT64* k1 = (const UINT64*)key1;
1429 const UINT64* k2 = (const UINT64*)key2;
1430
1431 if (!k1 || !k2)
1432 return FALSE;
1433
1434 return *k1 == *k2;
1435}
1436
1437WINPR_ATTR_NODISCARD
1438static UINT32 rail_window_key_hash(const void* key)
1439{
1440 const UINT64* k1 = (const UINT64*)key;
1441 return (UINT32)*k1;
1442}
1443
1444static void rail_window_free(void* value)
1445{
1446 xfAppWindow* appWindow = (xfAppWindow*)value;
1447
1448 if (!appWindow)
1449 return;
1450
1451 xf_DestroyWindow(appWindow->xfc, appWindow);
1452}
1453
1454int xf_rail_init(xfContext* xfc, RailClientContext* rail)
1455{
1456 rdpContext* context = (rdpContext*)xfc;
1457
1458 if (!xfc || !rail)
1459 return 0;
1460
1461 xfc->rail = rail;
1462 xf_rail_register_update_callbacks(context->update);
1463 rail->custom = (void*)xfc;
1464 rail->ServerExecuteResult = xf_rail_server_execute_result;
1465 rail->ServerSystemParam = xf_rail_server_system_param;
1466 rail->ServerLocalMoveSize = xf_rail_server_local_move_size;
1467 rail->ServerMinMaxInfo = xf_rail_server_min_max_info;
1468 rail->ServerLanguageBarInfo = xf_rail_server_language_bar_info;
1469 rail->ServerGetAppIdResponse = xf_rail_server_get_appid_response;
1470 xfc->railWindows = HashTable_New(TRUE);
1471
1472 if (!xfc->railWindows)
1473 return 0;
1474
1475 if (!HashTable_SetHashFunction(xfc->railWindows, rail_window_key_hash))
1476 goto fail;
1477 {
1478 wObject* obj = HashTable_KeyObject(xfc->railWindows);
1479 obj->fnObjectEquals = rail_window_key_equals;
1480 }
1481 {
1482 wObject* obj = HashTable_ValueObject(xfc->railWindows);
1483 obj->fnObjectFree = rail_window_free;
1484 }
1485 xfc->railIconCache = RailIconCache_New(xfc->log, xfc->common.context.settings);
1486
1487 if (!xfc->railIconCache)
1488 {
1489 }
1490
1491 return 1;
1492fail:
1493 HashTable_Free(xfc->railWindows);
1494 return 0;
1495}
1496
1497int xf_rail_uninit(xfContext* xfc, RailClientContext* rail)
1498{
1499 WINPR_UNUSED(rail);
1500
1501 if (xfc->rail)
1502 {
1503 xfc->rail->custom = nullptr;
1504 xfc->rail = nullptr;
1505 }
1506
1507 if (xfc->railWindows)
1508 {
1509 HashTable_Free(xfc->railWindows);
1510 xfc->railWindows = nullptr;
1511 }
1512
1513 if (xfc->railIconCache)
1514 {
1515 RailIconCache_Free(xfc->railIconCache);
1516 xfc->railIconCache = nullptr;
1517 }
1518
1519 return 1;
1520}
1521
1522xfAppWindow* xf_rail_add_window(xfContext* xfc, UINT64 id, INT32 x, INT32 y, UINT32 width,
1523 UINT32 height, UINT32 surfaceId)
1524{
1525 if (!xfc)
1526 return nullptr;
1527
1528 xfAppWindow* appWindow = (xfAppWindow*)calloc(1, sizeof(xfAppWindow));
1529
1530 if (!appWindow)
1531 return nullptr;
1532
1533 appWindow->xfc = xfc;
1534 appWindow->windowId = id;
1535 appWindow->surfaceId = surfaceId;
1536 appWindow->x = x;
1537 appWindow->y = y;
1538 appWindow->width = WINPR_ASSERTING_INT_CAST(int, width);
1539 appWindow->height = WINPR_ASSERTING_INT_CAST(int, height);
1540
1541 xf_AppWindowsLock(xfc);
1542 if (!xf_AppWindowCreate(xfc, appWindow))
1543 goto fail;
1544
1545 if (!HashTable_Insert(xfc->railWindows, &appWindow->windowId, (void*)appWindow))
1546 goto fail;
1547 return appWindow;
1548fail:
1549 rail_window_free(appWindow);
1550 xf_AppWindowsUnlock(xfc);
1551 return nullptr;
1552}
1553
1554BOOL xf_rail_del_window(xfContext* xfc, UINT64 id)
1555{
1556 if (!xfc)
1557 return FALSE;
1558
1559 if (!xfc->railWindows)
1560 return FALSE;
1561
1562 xf_lock_x11(xfc);
1563 const BOOL res = HashTable_Remove(xfc->railWindows, &id);
1564 xf_unlock_x11(xfc);
1565 return res;
1566}
1567
1568void xf_rail_return_windowFrom(xfAppWindow* window, BOOL alreadyLocked, const char* file,
1569 const char* fkt, size_t line)
1570{
1571 if (!window)
1572 return;
1573
1574 if (alreadyLocked)
1575 return;
1576
1577 xfAppWindowsUnlockFrom(window->xfc, file, fkt, line);
1578}
WINPR_ATTR_NODISCARD FREERDP_API const char * freerdp_settings_get_string(const rdpSettings *settings, FreeRDP_Settings_Keys_String id)
Returns a immutable string settings value.
WINPR_ATTR_NODISCARD FREERDP_API UINT32 freerdp_settings_get_uint32(const rdpSettings *settings, FreeRDP_Settings_Keys_UInt32 id)
Returns a UINT32 settings value.
This struct contains function pointer to initialize/free objects.
Definition collections.h:52
OBJECT_FREE_FN fnObjectFree
Definition collections.h:59
WINPR_ATTR_NODISCARD OBJECT_EQUALS_FN fnObjectEquals
Definition collections.h:61