27#include <winpr/wlog.h>
28#include <winpr/image.h>
30#include "sdl_clip.hpp"
31#include "sdl_freerdp.hpp"
33#define TAG CLIENT_TAG("sdl.cliprdr")
35#define mime_text_plain "text/plain"
37const char mime_text_utf8[] = mime_text_plain
";charset=utf-8";
39static const std::vector<const char*>& s_mime_text()
41 static std::vector<const char*> values;
44 values = std::vector<const char*>(
45 { mime_text_plain, mime_text_utf8,
"UTF8_STRING",
"COMPOUND_TEXT",
"TEXT",
"STRING" });
50static const char s_mime_png[] =
"image/png";
51static const char s_mime_webp[] =
"image/webp";
52static const char s_mime_jpg[] =
"image/jpeg";
54static const char s_mime_tiff[] =
"image/tiff";
55static const char s_mime_uri_list[] =
"text/uri-list";
56static const char s_mime_html[] =
"text/html";
58#define BMP_MIME_LIST "image/bmp", "image/x-bmp", "image/x-MS-bmp", "image/x-win-bitmap"
60static const std::vector<const char*>& s_mime_bitmap()
62 static std::vector<const char*> values;
65 values = std::vector<const char*>({ BMP_MIME_LIST });
70static const std::vector<const char*>& s_mime_image()
72 static std::vector<const char*> values;
75 if (winpr_image_format_is_supported(WINPR_IMAGE_WEBP))
76 values.push_back(s_mime_webp);
78 if (winpr_image_format_is_supported(WINPR_IMAGE_PNG))
79 values.push_back(s_mime_png);
81 if (winpr_image_format_is_supported(WINPR_IMAGE_JPEG))
82 values.push_back(s_mime_jpg);
84 auto bmp = std::vector<const char*>({ s_mime_tiff, BMP_MIME_LIST });
85 values.insert(values.end(), bmp.begin(), bmp.end());
90static const char s_mime_gnome_copied_files[] =
"x-special/gnome-copied-files";
91static const char s_mime_mate_copied_files[] =
"x-special/mate-copied-files";
93static const char* s_type_HtmlFormat =
"HTML Format";
94static const char* s_type_FileGroupDescriptorW =
"FileGroupDescriptorW";
96class ClipboardLockGuard
99 explicit ClipboardLockGuard(wClipboard* clipboard) : _clipboard(clipboard)
101 ClipboardLock(_clipboard);
103 ClipboardLockGuard(
const ClipboardLockGuard& other) =
delete;
104 ClipboardLockGuard(ClipboardLockGuard&& other) =
delete;
106 ClipboardLockGuard& operator=(
const ClipboardLockGuard& rhs) =
delete;
107 ClipboardLockGuard& operator=(ClipboardLockGuard&& rhs) =
delete;
109 ~ClipboardLockGuard()
111 ClipboardUnlock(_clipboard);
115 wClipboard* _clipboard;
120 return (lhs.formatId < rhs.formatId);
125 return (lhs.formatId == rhs.formatId);
129 : _sdl(sdl), _file(cliprdr_file_context_new(this)), _log(WLog_Get(TAG)),
130 _system(ClipboardCreate()), _event(CreateEventA(nullptr, TRUE, FALSE, nullptr))
137 cliprdr_file_context_free(_file);
138 ClipboardDestroy(_system);
139 (void)CloseHandle(_event);
142BOOL sdlClip::init(CliprdrClientContext* clip)
147 _ctx->MonitorReady = sdlClip::MonitorReady;
148 _ctx->ServerCapabilities = sdlClip::ReceiveServerCapabilities;
149 _ctx->ServerFormatList = sdlClip::ReceiveServerFormatList;
150 _ctx->ServerFormatListResponse = sdlClip::ReceiveFormatListResponse;
151 _ctx->ServerFormatDataRequest = sdlClip::ReceiveFormatDataRequest;
152 _ctx->ServerFormatDataResponse = sdlClip::ReceiveFormatDataResponse;
154 return cliprdr_file_context_init(_file, _ctx);
157BOOL sdlClip::uninit(CliprdrClientContext* clip)
160 if (!cliprdr_file_context_uninit(_file, _ctx))
163 clip->custom =
nullptr;
167bool sdlClip::handle_update(
const SDL_ClipboardEvent& ev)
169 if (!_ctx || !_sync || ev.owner)
172 if (ev.owner && (ev.reserved == 0x42))
176 SDL_SetClipboardData(sdlClip::ClipDataCb, sdlClip::ClipCleanCb,
this, ev.mime_types,
177 WINPR_ASSERTING_INT_CAST(
size_t, ev.num_mime_types));
178 _current_mimetypes.clear();
184 clearServerFormats();
186 std::string mime_html =
"text/html";
188 std::vector<std::string> mime_bitmap = {
"image/bmp",
"image/x-bmp",
"image/x-MS-bmp",
189 "image/x-win-bitmap" };
190 std::string mime_webp =
"image/webp";
191 std::string mime_png =
"image/png";
192 std::string mime_jpeg =
"image/jpeg";
193 std::string mime_tiff =
"image/tiff";
194 std::vector<std::string> mime_images = { mime_webp, mime_png, mime_jpeg, mime_tiff };
196 std::vector<std::string> clientFormatNames;
197 std::vector<CLIPRDR_FORMAT> clientFormats;
199 size_t nformats = WINPR_ASSERTING_INT_CAST(
size_t, ev.num_mime_types);
200 const char** clipboard_mime_formats = ev.mime_types;
202 WLog_Print(_log, WLOG_TRACE,
"SDL has %d formats", nformats);
204 bool textPushed =
false;
205 bool imgPushed =
false;
207 for (
size_t i = 0; i < nformats; i++)
209 auto local_mime = clipboard_mime_formats[i];
210 WLog_Print(_log, WLOG_TRACE,
" - %s", local_mime);
212 if (std::find(s_mime_text().begin(), s_mime_text().end(), local_mime) !=
218 clientFormats.push_back({ CF_TEXT,
nullptr });
219 clientFormats.push_back({ CF_OEMTEXT,
nullptr });
220 clientFormats.push_back({ CF_UNICODETEXT,
nullptr });
224 else if (local_mime == mime_html)
226 clientFormatNames.emplace_back(s_type_HtmlFormat);
227 else if ((std::find(mime_bitmap.begin(), mime_bitmap.end(), local_mime) !=
228 mime_bitmap.end()) ||
229 (std::find(mime_images.begin(), mime_images.end(), local_mime) !=
235 clientFormats.push_back({ CF_DIB,
nullptr });
236 clientFormats.push_back({ CF_DIBV5,
nullptr });
238 for (
auto& bmp : mime_bitmap)
239 clientFormatNames.push_back(bmp);
241 for (
auto& img : mime_images)
242 clientFormatNames.push_back(img);
249 for (
auto& name : clientFormatNames)
251 clientFormats.push_back({ ClipboardRegisterFormat(_system, name.c_str()), name.data() });
254 std::sort(clientFormats.begin(), clientFormats.end(),
255 [](
const auto& a,
const auto& b) { return a < b; });
256 auto u = std::unique(clientFormats.begin(), clientFormats.end());
257 clientFormats.erase(u, clientFormats.end());
260 { CB_FORMAT_LIST, 0, 0 },
261 static_cast<UINT32
>(clientFormats.size()),
262 clientFormats.data(),
265 WLog_Print(_log, WLOG_TRACE,
266 "-------------- client format list [%" PRIu32
"] ------------------",
267 formatList.numFormats);
268 for (UINT32 x = 0; x < formatList.numFormats; x++)
270 auto format = &formatList.formats[x];
271 WLog_Print(_log, WLOG_TRACE,
"client announces %" PRIu32
" [%s][%s]", format->formatId,
272 ClipboardGetFormatIdString(format->formatId), format->formatName);
276 WINPR_ASSERT(_ctx->ClientFormatList);
277 return _ctx->ClientFormatList(_ctx, &formatList) == CHANNEL_RC_OK;
282 WINPR_UNUSED(monitorReady);
283 WINPR_ASSERT(context);
284 WINPR_ASSERT(monitorReady);
286 auto clipboard =
static_cast<sdlClip*
>(
287 cliprdr_file_context_get_context(
static_cast<CliprdrFileContext*
>(context->custom)));
288 WINPR_ASSERT(clipboard);
290 auto ret = clipboard->SendClientCapabilities();
291 if (ret != CHANNEL_RC_OK)
294 clipboard->_sync =
true;
295 if (!sdl_push_user_event(SDL_EVENT_CLIPBOARD_UPDATE))
296 return ERROR_INTERNAL_ERROR;
298 return CHANNEL_RC_OK;
301UINT sdlClip::SendClientCapabilities()
304 CB_CAPSTYPE_GENERAL, 12, CB_CAPS_VERSION_2,
305 CB_USE_LONG_FORMAT_NAMES | cliprdr_file_context_current_flags(_file)
312 WINPR_ASSERT(_ctx->ClientCapabilities);
313 return _ctx->ClientCapabilities(_ctx, &capabilities);
316void sdlClip::clearServerFormats()
318 _serverFormats.clear();
320 cliprdr_file_context_clear(_file);
323UINT sdlClip::SendFormatListResponse(BOOL status)
326 { CB_FORMAT_LIST_RESPONSE,
static_cast<UINT16
>(status ? CB_RESPONSE_OK : CB_RESPONSE_FAIL),
330 WINPR_ASSERT(_ctx->ClientFormatListResponse);
331 return _ctx->ClientFormatListResponse(_ctx, &formatListResponse);
334UINT sdlClip::SendDataResponse(
const BYTE* data,
size_t size)
338 if (size > UINT32_MAX)
339 return ERROR_INVALID_PARAMETER;
341 response.common.msgFlags = (data) ? CB_RESPONSE_OK : CB_RESPONSE_FAIL;
342 response.common.dataLen =
static_cast<UINT32
>(size);
343 response.requestedFormatData = data;
346 WINPR_ASSERT(_ctx->ClientFormatDataResponse);
347 return _ctx->ClientFormatDataResponse(_ctx, &response);
350UINT sdlClip::SendDataRequest(uint32_t formatID,
const std::string& mime)
354 _request_queue.emplace(formatID, mime);
357 WINPR_ASSERT(_ctx->ClientFormatDataRequest);
358 UINT ret = _ctx->ClientFormatDataRequest(_ctx, &request);
359 if (ret != CHANNEL_RC_OK)
361 WLog_Print(_log, WLOG_ERROR,
"error sending ClientFormatDataRequest, cancelling request");
362 _request_queue.pop();
368std::string sdlClip::getServerFormat(uint32_t
id)
370 for (
auto& fmt : _serverFormats)
372 if (fmt.formatId() ==
id)
374 if (fmt.formatName())
375 return fmt.formatName();
383uint32_t sdlClip::serverIdForMime(
const std::string& mime)
385 std::string cmp = mime;
386 if (mime_is_html(mime))
387 cmp = s_type_HtmlFormat;
388 if (mime_is_file(mime))
389 cmp = s_type_FileGroupDescriptorW;
391 for (
auto& format : _serverFormats)
393 if (!format.formatName())
395 if (cmp == format.formatName())
396 return format.formatId();
399 if (mime_is_image(mime))
401 if (mime_is_text(mime))
402 return CF_UNICODETEXT;
407UINT sdlClip::ReceiveServerCapabilities(CliprdrClientContext* context,
410 WINPR_ASSERT(context);
411 WINPR_ASSERT(capabilities);
413 auto capsPtr =
reinterpret_cast<const BYTE*
>(capabilities->capabilitySets);
414 WINPR_ASSERT(capsPtr);
416 auto clipboard =
static_cast<sdlClip*
>(
417 cliprdr_file_context_get_context(
static_cast<CliprdrFileContext*
>(context->custom)));
418 WINPR_ASSERT(clipboard);
420 if (!cliprdr_file_context_remote_set_flags(clipboard->_file, 0))
421 return ERROR_INTERNAL_ERROR;
423 for (UINT32 i = 0; i < capabilities->cCapabilitiesSets; i++)
427 if (caps->capabilitySetType == CB_CAPSTYPE_GENERAL)
431 if (!cliprdr_file_context_remote_set_flags(clipboard->_file, generalCaps->generalFlags))
432 return ERROR_INTERNAL_ERROR;
435 capsPtr += caps->capabilitySetLength;
438 return CHANNEL_RC_OK;
441UINT sdlClip::ReceiveServerFormatList(CliprdrClientContext* context,
449 if (!context || !context->custom)
450 return ERROR_INVALID_PARAMETER;
452 auto clipboard =
static_cast<sdlClip*
>(
453 cliprdr_file_context_get_context(
static_cast<CliprdrFileContext*
>(context->custom)));
454 WINPR_ASSERT(clipboard);
456 clipboard->clearServerFormats();
458 for (UINT32 i = 0; i < formatList->numFormats; i++)
462 clipboard->_serverFormats.emplace_back(format->formatId, format->formatName);
464 if (format->formatName)
466 if (strcmp(format->formatName, s_type_HtmlFormat) == 0)
471 else if (strcmp(format->formatName, s_type_FileGroupDescriptorW) == 0)
479 switch (format->formatId)
497 clipboard->_current_mimetypes.clear();
500 clipboard->_current_mimetypes.insert(clipboard->_current_mimetypes.end(),
501 s_mime_text().begin(), s_mime_text().end());
505 clipboard->_current_mimetypes.insert(clipboard->_current_mimetypes.end(),
506 s_mime_bitmap().begin(), s_mime_bitmap().end());
507 clipboard->_current_mimetypes.insert(clipboard->_current_mimetypes.end(),
508 s_mime_image().begin(), s_mime_image().end());
512 clipboard->_current_mimetypes.push_back(s_mime_html);
516 clipboard->_current_mimetypes.push_back(s_mime_uri_list);
517 clipboard->_current_mimetypes.push_back(s_mime_gnome_copied_files);
518 clipboard->_current_mimetypes.push_back(s_mime_mate_copied_files);
521 auto s = clipboard->_current_mimetypes.size();
522 SDL_Event ev = { SDL_EVENT_CLIPBOARD_UPDATE };
523 ev.clipboard.owner =
true;
524 ev.clipboard.num_mime_types = WINPR_ASSERTING_INT_CAST(Sint32, s);
525 ev.clipboard.mime_types = clipboard->_current_mimetypes.data();
528 ev.clipboard.reserved = 0x42;
529 auto rc = (SDL_PushEvent(&ev) == 1);
530 return clipboard->SendFormatListResponse(rc);
533UINT sdlClip::ReceiveFormatListResponse(WINPR_ATTR_UNUSED CliprdrClientContext* context,
536 WINPR_ASSERT(context);
537 WINPR_ASSERT(formatListResponse);
539 if (formatListResponse->common.msgFlags & CB_RESPONSE_FAIL)
540 WLog_WARN(TAG,
"format list update failed");
541 return CHANNEL_RC_OK;
544std::shared_ptr<BYTE> sdlClip::ReceiveFormatDataRequestHandle(
547 const char* mime =
nullptr;
552 std::shared_ptr<BYTE> data;
554 WINPR_ASSERT(clipboard);
555 WINPR_ASSERT(formatDataRequest);
558 auto localFormatId = formatId = formatDataRequest->requestedFormatId;
560 ClipboardLockGuard give_me_a_name(clipboard->_system);
561 std::lock_guard<CriticalSection> lock(clipboard->_lock);
563 const UINT32 fileFormatId =
564 ClipboardGetFormatId(clipboard->_system, s_type_FileGroupDescriptorW);
565 const UINT32 htmlFormatId = ClipboardGetFormatId(clipboard->_system, s_type_HtmlFormat);
572 localFormatId = ClipboardGetFormatId(clipboard->_system, mime_text_plain);
573 mime = mime_text_utf8;
578 mime = s_mime_bitmap()[0];
579 localFormatId = ClipboardGetFormatId(clipboard->_system, mime);
587 if (formatId == fileFormatId)
589 localFormatId = ClipboardGetFormatId(clipboard->_system, s_mime_uri_list);
590 mime = s_mime_uri_list;
592 else if (formatId == htmlFormatId)
594 localFormatId = ClipboardGetFormatId(clipboard->_system, s_mime_html);
603 auto sdldata = std::shared_ptr<void>(SDL_GetClipboardData(mime, &size), SDL_free);
607 if (fileFormatId == formatId)
609 auto bdata =
static_cast<const char*
>(sdldata.get());
610 if (!cliprdr_file_context_update_client_data(clipboard->_file, bdata, size))
614 res = ClipboardSetData(clipboard->_system, localFormatId, sdldata.get(),
615 static_cast<uint32_t
>(size));
622 auto ptr =
static_cast<BYTE*
>(ClipboardGetData(clipboard->_system, formatId, &ptrlen));
623 data = std::shared_ptr<BYTE>(ptr, free);
628 if (fileFormatId == formatId)
630 BYTE* ddata =
nullptr;
632 const UINT32 flags = cliprdr_file_context_remote_get_flags(clipboard->_file);
633 const UINT32 error = cliprdr_serialize_file_list_ex(
637 auto tmp = std::shared_ptr<BYTE>(ddata, free);
649UINT sdlClip::ReceiveFormatDataRequest(CliprdrClientContext* context,
652 WINPR_ASSERT(context);
653 WINPR_ASSERT(formatDataRequest);
655 auto clipboard =
static_cast<sdlClip*
>(
656 cliprdr_file_context_get_context(
static_cast<CliprdrFileContext*
>(context->custom)));
657 WINPR_ASSERT(clipboard);
660 auto rc = ReceiveFormatDataRequestHandle(clipboard, formatDataRequest, len);
661 return clipboard->SendDataResponse(rc.get(), len);
664UINT sdlClip::ReceiveFormatDataResponse(CliprdrClientContext* context,
667 WINPR_ASSERT(context);
668 WINPR_ASSERT(formatDataResponse);
670 const UINT32 size = formatDataResponse->common.dataLen;
671 const BYTE* data = formatDataResponse->requestedFormatData;
673 auto clipboard =
static_cast<sdlClip*
>(
674 cliprdr_file_context_get_context(
static_cast<CliprdrFileContext*
>(context->custom)));
675 WINPR_ASSERT(clipboard);
677 ClipboardLockGuard give_me_a_name(clipboard->_system);
678 std::lock_guard<CriticalSection> lock(clipboard->_lock);
679 if (clipboard->_request_queue.empty())
681 WLog_Print(clipboard->_log, WLOG_ERROR,
"no pending format request");
682 return ERROR_INTERNAL_ERROR;
687 UINT32 srcFormatId = 0;
688 auto& request = clipboard->_request_queue.front();
689 bool success = (formatDataResponse->common.msgFlags & CB_RESPONSE_OK) &&
690 !(formatDataResponse->common.msgFlags & CB_RESPONSE_FAIL);
691 request.setSuccess(success);
695 WLog_Print(clipboard->_log, WLOG_WARN,
696 "clipboard data request for format %" PRIu32
" [%s], mime %s failed",
697 request.format(), request.formatstr().c_str(), request.mime().c_str());
701 switch (request.format())
706 srcFormatId = request.format();
711 srcFormatId = request.format();
716 auto name = clipboard->getServerFormat(request.format());
719 if (name == s_type_FileGroupDescriptorW)
722 ClipboardGetFormatId(clipboard->_system, s_type_FileGroupDescriptorW);
724 if (!cliprdr_file_context_update_server_data(
725 clipboard->_file, clipboard->_system, data, size))
726 return ERROR_INTERNAL_ERROR;
728 else if (name == s_type_HtmlFormat)
730 srcFormatId = ClipboardGetFormatId(clipboard->_system, s_type_HtmlFormat);
737 if (!ClipboardSetData(clipboard->_system, srcFormatId, data, size))
739 WLog_Print(clipboard->_log, WLOG_ERROR,
"error when setting clipboard data");
740 return ERROR_INTERNAL_ERROR;
744 if (!SetEvent(clipboard->_event))
745 return ERROR_INTERNAL_ERROR;
747 return CHANNEL_RC_OK;
750const void* sdlClip::ClipDataCb(
void* userdata,
const char* mime_type,
size_t* size)
752 auto clip =
static_cast<sdlClip*
>(userdata);
755 WINPR_ASSERT(mime_type);
760 if (mime_is_text(mime_type))
761 mime_type =
"text/plain";
764 ClipboardLockGuard give_me_a_name(clip->_system);
765 std::lock_guard<CriticalSection> lock(clip->_lock);
768 auto cache = clip->_cache_data.find(mime_type);
769 if (cache != clip->_cache_data.end())
771 *size = cache->second.size;
772 return cache->second.ptr.get();
775 auto formatID = clip->serverIdForMime(mime_type);
779 auto mimeFormatID = ClipboardRegisterFormat(clip->_system, mime_type);
780 auto fptr = ClipboardGetData(clip->_system, mimeFormatID, &fsize);
783 auto ptr = std::shared_ptr<void>(fptr, free);
784 clip->_cache_data.insert({ mime_type, { fsize, ptr } });
786 auto fcache = clip->_cache_data.find(mime_type);
787 if (fcache != clip->_cache_data.end())
789 *size = fcache->second.size;
790 return fcache->second.ptr.get();
794 WLog_Print(clip->_log, WLOG_INFO,
"requesting format %s [0x%08" PRIx32
"]", mime_type,
796 if (clip->SendDataRequest(formatID, mime_type))
800 HANDLE hdl[2] = { freerdp_abort_event(clip->_sdl->context()), clip->_event };
802 DWORD status = WaitForMultipleObjects(ARRAYSIZE(hdl), hdl, FALSE, 10 * 1000);
804 if (status != WAIT_OBJECT_0 + 1)
806 std::lock_guard<CriticalSection> lock(clip->_lock);
807 clip->_request_queue.pop();
809 if (status == WAIT_TIMEOUT)
810 WLog_Print(clip->_log, WLOG_ERROR,
811 "no reply in 10 seconds, returning empty content");
818 ClipboardLockGuard give_me_a_name(clip->_system);
819 std::lock_guard<CriticalSection> lock(clip->_lock);
820 auto request = clip->_request_queue.front();
821 clip->_request_queue.pop();
823 if (clip->_request_queue.empty())
824 (void)ResetEvent(clip->_event);
826 if (request.success())
828 auto formatID = ClipboardRegisterFormat(clip->_system, mime_type);
829 auto data = ClipboardGetData(clip->_system, formatID, &len);
832 WLog_Print(clip->_log, WLOG_ERROR,
"error retrieving clipboard data");
836 auto ptr = std::shared_ptr<void>(data, free);
837 clip->_cache_data.insert({ mime_type, { len, ptr } });
846void sdlClip::ClipCleanCb(
void* userdata)
848 auto clip =
static_cast<sdlClip*
>(userdata);
850 ClipboardLockGuard give_me_a_name(clip->_system);
851 std::lock_guard<CriticalSection> lock(clip->_lock);
852 ClipboardEmpty(clip->_system);
855bool sdlClip::mime_is_file(
const std::string& mime)
857 if (strncmp(s_mime_uri_list, mime.c_str(),
sizeof(s_mime_uri_list)) == 0)
859 if (strncmp(s_mime_gnome_copied_files, mime.c_str(),
sizeof(s_mime_gnome_copied_files)) == 0)
861 if (strncmp(s_mime_mate_copied_files, mime.c_str(),
sizeof(s_mime_mate_copied_files)) == 0)
866bool sdlClip::mime_is_text(
const std::string& mime)
868 for (
const auto& tmime : s_mime_text())
870 assert(tmime !=
nullptr);
878bool sdlClip::mime_is_image(
const std::string& mime)
880 for (
const auto& imime : s_mime_image())
882 assert(imime !=
nullptr);
890bool sdlClip::mime_is_bmp(
const std::string& mime)
892 for (
const auto& imime : s_mime_bitmap())
894 assert(imime !=
nullptr);
902bool sdlClip::mime_is_html(
const std::string& mime)
904 return mime.compare(s_mime_html) == 0;
907ClipRequest::ClipRequest(UINT32 format,
const std::string& mime)
908 : _format(format), _mime(mime), _success(false)
912uint32_t ClipRequest::format()
const
917std::string ClipRequest::formatstr()
const
919 return ClipboardGetFormatIdString(_format);
922std::string ClipRequest::mime()
const
927bool ClipRequest::success()
const
932void ClipRequest::setSuccess(
bool status)
object that handles clipboard context for the SDL3 client