21 #include <freerdp/config.h>
25 #include <winpr/crt.h>
26 #include <winpr/image.h>
27 #include <winpr/stream.h>
28 #include <winpr/clipboard.h>
30 #include <freerdp/log.h>
31 #include <freerdp/client/cliprdr.h>
32 #include <freerdp/channels/channels.h>
33 #include <freerdp/channels/cliprdr.h>
35 #include <freerdp/client/client_cliprdr_file.h>
37 #include "wlf_cliprdr.h"
39 #define TAG CLIENT_TAG("wayland.cliprdr")
41 #define mime_text_plain "text/plain"
42 #define mime_text_utf8 mime_text_plain ";charset=utf-8"
45 static const char* mime_text[] = { mime_text_plain, mime_text_utf8,
"UTF8_STRING",
46 "COMPOUND_TEXT",
"TEXT",
"STRING" };
48 static const char mime_png[] =
"image/png";
49 static const char mime_webp[] =
"image/webp";
50 static const char mime_jpg[] =
"image/jpeg";
51 static const char mime_tiff[] =
"image/tiff";
52 static const char mime_uri_list[] =
"text/uri-list";
53 static const char mime_html[] =
"text/html";
55 #define BMP_MIME_LIST "image/bmp", "image/x-bmp", "image/x-MS-bmp", "image/x-win-bitmap"
56 static const char* mime_bitmap[] = { BMP_MIME_LIST };
57 static const char* mime_image[] = { mime_png, mime_webp, mime_jpg, mime_tiff, BMP_MIME_LIST };
59 static const char mime_gnome_copied_files[] =
"x-special/gnome-copied-files";
60 static const char mime_mate_copied_files[] =
"x-special/mate-copied-files";
62 static const char type_FileGroupDescriptorW[] =
"FileGroupDescriptorW";
63 static const char type_HtmlFormat[] =
"HTML Format";
68 UINT32 responseFormat;
74 const FILE* responseFile;
75 UINT32 responseFormat;
76 const char* responseMime;
82 rdpChannels* channels;
83 CliprdrClientContext* context;
89 size_t numClientFormats;
92 size_t numServerFormats;
98 CliprdrFileContext* file;
100 wQueue* request_queue;
103 static void wlf_request_free(
void* rq)
105 wlf_request* request = rq;
108 free(request->responseMime);
109 if (request->responseFile)
110 (void)fclose(request->responseFile);
115 static wlf_request* wlf_request_new(
void)
117 return calloc(1,
sizeof(wlf_request));
120 static void* wlf_request_clone(
const void* oth)
122 const wlf_request* other = (
const wlf_request*)oth;
123 wlf_request* copy = wlf_request_new();
127 if (other->responseMime)
129 copy->responseMime = _strdup(other->responseMime);
130 if (!copy->responseMime)
135 wlf_request_free(copy);
139 static BOOL wlf_mime_is_file(
const char* mime)
141 if (strncmp(mime_uri_list, mime,
sizeof(mime_uri_list)) == 0)
143 if (strncmp(mime_gnome_copied_files, mime,
sizeof(mime_gnome_copied_files)) == 0)
145 if (strncmp(mime_mate_copied_files, mime,
sizeof(mime_mate_copied_files)) == 0)
150 static BOOL wlf_mime_is_text(
const char* mime)
152 for (
size_t x = 0; x < ARRAYSIZE(mime_text); x++)
154 if (strcmp(mime, mime_text[x]) == 0)
161 static BOOL wlf_mime_is_image(
const char* mime)
163 for (
size_t x = 0; x < ARRAYSIZE(mime_image); x++)
165 if (strcmp(mime, mime_image[x]) == 0)
172 static BOOL wlf_mime_is_html(
const char* mime)
174 if (strcmp(mime, mime_html) == 0)
180 static void wlf_cliprdr_free_server_formats(wfClipboard* clipboard)
182 if (clipboard && clipboard->serverFormats)
184 for (
size_t j = 0; j < clipboard->numServerFormats; j++)
187 free(format->formatName);
190 free(clipboard->serverFormats);
191 clipboard->serverFormats = NULL;
192 clipboard->numServerFormats = 0;
196 UwacClipboardOfferDestroy(clipboard->seat);
199 static void wlf_cliprdr_free_client_formats(wfClipboard* clipboard)
201 if (clipboard && clipboard->numClientFormats)
203 for (
size_t j = 0; j < clipboard->numClientFormats; j++)
206 free(format->formatName);
209 free(clipboard->clientFormats);
210 clipboard->clientFormats = NULL;
211 clipboard->numClientFormats = 0;
215 UwacClipboardOfferDestroy(clipboard->seat);
223 static UINT wlf_cliprdr_send_client_format_list(wfClipboard* clipboard)
225 WINPR_ASSERT(clipboard);
228 .numFormats = (UINT32)clipboard->numClientFormats,
229 .formats = clipboard->clientFormats,
230 .common.msgType = CB_FORMAT_LIST };
232 cliprdr_file_context_clear(clipboard->file);
234 WLog_VRB(TAG,
"-------------- client format list [%" PRIu32
"] ------------------",
235 formatList.numFormats);
236 for (UINT32 x = 0; x < formatList.numFormats; x++)
239 WLog_VRB(TAG,
"client announces %" PRIu32
" [%s][%s]", format->formatId,
240 ClipboardGetFormatIdString(format->formatId), format->formatName);
242 WINPR_ASSERT(clipboard->context);
243 WINPR_ASSERT(clipboard->context->ClientFormatList);
244 return clipboard->context->ClientFormatList(clipboard->context, &formatList);
247 static void wfl_cliprdr_add_client_format_id(wfClipboard* clipboard, UINT32 formatId)
250 const char* name = ClipboardGetFormatName(clipboard->system, formatId);
252 for (
size_t x = 0; x < clipboard->numClientFormats; x++)
254 format = &clipboard->clientFormats[x];
256 if (format->formatId == formatId)
260 format = realloc(clipboard->clientFormats,
266 clipboard->clientFormats = format;
267 format = &clipboard->clientFormats[clipboard->numClientFormats++];
268 format->formatId = formatId;
269 format->formatName = NULL;
271 if (name && (formatId >= CF_MAX))
272 format->formatName = _strdup(name);
275 static BOOL wlf_cliprdr_add_client_format(wfClipboard* clipboard,
const char* mime)
278 ClipboardLock(clipboard->system);
279 if (wlf_mime_is_html(mime))
281 UINT32 formatId = ClipboardGetFormatId(clipboard->system, type_HtmlFormat);
282 wfl_cliprdr_add_client_format_id(clipboard, formatId);
284 else if (wlf_mime_is_text(mime))
286 wfl_cliprdr_add_client_format_id(clipboard, CF_TEXT);
287 wfl_cliprdr_add_client_format_id(clipboard, CF_OEMTEXT);
288 wfl_cliprdr_add_client_format_id(clipboard, CF_UNICODETEXT);
290 else if (wlf_mime_is_image(mime))
292 for (
size_t x = 0; x < ARRAYSIZE(mime_image); x++)
294 const char* mime_bmp = mime_image[x];
295 UINT32 formatId = ClipboardGetFormatId(clipboard->system, mime_bmp);
297 wfl_cliprdr_add_client_format_id(clipboard, formatId);
299 wfl_cliprdr_add_client_format_id(clipboard, CF_DIB);
300 wfl_cliprdr_add_client_format_id(clipboard, CF_TIFF);
302 else if (wlf_mime_is_file(mime))
304 const UINT32 fileFormatId =
305 ClipboardGetFormatId(clipboard->system, type_FileGroupDescriptorW);
306 wfl_cliprdr_add_client_format_id(clipboard, fileFormatId);
309 ClipboardUnlock(clipboard->system);
310 if (wlf_cliprdr_send_client_format_list(clipboard) != CHANNEL_RC_OK)
320 static UINT wlf_cliprdr_send_data_request(wfClipboard* clipboard,
const wlf_const_request* rq)
326 if (!Queue_Enqueue(clipboard->request_queue, rq))
327 return ERROR_INTERNAL_ERROR;
329 WINPR_ASSERT(clipboard);
330 WINPR_ASSERT(clipboard->context);
331 WINPR_ASSERT(clipboard->context->ClientFormatDataRequest);
332 return clipboard->context->ClientFormatDataRequest(clipboard->context, &request);
340 static UINT wlf_cliprdr_send_data_response(wfClipboard* clipboard,
const BYTE* data,
size_t size)
344 if (size > UINT32_MAX)
345 return ERROR_INVALID_PARAMETER;
347 response.common.msgFlags = (data) ? CB_RESPONSE_OK : CB_RESPONSE_FAIL;
348 response.common.dataLen = (UINT32)size;
349 response.requestedFormatData = data;
351 WINPR_ASSERT(clipboard);
352 WINPR_ASSERT(clipboard->context);
353 WINPR_ASSERT(clipboard->context->ClientFormatDataResponse);
354 return clipboard->context->ClientFormatDataResponse(clipboard->context, &response);
357 BOOL wlf_cliprdr_handle_event(wfClipboard* clipboard,
const UwacClipboardEvent* event)
359 if (!clipboard || !event)
362 if (!clipboard->context)
367 case UWAC_EVENT_CLIPBOARD_AVAILABLE:
368 clipboard->seat =
event->seat;
371 case UWAC_EVENT_CLIPBOARD_OFFER:
372 WLog_Print(clipboard->log, WLOG_DEBUG,
"client announces mime %s", event->mime);
373 return wlf_cliprdr_add_client_format(clipboard, event->mime);
375 case UWAC_EVENT_CLIPBOARD_SELECT:
376 WLog_Print(clipboard->log, WLOG_DEBUG,
"client announces new data");
377 wlf_cliprdr_free_client_formats(clipboard);
390 static UINT wlf_cliprdr_send_client_capabilities(wfClipboard* clipboard)
392 WINPR_ASSERT(clipboard);
395 .capabilitySetType = CB_CAPSTYPE_GENERAL,
396 .capabilitySetLength = 12,
397 .version = CB_CAPS_VERSION_2,
399 CB_USE_LONG_FORMAT_NAMES | cliprdr_file_context_current_flags(clipboard->file)
405 WINPR_ASSERT(clipboard);
406 WINPR_ASSERT(clipboard->context);
407 WINPR_ASSERT(clipboard->context->ClientCapabilities);
408 return clipboard->context->ClientCapabilities(clipboard->context, &capabilities);
416 static UINT wlf_cliprdr_send_client_format_list_response(wfClipboard* clipboard, BOOL status)
419 .common.msgType = CB_FORMAT_LIST_RESPONSE,
420 .common.msgFlags = status ? CB_RESPONSE_OK : CB_RESPONSE_FAIL,
423 WINPR_ASSERT(clipboard);
424 WINPR_ASSERT(clipboard->context);
425 WINPR_ASSERT(clipboard->context->ClientFormatListResponse);
426 return clipboard->context->ClientFormatListResponse(clipboard->context, &formatListResponse);
434 static UINT wlf_cliprdr_monitor_ready(CliprdrClientContext* context,
439 WINPR_UNUSED(monitorReady);
440 WINPR_ASSERT(context);
441 WINPR_ASSERT(monitorReady);
443 wfClipboard* clipboard = cliprdr_file_context_get_context(context->custom);
444 WINPR_ASSERT(clipboard);
446 if ((ret = wlf_cliprdr_send_client_capabilities(clipboard)) != CHANNEL_RC_OK)
449 if ((ret = wlf_cliprdr_send_client_format_list(clipboard)) != CHANNEL_RC_OK)
452 clipboard->sync = TRUE;
453 return CHANNEL_RC_OK;
461 static UINT wlf_cliprdr_server_capabilities(CliprdrClientContext* context,
464 WINPR_ASSERT(context);
465 WINPR_ASSERT(capabilities);
467 const BYTE* capsPtr = (
const BYTE*)capabilities->capabilitySets;
468 WINPR_ASSERT(capsPtr);
470 wfClipboard* clipboard = cliprdr_file_context_get_context(context->custom);
471 WINPR_ASSERT(clipboard);
473 if (!cliprdr_file_context_remote_set_flags(clipboard->file, 0))
474 return ERROR_INTERNAL_ERROR;
476 for (UINT32 i = 0; i < capabilities->cCapabilitiesSets; i++)
480 if (caps->capabilitySetType == CB_CAPSTYPE_GENERAL)
485 if (!cliprdr_file_context_remote_set_flags(clipboard->file, generalCaps->generalFlags))
486 return ERROR_INTERNAL_ERROR;
489 capsPtr += caps->capabilitySetLength;
492 return CHANNEL_RC_OK;
495 static UINT32 wlf_get_server_format_id(
const wfClipboard* clipboard,
const char* name)
497 WINPR_ASSERT(clipboard);
500 for (UINT32 x = 0; x < clipboard->numServerFormats; x++)
503 if (!format->formatName)
505 if (strcmp(name, format->formatName) == 0)
506 return format->formatId;
511 static const char* wlf_get_server_format_name(
const wfClipboard* clipboard, UINT32 formatId)
513 WINPR_ASSERT(clipboard);
515 for (UINT32 x = 0; x < clipboard->numServerFormats; x++)
518 if (format->formatId == formatId)
519 return format->formatName;
524 static void wlf_cliprdr_transfer_data(UwacSeat* seat,
void* context,
const char* mime,
int fd)
526 wfClipboard* clipboard = (wfClipboard*)context;
529 EnterCriticalSection(&clipboard->lock);
531 wlf_const_request request = { 0 };
532 if (wlf_mime_is_html(mime))
534 request.responseMime = mime_html;
535 request.responseFormat = wlf_get_server_format_id(clipboard, type_HtmlFormat);
537 else if (wlf_mime_is_file(mime))
539 request.responseMime = mime;
540 request.responseFormat = wlf_get_server_format_id(clipboard, type_FileGroupDescriptorW);
542 else if (wlf_mime_is_text(mime))
544 request.responseMime = mime_text_plain;
545 request.responseFormat = CF_UNICODETEXT;
547 else if (wlf_mime_is_image(mime))
549 request.responseMime = mime;
550 if (strcmp(mime, mime_tiff) == 0)
551 request.responseFormat = CF_TIFF;
553 request.responseFormat = CF_DIB;
556 if (request.responseMime != NULL)
558 request.responseFile = fdopen(fd,
"w");
560 if (request.responseFile)
561 wlf_cliprdr_send_data_request(clipboard, &request);
563 WLog_Print(clipboard->log, WLOG_ERROR,
564 "failed to open clipboard file descriptor for MIME %s",
565 request.responseMime);
568 LeaveCriticalSection(&clipboard->lock);
571 static void wlf_cliprdr_cancel_data(UwacSeat* seat,
void* context)
573 wfClipboard* clipboard = (wfClipboard*)context;
576 WINPR_ASSERT(clipboard);
577 cliprdr_file_context_clear(clipboard->file);
588 static UINT wlf_cliprdr_server_format_list(CliprdrClientContext* context,
596 if (!context || !context->custom)
597 return ERROR_INVALID_PARAMETER;
599 wfClipboard* clipboard = cliprdr_file_context_get_context(context->custom);
600 WINPR_ASSERT(clipboard);
602 wlf_cliprdr_free_server_formats(clipboard);
603 cliprdr_file_context_clear(clipboard->file);
605 if (!(clipboard->serverFormats =
608 WLog_Print(clipboard->log, WLOG_ERROR,
609 "failed to allocate %" PRIuz
" CLIPRDR_FORMAT structs",
610 clipboard->numServerFormats);
611 return CHANNEL_RC_NO_MEMORY;
614 clipboard->numServerFormats = formatList->numFormats;
616 if (!clipboard->seat)
618 WLog_Print(clipboard->log, WLOG_ERROR,
619 "clipboard->seat=NULL, check your client implementation");
620 return ERROR_INTERNAL_ERROR;
623 for (UINT32 i = 0; i < formatList->numFormats; i++)
627 srvFormat->formatId = format->formatId;
629 if (format->formatName)
631 srvFormat->formatName = _strdup(format->formatName);
633 if (!srvFormat->formatName)
635 wlf_cliprdr_free_server_formats(clipboard);
636 return CHANNEL_RC_NO_MEMORY;
640 if (format->formatName)
642 if (strcmp(format->formatName, type_HtmlFormat) == 0)
647 else if (strcmp(format->formatName, type_FileGroupDescriptorW) == 0)
655 switch (format->formatId)
675 UwacClipboardOfferCreate(clipboard->seat, mime_html);
678 if (file && cliprdr_file_context_has_local_support(clipboard->file))
680 UwacClipboardOfferCreate(clipboard->seat, mime_uri_list);
681 UwacClipboardOfferCreate(clipboard->seat, mime_gnome_copied_files);
682 UwacClipboardOfferCreate(clipboard->seat, mime_mate_copied_files);
687 for (
size_t x = 0; x < ARRAYSIZE(mime_text); x++)
688 UwacClipboardOfferCreate(clipboard->seat, mime_text[x]);
693 for (
size_t x = 0; x < ARRAYSIZE(mime_image); x++)
694 UwacClipboardOfferCreate(clipboard->seat, mime_image[x]);
697 UwacClipboardOfferAnnounce(clipboard->seat, clipboard, wlf_cliprdr_transfer_data,
698 wlf_cliprdr_cancel_data);
699 return wlf_cliprdr_send_client_format_list_response(clipboard, TRUE);
708 wlf_cliprdr_server_format_list_response(CliprdrClientContext* context,
711 WINPR_ASSERT(context);
712 WINPR_ASSERT(formatListResponse);
714 if (formatListResponse->common.msgFlags & CB_RESPONSE_FAIL)
715 WLog_WARN(TAG,
"format list update failed");
716 return CHANNEL_RC_OK;
725 wlf_cliprdr_server_format_data_request(CliprdrClientContext* context,
728 UINT rc = CHANNEL_RC_OK;
731 const char* mime = NULL;
733 UINT32 localFormatId = 0;
734 wfClipboard* clipboard = 0;
739 WINPR_ASSERT(context);
740 WINPR_ASSERT(formatDataRequest);
742 localFormatId = formatId = formatDataRequest->requestedFormatId;
743 clipboard = cliprdr_file_context_get_context(context->custom);
744 WINPR_ASSERT(clipboard);
746 ClipboardLock(clipboard->system);
747 const UINT32 fileFormatId = ClipboardGetFormatId(clipboard->system, type_FileGroupDescriptorW);
748 const UINT32 htmlFormatId = ClipboardGetFormatId(clipboard->system, type_HtmlFormat);
755 localFormatId = ClipboardGetFormatId(clipboard->system, mime_text_plain);
756 mime = mime_text_utf8;
761 mime = mime_bitmap[0];
769 if (formatId == fileFormatId)
771 localFormatId = ClipboardGetFormatId(clipboard->system, mime_uri_list);
772 mime = mime_uri_list;
774 else if (formatId == htmlFormatId)
776 localFormatId = ClipboardGetFormatId(clipboard->system, mime_html);
784 data = UwacClipboardDataGet(clipboard->seat, mime, &size);
786 if (!data || (size > UINT32_MAX))
789 if (fileFormatId == formatId)
791 if (!cliprdr_file_context_update_client_data(clipboard->file, data, size))
795 const BOOL res = ClipboardSetData(clipboard->system, localFormatId, data, (UINT32)size);
801 data = ClipboardGetData(clipboard->system, formatId, &len);
806 if (fileFormatId == formatId)
808 const UINT32 flags = cliprdr_file_context_remote_get_flags(clipboard->file);
809 const UINT32 error = cliprdr_serialize_file_list_ex(
815 ClipboardUnlock(clipboard->system);
816 rc = wlf_cliprdr_send_data_response(clipboard, ddata, dsize);
827 wlf_cliprdr_server_format_data_response(CliprdrClientContext* context,
830 UINT rc = ERROR_INTERNAL_ERROR;
832 WINPR_ASSERT(context);
833 WINPR_ASSERT(formatDataResponse);
835 const UINT32 size = formatDataResponse->common.dataLen;
836 const BYTE* data = formatDataResponse->requestedFormatData;
838 wfClipboard* clipboard = cliprdr_file_context_get_context(context->custom);
839 WINPR_ASSERT(clipboard);
841 wlf_request* request = Queue_Dequeue(clipboard->request_queue);
846 if (formatDataResponse->common.msgFlags & CB_RESPONSE_FAIL)
848 WLog_WARN(TAG,
"clipboard data request for format %" PRIu32
" [%s], mime %s failed",
849 request->responseFormat, ClipboardGetFormatIdString(request->responseFormat),
850 request->responseMime);
853 rc = ERROR_INTERNAL_ERROR;
855 ClipboardLock(clipboard->system);
856 EnterCriticalSection(&clipboard->lock);
859 UINT32 srcFormatId = 0;
860 UINT32 dstFormatId = 0;
861 switch (request->responseFormat)
866 srcFormatId = request->responseFormat;
867 dstFormatId = ClipboardGetFormatId(clipboard->system, request->responseMime);
872 srcFormatId = request->responseFormat;
873 dstFormatId = ClipboardGetFormatId(clipboard->system, request->responseMime);
878 const char* name = wlf_get_server_format_name(clipboard, request->responseFormat);
881 if (strcmp(type_FileGroupDescriptorW, name) == 0)
884 ClipboardGetFormatId(clipboard->system, type_FileGroupDescriptorW);
885 dstFormatId = ClipboardGetFormatId(clipboard->system, request->responseMime);
887 if (!cliprdr_file_context_update_server_data(clipboard->file, clipboard->system,
891 else if (strcmp(type_HtmlFormat, name) == 0)
893 srcFormatId = ClipboardGetFormatId(clipboard->system, type_HtmlFormat);
894 dstFormatId = ClipboardGetFormatId(clipboard->system, request->responseMime);
903 const BOOL sres = ClipboardSetData(clipboard->system, srcFormatId, data, size);
905 cdata = ClipboardGetData(clipboard->system, dstFormatId, &len);
910 if (request->responseFile)
912 const size_t res = fwrite(cdata, 1, len, request->responseFile);
921 ClipboardUnlock(clipboard->system);
922 LeaveCriticalSection(&clipboard->lock);
924 wlf_request_free(request);
928 wfClipboard* wlf_clipboard_new(
wlfContext* wfc)
930 rdpChannels* channels = NULL;
931 wfClipboard* clipboard = NULL;
935 clipboard = (wfClipboard*)calloc(1,
sizeof(wfClipboard));
940 InitializeCriticalSection(&clipboard->lock);
941 clipboard->wfc = wfc;
942 channels = wfc->common.context.channels;
943 clipboard->log = WLog_Get(TAG);
944 clipboard->channels = channels;
945 clipboard->system = ClipboardCreate();
946 if (!clipboard->system)
949 clipboard->file = cliprdr_file_context_new(clipboard);
950 if (!clipboard->file)
953 if (!cliprdr_file_context_set_locally_available(clipboard->file, TRUE))
956 clipboard->request_queue = Queue_New(TRUE, -1, -1);
957 if (!clipboard->request_queue)
960 wObject* obj = Queue_Object(clipboard->request_queue);
962 obj->fnObjectFree = wlf_request_free;
963 obj->fnObjectNew = wlf_request_clone;
968 wlf_clipboard_free(clipboard);
972 void wlf_clipboard_free(wfClipboard* clipboard)
977 cliprdr_file_context_free(clipboard->file);
979 wlf_cliprdr_free_server_formats(clipboard);
980 wlf_cliprdr_free_client_formats(clipboard);
981 ClipboardDestroy(clipboard->system);
983 EnterCriticalSection(&clipboard->lock);
985 Queue_Free(clipboard->request_queue);
986 LeaveCriticalSection(&clipboard->lock);
987 DeleteCriticalSection(&clipboard->lock);
991 BOOL wlf_cliprdr_init(wfClipboard* clipboard, CliprdrClientContext* cliprdr)
993 WINPR_ASSERT(clipboard);
994 WINPR_ASSERT(cliprdr);
996 clipboard->context = cliprdr;
997 cliprdr->MonitorReady = wlf_cliprdr_monitor_ready;
998 cliprdr->ServerCapabilities = wlf_cliprdr_server_capabilities;
999 cliprdr->ServerFormatList = wlf_cliprdr_server_format_list;
1000 cliprdr->ServerFormatListResponse = wlf_cliprdr_server_format_list_response;
1001 cliprdr->ServerFormatDataRequest = wlf_cliprdr_server_format_data_request;
1002 cliprdr->ServerFormatDataResponse = wlf_cliprdr_server_format_data_response;
1004 return cliprdr_file_context_init(clipboard->file, cliprdr);
1007 BOOL wlf_cliprdr_uninit(wfClipboard* clipboard, CliprdrClientContext* cliprdr)
1009 WINPR_ASSERT(clipboard);
1010 if (!cliprdr_file_context_uninit(clipboard->file, cliprdr))
1014 cliprdr->custom = NULL;
This struct contains function pointer to initialize/free objects.