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
31#include <freerdp/client/rail.h>
32
33#include "xf_window.h"
34#include "xf_rail.h"
35#include "xf_utils.h"
36
37#include <freerdp/log.h>
38#define TAG CLIENT_TAG("x11")
39
40static const char* error_code_names[] = { "RAIL_EXEC_S_OK",
41 "RAIL_EXEC_E_HOOK_NOT_LOADED",
42 "RAIL_EXEC_E_DECODE_FAILED",
43 "RAIL_EXEC_E_NOT_IN_ALLOWLIST",
44 "RAIL_EXEC_E_FILE_NOT_FOUND",
45 "RAIL_EXEC_E_FAIL",
46 "RAIL_EXEC_E_SESSION_LOCKED" };
47
48#ifdef WITH_DEBUG_RAIL
49static const char* movetype_names[] = {
50 "(invalid)", "RAIL_WMSZ_LEFT", "RAIL_WMSZ_RIGHT",
51 "RAIL_WMSZ_TOP", "RAIL_WMSZ_TOPLEFT", "RAIL_WMSZ_TOPRIGHT",
52 "RAIL_WMSZ_BOTTOM", "RAIL_WMSZ_BOTTOMLEFT", "RAIL_WMSZ_BOTTOMRIGHT",
53 "RAIL_WMSZ_MOVE", "RAIL_WMSZ_KEYMOVE", "RAIL_WMSZ_KEYSIZE"
54};
55#endif
56
57struct xf_rail_icon
58{
59 long* data;
60 int length;
61};
62typedef struct xf_rail_icon xfRailIcon;
63
64struct xf_rail_icon_cache
65{
66 xfRailIcon* entries;
67 UINT32 numCaches;
68 UINT32 numCacheEntries;
69 xfRailIcon scratch;
70};
71
72typedef struct
73{
74 xfContext* xfc;
75 const RECTANGLE_16* rect;
76} rail_paint_fn_arg_t;
77
78void xf_rail_enable_remoteapp_mode(xfContext* xfc)
79{
80 WINPR_ASSERT(xfc);
81 if (!xfc->remote_app)
82 {
83 xfc->remote_app = TRUE;
84 xfc->drawable = xf_CreateDummyWindow(xfc);
85 xf_DestroyDesktopWindow(xfc, xfc->window);
86 xfc->window = NULL;
87 }
88}
89
90void xf_rail_disable_remoteapp_mode(xfContext* xfc)
91{
92 WINPR_ASSERT(xfc);
93 if (xfc->remote_app)
94 {
95 xfc->remote_app = FALSE;
96 xf_DestroyDummyWindow(xfc, xfc->drawable);
97 xf_create_window(xfc);
98 xf_create_image(xfc);
99 }
100}
101
102void xf_rail_send_activate(xfContext* xfc, Window xwindow, BOOL enabled)
103{
104 RAIL_ACTIVATE_ORDER activate = { 0 };
105 xfAppWindow* appWindow = xf_AppWindowFromX11Window(xfc, xwindow);
106
107 if (!appWindow)
108 return;
109
110 if (enabled)
111 xf_SetWindowStyle(xfc, appWindow, appWindow->dwStyle, appWindow->dwExStyle);
112
113 WINPR_ASSERT(appWindow->windowId <= UINT32_MAX);
114 activate.windowId = (UINT32)appWindow->windowId;
115 activate.enabled = enabled;
116 xfc->rail->ClientActivate(xfc->rail, &activate);
117}
118
119BOOL xf_rail_send_client_system_command(xfContext* xfc, UINT64 windowId, UINT16 command)
120{
121 WINPR_ASSERT(xfc);
122 WINPR_ASSERT(xfc->rail);
123 WINPR_ASSERT(xfc->rail->ClientSystemCommand);
124 if (windowId > UINT32_MAX)
125 return FALSE;
126
127 const RAIL_SYSCOMMAND_ORDER syscommand = { .windowId = (UINT32)windowId, .command = command };
128 const UINT rc = xfc->rail->ClientSystemCommand(xfc->rail, &syscommand);
129 return rc == CHANNEL_RC_OK;
130}
131
138void xf_rail_adjust_position(xfContext* xfc, xfAppWindow* appWindow)
139{
140 RAIL_WINDOW_MOVE_ORDER windowMove = { 0 };
141
142 WINPR_ASSERT(xfc);
143 WINPR_ASSERT(appWindow);
144 if (!appWindow->is_mapped || appWindow->local_move.state != LMS_NOT_ACTIVE)
145 return;
146
147 /* If current window position disagrees with RDP window position, send update to RDP server */
148 if (appWindow->x != appWindow->windowOffsetX || appWindow->y != appWindow->windowOffsetY ||
149 appWindow->width != (INT64)appWindow->windowWidth ||
150 appWindow->height != (INT64)appWindow->windowHeight)
151 {
152 WINPR_ASSERT(appWindow->windowId <= UINT32_MAX);
153 windowMove.windowId = (UINT32)appWindow->windowId;
154 /*
155 * Calculate new size/position for the rail window(new values for
156 * windowOffsetX/windowOffsetY/windowWidth/windowHeight) on the server
157 */
158 const INT16 left = WINPR_ASSERTING_INT_CAST(INT16, appWindow->resizeMarginLeft);
159 const INT16 right = WINPR_ASSERTING_INT_CAST(INT16, appWindow->resizeMarginRight);
160 const INT16 top = WINPR_ASSERTING_INT_CAST(INT16, appWindow->resizeMarginTop);
161 const INT16 bottom = WINPR_ASSERTING_INT_CAST(INT16, appWindow->resizeMarginBottom);
162 windowMove.left = WINPR_ASSERTING_INT_CAST(INT16, appWindow->x - left);
163 windowMove.top = WINPR_ASSERTING_INT_CAST(INT16, appWindow->y - top);
164 windowMove.right = WINPR_ASSERTING_INT_CAST(INT16, appWindow->x + appWindow->width + right);
165 windowMove.bottom =
166 WINPR_ASSERTING_INT_CAST(INT16, appWindow->y + appWindow->height + bottom);
167 xfc->rail->ClientWindowMove(xfc->rail, &windowMove);
168 }
169}
170
171void xf_rail_end_local_move(xfContext* xfc, xfAppWindow* appWindow)
172{
173 int x = 0;
174 int y = 0;
175 int child_x = 0;
176 int child_y = 0;
177 unsigned int mask = 0;
178 Window root_window = 0;
179 Window child_window = 0;
180
181 WINPR_ASSERT(xfc);
182 WINPR_ASSERT(appWindow);
183
184 if ((appWindow->local_move.direction == NET_WM_MOVERESIZE_MOVE_KEYBOARD) ||
185 (appWindow->local_move.direction == NET_WM_MOVERESIZE_SIZE_KEYBOARD))
186 {
187 RAIL_WINDOW_MOVE_ORDER windowMove = { 0 };
188
189 /*
190 * For keyboard moves send and explicit update to RDP server
191 */
192 WINPR_ASSERT(appWindow->windowId <= UINT32_MAX);
193 windowMove.windowId = (UINT32)appWindow->windowId;
194 /*
195 * Calculate new size/position for the rail window(new values for
196 * windowOffsetX/windowOffsetY/windowWidth/windowHeight) on the server
197 *
198 */
199 const INT16 left = WINPR_ASSERTING_INT_CAST(INT16, appWindow->resizeMarginLeft);
200 const INT16 right = WINPR_ASSERTING_INT_CAST(INT16, appWindow->resizeMarginRight);
201 const INT16 top = WINPR_ASSERTING_INT_CAST(INT16, appWindow->resizeMarginTop);
202 const INT16 bottom = WINPR_ASSERTING_INT_CAST(INT16, appWindow->resizeMarginBottom);
203 const INT16 w = WINPR_ASSERTING_INT_CAST(INT16, appWindow->width + right);
204 const INT16 h = WINPR_ASSERTING_INT_CAST(INT16, appWindow->height + bottom);
205 windowMove.left = WINPR_ASSERTING_INT_CAST(INT16, appWindow->x - left);
206 windowMove.top = WINPR_ASSERTING_INT_CAST(INT16, appWindow->y - top);
207 windowMove.right = WINPR_ASSERTING_INT_CAST(INT16, appWindow->x + w); /* In the update to
208 RDP the position is one past the window */
209 windowMove.bottom = WINPR_ASSERTING_INT_CAST(INT16, appWindow->y + h);
210 xfc->rail->ClientWindowMove(xfc->rail, &windowMove);
211 }
212
213 /*
214 * Simulate button up at new position to end the local move (per RDP spec)
215 */
216 XQueryPointer(xfc->display, appWindow->handle, &root_window, &child_window, &x, &y, &child_x,
217 &child_y, &mask);
218
219 /* only send the mouse coordinates if not a keyboard move or size */
220 if ((appWindow->local_move.direction != NET_WM_MOVERESIZE_MOVE_KEYBOARD) &&
221 (appWindow->local_move.direction != NET_WM_MOVERESIZE_SIZE_KEYBOARD))
222 {
223 freerdp_client_send_button_event(&xfc->common, FALSE, PTR_FLAGS_BUTTON1, x, y);
224 }
225
226 /*
227 * Proactively update the RAIL window dimensions. There is a race condition where
228 * we can start to receive GDI orders for the new window dimensions before we
229 * receive the RAIL ORDER for the new window size. This avoids that race condition.
230 */
231 appWindow->windowOffsetX = appWindow->x;
232 appWindow->windowOffsetY = appWindow->y;
233 appWindow->windowWidth = WINPR_ASSERTING_INT_CAST(uint32_t, appWindow->width);
234 appWindow->windowHeight = WINPR_ASSERTING_INT_CAST(uint32_t, appWindow->height);
235 appWindow->local_move.state = LMS_TERMINATING;
236}
237
238BOOL xf_rail_paint_surface(xfContext* xfc, UINT64 windowId, const RECTANGLE_16* rect)
239{
240 xfAppWindow* appWindow = xf_rail_get_window(xfc, windowId);
241
242 WINPR_ASSERT(rect);
243
244 if (!appWindow)
245 return FALSE;
246
247 const RECTANGLE_16 windowRect = {
248 .left = WINPR_ASSERTING_INT_CAST(UINT16, MAX(appWindow->x, 0)),
249 .top = WINPR_ASSERTING_INT_CAST(UINT16, MAX(appWindow->y, 0)),
250 .right = WINPR_ASSERTING_INT_CAST(UINT16, MAX(appWindow->x + appWindow->width, 0)),
251 .bottom = WINPR_ASSERTING_INT_CAST(UINT16, MAX(appWindow->y + appWindow->height, 0))
252 };
253
254 REGION16 windowInvalidRegion = { 0 };
255 region16_init(&windowInvalidRegion);
256 region16_union_rect(&windowInvalidRegion, &windowInvalidRegion, &windowRect);
257 region16_intersect_rect(&windowInvalidRegion, &windowInvalidRegion, rect);
258
259 if (!region16_is_empty(&windowInvalidRegion))
260 {
261 const RECTANGLE_16* extents = region16_extents(&windowInvalidRegion);
262
263 const RECTANGLE_16 updateRect = {
264 .left = WINPR_ASSERTING_INT_CAST(UINT16, extents->left - appWindow->x),
265 .top = WINPR_ASSERTING_INT_CAST(UINT16, extents->top - appWindow->y),
266 .right = WINPR_ASSERTING_INT_CAST(UINT16, extents->right - appWindow->x),
267 .bottom = WINPR_ASSERTING_INT_CAST(UINT16, extents->bottom - appWindow->y)
268 };
269
270 xf_UpdateWindowArea(xfc, appWindow, updateRect.left, updateRect.top,
271 updateRect.right - updateRect.left, updateRect.bottom - updateRect.top);
272 }
273 region16_uninit(&windowInvalidRegion);
274 return TRUE;
275}
276
277static BOOL rail_paint_fn(const void* pvkey, WINPR_ATTR_UNUSED void* value, void* pvarg)
278{
279 rail_paint_fn_arg_t* arg = pvarg;
280 WINPR_ASSERT(pvkey);
281 WINPR_ASSERT(arg);
282
283 const UINT64 key = *(const UINT64*)pvkey;
284 return xf_rail_paint_surface(arg->xfc, key, arg->rect);
285}
286
287BOOL xf_rail_paint(xfContext* xfc, const RECTANGLE_16* rect)
288{
289 rail_paint_fn_arg_t arg = { .xfc = xfc, .rect = rect };
290
291 WINPR_ASSERT(xfc);
292 WINPR_ASSERT(rect);
293
294 if (!xfc->railWindows)
295 return TRUE;
296
297 return HashTable_Foreach(xfc->railWindows, rail_paint_fn, &arg);
298}
299
300#define window_state_log_style(log, windowState) \
301 window_state_log_style_int((log), (windowState), __FILE__, __func__, __LINE__)
302static void window_state_log_style_int(wLog* log, const WINDOW_STATE_ORDER* windowState,
303 const char* file, const char* fkt, size_t line)
304{
305 const DWORD log_level = WLOG_DEBUG;
306
307 WINPR_ASSERT(log);
308 WINPR_ASSERT(windowState);
309 if (WLog_IsLevelActive(log, log_level))
310 {
311 char buffer1[128] = { 0 };
312 char buffer2[128] = { 0 };
313
314 window_styles_to_string(windowState->style, buffer1, sizeof(buffer1));
315 window_styles_ex_to_string(windowState->extendedStyle, buffer2, sizeof(buffer2));
316 WLog_PrintMessage(log, WLOG_MESSAGE_TEXT, log_level, line, file, fkt,
317 "windowStyle={%s, %s}", buffer1, buffer2);
318 }
319}
320
321/* RemoteApp Core Protocol Extension */
322
323static BOOL xf_rail_window_common(rdpContext* context, const WINDOW_ORDER_INFO* orderInfo,
324 const WINDOW_STATE_ORDER* windowState)
325{
326 xfAppWindow* appWindow = NULL;
327 xfContext* xfc = (xfContext*)context;
328
329 WINPR_ASSERT(xfc);
330 WINPR_ASSERT(orderInfo);
331 WINPR_ASSERT(windowState);
332
333 UINT32 fieldFlags = orderInfo->fieldFlags;
334 BOOL position_or_size_updated = FALSE;
335 appWindow = xf_rail_get_window(xfc, orderInfo->windowId);
336
337 if (fieldFlags & WINDOW_ORDER_STATE_NEW)
338 {
339 if (!appWindow)
340 appWindow = xf_rail_add_window(xfc, orderInfo->windowId, windowState->windowOffsetX,
341 windowState->windowOffsetY, windowState->windowWidth,
342 windowState->windowHeight, 0xFFFFFFFF);
343
344 if (!appWindow)
345 return FALSE;
346
347 appWindow->dwStyle = windowState->style;
348 appWindow->dwExStyle = windowState->extendedStyle;
349 window_state_log_style(xfc->log, windowState);
350
351 /* Ensure window always gets a window title */
352 if (fieldFlags & WINDOW_ORDER_FIELD_TITLE)
353 {
354 union
355 {
356 WCHAR* wc;
357 BYTE* b;
358 } cnv;
359 char* title = NULL;
360
361 cnv.b = windowState->titleInfo.string;
362 if (windowState->titleInfo.length == 0)
363 {
364 if (!(title = _strdup("")))
365 {
366 WLog_ERR(TAG, "failed to duplicate empty window title string");
367 /* error handled below */
368 }
369 }
370 else if (!(title = ConvertWCharNToUtf8Alloc(
371 cnv.wc, windowState->titleInfo.length / sizeof(WCHAR), NULL)))
372 {
373 WLog_ERR(TAG, "failed to convert window title");
374 /* error handled below */
375 }
376
377 appWindow->title = title;
378 }
379 else
380 {
381 if (!(appWindow->title = _strdup("RdpRailWindow")))
382 WLog_ERR(TAG, "failed to duplicate default window title string");
383 }
384
385 if (!appWindow->title)
386 {
387 free(appWindow);
388 return FALSE;
389 }
390
391 xf_AppWindowInit(xfc, appWindow);
392 }
393
394 if (!appWindow)
395 return FALSE;
396
397 /* Keep track of any position/size update so that we can force a refresh of the window */
398 if ((fieldFlags & WINDOW_ORDER_FIELD_WND_OFFSET) ||
399 (fieldFlags & WINDOW_ORDER_FIELD_WND_SIZE) ||
400 (fieldFlags & WINDOW_ORDER_FIELD_CLIENT_AREA_OFFSET) ||
401 (fieldFlags & WINDOW_ORDER_FIELD_CLIENT_AREA_SIZE) ||
402 (fieldFlags & WINDOW_ORDER_FIELD_WND_CLIENT_DELTA) ||
403 (fieldFlags & WINDOW_ORDER_FIELD_VIS_OFFSET) ||
404 (fieldFlags & WINDOW_ORDER_FIELD_VISIBILITY))
405 {
406 position_or_size_updated = TRUE;
407 }
408
409 /* Update Parameters */
410
411 if (fieldFlags & WINDOW_ORDER_FIELD_WND_OFFSET)
412 {
413 appWindow->windowOffsetX = windowState->windowOffsetX;
414 appWindow->windowOffsetY = windowState->windowOffsetY;
415 }
416
417 if (fieldFlags & WINDOW_ORDER_FIELD_WND_SIZE)
418 {
419 appWindow->windowWidth = windowState->windowWidth;
420 appWindow->windowHeight = windowState->windowHeight;
421 }
422
423 if (fieldFlags & WINDOW_ORDER_FIELD_RESIZE_MARGIN_X)
424 {
425 appWindow->resizeMarginLeft = windowState->resizeMarginLeft;
426 appWindow->resizeMarginRight = windowState->resizeMarginRight;
427 }
428
429 if (fieldFlags & WINDOW_ORDER_FIELD_RESIZE_MARGIN_Y)
430 {
431 appWindow->resizeMarginTop = windowState->resizeMarginTop;
432 appWindow->resizeMarginBottom = windowState->resizeMarginBottom;
433 }
434
435 if (fieldFlags & WINDOW_ORDER_FIELD_OWNER)
436 {
437 appWindow->ownerWindowId = windowState->ownerWindowId;
438 }
439
440 if (fieldFlags & WINDOW_ORDER_FIELD_STYLE)
441 {
442 appWindow->dwStyle = windowState->style;
443 appWindow->dwExStyle = windowState->extendedStyle;
444 window_state_log_style(xfc->log, windowState);
445 }
446
447 if (fieldFlags & WINDOW_ORDER_FIELD_SHOW)
448 {
449 appWindow->showState = windowState->showState;
450 }
451
452 if (fieldFlags & WINDOW_ORDER_FIELD_TITLE)
453 {
454 char* title = NULL;
455 union
456 {
457 WCHAR* wc;
458 BYTE* b;
459 } cnv;
460
461 cnv.b = windowState->titleInfo.string;
462 if (windowState->titleInfo.length == 0)
463 {
464 if (!(title = _strdup("")))
465 {
466 WLog_ERR(TAG, "failed to duplicate empty window title string");
467 return FALSE;
468 }
469 }
470 else if (!(title = ConvertWCharNToUtf8Alloc(
471 cnv.wc, windowState->titleInfo.length / sizeof(WCHAR), NULL)))
472 {
473 WLog_ERR(TAG, "failed to convert window title");
474 return FALSE;
475 }
476
477 free(appWindow->title);
478 appWindow->title = title;
479 }
480
481 if (fieldFlags & WINDOW_ORDER_FIELD_CLIENT_AREA_OFFSET)
482 {
483 appWindow->clientOffsetX = windowState->clientOffsetX;
484 appWindow->clientOffsetY = windowState->clientOffsetY;
485 }
486
487 if (fieldFlags & WINDOW_ORDER_FIELD_CLIENT_AREA_SIZE)
488 {
489 appWindow->clientAreaWidth = windowState->clientAreaWidth;
490 appWindow->clientAreaHeight = windowState->clientAreaHeight;
491 }
492
493 if (fieldFlags & WINDOW_ORDER_FIELD_WND_CLIENT_DELTA)
494 {
495 appWindow->windowClientDeltaX = windowState->windowClientDeltaX;
496 appWindow->windowClientDeltaY = windowState->windowClientDeltaY;
497 }
498
499 if (fieldFlags & WINDOW_ORDER_FIELD_WND_RECTS)
500 {
501 if (appWindow->windowRects)
502 {
503 free(appWindow->windowRects);
504 appWindow->windowRects = NULL;
505 }
506
507 appWindow->numWindowRects = windowState->numWindowRects;
508
509 if (appWindow->numWindowRects)
510 {
511 appWindow->windowRects =
512 (RECTANGLE_16*)calloc(appWindow->numWindowRects, sizeof(RECTANGLE_16));
513
514 if (!appWindow->windowRects)
515 return FALSE;
516
517 CopyMemory(appWindow->windowRects, windowState->windowRects,
518 appWindow->numWindowRects * sizeof(RECTANGLE_16));
519 }
520 }
521
522 if (fieldFlags & WINDOW_ORDER_FIELD_VIS_OFFSET)
523 {
524 appWindow->visibleOffsetX = windowState->visibleOffsetX;
525 appWindow->visibleOffsetY = windowState->visibleOffsetY;
526 }
527
528 if (fieldFlags & WINDOW_ORDER_FIELD_VISIBILITY)
529 {
530 if (appWindow->visibilityRects)
531 {
532 free(appWindow->visibilityRects);
533 appWindow->visibilityRects = NULL;
534 }
535
536 appWindow->numVisibilityRects = windowState->numVisibilityRects;
537
538 if (appWindow->numVisibilityRects)
539 {
540 appWindow->visibilityRects =
541 (RECTANGLE_16*)calloc(appWindow->numVisibilityRects, sizeof(RECTANGLE_16));
542
543 if (!appWindow->visibilityRects)
544 return FALSE;
545
546 CopyMemory(appWindow->visibilityRects, windowState->visibilityRects,
547 appWindow->numVisibilityRects * sizeof(RECTANGLE_16));
548 }
549 }
550
551 /* Update Window */
552
553 if (fieldFlags & WINDOW_ORDER_FIELD_STYLE)
554 {
555 }
556
557 if (fieldFlags & WINDOW_ORDER_FIELD_SHOW)
558 {
559 xf_ShowWindow(xfc, appWindow, WINPR_ASSERTING_INT_CAST(UINT8, appWindow->showState));
560 }
561
562 if (fieldFlags & WINDOW_ORDER_FIELD_TITLE)
563 {
564 if (appWindow->title)
565 xf_SetWindowText(xfc, appWindow, appWindow->title);
566 }
567
568 if (position_or_size_updated)
569 {
570 const INT32 visibilityRectsOffsetX =
571 (appWindow->visibleOffsetX -
572 (appWindow->clientOffsetX - appWindow->windowClientDeltaX));
573 const INT32 visibilityRectsOffsetY =
574 (appWindow->visibleOffsetY -
575 (appWindow->clientOffsetY - appWindow->windowClientDeltaY));
576
577 /*
578 * The rail server like to set the window to a small size when it is minimized even though
579 * it is hidden in some cases this can cause the window not to restore back to its original
580 * size. Therefore we don't update our local window when that rail window state is minimized
581 */
582 if (appWindow->rail_state != WINDOW_SHOW_MINIMIZED)
583 {
584 /* Redraw window area if already in the correct position */
585 if (appWindow->x == (INT64)appWindow->windowOffsetX &&
586 appWindow->y == (INT64)appWindow->windowOffsetY &&
587 appWindow->width == (INT64)appWindow->windowWidth &&
588 appWindow->height == (INT64)appWindow->windowHeight)
589 {
590 xf_UpdateWindowArea(xfc, appWindow, 0, 0,
591 WINPR_ASSERTING_INT_CAST(int, appWindow->windowWidth),
592 WINPR_ASSERTING_INT_CAST(int, appWindow->windowHeight));
593 }
594 else
595 {
596 xf_MoveWindow(xfc, appWindow, appWindow->windowOffsetX, appWindow->windowOffsetY,
597 WINPR_ASSERTING_INT_CAST(int, appWindow->windowWidth),
598 WINPR_ASSERTING_INT_CAST(int, appWindow->windowHeight));
599 }
600
601 xf_SetWindowVisibilityRects(
602 xfc, appWindow, WINPR_ASSERTING_INT_CAST(uint32_t, visibilityRectsOffsetX),
603 WINPR_ASSERTING_INT_CAST(uint32_t, visibilityRectsOffsetY),
604 appWindow->visibilityRects,
605 WINPR_ASSERTING_INT_CAST(int, appWindow->numVisibilityRects));
606 }
607
608 if (appWindow->rail_state == WINDOW_SHOW_MAXIMIZED)
609 {
610 xf_SendClientEvent(xfc, appWindow->handle, xfc->NET_WM_STATE, 4, NET_WM_STATE_ADD,
611 xfc->NET_WM_STATE_MAXIMIZED_VERT, xfc->NET_WM_STATE_MAXIMIZED_HORZ,
612 0, 0);
613 }
614 }
615
616 if (fieldFlags & (WINDOW_ORDER_STATE_NEW | WINDOW_ORDER_FIELD_STYLE))
617 xf_SetWindowStyle(xfc, appWindow, appWindow->dwStyle, appWindow->dwExStyle);
618
619 /* We should only be using the visibility rects for shaping the window */
620 /*if (fieldFlags & WINDOW_ORDER_FIELD_WND_RECTS)
621 {
622 xf_SetWindowRects(xfc, appWindow, appWindow->windowRects, appWindow->numWindowRects);
623 }*/
624 return TRUE;
625}
626
627static BOOL xf_rail_window_delete(rdpContext* context, const WINDOW_ORDER_INFO* orderInfo)
628{
629 xfContext* xfc = (xfContext*)context;
630 WINPR_ASSERT(xfc);
631 return xf_rail_del_window(xfc, orderInfo->windowId);
632}
633
634static xfRailIconCache* RailIconCache_New(rdpSettings* settings)
635{
636 xfRailIconCache* cache = calloc(1, sizeof(xfRailIconCache));
637
638 if (!cache)
639 return NULL;
640
641 cache->numCaches = freerdp_settings_get_uint32(settings, FreeRDP_RemoteAppNumIconCaches);
642 cache->numCacheEntries =
643 freerdp_settings_get_uint32(settings, FreeRDP_RemoteAppNumIconCacheEntries);
644 cache->entries = calloc(1ull * cache->numCaches * cache->numCacheEntries, sizeof(xfRailIcon));
645
646 if (!cache->entries)
647 {
648 WLog_ERR(TAG, "failed to allocate icon cache %" PRIu32 " x %" PRIu32 " entries",
649 cache->numCaches, cache->numCacheEntries);
650 free(cache);
651 return NULL;
652 }
653
654 return cache;
655}
656
657static void RailIconCache_Free(xfRailIconCache* cache)
658{
659 if (!cache)
660 return;
661
662 for (UINT32 i = 0; i < cache->numCaches * cache->numCacheEntries; i++)
663 {
664 xfRailIcon* cur = &cache->entries[i];
665 free(cur->data);
666 }
667
668 free(cache->scratch.data);
669 free(cache->entries);
670 free(cache);
671}
672
673static xfRailIcon* RailIconCache_Lookup(xfRailIconCache* cache, UINT8 cacheId, UINT16 cacheEntry)
674{
675 WINPR_ASSERT(cache);
676 /*
677 * MS-RDPERP 2.2.1.2.3 Icon Info (TS_ICON_INFO)
678 *
679 * CacheId (1 byte):
680 * If the value is 0xFFFF, the icon SHOULD NOT be cached.
681 *
682 * Yes, the spec says "0xFFFF" in the 2018-03-16 revision,
683 * but the actual protocol field is 1-byte wide.
684 */
685 if (cacheId == 0xFF)
686 return &cache->scratch;
687
688 if (cacheId >= cache->numCaches)
689 return NULL;
690
691 if (cacheEntry >= cache->numCacheEntries)
692 return NULL;
693
694 return &cache->entries[cache->numCacheEntries * cacheId + cacheEntry];
695}
696
697/*
698 * _NET_WM_ICON format is defined as "array of CARDINAL" values which for
699 * Xlib must be represented with an array of C's "long" values. Note that
700 * "long" != "INT32" on 64-bit systems. Therefore we can't simply cast
701 * the bitmap data as (unsigned char*), we have to copy all the pixels.
702 *
703 * The first two values are width and height followed by actual color data
704 * in ARGB format (e.g., 0xFFFF0000L is opaque red), pixels are in normal,
705 * left-to-right top-down order.
706 */
707static BOOL convert_rail_icon(const ICON_INFO* iconInfo, xfRailIcon* railIcon)
708{
709 WINPR_ASSERT(iconInfo);
710 WINPR_ASSERT(railIcon);
711
712 BYTE* nextPixel = NULL;
713 long* pixels = NULL;
714 BYTE* argbPixels = calloc(1ull * iconInfo->width * iconInfo->height, 4);
715
716 if (!argbPixels)
717 goto error;
718
719 if (!freerdp_image_copy_from_icon_data(
720 argbPixels, PIXEL_FORMAT_ARGB32, 0, 0, 0,
721 WINPR_ASSERTING_INT_CAST(UINT16, iconInfo->width),
722 WINPR_ASSERTING_INT_CAST(UINT16, iconInfo->height), iconInfo->bitsColor,
723 WINPR_ASSERTING_INT_CAST(UINT16, iconInfo->cbBitsColor), iconInfo->bitsMask,
724 WINPR_ASSERTING_INT_CAST(UINT16, iconInfo->cbBitsMask), iconInfo->colorTable,
725 WINPR_ASSERTING_INT_CAST(UINT16, iconInfo->cbColorTable), iconInfo->bpp))
726 goto error;
727
728 const UINT32 nelements = 2 + iconInfo->width * iconInfo->height;
729 pixels = realloc(railIcon->data, nelements * sizeof(long));
730
731 if (!pixels)
732 goto error;
733
734 railIcon->data = pixels;
735
736 railIcon->length = WINPR_ASSERTING_INT_CAST(int, nelements);
737 pixels[0] = iconInfo->width;
738 pixels[1] = iconInfo->height;
739 nextPixel = argbPixels;
740
741 for (UINT32 i = 2; i < nelements; i++)
742 {
743 pixels[i] = FreeRDPReadColor(nextPixel, PIXEL_FORMAT_BGRA32);
744 nextPixel += 4;
745 }
746
747 free(argbPixels);
748 return TRUE;
749error:
750 free(argbPixels);
751 return FALSE;
752}
753
754static void xf_rail_set_window_icon(xfContext* xfc, xfAppWindow* railWindow, xfRailIcon* icon,
755 BOOL replace)
756{
757 WINPR_ASSERT(xfc);
758
759 LogTagAndXChangeProperty(TAG, xfc->display, railWindow->handle, xfc->NET_WM_ICON, XA_CARDINAL,
760 32, replace ? PropModeReplace : PropModeAppend,
761 (unsigned char*)icon->data, icon->length);
762 XFlush(xfc->display);
763}
764
765static BOOL xf_rail_window_icon(rdpContext* context, const WINDOW_ORDER_INFO* orderInfo,
766 const WINDOW_ICON_ORDER* windowIcon)
767{
768 xfContext* xfc = (xfContext*)context;
769 BOOL replaceIcon = 0;
770 xfAppWindow* railWindow = xf_rail_get_window(xfc, orderInfo->windowId);
771
772 if (!railWindow)
773 return TRUE;
774
775 WINPR_ASSERT(windowIcon);
776 WINPR_ASSERT(windowIcon->iconInfo);
777 xfRailIcon* icon = RailIconCache_Lookup(
778 xfc->railIconCache, WINPR_ASSERTING_INT_CAST(UINT8, windowIcon->iconInfo->cacheId),
779 WINPR_ASSERTING_INT_CAST(UINT16, windowIcon->iconInfo->cacheEntry));
780
781 if (!icon)
782 {
783 WLog_WARN(TAG, "failed to get icon from cache %02X:%04X", windowIcon->iconInfo->cacheId,
784 windowIcon->iconInfo->cacheEntry);
785 return FALSE;
786 }
787
788 if (!convert_rail_icon(windowIcon->iconInfo, icon))
789 {
790 WLog_WARN(TAG, "failed to convert icon for window %08X", orderInfo->windowId);
791 return FALSE;
792 }
793
794 replaceIcon = !!(orderInfo->fieldFlags & WINDOW_ORDER_STATE_NEW);
795 xf_rail_set_window_icon(xfc, railWindow, icon, replaceIcon);
796 return TRUE;
797}
798
799static BOOL xf_rail_window_cached_icon(rdpContext* context, const WINDOW_ORDER_INFO* orderInfo,
800 const WINDOW_CACHED_ICON_ORDER* windowCachedIcon)
801{
802 xfContext* xfc = (xfContext*)context;
803 WINPR_ASSERT(orderInfo);
804
805 BOOL replaceIcon = 0;
806 xfAppWindow* railWindow = xf_rail_get_window(xfc, orderInfo->windowId);
807
808 if (!railWindow)
809 return TRUE;
810
811 WINPR_ASSERT(windowCachedIcon);
812
813 xfRailIcon* icon = RailIconCache_Lookup(
814 xfc->railIconCache, WINPR_ASSERTING_INT_CAST(UINT8, windowCachedIcon->cachedIcon.cacheId),
815 WINPR_ASSERTING_INT_CAST(UINT16, windowCachedIcon->cachedIcon.cacheEntry));
816
817 if (!icon)
818 {
819 WLog_WARN(TAG, "failed to get icon from cache %02X:%04X",
820 windowCachedIcon->cachedIcon.cacheId, windowCachedIcon->cachedIcon.cacheEntry);
821 return FALSE;
822 }
823
824 replaceIcon = !!(orderInfo->fieldFlags & WINDOW_ORDER_STATE_NEW);
825 xf_rail_set_window_icon(xfc, railWindow, icon, replaceIcon);
826 return TRUE;
827}
828
829static BOOL
830xf_rail_notify_icon_common(WINPR_ATTR_UNUSED rdpContext* context,
831 const WINDOW_ORDER_INFO* orderInfo,
832 WINPR_ATTR_UNUSED const NOTIFY_ICON_STATE_ORDER* notifyIconState)
833{
834 WLog_ERR("TODO", "TODO: implement");
835 if (orderInfo->fieldFlags & WINDOW_ORDER_FIELD_NOTIFY_VERSION)
836 {
837 }
838
839 if (orderInfo->fieldFlags & WINDOW_ORDER_FIELD_NOTIFY_TIP)
840 {
841 }
842
843 if (orderInfo->fieldFlags & WINDOW_ORDER_FIELD_NOTIFY_INFO_TIP)
844 {
845 }
846
847 if (orderInfo->fieldFlags & WINDOW_ORDER_FIELD_NOTIFY_STATE)
848 {
849 }
850
851 if (orderInfo->fieldFlags & WINDOW_ORDER_ICON)
852 {
853 }
854
855 if (orderInfo->fieldFlags & WINDOW_ORDER_CACHED_ICON)
856 {
857 }
858
859 return TRUE;
860}
861
862static BOOL xf_rail_notify_icon_create(rdpContext* context, const WINDOW_ORDER_INFO* orderInfo,
863 const NOTIFY_ICON_STATE_ORDER* notifyIconState)
864{
865 return xf_rail_notify_icon_common(context, orderInfo, notifyIconState);
866}
867
868static BOOL xf_rail_notify_icon_update(rdpContext* context, const WINDOW_ORDER_INFO* orderInfo,
869 const NOTIFY_ICON_STATE_ORDER* notifyIconState)
870{
871 return xf_rail_notify_icon_common(context, orderInfo, notifyIconState);
872}
873
874static BOOL xf_rail_notify_icon_delete(WINPR_ATTR_UNUSED rdpContext* context,
875 WINPR_ATTR_UNUSED const WINDOW_ORDER_INFO* orderInfo)
876{
877 WLog_ERR("TODO", "TODO: implement");
878 return TRUE;
879}
880
881static BOOL
882xf_rail_monitored_desktop(WINPR_ATTR_UNUSED rdpContext* context,
883 WINPR_ATTR_UNUSED const WINDOW_ORDER_INFO* orderInfo,
884 WINPR_ATTR_UNUSED const MONITORED_DESKTOP_ORDER* monitoredDesktop)
885{
886 WLog_ERR("TODO", "TODO: implement");
887 return TRUE;
888}
889
890static BOOL xf_rail_non_monitored_desktop(rdpContext* context,
891 WINPR_ATTR_UNUSED const WINDOW_ORDER_INFO* orderInfo)
892{
893 xfContext* xfc = (xfContext*)context;
894 xf_rail_disable_remoteapp_mode(xfc);
895 return TRUE;
896}
897
898static void xf_rail_register_update_callbacks(rdpUpdate* update)
899{
900 WINPR_ASSERT(update);
901
902 rdpWindowUpdate* window = update->window;
903 WINPR_ASSERT(window);
904
905 window->WindowCreate = xf_rail_window_common;
906 window->WindowUpdate = xf_rail_window_common;
907 window->WindowDelete = xf_rail_window_delete;
908 window->WindowIcon = xf_rail_window_icon;
909 window->WindowCachedIcon = xf_rail_window_cached_icon;
910 window->NotifyIconCreate = xf_rail_notify_icon_create;
911 window->NotifyIconUpdate = xf_rail_notify_icon_update;
912 window->NotifyIconDelete = xf_rail_notify_icon_delete;
913 window->MonitoredDesktop = xf_rail_monitored_desktop;
914 window->NonMonitoredDesktop = xf_rail_non_monitored_desktop;
915}
916
917/* RemoteApp Virtual Channel Extension */
918
924static UINT xf_rail_server_execute_result(RailClientContext* context,
925 const RAIL_EXEC_RESULT_ORDER* execResult)
926{
927 WINPR_ASSERT(context);
928 WINPR_ASSERT(execResult);
929
930 xfContext* xfc = (xfContext*)context->custom;
931 WINPR_ASSERT(xfc);
932
933 if (execResult->execResult != RAIL_EXEC_S_OK)
934 {
935 WLog_ERR(TAG, "RAIL exec error: execResult=%s NtError=0x%X\n",
936 error_code_names[execResult->execResult], execResult->rawResult);
937 freerdp_abort_connect_context(&xfc->common.context);
938 }
939 else
940 {
941 xf_rail_enable_remoteapp_mode(xfc);
942 }
943
944 return CHANNEL_RC_OK;
945}
946
952static UINT xf_rail_server_system_param(WINPR_ATTR_UNUSED RailClientContext* context,
953 WINPR_ATTR_UNUSED const RAIL_SYSPARAM_ORDER* sysparam)
954{
955 // TODO: Actually apply param
956 WLog_ERR("TODO", "TODO: implement");
957 return CHANNEL_RC_OK;
958}
959
965static UINT xf_rail_server_handshake(RailClientContext* context,
966 WINPR_ATTR_UNUSED const RAIL_HANDSHAKE_ORDER* handshake)
967{
968 return client_rail_server_start_cmd(context);
969}
970
976static UINT
977xf_rail_server_handshake_ex(RailClientContext* context,
978 WINPR_ATTR_UNUSED const RAIL_HANDSHAKE_EX_ORDER* handshakeEx)
979{
980 return client_rail_server_start_cmd(context);
981}
982
988static UINT xf_rail_server_local_move_size(RailClientContext* context,
989 const RAIL_LOCALMOVESIZE_ORDER* localMoveSize)
990{
991 int x = 0;
992 int y = 0;
993 int direction = 0;
994 Window child_window = 0;
995 WINPR_ASSERT(context);
996 WINPR_ASSERT(localMoveSize);
997
998 xfContext* xfc = (xfContext*)context->custom;
999 xfAppWindow* appWindow = xf_rail_get_window(xfc, localMoveSize->windowId);
1000
1001 if (!appWindow)
1002 return ERROR_INTERNAL_ERROR;
1003
1004 switch (localMoveSize->moveSizeType)
1005 {
1006 case RAIL_WMSZ_LEFT:
1007 direction = NET_WM_MOVERESIZE_SIZE_LEFT;
1008 x = localMoveSize->posX;
1009 y = localMoveSize->posY;
1010 break;
1011
1012 case RAIL_WMSZ_RIGHT:
1013 direction = NET_WM_MOVERESIZE_SIZE_RIGHT;
1014 x = localMoveSize->posX;
1015 y = localMoveSize->posY;
1016 break;
1017
1018 case RAIL_WMSZ_TOP:
1019 direction = NET_WM_MOVERESIZE_SIZE_TOP;
1020 x = localMoveSize->posX;
1021 y = localMoveSize->posY;
1022 break;
1023
1024 case RAIL_WMSZ_TOPLEFT:
1025 direction = NET_WM_MOVERESIZE_SIZE_TOPLEFT;
1026 x = localMoveSize->posX;
1027 y = localMoveSize->posY;
1028 break;
1029
1030 case RAIL_WMSZ_TOPRIGHT:
1031 direction = NET_WM_MOVERESIZE_SIZE_TOPRIGHT;
1032 x = localMoveSize->posX;
1033 y = localMoveSize->posY;
1034 break;
1035
1036 case RAIL_WMSZ_BOTTOM:
1037 direction = NET_WM_MOVERESIZE_SIZE_BOTTOM;
1038 x = localMoveSize->posX;
1039 y = localMoveSize->posY;
1040 break;
1041
1042 case RAIL_WMSZ_BOTTOMLEFT:
1043 direction = NET_WM_MOVERESIZE_SIZE_BOTTOMLEFT;
1044 x = localMoveSize->posX;
1045 y = localMoveSize->posY;
1046 break;
1047
1048 case RAIL_WMSZ_BOTTOMRIGHT:
1049 direction = NET_WM_MOVERESIZE_SIZE_BOTTOMRIGHT;
1050 x = localMoveSize->posX;
1051 y = localMoveSize->posY;
1052 break;
1053
1054 case RAIL_WMSZ_MOVE:
1055 direction = NET_WM_MOVERESIZE_MOVE;
1056 XTranslateCoordinates(xfc->display, appWindow->handle, RootWindowOfScreen(xfc->screen),
1057 localMoveSize->posX, localMoveSize->posY, &x, &y, &child_window);
1058 break;
1059
1060 case RAIL_WMSZ_KEYMOVE:
1061 direction = NET_WM_MOVERESIZE_MOVE_KEYBOARD;
1062 x = localMoveSize->posX;
1063 y = localMoveSize->posY;
1064 /* FIXME: local keyboard moves not working */
1065 return CHANNEL_RC_OK;
1066
1067 case RAIL_WMSZ_KEYSIZE:
1068 direction = NET_WM_MOVERESIZE_SIZE_KEYBOARD;
1069 x = localMoveSize->posX;
1070 y = localMoveSize->posY;
1071 /* FIXME: local keyboard moves not working */
1072 return CHANNEL_RC_OK;
1073 default:
1074 break;
1075 }
1076
1077 if (localMoveSize->isMoveSizeStart)
1078 xf_StartLocalMoveSize(xfc, appWindow, direction, x, y);
1079 else
1080 xf_EndLocalMoveSize(xfc, appWindow);
1081
1082 return CHANNEL_RC_OK;
1083}
1084
1090static UINT xf_rail_server_min_max_info(RailClientContext* context,
1091 const RAIL_MINMAXINFO_ORDER* minMaxInfo)
1092{
1093 WINPR_ASSERT(context);
1094 WINPR_ASSERT(minMaxInfo);
1095
1096 xfContext* xfc = (xfContext*)context->custom;
1097 xfAppWindow* appWindow = xf_rail_get_window(xfc, minMaxInfo->windowId);
1098
1099 if (appWindow)
1100 {
1101 xf_SetWindowMinMaxInfo(xfc, appWindow, minMaxInfo->maxWidth, minMaxInfo->maxHeight,
1102 minMaxInfo->maxPosX, minMaxInfo->maxPosY, minMaxInfo->minTrackWidth,
1103 minMaxInfo->minTrackHeight, minMaxInfo->maxTrackWidth,
1104 minMaxInfo->maxTrackHeight);
1105 }
1106
1107 return CHANNEL_RC_OK;
1108}
1109
1115static UINT
1116xf_rail_server_language_bar_info(WINPR_ATTR_UNUSED RailClientContext* context,
1117 WINPR_ATTR_UNUSED const RAIL_LANGBAR_INFO_ORDER* langBarInfo)
1118{
1119 WLog_ERR("TODO", "TODO: implement");
1120 return CHANNEL_RC_OK;
1121}
1122
1128static UINT
1129xf_rail_server_get_appid_response(WINPR_ATTR_UNUSED RailClientContext* context,
1130 WINPR_ATTR_UNUSED const RAIL_GET_APPID_RESP_ORDER* getAppIdResp)
1131{
1132 WLog_ERR("TODO", "TODO: implement");
1133 return CHANNEL_RC_OK;
1134}
1135
1136static BOOL rail_window_key_equals(const void* key1, const void* key2)
1137{
1138 const UINT64* k1 = (const UINT64*)key1;
1139 const UINT64* k2 = (const UINT64*)key2;
1140
1141 if (!k1 || !k2)
1142 return FALSE;
1143
1144 return *k1 == *k2;
1145}
1146
1147static UINT32 rail_window_key_hash(const void* key)
1148{
1149 const UINT64* k1 = (const UINT64*)key;
1150 return (UINT32)*k1;
1151}
1152
1153static void rail_window_free(void* value)
1154{
1155 xfAppWindow* appWindow = (xfAppWindow*)value;
1156
1157 if (!appWindow)
1158 return;
1159
1160 xf_DestroyWindow(appWindow->xfc, appWindow);
1161}
1162
1163int xf_rail_init(xfContext* xfc, RailClientContext* rail)
1164{
1165 rdpContext* context = (rdpContext*)xfc;
1166
1167 if (!xfc || !rail)
1168 return 0;
1169
1170 xfc->rail = rail;
1171 xf_rail_register_update_callbacks(context->update);
1172 rail->custom = (void*)xfc;
1173 rail->ServerExecuteResult = xf_rail_server_execute_result;
1174 rail->ServerSystemParam = xf_rail_server_system_param;
1175 rail->ServerHandshake = xf_rail_server_handshake;
1176 rail->ServerHandshakeEx = xf_rail_server_handshake_ex;
1177 rail->ServerLocalMoveSize = xf_rail_server_local_move_size;
1178 rail->ServerMinMaxInfo = xf_rail_server_min_max_info;
1179 rail->ServerLanguageBarInfo = xf_rail_server_language_bar_info;
1180 rail->ServerGetAppIdResponse = xf_rail_server_get_appid_response;
1181 xfc->railWindows = HashTable_New(TRUE);
1182
1183 if (!xfc->railWindows)
1184 return 0;
1185
1186 if (!HashTable_SetHashFunction(xfc->railWindows, rail_window_key_hash))
1187 goto fail;
1188 {
1189 wObject* obj = HashTable_KeyObject(xfc->railWindows);
1190 obj->fnObjectEquals = rail_window_key_equals;
1191 }
1192 {
1193 wObject* obj = HashTable_ValueObject(xfc->railWindows);
1194 obj->fnObjectFree = rail_window_free;
1195 }
1196 xfc->railIconCache = RailIconCache_New(xfc->common.context.settings);
1197
1198 if (!xfc->railIconCache)
1199 {
1200 }
1201
1202 return 1;
1203fail:
1204 HashTable_Free(xfc->railWindows);
1205 return 0;
1206}
1207
1208int xf_rail_uninit(xfContext* xfc, RailClientContext* rail)
1209{
1210 WINPR_UNUSED(rail);
1211
1212 if (xfc->rail)
1213 {
1214 xfc->rail->custom = NULL;
1215 xfc->rail = NULL;
1216 }
1217
1218 if (xfc->railWindows)
1219 {
1220 HashTable_Free(xfc->railWindows);
1221 xfc->railWindows = NULL;
1222 }
1223
1224 if (xfc->railIconCache)
1225 {
1226 RailIconCache_Free(xfc->railIconCache);
1227 xfc->railIconCache = NULL;
1228 }
1229
1230 return 1;
1231}
1232
1233xfAppWindow* xf_rail_add_window(xfContext* xfc, UINT64 id, INT32 x, INT32 y, UINT32 width,
1234 UINT32 height, UINT32 surfaceId)
1235{
1236 if (!xfc)
1237 return NULL;
1238
1239 xfAppWindow* appWindow = (xfAppWindow*)calloc(1, sizeof(xfAppWindow));
1240
1241 if (!appWindow)
1242 return NULL;
1243
1244 appWindow->xfc = xfc;
1245 appWindow->windowId = id;
1246 appWindow->surfaceId = surfaceId;
1247 appWindow->x = x;
1248 appWindow->y = y;
1249 appWindow->width = WINPR_ASSERTING_INT_CAST(int, width);
1250 appWindow->height = WINPR_ASSERTING_INT_CAST(int, height);
1251
1252 if (!xf_AppWindowCreate(xfc, appWindow))
1253 goto fail;
1254 if (!HashTable_Insert(xfc->railWindows, &appWindow->windowId, (void*)appWindow))
1255 goto fail;
1256 return appWindow;
1257fail:
1258 rail_window_free(appWindow);
1259 return NULL;
1260}
1261
1262BOOL xf_rail_del_window(xfContext* xfc, UINT64 id)
1263{
1264 if (!xfc)
1265 return FALSE;
1266
1267 if (!xfc->railWindows)
1268 return FALSE;
1269
1270 return HashTable_Remove(xfc->railWindows, &id);
1271}
1272
1273xfAppWindow* xf_rail_get_window(xfContext* xfc, UINT64 id)
1274{
1275 if (!xfc)
1276 return NULL;
1277
1278 if (!xfc->railWindows)
1279 return FALSE;
1280
1281 return HashTable_GetItemValue(xfc->railWindows, &id);
1282}
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:57