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