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