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*> s_mime_text = { mime_text_plain, mime_text_utf8,
38 "UTF8_STRING",
"COMPOUND_TEXT",
41 static const char s_mime_png[] =
"image/png";
42 static const char s_mime_webp[] =
"image/webp";
43 static const char s_mime_jpg[] =
"image/jpeg";
44 static const char s_mime_tiff[] =
"image/tiff";
45 static const char s_mime_uri_list[] =
"text/uri-list";
46 static const char s_mime_html[] =
"text/html";
48 #define BMP_MIME_LIST "image/bmp", "image/x-bmp", "image/x-MS-bmp", "image/x-win-bitmap"
49 static const std::vector<const char*> s_mime_bitmap = { BMP_MIME_LIST };
50 static const std::vector<const char*> s_mime_image = { s_mime_png, s_mime_webp, s_mime_jpg,
51 s_mime_tiff, BMP_MIME_LIST };
53 static const char s_mime_gnome_copied_files[] =
"x-special/gnome-copied-files";
54 static const char s_mime_mate_copied_files[] =
"x-special/mate-copied-files";
56 static const char* s_type_HtmlFormat =
"HTML Format";
57 static const char* s_type_FileGroupDescriptorW =
"FileGroupDescriptorW";
59 class ClipboardLockGuard
62 explicit ClipboardLockGuard(wClipboard* clipboard) : _clipboard(clipboard)
64 ClipboardLock(_clipboard);
66 ClipboardLockGuard(
const ClipboardLockGuard& other) =
delete;
67 ClipboardLockGuard(ClipboardLockGuard&& other) =
delete;
69 ClipboardLockGuard& operator=(
const ClipboardLockGuard& rhs) =
delete;
70 ClipboardLockGuard& operator=(ClipboardLockGuard&& rhs) =
delete;
74 ClipboardUnlock(_clipboard);
78 wClipboard* _clipboard;
83 return (lhs.formatId < rhs.formatId);
87 return (lhs.formatId > rhs.formatId);
91 return (lhs.formatId == rhs.formatId);
95 : _sdl(sdl), _file(cliprdr_file_context_new(this)), _log(WLog_Get(TAG)),
96 _system(ClipboardCreate()), _event(CreateEventA(nullptr, TRUE, FALSE, nullptr))
103 cliprdr_file_context_free(_file);
104 ClipboardDestroy(_system);
105 (void)CloseHandle(_event);
108 BOOL sdlClip::init(CliprdrClientContext* clip)
113 _ctx->MonitorReady = sdlClip::MonitorReady;
114 _ctx->ServerCapabilities = sdlClip::ReceiveServerCapabilities;
115 _ctx->ServerFormatList = sdlClip::ReceiveServerFormatList;
116 _ctx->ServerFormatListResponse = sdlClip::ReceiveFormatListResponse;
117 _ctx->ServerFormatDataRequest = sdlClip::ReceiveFormatDataRequest;
118 _ctx->ServerFormatDataResponse = sdlClip::ReceiveFormatDataResponse;
120 return cliprdr_file_context_init(_file, _ctx);
123 BOOL sdlClip::uninit(CliprdrClientContext* clip)
126 if (!cliprdr_file_context_uninit(_file, _ctx))
129 clip->custom =
nullptr;
133 bool sdlClip::handle_update(
const SDL_ClipboardEvent& ev)
135 if (!_ctx || !_sync || ev.owner)
138 clearServerFormats();
140 std::string mime_uri_list =
"text/uri-list";
141 std::string mime_html =
"text/html";
143 std::vector<std::string> mime_bitmap = {
"image/bmp",
"image/x-bmp",
"image/x-MS-bmp",
144 "image/x-win-bitmap" };
145 std::string mime_webp =
"image/webp";
146 std::string mime_png =
"image/png";
147 std::string mime_jpeg =
"image/jpeg";
148 std::string mime_tiff =
"image/tiff";
149 std::vector<std::string> mime_images = { mime_webp, mime_png, mime_jpeg, mime_tiff };
151 std::vector<std::string> clientFormatNames;
152 std::vector<CLIPRDR_FORMAT> clientFormats;
154 size_t nformats = ev.n_mime_types;
155 const char** clipboard_mime_formats = ev.mime_types;
157 WLog_Print(_log, WLOG_TRACE,
"SDL has %d formats", nformats);
159 bool textPushed =
false;
160 bool imgPushed =
false;
162 for (
size_t i = 0; i < nformats; i++)
164 std::string local_mime = clipboard_mime_formats[i];
165 WLog_Print(_log, WLOG_TRACE,
" - %s", local_mime.c_str());
167 if (std::find(s_mime_text.begin(), s_mime_text.end(), local_mime) != s_mime_text.end())
172 clientFormats.push_back({ CF_TEXT,
nullptr });
173 clientFormats.push_back({ CF_OEMTEXT,
nullptr });
174 clientFormats.push_back({ CF_UNICODETEXT,
nullptr });
178 else if (local_mime == mime_html)
180 clientFormatNames.emplace_back(s_type_HtmlFormat);
181 else if (std::find(mime_bitmap.begin(), mime_bitmap.end(), local_mime) != mime_bitmap.end())
186 clientFormats.push_back({ CF_DIB,
nullptr });
187 clientFormats.push_back({ CF_DIBV5,
nullptr });
189 for (
auto& bmp : mime_bitmap)
190 clientFormatNames.push_back(bmp);
192 for (
auto& img : mime_images)
193 clientFormatNames.push_back(img);
200 for (
auto& name : clientFormatNames)
202 clientFormats.push_back({ ClipboardRegisterFormat(_system, name.c_str()), name.data() });
205 std::sort(clientFormats.begin(), clientFormats.end(),
206 [](
const auto& a,
const auto& b) { return a < b; });
207 auto u = std::unique(clientFormats.begin(), clientFormats.end());
208 clientFormats.erase(u, clientFormats.end());
211 .common = { .msgType = CB_FORMAT_LIST, .msgFlags = 0, .dataLen = 0 },
212 .numFormats =
static_cast<UINT32
>(clientFormats.size()),
213 .formats = clientFormats.data(),
216 WLog_Print(_log, WLOG_TRACE,
217 "-------------- client format list [%" PRIu32
"] ------------------",
218 formatList.numFormats);
219 for (UINT32 x = 0; x < formatList.numFormats; x++)
221 auto format = &formatList.formats[x];
222 WLog_Print(_log, WLOG_TRACE,
"client announces %" PRIu32
" [%s][%s]", format->formatId,
223 ClipboardGetFormatIdString(format->formatId), format->formatName);
227 WINPR_ASSERT(_ctx->ClientFormatList);
228 return _ctx->ClientFormatList(_ctx, &formatList) == CHANNEL_RC_OK;
233 WINPR_UNUSED(monitorReady);
234 WINPR_ASSERT(context);
235 WINPR_ASSERT(monitorReady);
237 auto clipboard =
static_cast<sdlClip*
>(
238 cliprdr_file_context_get_context(
static_cast<CliprdrFileContext*
>(context->custom)));
239 WINPR_ASSERT(clipboard);
241 auto ret = clipboard->SendClientCapabilities();
242 if (ret != CHANNEL_RC_OK)
245 clipboard->_sync =
true;
246 SDL_ClipboardEvent ev = { SDL_EVENT_CLIPBOARD_UPDATE, 0, 0,
false, 0,
nullptr };
247 if (!clipboard->handle_update(ev))
248 return ERROR_INTERNAL_ERROR;
250 return CHANNEL_RC_OK;
253 UINT sdlClip::SendClientCapabilities()
256 .capabilitySetType = CB_CAPSTYPE_GENERAL,
257 .capabilitySetLength = 12,
258 .version = CB_CAPS_VERSION_2,
259 .generalFlags = CB_USE_LONG_FORMAT_NAMES | cliprdr_file_context_current_flags(_file)
262 .common = { .msgType = CB_TYPE_NONE, .msgFlags = 0, .dataLen = 0 },
263 .cCapabilitiesSets = 1,
268 WINPR_ASSERT(_ctx->ClientCapabilities);
269 return _ctx->ClientCapabilities(_ctx, &capabilities);
272 void sdlClip::clearServerFormats()
274 _serverFormats.clear();
275 cliprdr_file_context_clear(_file);
278 UINT sdlClip::SendFormatListResponse(BOOL status)
281 .common = { .msgType = CB_FORMAT_LIST_RESPONSE,
282 .msgFlags =
static_cast<UINT16
>(status ? CB_RESPONSE_OK : CB_RESPONSE_FAIL),
286 WINPR_ASSERT(_ctx->ClientFormatListResponse);
287 return _ctx->ClientFormatListResponse(_ctx, &formatListResponse);
290 UINT sdlClip::SendDataResponse(
const BYTE* data,
size_t size)
294 if (size > UINT32_MAX)
295 return ERROR_INVALID_PARAMETER;
297 response.common.msgFlags = (data) ? CB_RESPONSE_OK : CB_RESPONSE_FAIL;
298 response.common.dataLen =
static_cast<UINT32
>(size);
299 response.requestedFormatData = data;
302 WINPR_ASSERT(_ctx->ClientFormatDataResponse);
303 return _ctx->ClientFormatDataResponse(_ctx, &response);
306 UINT sdlClip::SendDataRequest(uint32_t formatID,
const std::string& mime)
309 .common = { .msgType = CB_TYPE_NONE, .msgFlags = 0, .dataLen = 0 },
310 .requestedFormatId = formatID
313 _request_queue.push({ formatID, mime });
316 WINPR_ASSERT(_ctx->ClientFormatDataRequest);
317 UINT ret = _ctx->ClientFormatDataRequest(_ctx, &request);
318 if (ret != CHANNEL_RC_OK)
320 WLog_Print(_log, WLOG_ERROR,
"error sending ClientFormatDataRequest, cancelling request");
321 _request_queue.pop();
327 std::string sdlClip::getServerFormat(uint32_t
id)
329 for (
auto& fmt : _serverFormats)
331 if (fmt.formatId() ==
id)
333 if (fmt.formatName())
334 return fmt.formatName();
342 uint32_t sdlClip::serverIdForMime(
const std::string& mime)
344 std::string cmp = mime;
345 if (mime_is_html(mime))
346 cmp = s_type_HtmlFormat;
347 if (mime_is_file(mime))
348 cmp = s_type_FileGroupDescriptorW;
350 for (
auto& format : _serverFormats)
352 if (!format.formatName())
354 if (cmp == format.formatName())
355 return format.formatId();
358 if (mime_is_image(mime))
360 if (mime_is_text(mime))
361 return CF_UNICODETEXT;
366 UINT sdlClip::ReceiveServerCapabilities(CliprdrClientContext* context,
369 WINPR_ASSERT(context);
370 WINPR_ASSERT(capabilities);
372 auto capsPtr =
reinterpret_cast<const BYTE*
>(capabilities->capabilitySets);
373 WINPR_ASSERT(capsPtr);
375 auto clipboard =
static_cast<sdlClip*
>(
376 cliprdr_file_context_get_context(
static_cast<CliprdrFileContext*
>(context->custom)));
377 WINPR_ASSERT(clipboard);
379 if (!cliprdr_file_context_remote_set_flags(clipboard->_file, 0))
380 return ERROR_INTERNAL_ERROR;
382 for (UINT32 i = 0; i < capabilities->cCapabilitiesSets; i++)
386 if (caps->capabilitySetType == CB_CAPSTYPE_GENERAL)
390 if (!cliprdr_file_context_remote_set_flags(clipboard->_file, generalCaps->generalFlags))
391 return ERROR_INTERNAL_ERROR;
394 capsPtr += caps->capabilitySetLength;
397 return CHANNEL_RC_OK;
400 UINT sdlClip::ReceiveServerFormatList(CliprdrClientContext* context,
408 if (!context || !context->custom)
409 return ERROR_INVALID_PARAMETER;
411 auto clipboard =
static_cast<sdlClip*
>(
412 cliprdr_file_context_get_context(
static_cast<CliprdrFileContext*
>(context->custom)));
413 WINPR_ASSERT(clipboard);
415 clipboard->clearServerFormats();
417 for (UINT32 i = 0; i < formatList->numFormats; i++)
421 clipboard->_serverFormats.emplace_back(format->formatId, format->formatName);
423 if (format->formatName)
425 if (strcmp(format->formatName, s_type_HtmlFormat) == 0)
430 else if (strcmp(format->formatName, s_type_FileGroupDescriptorW) == 0)
438 switch (format->formatId)
456 std::vector<const char*> mimetypes;
459 mimetypes.insert(mimetypes.end(), s_mime_text.begin(), s_mime_text.end());
463 mimetypes.insert(mimetypes.end(), s_mime_bitmap.begin(), s_mime_bitmap.end());
464 mimetypes.insert(mimetypes.end(), s_mime_image.begin(), s_mime_image.end());
468 mimetypes.push_back(s_mime_html);
472 mimetypes.push_back(s_mime_uri_list);
473 mimetypes.push_back(s_mime_gnome_copied_files);
474 mimetypes.push_back(s_mime_mate_copied_files);
477 const bool rc = SDL_SetClipboardData(sdlClip::ClipDataCb, sdlClip::ClipCleanCb, clipboard,
478 mimetypes.data(), mimetypes.size());
479 return clipboard->SendFormatListResponse(rc);
482 UINT sdlClip::ReceiveFormatListResponse(CliprdrClientContext* context,
485 WINPR_ASSERT(context);
486 WINPR_ASSERT(formatListResponse);
488 if (formatListResponse->common.msgFlags & CB_RESPONSE_FAIL)
489 WLog_WARN(TAG,
"format list update failed");
490 return CHANNEL_RC_OK;
493 std::shared_ptr<BYTE> sdlClip::ReceiveFormatDataRequestHandle(
496 const char* mime =
nullptr;
501 std::shared_ptr<BYTE> data;
503 WINPR_ASSERT(clipboard);
504 WINPR_ASSERT(formatDataRequest);
507 auto localFormatId = formatId = formatDataRequest->requestedFormatId;
509 ClipboardLockGuard give_me_a_name(clipboard->_system);
510 std::lock_guard<CriticalSection> lock(clipboard->_lock);
512 const UINT32 fileFormatId =
513 ClipboardGetFormatId(clipboard->_system, s_type_FileGroupDescriptorW);
514 const UINT32 htmlFormatId = ClipboardGetFormatId(clipboard->_system, s_type_HtmlFormat);
521 localFormatId = ClipboardGetFormatId(clipboard->_system, mime_text_plain);
522 mime = mime_text_utf8;
527 mime = s_mime_bitmap[0];
535 if (formatId == fileFormatId)
537 localFormatId = ClipboardGetFormatId(clipboard->_system, s_mime_uri_list);
538 mime = s_mime_uri_list;
540 else if (formatId == htmlFormatId)
542 localFormatId = ClipboardGetFormatId(clipboard->_system, s_mime_html);
551 auto sdldata = std::shared_ptr<void>(SDL_GetClipboardData(mime, &size), SDL_free);
555 if (fileFormatId == formatId)
557 auto bdata =
static_cast<const char*
>(sdldata.get());
558 if (!cliprdr_file_context_update_client_data(clipboard->_file, bdata, size))
562 res = ClipboardSetData(clipboard->_system, localFormatId, sdldata.get(),
563 static_cast<uint32_t
>(size));
570 auto ptr =
static_cast<BYTE*
>(ClipboardGetData(clipboard->_system, formatId, &ptrlen));
571 data = std::shared_ptr<BYTE>(ptr, free);
576 if (fileFormatId == formatId)
578 BYTE* ddata =
nullptr;
580 const UINT32 flags = cliprdr_file_context_remote_get_flags(clipboard->_file);
581 const UINT32 error = cliprdr_serialize_file_list_ex(
585 auto tmp = std::shared_ptr<BYTE>(ddata, free);
597 UINT sdlClip::ReceiveFormatDataRequest(CliprdrClientContext* context,
600 WINPR_ASSERT(context);
601 WINPR_ASSERT(formatDataRequest);
603 auto clipboard =
static_cast<sdlClip*
>(
604 cliprdr_file_context_get_context(
static_cast<CliprdrFileContext*
>(context->custom)));
605 WINPR_ASSERT(clipboard);
608 auto rc = ReceiveFormatDataRequestHandle(clipboard, formatDataRequest, len);
609 return clipboard->SendDataResponse(rc.get(), len);
612 UINT sdlClip::ReceiveFormatDataResponse(CliprdrClientContext* context,
615 WINPR_ASSERT(context);
616 WINPR_ASSERT(formatDataResponse);
618 const UINT32 size = formatDataResponse->common.dataLen;
619 const BYTE* data = formatDataResponse->requestedFormatData;
621 auto clipboard =
static_cast<sdlClip*
>(
622 cliprdr_file_context_get_context(
static_cast<CliprdrFileContext*
>(context->custom)));
623 WINPR_ASSERT(clipboard);
625 ClipboardLockGuard give_me_a_name(clipboard->_system);
626 std::lock_guard<CriticalSection> lock(clipboard->_lock);
627 if (clipboard->_request_queue.empty())
629 WLog_Print(clipboard->_log, WLOG_ERROR,
"no pending format request");
630 return ERROR_INTERNAL_ERROR;
635 UINT32 srcFormatId = 0;
636 auto& request = clipboard->_request_queue.front();
637 bool success = (formatDataResponse->common.msgFlags & CB_RESPONSE_OK) &&
638 !(formatDataResponse->common.msgFlags & CB_RESPONSE_FAIL);
639 request.setSuccess(success);
643 WLog_Print(clipboard->_log, WLOG_WARN,
644 "clipboard data request for format %" PRIu32
" [%s], mime %s failed",
645 request.format(), request.formatstr().c_str(), request.mime().c_str());
649 switch (request.format())
654 srcFormatId = request.format();
659 srcFormatId = request.format();
664 auto name = clipboard->getServerFormat(request.format());
667 if (name == s_type_FileGroupDescriptorW)
670 ClipboardGetFormatId(clipboard->_system, s_type_FileGroupDescriptorW);
672 if (!cliprdr_file_context_update_server_data(
673 clipboard->_file, clipboard->_system, data, size))
674 return ERROR_INTERNAL_ERROR;
676 else if (name == s_type_HtmlFormat)
678 srcFormatId = ClipboardGetFormatId(clipboard->_system, s_type_HtmlFormat);
685 if (!ClipboardSetData(clipboard->_system, srcFormatId, data, size))
687 WLog_Print(clipboard->_log, WLOG_ERROR,
"error when setting clipboard data");
688 return ERROR_INTERNAL_ERROR;
692 if (!SetEvent(clipboard->_event))
693 return ERROR_INTERNAL_ERROR;
695 return CHANNEL_RC_OK;
698 const void* sdlClip::ClipDataCb(
void* userdata,
const char* mime_type,
size_t* size)
700 auto clip =
static_cast<sdlClip*
>(userdata);
703 WINPR_ASSERT(mime_type);
708 if (mime_is_text(mime_type))
709 mime_type =
"text/plain";
712 ClipboardLockGuard give_me_a_name(clip->_system);
713 std::lock_guard<CriticalSection> lock(clip->_lock);
714 auto cache = clip->_cache_data.find(mime_type);
715 if (cache != clip->_cache_data.end())
717 *size = cache->second.size;
718 return cache->second.ptr.get();
721 uint32_t formatID = clip->serverIdForMime(mime_type);
722 if (clip->SendDataRequest(formatID, mime_type))
727 HANDLE hdl[2] = { freerdp_abort_event(clip->_sdl->context()), clip->_event };
733 sdl->critical.unlock();
735 DWORD status = WaitForMultipleObjects(ARRAYSIZE(hdl), hdl, FALSE, 10 * 1000);
737 sdl->critical.lock();
739 if (status != WAIT_OBJECT_0 + 1)
741 std::lock_guard<CriticalSection> lock(clip->_lock);
742 clip->_request_queue.pop();
744 if (status == WAIT_TIMEOUT)
745 WLog_Print(clip->_log, WLOG_ERROR,
746 "no reply in 10 seconds, returning empty content");
753 ClipboardLockGuard give_me_a_name(clip->_system);
754 std::lock_guard<CriticalSection> lock(clip->_lock);
755 auto request = clip->_request_queue.front();
756 clip->_request_queue.pop();
758 if (!clip->_request_queue.size())
759 (void)ResetEvent(clip->_event);
761 if (request.success())
763 auto formatID = ClipboardRegisterFormat(clip->_system, mime_type);
764 auto data = ClipboardGetData(clip->_system, formatID, &len);
767 WLog_Print(clip->_log, WLOG_ERROR,
"error retrieving clipboard data");
771 auto ptr = std::shared_ptr<void>(data, free);
772 clip->_cache_data.insert({ mime_type, { len, ptr } });
781 void sdlClip::ClipCleanCb(
void* userdata)
783 auto clip =
static_cast<sdlClip*
>(userdata);
785 ClipboardLockGuard give_me_a_name(clip->_system);
786 std::lock_guard<CriticalSection> lock(clip->_lock);
787 ClipboardEmpty(clip->_system);
788 clip->_cache_data.clear();
791 bool sdlClip::mime_is_file(
const std::string& mime)
793 if (strncmp(s_mime_uri_list, mime.c_str(),
sizeof(s_mime_uri_list)) == 0)
795 if (strncmp(s_mime_gnome_copied_files, mime.c_str(),
sizeof(s_mime_gnome_copied_files)) == 0)
797 if (strncmp(s_mime_mate_copied_files, mime.c_str(),
sizeof(s_mime_mate_copied_files)) == 0)
802 bool sdlClip::mime_is_text(
const std::string& mime)
804 for (
const auto& tmime : s_mime_text)
806 assert(tmime !=
nullptr);
814 bool sdlClip::mime_is_image(
const std::string& mime)
816 for (
const auto& imime : s_mime_image)
818 assert(imime !=
nullptr);
826 bool sdlClip::mime_is_html(
const std::string& mime)
828 return mime.compare(s_mime_html) == 0;
831 ClipRequest::ClipRequest(UINT32 format,
const std::string& mime)
832 : _format(format), _mime(mime), _success(false)
836 uint32_t ClipRequest::format()
const
841 std::string ClipRequest::formatstr()
const
843 return ClipboardGetFormatIdString(_format);
846 std::string ClipRequest::mime()
const
851 bool ClipRequest::success()
const
856 void ClipRequest::setSuccess(
bool status)
object that handles clipboard context for the SDL3 client