27 #include <winpr/wlog.h>
29 #include "sdl_clip.hpp"
30 #include "sdl_freerdp.hpp"
32 #define TAG CLIENT_TAG("sdl.cliprdr")
34 #define mime_text_plain "text/plain"
35 #define mime_text_utf8 mime_text_plain ";charset=utf-8"
37 static const std::vector<const char*> mime_text = { mime_text_plain, mime_text_utf8,
"UTF8_STRING",
38 "COMPOUND_TEXT",
"TEXT",
"STRING" };
40 static const char mime_png[] =
"image/png";
41 static const char mime_webp[] =
"image/webp";
42 static const char mime_jpg[] =
"image/jpeg";
43 static const char mime_tiff[] =
"image/tiff";
44 static const char mime_uri_list[] =
"text/uri-list";
45 static const char mime_html[] =
"text/html";
47 #define BMP_MIME_LIST "image/bmp", "image/x-bmp", "image/x-MS-bmp", "image/x-win-bitmap"
48 static const std::vector<const char*> mime_bitmap = { BMP_MIME_LIST };
49 static const std::vector<const char*> mime_image = { mime_png, mime_webp, mime_jpg, mime_tiff,
52 static const char mime_gnome_copied_files[] =
"x-special/gnome-copied-files";
53 static const char mime_mate_copied_files[] =
"x-special/mate-copied-files";
55 static const char* type_HtmlFormat =
"HTML Format";
56 static const char* type_FileGroupDescriptorW =
"FileGroupDescriptorW";
58 class ClipboardLockGuard
61 explicit ClipboardLockGuard(wClipboard* clipboard) : _clipboard(clipboard)
63 ClipboardLock(_clipboard);
65 ClipboardLockGuard(
const ClipboardLockGuard& other) =
delete;
66 ClipboardLockGuard(ClipboardLockGuard&& other) =
delete;
68 ClipboardLockGuard& operator=(
const ClipboardLockGuard& rhs) =
delete;
69 ClipboardLockGuard& operator=(ClipboardLockGuard&& rhs) =
delete;
73 ClipboardUnlock(_clipboard);
77 wClipboard* _clipboard;
82 return (lhs.formatId < rhs.formatId);
86 return (lhs.formatId > rhs.formatId);
90 return (lhs.formatId == rhs.formatId);
94 : _sdl(sdl), _file(cliprdr_file_context_new(this)), _log(WLog_Get(TAG)),
95 _system(ClipboardCreate()), _event(CreateEventA(nullptr, TRUE, FALSE, nullptr))
102 cliprdr_file_context_free(_file);
103 ClipboardDestroy(_system);
104 (void)CloseHandle(_event);
107 BOOL sdlClip::init(CliprdrClientContext* clip)
112 _ctx->MonitorReady = sdlClip::MonitorReady;
113 _ctx->ServerCapabilities = sdlClip::ReceiveServerCapabilities;
114 _ctx->ServerFormatList = sdlClip::ReceiveServerFormatList;
115 _ctx->ServerFormatListResponse = sdlClip::ReceiveFormatListResponse;
116 _ctx->ServerFormatDataRequest = sdlClip::ReceiveFormatDataRequest;
117 _ctx->ServerFormatDataResponse = sdlClip::ReceiveFormatDataResponse;
119 return cliprdr_file_context_init(_file, _ctx);
122 BOOL sdlClip::uninit(CliprdrClientContext* clip)
125 if (!cliprdr_file_context_uninit(_file, _ctx))
128 clip->custom =
nullptr;
132 bool sdlClip::handle_update(
const SDL_ClipboardEvent& ev)
134 if (!_ctx || !_sync || ev.owner)
137 clearServerFormats();
139 std::string mime_uri_list =
"text/uri-list";
140 std::string mime_html =
"text/html";
142 std::vector<std::string> mime_bitmap = {
"image/bmp",
"image/x-bmp",
"image/x-MS-bmp",
143 "image/x-win-bitmap" };
144 std::string mime_webp =
"image/webp";
145 std::string mime_png =
"image/png";
146 std::string mime_jpeg =
"image/jpeg";
147 std::string mime_tiff =
"image/tiff";
148 std::vector<std::string> mime_images = { mime_webp, mime_png, mime_jpeg, mime_tiff };
150 std::vector<std::string> clientFormatNames;
151 std::vector<CLIPRDR_FORMAT> clientFormats;
153 size_t nformats = ev.n_mime_types;
154 const char** clipboard_mime_formats = ev.mime_types;
156 WLog_Print(_log, WLOG_TRACE,
"SDL has %d formats", nformats);
158 bool textPushed =
false;
159 bool imgPushed =
false;
161 for (
size_t i = 0; i < nformats; i++)
163 std::string local_mime = clipboard_mime_formats[i];
164 WLog_Print(_log, WLOG_TRACE,
" - %s", local_mime.c_str());
166 if (std::find(mime_text.begin(), mime_text.end(), local_mime) != mime_text.end())
171 clientFormats.push_back({ CF_TEXT,
nullptr });
172 clientFormats.push_back({ CF_OEMTEXT,
nullptr });
173 clientFormats.push_back({ CF_UNICODETEXT,
nullptr });
177 else if (local_mime == mime_html)
179 clientFormatNames.emplace_back(type_HtmlFormat);
180 else if (std::find(mime_bitmap.begin(), mime_bitmap.end(), local_mime) != mime_bitmap.end())
185 clientFormats.push_back({ CF_DIB,
nullptr });
186 clientFormats.push_back({ CF_DIBV5,
nullptr });
188 for (
auto& bmp : mime_bitmap)
189 clientFormatNames.push_back(bmp);
191 for (
auto& img : mime_images)
192 clientFormatNames.push_back(img);
199 for (
auto& name : clientFormatNames)
201 clientFormats.push_back({ ClipboardRegisterFormat(_system, name.c_str()), name.data() });
204 std::sort(clientFormats.begin(), clientFormats.end(),
205 [](
const auto& a,
const auto& b) { return a < b; });
206 auto u = std::unique(clientFormats.begin(), clientFormats.end());
207 clientFormats.erase(u, clientFormats.end());
210 .common = { .msgType = CB_FORMAT_LIST, .msgFlags = 0 },
211 .numFormats =
static_cast<UINT32
>(clientFormats.size()),
212 .formats = clientFormats.data(),
215 WLog_Print(_log, WLOG_TRACE,
216 "-------------- client format list [%" PRIu32
"] ------------------",
217 formatList.numFormats);
218 for (UINT32 x = 0; x < formatList.numFormats; x++)
220 auto format = &formatList.formats[x];
221 WLog_Print(_log, WLOG_TRACE,
"client announces %" PRIu32
" [%s][%s]", format->formatId,
222 ClipboardGetFormatIdString(format->formatId), format->formatName);
226 WINPR_ASSERT(_ctx->ClientFormatList);
227 return _ctx->ClientFormatList(_ctx, &formatList) == CHANNEL_RC_OK;
232 WINPR_UNUSED(monitorReady);
233 WINPR_ASSERT(context);
234 WINPR_ASSERT(monitorReady);
236 auto clipboard =
static_cast<sdlClip*
>(
237 cliprdr_file_context_get_context(
static_cast<CliprdrFileContext*
>(context->custom)));
238 WINPR_ASSERT(clipboard);
240 auto ret = clipboard->SendClientCapabilities();
241 if (ret != CHANNEL_RC_OK)
244 clipboard->_sync =
true;
245 SDL_ClipboardEvent ev = { SDL_EVENT_CLIPBOARD_UPDATE, 0, 0,
false, 0,
nullptr };
246 if (!clipboard->handle_update(ev))
247 return ERROR_INTERNAL_ERROR;
249 return CHANNEL_RC_OK;
252 UINT sdlClip::SendClientCapabilities()
255 .capabilitySetType = CB_CAPSTYPE_GENERAL,
256 .capabilitySetLength = 12,
257 .version = CB_CAPS_VERSION_2,
258 .generalFlags = CB_USE_LONG_FORMAT_NAMES | cliprdr_file_context_current_flags(_file)
261 .cCapabilitiesSets = 1,
266 WINPR_ASSERT(_ctx->ClientCapabilities);
267 return _ctx->ClientCapabilities(_ctx, &capabilities);
270 void sdlClip::clearServerFormats()
272 _serverFormats.clear();
273 cliprdr_file_context_clear(_file);
276 UINT sdlClip::SendFormatListResponse(BOOL status)
279 .common = { .msgType = CB_FORMAT_LIST_RESPONSE,
280 .msgFlags =
static_cast<UINT16
>(status ? CB_RESPONSE_OK : CB_RESPONSE_FAIL),
284 WINPR_ASSERT(_ctx->ClientFormatListResponse);
285 return _ctx->ClientFormatListResponse(_ctx, &formatListResponse);
288 UINT sdlClip::SendDataResponse(
const BYTE* data,
size_t size)
292 if (size > UINT32_MAX)
293 return ERROR_INVALID_PARAMETER;
295 response.common.msgFlags = (data) ? CB_RESPONSE_OK : CB_RESPONSE_FAIL;
296 response.common.dataLen =
static_cast<UINT32
>(size);
297 response.requestedFormatData = data;
300 WINPR_ASSERT(_ctx->ClientFormatDataResponse);
301 return _ctx->ClientFormatDataResponse(_ctx, &response);
304 UINT sdlClip::SendDataRequest(uint32_t formatID,
const std::string& mime)
308 _request_queue.push({ formatID, mime });
311 WINPR_ASSERT(_ctx->ClientFormatDataRequest);
312 UINT ret = _ctx->ClientFormatDataRequest(_ctx, &request);
313 if (ret != CHANNEL_RC_OK)
315 WLog_Print(_log, WLOG_ERROR,
"error sending ClientFormatDataRequest, cancelling request");
316 _request_queue.pop();
322 std::string sdlClip::getServerFormat(uint32_t
id)
324 for (
auto& fmt : _serverFormats)
326 if (fmt.formatId() ==
id)
328 if (fmt.formatName())
329 return fmt.formatName();
337 uint32_t sdlClip::serverIdForMime(
const std::string& mime)
339 std::string cmp = mime;
340 if (mime_is_html(mime))
341 cmp = type_HtmlFormat;
342 if (mime_is_file(mime))
343 cmp = type_FileGroupDescriptorW;
345 for (
auto& format : _serverFormats)
347 if (!format.formatName())
349 if (cmp == format.formatName())
350 return format.formatId();
353 if (mime_is_image(mime))
355 if (mime_is_text(mime))
356 return CF_UNICODETEXT;
361 UINT sdlClip::ReceiveServerCapabilities(CliprdrClientContext* context,
364 WINPR_ASSERT(context);
365 WINPR_ASSERT(capabilities);
367 auto capsPtr =
reinterpret_cast<const BYTE*
>(capabilities->capabilitySets);
368 WINPR_ASSERT(capsPtr);
370 auto clipboard =
static_cast<sdlClip*
>(
371 cliprdr_file_context_get_context(
static_cast<CliprdrFileContext*
>(context->custom)));
372 WINPR_ASSERT(clipboard);
374 if (!cliprdr_file_context_remote_set_flags(clipboard->_file, 0))
375 return ERROR_INTERNAL_ERROR;
377 for (UINT32 i = 0; i < capabilities->cCapabilitiesSets; i++)
381 if (caps->capabilitySetType == CB_CAPSTYPE_GENERAL)
385 if (!cliprdr_file_context_remote_set_flags(clipboard->_file, generalCaps->generalFlags))
386 return ERROR_INTERNAL_ERROR;
389 capsPtr += caps->capabilitySetLength;
392 return CHANNEL_RC_OK;
395 UINT sdlClip::ReceiveServerFormatList(CliprdrClientContext* context,
403 if (!context || !context->custom)
404 return ERROR_INVALID_PARAMETER;
406 auto clipboard =
static_cast<sdlClip*
>(
407 cliprdr_file_context_get_context(
static_cast<CliprdrFileContext*
>(context->custom)));
408 WINPR_ASSERT(clipboard);
410 clipboard->clearServerFormats();
412 for (UINT32 i = 0; i < formatList->numFormats; i++)
416 clipboard->_serverFormats.emplace_back(format->formatId, format->formatName);
418 if (format->formatName)
420 if (strcmp(format->formatName, type_HtmlFormat) == 0)
425 else if (strcmp(format->formatName, type_FileGroupDescriptorW) == 0)
433 switch (format->formatId)
451 std::vector<const char*> mimetypes;
454 mimetypes.insert(mimetypes.end(), mime_text.begin(), mime_text.end());
458 mimetypes.insert(mimetypes.end(), mime_bitmap.begin(), mime_bitmap.end());
459 mimetypes.insert(mimetypes.end(), mime_image.begin(), mime_image.end());
463 mimetypes.push_back(mime_html);
467 mimetypes.push_back(mime_uri_list);
468 mimetypes.push_back(mime_gnome_copied_files);
469 mimetypes.push_back(mime_mate_copied_files);
472 const bool rc = SDL_SetClipboardData(sdlClip::ClipDataCb, sdlClip::ClipCleanCb, clipboard,
473 mimetypes.data(), mimetypes.size());
474 return clipboard->SendFormatListResponse(rc);
477 UINT sdlClip::ReceiveFormatListResponse(CliprdrClientContext* context,
480 WINPR_ASSERT(context);
481 WINPR_ASSERT(formatListResponse);
483 if (formatListResponse->common.msgFlags & CB_RESPONSE_FAIL)
484 WLog_WARN(TAG,
"format list update failed");
485 return CHANNEL_RC_OK;
488 std::shared_ptr<BYTE> sdlClip::ReceiveFormatDataRequestHandle(
491 const char* mime =
nullptr;
496 std::shared_ptr<BYTE> data;
498 WINPR_ASSERT(clipboard);
499 WINPR_ASSERT(formatDataRequest);
502 auto localFormatId = formatId = formatDataRequest->requestedFormatId;
504 ClipboardLockGuard give_me_a_name(clipboard->_system);
505 std::lock_guard<CriticalSection> lock(clipboard->_lock);
507 const UINT32 fileFormatId = ClipboardGetFormatId(clipboard->_system, type_FileGroupDescriptorW);
508 const UINT32 htmlFormatId = ClipboardGetFormatId(clipboard->_system, type_HtmlFormat);
515 localFormatId = ClipboardGetFormatId(clipboard->_system, mime_text_plain);
516 mime = mime_text_utf8;
521 mime = mime_bitmap[0];
529 if (formatId == fileFormatId)
531 localFormatId = ClipboardGetFormatId(clipboard->_system, mime_uri_list);
532 mime = mime_uri_list;
534 else if (formatId == htmlFormatId)
536 localFormatId = ClipboardGetFormatId(clipboard->_system, mime_html);
545 auto sdldata = std::shared_ptr<void>(SDL_GetClipboardData(mime, &size), SDL_free);
549 if (fileFormatId == formatId)
551 auto bdata =
static_cast<const char*
>(sdldata.get());
552 if (!cliprdr_file_context_update_client_data(clipboard->_file, bdata, size))
556 res = ClipboardSetData(clipboard->_system, localFormatId, sdldata.get(),
557 static_cast<uint32_t
>(size));
564 auto ptr =
static_cast<BYTE*
>(ClipboardGetData(clipboard->_system, formatId, &ptrlen));
565 data = std::shared_ptr<BYTE>(ptr, free);
570 if (fileFormatId == formatId)
572 BYTE* ddata =
nullptr;
574 const UINT32 flags = cliprdr_file_context_remote_get_flags(clipboard->_file);
575 const UINT32 error = cliprdr_serialize_file_list_ex(
579 auto tmp = std::shared_ptr<BYTE>(ddata, free);
591 UINT sdlClip::ReceiveFormatDataRequest(CliprdrClientContext* context,
594 WINPR_ASSERT(context);
595 WINPR_ASSERT(formatDataRequest);
597 auto clipboard =
static_cast<sdlClip*
>(
598 cliprdr_file_context_get_context(
static_cast<CliprdrFileContext*
>(context->custom)));
599 WINPR_ASSERT(clipboard);
602 auto rc = ReceiveFormatDataRequestHandle(clipboard, formatDataRequest, len);
603 return clipboard->SendDataResponse(rc.get(), len);
606 UINT sdlClip::ReceiveFormatDataResponse(CliprdrClientContext* context,
609 WINPR_ASSERT(context);
610 WINPR_ASSERT(formatDataResponse);
612 const UINT32 size = formatDataResponse->common.dataLen;
613 const BYTE* data = formatDataResponse->requestedFormatData;
615 auto clipboard =
static_cast<sdlClip*
>(
616 cliprdr_file_context_get_context(
static_cast<CliprdrFileContext*
>(context->custom)));
617 WINPR_ASSERT(clipboard);
619 ClipboardLockGuard give_me_a_name(clipboard->_system);
620 std::lock_guard<CriticalSection> lock(clipboard->_lock);
621 if (clipboard->_request_queue.empty())
623 WLog_Print(clipboard->_log, WLOG_ERROR,
"no pending format request");
624 return ERROR_INTERNAL_ERROR;
629 UINT32 srcFormatId = 0;
630 auto& request = clipboard->_request_queue.front();
631 bool success = (formatDataResponse->common.msgFlags & CB_RESPONSE_OK) &&
632 !(formatDataResponse->common.msgFlags & CB_RESPONSE_FAIL);
633 request.setSuccess(success);
637 WLog_Print(clipboard->_log, WLOG_WARN,
638 "clipboard data request for format %" PRIu32
" [%s], mime %s failed",
639 request.format(), request.formatstr().c_str(), request.mime().c_str());
643 switch (request.format())
648 srcFormatId = request.format();
653 srcFormatId = request.format();
658 auto name = clipboard->getServerFormat(request.format());
661 if (name == type_FileGroupDescriptorW)
664 ClipboardGetFormatId(clipboard->_system, type_FileGroupDescriptorW);
666 if (!cliprdr_file_context_update_server_data(
667 clipboard->_file, clipboard->_system, data, size))
668 return ERROR_INTERNAL_ERROR;
670 else if (name == type_HtmlFormat)
672 srcFormatId = ClipboardGetFormatId(clipboard->_system, type_HtmlFormat);
679 if (!ClipboardSetData(clipboard->_system, srcFormatId, data, size))
681 WLog_Print(clipboard->_log, WLOG_ERROR,
"error when setting clipboard data");
682 return ERROR_INTERNAL_ERROR;
686 if (!SetEvent(clipboard->_event))
687 return ERROR_INTERNAL_ERROR;
689 return CHANNEL_RC_OK;
692 const void* sdlClip::ClipDataCb(
void* userdata,
const char* mime_type,
size_t* size)
694 auto clip =
static_cast<sdlClip*
>(userdata);
697 WINPR_ASSERT(mime_type);
702 if (mime_is_text(mime_type))
703 mime_type =
"text/plain";
706 ClipboardLockGuard give_me_a_name(clip->_system);
707 std::lock_guard<CriticalSection> lock(clip->_lock);
708 auto cache = clip->_cache_data.find(mime_type);
709 if (cache != clip->_cache_data.end())
711 *size = cache->second.size;
712 return cache->second.ptr.get();
715 uint32_t formatID = clip->serverIdForMime(mime_type);
716 if (clip->SendDataRequest(formatID, mime_type))
721 HANDLE hdl[2] = { freerdp_abort_event(clip->_sdl->context()), clip->_event };
727 sdl->critical.unlock();
729 DWORD status = WaitForMultipleObjects(ARRAYSIZE(hdl), hdl, FALSE, 10 * 1000);
731 sdl->critical.lock();
733 if (status != WAIT_OBJECT_0 + 1)
735 std::lock_guard<CriticalSection> lock(clip->_lock);
736 clip->_request_queue.pop();
738 if (status == WAIT_TIMEOUT)
739 WLog_Print(clip->_log, WLOG_ERROR,
740 "no reply in 10 seconds, returning empty content");
747 ClipboardLockGuard give_me_a_name(clip->_system);
748 std::lock_guard<CriticalSection> lock(clip->_lock);
749 auto request = clip->_request_queue.front();
750 clip->_request_queue.pop();
752 if (!clip->_request_queue.size())
753 (void)ResetEvent(clip->_event);
755 if (request.success())
757 auto formatID = ClipboardRegisterFormat(clip->_system, mime_type);
758 auto data = ClipboardGetData(clip->_system, formatID, &len);
761 WLog_Print(clip->_log, WLOG_ERROR,
"error retrieving clipboard data");
765 auto ptr = std::shared_ptr<void>(data, free);
766 clip->_cache_data.insert({ mime_type, { len, ptr } });
775 void sdlClip::ClipCleanCb(
void* userdata)
777 auto clip =
static_cast<sdlClip*
>(userdata);
779 ClipboardLockGuard give_me_a_name(clip->_system);
780 std::lock_guard<CriticalSection> lock(clip->_lock);
781 ClipboardEmpty(clip->_system);
782 clip->_cache_data.clear();
785 bool sdlClip::mime_is_file(
const std::string& mime)
787 if (strncmp(mime_uri_list, mime.c_str(),
sizeof(mime_uri_list)) == 0)
789 if (strncmp(mime_gnome_copied_files, mime.c_str(),
sizeof(mime_gnome_copied_files)) == 0)
791 if (strncmp(mime_mate_copied_files, mime.c_str(),
sizeof(mime_mate_copied_files)) == 0)
796 bool sdlClip::mime_is_text(
const std::string& mime)
798 for (
size_t x = 0; x < ARRAYSIZE(mime_text); x++)
800 if (mime == mime_text[x])
807 bool sdlClip::mime_is_image(
const std::string& mime)
809 for (
size_t x = 0; x < ARRAYSIZE(mime_image); x++)
811 if (mime == mime_image[x])
818 bool sdlClip::mime_is_html(
const std::string& mime)
820 return mime.compare(mime_html) == 0;
823 ClipRequest::ClipRequest(UINT32 format,
const std::string& mime)
824 : _format(format), _mime(mime), _success(false)
828 uint32_t ClipRequest::format()
const
833 std::string ClipRequest::formatstr()
const
835 return ClipboardGetFormatIdString(_format);
838 std::string ClipRequest::mime()
const
843 bool ClipRequest::success()
const
848 void ClipRequest::setSuccess(
bool status)
object that handles clipboard context for the SDL3 client