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()
39 static std::vector<const char*> values;
42 values = std::vector<const char*>(
43 { mime_text_plain, mime_text_utf8,
"UTF8_STRING",
"COMPOUND_TEXT",
"TEXT",
"STRING" });
48 static const char s_mime_png[] =
"image/png";
49 static const char s_mime_webp[] =
"image/webp";
50 static const char s_mime_jpg[] =
"image/jpeg";
51 static const char s_mime_tiff[] =
"image/tiff";
52 static const char s_mime_uri_list[] =
"text/uri-list";
53 static const char s_mime_html[] =
"text/html";
55 #define BMP_MIME_LIST "image/bmp", "image/x-bmp", "image/x-MS-bmp", "image/x-win-bitmap"
57 static const std::vector<const char*>& s_mime_bitmap()
59 static std::vector<const char*> values;
62 values = std::vector<const char*>({ BMP_MIME_LIST });
67 static const std::vector<const char*>& s_mime_image()
69 static std::vector<const char*> values;
72 values = std::vector<const char*>(
73 { s_mime_png, s_mime_webp, s_mime_jpg, s_mime_tiff, BMP_MIME_LIST });
78 static const char s_mime_gnome_copied_files[] =
"x-special/gnome-copied-files";
79 static const char s_mime_mate_copied_files[] =
"x-special/mate-copied-files";
81 static const char* s_type_HtmlFormat =
"HTML Format";
82 static const char* s_type_FileGroupDescriptorW =
"FileGroupDescriptorW";
84 class ClipboardLockGuard
87 explicit ClipboardLockGuard(wClipboard* clipboard) : _clipboard(clipboard)
89 ClipboardLock(_clipboard);
91 ClipboardLockGuard(
const ClipboardLockGuard& other) =
delete;
92 ClipboardLockGuard(ClipboardLockGuard&& other) =
delete;
94 ClipboardLockGuard& operator=(
const ClipboardLockGuard& rhs) =
delete;
95 ClipboardLockGuard& operator=(ClipboardLockGuard&& rhs) =
delete;
99 ClipboardUnlock(_clipboard);
103 wClipboard* _clipboard;
108 return (lhs.formatId < rhs.formatId);
112 return (lhs.formatId > rhs.formatId);
116 return (lhs.formatId == rhs.formatId);
120 : _sdl(sdl), _file(cliprdr_file_context_new(this)), _log(WLog_Get(TAG)),
121 _system(ClipboardCreate()), _event(CreateEventA(nullptr, TRUE, FALSE, nullptr))
128 cliprdr_file_context_free(_file);
129 ClipboardDestroy(_system);
130 (void)CloseHandle(_event);
133 BOOL sdlClip::init(CliprdrClientContext* clip)
138 _ctx->MonitorReady = sdlClip::MonitorReady;
139 _ctx->ServerCapabilities = sdlClip::ReceiveServerCapabilities;
140 _ctx->ServerFormatList = sdlClip::ReceiveServerFormatList;
141 _ctx->ServerFormatListResponse = sdlClip::ReceiveFormatListResponse;
142 _ctx->ServerFormatDataRequest = sdlClip::ReceiveFormatDataRequest;
143 _ctx->ServerFormatDataResponse = sdlClip::ReceiveFormatDataResponse;
145 return cliprdr_file_context_init(_file, _ctx);
148 BOOL sdlClip::uninit(CliprdrClientContext* clip)
151 if (!cliprdr_file_context_uninit(_file, _ctx))
154 clip->custom =
nullptr;
158 bool sdlClip::handle_update(
const SDL_ClipboardEvent& ev)
160 if (!_ctx || !_sync || ev.owner)
163 clearServerFormats();
165 std::string mime_uri_list =
"text/uri-list";
166 std::string mime_html =
"text/html";
168 std::vector<std::string> mime_bitmap = {
"image/bmp",
"image/x-bmp",
"image/x-MS-bmp",
169 "image/x-win-bitmap" };
170 std::string mime_webp =
"image/webp";
171 std::string mime_png =
"image/png";
172 std::string mime_jpeg =
"image/jpeg";
173 std::string mime_tiff =
"image/tiff";
174 std::vector<std::string> mime_images = { mime_webp, mime_png, mime_jpeg, mime_tiff };
176 std::vector<std::string> clientFormatNames;
177 std::vector<CLIPRDR_FORMAT> clientFormats;
179 size_t nformats = ev.n_mime_types;
180 const char** clipboard_mime_formats = ev.mime_types;
182 WLog_Print(_log, WLOG_TRACE,
"SDL has %d formats", nformats);
184 bool textPushed =
false;
185 bool imgPushed =
false;
187 for (
size_t i = 0; i < nformats; i++)
189 std::string local_mime = clipboard_mime_formats[i];
190 WLog_Print(_log, WLOG_TRACE,
" - %s", local_mime.c_str());
192 if (std::find(s_mime_text().begin(), s_mime_text().end(), local_mime) !=
198 clientFormats.push_back({ CF_TEXT,
nullptr });
199 clientFormats.push_back({ CF_OEMTEXT,
nullptr });
200 clientFormats.push_back({ CF_UNICODETEXT,
nullptr });
204 else if (local_mime == mime_html)
206 clientFormatNames.emplace_back(s_type_HtmlFormat);
207 else if (std::find(mime_bitmap.begin(), mime_bitmap.end(), local_mime) != mime_bitmap.end())
212 clientFormats.push_back({ CF_DIB,
nullptr });
213 clientFormats.push_back({ CF_DIBV5,
nullptr });
215 for (
auto& bmp : mime_bitmap)
216 clientFormatNames.push_back(bmp);
218 for (
auto& img : mime_images)
219 clientFormatNames.push_back(img);
226 for (
auto& name : clientFormatNames)
228 clientFormats.push_back({ ClipboardRegisterFormat(_system, name.c_str()), name.data() });
231 std::sort(clientFormats.begin(), clientFormats.end(),
232 [](
const auto& a,
const auto& b) { return a < b; });
233 auto u = std::unique(clientFormats.begin(), clientFormats.end());
234 clientFormats.erase(u, clientFormats.end());
237 .common = { .msgType = CB_FORMAT_LIST, .msgFlags = 0, .dataLen = 0 },
238 .numFormats =
static_cast<UINT32
>(clientFormats.size()),
239 .formats = clientFormats.data(),
242 WLog_Print(_log, WLOG_TRACE,
243 "-------------- client format list [%" PRIu32
"] ------------------",
244 formatList.numFormats);
245 for (UINT32 x = 0; x < formatList.numFormats; x++)
247 auto format = &formatList.formats[x];
248 WLog_Print(_log, WLOG_TRACE,
"client announces %" PRIu32
" [%s][%s]", format->formatId,
249 ClipboardGetFormatIdString(format->formatId), format->formatName);
253 WINPR_ASSERT(_ctx->ClientFormatList);
254 return _ctx->ClientFormatList(_ctx, &formatList) == CHANNEL_RC_OK;
259 WINPR_UNUSED(monitorReady);
260 WINPR_ASSERT(context);
261 WINPR_ASSERT(monitorReady);
263 auto clipboard =
static_cast<sdlClip*
>(
264 cliprdr_file_context_get_context(
static_cast<CliprdrFileContext*
>(context->custom)));
265 WINPR_ASSERT(clipboard);
267 auto ret = clipboard->SendClientCapabilities();
268 if (ret != CHANNEL_RC_OK)
271 clipboard->_sync =
true;
272 SDL_ClipboardEvent ev = { SDL_EVENT_CLIPBOARD_UPDATE, 0, 0,
false, 0,
nullptr };
273 if (!clipboard->handle_update(ev))
274 return ERROR_INTERNAL_ERROR;
276 return CHANNEL_RC_OK;
279 UINT sdlClip::SendClientCapabilities()
282 .capabilitySetType = CB_CAPSTYPE_GENERAL,
283 .capabilitySetLength = 12,
284 .version = CB_CAPS_VERSION_2,
285 .generalFlags = CB_USE_LONG_FORMAT_NAMES | cliprdr_file_context_current_flags(_file)
288 .common = { .msgType = CB_TYPE_NONE, .msgFlags = 0, .dataLen = 0 },
289 .cCapabilitiesSets = 1,
294 WINPR_ASSERT(_ctx->ClientCapabilities);
295 return _ctx->ClientCapabilities(_ctx, &capabilities);
298 void sdlClip::clearServerFormats()
300 _serverFormats.clear();
301 cliprdr_file_context_clear(_file);
304 UINT sdlClip::SendFormatListResponse(BOOL status)
307 .common = { .msgType = CB_FORMAT_LIST_RESPONSE,
308 .msgFlags =
static_cast<UINT16
>(status ? CB_RESPONSE_OK : CB_RESPONSE_FAIL),
312 WINPR_ASSERT(_ctx->ClientFormatListResponse);
313 return _ctx->ClientFormatListResponse(_ctx, &formatListResponse);
316 UINT sdlClip::SendDataResponse(
const BYTE* data,
size_t size)
320 if (size > UINT32_MAX)
321 return ERROR_INVALID_PARAMETER;
323 response.common.msgFlags = (data) ? CB_RESPONSE_OK : CB_RESPONSE_FAIL;
324 response.common.dataLen =
static_cast<UINT32
>(size);
325 response.requestedFormatData = data;
328 WINPR_ASSERT(_ctx->ClientFormatDataResponse);
329 return _ctx->ClientFormatDataResponse(_ctx, &response);
332 UINT sdlClip::SendDataRequest(uint32_t formatID,
const std::string& mime)
335 .common = { .msgType = CB_TYPE_NONE, .msgFlags = 0, .dataLen = 0 },
336 .requestedFormatId = formatID
339 _request_queue.push({ formatID, mime });
342 WINPR_ASSERT(_ctx->ClientFormatDataRequest);
343 UINT ret = _ctx->ClientFormatDataRequest(_ctx, &request);
344 if (ret != CHANNEL_RC_OK)
346 WLog_Print(_log, WLOG_ERROR,
"error sending ClientFormatDataRequest, cancelling request");
347 _request_queue.pop();
353 std::string sdlClip::getServerFormat(uint32_t
id)
355 for (
auto& fmt : _serverFormats)
357 if (fmt.formatId() ==
id)
359 if (fmt.formatName())
360 return fmt.formatName();
368 uint32_t sdlClip::serverIdForMime(
const std::string& mime)
370 std::string cmp = mime;
371 if (mime_is_html(mime))
372 cmp = s_type_HtmlFormat;
373 if (mime_is_file(mime))
374 cmp = s_type_FileGroupDescriptorW;
376 for (
auto& format : _serverFormats)
378 if (!format.formatName())
380 if (cmp == format.formatName())
381 return format.formatId();
384 if (mime_is_image(mime))
386 if (mime_is_text(mime))
387 return CF_UNICODETEXT;
392 UINT sdlClip::ReceiveServerCapabilities(CliprdrClientContext* context,
395 WINPR_ASSERT(context);
396 WINPR_ASSERT(capabilities);
398 auto capsPtr =
reinterpret_cast<const BYTE*
>(capabilities->capabilitySets);
399 WINPR_ASSERT(capsPtr);
401 auto clipboard =
static_cast<sdlClip*
>(
402 cliprdr_file_context_get_context(
static_cast<CliprdrFileContext*
>(context->custom)));
403 WINPR_ASSERT(clipboard);
405 if (!cliprdr_file_context_remote_set_flags(clipboard->_file, 0))
406 return ERROR_INTERNAL_ERROR;
408 for (UINT32 i = 0; i < capabilities->cCapabilitiesSets; i++)
412 if (caps->capabilitySetType == CB_CAPSTYPE_GENERAL)
416 if (!cliprdr_file_context_remote_set_flags(clipboard->_file, generalCaps->generalFlags))
417 return ERROR_INTERNAL_ERROR;
420 capsPtr += caps->capabilitySetLength;
423 return CHANNEL_RC_OK;
426 UINT sdlClip::ReceiveServerFormatList(CliprdrClientContext* context,
434 if (!context || !context->custom)
435 return ERROR_INVALID_PARAMETER;
437 auto clipboard =
static_cast<sdlClip*
>(
438 cliprdr_file_context_get_context(
static_cast<CliprdrFileContext*
>(context->custom)));
439 WINPR_ASSERT(clipboard);
441 clipboard->clearServerFormats();
443 for (UINT32 i = 0; i < formatList->numFormats; i++)
447 clipboard->_serverFormats.emplace_back(format->formatId, format->formatName);
449 if (format->formatName)
451 if (strcmp(format->formatName, s_type_HtmlFormat) == 0)
456 else if (strcmp(format->formatName, s_type_FileGroupDescriptorW) == 0)
464 switch (format->formatId)
482 std::vector<const char*> mimetypes;
485 mimetypes.insert(mimetypes.end(), s_mime_text().begin(), s_mime_text().end());
489 mimetypes.insert(mimetypes.end(), s_mime_bitmap().begin(), s_mime_bitmap().end());
490 mimetypes.insert(mimetypes.end(), s_mime_image().begin(), s_mime_image().end());
494 mimetypes.push_back(s_mime_html);
498 mimetypes.push_back(s_mime_uri_list);
499 mimetypes.push_back(s_mime_gnome_copied_files);
500 mimetypes.push_back(s_mime_mate_copied_files);
503 const bool rc = SDL_SetClipboardData(sdlClip::ClipDataCb, sdlClip::ClipCleanCb, clipboard,
504 mimetypes.data(), mimetypes.size());
505 return clipboard->SendFormatListResponse(rc);
508 UINT sdlClip::ReceiveFormatListResponse(CliprdrClientContext* context,
511 WINPR_ASSERT(context);
512 WINPR_ASSERT(formatListResponse);
514 if (formatListResponse->common.msgFlags & CB_RESPONSE_FAIL)
515 WLog_WARN(TAG,
"format list update failed");
516 return CHANNEL_RC_OK;
519 std::shared_ptr<BYTE> sdlClip::ReceiveFormatDataRequestHandle(
522 const char* mime =
nullptr;
527 std::shared_ptr<BYTE> data;
529 WINPR_ASSERT(clipboard);
530 WINPR_ASSERT(formatDataRequest);
533 auto localFormatId = formatId = formatDataRequest->requestedFormatId;
535 ClipboardLockGuard give_me_a_name(clipboard->_system);
536 std::lock_guard<CriticalSection> lock(clipboard->_lock);
538 const UINT32 fileFormatId =
539 ClipboardGetFormatId(clipboard->_system, s_type_FileGroupDescriptorW);
540 const UINT32 htmlFormatId = ClipboardGetFormatId(clipboard->_system, s_type_HtmlFormat);
547 localFormatId = ClipboardGetFormatId(clipboard->_system, mime_text_plain);
548 mime = mime_text_utf8;
553 mime = s_mime_bitmap()[0];
561 if (formatId == fileFormatId)
563 localFormatId = ClipboardGetFormatId(clipboard->_system, s_mime_uri_list);
564 mime = s_mime_uri_list;
566 else if (formatId == htmlFormatId)
568 localFormatId = ClipboardGetFormatId(clipboard->_system, s_mime_html);
577 auto sdldata = std::shared_ptr<void>(SDL_GetClipboardData(mime, &size), SDL_free);
581 if (fileFormatId == formatId)
583 auto bdata =
static_cast<const char*
>(sdldata.get());
584 if (!cliprdr_file_context_update_client_data(clipboard->_file, bdata, size))
588 res = ClipboardSetData(clipboard->_system, localFormatId, sdldata.get(),
589 static_cast<uint32_t
>(size));
596 auto ptr =
static_cast<BYTE*
>(ClipboardGetData(clipboard->_system, formatId, &ptrlen));
597 data = std::shared_ptr<BYTE>(ptr, free);
602 if (fileFormatId == formatId)
604 BYTE* ddata =
nullptr;
606 const UINT32 flags = cliprdr_file_context_remote_get_flags(clipboard->_file);
607 const UINT32 error = cliprdr_serialize_file_list_ex(
611 auto tmp = std::shared_ptr<BYTE>(ddata, free);
623 UINT sdlClip::ReceiveFormatDataRequest(CliprdrClientContext* context,
626 WINPR_ASSERT(context);
627 WINPR_ASSERT(formatDataRequest);
629 auto clipboard =
static_cast<sdlClip*
>(
630 cliprdr_file_context_get_context(
static_cast<CliprdrFileContext*
>(context->custom)));
631 WINPR_ASSERT(clipboard);
634 auto rc = ReceiveFormatDataRequestHandle(clipboard, formatDataRequest, len);
635 return clipboard->SendDataResponse(rc.get(), len);
638 UINT sdlClip::ReceiveFormatDataResponse(CliprdrClientContext* context,
641 WINPR_ASSERT(context);
642 WINPR_ASSERT(formatDataResponse);
644 const UINT32 size = formatDataResponse->common.dataLen;
645 const BYTE* data = formatDataResponse->requestedFormatData;
647 auto clipboard =
static_cast<sdlClip*
>(
648 cliprdr_file_context_get_context(
static_cast<CliprdrFileContext*
>(context->custom)));
649 WINPR_ASSERT(clipboard);
651 ClipboardLockGuard give_me_a_name(clipboard->_system);
652 std::lock_guard<CriticalSection> lock(clipboard->_lock);
653 if (clipboard->_request_queue.empty())
655 WLog_Print(clipboard->_log, WLOG_ERROR,
"no pending format request");
656 return ERROR_INTERNAL_ERROR;
661 UINT32 srcFormatId = 0;
662 auto& request = clipboard->_request_queue.front();
663 bool success = (formatDataResponse->common.msgFlags & CB_RESPONSE_OK) &&
664 !(formatDataResponse->common.msgFlags & CB_RESPONSE_FAIL);
665 request.setSuccess(success);
669 WLog_Print(clipboard->_log, WLOG_WARN,
670 "clipboard data request for format %" PRIu32
" [%s], mime %s failed",
671 request.format(), request.formatstr().c_str(), request.mime().c_str());
675 switch (request.format())
680 srcFormatId = request.format();
685 srcFormatId = request.format();
690 auto name = clipboard->getServerFormat(request.format());
693 if (name == s_type_FileGroupDescriptorW)
696 ClipboardGetFormatId(clipboard->_system, s_type_FileGroupDescriptorW);
698 if (!cliprdr_file_context_update_server_data(
699 clipboard->_file, clipboard->_system, data, size))
700 return ERROR_INTERNAL_ERROR;
702 else if (name == s_type_HtmlFormat)
704 srcFormatId = ClipboardGetFormatId(clipboard->_system, s_type_HtmlFormat);
711 if (!ClipboardSetData(clipboard->_system, srcFormatId, data, size))
713 WLog_Print(clipboard->_log, WLOG_ERROR,
"error when setting clipboard data");
714 return ERROR_INTERNAL_ERROR;
718 if (!SetEvent(clipboard->_event))
719 return ERROR_INTERNAL_ERROR;
721 return CHANNEL_RC_OK;
724 const void* sdlClip::ClipDataCb(
void* userdata,
const char* mime_type,
size_t* size)
726 auto clip =
static_cast<sdlClip*
>(userdata);
729 WINPR_ASSERT(mime_type);
734 if (mime_is_text(mime_type))
735 mime_type =
"text/plain";
738 ClipboardLockGuard give_me_a_name(clip->_system);
739 std::lock_guard<CriticalSection> lock(clip->_lock);
740 auto cache = clip->_cache_data.find(mime_type);
741 if (cache != clip->_cache_data.end())
743 *size = cache->second.size;
744 return cache->second.ptr.get();
747 uint32_t formatID = clip->serverIdForMime(mime_type);
748 if (clip->SendDataRequest(formatID, mime_type))
753 HANDLE hdl[2] = { freerdp_abort_event(clip->_sdl->context()), clip->_event };
759 sdl->critical.unlock();
761 DWORD status = WaitForMultipleObjects(ARRAYSIZE(hdl), hdl, FALSE, 10 * 1000);
763 sdl->critical.lock();
765 if (status != WAIT_OBJECT_0 + 1)
767 std::lock_guard<CriticalSection> lock(clip->_lock);
768 clip->_request_queue.pop();
770 if (status == WAIT_TIMEOUT)
771 WLog_Print(clip->_log, WLOG_ERROR,
772 "no reply in 10 seconds, returning empty content");
779 ClipboardLockGuard give_me_a_name(clip->_system);
780 std::lock_guard<CriticalSection> lock(clip->_lock);
781 auto request = clip->_request_queue.front();
782 clip->_request_queue.pop();
784 if (!clip->_request_queue.size())
785 (void)ResetEvent(clip->_event);
787 if (request.success())
789 auto formatID = ClipboardRegisterFormat(clip->_system, mime_type);
790 auto data = ClipboardGetData(clip->_system, formatID, &len);
793 WLog_Print(clip->_log, WLOG_ERROR,
"error retrieving clipboard data");
797 auto ptr = std::shared_ptr<void>(data, free);
798 clip->_cache_data.insert({ mime_type, { len, ptr } });
807 void sdlClip::ClipCleanCb(
void* userdata)
809 auto clip =
static_cast<sdlClip*
>(userdata);
811 ClipboardLockGuard give_me_a_name(clip->_system);
812 std::lock_guard<CriticalSection> lock(clip->_lock);
813 ClipboardEmpty(clip->_system);
814 clip->_cache_data.clear();
817 bool sdlClip::mime_is_file(
const std::string& mime)
819 if (strncmp(s_mime_uri_list, mime.c_str(),
sizeof(s_mime_uri_list)) == 0)
821 if (strncmp(s_mime_gnome_copied_files, mime.c_str(),
sizeof(s_mime_gnome_copied_files)) == 0)
823 if (strncmp(s_mime_mate_copied_files, mime.c_str(),
sizeof(s_mime_mate_copied_files)) == 0)
828 bool sdlClip::mime_is_text(
const std::string& mime)
830 for (
const auto& tmime : s_mime_text())
832 assert(tmime !=
nullptr);
840 bool sdlClip::mime_is_image(
const std::string& mime)
842 for (
const auto& imime : s_mime_image())
844 assert(imime !=
nullptr);
852 bool sdlClip::mime_is_html(
const std::string& mime)
854 return mime.compare(s_mime_html) == 0;
857 ClipRequest::ClipRequest(UINT32 format,
const std::string& mime)
858 : _format(format), _mime(mime), _success(false)
862 uint32_t ClipRequest::format()
const
867 std::string ClipRequest::formatstr()
const
869 return ClipboardGetFormatIdString(_format);
872 std::string ClipRequest::mime()
const
877 bool ClipRequest::success()
const
882 void ClipRequest::setSuccess(
bool status)
object that handles clipboard context for the SDL3 client