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