FreeRDP
xf_cliprdr.c
1 
22 #include <freerdp/config.h>
23 
24 #include <stdlib.h>
25 #include <errno.h>
26 
27 #include <X11/Xlib.h>
28 #include <X11/Xatom.h>
29 
30 #ifdef WITH_XFIXES
31 #include <X11/extensions/Xfixes.h>
32 #endif
33 
34 #include <winpr/crt.h>
35 #include <winpr/assert.h>
36 #include <winpr/image.h>
37 #include <winpr/stream.h>
38 #include <winpr/clipboard.h>
39 #include <winpr/path.h>
40 
41 #include <freerdp/utils/signal.h>
42 #include <freerdp/log.h>
43 #include <freerdp/client/cliprdr.h>
44 #include <freerdp/channels/channels.h>
45 #include <freerdp/channels/cliprdr.h>
46 
47 #include <freerdp/client/client_cliprdr_file.h>
48 
49 #include "xf_cliprdr.h"
50 #include "xf_event.h"
51 #include "xf_utils.h"
52 
53 #define TAG CLIENT_TAG("x11.cliprdr")
54 
55 #define MAX_CLIPBOARD_FORMATS 255
56 
57 #ifdef WITH_DEBUG_CLIPRDR
58 #define DEBUG_CLIPRDR(...) WLog_DBG(TAG, __VA_ARGS__)
59 #else
60 #define DEBUG_CLIPRDR(...) \
61  do \
62  { \
63  } while (0)
64 #endif
65 
66 typedef struct
67 {
68  Atom atom;
69  UINT32 formatToRequest;
70  UINT32 localFormat;
71  char* formatName;
72 } xfCliprdrFormat;
73 
74 typedef struct
75 {
76  BYTE* data;
77  UINT32 data_length;
78 } xfCachedData;
79 
80 typedef struct
81 {
82  UINT32 localFormat;
83  UINT32 formatToRequest;
84  char* formatName;
85 } RequestedFormat;
86 
87 struct xf_clipboard
88 {
89  xfContext* xfc;
90  rdpChannels* channels;
91  CliprdrClientContext* context;
92 
93  wClipboard* system;
94 
95  Window root_window;
96  Atom clipboard_atom;
97  Atom property_atom;
98 
99  Atom timestamp_property_atom;
100  Time selection_ownership_timestamp;
101 
102  Atom raw_transfer_atom;
103  Atom raw_format_list_atom;
104 
105  UINT32 numClientFormats;
106  xfCliprdrFormat clientFormats[20];
107 
108  UINT32 numServerFormats;
109  CLIPRDR_FORMAT* serverFormats;
110 
111  size_t numTargets;
112  Atom targets[20];
113 
114  int requestedFormatId;
115 
116  wHashTable* cachedData;
117  wHashTable* cachedRawData;
118 
119  BOOL data_raw_format;
120 
121  RequestedFormat* requestedFormat;
122 
123  XSelectionEvent* respond;
124 
125  Window owner;
126  BOOL sync;
127 
128  /* INCR mechanism */
129  Atom incr_atom;
130  BOOL incr_starts;
131  BYTE* incr_data;
132  size_t incr_data_length;
133  long event_mask;
134 
135  /* XFixes extension */
136  int xfixes_event_base;
137  int xfixes_error_base;
138  BOOL xfixes_supported;
139 
140  /* last sent data */
141  CLIPRDR_FORMAT* lastSentFormats;
142  UINT32 lastSentNumFormats;
143  CliprdrFileContext* file;
144 };
145 
146 static const char mime_text_plain[] = "text/plain";
147 static const char mime_uri_list[] = "text/uri-list";
148 static const char mime_html[] = "text/html";
149 static const char* mime_bitmap[] = { "image/bmp", "image/x-bmp", "image/x-MS-bmp",
150  "image/x-win-bitmap" };
151 static const char mime_webp[] = "image/webp";
152 static const char mime_png[] = "image/png";
153 static const char mime_jpeg[] = "image/jpeg";
154 static const char mime_tiff[] = "image/tiff";
155 static const char* mime_images[] = { mime_webp, mime_png, mime_jpeg, mime_tiff };
156 
157 static const char mime_gnome_copied_files[] = "x-special/gnome-copied-files";
158 static const char mime_mate_copied_files[] = "x-special/mate-copied-files";
159 
160 static const char type_FileGroupDescriptorW[] = "FileGroupDescriptorW";
161 static const char type_HtmlFormat[] = "HTML Format";
162 
163 static void xf_cliprdr_clear_cached_data(xfClipboard* clipboard);
164 static UINT xf_cliprdr_send_client_format_list(xfClipboard* clipboard, BOOL force);
165 static void xf_cliprdr_set_selection_owner(xfContext* xfc, xfClipboard* clipboard, Time timestamp);
166 
167 static void requested_format_free(RequestedFormat** ppRequestedFormat)
168 {
169  if (!ppRequestedFormat)
170  return;
171  if (!(*ppRequestedFormat))
172  return;
173 
174  free((*ppRequestedFormat)->formatName);
175  free(*ppRequestedFormat);
176  *ppRequestedFormat = NULL;
177 }
178 
179 static BOOL requested_format_replace(RequestedFormat** ppRequestedFormat, UINT32 formatId,
180  const char* formatName)
181 {
182  if (!ppRequestedFormat)
183  return FALSE;
184 
185  requested_format_free(ppRequestedFormat);
186  RequestedFormat* requested = calloc(1, sizeof(RequestedFormat));
187  if (!requested)
188  return FALSE;
189  requested->localFormat = formatId;
190  requested->formatToRequest = formatId;
191  if (formatName)
192  {
193  requested->formatName = _strdup(formatName);
194  if (!requested->formatName)
195  {
196  free(requested);
197  return FALSE;
198  }
199  }
200 
201  *ppRequestedFormat = requested;
202  return TRUE;
203 }
204 
205 static BOOL requested_format_matches(const RequestedFormat* pRequestedFormat, UINT32 formatId,
206  const char* formatName)
207 {
208  if (!pRequestedFormat)
209  return FALSE;
210  if (pRequestedFormat->formatToRequest != formatId)
211  return FALSE;
212  if (formatName || pRequestedFormat->formatName)
213  {
214  if (!formatName || !pRequestedFormat->formatName)
215  return FALSE;
216  if (strcmp(formatName, pRequestedFormat->formatName) != 0)
217  return FALSE;
218  }
219  return TRUE;
220 }
221 
222 static void xf_cached_data_free(void* ptr)
223 {
224  xfCachedData* cached_data = ptr;
225  if (!cached_data)
226  return;
227 
228  free(cached_data->data);
229  free(cached_data);
230 }
231 
232 static xfCachedData* xf_cached_data_new(BYTE* data, size_t data_length)
233 {
234  if (data_length > UINT32_MAX)
235  return NULL;
236 
237  xfCachedData* cached_data = calloc(1, sizeof(xfCachedData));
238  if (!cached_data)
239  return NULL;
240 
241  cached_data->data = data;
242  cached_data->data_length = (UINT32)data_length;
243 
244  return cached_data;
245 }
246 
247 static xfCachedData* xf_cached_data_new_copy(const BYTE* data, size_t data_length)
248 {
249  BYTE* copy = NULL;
250  if (data_length > 0)
251  {
252  copy = calloc(data_length + 1, sizeof(BYTE));
253  if (!copy)
254  return NULL;
255  memcpy(copy, data, data_length);
256  }
257 
258  xfCachedData* cache = xf_cached_data_new(copy, data_length);
259  if (!cache)
260  free(copy);
261  return cache;
262 }
263 
264 static void xf_clipboard_free_server_formats(xfClipboard* clipboard)
265 {
266  WINPR_ASSERT(clipboard);
267  if (clipboard->serverFormats)
268  {
269  for (size_t i = 0; i < clipboard->numServerFormats; i++)
270  {
271  CLIPRDR_FORMAT* format = &clipboard->serverFormats[i];
272  free(format->formatName);
273  }
274 
275  free(clipboard->serverFormats);
276  clipboard->serverFormats = NULL;
277  }
278 }
279 
280 static BOOL xf_cliprdr_update_owner(xfClipboard* clipboard)
281 {
282  WINPR_ASSERT(clipboard);
283 
284  xfContext* xfc = clipboard->xfc;
285  WINPR_ASSERT(xfc);
286 
287  if (!clipboard->sync)
288  return FALSE;
289 
290  Window owner = XGetSelectionOwner(xfc->display, clipboard->clipboard_atom);
291  if (clipboard->owner == owner)
292  return FALSE;
293 
294  clipboard->owner = owner;
295  return TRUE;
296 }
297 
298 static void xf_cliprdr_check_owner(xfClipboard* clipboard)
299 {
300  if (xf_cliprdr_update_owner(clipboard))
301  xf_cliprdr_send_client_format_list(clipboard, FALSE);
302 }
303 
304 static BOOL xf_cliprdr_is_self_owned(xfClipboard* clipboard)
305 {
306  xfContext* xfc = NULL;
307 
308  WINPR_ASSERT(clipboard);
309 
310  xfc = clipboard->xfc;
311  WINPR_ASSERT(xfc);
312  return XGetSelectionOwner(xfc->display, clipboard->clipboard_atom) == xfc->drawable;
313 }
314 
315 static void xf_cliprdr_set_raw_transfer_enabled(xfClipboard* clipboard, BOOL enabled)
316 {
317  UINT32 data = WINPR_ASSERTING_INT_CAST(uint32_t, enabled);
318  xfContext* xfc = NULL;
319 
320  WINPR_ASSERT(clipboard);
321 
322  xfc = clipboard->xfc;
323  WINPR_ASSERT(xfc);
324  LogTagAndXChangeProperty(TAG, xfc->display, xfc->drawable, clipboard->raw_transfer_atom,
325  XA_INTEGER, 32, PropModeReplace, (BYTE*)&data, 1);
326 }
327 
328 static BOOL xf_cliprdr_is_raw_transfer_available(xfClipboard* clipboard)
329 {
330  Atom type = 0;
331  int format = 0;
332  int result = 0;
333  unsigned long length = 0;
334  unsigned long bytes_left = 0;
335  UINT32* data = NULL;
336  UINT32 is_enabled = 0;
337  Window owner = None;
338  xfContext* xfc = NULL;
339 
340  WINPR_ASSERT(clipboard);
341 
342  xfc = clipboard->xfc;
343  WINPR_ASSERT(xfc);
344 
345  owner = XGetSelectionOwner(xfc->display, clipboard->clipboard_atom);
346 
347  if (owner != None)
348  {
349  result = LogTagAndXGetWindowProperty(TAG, xfc->display, owner, clipboard->raw_transfer_atom,
350  0, 4, 0, XA_INTEGER, &type, &format, &length,
351  &bytes_left, (BYTE**)&data);
352  }
353 
354  if (data)
355  {
356  is_enabled = *data;
357  XFree(data);
358  }
359 
360  if ((owner == None) || (owner == xfc->drawable))
361  return FALSE;
362 
363  if (result != Success)
364  return FALSE;
365 
366  return is_enabled ? TRUE : FALSE;
367 }
368 
369 static BOOL xf_cliprdr_formats_equal(const CLIPRDR_FORMAT* server, const xfCliprdrFormat* client)
370 {
371  WINPR_ASSERT(server);
372  WINPR_ASSERT(client);
373 
374  if (server->formatName && client->formatName)
375  {
376  /* The server may be using short format names while we store them in full form. */
377  return (0 == strncmp(server->formatName, client->formatName, strlen(server->formatName)));
378  }
379 
380  if (!server->formatName && !client->formatName)
381  {
382  return (server->formatId == client->formatToRequest);
383  }
384 
385  return FALSE;
386 }
387 
388 static const xfCliprdrFormat* xf_cliprdr_get_client_format_by_id(xfClipboard* clipboard,
389  UINT32 formatId)
390 {
391  WINPR_ASSERT(clipboard);
392 
393  for (size_t index = 0; index < clipboard->numClientFormats; index++)
394  {
395  const xfCliprdrFormat* format = &(clipboard->clientFormats[index]);
396 
397  if (format->formatToRequest == formatId)
398  return format;
399  }
400 
401  return NULL;
402 }
403 
404 static const xfCliprdrFormat* xf_cliprdr_get_client_format_by_atom(xfClipboard* clipboard,
405  Atom atom)
406 {
407  WINPR_ASSERT(clipboard);
408 
409  for (UINT32 i = 0; i < clipboard->numClientFormats; i++)
410  {
411  const xfCliprdrFormat* format = &(clipboard->clientFormats[i]);
412 
413  if (format->atom == atom)
414  return format;
415  }
416 
417  return NULL;
418 }
419 
420 static const CLIPRDR_FORMAT* xf_cliprdr_get_server_format_by_atom(xfClipboard* clipboard, Atom atom)
421 {
422  WINPR_ASSERT(clipboard);
423 
424  for (size_t i = 0; i < clipboard->numClientFormats; i++)
425  {
426  const xfCliprdrFormat* client_format = &(clipboard->clientFormats[i]);
427 
428  if (client_format->atom == atom)
429  {
430  for (size_t j = 0; j < clipboard->numServerFormats; j++)
431  {
432  const CLIPRDR_FORMAT* server_format = &(clipboard->serverFormats[j]);
433 
434  if (xf_cliprdr_formats_equal(server_format, client_format))
435  return server_format;
436  }
437  }
438  }
439 
440  return NULL;
441 }
442 
448 static UINT xf_cliprdr_send_data_request(xfClipboard* clipboard, UINT32 formatId,
449  const xfCliprdrFormat* cformat)
450 {
451  CLIPRDR_FORMAT_DATA_REQUEST request = { 0 };
452  request.requestedFormatId = formatId;
453 
454  DEBUG_CLIPRDR("requesting format 0x%08" PRIx32 " [%s] {local 0x%08" PRIx32 "} [%s]", formatId,
455  ClipboardGetFormatIdString(formatId), cformat->localFormat, cformat->formatName);
456 
457  WINPR_ASSERT(clipboard);
458  WINPR_ASSERT(clipboard->context);
459  WINPR_ASSERT(clipboard->context->ClientFormatDataRequest);
460  return clipboard->context->ClientFormatDataRequest(clipboard->context, &request);
461 }
462 
468 static UINT xf_cliprdr_send_data_response(xfClipboard* clipboard, const xfCliprdrFormat* format,
469  const BYTE* data, size_t size)
470 {
471  CLIPRDR_FORMAT_DATA_RESPONSE response = { 0 };
472 
473  WINPR_ASSERT(clipboard);
474 
475  /* No request currently pending, do not send a response. */
476  if (clipboard->requestedFormatId < 0)
477  return CHANNEL_RC_OK;
478 
479  if (size == 0)
480  {
481  if (format)
482  DEBUG_CLIPRDR("send CB_RESPONSE_FAIL response {format 0x%08" PRIx32
483  " [%s] {local 0x%08" PRIx32 "} [%s]",
484  format->formatToRequest,
485  ClipboardGetFormatIdString(format->formatToRequest), format->localFormat,
486  format->formatName);
487  else
488  DEBUG_CLIPRDR("send CB_RESPONSE_FAIL response");
489  }
490  else
491  {
492  WINPR_ASSERT(format);
493  DEBUG_CLIPRDR("send response format 0x%08" PRIx32 " [%s] {local 0x%08" PRIx32 "} [%s]",
494  format->formatToRequest, ClipboardGetFormatIdString(format->formatToRequest),
495  format->localFormat, format->formatName);
496  }
497  /* Request handled, reset to invalid */
498  clipboard->requestedFormatId = -1;
499 
500  response.common.msgFlags = (data) ? CB_RESPONSE_OK : CB_RESPONSE_FAIL;
501 
502  WINPR_ASSERT(size <= UINT32_MAX);
503  response.common.dataLen = (UINT32)size;
504  response.requestedFormatData = data;
505 
506  WINPR_ASSERT(clipboard->context);
507  WINPR_ASSERT(clipboard->context->ClientFormatDataResponse);
508  return clipboard->context->ClientFormatDataResponse(clipboard->context, &response);
509 }
510 
511 static wStream* xf_cliprdr_serialize_server_format_list(xfClipboard* clipboard)
512 {
513  UINT32 formatCount = 0;
514  wStream* s = NULL;
515 
516  WINPR_ASSERT(clipboard);
517 
518  /* Typical MS Word format list is about 80 bytes long. */
519  if (!(s = Stream_New(NULL, 128)))
520  {
521  WLog_ERR(TAG, "failed to allocate serialized format list");
522  goto error;
523  }
524 
525  /* If present, the last format is always synthetic CF_RAW. Do not include it. */
526  formatCount = (clipboard->numServerFormats > 0) ? clipboard->numServerFormats - 1 : 0;
527  Stream_Write_UINT32(s, formatCount);
528 
529  for (UINT32 i = 0; i < formatCount; i++)
530  {
531  CLIPRDR_FORMAT* format = &clipboard->serverFormats[i];
532  size_t name_length = format->formatName ? strlen(format->formatName) : 0;
533 
534  DEBUG_CLIPRDR("server announced 0x%08" PRIx32 " [%s][%s]", format->formatId,
535  ClipboardGetFormatIdString(format->formatId), format->formatName);
536  if (!Stream_EnsureRemainingCapacity(s, sizeof(UINT32) + name_length + 1))
537  {
538  WLog_ERR(TAG, "failed to expand serialized format list");
539  goto error;
540  }
541 
542  Stream_Write_UINT32(s, format->formatId);
543 
544  if (format->formatName)
545  Stream_Write(s, format->formatName, name_length);
546 
547  Stream_Write_UINT8(s, '\0');
548  }
549 
550  Stream_SealLength(s);
551  return s;
552 error:
553  Stream_Free(s, TRUE);
554  return NULL;
555 }
556 
557 static CLIPRDR_FORMAT* xf_cliprdr_parse_server_format_list(BYTE* data, size_t length,
558  UINT32* numFormats)
559 {
560  wStream* s = NULL;
561  CLIPRDR_FORMAT* formats = NULL;
562 
563  WINPR_ASSERT(data || (length == 0));
564  WINPR_ASSERT(numFormats);
565 
566  if (!(s = Stream_New(data, length)))
567  {
568  WLog_ERR(TAG, "failed to allocate stream for parsing serialized format list");
569  goto error;
570  }
571 
572  if (!Stream_CheckAndLogRequiredLength(TAG, s, sizeof(UINT32)))
573  goto error;
574 
575  Stream_Read_UINT32(s, *numFormats);
576 
577  if (*numFormats > MAX_CLIPBOARD_FORMATS)
578  {
579  WLog_ERR(TAG, "unexpectedly large number of formats: %" PRIu32 "", *numFormats);
580  goto error;
581  }
582 
583  if (!(formats = (CLIPRDR_FORMAT*)calloc(*numFormats, sizeof(CLIPRDR_FORMAT))))
584  {
585  WLog_ERR(TAG, "failed to allocate format list");
586  goto error;
587  }
588 
589  for (UINT32 i = 0; i < *numFormats; i++)
590  {
591  const char* formatName = NULL;
592  size_t formatNameLength = 0;
593 
594  if (!Stream_CheckAndLogRequiredLength(TAG, s, sizeof(UINT32)))
595  goto error;
596 
597  Stream_Read_UINT32(s, formats[i].formatId);
598  formatName = (const char*)Stream_Pointer(s);
599  formatNameLength = strnlen(formatName, Stream_GetRemainingLength(s));
600 
601  if (formatNameLength == Stream_GetRemainingLength(s))
602  {
603  WLog_ERR(TAG, "missing terminating null byte, %" PRIuz " bytes left to read",
604  formatNameLength);
605  goto error;
606  }
607 
608  formats[i].formatName = strndup(formatName, formatNameLength);
609  Stream_Seek(s, formatNameLength + 1);
610  }
611 
612  Stream_Free(s, FALSE);
613  return formats;
614 error:
615  Stream_Free(s, FALSE);
616  free(formats);
617  *numFormats = 0;
618  return NULL;
619 }
620 
621 static void xf_cliprdr_free_formats(CLIPRDR_FORMAT* formats, UINT32 numFormats)
622 {
623  WINPR_ASSERT(formats || (numFormats == 0));
624 
625  for (UINT32 i = 0; i < numFormats; i++)
626  {
627  free(formats[i].formatName);
628  }
629 
630  free(formats);
631 }
632 
633 static CLIPRDR_FORMAT* xf_cliprdr_get_raw_server_formats(xfClipboard* clipboard, UINT32* numFormats)
634 {
635  Atom type = None;
636  int format = 0;
637  unsigned long length = 0;
638  unsigned long remaining = 0;
639  BYTE* data = NULL;
640  CLIPRDR_FORMAT* formats = NULL;
641  xfContext* xfc = NULL;
642 
643  WINPR_ASSERT(clipboard);
644  WINPR_ASSERT(numFormats);
645 
646  xfc = clipboard->xfc;
647  WINPR_ASSERT(xfc);
648 
649  *numFormats = 0;
650 
651  Window owner = XGetSelectionOwner(xfc->display, clipboard->clipboard_atom);
652  LogTagAndXGetWindowProperty(TAG, xfc->display, owner, clipboard->raw_format_list_atom, 0, 4096,
653  False, clipboard->raw_format_list_atom, &type, &format, &length,
654  &remaining, &data);
655 
656  if (data && length > 0 && format == 8 && type == clipboard->raw_format_list_atom)
657  {
658  formats = xf_cliprdr_parse_server_format_list(data, length, numFormats);
659  }
660  else
661  {
662  WLog_ERR(TAG,
663  "failed to retrieve raw format list: data=%p, length=%lu, format=%d, type=%lu "
664  "(expected=%lu)",
665  (void*)data, length, format, (unsigned long)type,
666  (unsigned long)clipboard->raw_format_list_atom);
667  }
668 
669  if (data)
670  XFree(data);
671 
672  return formats;
673 }
674 
675 static BOOL xf_cliprdr_should_add_format(const CLIPRDR_FORMAT* formats, size_t count,
676  const xfCliprdrFormat* xformat)
677 {
678  WINPR_ASSERT(formats);
679 
680  if (!xformat)
681  return FALSE;
682 
683  for (size_t x = 0; x < count; x++)
684  {
685  const CLIPRDR_FORMAT* format = &formats[x];
686  if (format->formatId == xformat->formatToRequest)
687  return FALSE;
688  }
689  return TRUE;
690 }
691 
692 static CLIPRDR_FORMAT* xf_cliprdr_get_formats_from_targets(xfClipboard* clipboard,
693  UINT32* numFormats)
694 {
695  Atom atom = None;
696  BYTE* data = NULL;
697  int format_property = 0;
698  unsigned long length = 0;
699  unsigned long bytes_left = 0;
700  CLIPRDR_FORMAT* formats = NULL;
701 
702  WINPR_ASSERT(clipboard);
703  WINPR_ASSERT(numFormats);
704 
705  xfContext* xfc = clipboard->xfc;
706  WINPR_ASSERT(xfc);
707 
708  *numFormats = 0;
709  LogTagAndXGetWindowProperty(TAG, xfc->display, xfc->drawable, clipboard->property_atom, 0, 200,
710  0, XA_ATOM, &atom, &format_property, &length, &bytes_left, &data);
711 
712  if (length > 0)
713  {
714  if (!data)
715  {
716  WLog_ERR(TAG, "XGetWindowProperty set length = %lu but data is NULL", length);
717  goto out;
718  }
719 
720  if (!(formats = (CLIPRDR_FORMAT*)calloc(length, sizeof(CLIPRDR_FORMAT))))
721  {
722  WLog_ERR(TAG, "failed to allocate %lu CLIPRDR_FORMAT structs", length);
723  goto out;
724  }
725  }
726 
727  for (unsigned long i = 0; i < length; i++)
728  {
729  Atom tatom = ((Atom*)data)[i];
730  const xfCliprdrFormat* format = xf_cliprdr_get_client_format_by_atom(clipboard, tatom);
731 
732  if (xf_cliprdr_should_add_format(formats, *numFormats, format))
733  {
734  CLIPRDR_FORMAT* cformat = &formats[*numFormats];
735  cformat->formatId = format->formatToRequest;
736  if (format->formatName)
737  {
738  cformat->formatName = _strdup(format->formatName);
739  WINPR_ASSERT(cformat->formatName);
740  }
741  else
742  cformat->formatName = NULL;
743 
744  *numFormats += 1;
745  }
746  }
747 
748 out:
749 
750  if (data)
751  XFree(data);
752 
753  return formats;
754 }
755 
756 static CLIPRDR_FORMAT* xf_cliprdr_get_client_formats(xfClipboard* clipboard, UINT32* numFormats)
757 {
758  CLIPRDR_FORMAT* formats = NULL;
759 
760  WINPR_ASSERT(clipboard);
761  WINPR_ASSERT(numFormats);
762 
763  *numFormats = 0;
764 
765  if (xf_cliprdr_is_raw_transfer_available(clipboard))
766  {
767  formats = xf_cliprdr_get_raw_server_formats(clipboard, numFormats);
768  }
769 
770  if (*numFormats == 0)
771  {
772  xf_cliprdr_free_formats(formats, *numFormats);
773  formats = xf_cliprdr_get_formats_from_targets(clipboard, numFormats);
774  }
775 
776  return formats;
777 }
778 
779 static void xf_cliprdr_provide_server_format_list(xfClipboard* clipboard)
780 {
781  wStream* formats = NULL;
782  xfContext* xfc = NULL;
783 
784  WINPR_ASSERT(clipboard);
785 
786  xfc = clipboard->xfc;
787  WINPR_ASSERT(xfc);
788 
789  formats = xf_cliprdr_serialize_server_format_list(clipboard);
790 
791  if (formats)
792  {
793  const size_t len = Stream_Length(formats);
794  WINPR_ASSERT(len <= INT32_MAX);
795  LogTagAndXChangeProperty(TAG, xfc->display, xfc->drawable, clipboard->raw_format_list_atom,
796  clipboard->raw_format_list_atom, 8, PropModeReplace,
797  Stream_Buffer(formats), (int)len);
798  }
799  else
800  {
801  LogTagAndXDeleteProperty(TAG, xfc->display, xfc->drawable, clipboard->raw_format_list_atom);
802  }
803 
804  Stream_Free(formats, TRUE);
805 }
806 
807 static BOOL xf_clipboard_format_equal(const CLIPRDR_FORMAT* a, const CLIPRDR_FORMAT* b)
808 {
809  WINPR_ASSERT(a);
810  WINPR_ASSERT(b);
811 
812  if (a->formatId != b->formatId)
813  return FALSE;
814  if (!a->formatName && !b->formatName)
815  return TRUE;
816  if (!a->formatName || !b->formatName)
817  return FALSE;
818  return strcmp(a->formatName, b->formatName) == 0;
819 }
820 
821 static BOOL xf_clipboard_changed(xfClipboard* clipboard, const CLIPRDR_FORMAT* formats,
822  UINT32 numFormats)
823 {
824  WINPR_ASSERT(clipboard);
825  WINPR_ASSERT(formats || (numFormats == 0));
826 
827  if (clipboard->lastSentNumFormats != numFormats)
828  return TRUE;
829 
830  for (UINT32 x = 0; x < numFormats; x++)
831  {
832  const CLIPRDR_FORMAT* cur = &clipboard->lastSentFormats[x];
833  BOOL contained = FALSE;
834  for (UINT32 y = 0; y < numFormats; y++)
835  {
836  if (xf_clipboard_format_equal(cur, &formats[y]))
837  {
838  contained = TRUE;
839  break;
840  }
841  }
842  if (!contained)
843  return TRUE;
844  }
845 
846  return FALSE;
847 }
848 
849 static void xf_clipboard_formats_free(xfClipboard* clipboard)
850 {
851  WINPR_ASSERT(clipboard);
852 
853  xf_cliprdr_free_formats(clipboard->lastSentFormats, clipboard->lastSentNumFormats);
854  clipboard->lastSentFormats = NULL;
855  clipboard->lastSentNumFormats = 0;
856 }
857 
858 static BOOL xf_clipboard_copy_formats(xfClipboard* clipboard, const CLIPRDR_FORMAT* formats,
859  UINT32 numFormats)
860 {
861  WINPR_ASSERT(clipboard);
862  WINPR_ASSERT(formats || (numFormats == 0));
863 
864  xf_clipboard_formats_free(clipboard);
865  if (numFormats > 0)
866  clipboard->lastSentFormats = calloc(numFormats, sizeof(CLIPRDR_FORMAT));
867  if (!clipboard->lastSentFormats)
868  return FALSE;
869  clipboard->lastSentNumFormats = numFormats;
870  for (UINT32 x = 0; x < numFormats; x++)
871  {
872  CLIPRDR_FORMAT* lcur = &clipboard->lastSentFormats[x];
873  const CLIPRDR_FORMAT* cur = &formats[x];
874  *lcur = *cur;
875  if (cur->formatName)
876  lcur->formatName = _strdup(cur->formatName);
877  }
878  return FALSE;
879 }
880 
881 static UINT xf_cliprdr_send_format_list(xfClipboard* clipboard, const CLIPRDR_FORMAT* formats,
882  UINT32 numFormats, BOOL force)
883 {
884  union
885  {
886  const CLIPRDR_FORMAT* cpv;
887  CLIPRDR_FORMAT* pv;
888  } cnv = { .cpv = formats };
889  const CLIPRDR_FORMAT_LIST formatList = { .common.msgFlags = 0,
890  .numFormats = numFormats,
891  .formats = cnv.pv,
892  .common.msgType = CB_FORMAT_LIST };
893  UINT ret = 0;
894 
895  WINPR_ASSERT(clipboard);
896  WINPR_ASSERT(formats || (numFormats == 0));
897 
898  if (!force && !xf_clipboard_changed(clipboard, formats, numFormats))
899  return CHANNEL_RC_OK;
900 
901 #if defined(WITH_DEBUG_CLIPRDR)
902  for (UINT32 x = 0; x < numFormats; x++)
903  {
904  const CLIPRDR_FORMAT* format = &formats[x];
905  DEBUG_CLIPRDR("announcing format 0x%08" PRIx32 " [%s] [%s]", format->formatId,
906  ClipboardGetFormatIdString(format->formatId), format->formatName);
907  }
908 #endif
909 
910  xf_clipboard_copy_formats(clipboard, formats, numFormats);
911  /* Ensure all pending requests are answered. */
912  xf_cliprdr_send_data_response(clipboard, NULL, NULL, 0);
913 
914  xf_cliprdr_clear_cached_data(clipboard);
915 
916  ret = cliprdr_file_context_notify_new_client_format_list(clipboard->file);
917  if (ret)
918  return ret;
919 
920  WINPR_ASSERT(clipboard->context);
921  WINPR_ASSERT(clipboard->context->ClientFormatList);
922  return clipboard->context->ClientFormatList(clipboard->context, &formatList);
923 }
924 
925 static void xf_cliprdr_get_requested_targets(xfClipboard* clipboard)
926 {
927  UINT32 numFormats = 0;
928  CLIPRDR_FORMAT* formats = xf_cliprdr_get_client_formats(clipboard, &numFormats);
929  xf_cliprdr_send_format_list(clipboard, formats, numFormats, FALSE);
930  xf_cliprdr_free_formats(formats, numFormats);
931 }
932 
933 static void xf_cliprdr_process_requested_data(xfClipboard* clipboard, BOOL hasData,
934  const BYTE* data, size_t size)
935 {
936  BOOL bSuccess = 0;
937  UINT32 SrcSize = 0;
938  UINT32 DstSize = 0;
939  INT64 srcFormatId = -1;
940  BYTE* pDstData = NULL;
941  const xfCliprdrFormat* format = NULL;
942 
943  WINPR_ASSERT(clipboard);
944 
945  if (clipboard->incr_starts && hasData)
946  return;
947 
948  /* Reset incr_data_length, as we've reached the end of a possible incremental update.
949  * this ensures on next event that the buffer is not reused. */
950  clipboard->incr_data_length = 0;
951 
952  format = xf_cliprdr_get_client_format_by_id(
953  clipboard, WINPR_ASSERTING_INT_CAST(uint32_t, clipboard->requestedFormatId));
954 
955  if (!hasData || !data || !format)
956  {
957  xf_cliprdr_send_data_response(clipboard, format, NULL, 0);
958  return;
959  }
960 
961  switch (format->formatToRequest)
962  {
963  case CF_RAW:
964  srcFormatId = CF_RAW;
965  break;
966 
967  case CF_TEXT:
968  case CF_OEMTEXT:
969  case CF_UNICODETEXT:
970  srcFormatId = format->localFormat;
971  break;
972 
973  default:
974  srcFormatId = format->localFormat;
975  break;
976  }
977 
978  if (srcFormatId < 0)
979  {
980  xf_cliprdr_send_data_response(clipboard, format, NULL, 0);
981  return;
982  }
983 
984  ClipboardLock(clipboard->system);
985  SrcSize = (UINT32)size;
986  bSuccess = ClipboardSetData(clipboard->system, (UINT32)srcFormatId, data, SrcSize);
987 
988  if (bSuccess)
989  {
990  DstSize = 0;
991  pDstData = (BYTE*)ClipboardGetData(clipboard->system, format->formatToRequest, &DstSize);
992  }
993  ClipboardUnlock(clipboard->system);
994 
995  if (!pDstData)
996  {
997  xf_cliprdr_send_data_response(clipboard, format, NULL, 0);
998  return;
999  }
1000 
1001  /*
1002  * File lists require a bit of postprocessing to convert them from WinPR's FILDESCRIPTOR
1003  * format to CLIPRDR_FILELIST expected by the server.
1004  *
1005  * We check for "FileGroupDescriptorW" format being registered (i.e., nonzero) in order
1006  * to not process CF_RAW as a file list in case WinPR does not support file transfers.
1007  */
1008  ClipboardLock(clipboard->system);
1009  if (format->formatToRequest &&
1010  (format->formatToRequest ==
1011  ClipboardGetFormatId(clipboard->system, type_FileGroupDescriptorW)))
1012  {
1013  UINT error = NO_ERROR;
1014  FILEDESCRIPTORW* file_array = (FILEDESCRIPTORW*)pDstData;
1015  UINT32 file_count = DstSize / sizeof(FILEDESCRIPTORW);
1016  pDstData = NULL;
1017  DstSize = 0;
1018 
1019  const UINT32 flags = cliprdr_file_context_remote_get_flags(clipboard->file);
1020  error = cliprdr_serialize_file_list_ex(flags, file_array, file_count, &pDstData, &DstSize);
1021 
1022  if (error)
1023  WLog_ERR(TAG, "failed to serialize CLIPRDR_FILELIST: 0x%08X", error);
1024  else
1025  {
1026  UINT32 formatId = ClipboardGetFormatId(clipboard->system, mime_uri_list);
1027  UINT32 url_size = 0;
1028 
1029  char* url = ClipboardGetData(clipboard->system, formatId, &url_size);
1030  cliprdr_file_context_update_client_data(clipboard->file, url, url_size);
1031  free(url);
1032  }
1033 
1034  free(file_array);
1035  }
1036  ClipboardUnlock(clipboard->system);
1037 
1038  xf_cliprdr_send_data_response(clipboard, format, pDstData, DstSize);
1039  free(pDstData);
1040 }
1041 
1042 static BOOL xf_add_input_flags(xfClipboard* clipboard, long mask)
1043 {
1044  WINPR_ASSERT(clipboard);
1045 
1046  xfContext* xfc = clipboard->xfc;
1047  WINPR_ASSERT(xfc);
1048 
1049  XWindowAttributes attr = { 0 };
1050  XGetWindowAttributes(xfc->display, xfc->drawable, &attr);
1051  if ((attr.all_event_masks & mask) == 0)
1052  clipboard->event_mask = attr.all_event_masks;
1053 
1054  XSelectInput(xfc->display, xfc->drawable, attr.all_event_masks | mask);
1055  return TRUE;
1056 }
1057 
1058 static BOOL xf_restore_input_flags(xfClipboard* clipboard)
1059 {
1060  WINPR_ASSERT(clipboard);
1061 
1062  xfContext* xfc = clipboard->xfc;
1063  WINPR_ASSERT(xfc);
1064 
1065  if (clipboard->event_mask != 0)
1066  {
1067  XSelectInput(xfc->display, xfc->drawable, clipboard->event_mask);
1068  clipboard->event_mask = 0;
1069  }
1070  return TRUE;
1071 }
1072 
1073 static BOOL append(xfClipboard* clipboard, const void* sdata, size_t length)
1074 {
1075  WINPR_ASSERT(clipboard);
1076 
1077  const size_t size = length + clipboard->incr_data_length + 2;
1078  BYTE* data = realloc(clipboard->incr_data, size);
1079  if (!data)
1080  return FALSE;
1081  clipboard->incr_data = data;
1082  memcpy(&data[clipboard->incr_data_length], sdata, length);
1083  clipboard->incr_data_length += length;
1084  clipboard->incr_data[clipboard->incr_data_length + 0] = '\0';
1085  clipboard->incr_data[clipboard->incr_data_length + 1] = '\0';
1086  return TRUE;
1087 }
1088 
1089 static BOOL xf_cliprdr_stop_incr(xfClipboard* clipboard)
1090 {
1091  clipboard->incr_starts = FALSE;
1092  clipboard->incr_data_length = 0;
1093  return xf_restore_input_flags(clipboard);
1094 }
1095 
1096 static BOOL xf_cliprdr_get_requested_data(xfClipboard* clipboard, Atom target)
1097 {
1098  WINPR_ASSERT(clipboard);
1099 
1100  xfContext* xfc = clipboard->xfc;
1101  WINPR_ASSERT(xfc);
1102 
1103  const xfCliprdrFormat* format = xf_cliprdr_get_client_format_by_id(
1104  clipboard, WINPR_ASSERTING_INT_CAST(uint32_t, clipboard->requestedFormatId));
1105 
1106  if (!format || (format->atom != target))
1107  {
1108  xf_cliprdr_send_data_response(clipboard, format, NULL, 0);
1109  return FALSE;
1110  }
1111 
1112  Atom type = 0;
1113  BOOL has_data = FALSE;
1114  int format_property = 0;
1115  unsigned long length = 0;
1116  unsigned long total_bytes = 0;
1117  BYTE* property_data = NULL;
1118  const int rc = LogTagAndXGetWindowProperty(
1119  TAG, xfc->display, xfc->drawable, clipboard->property_atom, 0, 0, False, target, &type,
1120  &format_property, &length, &total_bytes, &property_data);
1121  if (rc != Success)
1122  {
1123  xf_cliprdr_send_data_response(clipboard, format, NULL, 0);
1124  return FALSE;
1125  }
1126 
1127  size_t len = 0;
1128 
1129  /* No data, empty return */
1130  if ((total_bytes <= 0) && !clipboard->incr_starts)
1131  {
1132  xf_cliprdr_stop_incr(clipboard);
1133  }
1134  /* We have to read incremental updates */
1135  else if (type == clipboard->incr_atom)
1136  {
1137  xf_cliprdr_stop_incr(clipboard);
1138  clipboard->incr_starts = TRUE;
1139  has_data = TRUE; /* data will follow in PropertyNotify event */
1140  }
1141  else
1142  {
1143  BYTE* incremental_data = NULL;
1144  unsigned long incremental_len = 0;
1145 
1146  /* Incremental updates completed, pass data */
1147  len = clipboard->incr_data_length;
1148  if (total_bytes <= 0)
1149  {
1150  xf_cliprdr_stop_incr(clipboard);
1151  has_data = TRUE;
1152  }
1153  /* Read incremental data batch */
1154  else if (LogTagAndXGetWindowProperty(
1155  TAG, xfc->display, xfc->drawable, clipboard->property_atom, 0,
1156  WINPR_ASSERTING_INT_CAST(int32_t, total_bytes), False, target, &type,
1157  &format_property, &incremental_len, &length, &incremental_data) == Success)
1158  {
1159  has_data = append(clipboard, incremental_data, incremental_len);
1160  len = clipboard->incr_data_length;
1161  }
1162 
1163  if (incremental_data)
1164  XFree(incremental_data);
1165  }
1166 
1167  LogTagAndXDeleteProperty(TAG, xfc->display, xfc->drawable, clipboard->property_atom);
1168  xf_cliprdr_process_requested_data(clipboard, has_data, clipboard->incr_data, len);
1169 
1170  return TRUE;
1171 }
1172 
1173 static void xf_cliprdr_append_target(xfClipboard* clipboard, Atom target)
1174 {
1175  WINPR_ASSERT(clipboard);
1176 
1177  if (clipboard->numTargets >= ARRAYSIZE(clipboard->targets))
1178  return;
1179 
1180  for (size_t i = 0; i < clipboard->numTargets; i++)
1181  {
1182  if (clipboard->targets[i] == target)
1183  return;
1184  }
1185 
1186  clipboard->targets[clipboard->numTargets++] = target;
1187 }
1188 
1189 static void xf_cliprdr_provide_targets(xfClipboard* clipboard, const XSelectionEvent* respond)
1190 {
1191  xfContext* xfc = NULL;
1192 
1193  WINPR_ASSERT(clipboard);
1194 
1195  xfc = clipboard->xfc;
1196  WINPR_ASSERT(xfc);
1197 
1198  if (respond->property != None)
1199  {
1200  WINPR_ASSERT(clipboard->numTargets <= INT32_MAX);
1201  LogTagAndXChangeProperty(TAG, xfc->display, respond->requestor, respond->property, XA_ATOM,
1202  32, PropModeReplace, (BYTE*)clipboard->targets,
1203  (int)clipboard->numTargets);
1204  }
1205 }
1206 
1207 static void xf_cliprdr_provide_timestamp(xfClipboard* clipboard, const XSelectionEvent* respond)
1208 {
1209  xfContext* xfc = NULL;
1210 
1211  WINPR_ASSERT(clipboard);
1212 
1213  xfc = clipboard->xfc;
1214  WINPR_ASSERT(xfc);
1215 
1216  if (respond->property != None)
1217  {
1218  LogTagAndXChangeProperty(TAG, xfc->display, respond->requestor, respond->property,
1219  XA_INTEGER, 32, PropModeReplace,
1220  (BYTE*)&clipboard->selection_ownership_timestamp, 1);
1221  }
1222 }
1223 
1224 static void xf_cliprdr_provide_data(xfClipboard* clipboard, const XSelectionEvent* respond,
1225  const BYTE* data, UINT32 size)
1226 {
1227  xfContext* xfc = NULL;
1228 
1229  WINPR_ASSERT(clipboard);
1230 
1231  xfc = clipboard->xfc;
1232  WINPR_ASSERT(xfc);
1233 
1234  if (respond->property != None)
1235  {
1236  LogTagAndXChangeProperty(TAG, xfc->display, respond->requestor, respond->property,
1237  respond->target, 8, PropModeReplace, data,
1238  WINPR_ASSERTING_INT_CAST(int32_t, size));
1239  }
1240 }
1241 
1242 static void log_selection_event(xfContext* xfc, const XEvent* event)
1243 {
1244  const DWORD level = WLOG_TRACE;
1245  static wLog* _log_cached_ptr = NULL;
1246  if (!_log_cached_ptr)
1247  _log_cached_ptr = WLog_Get(TAG);
1248  if (WLog_IsLevelActive(_log_cached_ptr, level))
1249  {
1250 
1251  switch (event->type)
1252  {
1253  case SelectionClear:
1254  {
1255  const XSelectionClearEvent* xevent = &event->xselectionclear;
1256  char* selection =
1257  Safe_XGetAtomName(_log_cached_ptr, xfc->display, xevent->selection);
1258  WLog_Print(_log_cached_ptr, level, "got event %s [selection %s]",
1259  x11_event_string(event->type), selection);
1260  XFree(selection);
1261  }
1262  break;
1263  case SelectionNotify:
1264  {
1265  const XSelectionEvent* xevent = &event->xselection;
1266  char* selection =
1267  Safe_XGetAtomName(_log_cached_ptr, xfc->display, xevent->selection);
1268  char* target = Safe_XGetAtomName(_log_cached_ptr, xfc->display, xevent->target);
1269  char* property = Safe_XGetAtomName(_log_cached_ptr, xfc->display, xevent->property);
1270  WLog_Print(_log_cached_ptr, level,
1271  "got event %s [selection %s, target %s, property %s]",
1272  x11_event_string(event->type), selection, target, property);
1273  XFree(selection);
1274  XFree(target);
1275  XFree(property);
1276  }
1277  break;
1278  case SelectionRequest:
1279  {
1280  const XSelectionRequestEvent* xevent = &event->xselectionrequest;
1281  char* selection =
1282  Safe_XGetAtomName(_log_cached_ptr, xfc->display, xevent->selection);
1283  char* target = Safe_XGetAtomName(_log_cached_ptr, xfc->display, xevent->target);
1284  char* property = Safe_XGetAtomName(_log_cached_ptr, xfc->display, xevent->property);
1285  WLog_Print(_log_cached_ptr, level,
1286  "got event %s [selection %s, target %s, property %s]",
1287  x11_event_string(event->type), selection, target, property);
1288  XFree(selection);
1289  XFree(target);
1290  XFree(property);
1291  }
1292  break;
1293  case PropertyNotify:
1294  {
1295  const XPropertyEvent* xevent = &event->xproperty;
1296  char* atom = Safe_XGetAtomName(_log_cached_ptr, xfc->display, xevent->atom);
1297  WLog_Print(_log_cached_ptr, level, "got event %s [atom %s]",
1298  x11_event_string(event->type), atom);
1299  XFree(atom);
1300  }
1301  break;
1302  default:
1303  break;
1304  }
1305  }
1306 }
1307 
1308 static BOOL xf_cliprdr_process_selection_notify(xfClipboard* clipboard,
1309  const XSelectionEvent* xevent)
1310 {
1311  WINPR_ASSERT(clipboard);
1312  WINPR_ASSERT(xevent);
1313 
1314  if (xevent->target == clipboard->targets[1])
1315  {
1316  if (xevent->property == None)
1317  {
1318  xf_cliprdr_send_client_format_list(clipboard, FALSE);
1319  }
1320  else
1321  {
1322  xf_cliprdr_get_requested_targets(clipboard);
1323  }
1324 
1325  return TRUE;
1326  }
1327  else
1328  {
1329  return xf_cliprdr_get_requested_data(clipboard, xevent->target);
1330  }
1331 }
1332 
1333 void xf_cliprdr_clear_cached_data(xfClipboard* clipboard)
1334 {
1335  WINPR_ASSERT(clipboard);
1336 
1337  ClipboardLock(clipboard->system);
1338  ClipboardEmpty(clipboard->system);
1339 
1340  HashTable_Clear(clipboard->cachedData);
1341  HashTable_Clear(clipboard->cachedRawData);
1342 
1343  cliprdr_file_context_clear(clipboard->file);
1344 
1345  xf_cliprdr_stop_incr(clipboard);
1346  ClipboardUnlock(clipboard->system);
1347 }
1348 
1349 static void* format_to_cache_slot(UINT32 format)
1350 {
1351  union
1352  {
1353  uintptr_t uptr;
1354  void* vptr;
1355  } cnv;
1356  cnv.uptr = 0x100000000ULL + format;
1357  return cnv.vptr;
1358 }
1359 
1360 static UINT32 get_dst_format_id_for_local_request(xfClipboard* clipboard,
1361  const xfCliprdrFormat* format)
1362 {
1363  UINT32 dstFormatId = 0;
1364 
1365  WINPR_ASSERT(format);
1366 
1367  if (!format->formatName)
1368  return format->localFormat;
1369 
1370  ClipboardLock(clipboard->system);
1371  if (strcmp(format->formatName, type_HtmlFormat) == 0)
1372  dstFormatId = ClipboardGetFormatId(clipboard->system, mime_html);
1373  ClipboardUnlock(clipboard->system);
1374 
1375  if (strcmp(format->formatName, type_FileGroupDescriptorW) == 0)
1376  dstFormatId = format->localFormat;
1377 
1378  return dstFormatId;
1379 }
1380 
1381 static void get_src_format_info_for_local_request(xfClipboard* clipboard,
1382  const xfCliprdrFormat* format,
1383  UINT32* srcFormatId, BOOL* nullTerminated)
1384 {
1385  *srcFormatId = 0;
1386  *nullTerminated = FALSE;
1387 
1388  if (format->formatName)
1389  {
1390  ClipboardLock(clipboard->system);
1391  if (strcmp(format->formatName, type_HtmlFormat) == 0)
1392  {
1393  *srcFormatId = ClipboardGetFormatId(clipboard->system, type_HtmlFormat);
1394  *nullTerminated = TRUE;
1395  }
1396  else if (strcmp(format->formatName, type_FileGroupDescriptorW) == 0)
1397  {
1398  *srcFormatId = ClipboardGetFormatId(clipboard->system, type_FileGroupDescriptorW);
1399  *nullTerminated = TRUE;
1400  }
1401  ClipboardUnlock(clipboard->system);
1402  }
1403  else
1404  {
1405  *srcFormatId = format->formatToRequest;
1406  switch (format->formatToRequest)
1407  {
1408  case CF_TEXT:
1409  case CF_OEMTEXT:
1410  case CF_UNICODETEXT:
1411  *nullTerminated = TRUE;
1412  break;
1413  case CF_DIB:
1414  *srcFormatId = CF_DIB;
1415  break;
1416  case CF_TIFF:
1417  *srcFormatId = CF_TIFF;
1418  break;
1419  default:
1420  break;
1421  }
1422  }
1423 }
1424 
1425 static xfCachedData* convert_data_from_existing_raw_data(xfClipboard* clipboard,
1426  xfCachedData* cached_raw_data,
1427  UINT32 srcFormatId, BOOL nullTerminated,
1428  UINT32 dstFormatId)
1429 {
1430  xfCachedData* cached_data = NULL;
1431  BOOL success = 0;
1432  BYTE* dst_data = NULL;
1433  UINT32 dst_size = 0;
1434 
1435  WINPR_ASSERT(clipboard);
1436  WINPR_ASSERT(cached_raw_data);
1437  WINPR_ASSERT(cached_raw_data->data);
1438 
1439  ClipboardLock(clipboard->system);
1440  success = ClipboardSetData(clipboard->system, srcFormatId, cached_raw_data->data,
1441  cached_raw_data->data_length);
1442  if (!success)
1443  {
1444  WLog_WARN(TAG, "Failed to set clipboard data (formatId: %u, data: %p, data_length: %u)",
1445  srcFormatId, cached_raw_data->data, cached_raw_data->data_length);
1446  ClipboardUnlock(clipboard->system);
1447  return NULL;
1448  }
1449 
1450  dst_data = ClipboardGetData(clipboard->system, dstFormatId, &dst_size);
1451  if (!dst_data)
1452  {
1453  WLog_WARN(TAG, "Failed to get converted clipboard data");
1454  ClipboardUnlock(clipboard->system);
1455  return NULL;
1456  }
1457  ClipboardUnlock(clipboard->system);
1458 
1459  if (nullTerminated)
1460  {
1461  BYTE* nullTerminator = memchr(dst_data, '\0', dst_size);
1462  if (nullTerminator)
1463  {
1464  const intptr_t diff = nullTerminator - dst_data;
1465  WINPR_ASSERT(diff >= 0);
1466  WINPR_ASSERT(diff <= UINT32_MAX);
1467  dst_size = (UINT32)diff;
1468  }
1469  }
1470 
1471  cached_data = xf_cached_data_new(dst_data, dst_size);
1472  if (!cached_data)
1473  {
1474  WLog_WARN(TAG, "Failed to allocate cache entry");
1475  free(dst_data);
1476  return NULL;
1477  }
1478 
1479  if (!HashTable_Insert(clipboard->cachedData, format_to_cache_slot(dstFormatId), cached_data))
1480  {
1481  WLog_WARN(TAG, "Failed to cache clipboard data");
1482  xf_cached_data_free(cached_data);
1483  return NULL;
1484  }
1485 
1486  return cached_data;
1487 }
1488 
1489 static BOOL xf_cliprdr_process_selection_request(xfClipboard* clipboard,
1490  const XSelectionRequestEvent* xevent)
1491 {
1492  int fmt = 0;
1493  Atom type = 0;
1494  UINT32 formatId = 0;
1495  XSelectionEvent* respond = NULL;
1496  BYTE* data = NULL;
1497  BOOL delayRespond = 0;
1498  BOOL rawTransfer = 0;
1499  unsigned long length = 0;
1500  unsigned long bytes_left = 0;
1501  xfContext* xfc = NULL;
1502 
1503  WINPR_ASSERT(clipboard);
1504  WINPR_ASSERT(xevent);
1505 
1506  xfc = clipboard->xfc;
1507  WINPR_ASSERT(xfc);
1508 
1509  if (xevent->owner != xfc->drawable)
1510  return FALSE;
1511 
1512  delayRespond = FALSE;
1513 
1514  if (!(respond = (XSelectionEvent*)calloc(1, sizeof(XSelectionEvent))))
1515  {
1516  WLog_ERR(TAG, "failed to allocate XEvent data");
1517  return FALSE;
1518  }
1519 
1520  respond->property = None;
1521  respond->type = SelectionNotify;
1522  respond->display = xevent->display;
1523  respond->requestor = xevent->requestor;
1524  respond->selection = xevent->selection;
1525  respond->target = xevent->target;
1526  respond->time = xevent->time;
1527 
1528  if (xevent->target == clipboard->targets[0]) /* TIMESTAMP */
1529  {
1530  /* Someone else requests the selection's timestamp */
1531  respond->property = xevent->property;
1532  xf_cliprdr_provide_timestamp(clipboard, respond);
1533  }
1534  else if (xevent->target == clipboard->targets[1]) /* TARGETS */
1535  {
1536  /* Someone else requests our available formats */
1537  respond->property = xevent->property;
1538  xf_cliprdr_provide_targets(clipboard, respond);
1539  }
1540  else
1541  {
1542  const CLIPRDR_FORMAT* format =
1543  xf_cliprdr_get_server_format_by_atom(clipboard, xevent->target);
1544  const xfCliprdrFormat* cformat =
1545  xf_cliprdr_get_client_format_by_atom(clipboard, xevent->target);
1546 
1547  if (format && (xevent->requestor != xfc->drawable))
1548  {
1549  formatId = format->formatId;
1550  rawTransfer = FALSE;
1551  xfCachedData* cached_data = NULL;
1552  UINT32 dstFormatId = 0;
1553 
1554  if (formatId == CF_RAW)
1555  {
1556  if (LogTagAndXGetWindowProperty(
1557  TAG, xfc->display, xevent->requestor, clipboard->property_atom, 0, 4, 0,
1558  XA_INTEGER, &type, &fmt, &length, &bytes_left, &data) != Success)
1559  {
1560  }
1561 
1562  if (data)
1563  {
1564  rawTransfer = TRUE;
1565  CopyMemory(&formatId, data, 4);
1566  XFree(data);
1567  }
1568  }
1569 
1570  dstFormatId = get_dst_format_id_for_local_request(clipboard, cformat);
1571  DEBUG_CLIPRDR("formatId: %u, dstFormatId: %u", formatId, dstFormatId);
1572 
1573  if (!rawTransfer)
1574  cached_data = HashTable_GetItemValue(clipboard->cachedData,
1575  format_to_cache_slot(dstFormatId));
1576  else
1577  cached_data = HashTable_GetItemValue(clipboard->cachedRawData,
1578  format_to_cache_slot(formatId));
1579 
1580  DEBUG_CLIPRDR("hasCachedData: %u, rawTransfer: %u", cached_data ? 1 : 0, rawTransfer);
1581 
1582  if (!cached_data && !rawTransfer)
1583  {
1584  UINT32 srcFormatId = 0;
1585  BOOL nullTerminated = FALSE;
1586  xfCachedData* cached_raw_data = NULL;
1587 
1588  get_src_format_info_for_local_request(clipboard, cformat, &srcFormatId,
1589  &nullTerminated);
1590  cached_raw_data =
1591  HashTable_GetItemValue(clipboard->cachedRawData, (void*)(UINT_PTR)srcFormatId);
1592 
1593  DEBUG_CLIPRDR("hasCachedRawData: %u, rawDataLength: %u", cached_raw_data ? 1 : 0,
1594  cached_raw_data ? cached_raw_data->data_length : 0);
1595 
1596  if (cached_raw_data && cached_raw_data->data_length != 0)
1597  cached_data = convert_data_from_existing_raw_data(
1598  clipboard, cached_raw_data, srcFormatId, nullTerminated, dstFormatId);
1599  }
1600 
1601  DEBUG_CLIPRDR("hasCachedData: %u", cached_data ? 1 : 0);
1602 
1603  if (cached_data)
1604  {
1605  /* Cached clipboard data available. Send it now */
1606  respond->property = xevent->property;
1607 
1608  // NOLINTNEXTLINE(clang-analyzer-unix.Malloc)
1609  xf_cliprdr_provide_data(clipboard, respond, cached_data->data,
1610  cached_data->data_length);
1611  }
1612  else if (clipboard->respond)
1613  {
1614  /* duplicate request */
1615  }
1616  else
1617  {
1618  WINPR_ASSERT(cformat);
1619 
1624  respond->property = xevent->property;
1625  clipboard->respond = respond;
1626  requested_format_replace(&clipboard->requestedFormat, formatId,
1627  cformat->formatName);
1628  clipboard->data_raw_format = rawTransfer;
1629  delayRespond = TRUE;
1630  xf_cliprdr_send_data_request(clipboard, formatId, cformat);
1631  }
1632  }
1633  }
1634 
1635  if (!delayRespond)
1636  {
1637  union
1638  {
1639  XEvent* ev;
1640  XSelectionEvent* sev;
1641  } conv;
1642 
1643  conv.sev = respond;
1644  XSendEvent(xfc->display, xevent->requestor, 0, 0, conv.ev);
1645  XFlush(xfc->display);
1646  free(respond);
1647  }
1648 
1649  return TRUE;
1650 }
1651 
1652 static BOOL xf_cliprdr_process_selection_clear(xfClipboard* clipboard,
1653  const XSelectionClearEvent* xevent)
1654 {
1655  xfContext* xfc = NULL;
1656 
1657  WINPR_ASSERT(clipboard);
1658  WINPR_ASSERT(xevent);
1659 
1660  xfc = clipboard->xfc;
1661  WINPR_ASSERT(xfc);
1662 
1663  WINPR_UNUSED(xevent);
1664 
1665  if (xf_cliprdr_is_self_owned(clipboard))
1666  return FALSE;
1667 
1668  LogTagAndXDeleteProperty(TAG, xfc->display, clipboard->root_window, clipboard->property_atom);
1669  return TRUE;
1670 }
1671 
1672 static BOOL xf_cliprdr_process_property_notify(xfClipboard* clipboard, const XPropertyEvent* xevent)
1673 {
1674  const xfCliprdrFormat* format = NULL;
1675  xfContext* xfc = NULL;
1676 
1677  if (!clipboard)
1678  return TRUE;
1679 
1680  xfc = clipboard->xfc;
1681  WINPR_ASSERT(xfc);
1682  WINPR_ASSERT(xevent);
1683 
1684  if (xevent->atom == clipboard->timestamp_property_atom)
1685  {
1686  /* This is the response to the property change we did
1687  * in xf_cliprdr_prepare_to_set_selection_owner. Now
1688  * we can set ourselves as the selection owner. (See
1689  * comments in those functions below.) */
1690  xf_cliprdr_set_selection_owner(xfc, clipboard, xevent->time);
1691  return TRUE;
1692  }
1693 
1694  if (xevent->atom != clipboard->property_atom)
1695  return FALSE; /* Not cliprdr-related */
1696 
1697  if (xevent->window == clipboard->root_window)
1698  {
1699  xf_cliprdr_send_client_format_list(clipboard, FALSE);
1700  }
1701  else if ((xevent->window == xfc->drawable) && (xevent->state == PropertyNewValue) &&
1702  clipboard->incr_starts)
1703  {
1704  format = xf_cliprdr_get_client_format_by_id(
1705  clipboard, WINPR_ASSERTING_INT_CAST(uint32_t, clipboard->requestedFormatId));
1706 
1707  if (format)
1708  xf_cliprdr_get_requested_data(clipboard, format->atom);
1709  }
1710 
1711  return TRUE;
1712 }
1713 
1714 void xf_cliprdr_handle_xevent(xfContext* xfc, const XEvent* event)
1715 {
1716  xfClipboard* clipboard = NULL;
1717 
1718  if (!xfc || !event)
1719  return;
1720 
1721  clipboard = xfc->clipboard;
1722 
1723  if (!clipboard)
1724  return;
1725 
1726 #ifdef WITH_XFIXES
1727 
1728  if (clipboard->xfixes_supported &&
1729  event->type == XFixesSelectionNotify + clipboard->xfixes_event_base)
1730  {
1731  const XFixesSelectionNotifyEvent* se = (const XFixesSelectionNotifyEvent*)event;
1732 
1733  if (se->subtype == XFixesSetSelectionOwnerNotify)
1734  {
1735  if (se->selection != clipboard->clipboard_atom)
1736  return;
1737 
1738  if (XGetSelectionOwner(xfc->display, se->selection) == xfc->drawable)
1739  return;
1740 
1741  clipboard->owner = None;
1742  xf_cliprdr_check_owner(clipboard);
1743  }
1744 
1745  return;
1746  }
1747 
1748 #endif
1749 
1750  switch (event->type)
1751  {
1752  case SelectionNotify:
1753  log_selection_event(xfc, event);
1754  xf_cliprdr_process_selection_notify(clipboard, &event->xselection);
1755  break;
1756 
1757  case SelectionRequest:
1758  log_selection_event(xfc, event);
1759  xf_cliprdr_process_selection_request(clipboard, &event->xselectionrequest);
1760  break;
1761 
1762  case SelectionClear:
1763  log_selection_event(xfc, event);
1764  xf_cliprdr_process_selection_clear(clipboard, &event->xselectionclear);
1765  break;
1766 
1767  case PropertyNotify:
1768  log_selection_event(xfc, event);
1769  xf_cliprdr_process_property_notify(clipboard, &event->xproperty);
1770  break;
1771 
1772  case FocusIn:
1773  if (!clipboard->xfixes_supported)
1774  {
1775  xf_cliprdr_check_owner(clipboard);
1776  }
1777 
1778  break;
1779  default:
1780  break;
1781  }
1782 }
1783 
1789 static UINT xf_cliprdr_send_client_capabilities(xfClipboard* clipboard)
1790 {
1791  CLIPRDR_CAPABILITIES capabilities = { 0 };
1792  CLIPRDR_GENERAL_CAPABILITY_SET generalCapabilitySet = { 0 };
1793 
1794  WINPR_ASSERT(clipboard);
1795 
1796  capabilities.cCapabilitiesSets = 1;
1797  capabilities.capabilitySets = (CLIPRDR_CAPABILITY_SET*)&(generalCapabilitySet);
1798  generalCapabilitySet.capabilitySetType = CB_CAPSTYPE_GENERAL;
1799  generalCapabilitySet.capabilitySetLength = 12;
1800  generalCapabilitySet.version = CB_CAPS_VERSION_2;
1801  generalCapabilitySet.generalFlags = CB_USE_LONG_FORMAT_NAMES;
1802 
1803  WINPR_ASSERT(clipboard);
1804  generalCapabilitySet.generalFlags |= cliprdr_file_context_current_flags(clipboard->file);
1805 
1806  WINPR_ASSERT(clipboard->context);
1807  WINPR_ASSERT(clipboard->context->ClientCapabilities);
1808  return clipboard->context->ClientCapabilities(clipboard->context, &capabilities);
1809 }
1810 
1816 static UINT xf_cliprdr_send_client_format_list(xfClipboard* clipboard, BOOL force)
1817 {
1818  WINPR_ASSERT(clipboard);
1819 
1820  xfContext* xfc = clipboard->xfc;
1821  WINPR_ASSERT(xfc);
1822 
1823  UINT32 numFormats = 0;
1824  CLIPRDR_FORMAT* formats = xf_cliprdr_get_client_formats(clipboard, &numFormats);
1825 
1826  const UINT ret = xf_cliprdr_send_format_list(clipboard, formats, numFormats, force);
1827 
1828  if (clipboard->owner && clipboard->owner != xfc->drawable)
1829  {
1830  /* Request the owner for TARGETS, and wait for SelectionNotify event */
1831  LogTagAndXConvertSelection(TAG, xfc->display, clipboard->clipboard_atom,
1832  clipboard->targets[1], clipboard->property_atom, xfc->drawable,
1833  CurrentTime);
1834  }
1835 
1836  xf_cliprdr_free_formats(formats, numFormats);
1837 
1838  return ret;
1839 }
1840 
1846 static UINT xf_cliprdr_send_client_format_list_response(xfClipboard* clipboard, BOOL status)
1847 {
1848  CLIPRDR_FORMAT_LIST_RESPONSE formatListResponse = { 0 };
1849 
1850  formatListResponse.common.msgType = CB_FORMAT_LIST_RESPONSE;
1851  formatListResponse.common.msgFlags = status ? CB_RESPONSE_OK : CB_RESPONSE_FAIL;
1852  formatListResponse.common.dataLen = 0;
1853 
1854  WINPR_ASSERT(clipboard);
1855  WINPR_ASSERT(clipboard->context);
1856  WINPR_ASSERT(clipboard->context->ClientFormatListResponse);
1857  return clipboard->context->ClientFormatListResponse(clipboard->context, &formatListResponse);
1858 }
1859 
1865 static UINT xf_cliprdr_monitor_ready(CliprdrClientContext* context,
1866  const CLIPRDR_MONITOR_READY* monitorReady)
1867 {
1868  UINT ret = 0;
1869  xfClipboard* clipboard = NULL;
1870 
1871  WINPR_ASSERT(context);
1872  WINPR_ASSERT(monitorReady);
1873 
1874  clipboard = cliprdr_file_context_get_context(context->custom);
1875  WINPR_ASSERT(clipboard);
1876 
1877  WINPR_UNUSED(monitorReady);
1878 
1879  if ((ret = xf_cliprdr_send_client_capabilities(clipboard)) != CHANNEL_RC_OK)
1880  return ret;
1881 
1882  xf_clipboard_formats_free(clipboard);
1883 
1884  if ((ret = xf_cliprdr_send_client_format_list(clipboard, TRUE)) != CHANNEL_RC_OK)
1885  return ret;
1886 
1887  clipboard->sync = TRUE;
1888  return CHANNEL_RC_OK;
1889 }
1890 
1896 static UINT xf_cliprdr_server_capabilities(CliprdrClientContext* context,
1897  const CLIPRDR_CAPABILITIES* capabilities)
1898 {
1899  const CLIPRDR_GENERAL_CAPABILITY_SET* generalCaps = NULL;
1900  const BYTE* capsPtr = NULL;
1901  xfClipboard* clipboard = NULL;
1902 
1903  WINPR_ASSERT(context);
1904  WINPR_ASSERT(capabilities);
1905 
1906  clipboard = cliprdr_file_context_get_context(context->custom);
1907  WINPR_ASSERT(clipboard);
1908 
1909  capsPtr = (const BYTE*)capabilities->capabilitySets;
1910  WINPR_ASSERT(capsPtr);
1911 
1912  cliprdr_file_context_remote_set_flags(clipboard->file, 0);
1913 
1914  for (UINT32 i = 0; i < capabilities->cCapabilitiesSets; i++)
1915  {
1916  const CLIPRDR_CAPABILITY_SET* caps = (const CLIPRDR_CAPABILITY_SET*)capsPtr;
1917 
1918  if (caps->capabilitySetType == CB_CAPSTYPE_GENERAL)
1919  {
1920  generalCaps = (const CLIPRDR_GENERAL_CAPABILITY_SET*)caps;
1921 
1922  cliprdr_file_context_remote_set_flags(clipboard->file, generalCaps->generalFlags);
1923  }
1924 
1925  capsPtr += caps->capabilitySetLength;
1926  }
1927 
1928  return CHANNEL_RC_OK;
1929 }
1930 
1931 static void xf_cliprdr_prepare_to_set_selection_owner(xfContext* xfc, xfClipboard* clipboard)
1932 {
1933  WINPR_ASSERT(xfc);
1934  WINPR_ASSERT(clipboard);
1935  /*
1936  * When you're writing to the selection in response to a
1937  * normal X event like a mouse click or keyboard action, you
1938  * get the selection timestamp by copying the time field out
1939  * of that X event. Here, we're doing it on our own
1940  * initiative, so we have to _request_ the X server time.
1941  *
1942  * There isn't a GetServerTime request in the X protocol, so I
1943  * work around it by setting a property on our own window, and
1944  * waiting for a PropertyNotify event to come back telling me
1945  * it's been done - which will have a timestamp we can use.
1946  */
1947 
1948  /* We have to set the property to some value, but it doesn't
1949  * matter what. Set it to its own name, which we have here
1950  * anyway! */
1951  Atom value = clipboard->timestamp_property_atom;
1952 
1953  LogTagAndXChangeProperty(TAG, xfc->display, xfc->drawable, clipboard->timestamp_property_atom,
1954  XA_ATOM, 32, PropModeReplace, (BYTE*)&value, 1);
1955  XFlush(xfc->display);
1956 }
1957 
1958 static void xf_cliprdr_set_selection_owner(xfContext* xfc, xfClipboard* clipboard, Time timestamp)
1959 {
1960  WINPR_ASSERT(xfc);
1961  WINPR_ASSERT(clipboard);
1962  /*
1963  * Actually set ourselves up as the selection owner, now that
1964  * we have a timestamp to use.
1965  */
1966 
1967  clipboard->selection_ownership_timestamp = timestamp;
1968  XSetSelectionOwner(xfc->display, clipboard->clipboard_atom, xfc->drawable, timestamp);
1969  XFlush(xfc->display);
1970 }
1971 
1977 static UINT xf_cliprdr_server_format_list(CliprdrClientContext* context,
1978  const CLIPRDR_FORMAT_LIST* formatList)
1979 {
1980  xfContext* xfc = NULL;
1981  UINT ret = 0;
1982  xfClipboard* clipboard = NULL;
1983 
1984  WINPR_ASSERT(context);
1985  WINPR_ASSERT(formatList);
1986 
1987  clipboard = cliprdr_file_context_get_context(context->custom);
1988  WINPR_ASSERT(clipboard);
1989 
1990  xfc = clipboard->xfc;
1991  WINPR_ASSERT(xfc);
1992 
1993  xf_lock_x11(xfc);
1994 
1995  /* Clear the active SelectionRequest, as it is now invalid */
1996  free(clipboard->respond);
1997  clipboard->respond = NULL;
1998 
1999  xf_clipboard_formats_free(clipboard);
2000  xf_cliprdr_clear_cached_data(clipboard);
2001  requested_format_free(&clipboard->requestedFormat);
2002 
2003  xf_clipboard_free_server_formats(clipboard);
2004 
2005  clipboard->numServerFormats = formatList->numFormats + 1; /* +1 for CF_RAW */
2006 
2007  if (!(clipboard->serverFormats =
2008  (CLIPRDR_FORMAT*)calloc(clipboard->numServerFormats, sizeof(CLIPRDR_FORMAT))))
2009  {
2010  WLog_ERR(TAG, "failed to allocate %d CLIPRDR_FORMAT structs", clipboard->numServerFormats);
2011  ret = CHANNEL_RC_NO_MEMORY;
2012  goto out;
2013  }
2014 
2015  for (size_t i = 0; i < formatList->numFormats; i++)
2016  {
2017  const CLIPRDR_FORMAT* format = &formatList->formats[i];
2018  CLIPRDR_FORMAT* srvFormat = &clipboard->serverFormats[i];
2019 
2020  srvFormat->formatId = format->formatId;
2021 
2022  if (format->formatName)
2023  {
2024  srvFormat->formatName = _strdup(format->formatName);
2025 
2026  if (!srvFormat->formatName)
2027  {
2028  for (UINT32 k = 0; k < i; k++)
2029  free(clipboard->serverFormats[k].formatName);
2030 
2031  clipboard->numServerFormats = 0;
2032  free(clipboard->serverFormats);
2033  clipboard->serverFormats = NULL;
2034  ret = CHANNEL_RC_NO_MEMORY;
2035  goto out;
2036  }
2037  }
2038  }
2039 
2040  ret = cliprdr_file_context_notify_new_server_format_list(clipboard->file);
2041  if (ret)
2042  goto out;
2043 
2044  /* CF_RAW is always implicitly supported by the server */
2045  {
2046  CLIPRDR_FORMAT* format = &clipboard->serverFormats[formatList->numFormats];
2047  format->formatId = CF_RAW;
2048  format->formatName = NULL;
2049  }
2050  xf_cliprdr_provide_server_format_list(clipboard);
2051  clipboard->numTargets = 2;
2052 
2053  for (size_t i = 0; i < formatList->numFormats; i++)
2054  {
2055  const CLIPRDR_FORMAT* format = &formatList->formats[i];
2056 
2057  for (size_t j = 0; j < clipboard->numClientFormats; j++)
2058  {
2059  const xfCliprdrFormat* clientFormat = &clipboard->clientFormats[j];
2060  if (xf_cliprdr_formats_equal(format, clientFormat))
2061  {
2062  if ((clientFormat->formatName != NULL) &&
2063  (strcmp(type_FileGroupDescriptorW, clientFormat->formatName) == 0))
2064  {
2065  if (!cliprdr_file_context_has_local_support(clipboard->file))
2066  continue;
2067  }
2068  xf_cliprdr_append_target(clipboard, clientFormat->atom);
2069  }
2070  }
2071  }
2072 
2073  ret = xf_cliprdr_send_client_format_list_response(clipboard, TRUE);
2074  if (xfc->remote_app)
2075  xf_cliprdr_set_selection_owner(xfc, clipboard, CurrentTime);
2076  else
2077  xf_cliprdr_prepare_to_set_selection_owner(xfc, clipboard);
2078 
2079 out:
2080  xf_unlock_x11(xfc);
2081 
2082  return ret;
2083 }
2084 
2090 static UINT
2091 xf_cliprdr_server_format_list_response(CliprdrClientContext* context,
2092  const CLIPRDR_FORMAT_LIST_RESPONSE* formatListResponse)
2093 {
2094  WINPR_ASSERT(context);
2095  WINPR_ASSERT(formatListResponse);
2096  // xfClipboard* clipboard = (xfClipboard*) context->custom;
2097  return CHANNEL_RC_OK;
2098 }
2099 
2105 static UINT
2106 xf_cliprdr_server_format_data_request(CliprdrClientContext* context,
2107  const CLIPRDR_FORMAT_DATA_REQUEST* formatDataRequest)
2108 {
2109  BOOL rawTransfer = 0;
2110  const xfCliprdrFormat* format = NULL;
2111  UINT32 formatId = 0;
2112  xfContext* xfc = NULL;
2113  xfClipboard* clipboard = NULL;
2114 
2115  WINPR_ASSERT(context);
2116  WINPR_ASSERT(formatDataRequest);
2117 
2118  clipboard = cliprdr_file_context_get_context(context->custom);
2119  WINPR_ASSERT(clipboard);
2120 
2121  xfc = clipboard->xfc;
2122  WINPR_ASSERT(xfc);
2123 
2124  formatId = formatDataRequest->requestedFormatId;
2125 
2126  rawTransfer = xf_cliprdr_is_raw_transfer_available(clipboard);
2127 
2128  if (rawTransfer)
2129  {
2130  format = xf_cliprdr_get_client_format_by_id(clipboard, CF_RAW);
2131  LogTagAndXChangeProperty(TAG, xfc->display, xfc->drawable, clipboard->property_atom,
2132  XA_INTEGER, 32, PropModeReplace, (BYTE*)&formatId, 1);
2133  }
2134  else
2135  format = xf_cliprdr_get_client_format_by_id(clipboard, formatId);
2136 
2137  clipboard->requestedFormatId = rawTransfer ? CF_RAW : WINPR_ASSERTING_INT_CAST(int, formatId);
2138  if (!format)
2139  return xf_cliprdr_send_data_response(clipboard, format, NULL, 0);
2140 
2141  DEBUG_CLIPRDR("requested format 0x%08" PRIx32 " [%s] {local 0x%08" PRIx32 "} [%s]",
2142  format->formatToRequest, ClipboardGetFormatIdString(format->formatToRequest),
2143  format->localFormat, format->formatName);
2144  LogTagAndXConvertSelection(TAG, xfc->display, clipboard->clipboard_atom, format->atom,
2145  clipboard->property_atom, xfc->drawable, CurrentTime);
2146  XFlush(xfc->display);
2147  /* After this point, we expect a SelectionNotify event from the clipboard owner. */
2148  return CHANNEL_RC_OK;
2149 }
2150 
2156 static UINT
2157 xf_cliprdr_server_format_data_response(CliprdrClientContext* context,
2158  const CLIPRDR_FORMAT_DATA_RESPONSE* formatDataResponse)
2159 {
2160  BOOL bSuccess = 0;
2161  BYTE* pDstData = NULL;
2162  UINT32 DstSize = 0;
2163  UINT32 SrcSize = 0;
2164  UINT32 srcFormatId = 0;
2165  UINT32 dstFormatId = 0;
2166  BOOL nullTerminated = FALSE;
2167  UINT32 size = 0;
2168  const BYTE* data = NULL;
2169  xfContext* xfc = NULL;
2170  xfClipboard* clipboard = NULL;
2171  xfCachedData* cached_data = NULL;
2172 
2173  WINPR_ASSERT(context);
2174  WINPR_ASSERT(formatDataResponse);
2175 
2176  clipboard = cliprdr_file_context_get_context(context->custom);
2177  WINPR_ASSERT(clipboard);
2178 
2179  xfc = clipboard->xfc;
2180  WINPR_ASSERT(xfc);
2181 
2182  size = formatDataResponse->common.dataLen;
2183  data = formatDataResponse->requestedFormatData;
2184 
2185  if (formatDataResponse->common.msgFlags == CB_RESPONSE_FAIL)
2186  {
2187  WLog_WARN(TAG, "Format Data Response PDU msgFlags is CB_RESPONSE_FAIL");
2188  free(clipboard->respond);
2189  clipboard->respond = NULL;
2190  return CHANNEL_RC_OK;
2191  }
2192 
2193  if (!clipboard->respond)
2194  return CHANNEL_RC_OK;
2195 
2196  pDstData = NULL;
2197  DstSize = 0;
2198  srcFormatId = 0;
2199  dstFormatId = 0;
2200 
2201  const RequestedFormat* format = clipboard->requestedFormat;
2202  if (clipboard->data_raw_format)
2203  {
2204  srcFormatId = CF_RAW;
2205  dstFormatId = CF_RAW;
2206  }
2207  else if (!format)
2208  return ERROR_INTERNAL_ERROR;
2209  else if (format->formatName)
2210  {
2211  ClipboardLock(clipboard->system);
2212  if (strcmp(format->formatName, type_HtmlFormat) == 0)
2213  {
2214  srcFormatId = ClipboardGetFormatId(clipboard->system, type_HtmlFormat);
2215  dstFormatId = ClipboardGetFormatId(clipboard->system, mime_html);
2216  nullTerminated = TRUE;
2217  }
2218 
2219  if (strcmp(format->formatName, type_FileGroupDescriptorW) == 0)
2220  {
2221  if (!cliprdr_file_context_update_server_data(clipboard->file, clipboard->system, data,
2222  size))
2223  WLog_WARN(TAG, "failed to update file descriptors");
2224 
2225  srcFormatId = ClipboardGetFormatId(clipboard->system, type_FileGroupDescriptorW);
2226  const xfCliprdrFormat* dstTargetFormat =
2227  xf_cliprdr_get_client_format_by_atom(clipboard, clipboard->respond->target);
2228  if (!dstTargetFormat)
2229  {
2230  dstFormatId = ClipboardGetFormatId(clipboard->system, mime_uri_list);
2231  }
2232  else
2233  {
2234  dstFormatId = dstTargetFormat->localFormat;
2235  }
2236 
2237  nullTerminated = TRUE;
2238  }
2239  ClipboardUnlock(clipboard->system);
2240  }
2241  else
2242  {
2243  srcFormatId = format->formatToRequest;
2244  dstFormatId = format->localFormat;
2245  switch (format->formatToRequest)
2246  {
2247  case CF_TEXT:
2248  nullTerminated = TRUE;
2249  break;
2250 
2251  case CF_OEMTEXT:
2252  nullTerminated = TRUE;
2253  break;
2254 
2255  case CF_UNICODETEXT:
2256  nullTerminated = TRUE;
2257  break;
2258 
2259  case CF_DIB:
2260  srcFormatId = CF_DIB;
2261  break;
2262 
2263  case CF_TIFF:
2264  srcFormatId = CF_TIFF;
2265  break;
2266 
2267  default:
2268  break;
2269  }
2270  }
2271 
2272  DEBUG_CLIPRDR("requested format 0x%08" PRIx32 " [%s] {local 0x%08" PRIx32 "} [%s]",
2273  format->formatToRequest, ClipboardGetFormatIdString(format->formatToRequest),
2274  format->localFormat, format->formatName);
2275  SrcSize = size;
2276 
2277  DEBUG_CLIPRDR("srcFormatId: %u, dstFormatId: %u", srcFormatId, dstFormatId);
2278 
2279  ClipboardLock(clipboard->system);
2280  bSuccess = ClipboardSetData(clipboard->system, srcFormatId, data, SrcSize);
2281 
2282  BOOL willQuit = FALSE;
2283  if (bSuccess)
2284  {
2285  if (SrcSize == 0)
2286  {
2287  WLog_DBG(TAG, "skipping, empty data detected!");
2288  free(clipboard->respond);
2289  clipboard->respond = NULL;
2290  willQuit = TRUE;
2291  }
2292  else
2293  {
2294  pDstData = (BYTE*)ClipboardGetData(clipboard->system, dstFormatId, &DstSize);
2295 
2296  if (!pDstData)
2297  {
2298  WLog_WARN(TAG, "failed to get clipboard data in format %s [source format %s]",
2299  ClipboardGetFormatName(clipboard->system, dstFormatId),
2300  ClipboardGetFormatName(clipboard->system, srcFormatId));
2301  }
2302 
2303  if (nullTerminated && pDstData)
2304  {
2305  BYTE* nullTerminator = memchr(pDstData, '\0', DstSize);
2306  if (nullTerminator)
2307  {
2308  const intptr_t diff = nullTerminator - pDstData;
2309  WINPR_ASSERT(diff >= 0);
2310  WINPR_ASSERT(diff <= UINT32_MAX);
2311  DstSize = (UINT32)diff;
2312  }
2313  }
2314  }
2315  }
2316  ClipboardUnlock(clipboard->system);
2317  if (willQuit)
2318  return CHANNEL_RC_OK;
2319 
2320  /* Cache converted and original data to avoid doing a possibly costly
2321  * conversion again on subsequent requests */
2322  if (pDstData)
2323  {
2324  cached_data = xf_cached_data_new(pDstData, DstSize);
2325  if (!cached_data)
2326  {
2327  WLog_WARN(TAG, "Failed to allocate cache entry");
2328  free(pDstData);
2329  return CHANNEL_RC_OK;
2330  }
2331  if (!HashTable_Insert(clipboard->cachedData, format_to_cache_slot(dstFormatId),
2332  cached_data))
2333  {
2334  WLog_WARN(TAG, "Failed to cache clipboard data");
2335  xf_cached_data_free(cached_data);
2336  return CHANNEL_RC_OK;
2337  }
2338  }
2339 
2340  /* We have to copy the original data again, as pSrcData is now owned
2341  * by clipboard->system. Memory allocation failure is not fatal here
2342  * as this is only a cached value. */
2343  {
2344  // clipboard->cachedData owns cached_data
2345  // NOLINTNEXTLINE(clang-analyzer-unix.Malloc
2346  xfCachedData* cached_raw_data = xf_cached_data_new_copy(data, size);
2347  if (!cached_raw_data)
2348  WLog_WARN(TAG, "Failed to allocate cache entry");
2349  else
2350  {
2351  if (!HashTable_Insert(clipboard->cachedRawData, (void*)(UINT_PTR)srcFormatId,
2352  cached_raw_data))
2353  {
2354  WLog_WARN(TAG, "Failed to cache clipboard data");
2355  xf_cached_data_free(cached_raw_data);
2356  }
2357  }
2358  }
2359 
2360  // clipboard->cachedRawData owns cached_raw_data
2361  // NOLINTNEXTLINE(clang-analyzer-unix.Malloc)
2362  xf_cliprdr_provide_data(clipboard, clipboard->respond, pDstData, DstSize);
2363  {
2364  union
2365  {
2366  XEvent* ev;
2367  XSelectionEvent* sev;
2368  } conv;
2369 
2370  conv.sev = clipboard->respond;
2371 
2372  XSendEvent(xfc->display, clipboard->respond->requestor, 0, 0, conv.ev);
2373  XFlush(xfc->display);
2374  }
2375  free(clipboard->respond);
2376  clipboard->respond = NULL;
2377  return CHANNEL_RC_OK;
2378 }
2379 
2380 static BOOL xf_cliprdr_is_valid_unix_filename(LPCWSTR filename)
2381 {
2382  if (!filename)
2383  return FALSE;
2384 
2385  if (filename[0] == L'\0')
2386  return FALSE;
2387 
2388  /* Reserved characters */
2389  for (const WCHAR* c = filename; *c; ++c)
2390  {
2391  if (*c == L'/')
2392  return FALSE;
2393  }
2394 
2395  return TRUE;
2396 }
2397 
2398 xfClipboard* xf_clipboard_new(xfContext* xfc, BOOL relieveFilenameRestriction)
2399 {
2400  int n = 0;
2401  rdpChannels* channels = NULL;
2402  xfClipboard* clipboard = NULL;
2403  const char* selectionAtom = NULL;
2404  xfCliprdrFormat* clientFormat = NULL;
2405  wObject* obj = NULL;
2406 
2407  WINPR_ASSERT(xfc);
2408  WINPR_ASSERT(xfc->common.context.settings);
2409 
2410  if (!(clipboard = (xfClipboard*)calloc(1, sizeof(xfClipboard))))
2411  {
2412  WLog_ERR(TAG, "failed to allocate xfClipboard data");
2413  return NULL;
2414  }
2415 
2416  clipboard->file = cliprdr_file_context_new(clipboard);
2417  if (!clipboard->file)
2418  goto fail;
2419 
2420  xfc->clipboard = clipboard;
2421  clipboard->xfc = xfc;
2422  channels = xfc->common.context.channels;
2423  clipboard->channels = channels;
2424  clipboard->system = ClipboardCreate();
2425  clipboard->requestedFormatId = -1;
2426  clipboard->root_window = DefaultRootWindow(xfc->display);
2427 
2428  selectionAtom =
2429  freerdp_settings_get_string(xfc->common.context.settings, FreeRDP_ClipboardUseSelection);
2430  if (!selectionAtom)
2431  selectionAtom = "CLIPBOARD";
2432 
2433  clipboard->clipboard_atom = Logging_XInternAtom(xfc->log, xfc->display, selectionAtom, FALSE);
2434 
2435  if (clipboard->clipboard_atom == None)
2436  {
2437  WLog_ERR(TAG, "unable to get %s atom", selectionAtom);
2438  goto fail;
2439  }
2440 
2441  clipboard->timestamp_property_atom =
2442  Logging_XInternAtom(xfc->log, xfc->display, "_FREERDP_TIMESTAMP_PROPERTY", FALSE);
2443  clipboard->property_atom =
2444  Logging_XInternAtom(xfc->log, xfc->display, "_FREERDP_CLIPRDR", FALSE);
2445  clipboard->raw_transfer_atom =
2446  Logging_XInternAtom(xfc->log, xfc->display, "_FREERDP_CLIPRDR_RAW", FALSE);
2447  clipboard->raw_format_list_atom =
2448  Logging_XInternAtom(xfc->log, xfc->display, "_FREERDP_CLIPRDR_FORMATS", FALSE);
2449  xf_cliprdr_set_raw_transfer_enabled(clipboard, TRUE);
2450  XSelectInput(xfc->display, clipboard->root_window, PropertyChangeMask);
2451 #ifdef WITH_XFIXES
2452 
2453  if (XFixesQueryExtension(xfc->display, &clipboard->xfixes_event_base,
2454  &clipboard->xfixes_error_base))
2455  {
2456  int xfmajor = 0;
2457  int xfminor = 0;
2458 
2459  if (XFixesQueryVersion(xfc->display, &xfmajor, &xfminor))
2460  {
2461  XFixesSelectSelectionInput(xfc->display, clipboard->root_window,
2462  clipboard->clipboard_atom,
2463  XFixesSetSelectionOwnerNotifyMask);
2464  clipboard->xfixes_supported = TRUE;
2465  }
2466  else
2467  {
2468  WLog_ERR(TAG, "Error querying X Fixes extension version");
2469  }
2470  }
2471  else
2472  {
2473  WLog_ERR(TAG, "Error loading X Fixes extension");
2474  }
2475 
2476 #else
2477  WLog_ERR(
2478  TAG,
2479  "Warning: Using clipboard redirection without XFIXES extension is strongly discouraged!");
2480 #endif
2481  clientFormat = &clipboard->clientFormats[n++];
2482  clientFormat->atom = Logging_XInternAtom(xfc->log, xfc->display, "_FREERDP_RAW", False);
2483  clientFormat->localFormat = clientFormat->formatToRequest = CF_RAW;
2484 
2485  clientFormat = &clipboard->clientFormats[n++];
2486  clientFormat->atom = Logging_XInternAtom(xfc->log, xfc->display, "UTF8_STRING", False);
2487  clientFormat->formatToRequest = CF_UNICODETEXT;
2488  clientFormat->localFormat = ClipboardGetFormatId(xfc->clipboard->system, mime_text_plain);
2489 
2490  clientFormat = &clipboard->clientFormats[n++];
2491  clientFormat->atom = XA_STRING;
2492  clientFormat->formatToRequest = CF_TEXT;
2493  clientFormat->localFormat = ClipboardGetFormatId(xfc->clipboard->system, mime_text_plain);
2494 
2495  clientFormat = &clipboard->clientFormats[n++];
2496  clientFormat->atom = Logging_XInternAtom(xfc->log, xfc->display, mime_tiff, False);
2497  clientFormat->formatToRequest = clientFormat->localFormat = CF_TIFF;
2498 
2499  for (size_t x = 0; x < ARRAYSIZE(mime_bitmap); x++)
2500  {
2501  const char* mime_bmp = mime_bitmap[x];
2502  const DWORD format = ClipboardGetFormatId(xfc->clipboard->system, mime_bmp);
2503  if (format == 0)
2504  {
2505  WLog_DBG(TAG, "skipping local bitmap format %s [NOT SUPPORTED]", mime_bmp);
2506  continue;
2507  }
2508 
2509  WLog_DBG(TAG, "register local bitmap format %s [0x%08" PRIx32 "]", mime_bmp, format);
2510  clientFormat = &clipboard->clientFormats[n++];
2511  clientFormat->localFormat = format;
2512  clientFormat->atom = Logging_XInternAtom(xfc->log, xfc->display, mime_bmp, False);
2513  clientFormat->formatToRequest = CF_DIB;
2514  }
2515 
2516  for (size_t x = 0; x < ARRAYSIZE(mime_images); x++)
2517  {
2518  const char* mime_bmp = mime_images[x];
2519  const DWORD format = ClipboardGetFormatId(xfc->clipboard->system, mime_bmp);
2520  if (format == 0)
2521  {
2522  WLog_DBG(TAG, "skipping local bitmap format %s [NOT SUPPORTED]", mime_bmp);
2523  continue;
2524  }
2525 
2526  WLog_DBG(TAG, "register local bitmap format %s [0x%08" PRIx32 "]", mime_bmp, format);
2527  clientFormat = &clipboard->clientFormats[n++];
2528  clientFormat->localFormat = format;
2529  clientFormat->atom = Logging_XInternAtom(xfc->log, xfc->display, mime_bmp, False);
2530  clientFormat->formatToRequest = CF_DIB;
2531  }
2532 
2533  clientFormat = &clipboard->clientFormats[n++];
2534  clientFormat->atom = Logging_XInternAtom(xfc->log, xfc->display, mime_html, False);
2535  clientFormat->formatToRequest = ClipboardGetFormatId(xfc->clipboard->system, type_HtmlFormat);
2536  clientFormat->localFormat = ClipboardGetFormatId(xfc->clipboard->system, mime_html);
2537  clientFormat->formatName = _strdup(type_HtmlFormat);
2538 
2539  if (!clientFormat->formatName)
2540  goto fail;
2541 
2542  clientFormat = &clipboard->clientFormats[n++];
2543 
2544  /*
2545  * Existence of registered format IDs for file formats does not guarantee that they are
2546  * in fact supported by wClipboard (as further initialization may have failed after format
2547  * registration). However, they are definitely not supported if there are no registered
2548  * formats. In this case we should not list file formats in TARGETS.
2549  */
2550  const UINT32 fgid = ClipboardGetFormatId(clipboard->system, type_FileGroupDescriptorW);
2551  const UINT32 uid = ClipboardGetFormatId(clipboard->system, mime_uri_list);
2552  if (uid)
2553  {
2554  cliprdr_file_context_set_locally_available(clipboard->file, TRUE);
2555  clientFormat->atom = Logging_XInternAtom(xfc->log, xfc->display, mime_uri_list, False);
2556  clientFormat->localFormat = uid;
2557  clientFormat->formatToRequest = fgid;
2558  clientFormat->formatName = _strdup(type_FileGroupDescriptorW);
2559 
2560  if (!clientFormat->formatName)
2561  goto fail;
2562 
2563  clientFormat = &clipboard->clientFormats[n++];
2564  }
2565 
2566  const UINT32 gid = ClipboardGetFormatId(clipboard->system, mime_gnome_copied_files);
2567  if (gid != 0)
2568  {
2569  cliprdr_file_context_set_locally_available(clipboard->file, TRUE);
2570  clientFormat->atom =
2571  Logging_XInternAtom(xfc->log, xfc->display, mime_gnome_copied_files, False);
2572  clientFormat->localFormat = gid;
2573  clientFormat->formatToRequest = fgid;
2574  clientFormat->formatName = _strdup(type_FileGroupDescriptorW);
2575 
2576  if (!clientFormat->formatName)
2577  goto fail;
2578 
2579  clientFormat = &clipboard->clientFormats[n++];
2580  }
2581 
2582  const UINT32 mid = ClipboardGetFormatId(clipboard->system, mime_mate_copied_files);
2583  if (mid != 0)
2584  {
2585  cliprdr_file_context_set_locally_available(clipboard->file, TRUE);
2586  clientFormat->atom =
2587  Logging_XInternAtom(xfc->log, xfc->display, mime_mate_copied_files, False);
2588  clientFormat->localFormat = mid;
2589  clientFormat->formatToRequest = fgid;
2590  clientFormat->formatName = _strdup(type_FileGroupDescriptorW);
2591 
2592  if (!clientFormat->formatName)
2593  goto fail;
2594  }
2595 
2596  clipboard->numClientFormats = WINPR_ASSERTING_INT_CAST(uint32_t, n);
2597  clipboard->targets[0] = Logging_XInternAtom(xfc->log, xfc->display, "TIMESTAMP", FALSE);
2598  clipboard->targets[1] = Logging_XInternAtom(xfc->log, xfc->display, "TARGETS", FALSE);
2599  clipboard->numTargets = 2;
2600  clipboard->incr_atom = Logging_XInternAtom(xfc->log, xfc->display, "INCR", FALSE);
2601 
2602  if (relieveFilenameRestriction)
2603  {
2604  WLog_DBG(TAG, "Relieving CLIPRDR filename restriction");
2605  ClipboardGetDelegate(clipboard->system)->IsFileNameComponentValid =
2606  xf_cliprdr_is_valid_unix_filename;
2607  }
2608 
2609  clipboard->cachedData = HashTable_New(TRUE);
2610  if (!clipboard->cachedData)
2611  goto fail;
2612 
2613  obj = HashTable_ValueObject(clipboard->cachedData);
2614  obj->fnObjectFree = xf_cached_data_free;
2615 
2616  clipboard->cachedRawData = HashTable_New(TRUE);
2617  if (!clipboard->cachedRawData)
2618  goto fail;
2619 
2620  obj = HashTable_ValueObject(clipboard->cachedRawData);
2621  obj->fnObjectFree = xf_cached_data_free;
2622 
2623  return clipboard;
2624 
2625 fail:
2626  WINPR_PRAGMA_DIAG_PUSH
2627  WINPR_PRAGMA_DIAG_IGNORED_MISMATCHED_DEALLOC
2628  xf_clipboard_free(clipboard);
2629  WINPR_PRAGMA_DIAG_POP
2630  return NULL;
2631 }
2632 
2633 void xf_clipboard_free(xfClipboard* clipboard)
2634 {
2635  if (!clipboard)
2636  return;
2637 
2638  xf_clipboard_free_server_formats(clipboard);
2639 
2640  if (clipboard->numClientFormats)
2641  {
2642  for (UINT32 i = 0; i < clipboard->numClientFormats; i++)
2643  {
2644  xfCliprdrFormat* format = &clipboard->clientFormats[i];
2645  free(format->formatName);
2646  }
2647  }
2648 
2649  cliprdr_file_context_free(clipboard->file);
2650 
2651  ClipboardDestroy(clipboard->system);
2652  xf_clipboard_formats_free(clipboard);
2653  HashTable_Free(clipboard->cachedRawData);
2654  HashTable_Free(clipboard->cachedData);
2655  requested_format_free(&clipboard->requestedFormat);
2656  free(clipboard->respond);
2657  free(clipboard->incr_data);
2658  free(clipboard);
2659 }
2660 
2661 void xf_cliprdr_init(xfContext* xfc, CliprdrClientContext* cliprdr)
2662 {
2663  WINPR_ASSERT(xfc);
2664  WINPR_ASSERT(cliprdr);
2665 
2666  xfc->cliprdr = cliprdr;
2667  xfc->clipboard->context = cliprdr;
2668 
2669  cliprdr->MonitorReady = xf_cliprdr_monitor_ready;
2670  cliprdr->ServerCapabilities = xf_cliprdr_server_capabilities;
2671  cliprdr->ServerFormatList = xf_cliprdr_server_format_list;
2672  cliprdr->ServerFormatListResponse = xf_cliprdr_server_format_list_response;
2673  cliprdr->ServerFormatDataRequest = xf_cliprdr_server_format_data_request;
2674  cliprdr->ServerFormatDataResponse = xf_cliprdr_server_format_data_response;
2675 
2676  cliprdr_file_context_init(xfc->clipboard->file, cliprdr);
2677 }
2678 
2679 void xf_cliprdr_uninit(xfContext* xfc, CliprdrClientContext* cliprdr)
2680 {
2681  WINPR_ASSERT(xfc);
2682  WINPR_ASSERT(cliprdr);
2683 
2684  xfc->cliprdr = NULL;
2685 
2686  if (xfc->clipboard)
2687  {
2688  cliprdr_file_context_uninit(xfc->clipboard->file, cliprdr);
2689  xfc->clipboard->context = NULL;
2690  }
2691 }
FREERDP_API const char * freerdp_settings_get_string(const rdpSettings *settings, FreeRDP_Settings_Keys_String id)
Returns a immutable string settings value.
This struct contains function pointer to initialize/free objects.
Definition: collections.h:57