FreeRDP
Loading...
Searching...
No Matches
sdl_clip.cpp
1
21#include <memory>
22#include <string>
23#include <sstream>
24#include <mutex>
25#include <iterator>
26#include <algorithm>
27#include <utility>
28#include <cstring>
29
30#include <winpr/wlog.h>
31#include <winpr/image.h>
32
33#include "sdl_clip.hpp"
34#include "sdl_context.hpp"
35
36#define TAG CLIENT_TAG("sdl.cliprdr")
37
38#define mime_text_plain "text/plain"
39const char mime_text_utf8[] = "text/plain;charset=utf-8";
40
41static const char* first_announcement[] = {
42 "sdl-clipboard-monitor-ready-4b8c7d9b-6d5c-4dcc-add1-21573ca564b0"
43};
44
45[[nodiscard]] static const std::vector<const char*>& s_mime_text()
46{
47 static std::vector<const char*> values;
48 if (values.empty())
49 {
50 values = std::vector<const char*>(
51 { mime_text_utf8, "UTF8_STRING", mime_text_plain, "COMPOUND_TEXT", "TEXT", "STRING" });
52 }
53 return values;
54}
55
56static const char s_mime_jxl[] = "image/jxl";
57static const char s_mime_avif[] = "image/avif";
58static const char s_mime_png[] = "image/png";
59static const char s_mime_webp[] = "image/webp";
60static const char s_mime_jpg[] = "image/jpeg";
61
62static const char s_mime_tiff[] = "image/tiff";
63static const char s_mime_uri_list[] = "text/uri-list";
64/* SDL reports the native pasteboard types on macOS, Finder files have no MIME type */
65static const char s_type_file_url[] = "public.file-url";
66static const char s_mime_html[] = "text/html";
67
68#define BMP_MIME_LIST "image/bmp", "image/x-bmp", "image/x-MS-bmp", "image/x-win-bitmap"
69
70[[nodiscard]] static const std::vector<const char*>& s_mime_bitmap()
71{
72 static std::vector<const char*> values;
73 if (values.empty())
74 {
75 values = std::vector<const char*>({ BMP_MIME_LIST });
76 }
77 return values;
78}
79
80[[nodiscard]] static const std::vector<const char*>& s_mime_image()
81{
82 static std::vector<const char*> values;
83 if (values.empty())
84 {
85 if (winpr_image_format_is_supported(WINPR_IMAGE_WEBP))
86 values.push_back(s_mime_webp);
87
88 if (winpr_image_format_is_supported(WINPR_IMAGE_PNG))
89 values.push_back(s_mime_png);
90
91 if (winpr_image_format_is_supported(WINPR_IMAGE_JPEG))
92 values.push_back(s_mime_jpg);
93
94 auto bmp = std::vector<const char*>({ BMP_MIME_LIST });
95 values.insert(values.end(), bmp.begin(), bmp.end());
96 }
97 return values;
98}
99
100/* Local image types winpr can turn into a CF_DIB, in order of preference: BMP only needs its
101 * file header stripped, the others need the matching winpr image support compiled in. */
102[[nodiscard]] static const std::vector<const char*>& s_mime_dib_sources()
103{
104 static const std::vector<const char*> values = []()
105 {
106 std::vector<const char*> v({ BMP_MIME_LIST });
107 if (winpr_image_format_is_supported(WINPR_IMAGE_PNG))
108 v.push_back(s_mime_png);
109 if (winpr_image_format_is_supported(WINPR_IMAGE_WEBP))
110 v.push_back(s_mime_webp);
111 if (winpr_image_format_is_supported(WINPR_IMAGE_JPEG))
112 v.push_back(s_mime_jpg);
113 return v;
114 }();
115 return values;
116}
117
118/* Local image types an "HTML Format" request may be answered from */
119[[nodiscard]] static const std::vector<const char*>& s_mime_html_image_sources()
120{
121 static const std::vector<const char*> values({ s_mime_jpg, s_mime_png, s_mime_webp, s_mime_avif,
122 s_mime_jxl, s_mime_tiff, BMP_MIME_LIST });
123 return values;
124}
125
126/* The mime types SDL has cached for the current clipboard owner. Unlike SDL_HasClipboardData
127 * this does no clipboard I/O: on X11, SDL_HasClipboardData transfers the data, and calls our
128 * own ClipDataCb when we are the owner. */
129[[nodiscard]] static std::vector<std::string> localMimeTypes()
130{
131 std::vector<std::string> list;
132 size_t count = 0;
133 auto mimes = SDL_GetClipboardMimeTypes(&count);
134 for (size_t x = 0; mimes && (x < count); x++)
135 {
136 if (mimes[x])
137 list.emplace_back(mimes[x]);
138 }
139 SDL_free(static_cast<void*>(mimes));
140 return list;
141}
142
143[[nodiscard]] static bool hasMime(const std::vector<std::string>& list, const char* mime)
144{
145 return std::find(list.begin(), list.end(), mime) != list.end();
146}
147
148[[nodiscard]] static const char* firstOffered(const std::vector<std::string>& offered,
149 const std::vector<const char*>& candidates)
150{
151 for (const auto& m : candidates)
152 {
153 if (hasMime(offered, m))
154 return m;
155 }
156 return nullptr;
157}
158
159/* WinPR synthesizes text with its terminating '\0' included in the size, as the RDP
160 * wire formats require. Local applications expect the bare string, so cut it at the
161 * first '\0' (as the X11 client does). */
162[[nodiscard]] static uint32_t textLength(const void* data, uint32_t size)
163{
164 const auto end = memchr(data, '\0', size);
165 if (!end)
166 return size;
167 return WINPR_ASSERTING_INT_CAST(uint32_t,
168 static_cast<const char*>(end) - static_cast<const char*>(data));
169}
170
171static const char s_mime_gnome_copied_files[] = "x-special/gnome-copied-files";
172static const char s_mime_mate_copied_files[] = "x-special/mate-copied-files";
173
174static const char s_mime_freerdp_update[] = "x-special/freerdp-clipboard-update";
175
176static const char* s_type_HtmlFormat = "HTML Format";
177static const char* s_type_FileGroupDescriptorW = "FileGroupDescriptorW";
178
179class ClipboardLockGuard
180{
181 public:
182 explicit ClipboardLockGuard(wClipboard* clipboard) : _clipboard(clipboard)
183 {
184 ClipboardLock(_clipboard);
185 }
186 ClipboardLockGuard(const ClipboardLockGuard& other) = delete;
187 ClipboardLockGuard(ClipboardLockGuard&& other) = delete;
188
189 ClipboardLockGuard& operator=(const ClipboardLockGuard& rhs) = delete;
190 ClipboardLockGuard& operator=(ClipboardLockGuard&& rhs) = delete;
191
192 ~ClipboardLockGuard()
193 {
194 ClipboardUnlock(_clipboard);
195 }
196
197 private:
198 wClipboard* _clipboard;
199};
200
201static bool operator<(const CLIPRDR_FORMAT& lhs, const CLIPRDR_FORMAT& rhs)
202{
203 return (lhs.formatId < rhs.formatId);
204}
205
206static bool operator==(const CLIPRDR_FORMAT& lhs, const CLIPRDR_FORMAT& rhs)
207{
208 return (lhs.formatId == rhs.formatId);
209}
210
211sdlClip::sdlClip(SdlContext* sdl)
212 : _sdl(sdl), _file(cliprdr_file_context_new(this)), _log(WLog_Get(TAG)),
213 _system(ClipboardCreate()), _event(CreateEventA(nullptr, TRUE, FALSE, nullptr)),
214 _uuid(sdl::utils::generate_uuid_v4())
215{
216 WINPR_ASSERT(sdl);
217
218 std::stringstream ss;
219 ss << s_mime_freerdp_update << "-" << _uuid;
220 _mime_uuid = ss.str();
221
222 std::ignore = cliprdr_file_context_set_locally_available(_file, TRUE);
223}
224
225sdlClip::~sdlClip()
226{
227 cliprdr_file_context_free(_file);
228 ClipboardDestroy(_system);
229 std::ignore = CloseHandle(_event);
230}
231
232bool sdlClip::init(CliprdrClientContext* clip)
233{
234 WINPR_ASSERT(clip);
235 _ctx = clip;
236 clip->custom = this;
237 _ctx->MonitorReady = sdlClip::MonitorReady;
238 _ctx->ServerCapabilities = sdlClip::ReceiveServerCapabilities;
239 _ctx->ServerFormatList = sdlClip::ReceiveServerFormatList;
240 _ctx->ServerFormatListResponse = sdlClip::ReceiveFormatListResponse;
241 _ctx->ServerFormatDataRequest = sdlClip::ReceiveFormatDataRequest;
242 _ctx->ServerFormatDataResponse = sdlClip::ReceiveFormatDataResponse;
243
244 return cliprdr_file_context_init(_file, _ctx);
245}
246
247bool sdlClip::uninit(CliprdrClientContext* clip)
248{
249 WINPR_ASSERT(clip);
250 if (!cliprdr_file_context_uninit(_file, _ctx))
251 return false;
252 ClipboardLockGuard systemlock(_system);
253 std::scoped_lock lock(_lock);
254 _ctx = nullptr;
255 clip->custom = nullptr;
256 return true;
257}
258
259bool sdlClip::contains(const char** mime_types, Sint32 count)
260{
261 for (Sint32 x = 0; x < count; x++)
262 {
263 const auto mime = mime_types[x];
264 if (mime && (strcmp(_mime_uuid.c_str(), mime) == 0))
265 return true;
266 }
267 return false;
268}
269
270bool sdlClip::ownsClipboard() const
271{
272 return hasMime(localMimeTypes(), _mime_uuid.c_str());
273}
274
275bool sdlClip::handleEvent(const SDL_ClipboardEvent& ev)
276{
277 std::unique_ptr<char*, decltype(&SDL_free)> current(nullptr, SDL_free);
278 const char** mime_types = ev.mime_types;
279 size_t nformats = WINPR_ASSERTING_INT_CAST(size_t, ev.num_mime_types);
280 if (_ctx && _sync && ev.owner && (ev.num_mime_types == 1))
281 {
282 if (strcmp(first_announcement[0], ev.mime_types[0]) == 0)
283 {
284 current.reset(SDL_GetClipboardMimeTypes(&nformats));
285 mime_types = const_cast<const char**>(current.get());
286 if (!mime_types)
287 nformats = 0;
288 }
289 }
290 else if (!_ctx || !_sync || ev.owner)
291 {
292 _last_timestamp = ev.timestamp;
293
294 /* The channel thread replaces _current_mimetypes on every server format list, so take
295 * them under the lock instead of trusting pointers carried in the event. */
296 std::vector<std::string> mimes;
297 {
298 ClipboardLockGuard systemlock(_system);
299 std::scoped_lock lock(_lock);
300 mimes.swap(_current_mimetypes);
301 if (!mimes.empty())
302 _cache_data.clear();
303 }
304 if (mimes.empty())
305 return true;
306
307 std::vector<const char*> cmimes;
308 cmimes.reserve(mimes.size());
309 for (const auto& m : mimes)
310 cmimes.push_back(m.c_str());
311
312 /* Server files need native clipboard items on some platforms (macOS), which only the
313 * file context can offer. It requests the data with ClipDataCb like SDL does. */
314 if (cliprdr_file_context_set_local_clipboard_data(_file, sdlClip::ClipDataCb, this,
315 cmimes.data(), cmimes.size()))
316 return true;
317
318 /* SDL copies the mime type strings */
319 return SDL_SetClipboardData(sdlClip::ClipDataCb, sdlClip::ClipCleanCb, this, cmimes.data(),
320 cmimes.size());
321 }
322
323 if (ev.timestamp == _last_timestamp)
324 return true;
325
326 if (contains(mime_types, WINPR_ASSERTING_INT_CAST(Sint32, nformats)))
327 return true;
328
329 /* SDL holds this session's server data right now, so this update was queued before our
330 * later SDL_SetClipboardData took effect: typically the selection a compositor re-sends on
331 * focus-in, racing a server format list. Announcing it would replace the fresh server
332 * clipboard with the stale local one (and the server would then ask us for data we no
333 * longer own). A genuine local change cancels our data before its update is queued. */
334 if (ownsClipboard())
335 {
336 WLog_Print(_log, WLOG_DEBUG, "ignoring stale local clipboard update");
337 return true;
338 }
339
340 {
341 ClipboardLockGuard systemlock(_system);
342 std::scoped_lock lock(_lock);
343 clearServerFormats();
344 }
345
346 const std::string mime_html = s_mime_html;
347
348 const std::vector<std::string> mime_bitmap = { BMP_MIME_LIST };
349 const std::string mime_webp = s_mime_webp;
350 const std::string mime_png = s_mime_png;
351 const std::string mime_jpeg = s_mime_jpg;
352 const std::string mime_tiff = s_mime_tiff;
353 const std::vector<std::string> mime_images = { mime_webp, mime_png, mime_jpeg,
354 mime_tiff, s_mime_avif, s_mime_jxl };
355
356 std::vector<std::string> clientFormatNames;
357 std::vector<CLIPRDR_FORMAT> clientFormats;
358
359 const char** clipboard_mime_formats = mime_types;
360
361 WLog_Print(_log, WLOG_TRACE, "SDL has %" PRIuz " formats", nformats);
362
363 bool textPushed = false;
364 bool imgPushed = false;
365 bool filePushed = false;
366
367 for (size_t i = 0; i < nformats; i++)
368 {
369 std::string local_mime = clipboard_mime_formats[i];
370 WLog_Print(_log, WLOG_TRACE, " - %s", local_mime.c_str());
371
372 if (std::find(s_mime_text().begin(), s_mime_text().end(), local_mime) !=
373 s_mime_text().end())
374 {
375 /* text formats */
376 if (!textPushed)
377 {
378 clientFormats.push_back({ CF_TEXT, nullptr });
379 clientFormats.push_back({ CF_OEMTEXT, nullptr });
380 clientFormats.push_back({ CF_UNICODETEXT, nullptr });
381 textPushed = true;
382 }
383 }
384 else if (local_mime == mime_html)
385 /* html */
386 clientFormatNames.emplace_back(s_type_HtmlFormat);
387 else if ((std::find(mime_bitmap.begin(), mime_bitmap.end(), local_mime) !=
388 mime_bitmap.end()) ||
389 (std::find(mime_images.begin(), mime_images.end(), local_mime) !=
390 mime_images.end()))
391 {
392 /* image formats */
393 if (!imgPushed)
394 {
395 clientFormats.push_back({ CF_DIB, nullptr });
396#if defined(WINPR_UTILS_IMAGE_DIBv5)
397 clientFormats.push_back({ CF_DIBV5, nullptr });
398#endif
399
400 if (winpr_image_format_is_supported(WINPR_IMAGE_BITMAP))
401 {
402 for (auto& bmp : mime_bitmap)
403 clientFormatNames.push_back(bmp);
404 }
405
406 if (winpr_image_format_is_supported(WINPR_IMAGE_JPEG))
407 clientFormatNames.push_back(mime_jpeg);
408 if (winpr_image_format_is_supported(WINPR_IMAGE_WEBP))
409 clientFormatNames.push_back(mime_webp);
410 if (winpr_image_format_is_supported(WINPR_IMAGE_PNG))
411 clientFormatNames.push_back(mime_png);
412
413 clientFormatNames.emplace_back(s_type_HtmlFormat);
414 imgPushed = true;
415 }
416 clientFormatNames.push_back(local_mime);
417 }
418 else if (mime_is_file(local_mime) || (local_mime == s_type_file_url))
419 {
420 if (!filePushed)
421 {
422 clientFormatNames.emplace_back(s_type_FileGroupDescriptorW);
423 filePushed = true;
424 }
425 }
426 }
427
428 std::sort(clientFormatNames.begin(), clientFormatNames.end());
429 clientFormatNames.erase(std::unique(clientFormatNames.begin(), clientFormatNames.end()),
430 clientFormatNames.end());
431
432 for (auto& name : clientFormatNames)
433 {
434 clientFormats.push_back({ ClipboardRegisterFormat(_system, name.c_str()), name.data() });
435 }
436
437 std::sort(clientFormats.begin(), clientFormats.end(),
438 [](const auto& a, const auto& b) { return a < b; });
439 auto u = std::unique(clientFormats.begin(), clientFormats.end());
440 clientFormats.erase(u, clientFormats.end());
441
442 const CLIPRDR_FORMAT_LIST formatList = {
443 { CB_FORMAT_LIST, 0, 0 },
444 static_cast<UINT32>(clientFormats.size()),
445 clientFormats.data(),
446 };
447
448 WLog_Print(_log, WLOG_TRACE,
449 "-------------- client format list [%" PRIu32 "] ------------------",
450 formatList.numFormats);
451 for (UINT32 x = 0; x < formatList.numFormats; x++)
452 {
453 auto format = &formatList.formats[x];
454 WLog_Print(_log, WLOG_TRACE, "client announces %" PRIu32 " [%s][%s]", format->formatId,
455 ClipboardGetFormatIdString(format->formatId), format->formatName);
456 }
457
458 if (cliprdr_file_context_notify_new_client_format_list(_file) != CHANNEL_RC_OK)
459 return false;
460
461 WINPR_ASSERT(_ctx);
462 WINPR_ASSERT(_ctx->ClientFormatList);
463 return _ctx->ClientFormatList(_ctx, &formatList) == CHANNEL_RC_OK;
464}
465
466UINT sdlClip::MonitorReady(CliprdrClientContext* context, const CLIPRDR_MONITOR_READY* monitorReady)
467{
468 WINPR_UNUSED(monitorReady);
469 WINPR_ASSERT(context);
470 WINPR_ASSERT(monitorReady);
471
472 auto clipboard = static_cast<sdlClip*>(
473 cliprdr_file_context_get_context(static_cast<CliprdrFileContext*>(context->custom)));
474 WINPR_ASSERT(clipboard);
475
476 auto ret = clipboard->SendClientCapabilities();
477 if (ret != CHANNEL_RC_OK)
478 return ret;
479
480 clipboard->_sync = true;
481 SDL_Event ev = { SDL_EVENT_CLIPBOARD_UPDATE };
482 ev.clipboard.owner = true;
483 ev.clipboard.timestamp = SDL_GetTicksNS();
484 ev.clipboard.num_mime_types = ARRAYSIZE(first_announcement);
485 ev.clipboard.mime_types = first_announcement;
486
487 auto rc = SDL_PushEvent(&ev);
488 if (rc != 1)
489 return ERROR_INTERNAL_ERROR;
490
491 return CHANNEL_RC_OK;
492}
493
494UINT sdlClip::SendClientCapabilities()
495{
496 CLIPRDR_GENERAL_CAPABILITY_SET generalCapabilitySet = {
497 CB_CAPSTYPE_GENERAL, 12, CB_CAPS_VERSION_2,
498 CB_USE_LONG_FORMAT_NAMES | cliprdr_file_context_current_flags(_file)
499 };
500 const CLIPRDR_CAPABILITIES capabilities = {
501 { CB_TYPE_NONE, 0, 0 }, 1, reinterpret_cast<CLIPRDR_CAPABILITY_SET*>(&generalCapabilitySet)
502 };
503
504 WINPR_ASSERT(_ctx);
505 WINPR_ASSERT(_ctx->ClientCapabilities);
506 return _ctx->ClientCapabilities(_ctx, &capabilities);
507}
508
509void sdlClip::clearServerFormats()
510{
511 {
512 ClipboardLockGuard systemlock(_system);
513 std::scoped_lock lock(_lock);
514 _serverFormats.clear();
515 _cache_data.clear();
516 /* The WinPR clipboard may still hold local data converted for an earlier server request.
517 * ClipDataCb() would hand that out instead of requesting the new server data. */
518 ClipboardEmpty(_system);
519 }
520 cliprdr_file_context_clear(_file);
521}
522
523UINT sdlClip::SendFormatListResponse(BOOL status)
524{
525 const CLIPRDR_FORMAT_LIST_RESPONSE formatListResponse = {
526 { CB_FORMAT_LIST_RESPONSE, static_cast<UINT16>(status ? CB_RESPONSE_OK : CB_RESPONSE_FAIL),
527 0 }
528 };
529 WINPR_ASSERT(_ctx);
530 WINPR_ASSERT(_ctx->ClientFormatListResponse);
531 return _ctx->ClientFormatListResponse(_ctx, &formatListResponse);
532}
533
534UINT sdlClip::SendDataResponse(const BYTE* data, size_t size)
535{
536 CLIPRDR_FORMAT_DATA_RESPONSE response = {};
537
538 if (size > UINT32_MAX)
539 return ERROR_INVALID_PARAMETER;
540
541 response.common.msgFlags = (data) ? CB_RESPONSE_OK : CB_RESPONSE_FAIL;
542 response.common.dataLen = static_cast<UINT32>(size);
543 response.requestedFormatData = data;
544
545 WINPR_ASSERT(_ctx);
546 WINPR_ASSERT(_ctx->ClientFormatDataResponse);
547 return _ctx->ClientFormatDataResponse(_ctx, &response);
548}
549
550UINT sdlClip::SendDataRequest(uint32_t formatID, const std::string& mime)
551{
552 const CLIPRDR_FORMAT_DATA_REQUEST request = { { CB_TYPE_NONE, 0, 0 }, formatID };
553
554 _request_queue.emplace(formatID, mime);
555
556 WINPR_ASSERT(_ctx);
557 WINPR_ASSERT(_ctx->ClientFormatDataRequest);
558 UINT ret = _ctx->ClientFormatDataRequest(_ctx, &request);
559 if (ret != CHANNEL_RC_OK)
560 {
561 WLog_Print(_log, WLOG_ERROR, "error sending ClientFormatDataRequest, cancelling request");
562 _request_queue.pop();
563 }
564
565 return ret;
566}
567
568std::string sdlClip::getServerFormat(uint32_t id)
569{
570 for (auto& fmt : _serverFormats)
571 {
572 if (fmt.formatId() == id)
573 {
574 if (fmt.formatName())
575 return fmt.formatName();
576 break;
577 }
578 }
579
580 return "";
581}
582
583uint32_t sdlClip::serverIdForMime(const std::string& mime)
584{
585 std::string cmp = mime;
586 if (mime_is_html(mime))
587 cmp = s_type_HtmlFormat;
588 if (mime_is_file(mime))
589 cmp = s_type_FileGroupDescriptorW;
590
591 for (auto& format : _serverFormats)
592 {
593 if (!format.formatName())
594 continue;
595 if (cmp == format.formatName())
596 return format.formatId();
597 }
598
599 if (mime_is_image(mime) || mime_is_bmp(mime))
600 {
601 /* not offered under this name: CF_DIB, or any other image format of the server; the
602 * WinPR clipboard converts it */
603 uint32_t named = 0;
604 for (auto& format : _serverFormats)
605 {
606 if ((format.formatId() == CF_DIB) || (format.formatId() == CF_DIBV5))
607 return format.formatId();
608 if (!named && format.formatName() &&
609 (mime_is_image(format.formatName()) || mime_is_bmp(format.formatName())))
610 named = format.formatId();
611 }
612 return named ? named : CF_DIB;
613 }
614 if (mime_is_text(mime))
615 return CF_UNICODETEXT;
616
617 return 0;
618}
619
620UINT sdlClip::ReceiveServerCapabilities(CliprdrClientContext* context,
621 const CLIPRDR_CAPABILITIES* capabilities)
622{
623 WINPR_ASSERT(context);
624 WINPR_ASSERT(capabilities);
625
626 auto capsPtr = reinterpret_cast<const BYTE*>(capabilities->capabilitySets);
627 WINPR_ASSERT(capsPtr);
628
629 auto clipboard = static_cast<sdlClip*>(
630 cliprdr_file_context_get_context(static_cast<CliprdrFileContext*>(context->custom)));
631 WINPR_ASSERT(clipboard);
632
633 if (!cliprdr_file_context_remote_set_flags(clipboard->_file, 0))
634 return ERROR_INTERNAL_ERROR;
635
636 for (UINT32 i = 0; i < capabilities->cCapabilitiesSets; i++)
637 {
638 auto caps = reinterpret_cast<const CLIPRDR_CAPABILITY_SET*>(capsPtr);
639
640 if (caps->capabilitySetType == CB_CAPSTYPE_GENERAL)
641 {
642 auto generalCaps = reinterpret_cast<const CLIPRDR_GENERAL_CAPABILITY_SET*>(caps);
643
644 if (!cliprdr_file_context_remote_set_flags(clipboard->_file, generalCaps->generalFlags))
645 return ERROR_INTERNAL_ERROR;
646 }
647
648 capsPtr += caps->capabilitySetLength;
649 }
650
651 return CHANNEL_RC_OK;
652}
653
654UINT sdlClip::ReceiveServerFormatList(CliprdrClientContext* context,
655 const CLIPRDR_FORMAT_LIST* formatList)
656{
657 BOOL html = FALSE;
658 BOOL text = FALSE;
659 BOOL image = FALSE;
660 BOOL file = FALSE;
661
662 if (!context || !context->custom)
663 return ERROR_INVALID_PARAMETER;
664
665 auto filecontext = static_cast<CliprdrFileContext*>(context->custom);
666 auto clipboard = static_cast<sdlClip*>(cliprdr_file_context_get_context(filecontext));
667 WINPR_ASSERT(clipboard);
668
669 ClipboardLockGuard systemlock(clipboard->_system);
670 std::scoped_lock lock(clipboard->_lock);
671
672 clipboard->clearServerFormats();
673 /* _system may still hold local clipboard data converted for an earlier server request.
674 * ClipDataCb converts from _system before asking the server, so left in place it would
675 * answer the next local paste with that stale local data instead of the new server data. */
676 ClipboardEmpty(clipboard->_system);
677
678 for (UINT32 i = 0; i < formatList->numFormats; i++)
679 {
680 const CLIPRDR_FORMAT* format = &formatList->formats[i];
681
682 clipboard->_serverFormats.emplace_back(format->formatId, format->formatName);
683
684 if (format->formatName)
685 {
686 if (strcmp(format->formatName, s_type_HtmlFormat) == 0)
687 {
688 text = TRUE;
689 html = TRUE;
690 }
691 else if (strcmp(format->formatName, s_type_FileGroupDescriptorW) == 0)
692 {
693 file = TRUE;
694 text = TRUE;
695 }
696 else if (mime_is_image(format->formatName) || mime_is_bmp(format->formatName))
697 {
698 /* e.g. GNOME Remote Desktop offers image/png alone for PNG-only sources */
699 image = TRUE;
700 }
701 }
702 else
703 {
704 switch (format->formatId)
705 {
706 case CF_TEXT:
707 case CF_OEMTEXT:
708 case CF_UNICODETEXT:
709 text = TRUE;
710 break;
711
712 case CF_DIB:
713 image = TRUE;
714 break;
715
716 default:
717 break;
718 }
719 }
720 }
721
722 clipboard->_current_mimetypes.clear();
723
724 {
725 auto res = cliprdr_file_context_notify_new_server_format_list(filecontext);
726 if (res != CHANNEL_RC_OK)
727 return res;
728 }
729
730 if (text)
731 {
732 clipboard->_current_mimetypes.insert(clipboard->_current_mimetypes.end(),
733 s_mime_text().begin(), s_mime_text().end());
734 }
735 if (image)
736 {
737 clipboard->_current_mimetypes.insert(clipboard->_current_mimetypes.end(),
738 s_mime_bitmap().begin(), s_mime_bitmap().end());
739 clipboard->_current_mimetypes.insert(clipboard->_current_mimetypes.end(),
740 s_mime_image().begin(), s_mime_image().end());
741 }
742 if (html)
743 {
744 clipboard->_current_mimetypes.emplace_back(s_mime_html);
745 }
746 if (file)
747 {
748 clipboard->_current_mimetypes.emplace_back(s_mime_uri_list);
749 clipboard->_current_mimetypes.emplace_back(s_mime_gnome_copied_files);
750 clipboard->_current_mimetypes.emplace_back(s_mime_mate_copied_files);
751 }
752 clipboard->_current_mimetypes.emplace_back(clipboard->_mime_uuid.c_str());
753
754 auto& mime = clipboard->_current_mimetypes;
755 std::sort(mime.begin(), mime.end());
756 mime.erase(std::unique(mime.begin(), mime.end()), mime.end());
757
758 WLog_Print(clipboard->_log, WLOG_TRACE,
759 "-------------- server mime types [%" PRIuz "] ------------------", mime.size());
760 for (const auto& m : mime)
761 {
762 WLog_Print(clipboard->_log, WLOG_TRACE, "server announces %s]", m.c_str());
763 }
764
765 /* handleEvent picks the mime types up from _current_mimetypes */
766 SDL_Event ev = { SDL_EVENT_CLIPBOARD_UPDATE };
767 ev.clipboard.owner = true;
768 ev.clipboard.timestamp = SDL_GetTicksNS();
769
770 auto rc = (SDL_PushEvent(&ev) == 1);
771 return clipboard->SendFormatListResponse(rc);
772}
773
774UINT sdlClip::ReceiveFormatListResponse(WINPR_ATTR_UNUSED CliprdrClientContext* context,
775 const CLIPRDR_FORMAT_LIST_RESPONSE* formatListResponse)
776{
777 WINPR_ASSERT(context);
778 WINPR_ASSERT(formatListResponse);
779
780 if (formatListResponse->common.msgFlags & CB_RESPONSE_FAIL)
781 WLog_WARN(TAG, "format list update failed");
782 return CHANNEL_RC_OK;
783}
784
785std::shared_ptr<BYTE> sdlClip::getLocalData(uint32_t formatId, uint32_t& len)
786{
787 len = 0;
788
789 const auto offered = localMimeTypes();
790
791 /* The local clipboard still holds what this session received from the server (the
792 * server asked before our format list reached it). Reading it through SDL would call
793 * ClipDataCb and ask the server for its own data, so fail the request instead. */
794 if (hasMime(offered, _mime_uuid.c_str()))
795 {
796 WLog_Print(_log, WLOG_DEBUG, "local clipboard holds server data, not answering request");
797 return {};
798 }
799
800 UINT32 fileFormatId = 0;
801 UINT32 htmlFormatId = 0;
802 {
803 ClipboardLockGuard systemlock(_system);
804 std::scoped_lock lock(_lock);
805 WLog_Print(_log, WLOG_DEBUG, "Requesting format %s [0x%08" PRIx32 "] [%s]",
806 ClipboardGetFormatIdString(formatId), formatId,
807 ClipboardGetFormatName(_system, formatId));
808 fileFormatId = ClipboardGetFormatId(_system, s_type_FileGroupDescriptorW);
809 htmlFormatId = ClipboardGetFormatId(_system, s_type_HtmlFormat);
810 }
811
812 /* Local mime types to try, in order, and the _system format each is stored as for winpr to
813 * convert: its registered format, the requested one, or a newly registered one. */
814 enum class Store
815 {
816 Lookup,
817 Requested,
818 Register
819 };
820 struct Candidate
821 {
822 const char* mime;
823 Store store;
824 };
825 std::vector<Candidate> candidates;
826
827 switch (formatId)
828 {
829 case CF_TEXT:
830 case CF_OEMTEXT:
831 case CF_UNICODETEXT:
832 {
833 const char* mime = firstOffered(offered, s_mime_text());
834 if (mime)
835 candidates.push_back({ mime, Store::Lookup });
836 }
837 break;
838
839 case CF_DIB:
840 case CF_DIBV5:
841 /* Every convertible type on offer (screenshot tools offer image/png only, never
842 * image/bmp), in case a decoder rejects one of them. */
843 for (const auto& mime : s_mime_dib_sources())
844 {
845 if (hasMime(offered, mime))
846 candidates.push_back({ mime, Store::Register });
847 }
848 break;
849
850 case CF_TIFF:
851 candidates.push_back({ s_mime_tiff, Store::Requested });
852 break;
853
854 default:
855 if (formatId == fileFormatId)
856 candidates.push_back({ s_mime_uri_list, Store::Lookup });
857 else if (formatId == htmlFormatId)
858 {
859 /* In case HTML format was requested but we only have images in local clipboard */
860 const char* mime = hasMime(offered, s_mime_html)
861 ? s_mime_html
862 : firstOffered(offered, s_mime_html_image_sources());
863 if (mime)
864 candidates.push_back({ mime, Store::Lookup });
865 }
866 else
867 {
868 ClipboardLockGuard systemlock(_system);
869 std::scoped_lock lock(_lock);
870 const char* formatName = ClipboardGetFormatName(_system, formatId);
871 if (formatName && hasMime(offered, formatName))
872 {
873 /* the name is owned by _system and outlives this function */
874 candidates.push_back({ formatName, Store::Lookup });
875 }
876 else if (formatName && (mime_is_image(formatName) || mime_is_bmp(formatName)))
877 {
878 /* We announce every image format WinPR can convert to, but the SDL backend
879 * may only provide some of them (Windows: image/bmp). Take the ones on
880 * offer and let the WinPR clipboard convert to the requested format. */
881 for (const auto& mime : s_mime_dib_sources())
882 {
883 if (hasMime(offered, mime))
884 candidates.push_back({ mime, Store::Register });
885 }
886 }
887 }
888 break;
889 }
890
891 std::shared_ptr<BYTE> data;
892 for (const auto& candidate : candidates)
893 {
894 /* Read without holding our locks: on X11 this pumps events and can take a while, and
895 * the channel thread needs the locks to deliver server data. */
896 size_t size = 0;
897 std::shared_ptr<void> sdldata;
898 if (formatId == fileFormatId)
899 sdldata =
900 std::shared_ptr<void>(cliprdr_file_context_get_local_uri_list(_file, &size), free);
901 if (!sdldata)
902 sdldata = std::shared_ptr<void>(SDL_GetClipboardData(candidate.mime, &size), SDL_free);
903 if (!sdldata || (size > UINT32_MAX))
904 continue;
905
906 ClipboardLockGuard systemlock(_system);
907 std::scoped_lock lock(_lock);
908
909 if (formatId == fileFormatId)
910 {
911 auto bdata = static_cast<const char*>(sdldata.get());
912 if (!cliprdr_file_context_update_client_data(_file, bdata, size))
913 continue;
914 }
915
916 UINT32 storeId = formatId;
917 if (candidate.store == Store::Lookup)
918 storeId = ClipboardGetFormatId(_system, candidate.mime);
919 else if (candidate.store == Store::Register)
920 storeId = ClipboardRegisterFormat(_system, candidate.mime);
921
922 uint32_t ptrlen = 0;
923 BYTE* ptr = nullptr;
924 if (ClipboardSetData(_system, storeId, sdldata.get(), static_cast<uint32_t>(size)))
925 ptr = static_cast<BYTE*>(ClipboardGetData(_system, formatId, &ptrlen));
926
927 /* _system is also where ClipDataCb looks for server data: leave no local data behind,
928 * or a later local paste would be answered with it. */
929 ClipboardEmpty(_system);
930
931 if (!ptr)
932 {
933 WLog_Print(_log, WLOG_DEBUG, "could not convert local %s", candidate.mime);
934 continue;
935 }
936
937 data = std::shared_ptr<BYTE>(ptr, free);
938 len = ptrlen;
939
940 if (formatId == fileFormatId)
941 {
942 BYTE* ddata = nullptr;
943 UINT32 dsize = 0;
944 const UINT32 flags = cliprdr_file_context_remote_get_flags(_file);
945 const UINT32 error = cliprdr_serialize_file_list_ex(
946 flags, reinterpret_cast<const FILEDESCRIPTORW*>(data.get()),
947 ptrlen / sizeof(FILEDESCRIPTORW), &ddata, &dsize);
948 data = std::shared_ptr<BYTE>(ddata, free);
949 len = dsize;
950 if (error)
951 {
952 data.reset();
953 len = 0;
954 }
955 }
956 break;
957 }
958 return data;
959}
960
961UINT sdlClip::ReceiveFormatDataRequest(CliprdrClientContext* context,
962 const CLIPRDR_FORMAT_DATA_REQUEST* formatDataRequest)
963{
964 WINPR_ASSERT(context);
965 WINPR_ASSERT(formatDataRequest);
966
967 auto clipboard = static_cast<sdlClip*>(
968 cliprdr_file_context_get_context(static_cast<CliprdrFileContext*>(context->custom)));
969 WINPR_ASSERT(clipboard);
970
971 {
972 std::scoped_lock lock(clipboard->_lock);
973 /* ClipDataCb is waiting on the main thread for the server: a local application is
974 * reading our offer of server data, so the local clipboard holds no client data to
975 * give. Answer now. Queued for the main thread, the request would wait for the end of
976 * that read while the server may hold its reply until we answer (seen with Windows:
977 * a 9 s freeze until ClipDataCb timed out). */
978 if (clipboard->_reading_server)
979 {
980 WLog_Print(clipboard->_log, WLOG_DEBUG,
981 "local read of server data in progress, failing server request");
982 return clipboard->SendDataResponse(nullptr, 0);
983 }
984 clipboard->_server_requests.push_back(formatDataRequest->requestedFormatId);
985 }
986
987 /* This runs on the channel thread, but the SDL clipboard API is main-thread only: on
988 * Wayland, reading the selection offer here races the main thread replacing it. */
989 if (!SDL_RunOnMainThread([](void* userdata)
990 { static_cast<sdlClip*>(userdata)->handleDataRequests(); }, clipboard,
991 false))
992 {
993 std::scoped_lock lock(clipboard->_lock);
994 if (!clipboard->_server_requests.empty())
995 clipboard->_server_requests.pop_back();
996 return clipboard->SendDataResponse(nullptr, 0);
997 }
998 return CHANNEL_RC_OK;
999}
1000
1001void sdlClip::handleDataRequests()
1002{
1003 for (;;)
1004 {
1005 uint32_t formatId = 0;
1006 {
1007 std::scoped_lock lock(_lock);
1008 if (_server_requests.empty())
1009 return;
1010 formatId = _server_requests.front();
1011 _server_requests.pop_front();
1012 }
1013 if (!_ctx)
1014 continue;
1015
1016 uint32_t len = 0;
1017 auto data = getLocalData(formatId, len);
1018 if (SendDataResponse(data.get(), len) != CHANNEL_RC_OK)
1019 WLog_Print(_log, WLOG_WARN, "failed to send clipboard data response");
1020 }
1021}
1022
1023UINT sdlClip::ReceiveFormatDataResponse(CliprdrClientContext* context,
1024 const CLIPRDR_FORMAT_DATA_RESPONSE* formatDataResponse)
1025{
1026 WINPR_ASSERT(context);
1027 WINPR_ASSERT(formatDataResponse);
1028
1029 const UINT32 size = formatDataResponse->common.dataLen;
1030 const BYTE* data = formatDataResponse->requestedFormatData;
1031
1032 auto clipboard = static_cast<sdlClip*>(
1033 cliprdr_file_context_get_context(static_cast<CliprdrFileContext*>(context->custom)));
1034 WINPR_ASSERT(clipboard);
1035
1036 ClipboardLockGuard systemlock(clipboard->_system);
1037 std::scoped_lock lock(clipboard->_lock);
1038 if (clipboard->_request_queue.empty())
1039 {
1040 /* The matching request already timed out (see the wait below) and was
1041 * popped, so this is a late reply on a slow/high-latency link. Dropping it
1042 * is harmless; returning an error here would kill the cliprdr channel thread
1043 * and tear down the whole session. Warn and continue instead. */
1044 WLog_Print(clipboard->_log, WLOG_WARN,
1045 "format data response with no pending request (late reply?), ignoring");
1046 return CHANNEL_RC_OK;
1047 }
1048
1049 do
1050 {
1051 UINT32 srcFormatId = 0;
1052 auto& request = clipboard->_request_queue.front();
1053 bool success = (formatDataResponse->common.msgFlags & CB_RESPONSE_OK) &&
1054 !(formatDataResponse->common.msgFlags & CB_RESPONSE_FAIL);
1055 request.setSuccess(success);
1056
1057 if (!success)
1058 {
1059 WLog_Print(clipboard->_log, WLOG_WARN,
1060 "clipboard data request for format %" PRIu32 " [%s], mime %s failed",
1061 request.format(), request.formatstr().c_str(), request.mime().c_str());
1062 break;
1063 }
1064
1065 switch (request.format())
1066 {
1067 case CF_TEXT:
1068 case CF_OEMTEXT:
1069 case CF_UNICODETEXT:
1070 srcFormatId = request.format();
1071 break;
1072
1073 case CF_DIB:
1074 case CF_DIBV5:
1075 srcFormatId = request.format();
1076 break;
1077
1078 default:
1079 {
1080 auto name = clipboard->getServerFormat(request.format());
1081 if (!name.empty())
1082 {
1083 if (name == s_type_FileGroupDescriptorW)
1084 {
1085 srcFormatId =
1086 ClipboardGetFormatId(clipboard->_system, s_type_FileGroupDescriptorW);
1087
1088 if (cliprdr_file_context_has_local_support(clipboard->_file))
1089 {
1090 if (!cliprdr_file_context_update_server_data(
1091 clipboard->_file, clipboard->_system, data, size))
1092 {
1093 WLog_Print(clipboard->_log, WLOG_WARN,
1094 "File clipboard failed to update");
1095 }
1096 }
1097 }
1098 else if (name == s_type_HtmlFormat)
1099 {
1100 srcFormatId = ClipboardGetFormatId(clipboard->_system, s_type_HtmlFormat);
1101 }
1102 else if (mime_is_image(name) || mime_is_bmp(name))
1103 {
1104 srcFormatId = ClipboardGetFormatId(clipboard->_system, name.c_str());
1105 }
1106 }
1107 }
1108 break;
1109 }
1110
1111 if (!ClipboardSetData(clipboard->_system, srcFormatId, data, size))
1112 {
1113 /* Fail this request only: an error return here would end the clipboard channel
1114 * and, without auto-reconnect, the session. */
1115 WLog_Print(clipboard->_log, WLOG_ERROR, "error when setting clipboard data");
1116 request.setSuccess(false);
1117 break;
1118 }
1119 WLog_Print(clipboard->_log, WLOG_DEBUG, "updated clipboard data %s [0x%08" PRIx32 "]",
1120 ClipboardGetFormatName(clipboard->_system, srcFormatId), srcFormatId);
1121 } while (false);
1122
1123 if (!SetEvent(clipboard->_event))
1124 {
1125 WLog_Print(clipboard->_log, WLOG_ERROR, "error when setting clipboard event");
1126 return ERROR_INTERNAL_ERROR;
1127 }
1128
1129 return CHANNEL_RC_OK;
1130}
1131
1132const void* sdlClip::ClipDataCb(void* userdata, const char* mime_type, size_t* size)
1133{
1134 auto clip = static_cast<sdlClip*>(userdata);
1135 WINPR_ASSERT(clip);
1136 WINPR_ASSERT(size);
1137 WINPR_ASSERT(mime_type);
1138
1139 *size = 0;
1140 uint32_t len = 0;
1141
1142 if (mime_is_text(mime_type))
1143 {
1144 if (mime_is_utf8(mime_type))
1145 mime_type = mime_text_utf8;
1146 else
1147 mime_type = mime_text_plain;
1148 }
1149
1150 {
1151 ClipboardLockGuard systemlock(clip->_system);
1152 std::scoped_lock lock(clip->_lock);
1153
1154 if (!clip->_ctx)
1155 return nullptr;
1156
1157 /* check if we already used this mime type */
1158 auto cache = clip->_cache_data.find(mime_type);
1159 if (cache != clip->_cache_data.end())
1160 {
1161 *size = cache->second.size;
1162 return cache->second.ptr.get();
1163 }
1164
1165 auto formatID = clip->serverIdForMime(mime_type);
1166
1167 /* Can we convert the data from existing formats in the clibpard? */
1168 uint32_t fsize = 0;
1169 auto mimeFormatID = ClipboardRegisterFormat(clip->_system, mime_type);
1170 auto fptr = ClipboardGetData(clip->_system, mimeFormatID, &fsize);
1171 if (fptr)
1172 {
1173 auto ptr = std::shared_ptr<void>(fptr, free);
1174 if (mime_is_text(mime_type))
1175 fsize = textLength(fptr, fsize);
1176 clip->_cache_data.insert({ mime_type, { fsize, ptr } });
1177
1178 auto fcache = clip->_cache_data.find(mime_type);
1179 if (fcache != clip->_cache_data.end())
1180 {
1181 *size = fcache->second.size;
1182 return fcache->second.ptr.get();
1183 }
1184 }
1185
1186 WLog_Print(clip->_log, WLOG_DEBUG, "requesting format %s [%s 0x%08" PRIx32 "]", mime_type,
1187 ClipboardGetFormatName(clip->_system, formatID), formatID);
1188
1189 /* From here until the reply, server data requests are answered on arrival (see
1190 * ReceiveFormatDataRequest). Those already queued get the same answer now: a local
1191 * application is reading our offer, so there is no client data to give. */
1192 clip->_reading_server = true;
1193 while (!clip->_server_requests.empty())
1194 {
1195 clip->_server_requests.pop_front();
1196 WLog_Print(clip->_log, WLOG_DEBUG,
1197 "local read of server data in progress, failing queued server request");
1198 std::ignore = clip->SendDataResponse(nullptr, 0);
1199 }
1200
1201 if (clip->SendDataRequest(formatID, mime_type))
1202 {
1203 clip->_reading_server = false;
1204 return nullptr;
1205 }
1206 }
1207 {
1208 HANDLE hdl[2] = { freerdp_abort_event(clip->_sdl->context()), clip->_event };
1209
1210 const UINT32 timeout =
1211 freerdp_settings_get_uint32(clip->_sdl->context()->settings, FreeRDP_TcpAckTimeout);
1212 DWORD status = WaitForMultipleObjects(ARRAYSIZE(hdl), hdl, FALSE, timeout);
1213
1214 if (status != WAIT_OBJECT_0 + 1)
1215 {
1216 std::scoped_lock lock(clip->_lock);
1217 clip->_reading_server = false;
1218 if (!clip->_request_queue.empty())
1219 clip->_request_queue.pop();
1220
1221 if (status == WAIT_TIMEOUT)
1222 WLog_Print(clip->_log, WLOG_ERROR,
1223 "no reply in 10 seconds, returning empty content");
1224
1225 return nullptr;
1226 }
1227 }
1228
1229 {
1230 ClipboardLockGuard systemlock(clip->_system);
1231 std::scoped_lock lock(clip->_lock);
1232 clip->_reading_server = false;
1233 if (clip->_request_queue.empty())
1234 return nullptr;
1235 auto request = clip->_request_queue.front();
1236 clip->_request_queue.pop();
1237
1238 if (clip->_request_queue.empty())
1239 std::ignore = ResetEvent(clip->_event);
1240
1241 if (request.success())
1242 {
1243 auto formatID = ClipboardRegisterFormat(clip->_system, mime_type);
1244 auto data = ClipboardGetData(clip->_system, formatID, &len);
1245 if (!data)
1246 {
1247 WLog_Print(clip->_log, WLOG_ERROR, "error retrieving clipboard data");
1248 return nullptr;
1249 }
1250
1251 auto ptr = std::shared_ptr<void>(data, free);
1252 if (mime_is_text(mime_type))
1253 len = textLength(data, len);
1254 clip->_cache_data.insert({ mime_type, { len, ptr } });
1255 *size = len;
1256 return ptr.get();
1257 }
1258
1259 return nullptr;
1260 }
1261}
1262
1263void sdlClip::ClipCleanCb(void* userdata)
1264{
1265 auto clip = static_cast<sdlClip*>(userdata);
1266 WINPR_ASSERT(clip);
1267 ClipboardLockGuard give_me_a_name(clip->_system);
1268 std::scoped_lock lock(clip->_lock);
1269 ClipboardEmpty(clip->_system);
1270}
1271
1272bool sdlClip::mime_is_file(const std::string& mime)
1273{
1274 if (strncmp(s_mime_uri_list, mime.c_str(), sizeof(s_mime_uri_list)) == 0)
1275 return true;
1276 if (strncmp(s_mime_gnome_copied_files, mime.c_str(), sizeof(s_mime_gnome_copied_files)) == 0)
1277 return true;
1278 if (strncmp(s_mime_mate_copied_files, mime.c_str(), sizeof(s_mime_mate_copied_files)) == 0)
1279 return true;
1280 return false;
1281}
1282
1283bool sdlClip::mime_is_text(const std::string& mime)
1284{
1285 for (const auto& tmime : s_mime_text())
1286 {
1287 assert(tmime != nullptr);
1288 if (mime == tmime)
1289 return true;
1290 }
1291
1292 return false;
1293}
1294
1295bool sdlClip::mime_is_utf8(const std::string& mime)
1296{
1297 if (mime == std::string(mime_text_utf8))
1298 return true;
1299
1300 return mime == std::string("UTF8_STRING");
1301}
1302
1303bool sdlClip::mime_is_image(const std::string& mime)
1304{
1305 for (const auto& imime : s_mime_image())
1306 {
1307 assert(imime != nullptr);
1308 if (mime == imime)
1309 return true;
1310 }
1311
1312 return false;
1313}
1314
1315bool sdlClip::mime_is_bmp(const std::string& mime)
1316{
1317 for (const auto& imime : s_mime_bitmap())
1318 {
1319 assert(imime != nullptr);
1320 if (mime == imime)
1321 return true;
1322 }
1323
1324 return false;
1325}
1326
1327bool sdlClip::mime_is_html(const std::string& mime)
1328{
1329 return mime.compare(s_mime_html) == 0;
1330}
1331
1332ClipRequest::ClipRequest(UINT32 format, const std::string& mime)
1333 : _format(format), _mime(mime), _success(false)
1334{
1335}
1336
1337uint32_t ClipRequest::format() const
1338{
1339 return _format;
1340}
1341
1342std::string ClipRequest::formatstr() const
1343{
1344 return ClipboardGetFormatIdString(_format);
1345}
1346
1347std::string ClipRequest::mime() const
1348{
1349 return _mime;
1350}
1351
1352bool ClipRequest::success() const
1353{
1354 return _success;
1355}
1356
1357void ClipRequest::setSuccess(bool status)
1358{
1359 _success = status;
1360}
1361
1362CliprdrFormat::CliprdrFormat(uint32_t formatID, const char* formatName) : _formatID(formatID)
1363{
1364 if (formatName)
1365 _formatName = formatName;
1366}
1367
1368uint32_t CliprdrFormat::formatId() const
1369{
1370 return _formatID;
1371}
1372
1373const char* CliprdrFormat::formatName() const
1374{
1375 if (_formatName.empty())
1376 return nullptr;
1377 return _formatName.c_str();
1378}
object that handles clipboard context for the SDL3 client
Definition sdl_clip.hpp:77
WINPR_ATTR_NODISCARD FREERDP_API UINT32 freerdp_settings_get_uint32(const rdpSettings *settings, FreeRDP_Settings_Keys_UInt32 id)
Returns a UINT32 settings value.