20#include <freerdp/config.h>
22#include <winpr/assert.h>
24#include "win_shadow_cliprdr.h"
26#if defined(CHANNEL_CLIPRDR_SERVER)
31#include <winpr/platform.h>
33#include <freerdp/log.h>
34#include <freerdp/utils/cliprdr_utils.h>
36#include <freerdp/channels/cliprdr.h>
37#include <freerdp/server/cliprdr.h>
47#define TAG SERVER_TAG("shadow.win.cliprdr")
52static const UINT64 CLIP_MAX_FILE_BYTES = 512ull * 1024 * 1024;
53static const UINT64 CLIP_MAX_TOTAL_BYTES = 2ull * 1024 * 1024 * 1024;
54static const UINT32 CLIP_CHUNK = 4u * 1024 * 1024;
55static const UINT32 CLIP_MAX_FILES = 256;
57#define FMT_BASE 0xC000
69 rdpShadowClient* client;
70 CliprdrServerContext* ctx;
99 UINT32 reqHead, reqTail;
102 WCHAR stageDir[MAX_PATH];
106static ShadowClip* clipOf(CliprdrServerContext* ctx)
108 return (ShadowClip*)ctx->custom;
113static void cliprdr_check_send(UINT rc,
const char* what)
115 if (rc != CHANNEL_RC_OK)
116 WLog_WARN(TAG,
"%s failed with %" PRIu32, what, rc);
126static BOOL open_clipboard_retry(
void)
128 for (
int i = 0; i < 20; ++i)
130 if (OpenClipboard(
nullptr))
139static void write_remote_text(ShadowClip* clip,
const BYTE* utf16, UINT32 len)
144 HANDLE hMem =
nullptr;
145 hMem = GlobalAlloc(GMEM_MOVEABLE, len + 2);
149 p = GlobalLock(hMem);
152 CopyMemory(p, utf16, len);
153 ZeroMemory((BYTE*)p + len, 2);
156 InterlockedIncrement(&clip->suppress);
157 if (open_clipboard_retry())
160 if (!SetClipboardData(CF_UNICODETEXT, hMem))
163 InterlockedDecrement(&clip->suppress);
170 InterlockedDecrement(&clip->suppress);
177static void write_remote_hdrop(ShadowClip* clip)
183 bytes =
sizeof(DROPFILES) +
sizeof(WCHAR);
184 WLog_INFO(TAG,
"write_remote_hdrop: enter inCount=%u", clip->inCount);
185 EnterCriticalSection(&clip->lock);
189 for (UINT32 i = 0; i < clip->inCount; ++i)
191 if (!wcschr(clip->inDesc[i].cFileName, L
'\\'))
193 WCHAR full[MAX_PATH];
194 _snwprintf_s(full, MAX_PATH, _TRUNCATE, L
"%s\\%s", clip->stageDir,
195 clip->inDesc[i].cFileName);
196 bytes += (wcslen(full) + 1) *
sizeof(WCHAR);
200 LeaveCriticalSection(&clip->lock);
204 HANDLE hMem =
nullptr;
205 hMem = GlobalAlloc(GMEM_MOVEABLE | GMEM_ZEROINIT, bytes);
208 DROPFILES* df = (DROPFILES*)GlobalLock(hMem);
214 df->pFiles =
sizeof(DROPFILES);
216 WCHAR* dst =
nullptr;
217 dst = (WCHAR*)((BYTE*)df +
sizeof(DROPFILES));
218 SIZE_T remain = bytes;
219 EnterCriticalSection(&clip->lock);
220 for (UINT32 i = 0; i < clip->inCount; ++i)
222 if (wcschr(clip->inDesc[i].cFileName, L
'\\'))
227 _snwprintf_s(dst, remain /
sizeof(WCHAR), _TRUNCATE, L
"%s\\%s", clip->stageDir,
228 clip->inDesc[i].cFileName);
229 const SIZE_T wrote = (wcslen(dst) + 1) *
sizeof(WCHAR);
233 LeaveCriticalSection(&clip->lock);
238 cfDrop = RegisterClipboardFormatW(CFSTR_PREFERREDDROPEFFECT);
239 HANDLE hEff =
nullptr;
240 hEff = GlobalAlloc(GMEM_MOVEABLE,
sizeof(DWORD));
244 e = (DWORD*)GlobalLock(hEff);
246 *e = DROPEFFECT_COPY;
250 InterlockedIncrement(&clip->suppress);
251 if (open_clipboard_retry())
254 if (SetClipboardData(CF_HDROP, hMem))
257 SetClipboardData(cfDrop, hEff);
264 InterlockedDecrement(&clip->suppress);
267 WLog_INFO(TAG,
"client files pasted to remote clipboard (%u files)", n);
274 InterlockedDecrement(&clip->suppress);
284static void clipfiles_push(ClipFile** list, UINT32* n, UINT32* cap,
const WCHAR* full,
293 DWORD a = GetFileAttributesW(full);
294 if (a == INVALID_FILE_ATTRIBUTES || *n >= CLIP_MAX_FILES)
298 *cap = *cap ? *cap * 2 : 64;
299 ClipFile* nl = (ClipFile*)realloc(*list, *cap *
sizeof(ClipFile));
309 ClipFile* cf = &(*list)[(*n)++];
310 wcscpy_s(cf->path, MAX_PATH, full);
311 wcscpy_s(cf->rel, MAX_PATH, rel);
314 if (!(a & FILE_ATTRIBUTE_DIRECTORY))
316 HANDLE f = CreateFileW(full, GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE,
nullptr,
317 OPEN_EXISTING, 0,
nullptr);
318 if (f != INVALID_HANDLE_VALUE)
321 if (GetFileSizeEx(f, &sz))
322 cf->size = (UINT64)sz.QuadPart;
329 _snwprintf_s(pat, MAX_PATH, _TRUNCATE, L
"%s\\*", full);
331 HANDLE h = FindFirstFileW(pat, &fd);
332 if (h == INVALID_HANDLE_VALUE)
336 if ((fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) &&
337 (!wcscmp(fd.cFileName, L
".") || !wcscmp(fd.cFileName, L
"..")))
339 WCHAR sub[MAX_PATH] = { 0 };
340 WCHAR srel[MAX_PATH] = { 0 };
341 _snwprintf_s(sub, MAX_PATH, _TRUNCATE, L
"%s\\%s", full, fd.cFileName);
342 _snwprintf_s(srel, MAX_PATH, _TRUNCATE, L
"%s\\%s", rel, fd.cFileName);
343 clipfiles_push(list, n, cap, sub, srel);
344 }
while (FindNextFileW(h, &fd) && *n < CLIP_MAX_FILES);
348static void snapshot_remote_files(ShadowClip* clip)
353 h = GetClipboardData(CF_HDROP);
356 HDROP drop = (HDROP)h;
358 cnt = DragQueryFileW(drop, 0xFFFFFFFF,
nullptr, 0);
359 ClipFile* list =
nullptr;
362 for (UINT i = 0; i < cnt && real < CLIP_MAX_FILES; ++i)
364 WCHAR path[MAX_PATH];
365 if (!DragQueryFileW(drop, i, path, MAX_PATH))
367 const WCHAR* base = wcsrchr(path, L
'\\');
368 clipfiles_push(&list, &real, &cap, path, base ? base + 1 : path);
370 EnterCriticalSection(&clip->lock);
374 LeaveCriticalSection(&clip->lock);
379static void announce_local(ShadowClip* clip)
383 if (!open_clipboard_retry())
385 BOOL hasText = FALSE;
386 BOOL hasFiles = FALSE;
387 hasText = IsClipboardFormatAvailable(CF_UNICODETEXT) || IsClipboardFormatAvailable(CF_TEXT);
388 hasFiles = IsClipboardFormatAvailable(CF_HDROP);
390 snapshot_remote_files(clip);
397 fmts[n].formatId = CF_UNICODETEXT;
398 fmts[n].formatName =
nullptr;
401 if (hasFiles && clip->nFiles)
403 fmts[n].formatId = clip->fgdId;
404 fmts[n].formatName = _strdup(
"FileGroupDescriptorW");
406 fmts[n].formatId = clip->contentsId;
407 fmts[n].formatName = _strdup(
"FileContents");
409 fmts[n].formatId = clip->dropId;
410 fmts[n].formatName = _strdup(
"PreferredDropEffect");
416 .common = { .msgType = CB_FORMAT_LIST, .msgFlags = 0, .dataLen = 0 },
420 cliprdr_check_send(clip->ctx->ServerFormatList(clip->ctx, &list),
"ServerFormatList");
421 for (UINT32 i = 0; i < n; ++i)
422 free(fmts[i].formatName);
427static void pull_abort(ShadowClip* clip)
431 if (clip->inFile != INVALID_HANDLE_VALUE && clip->inFile)
432 CloseHandle(clip->inFile);
433 clip->inFile =
nullptr;
435 clip->inDesc =
nullptr;
436 clip->inCount = clip->inIdx = 0;
437 clip->inSize = clip->inGot = clip->inTotal = 0;
438 clip->inAwaitSize = FALSE;
443static void finish_pull(ShadowClip* clip)
445 write_remote_hdrop(clip);
448static void rm_tree(
const WCHAR* dir)
451 _snwprintf_s(pat, MAX_PATH, _TRUNCATE, L
"%s\\*", dir);
453 HANDLE h = FindFirstFileW(pat, &fd);
454 if (h == INVALID_HANDLE_VALUE)
458 if (!wcscmp(fd.cFileName, L
".") || !wcscmp(fd.cFileName, L
".."))
460 WCHAR full[MAX_PATH];
461 _snwprintf_s(full, MAX_PATH, _TRUNCATE, L
"%s\\%s", dir, fd.cFileName);
462 if (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
465 RemoveDirectoryW(full);
471 }
while (FindNextFileW(h, &fd));
475static void wipe_stage_dir(ShadowClip* clip)
477 CreateDirectoryW(clip->stageDir,
nullptr);
478 rm_tree(clip->stageDir);
485static void mkdirs_under(ShadowClip* clip,
const WCHAR* rel, BOOL includeLast)
491 wcscpy_s(acc, MAX_PATH, clip->stageDir);
492 const WCHAR* p = rel;
495 const WCHAR* bs = wcschr(p, L
'\\');
496 const size_t n = bs ? (size_t)(bs - p) : wcslen(p);
497 if (!bs && !includeLast)
499 const size_t l = wcslen(acc);
500 if (l + 1 + n >= MAX_PATH)
503 wcsncpy_s(acc + l + 1, MAX_PATH - l - 1, p, n);
505 CreateDirectoryW(acc,
nullptr);
514static void pull_skip_dirs(ShadowClip* clip)
516 while (clip->inIdx < clip->inCount)
518 if (!(clip->inDesc[clip->inIdx].dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY))
520 mkdirs_under(clip, clip->inDesc[clip->inIdx].cFileName, TRUE);
527static BOOL pull_step(ShadowClip* clip)
531 if (clip->inIdx >= clip->inCount)
535 .streamId = ++clip->inStream,
536 .listIndex = clip->inIdx,
541 .haveClipDataId = FALSE,
543 if (clip->inGot == 0)
545 req.dwFlags = FILECONTENTS_SIZE;
547 clip->inAwaitSize = TRUE;
551 req.dwFlags = FILECONTENTS_RANGE;
552 const UINT64 left = clip->inSize - clip->inGot;
553 const UINT64 want = left < CLIP_CHUNK ? left : CLIP_CHUNK;
554 req.nPositionLow = (UINT32)(clip->inGot & 0xFFFFFFFFull);
555 req.nPositionHigh = (UINT32)(clip->inGot >> 32);
556 req.cbRequested = (UINT32)want;
557 clip->inAwaitSize = FALSE;
559 cliprdr_check_send(clip->ctx->ServerFileContentsRequest(clip->ctx, &req),
560 "ServerFileContentsRequest");
565static void pull_advance(ShadowClip* clip)
569 if (clip->inFile && clip->inFile != INVALID_HANDLE_VALUE)
570 CloseHandle(clip->inFile);
571 clip->inFile =
nullptr;
573 clip->inSize = clip->inGot = 0;
574 pull_skip_dirs(clip);
575 if (clip->inIdx >= clip->inCount)
581 (void)pull_step(clip);
589 if (!clip->inDesc || (r->common.msgFlags & CB_RESPONSE_FAIL))
591 WLog_WARN(TAG,
"filecontents response fail/idle (flags=0x%x)", r->common.msgFlags);
595 if (clip->inAwaitSize)
597 clip->inAwaitSize = FALSE;
600 if (r->common.dataLen < 8 || !r->requestedData)
605 clip->inSize = *(
const UINT64*)r->requestedData;
606 WLog_INFO(TAG,
"pull: SIZE idx=%u size=%llu dataLen=%u", clip->inIdx,
607 (
unsigned long long)clip->inSize, r->common.dataLen);
608 if (clip->inSize > CLIP_MAX_FILE_BYTES ||
609 clip->inTotal + clip->inSize > CLIP_MAX_TOTAL_BYTES)
611 WLog_WARN(TAG,
"file too large for clipboard transfer (%llu bytes)",
612 (
unsigned long long)clip->inSize);
616 mkdirs_under(clip, clip->inDesc[clip->inIdx].cFileName, FALSE);
617 WCHAR full[MAX_PATH];
618 _snwprintf_s(full, MAX_PATH, _TRUNCATE, L
"%s\\%s", clip->stageDir,
619 clip->inDesc[clip->inIdx].cFileName);
620 clip->inFile = CreateFileW(full, GENERIC_WRITE, 0,
nullptr, CREATE_ALWAYS,
621 FILE_ATTRIBUTE_NORMAL,
nullptr);
622 if (clip->inFile == INVALID_HANDLE_VALUE)
624 WLog_WARN(TAG,
"stage create failed err=%lu path=%ls", GetLastError(), full);
628 if (clip->inSize == 0)
637 .common = { .msgType = CB_FILECONTENTS_REQUEST, .msgFlags = 0 },
638 .streamId = ++clip->inStream,
639 .listIndex = clip->inIdx,
640 .dwFlags = FILECONTENTS_RANGE,
643 .cbRequested = (UINT32)(clip->inSize < CLIP_CHUNK ? clip->inSize : CLIP_CHUNK),
644 .haveClipDataId = FALSE,
647 cliprdr_check_send(clip->ctx->ServerFileContentsRequest(clip->ctx, &req),
648 "ServerFileContentsRequest");
652 if (clip->inFile && clip->inFile != INVALID_HANDLE_VALUE && r->requestedData)
656 off.QuadPart = (LONGLONG)clip->inGot;
657 SetFilePointerEx(clip->inFile, off,
nullptr, FILE_BEGIN);
658 WriteFile(clip->inFile, r->requestedData, r->common.dataLen, &wr,
nullptr);
662 if (clip->inGot >= clip->inSize)
665 (
void)pull_step(clip);
666 WLog_INFO(TAG,
"pull: RANGE idx=%u got=%u/%u", clip->inIdx, (
unsigned)clip->inGot,
667 (
unsigned)clip->inSize);
672static UINT srv_client_format_list(CliprdrServerContext* ctx,
const CLIPRDR_FORMAT_LIST* list)
677 ShadowClip* clip = clipOf(ctx);
680 WLog_INFO(TAG,
"format_list: %u formats", list->numFormats);
681 for (UINT32 i = 0; i < list->numFormats; ++i)
684 if (!f->formatName && (f->formatId == CF_UNICODETEXT || f->formatId == CF_TEXT))
685 textId = f->formatId;
686 else if (f->formatName && strcmp(f->formatName,
"FileGroupDescriptorW") == 0)
689 clip->peerFgdId = fgdId;
694 clip->reqQ[clip->reqTail++ % 8] = textId;
697 .requestedFormatId = textId };
698 cliprdr_check_send(ctx->ServerFormatDataRequest(ctx, &req),
"ServerFormatDataRequest");
700 if (fgdId && ctx->streamFileClipEnabled)
702 clip->reqQ[clip->reqTail++ % 8] = fgdId;
705 .requestedFormatId = fgdId };
706 cliprdr_check_send(ctx->ServerFormatDataRequest(ctx, &req),
"ServerFormatDataRequest");
709 .msgFlags = CB_RESPONSE_OK } };
710 cliprdr_check_send(ctx->ServerFormatListResponse(ctx, &resp),
"ServerFormatListResponse");
711 return CHANNEL_RC_OK;
714static UINT srv_client_format_data_response(CliprdrServerContext* ctx,
720 ShadowClip* clip = clipOf(ctx);
722 if (clip->reqHead != clip->reqTail)
723 id = clip->reqQ[clip->reqHead++ % 8];
724 if (!(r->common.msgFlags & CB_RESPONSE_OK) || !r->requestedFormatData)
725 return CHANNEL_RC_OK;
726 if (
id == CF_UNICODETEXT ||
id == CF_TEXT)
728 write_remote_text(clip, r->requestedFormatData, r->common.dataLen);
729 return CHANNEL_RC_OK;
731 if (
id == clip->peerFgdId &&
id != 0)
734 WLog_INFO(TAG,
"pull: fgd response, start");
736 wipe_stage_dir(clip);
737 if (cliprdr_parse_file_list(r->requestedFormatData, r->common.dataLen, &clip->inDesc,
738 &clip->inCount) != NO_ERROR)
743 clip->inDesc =
nullptr;
745 return CHANNEL_RC_OK;
747 if (clip->inCount > CLIP_MAX_FILES)
748 clip->inCount = CLIP_MAX_FILES;
750 clip->inSize = clip->inGot = clip->inTotal = 0;
751 clip->inFile =
nullptr;
752 pull_skip_dirs(clip);
753 if (clip->inIdx < clip->inCount)
754 (void)pull_step(clip);
762 return CHANNEL_RC_OK;
764 return CHANNEL_RC_OK;
769static UINT srv_client_format_data_request(CliprdrServerContext* ctx,
775 ShadowClip* clip = clipOf(ctx);
777 .msgFlags = CB_RESPONSE_FAIL,
779 .requestedFormatData =
nullptr };
781 if (req->requestedFormatId == CF_UNICODETEXT || req->requestedFormatId == CF_TEXT)
783 if (open_clipboard_retry())
786 h = GetClipboardData(CF_UNICODETEXT);
789 const WCHAR* s =
nullptr;
790 s = (
const WCHAR*)GlobalLock(h);
793 const SIZE_T len = (wcslen(s) + 1) *
sizeof(WCHAR);
794 buf = (BYTE*)malloc(len);
796 CopyMemory(buf, s, len);
803 resp.common.msgFlags = CB_RESPONSE_OK;
804 resp.common.dataLen = (UINT32)((wcslen((
const WCHAR*)buf) + 1) *
sizeof(WCHAR));
805 resp.requestedFormatData = buf;
809 else if (req->requestedFormatId == clip->fgdId)
812 BYTE* data =
nullptr;
813 EnterCriticalSection(&clip->lock);
817 for (UINT32 i = 0; i < clip->nFiles; ++i)
819 const BOOL isDir = (clip->files[i].attrs & FILE_ATTRIBUTE_DIRECTORY) != 0;
820 desc[i].dwFlags = FD_ATTRIBUTES | FD_PROGRESSUI | (isDir ? 0 : FD_FILESIZE);
821 desc[i].dwFileAttributes = clip->files[i].attrs;
822 desc[i].nFileSizeHigh = (DWORD)(clip->files[i].size >> 32);
823 desc[i].nFileSizeLow = (DWORD)(clip->files[i].size & 0xFFFFFFFFu);
824 wcscpy_s(desc[i].cFileName,
sizeof(desc[i].cFileName) /
sizeof(WCHAR),
827 if (cliprdr_serialize_file_list(desc, clip->nFiles, &data, &len) == NO_ERROR)
830 resp.common.msgFlags = CB_RESPONSE_OK;
831 resp.common.dataLen = len;
832 resp.requestedFormatData = buf;
836 LeaveCriticalSection(&clip->lock);
838 else if (req->requestedFormatId == clip->dropId)
840 buf = (BYTE*)malloc(
sizeof(DWORD));
843 *(DWORD*)buf = DROPEFFECT_COPY;
844 resp.common.msgFlags = CB_RESPONSE_OK;
845 resp.common.dataLen =
sizeof(DWORD);
846 resp.requestedFormatData = buf;
849 cliprdr_check_send(ctx->ServerFormatDataResponse(ctx, &resp),
"ServerFormatDataResponse");
851 return CHANNEL_RC_OK;
855static UINT srv_client_file_contents_request(CliprdrServerContext* ctx,
861 ShadowClip* clip = clipOf(ctx);
863 .msgFlags = CB_RESPONSE_FAIL,
865 .streamId = req->streamId,
867 .requestedData =
nullptr };
869 EnterCriticalSection(&clip->lock);
870 if (req->listIndex < clip->nFiles && clip->files)
872 ClipFile* cf = &clip->files[req->listIndex];
873 if (req->dwFlags & FILECONTENTS_SIZE)
875 buf = (BYTE*)malloc(8);
878 *(UINT64*)buf = cf->size;
879 resp.common.msgFlags = CB_RESPONSE_OK;
880 resp.common.dataLen = 8;
881 resp.cbRequested = 8;
882 resp.requestedData = buf;
885 else if (req->dwFlags & FILECONTENTS_RANGE)
887 HANDLE f = CreateFileW(cf->path, GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE,
888 nullptr, OPEN_EXISTING, 0,
nullptr);
889 if (f != INVALID_HANDLE_VALUE)
892 off.QuadPart = ((LONGLONG)req->nPositionHigh << 32) | req->nPositionLow;
893 SetFilePointerEx(f, off,
nullptr, FILE_BEGIN);
894 buf = (BYTE*)malloc(req->cbRequested);
896 if (buf && ReadFile(f, buf, req->cbRequested, &rd,
nullptr) && rd)
898 resp.common.msgFlags = CB_RESPONSE_OK;
899 resp.common.dataLen = rd;
900 resp.cbRequested = rd;
901 resp.requestedData = buf;
907 LeaveCriticalSection(&clip->lock);
908 cliprdr_check_send(ctx->ServerFileContentsResponse(ctx, &resp),
"ServerFileContentsResponse");
910 return CHANNEL_RC_OK;
913static UINT srv_client_file_contents_response(CliprdrServerContext* ctx,
919 on_client_file_contents(clipOf(ctx), r);
920 return CHANNEL_RC_OK;
925 WLog_INFO(TAG,
"client cliprdr caps received (%u sets)", caps ? caps->cCapabilitiesSets : 0);
926 return CHANNEL_RC_OK;
933 ShadowClip* clip = clipOf(ctx);
934 WLog_INFO(TAG,
"cliprdr ready -- announcing current remote clipboard");
935 announce_local(clip);
936 return CHANNEL_RC_OK;
944 WLog_DBG(TAG,
"client temp dir: %s", td->szTempDir);
945 return CHANNEL_RC_OK;
950static LRESULT CALLBACK clip_wndproc(HWND h, UINT m, WPARAM w, LPARAM l)
952 if (m == WM_CLIPBOARDUPDATE)
954 ShadowClip* clip =
nullptr;
955 clip = (ShadowClip*)GetWindowLongPtrW(h, GWLP_USERDATA);
958 if (InterlockedDecrement(&clip->suppress) >= 0)
960 InterlockedExchange(&clip->suppress, 0);
961 if (WaitForSingleObject(clip->stopEvt, 0) != WAIT_OBJECT_0)
962 announce_local(clip);
966 return DefWindowProcW(h, m, w, l);
969static DWORD WINAPI clip_watch_thread(LPVOID arg)
971 ShadowClip* clip = (ShadowClip*)arg;
974 WNDCLASSW wc = WINPR_C_ARRAY_INIT;
975 wc.lpfnWndProc = clip_wndproc;
976 wc.hInstance = GetModuleHandleW(
nullptr);
977 wc.lpszClassName = L
"FreeRDPShadowClip";
979 clip->hwnd = CreateWindowExW(0, wc.lpszClassName, L
"", 0, 0, 0, 0, 0, HWND_MESSAGE,
nullptr,
980 wc.hInstance,
nullptr);
983 SetWindowLongPtrW(clip->hwnd, GWLP_USERDATA, (LONG_PTR)clip);
984 AddClipboardFormatListener(clip->hwnd);
986 SetEvent(clip->ready);
987 MSG msg = WINPR_C_ARRAY_INIT;
988 while (GetMessageW(&msg,
nullptr, 0, 0) > 0)
990 if (WaitForSingleObject(clip->stopEvt, 0) == WAIT_OBJECT_0)
992 DispatchMessageW(&msg);
996 RemoveClipboardFormatListener(clip->hwnd);
997 DestroyWindow(clip->hwnd);
998 clip->hwnd =
nullptr;
1011static BOOL clip_client_is_local(rdpShadowClient* client)
1013 WINPR_ASSERT(client);
1014 const rdpSettings* settings = client->context.settings;
1015 WINPR_ASSERT(settings);
1021 if (_stricmp(addr,
"localhost") == 0 || strncmp(addr,
"127.", 4) == 0 ||
1022 strcmp(addr,
"::1") == 0)
1028 struct sockaddr_in v4;
1029 struct sockaddr_in6 v6;
1032 if (strchr(addr,
':'))
1034 if (inet_pton(AF_INET6, addr, &peer.v6.sin6_addr) == 1)
1035 peerLen =
sizeof(
struct sockaddr_in6);
1037 else if (inet_pton(AF_INET, addr, &peer.v4.sin_addr) == 1)
1038 peerLen =
sizeof(
struct sockaddr_in);
1045 ULONG size = 15 * 1024;
1046 PIP_ADAPTER_ADDRESSES adapters =
nullptr;
1047 for (
int attempt = 0; attempt < 3; ++attempt)
1049 adapters = (PIP_ADAPTER_ADDRESSES)malloc(size);
1053 rc = GetAdaptersAddresses(
1054 AF_UNSPEC, GAA_FLAG_SKIP_ANYCAST | GAA_FLAG_SKIP_MULTICAST | GAA_FLAG_SKIP_DNS_SERVER,
1055 nullptr, adapters, &size);
1056 if (rc == ERROR_SUCCESS)
1060 if (rc != ERROR_BUFFER_OVERFLOW)
1067 for (PIP_ADAPTER_ADDRESSES cur = adapters; cur && !local; cur = cur->Next)
1069 for (PIP_ADAPTER_UNICAST_ADDRESS u = cur->FirstUnicastAddress; u && !local; u = u->Next)
1071 const struct sockaddr* sa = u->Address.lpSockaddr;
1074 if ((sa->sa_family == AF_INET) && (peerLen ==
sizeof(
struct sockaddr_in)))
1076 const struct sockaddr_in* s4 = (
const struct sockaddr_in*)sa;
1077 if (memcmp(&s4->sin_addr, &peer.v4.sin_addr,
sizeof(
struct in_addr)) == 0)
1080 else if ((sa->sa_family == AF_INET6) && (peerLen ==
sizeof(
struct sockaddr_in6)))
1082 const struct sockaddr_in6* s6 = (
const struct sockaddr_in6*)sa;
1083 if (memcmp(&s6->sin6_addr, &peer.v6.sin6_addr,
sizeof(
struct in6_addr)) == 0)
1092int win_shadow_cliprdr_init(rdpShadowClient* client)
1094 WINPR_ASSERT(client);
1099 if (clip_client_is_local(client))
1101 WLog_INFO(TAG,
"client %s is local: clipboard bridge deactivated",
1106 ShadowClip* clip = (ShadowClip*)calloc(1,
sizeof(ShadowClip));
1109 clip->client = client;
1110 clip->ctx = cliprdr_server_context_new(client->vcm);
1116 clip->ctx->custom = clip;
1117 clip->ctx->rdpcontext = &client->context;
1118 clip->ctx->useLongFormatNames = TRUE;
1119 clip->ctx->streamFileClipEnabled = TRUE;
1120 clip->ctx->hasHugeFileSupport = TRUE;
1121 clip->ctx->ClientCapabilities = srv_client_capabilities;
1122 clip->ctx->MonitorReady = srv_monitor_ready;
1123 clip->ctx->TempDirectory = srv_temp_directory;
1124 clip->ctx->ClientFormatList = srv_client_format_list;
1125 clip->ctx->ClientFormatDataRequest = srv_client_format_data_request;
1126 clip->ctx->ClientFormatDataResponse = srv_client_format_data_response;
1127 clip->ctx->ClientFileContentsRequest = srv_client_file_contents_request;
1128 clip->ctx->ClientFileContentsResponse = srv_client_file_contents_response;
1130 InitializeCriticalSection(&clip->lock);
1131 clip->nextFmt = FMT_BASE;
1132 clip->fgdId = clip->nextFmt++;
1133 clip->contentsId = clip->nextFmt++;
1134 clip->dropId = clip->nextFmt++;
1135 clip->inFile =
nullptr;
1136 clip->ready = CreateEventW(
nullptr, TRUE, FALSE,
nullptr);
1137 clip->stopEvt = CreateEventW(
nullptr, TRUE, FALSE,
nullptr);
1144 tl = GetTempPathW(MAX_PATH, clip->stageDir);
1145 if (tl && tl < MAX_PATH - 32)
1147 WCHAR parent[MAX_PATH];
1148 _snwprintf_s(parent, MAX_PATH, _TRUNCATE, L
"%sfreerdp-shadow-clip", clip->stageDir);
1149 CreateDirectoryW(parent,
nullptr);
1150 _snwprintf_s(clip->stageDir + wcslen(clip->stageDir), MAX_PATH - wcslen(clip->stageDir),
1151 _TRUNCATE, L
"freerdp-shadow-clip\\%lu", GetCurrentProcessId());
1152 CreateDirectoryW(clip->stageDir,
nullptr);
1155 client->cliprdr = clip->ctx;
1157 const UINT rc = clip->ctx->Start(clip->ctx);
1158 if (rc != CHANNEL_RC_OK)
1160 win_shadow_cliprdr_uninit(client);
1163 clip->thread = CreateThread(
nullptr, 0, clip_watch_thread, clip, 0,
nullptr);
1164 WLog_INFO(TAG,
"cliprdr channel mounted (text+file sync enabled)");
1168void win_shadow_cliprdr_uninit(rdpShadowClient* client)
1170 WINPR_ASSERT(client);
1171 ShadowClip* clip = client->cliprdr ? clipOf(client->cliprdr) : nullptr;
1175 SetEvent(clip->stopEvt);
1177 PostMessageW(clip->hwnd, WM_QUIT, 0, 0);
1180 WaitForSingleObject(clip->thread, 3000);
1181 CloseHandle(clip->thread);
1186 CloseHandle(clip->ready);
1188 CloseHandle(clip->stopEvt);
1189 DeleteCriticalSection(&clip->lock);
1192 if (client->cliprdr)
1194 client->cliprdr->Stop(client->cliprdr);
1195 cliprdr_server_context_free(client->cliprdr);
1196 client->cliprdr =
nullptr;
1205int win_shadow_cliprdr_init(rdpShadowClient* client)
1207 WINPR_ASSERT(client);
1211void win_shadow_cliprdr_uninit(rdpShadowClient* client)
1213 WINPR_ASSERT(client);
WINPR_ATTR_NODISCARD FREERDP_API const char * freerdp_settings_get_string(const rdpSettings *settings, FreeRDP_Settings_Keys_String id)
Returns a immutable string settings value.