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