30#include <winpr/wlog.h>
31#include <winpr/image.h>
34#include "sdl_clip.hpp"
35#include "sdl_context.hpp"
37#if defined(__EMSCRIPTEN__)
42static std::mutex s_web_lock;
43static sdlClip* s_web_clip =
nullptr;
44#include <emscripten.h>
45#include <emscripten/proxying.h>
46#include <emscripten/threading.h>
47#include <winpr/string.h>
51#define TAG CLIENT_TAG("sdl.cliprdr")
53#define mime_text_plain "text/plain"
54const char mime_text_utf8[] =
"text/plain;charset=utf-8";
56static const char* first_announcement[] = {
57 "sdl-clipboard-monitor-ready-4b8c7d9b-6d5c-4dcc-add1-21573ca564b0"
60[[nodiscard]]
static const std::vector<const char*>& s_mime_text()
62 static std::vector<const char*> values;
65 values = std::vector<const char*>(
66 { mime_text_utf8,
"UTF8_STRING", mime_text_plain,
"COMPOUND_TEXT",
"TEXT",
"STRING" });
71static const char s_mime_jxl[] =
"image/jxl";
72static const char s_mime_avif[] =
"image/avif";
73static const char s_mime_png[] =
"image/png";
74static const char s_mime_webp[] =
"image/webp";
75static const char s_mime_jpg[] =
"image/jpeg";
77static const char s_mime_tiff[] =
"image/tiff";
78static const char s_mime_uri_list[] =
"text/uri-list";
80static const char s_type_file_url[] =
"public.file-url";
81static const char s_mime_html[] =
"text/html";
83#define BMP_MIME_LIST "image/bmp", "image/x-bmp", "image/x-MS-bmp", "image/x-win-bitmap"
85[[nodiscard]]
static const std::vector<const char*>& s_mime_bitmap()
87 static std::vector<const char*> values;
90 values = std::vector<const char*>({ BMP_MIME_LIST });
95[[nodiscard]]
static const std::vector<const char*>& s_mime_image()
97 static std::vector<const char*> values;
100 if (winpr_image_format_is_supported(WINPR_IMAGE_WEBP))
101 values.push_back(s_mime_webp);
103 if (winpr_image_format_is_supported(WINPR_IMAGE_PNG))
104 values.push_back(s_mime_png);
106 if (winpr_image_format_is_supported(WINPR_IMAGE_JPEG))
107 values.push_back(s_mime_jpg);
109 auto bmp = std::vector<const char*>({ BMP_MIME_LIST });
110 values.insert(values.end(), bmp.begin(), bmp.end());
117[[nodiscard]]
static const std::vector<const char*>& s_mime_dib_sources()
119 static const std::vector<const char*> values = []()
121 std::vector<const char*> v({ BMP_MIME_LIST });
122 if (winpr_image_format_is_supported(WINPR_IMAGE_PNG))
123 v.push_back(s_mime_png);
124 if (winpr_image_format_is_supported(WINPR_IMAGE_WEBP))
125 v.push_back(s_mime_webp);
126 if (winpr_image_format_is_supported(WINPR_IMAGE_JPEG))
127 v.push_back(s_mime_jpg);
134[[nodiscard]]
static const std::vector<const char*>& s_mime_html_image_sources()
136 static const std::vector<const char*> values({ s_mime_jpg, s_mime_png, s_mime_webp, s_mime_avif,
137 s_mime_jxl, s_mime_tiff, BMP_MIME_LIST });
144[[nodiscard]]
static std::vector<std::string> localMimeTypes()
146 std::vector<std::string> list;
148 auto mimes = SDL_GetClipboardMimeTypes(&count);
149 for (
size_t x = 0; mimes && (x < count); x++)
152 list.emplace_back(mimes[x]);
154 SDL_free(
static_cast<void*
>(mimes));
158[[nodiscard]]
static bool hasMime(
const std::vector<std::string>& list,
const char* mime)
160 return std::find(list.begin(), list.end(), mime) != list.end();
163[[nodiscard]]
static const char* firstOffered(
const std::vector<std::string>& offered,
164 const std::vector<const char*>& candidates)
166 for (
const auto& m : candidates)
168 if (hasMime(offered, m))
177[[nodiscard]]
static uint32_t textLength(
const void* data, uint32_t size)
179 const auto end = memchr(data,
'\0', size);
182 return WINPR_ASSERTING_INT_CAST(uint32_t,
183 static_cast<const char*
>(end) -
static_cast<const char*
>(data));
186static const char s_mime_gnome_copied_files[] =
"x-special/gnome-copied-files";
187static const char s_mime_mate_copied_files[] =
"x-special/mate-copied-files";
189static const char s_mime_freerdp_update[] =
"x-special/freerdp-clipboard-update";
191static const char* s_type_HtmlFormat =
"HTML Format";
192static const char* s_type_FileGroupDescriptorW =
"FileGroupDescriptorW";
194class ClipboardLockGuard
197 explicit ClipboardLockGuard(wClipboard* clipboard) : _clipboard(clipboard)
199 ClipboardLock(_clipboard);
201 ClipboardLockGuard(
const ClipboardLockGuard& other) =
delete;
202 ClipboardLockGuard(ClipboardLockGuard&& other) =
delete;
204 ClipboardLockGuard& operator=(
const ClipboardLockGuard& rhs) =
delete;
205 ClipboardLockGuard& operator=(ClipboardLockGuard&& rhs) =
delete;
207 ~ClipboardLockGuard()
209 ClipboardUnlock(_clipboard);
213 wClipboard* _clipboard;
218 return (lhs.formatId < rhs.formatId);
223 return (lhs.formatId == rhs.formatId);
227 : _sdl(
sdl), _file(cliprdr_file_context_new(this)), _log(WLog_Get(TAG)),
228 _system(ClipboardCreate()), _event(CreateEventA(nullptr, TRUE, FALSE, nullptr)),
229 _uuid(
sdl::utils::generate_uuid_v4())
233 std::stringstream ss;
234 ss << s_mime_freerdp_update <<
"-" << _uuid;
235 _mime_uuid = ss.str();
237 std::ignore = cliprdr_file_context_set_locally_available(_file, TRUE);
242#if defined(__EMSCRIPTEN__)
244 std::scoped_lock web(s_web_lock);
245 if (s_web_clip ==
this)
246 s_web_clip =
nullptr;
249 cliprdr_file_context_free(_file);
250 ClipboardDestroy(_system);
251 std::ignore = CloseHandle(_event);
254bool sdlClip::init(CliprdrClientContext* clip)
257 ClipboardLockGuard systemlock(_system);
258 std::scoped_lock lock(_lock);
261 _ctx->MonitorReady = sdlClip::MonitorReady;
262 _ctx->ServerCapabilities = sdlClip::ReceiveServerCapabilities;
263 _ctx->ServerFormatList = sdlClip::ReceiveServerFormatList;
264 _ctx->ServerFormatListResponse = sdlClip::ReceiveFormatListResponse;
265 _ctx->ServerFormatDataRequest = sdlClip::ReceiveFormatDataRequest;
266 _ctx->ServerFormatDataResponse = sdlClip::ReceiveFormatDataResponse;
267#if defined(__EMSCRIPTEN__)
269 std::scoped_lock web(s_web_lock);
274 return cliprdr_file_context_init(_file, _ctx);
277bool sdlClip::uninit(CliprdrClientContext* clip)
280#if defined(__EMSCRIPTEN__)
282 std::scoped_lock web(s_web_lock);
283 if (s_web_clip ==
this)
284 s_web_clip =
nullptr;
287 ClipboardLockGuard systemlock(_system);
288 std::scoped_lock lock(_lock);
289 if (!cliprdr_file_context_uninit(_file, _ctx))
292 clip->custom =
nullptr;
293 _server_requests.clear();
297bool sdlClip::contains(
const char** mime_types, Sint32 count)
299 for (Sint32 x = 0; x < count; x++)
301 const auto mime = mime_types[x];
302 if (mime && (strcmp(_mime_uuid.c_str(), mime) == 0))
308bool sdlClip::ownsClipboard()
const
310 return hasMime(localMimeTypes(), _mime_uuid.c_str());
313bool sdlClip::handleEvent(
const SDL_ClipboardEvent& ev)
315 std::unique_ptr<
char*,
decltype(&SDL_free)> current(
nullptr, SDL_free);
316 const char** mime_types = ev.mime_types;
317 size_t nformats = WINPR_ASSERTING_INT_CAST(
size_t, ev.num_mime_types);
318 bool connected =
false;
320 std::scoped_lock lock(_lock);
321 connected = (_ctx !=
nullptr);
323 if (connected && _ctx && _sync && ev.owner && (ev.num_mime_types == 1))
325 if (strcmp(first_announcement[0], ev.mime_types[0]) == 0)
327 current.reset(SDL_GetClipboardMimeTypes(&nformats));
328 mime_types =
const_cast<const char**
>(current.get());
333 else if (!connected || !_ctx || !_sync || ev.owner)
335 _last_timestamp = ev.timestamp;
339 std::vector<std::string> mimes;
341 ClipboardLockGuard systemlock(_system);
342 std::scoped_lock lock(_lock);
343 mimes.swap(_current_mimetypes);
350 std::vector<const char*> cmimes;
351 cmimes.reserve(mimes.size());
352 for (
const auto& m : mimes)
353 cmimes.push_back(m.c_str());
357 if (cliprdr_file_context_set_local_clipboard_data(_file, sdlClip::ClipDataCb,
this,
358 cmimes.data(), cmimes.size()))
362 return SDL_SetClipboardData(sdlClip::ClipDataCb, sdlClip::ClipCleanCb,
this, cmimes.data(),
366 if (ev.timestamp == _last_timestamp)
369 if (contains(mime_types, WINPR_ASSERTING_INT_CAST(Sint32, nformats)))
379 WLog_Print(_log, WLOG_DEBUG,
"ignoring stale local clipboard update");
384 ClipboardLockGuard systemlock(_system);
385 std::scoped_lock lock(_lock);
386 clearServerFormats();
389 const std::string mime_html = s_mime_html;
391 const std::vector<std::string> mime_bitmap = { BMP_MIME_LIST };
392 const std::string mime_webp = s_mime_webp;
393 const std::string mime_png = s_mime_png;
394 const std::string mime_jpeg = s_mime_jpg;
395 const std::string mime_tiff = s_mime_tiff;
396 const std::vector<std::string> mime_images = { mime_webp, mime_png, mime_jpeg,
397 mime_tiff, s_mime_avif, s_mime_jxl };
399 std::vector<std::string> clientFormatNames;
400 std::vector<CLIPRDR_FORMAT> clientFormats;
402 const char** clipboard_mime_formats = mime_types;
404 WLog_Print(_log, WLOG_TRACE,
"SDL has %" PRIuz
" formats", nformats);
406 bool textPushed =
false;
407 bool imgPushed =
false;
408 bool filePushed =
false;
410 for (
size_t i = 0; i < nformats; i++)
412 std::string local_mime = clipboard_mime_formats[i];
413 WLog_Print(_log, WLOG_TRACE,
" - %s", local_mime.c_str());
415 if (std::find(s_mime_text().begin(), s_mime_text().end(), local_mime) !=
421 clientFormats.push_back({ CF_TEXT,
nullptr });
422 clientFormats.push_back({ CF_OEMTEXT,
nullptr });
423 clientFormats.push_back({ CF_UNICODETEXT,
nullptr });
427 else if (local_mime == mime_html)
429 clientFormatNames.emplace_back(s_type_HtmlFormat);
430 else if ((std::find(mime_bitmap.begin(), mime_bitmap.end(), local_mime) !=
431 mime_bitmap.end()) ||
432 (std::find(mime_images.begin(), mime_images.end(), local_mime) !=
438 clientFormats.push_back({ CF_DIB,
nullptr });
439#if defined(WINPR_UTILS_IMAGE_DIBv5)
440 clientFormats.push_back({ CF_DIBV5,
nullptr });
443 if (winpr_image_format_is_supported(WINPR_IMAGE_BITMAP))
445 for (
auto& bmp : mime_bitmap)
446 clientFormatNames.emplace_back(bmp);
449 if (winpr_image_format_is_supported(WINPR_IMAGE_JPEG))
450 clientFormatNames.emplace_back(mime_jpeg);
451 if (winpr_image_format_is_supported(WINPR_IMAGE_WEBP))
452 clientFormatNames.emplace_back(mime_webp);
453 if (winpr_image_format_is_supported(WINPR_IMAGE_PNG))
454 clientFormatNames.emplace_back(mime_png);
456 clientFormatNames.emplace_back(s_type_HtmlFormat);
459 clientFormatNames.emplace_back(local_mime);
461 else if (mime_is_file(local_mime) || (local_mime == s_type_file_url))
465 clientFormatNames.emplace_back(s_type_FileGroupDescriptorW);
471 std::sort(clientFormatNames.begin(), clientFormatNames.end());
472 clientFormatNames.erase(std::unique(clientFormatNames.begin(), clientFormatNames.end()),
473 clientFormatNames.end());
475 for (
auto& name : clientFormatNames)
477 clientFormats.push_back({ ClipboardRegisterFormat(_system, name.c_str()), name.data() });
480 std::sort(clientFormats.begin(), clientFormats.end(),
481 [](
const auto& a,
const auto& b) { return a < b; });
482 auto u = std::unique(clientFormats.begin(), clientFormats.end());
483 clientFormats.erase(u, clientFormats.end());
486 { CB_FORMAT_LIST, 0, 0 },
487 static_cast<UINT32
>(clientFormats.size()),
488 clientFormats.data(),
491 WLog_Print(_log, WLOG_TRACE,
492 "-------------- client format list [%" PRIu32
"] ------------------",
493 formatList.numFormats);
494 for (UINT32 x = 0; x < formatList.numFormats; x++)
496 auto format = &formatList.formats[x];
497 WLog_Print(_log, WLOG_TRACE,
"client announces %" PRIu32
" [%s][%s]", format->formatId,
498 ClipboardGetFormatIdString(format->formatId), format->formatName);
502 ClipboardLockGuard systemlock(_system);
503 std::scoped_lock lock(_lock);
507 if (cliprdr_file_context_notify_new_client_format_list(_file) != CHANNEL_RC_OK)
511 WINPR_ASSERT(_ctx->ClientFormatList);
512 return _ctx->ClientFormatList(_ctx, &formatList) == CHANNEL_RC_OK;
517 WINPR_UNUSED(monitorReady);
518 WINPR_ASSERT(context);
519 WINPR_ASSERT(monitorReady);
521 auto clipboard =
static_cast<sdlClip*
>(
522 cliprdr_file_context_get_context(
static_cast<CliprdrFileContext*
>(context->custom)));
523 WINPR_ASSERT(clipboard);
525 auto ret = clipboard->SendClientCapabilities();
526 if (ret != CHANNEL_RC_OK)
529 clipboard->_sync =
true;
531 ev.clipboard.type = SDL_EVENT_CLIPBOARD_UPDATE;
532 ev.clipboard.owner =
true;
533 ev.clipboard.timestamp = SDL_GetTicksNS();
534 ev.clipboard.num_mime_types = ARRAYSIZE(first_announcement);
535 ev.clipboard.mime_types = first_announcement;
537 auto rc = SDL_PushEvent(&ev);
539 return ERROR_INTERNAL_ERROR;
541 return CHANNEL_RC_OK;
544UINT sdlClip::SendClientCapabilities()
547 CB_CAPSTYPE_GENERAL, 12, CB_CAPS_VERSION_2,
548 CB_USE_LONG_FORMAT_NAMES | cliprdr_file_context_current_flags(_file)
555 WINPR_ASSERT(_ctx->ClientCapabilities);
556 return _ctx->ClientCapabilities(_ctx, &capabilities);
559void sdlClip::clearServerFormats()
562 ClipboardLockGuard systemlock(_system);
563 std::scoped_lock lock(_lock);
564 _serverFormats.clear();
568 ClipboardEmpty(_system);
570 cliprdr_file_context_clear(_file);
573UINT sdlClip::SendFormatListResponse(BOOL status)
576 { CB_FORMAT_LIST_RESPONSE,
static_cast<UINT16
>(status ? CB_RESPONSE_OK : CB_RESPONSE_FAIL),
580 WINPR_ASSERT(_ctx->ClientFormatListResponse);
581 return _ctx->ClientFormatListResponse(_ctx, &formatListResponse);
584UINT sdlClip::SendDataResponse(
const BYTE* data,
size_t size)
588 if (size > UINT32_MAX)
589 return ERROR_INVALID_PARAMETER;
591 response.common.msgFlags = (data) ? CB_RESPONSE_OK : CB_RESPONSE_FAIL;
592 response.common.dataLen =
static_cast<UINT32
>(size);
593 response.requestedFormatData = data;
596 WINPR_ASSERT(_ctx->ClientFormatDataResponse);
597 return _ctx->ClientFormatDataResponse(_ctx, &response);
600UINT sdlClip::SendDataRequest(uint32_t formatID,
const std::string& mime)
604 _request_queue.emplace(formatID, mime);
607 WINPR_ASSERT(_ctx->ClientFormatDataRequest);
608 UINT ret = _ctx->ClientFormatDataRequest(_ctx, &request);
609 if (ret != CHANNEL_RC_OK)
611 WLog_Print(_log, WLOG_ERROR,
"error sending ClientFormatDataRequest, cancelling request");
612 _request_queue.pop();
618std::string sdlClip::getServerFormat(uint32_t
id)
620 for (
auto& fmt : _serverFormats)
622 if (fmt.formatId() ==
id)
624 if (fmt.formatName())
625 return fmt.formatName();
633uint32_t sdlClip::serverIdForMime(
const std::string& mime)
635 std::string cmp = mime;
636 if (mime_is_html(mime))
637 cmp = s_type_HtmlFormat;
638 if (mime_is_file(mime))
639 cmp = s_type_FileGroupDescriptorW;
641 for (
auto& format : _serverFormats)
643 if (!format.formatName())
645 if (cmp == format.formatName())
646 return format.formatId();
649 if (mime_is_image(mime) || mime_is_bmp(mime))
654 for (
auto& format : _serverFormats)
656 if ((format.formatId() == CF_DIB) || (format.formatId() == CF_DIBV5))
657 return format.formatId();
658 if (!named && format.formatName() &&
659 (mime_is_image(format.formatName()) || mime_is_bmp(format.formatName())))
660 named = format.formatId();
662 return named ? named : CF_DIB;
664 if (mime_is_text(mime))
665 return CF_UNICODETEXT;
670UINT sdlClip::ReceiveServerCapabilities(CliprdrClientContext* context,
673 WINPR_ASSERT(context);
674 WINPR_ASSERT(capabilities);
676 auto capsPtr =
reinterpret_cast<const BYTE*
>(capabilities->capabilitySets);
677 WINPR_ASSERT(capsPtr);
679 auto clipboard =
static_cast<sdlClip*
>(
680 cliprdr_file_context_get_context(
static_cast<CliprdrFileContext*
>(context->custom)));
681 WINPR_ASSERT(clipboard);
683 if (!cliprdr_file_context_remote_set_flags(clipboard->_file, 0))
684 return ERROR_INTERNAL_ERROR;
686 for (UINT32 i = 0; i < capabilities->cCapabilitiesSets; i++)
690 if (caps->capabilitySetType == CB_CAPSTYPE_GENERAL)
694 if (!cliprdr_file_context_remote_set_flags(clipboard->_file, generalCaps->generalFlags))
695 return ERROR_INTERNAL_ERROR;
698 capsPtr += caps->capabilitySetLength;
701 return CHANNEL_RC_OK;
704UINT sdlClip::ReceiveServerFormatList(CliprdrClientContext* context,
712 if (!context || !context->custom)
713 return ERROR_INVALID_PARAMETER;
715 auto filecontext =
static_cast<CliprdrFileContext*
>(context->custom);
716 auto clipboard =
static_cast<sdlClip*
>(cliprdr_file_context_get_context(filecontext));
717 WINPR_ASSERT(clipboard);
719 ClipboardLockGuard systemlock(clipboard->_system);
720 std::scoped_lock lock(clipboard->_lock);
722 clipboard->clearServerFormats();
726 ClipboardEmpty(clipboard->_system);
728 for (UINT32 i = 0; i < formatList->numFormats; i++)
732 clipboard->_serverFormats.emplace_back(format->formatId, format->formatName);
734 if (format->formatName)
736 if (strcmp(format->formatName, s_type_HtmlFormat) == 0)
741 else if (strcmp(format->formatName, s_type_FileGroupDescriptorW) == 0)
746 else if (mime_is_image(format->formatName) || mime_is_bmp(format->formatName))
754 switch (format->formatId)
772 clipboard->_current_mimetypes.clear();
775 auto res = cliprdr_file_context_notify_new_server_format_list(filecontext);
776 if (res != CHANNEL_RC_OK)
782 clipboard->_current_mimetypes.insert(clipboard->_current_mimetypes.end(),
783 s_mime_text().begin(), s_mime_text().end());
787 clipboard->_current_mimetypes.insert(clipboard->_current_mimetypes.end(),
788 s_mime_bitmap().begin(), s_mime_bitmap().end());
789 clipboard->_current_mimetypes.insert(clipboard->_current_mimetypes.end(),
790 s_mime_image().begin(), s_mime_image().end());
794 clipboard->_current_mimetypes.emplace_back(s_mime_html);
798 clipboard->_current_mimetypes.emplace_back(s_mime_uri_list);
799 clipboard->_current_mimetypes.emplace_back(s_mime_gnome_copied_files);
800 clipboard->_current_mimetypes.emplace_back(s_mime_mate_copied_files);
802 clipboard->_current_mimetypes.emplace_back(clipboard->_mime_uuid.c_str());
804 auto& mime = clipboard->_current_mimetypes;
805 std::sort(mime.begin(), mime.end());
806 mime.erase(std::unique(mime.begin(), mime.end()), mime.end());
808 WLog_Print(clipboard->_log, WLOG_TRACE,
809 "-------------- server mime types [%" PRIuz
"] ------------------", mime.size());
810 for (
const auto& m : mime)
812 WLog_Print(clipboard->_log, WLOG_TRACE,
"server announces %s]", m.c_str());
818 ev.clipboard.type = SDL_EVENT_CLIPBOARD_UPDATE;
819 ev.clipboard.owner =
true;
820 ev.clipboard.timestamp = SDL_GetTicksNS();
821 ev.clipboard.num_mime_types = 0;
822 ev.clipboard.mime_types =
nullptr;
824 auto rc = (SDL_PushEvent(&ev) == 1);
825 const UINT ret = clipboard->SendFormatListResponse(rc);
826#if defined(__EMSCRIPTEN__)
827 if (ret == CHANNEL_RC_OK)
828 std::ignore = clipboard->webRequestServerText();
833UINT sdlClip::ReceiveFormatListResponse(WINPR_ATTR_UNUSED CliprdrClientContext* context,
836 WINPR_ASSERT(context);
837 WINPR_ASSERT(formatListResponse);
839 if (formatListResponse->common.msgFlags & CB_RESPONSE_FAIL)
840 WLog_WARN(TAG,
"format list update failed");
841 return CHANNEL_RC_OK;
844std::shared_ptr<BYTE> sdlClip::getLocalData(uint32_t formatId, uint32_t& len)
848 const auto offered = localMimeTypes();
853 if (hasMime(offered, _mime_uuid.c_str()))
855 WLog_Print(_log, WLOG_DEBUG,
"local clipboard holds server data, not answering request");
859 UINT32 fileFormatId = 0;
860 UINT32 htmlFormatId = 0;
862 ClipboardLockGuard systemlock(_system);
863 std::scoped_lock lock(_lock);
864 WLog_Print(_log, WLOG_DEBUG,
"Requesting format %s [0x%08" PRIx32
"] [%s]",
865 ClipboardGetFormatIdString(formatId), formatId,
866 ClipboardGetFormatName(_system, formatId));
867 fileFormatId = ClipboardGetFormatId(_system, s_type_FileGroupDescriptorW);
868 htmlFormatId = ClipboardGetFormatId(_system, s_type_HtmlFormat);
884 std::vector<Candidate> candidates;
892 const char* mime = firstOffered(offered, s_mime_text());
894 candidates.push_back({ mime, Store::Lookup });
902 for (
const auto& mime : s_mime_dib_sources())
904 if (hasMime(offered, mime))
905 candidates.push_back({ mime, Store::Register });
910 candidates.push_back({ s_mime_tiff, Store::Requested });
914 if (formatId == fileFormatId)
915 candidates.push_back({ s_mime_uri_list, Store::Lookup });
916 else if (formatId == htmlFormatId)
919 const char* mime = hasMime(offered, s_mime_html)
921 : firstOffered(offered, s_mime_html_image_sources());
923 candidates.push_back({ mime, Store::Lookup });
927 ClipboardLockGuard systemlock(_system);
928 std::scoped_lock lock(_lock);
929 const char* formatName = ClipboardGetFormatName(_system, formatId);
930 if (formatName && hasMime(offered, formatName))
933 candidates.push_back({ formatName, Store::Lookup });
935 else if (formatName && (mime_is_image(formatName) || mime_is_bmp(formatName)))
940 for (
const auto& mime : s_mime_dib_sources())
942 if (hasMime(offered, mime))
943 candidates.push_back({ mime, Store::Register });
950 std::shared_ptr<BYTE> data;
951 for (
const auto& candidate : candidates)
956 std::shared_ptr<void> sdldata;
957 if (formatId == fileFormatId)
959 std::shared_ptr<void>(cliprdr_file_context_get_local_uri_list(_file, &size), free);
961 sdldata = std::shared_ptr<void>(SDL_GetClipboardData(candidate.mime, &size), SDL_free);
962 if (!sdldata || (size > UINT32_MAX))
965 ClipboardLockGuard systemlock(_system);
966 std::scoped_lock lock(_lock);
968 if (formatId == fileFormatId)
970 auto bdata =
static_cast<const char*
>(sdldata.get());
971 if (!cliprdr_file_context_update_client_data(_file, bdata, size))
975 UINT32 storeId = formatId;
976 if (candidate.store == Store::Lookup)
977 storeId = ClipboardGetFormatId(_system, candidate.mime);
978 else if (candidate.store == Store::Register)
979 storeId = ClipboardRegisterFormat(_system, candidate.mime);
983 if (ClipboardSetData(_system, storeId, sdldata.get(),
static_cast<uint32_t
>(size)))
984 ptr =
static_cast<BYTE*
>(ClipboardGetData(_system, formatId, &ptrlen));
988 ClipboardEmpty(_system);
992 WLog_Print(_log, WLOG_DEBUG,
"could not convert local %s", candidate.mime);
996 data = std::shared_ptr<BYTE>(ptr, free);
999 if (formatId == fileFormatId)
1001 BYTE* ddata =
nullptr;
1003 const UINT32 flags = cliprdr_file_context_remote_get_flags(_file);
1004 const UINT32 error = cliprdr_serialize_file_list_ex(
1007 data = std::shared_ptr<BYTE>(ddata, free);
1020UINT sdlClip::ReceiveFormatDataRequest(CliprdrClientContext* context,
1023 WINPR_ASSERT(context);
1024 WINPR_ASSERT(formatDataRequest);
1026 auto clipboard =
static_cast<sdlClip*
>(
1027 cliprdr_file_context_get_context(
static_cast<CliprdrFileContext*
>(context->custom)));
1028 WINPR_ASSERT(clipboard);
1030#if defined(__EMSCRIPTEN__)
1035 if (!clipboard->webAnswerDataRequest(formatDataRequest))
1036 return clipboard->SendDataResponse(
nullptr, 0);
1037 return CHANNEL_RC_OK;
1040 std::scoped_lock lock(clipboard->_lock);
1042 FreeRDP_ClipboardFeatureMask);
1044 const UINT32 fileId =
1045 ClipboardRegisterFormat(clipboard->_system, s_type_FileGroupDescriptorW);
1046 if (fileId == formatDataRequest->requestedFormatId)
1048 if ((mask & CLIPRDR_FLAG_LOCAL_TO_REMOTE_FILES) == 0)
1049 return clipboard->SendDataResponse(
nullptr, 0);
1053 if ((mask & CLIPRDR_FLAG_LOCAL_TO_REMOTE) == 0)
1054 return clipboard->SendDataResponse(
nullptr, 0);
1062 if (clipboard->_reading_server)
1064 WLog_Print(clipboard->_log, WLOG_DEBUG,
1065 "local read of server data in progress, failing server request");
1066 return clipboard->SendDataResponse(
nullptr, 0);
1068 clipboard->_server_requests.push_back(formatDataRequest->requestedFormatId);
1073 if (!SDL_RunOnMainThread([](
void* userdata)
1074 {
static_cast<sdlClip*
>(userdata)->handleDataRequests(); }, clipboard,
1077 std::scoped_lock lock(clipboard->_lock);
1078 if (!clipboard->_server_requests.empty())
1079 clipboard->_server_requests.pop_back();
1080 return clipboard->SendDataResponse(
nullptr, 0);
1082 return CHANNEL_RC_OK;
1085void sdlClip::handleDataRequests()
1089 uint32_t formatId = 0;
1091 std::scoped_lock lock(_lock);
1092 if (_server_requests.empty())
1094 formatId = _server_requests.front();
1095 _server_requests.pop_front();
1101 auto data = getLocalData(formatId, len);
1102 std::scoped_lock lock(_lock);
1105 if (SendDataResponse(data.get(), len) != CHANNEL_RC_OK)
1106 WLog_Print(_log, WLOG_WARN,
"failed to send clipboard data response");
1110UINT sdlClip::ReceiveFormatDataResponse(CliprdrClientContext* context,
1113 WINPR_ASSERT(context);
1114 WINPR_ASSERT(formatDataResponse);
1116 const UINT32 size = formatDataResponse->common.dataLen;
1117 const BYTE* data = formatDataResponse->requestedFormatData;
1119 auto clipboard =
static_cast<sdlClip*
>(
1120 cliprdr_file_context_get_context(
static_cast<CliprdrFileContext*
>(context->custom)));
1121 WINPR_ASSERT(clipboard);
1123#if defined(__EMSCRIPTEN__)
1124 if (clipboard->webTakeDataResponse(formatDataResponse))
1125 return CHANNEL_RC_OK;
1127 ClipboardLockGuard systemlock(clipboard->_system);
1128 std::scoped_lock lock(clipboard->_lock);
1129 if (clipboard->_request_queue.empty())
1135 WLog_Print(clipboard->_log, WLOG_WARN,
1136 "format data response with no pending request (late reply?), ignoring");
1137 return CHANNEL_RC_OK;
1142 UINT32 srcFormatId = 0;
1143 auto& request = clipboard->_request_queue.front();
1144 bool success = (formatDataResponse->common.msgFlags & CB_RESPONSE_OK) &&
1145 !(formatDataResponse->common.msgFlags & CB_RESPONSE_FAIL);
1146 request.setSuccess(success);
1150 WLog_Print(clipboard->_log, WLOG_WARN,
1151 "clipboard data request for format %" PRIu32
" [%s], mime %s failed",
1152 request.format(), request.formatstr().c_str(), request.mime().c_str());
1156 switch (request.format())
1160 case CF_UNICODETEXT:
1161 srcFormatId = request.format();
1166 srcFormatId = request.format();
1171 auto name = clipboard->getServerFormat(request.format());
1174 if (name == s_type_FileGroupDescriptorW)
1177 ClipboardGetFormatId(clipboard->_system, s_type_FileGroupDescriptorW);
1179 if (cliprdr_file_context_has_local_support(clipboard->_file))
1181 if (!cliprdr_file_context_update_server_data(
1182 clipboard->_file, clipboard->_system, data, size))
1184 WLog_Print(clipboard->_log, WLOG_WARN,
1185 "File clipboard failed to update");
1189 else if (name == s_type_HtmlFormat)
1191 srcFormatId = ClipboardGetFormatId(clipboard->_system, s_type_HtmlFormat);
1193 else if (mime_is_image(name) || mime_is_bmp(name))
1195 srcFormatId = ClipboardGetFormatId(clipboard->_system, name.c_str());
1202 if (!ClipboardSetData(clipboard->_system, srcFormatId, data, size))
1206 WLog_Print(clipboard->_log, WLOG_ERROR,
"error when setting clipboard data");
1207 request.setSuccess(
false);
1210 WLog_Print(clipboard->_log, WLOG_DEBUG,
"updated clipboard data %s [0x%08" PRIx32
"]",
1211 ClipboardGetFormatName(clipboard->_system, srcFormatId), srcFormatId);
1214 if (!SetEvent(clipboard->_event))
1216 WLog_Print(clipboard->_log, WLOG_ERROR,
"error when setting clipboard event");
1217 return ERROR_INTERNAL_ERROR;
1220 return CHANNEL_RC_OK;
1223const void* sdlClip::ClipDataCb(
void* userdata,
const char* mime_type,
size_t* size)
1225 auto clip =
static_cast<sdlClip*
>(userdata);
1228 WINPR_ASSERT(mime_type);
1233 if (mime_is_text(mime_type))
1235 if (mime_is_utf8(mime_type))
1236 mime_type = mime_text_utf8;
1238 mime_type = mime_text_plain;
1242 ClipboardLockGuard systemlock(clip->_system);
1243 std::scoped_lock lock(clip->_lock);
1249 auto cache = clip->_cache_data.find(mime_type);
1250 if (cache != clip->_cache_data.end())
1252 *size = cache->second.size;
1253 return cache->second.ptr.get();
1256 auto formatID = clip->serverIdForMime(mime_type);
1260 auto mimeFormatID = ClipboardRegisterFormat(clip->_system, mime_type);
1261 auto fptr = ClipboardGetData(clip->_system, mimeFormatID, &fsize);
1264 auto ptr = std::shared_ptr<void>(fptr, free);
1265 if (mime_is_text(mime_type))
1266 fsize = textLength(fptr, fsize);
1267 clip->_cache_data.emplace(mime_type, cache_entry{ fsize, ptr });
1269 auto fcache = clip->_cache_data.find(mime_type);
1270 if (fcache != clip->_cache_data.end())
1272 *size = fcache->second.size;
1273 return fcache->second.ptr.get();
1277 WLog_Print(clip->_log, WLOG_DEBUG,
"requesting format %s [%s 0x%08" PRIx32
"]", mime_type,
1278 ClipboardGetFormatName(clip->_system, formatID), formatID);
1283 clip->_reading_server =
true;
1284 while (!clip->_server_requests.empty())
1286 clip->_server_requests.pop_front();
1287 WLog_Print(clip->_log, WLOG_DEBUG,
1288 "local read of server data in progress, failing queued server request");
1289 std::ignore = clip->SendDataResponse(
nullptr, 0);
1292 if (clip->SendDataRequest(formatID, mime_type))
1294 clip->_reading_server =
false;
1299 HANDLE hdl[2] = { freerdp_abort_event(clip->_sdl->context()), clip->_event };
1301 const UINT32 timeout =
1303 DWORD status = WaitForMultipleObjects(ARRAYSIZE(hdl), hdl, FALSE, timeout);
1305 if (status != WAIT_OBJECT_0 + 1)
1307 std::scoped_lock lock(clip->_lock);
1308 clip->_reading_server =
false;
1309 if (!clip->_request_queue.empty())
1310 clip->_request_queue.pop();
1312 if (status == WAIT_TIMEOUT)
1313 WLog_Print(clip->_log, WLOG_ERROR,
1314 "no reply in 10 seconds, returning empty content");
1321 ClipboardLockGuard systemlock(clip->_system);
1322 std::scoped_lock lock(clip->_lock);
1323 clip->_reading_server =
false;
1324 if (clip->_request_queue.empty())
1327 auto request = std::move(clip->_request_queue.front());
1328 clip->_request_queue.pop();
1330 if (clip->_request_queue.empty())
1331 std::ignore = ResetEvent(clip->_event);
1333 if (request.success())
1335 auto formatID = ClipboardRegisterFormat(clip->_system, mime_type);
1336 auto data = ClipboardGetData(clip->_system, formatID, &len);
1339 WLog_Print(clip->_log, WLOG_ERROR,
"error retrieving clipboard data");
1343 auto ptr = std::shared_ptr<void>(data, free);
1344 if (mime_is_text(mime_type))
1345 len = textLength(data, len);
1346 clip->_cache_data.emplace(mime_type, cache_entry{ len, ptr });
1355void sdlClip::ClipCleanCb(
void* userdata)
1357 auto clip =
static_cast<sdlClip*
>(userdata);
1359 ClipboardLockGuard give_me_a_name(clip->_system);
1360 std::scoped_lock lock(clip->_lock);
1361 ClipboardEmpty(clip->_system);
1364bool sdlClip::mime_is_file(
const std::string& mime)
1366 if (strncmp(s_mime_uri_list, mime.c_str(),
sizeof(s_mime_uri_list)) == 0)
1368 if (strncmp(s_mime_gnome_copied_files, mime.c_str(),
sizeof(s_mime_gnome_copied_files)) == 0)
1370 if (strncmp(s_mime_mate_copied_files, mime.c_str(),
sizeof(s_mime_mate_copied_files)) == 0)
1375bool sdlClip::mime_is_text(
const std::string& mime)
1377 for (
const auto& tmime : s_mime_text())
1379 assert(tmime !=
nullptr);
1387bool sdlClip::mime_is_utf8(
const std::string& mime)
1389 if (mime == std::string(mime_text_utf8))
1392 return mime == std::string(
"UTF8_STRING");
1395bool sdlClip::mime_is_image(
const std::string& mime)
1397 for (
const auto& imime : s_mime_image())
1399 assert(imime !=
nullptr);
1407bool sdlClip::mime_is_bmp(
const std::string& mime)
1409 for (
const auto& imime : s_mime_bitmap())
1411 assert(imime !=
nullptr);
1419bool sdlClip::mime_is_html(
const std::string& mime)
1421 return mime.compare(s_mime_html) == 0;
1424ClipRequest::ClipRequest(UINT32 format,
const std::string& mime)
1425 : _format(format), _mime(mime), _success(false)
1429uint32_t ClipRequest::format()
const
1434std::string ClipRequest::formatstr()
const
1436 return ClipboardGetFormatIdString(_format);
1439std::string ClipRequest::mime()
const
1444bool ClipRequest::success()
const
1449void ClipRequest::setSuccess(
bool status)
1454CliprdrFormat::CliprdrFormat(uint32_t formatID,
const char* formatName) : _formatID(formatID)
1457 _formatName = formatName;
1460uint32_t CliprdrFormat::formatId()
const
1465const char* CliprdrFormat::formatName()
const
1467 if (_formatName.empty())
1469 return _formatName.c_str();
1472#if defined(__EMSCRIPTEN__)
1478static const char s_web_mime_text[] =
"x-freerdp-web-text";
1479static const char s_web_mime_dib[] =
"x-freerdp-web-dib";
1480static const char s_web_mime_png[] =
"x-freerdp-web-png";
1483extern "C" void freerdp_web_clipboard_write_js(
const char* utf8);
1484extern "C" void freerdp_web_clipboard_write_image_js(
const BYTE* data,
size_t size,
int png);
1494static void web_write_on_main(
void* arg)
1496 auto job =
static_cast<web_write_job*
>(arg);
1498 freerdp_web_clipboard_write_js(job->text);
1500 freerdp_web_clipboard_write_image_js(job->dib, job->size, job->png ? 1 : 0);
1506static void web_write(
char* text, BYTE* dib,
size_t size,
bool png =
false)
1508 auto job =
static_cast<web_write_job*
>(calloc(1,
sizeof(web_write_job)));
1519 if (emscripten_is_main_browser_thread())
1520 web_write_on_main(job);
1521 else if (!emscripten_proxy_async(emscripten_proxy_get_system_queue(),
1522 emscripten_main_runtime_thread_id(), web_write_on_main, job))
1530bool sdlClip::webOffer(
const char* utf8,
const BYTE* dib,
size_t dibSize)
1532 std::scoped_lock web(s_web_lock);
1533 auto clip = s_web_clip;
1534 if (!clip || !clip->_ctx || !clip->_ctx->ClientFormatList)
1536 std::vector<CLIPRDR_FORMAT> formats;
1538 std::scoped_lock lock(clip->_lock);
1539 clip->_web_local = utf8 ? utf8 :
"";
1540 clip->_web_local_dib.assign(dib, dib ? dib + dibSize : dib);
1542 if (!clip->_web_local.empty())
1543 clip->_web_recent.push_back(std::hash<std::string>{}(clip->_web_local));
1545 clip->_web_recent.push_back(std::hash<std::string_view>{}(
1546 std::string_view(
reinterpret_cast<const char*
>(dib), dibSize)));
1547 while (clip->_web_recent.size() > 16)
1548 clip->_web_recent.pop_front();
1549 if (!clip->_web_local.empty())
1551 formats.push_back({ CF_UNICODETEXT,
nullptr });
1552 formats.push_back({ CF_TEXT,
nullptr });
1554 if (!clip->_web_local_dib.empty())
1555 formats.push_back({ CF_DIB,
nullptr });
1557 if (formats.empty())
1560 list.common.msgType = CB_FORMAT_LIST;
1561 list.numFormats =
static_cast<UINT32
>(formats.size());
1562 list.formats = formats.data();
1563 return clip->_ctx->ClientFormatList(clip->_ctx, &list) == CHANNEL_RC_OK;
1566bool sdlClip::webRequestServerText()
1568 bool has_text =
false;
1569 bool has_dib =
false;
1571 std::scoped_lock lock(_lock);
1572 for (
const auto& f : _serverFormats)
1574 has_text |= (f.formatId() == CF_UNICODETEXT);
1575 has_dib |= (f.formatId() == CF_DIB);
1577 if (f.formatName() && (strcmp(f.formatName(), s_mime_png) == 0))
1578 png_id = f.formatId();
1580 if (!has_text && !has_dib && !png_id)
1587 ok &= (SendDataRequest(CF_UNICODETEXT, s_web_mime_text) == CHANNEL_RC_OK);
1589 ok &= (SendDataRequest(CF_DIB, s_web_mime_dib) == CHANNEL_RC_OK);
1591 ok &= (SendDataRequest(png_id, s_web_mime_png) == CHANNEL_RC_OK);
1599 std::scoped_lock lock(_lock);
1600 if (_request_queue.empty())
1602 kind = _request_queue.front().mime();
1603 if ((kind != s_web_mime_text) && (kind != s_web_mime_dib) && (kind != s_web_mime_png))
1605 _request_queue.pop();
1608 if (!(response->common.msgFlags & CB_RESPONSE_OK) || !response->requestedFormatData)
1610 const BYTE* data = response->requestedFormatData;
1611 const size_t size = response->common.dataLen;
1612 if (kind == s_web_mime_text)
1614 char* utf8 = ConvertWCharNToUtf8Alloc(
reinterpret_cast<const WCHAR*
>(data),
1615 size /
sizeof(WCHAR),
nullptr);
1619 std::scoped_lock lock(_lock);
1620 const auto hv = std::hash<std::string>{}(std::string(utf8));
1621 if (std::find(_web_recent.begin(), _web_recent.end(), hv) != _web_recent.end())
1627 _web_local_dib.clear();
1629 web_write(utf8,
nullptr, 0);
1631 else if ((kind == s_web_mime_png) ? (size > 8)
1635 std::scoped_lock lock(_lock);
1636 const auto hv = std::hash<std::string_view>{}(
1637 std::string_view(
reinterpret_cast<const char*
>(data), size));
1638 if (std::find(_web_recent.begin(), _web_recent.end(), hv) != _web_recent.end())
1641 _web_local_dib.clear();
1643 auto copy =
static_cast<BYTE*
>(malloc(size));
1646 memcpy(copy, data, size);
1647 web_write(
nullptr, copy, size, kind == s_web_mime_png);
1656 std::vector<BYTE> dib;
1658 std::scoped_lock lock(_lock);
1659 if (_web_local.empty() && _web_local_dib.empty())
1662 dib = _web_local_dib;
1664 switch (request->requestedFormatId)
1666 case CF_UNICODETEXT:
1670 WCHAR* wstr = ConvertUtf8NToWCharAlloc(text.c_str(), text.size() + 1,
nullptr);
1674 std::ignore = SendDataResponse(
reinterpret_cast<const BYTE*
>(wstr),
1675 (_wcslen(wstr) + 1) *
sizeof(WCHAR));
1683 SendDataResponse(
reinterpret_cast<const BYTE*
>(text.c_str()), text.size() + 1);
1688 std::ignore = SendDataResponse(dib.data(), dib.size());
1708static void* web_offer_thread(
void* arg)
1710 auto job =
static_cast<web_offer_job*
>(arg);
1711 std::ignore = sdlClip::webOffer(job->text, job->dib, job->size);
1718extern "C" EMSCRIPTEN_KEEPALIVE
int freerdp_web_clipboard_offer(
const char* utf8,
const BYTE* dib,
1722 std::scoped_lock web(s_web_lock);
1726 auto job =
static_cast<web_offer_job*
>(calloc(1,
sizeof(web_offer_job)));
1729 job->text = utf8 ? _strdup(utf8) : nullptr;
1732 job->dib =
static_cast<BYTE*
>(malloc(dibSize));
1735 memcpy(job->dib, dib, dibSize);
1736 job->size = dibSize;
1740 if (pthread_create(&tid,
nullptr, web_offer_thread, job) != 0)
1747 pthread_detach(tid);
object that handles clipboard context for the SDL3 client
WINPR_ATTR_NODISCARD FREERDP_API UINT32 freerdp_settings_get_uint32(const rdpSettings *settings, FreeRDP_Settings_Keys_UInt32 id)
Returns a UINT32 settings value.