From d537988f2884f30552c0d29e1b605513d9a460d9 Mon Sep 17 00:00:00 2001 From: jackyzy823 Date: Sun, 27 Dec 2020 14:08:35 +0800 Subject: [PATCH] x11: add support for remote to local clipboard file copy --- client/X11/CMakeLists.txt | 4 + client/X11/xf_cliprdr.c | 665 ++++++++++++++++++++++++++++- cmake/FindFUSE.cmake | 34 ++ include/freerdp/channels/cliprdr.h | 2 + winpr/libwinpr/clipboard/posix.c | 136 +++++- 5 files changed, 824 insertions(+), 17 deletions(-) create mode 100644 cmake/FindFUSE.cmake diff --git a/client/X11/CMakeLists.txt b/client/X11/CMakeLists.txt index 869652c1f..7a41db220 100644 --- a/client/X11/CMakeLists.txt +++ b/client/X11/CMakeLists.txt @@ -226,6 +226,10 @@ if(WITH_XFIXES) set(${MODULE_PREFIX}_LIBS ${${MODULE_PREFIX}_LIBS} ${XFIXES_LIBRARIES}) endif() +find_package(FUSE REQUIRED) +include_directories(${FUSE_INCLUDE_DIRS}) +set(${MODULE_PREFIX}_LIBS ${${MODULE_PREFIX}_LIBS} ${FUSE_LIBRARIES}) + include_directories(${CMAKE_SOURCE_DIR}/resources) set(${MODULE_PREFIX}_LIBS ${${MODULE_PREFIX}_LIBS} freerdp-client freerdp m) diff --git a/client/X11/xf_cliprdr.c b/client/X11/xf_cliprdr.c index 9b964d3c1..831f1a30f 100644 --- a/client/X11/xf_cliprdr.c +++ b/client/X11/xf_cliprdr.c @@ -31,10 +31,18 @@ #include #endif +#define FUSE_USE_VERSION 26 +#include +#include +#include +#include +#include + #include #include #include #include +#include #include #include @@ -55,6 +63,33 @@ struct xf_cliprdr_format }; typedef struct xf_cliprdr_format xfCliprdrFormat; +struct xf_cliprdr_fuse_stream +{ + UINT32 stream_id; + fuse_req_t req; +}; +typedef struct xf_cliprdr_fuse_stream xfCliprdrFuseStream; + +struct xf_cliprdr_fuse_inode +{ + size_t parent_ino; + size_t ino; + size_t lindex; + mode_t st_mode; + off_t st_size; + struct timespec st_mtim; + char* name; + wArrayList* child_inos; +}; +typedef struct xf_cliprdr_fuse_inode xfCliprdrFuseInode; + +void xf_cliprdr_fuse_inode_free(void* obj) +{ + xfCliprdrFuseInode* inode = (xfCliprdrFuseInode*)obj; + free(inode->name); + ArrayList_Free(inode->child_inos); +} + struct xf_clipboard { xfContext* xfc; @@ -111,6 +146,15 @@ struct xf_clipboard /* File clipping */ BOOL streams_supported; BOOL file_formats_registered; + + /* FUSE related**/ + HANDLE fuse_thread; + struct fuse_session* fuse_sess; + + /* fuse reset per copy*/ + wArrayList* stream_list; + UINT32 current_stream_id; + wArrayList* ino_list; }; static UINT xf_cliprdr_send_client_format_list(xfClipboard* clipboard); @@ -824,6 +868,29 @@ static void xf_cliprdr_clear_cached_data(xfClipboard* clipboard) } clipboard->data_raw_length = 0; + + if (clipboard->stream_list) + { + size_t index; + size_t count; + xfCliprdrFuseStream* stream; + ArrayList_Lock(clipboard->stream_list); + clipboard->current_stream_id = 0; + /* reply error to all req first */ + count = ArrayList_Count(clipboard->stream_list); + for (index = 0; index < count; index++) + { + stream = (xfCliprdrFuseStream*)ArrayList_GetItem(clipboard->stream_list, index); + fuse_reply_err(stream->req, EIO); + } + ArrayList_Unlock(clipboard->stream_list); + + ArrayList_Clear(clipboard->stream_list); + } + if (clipboard->ino_list) + { + ArrayList_Clear(clipboard->ino_list); + } } static BOOL xf_cliprdr_process_selection_request(xfClipboard* clipboard, @@ -1157,6 +1224,66 @@ static UINT xf_cliprdr_send_client_format_list_response(xfClipboard* clipboard, return clipboard->context->ClientFormatListResponse(clipboard->context, &formatListResponse); } +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT xf_cliprdr_send_client_file_contents(xfClipboard* clipboard, UINT32 streamId, + UINT32 listIndex, UINT32 nPositionLow, + UINT32 nPositionHigh, UINT32 cbRequested) +{ + CLIPRDR_FILE_CONTENTS_REQUEST formatFileContentsRequest; + formatFileContentsRequest.streamId = streamId; + formatFileContentsRequest.listIndex = listIndex; + formatFileContentsRequest.dwFlags = FILECONTENTS_RANGE; + formatFileContentsRequest.nPositionHigh = nPositionHigh; + formatFileContentsRequest.nPositionLow = nPositionLow; + formatFileContentsRequest.cbRequested = cbRequested; + formatFileContentsRequest.haveClipDataId = FALSE; + return clipboard->context->ClientFileContentsRequest(clipboard->context, + &formatFileContentsRequest); +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT +xf_cliprdr_server_file_contents_response(CliprdrClientContext* context, + const CLIPRDR_FILE_CONTENTS_RESPONSE* fileContentsResponse) +{ + UINT32 count; + UINT32 index; + BOOL found = FALSE; + xfCliprdrFuseStream* stream; + xfClipboard* clipboard = (xfClipboard*)context->custom; + UINT32 stream_id = fileContentsResponse->streamId; + + ArrayList_Lock(clipboard->stream_list); + count = ArrayList_Count(clipboard->stream_list); + + for (index = 0; index < count; index++) + { + stream = (xfCliprdrFuseStream*)ArrayList_GetItem(clipboard->stream_list, index); + + if (stream->stream_id == stream_id) + { + found = TRUE; + break; + } + } + ArrayList_Unlock(clipboard->stream_list); + if (found) + { + fuse_reply_buf(stream->req, (const char*)fileContentsResponse->requestedData, + fileContentsResponse->cbRequested); + ArrayList_RemoveAt(clipboard->stream_list, index); + } + return CHANNEL_RC_OK; +} + /** * Function description * @@ -1388,6 +1515,19 @@ xf_cliprdr_server_format_data_request(CliprdrClientContext* context, return CHANNEL_RC_OK; } +static char* xf_cliprdr_fuse_split_basename(char* name, int len) +{ + int s = len - 1; + for (; s >= 0; s--) + { + if (*(name + s) == '\\') + { + return name + s; + } + } + return NULL; +} + /** * Function description * @@ -1403,6 +1543,7 @@ xf_cliprdr_server_format_data_response(CliprdrClientContext* context, UINT32 SrcSize; UINT32 srcFormatId; UINT32 dstFormatId; + xfCliprdrFormat* dstTargetFormat; BOOL nullTerminated = FALSE; UINT32 size = formatDataResponse->dataLen; const BYTE* data = formatDataResponse->requestedFormatData; @@ -1434,9 +1575,133 @@ xf_cliprdr_server_format_data_response(CliprdrClientContext* context, if (strcmp(clipboard->data_format_name, "FileGroupDescriptorW") == 0) { + /* Build inode table for FILEDESCRIPTORW*/ + /* Avoid Crash on empty message*/ + if (size >= 4) + { + UINT32 nrDescriptors = (UINT32)(data[3] << 24) | (UINT32)(data[2] << 16) | + (UINT32)(data[1] << 8) | (data[0] & 0xFF); + const FILEDESCRIPTORW* descriptors = (const FILEDESCRIPTORW*)&data[4]; + size_t count = (size - 4) / sizeof(FILEDESCRIPTORW); + + if (count >= 1 && count == nrDescriptors) + { + size_t lindex; + wHashTable* mapDir; + xfCliprdrFuseInode* rootNode = + (xfCliprdrFuseInode*)calloc(1, sizeof(xfCliprdrFuseInode)); + + rootNode->ino = 1; + rootNode->parent_ino = 1; + rootNode->name = _strdup("/"); + rootNode->st_mode = S_IFDIR | 0755; + rootNode->child_inos = ArrayList_New(TRUE); + rootNode->st_mtim.tv_sec = time(NULL); + ArrayList_Add(clipboard->ino_list, rootNode); + + mapDir = HashTable_New(TRUE); + mapDir->keyFree = HashTable_StringFree; + mapDir->keyClone = HashTable_StringClone; + mapDir->keyCompare = HashTable_StringCompare; + mapDir->hash = HashTable_StringHash; + + /* here we assume that parent folder always appears before its children */ + for (lindex = 0; lindex < count; lindex++) + { + xfCliprdrFuseInode* inode = + (xfCliprdrFuseInode*)calloc(1, sizeof(xfCliprdrFuseInode)); + size_t curLen = _wcsnlen(descriptors[lindex].cFileName, + ARRAYSIZE(descriptors[lindex].cFileName)); + char* curName = NULL; + int newLen = ConvertFromUnicode(CP_UTF8, 0, descriptors[lindex].cFileName, + (int)curLen, &curName, 0, NULL, NULL); + char* split_point = xf_cliprdr_fuse_split_basename(curName, newLen); + char* dirName = NULL; + char* baseName = NULL; + UINT64 ticks; + xfCliprdrFuseInode* parent; + + inode->lindex = lindex; + inode->ino = lindex + 2; + + if (split_point == NULL) + { + baseName = _strdup(curName); + inode->parent_ino = 1; + inode->name = baseName; + ArrayList_Add(rootNode->child_inos, (void*)inode->ino); + } + else + { + dirName = calloc(split_point - curName + 1, sizeof(char)); + _snprintf(dirName, split_point - curName + 1, "%s", curName); + /* drop last '\\' */ + baseName = _strdup(split_point + 1); + + parent = (xfCliprdrFuseInode*)HashTable_GetItemValue(mapDir, dirName); + inode->parent_ino = parent->ino; + inode->name = baseName; + ArrayList_Add(parent->child_inos, (void*)inode->ino); + free(dirName); + } + + if ((descriptors[lindex].dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0) + { + inode->st_mode = S_IFDIR | 0755; + inode->child_inos = ArrayList_New(TRUE); + HashTable_Add(mapDir, _strdup(curName), inode); + } + else + { + inode->st_mode = S_IFREG | 0644; + } + + free(curName); + + inode->st_size = (((UINT64)descriptors[lindex].nFileSizeHigh) << 32) | + ((UINT64)descriptors[lindex].nFileSizeLow); + ticks = + (((UINT64)descriptors[lindex].ftLastWriteTime.dwHighDateTime << 32) | + ((UINT64)descriptors[lindex].ftLastWriteTime.dwLowDateTime)) - + 116444736000000000; + inode->st_mtim.tv_sec = ticks / 10000000ULL; + /* tv_nsec Not used for now */ + inode->st_mtim.tv_nsec = ticks % 10000000ULL; + ArrayList_Add(clipboard->ino_list, inode); + } + + HashTable_Free(mapDir); + } + } + srcFormatId = ClipboardGetFormatId(clipboard->system, "FileGroupDescriptorW"); - dstFormatId = ClipboardGetFormatId(clipboard->system, "text/uri-list"); - nullTerminated = FALSE; + dstTargetFormat = + xf_cliprdr_get_client_format_by_atom(clipboard, clipboard->respond->target); + if (!dstTargetFormat) + { + dstFormatId = ClipboardGetFormatId(clipboard->system, "text/uri-list"); + } + else + { + switch (dstTargetFormat->formatId) + { + case CF_UNICODETEXT: + dstFormatId = ClipboardGetFormatId(clipboard->system, "UTF8_STRING"); + break; + case CB_FORMAT_TEXTURILIST: + dstFormatId = ClipboardGetFormatId(clipboard->system, "text/uri-list"); + break; + case CB_FORMAT_GNOMECOPIEDFILES: + dstFormatId = + ClipboardGetFormatId(clipboard->system, "x-special/gnome-copied-files"); + break; + case CB_FORMAT_MATECOPIEDFILES: + dstFormatId = + ClipboardGetFormatId(clipboard->system, "x-special/mate-copied-files"); + } + } + + nullTerminated = TRUE; } } else @@ -1663,6 +1928,334 @@ static UINT xf_cliprdr_clipboard_file_range_failure(wClipboardDelegate* delegate return clipboard->context->ClientFileContentsResponse(clipboard->context, &response); } +static void xf_cliprdr_fuse_getattr(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info* fi) +{ + double timeout = 0; + struct stat stbuf; + + xfCliprdrFuseInode* node; + xfClipboard* clipboard = (xfClipboard*)fuse_req_userdata(req); + + ArrayList_Lock(clipboard->ino_list); + node = (xfCliprdrFuseInode*)ArrayList_GetItem(clipboard->ino_list, ino - 1); + ArrayList_Unlock(clipboard->ino_list); + if (!node) + { + fuse_reply_err(req, ENOENT); + } + else + { + memset(&stbuf, 0, sizeof(stbuf)); + stbuf.st_ino = ino; + stbuf.st_mode = node->st_mode; + stbuf.st_mtime = node->st_mtim.tv_sec; + if (ino == FUSE_ROOT_ID) + { + stbuf.st_nlink = 2; + } + else + { + stbuf.st_nlink = 1; + } + fuse_reply_attr(req, &stbuf, timeout); + } +} + +static void xf_cliprdr_fuse_readdir(fuse_req_t req, fuse_ino_t ino, size_t size, off_t off, + struct fuse_file_info* fi) +{ + size_t count; + size_t index; + size_t child_ino; + size_t direntry_len; + char* buf = (char*)calloc(size, sizeof(char)); + size_t pos = 0; + xfCliprdrFuseInode* child_node; + xfCliprdrFuseInode* node; + xfClipboard* clipboard = (xfClipboard*)fuse_req_userdata(req); + + ArrayList_Lock(clipboard->ino_list); + node = (xfCliprdrFuseInode*)ArrayList_GetItem(clipboard->ino_list, ino - 1); + ArrayList_Unlock(clipboard->ino_list); + if (!node || !node->child_inos) + { + fuse_reply_err(req, ENOENT); + return; + } + else if ((node->st_mode & S_IFDIR) == 0) + { + fuse_reply_err(req, ENOTDIR); + return; + } + + ArrayList_Lock(node->child_inos); + count = ArrayList_Count(node->child_inos); + if (count == 0 || count + 1 <= off) + { + fuse_reply_buf(req, NULL, 0); + } + else + { + for (index = off; index < count + 2; index++) + { + if (index == 0) + { + struct stat stbuf; + stbuf.st_ino = ino; + direntry_len = fuse_add_direntry(req, NULL, 0, ".", NULL, index); + if (pos + direntry_len > size) + { + break; + } + fuse_add_direntry(req, buf + pos, size - pos, ".", &stbuf, index); + pos += direntry_len; + } + else if (index == 1) + { + struct stat stbuf; + stbuf.st_ino = node->parent_ino; + direntry_len = fuse_add_direntry(req, NULL, 0, "..", NULL, index); + if (pos + direntry_len > size) + { + break; + } + fuse_add_direntry(req, buf + pos, size - pos, "..", &stbuf, index); + pos += direntry_len; + } + else + { + child_ino = (size_t)ArrayList_GetItem(node->child_inos, index - 2); + ArrayList_Lock(clipboard->ino_list); + child_node = + (xfCliprdrFuseInode*)ArrayList_GetItem(clipboard->ino_list, child_ino - 1); + ArrayList_Unlock(clipboard->ino_list); + // child_node must exists? + direntry_len = fuse_add_direntry(req, NULL, 0, child_node->name, NULL, index); + if (pos + direntry_len > size) + { + break; + } + + struct stat stbuf; + stbuf.st_ino = child_node->ino; + fuse_add_direntry(req, buf + pos, size - pos, child_node->name, &stbuf, index); + pos += direntry_len; + } + } + + fuse_reply_buf(req, buf, pos); + free(buf); + } + ArrayList_Unlock(node->child_inos); +} + +static void xf_cliprdr_fuse_open(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info* fi) +{ + xfCliprdrFuseInode* node; + xfClipboard* clipboard = (xfClipboard*)fuse_req_userdata(req); + + ArrayList_Lock(clipboard->ino_list); + node = (xfCliprdrFuseInode*)ArrayList_GetItem(clipboard->ino_list, ino - 1); + ArrayList_Unlock(clipboard->ino_list); + if (!node) + { + fuse_reply_err(req, ENOENT); + } + else if ((node->st_mode & S_IFDIR) != 0) + { + fuse_reply_err(req, EISDIR); + } + else + { + /* Important for KDE to get file correctly*/ + fi->direct_io = 1; + fuse_reply_open(req, fi); + } +} + +static void xf_cliprdr_fuse_read(fuse_req_t req, fuse_ino_t ino, size_t size, off_t off, + struct fuse_file_info* fi) +{ + if (ino < 2) + { + fuse_reply_err(req, ENONET); + return; + } + xfCliprdrFuseInode* node; + xfClipboard* clipboard = (xfClipboard*)fuse_req_userdata(req); + + ArrayList_Lock(clipboard->ino_list); + node = (xfCliprdrFuseInode*)ArrayList_GetItem(clipboard->ino_list, ino - 1); + ArrayList_Unlock(clipboard->ino_list); + if (!node) + { + fuse_reply_err(req, ENONET); + return; + } + else if ((node->st_mode & S_IFDIR) != 0) + { + fuse_reply_err(req, EISDIR); + return; + } + + xfCliprdrFuseStream* stream = (xfCliprdrFuseStream*)calloc(1, sizeof(xfCliprdrFuseStream)); + + ArrayList_Lock(clipboard->stream_list); + stream->req = req; + stream->stream_id = clipboard->current_stream_id; + clipboard->current_stream_id++; + ArrayList_Add(clipboard->stream_list, stream); + ArrayList_Unlock(clipboard->stream_list); + + UINT32 nPositionLow = (off >> 0) & 0xFFFFFFFF; + UINT32 nPositionHigh = (off >> 32) & 0xFFFFFFFF; + + xf_cliprdr_send_client_file_contents(clipboard, stream->stream_id, node->lindex, nPositionLow, + nPositionHigh, size); +} + +static void xf_cliprdr_fuse_lookup(fuse_req_t req, fuse_ino_t parent, const char* name) +{ + size_t index; + size_t count; + size_t child_ino; + BOOL found = FALSE; + struct fuse_entry_param e; + xfCliprdrFuseInode* parent_node; + xfCliprdrFuseInode* child_node; + xfClipboard* clipboard = (xfClipboard*)fuse_req_userdata(req); + + ArrayList_Lock(clipboard->ino_list); + parent_node = (xfCliprdrFuseInode*)ArrayList_GetItem(clipboard->ino_list, parent - 1); + ArrayList_Unlock(clipboard->ino_list); + if (!parent_node || !parent_node->child_inos) + { + fuse_reply_err(req, ENOENT); + return; + } + + ArrayList_Lock(parent_node->child_inos); + count = ArrayList_Count(parent_node->child_inos); + for (index = 0; index < count; index++) + { + child_ino = (size_t)ArrayList_GetItem(parent_node->child_inos, index); + ArrayList_Lock(clipboard->ino_list); + child_node = (xfCliprdrFuseInode*)ArrayList_GetItem(clipboard->ino_list, child_ino - 1); + ArrayList_Unlock(clipboard->ino_list); + if (child_node && strcmp(name, child_node->name) == 0) + { + found = TRUE; + break; + } + } + ArrayList_Unlock(parent_node->child_inos); + if (found == TRUE) + { + memset(&e, 0, sizeof(e)); + e.ino = child_node->ino; + e.attr_timeout = 1.0; + e.entry_timeout = 1.0; + e.attr.st_ino = child_node->ino; + e.attr.st_mode = child_node->st_mode; + e.attr.st_nlink = 1; + e.attr.st_size = child_node->st_size; + e.attr.st_mtime = child_node->st_mtim.tv_sec; + fuse_reply_entry(req, &e); + } + else + { + fuse_reply_err(req, ENOENT); + } +} + +static void xf_cliprdr_fuse_opendir(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info* fi) +{ + xfCliprdrFuseInode* node; + xfClipboard* clipboard = (xfClipboard*)fuse_req_userdata(req); + + ArrayList_Lock(clipboard->ino_list); + node = (xfCliprdrFuseInode*)ArrayList_GetItem(clipboard->ino_list, ino - 1); + ArrayList_Unlock(clipboard->ino_list); + if (!node) + { + fuse_reply_err(req, ENOENT); + } + else if ((node->st_mode & S_IFDIR) == 0) + { + fuse_reply_err(req, ENOTDIR); + } + else + { + fuse_reply_open(req, fi); + } +} + +static struct fuse_lowlevel_ops xf_cliprdr_fuse_oper = { + .lookup = xf_cliprdr_fuse_lookup, + .getattr = xf_cliprdr_fuse_getattr, + .readdir = xf_cliprdr_fuse_readdir, + .open = xf_cliprdr_fuse_open, + .read = xf_cliprdr_fuse_read, + .opendir = xf_cliprdr_fuse_opendir, +}; + +static DWORD WINAPI xf_cliprdr_fuse_thread(LPVOID arg) +{ + xfClipboard* clipboard = (xfClipboard*)arg; + + /* TODO: set up a filesystem base path for local URI */ + /* TODO get basePath from config or use default*/ + UINT32 basePathSize; + char* basePath; + char* tmpPath; + tmpPath = GetKnownPath(KNOWN_PATH_TEMP); + /* 10 is max length of DWORD string and 1 for \0 */ + basePathSize = strlen(tmpPath) + strlen("/.xfreerdp.cliprdr.") + 11; + basePath = calloc(basePathSize, sizeof(char)); + _snprintf(&basePath[0], basePathSize, "%s/.xfreerdp.cliprdr.%d", tmpPath, + GetCurrentProcessId()); + free(tmpPath); + + if (!PathFileExistsA(basePath) && !PathMakePathA(basePath, 0)) + { + WLog_ERR(TAG, "Failed to create directory '%s'", basePath); + free(basePath); + return 0; + } + clipboard->delegate->basePath = basePath; + + struct fuse_chan* ch; + struct fuse_args args = FUSE_ARGS_INIT(0, NULL); + int err; + + if ((ch = fuse_mount(clipboard->delegate->basePath, &args)) != NULL) + { + clipboard->fuse_sess = fuse_lowlevel_new(&args, &xf_cliprdr_fuse_oper, + sizeof(xf_cliprdr_fuse_oper), (void*)clipboard); + if (clipboard->fuse_sess != NULL) + { + if (fuse_set_signal_handlers(clipboard->fuse_sess) != -1) + { + fuse_session_add_chan(clipboard->fuse_sess, ch); + err = fuse_session_loop(clipboard->fuse_sess); + fuse_remove_signal_handlers(clipboard->fuse_sess); + fuse_session_remove_chan(ch); + } + fuse_session_destroy(clipboard->fuse_sess); + } + fuse_unmount(clipboard->delegate->basePath, ch); + } + fuse_opt_free_args(&args); + + if (PathFileExistsA(clipboard->delegate->basePath)) + { + RemoveDirectoryA(clipboard->delegate->basePath); + } + + ExitThread(0); + return 0; +} + xfClipboard* xf_clipboard_new(xfContext* xfc) { int i, n = 0; @@ -1735,6 +2328,8 @@ xfClipboard* xf_clipboard_new(xfContext* xfc) n++; clipboard->clientFormats[n].atom = XInternAtom(xfc->display, "UTF8_STRING", False); clipboard->clientFormats[n].formatId = CF_UNICODETEXT; + /* This line for nautilus based file managers, beacuse they use UTF8_STRING for file-list */ + clipboard->clientFormats[n].formatName = _strdup("FileGroupDescriptorW"); n++; clipboard->clientFormats[n].atom = XA_STRING; clipboard->clientFormats[n].formatId = CF_TEXT; @@ -1778,6 +2373,33 @@ xfClipboard* xf_clipboard_new(xfContext* xfc) n++; } + if (ClipboardGetFormatId(clipboard->system, "x-special/gnome-copied-files")) + { + clipboard->file_formats_registered = TRUE; + clipboard->clientFormats[n].atom = + XInternAtom(xfc->display, "x-special/gnome-copied-files", False); + clipboard->clientFormats[n].formatId = CB_FORMAT_GNOMECOPIEDFILES; + clipboard->clientFormats[n].formatName = _strdup("FileGroupDescriptorW"); + + if (!clipboard->clientFormats[n].formatName) + goto error; + + n++; + } + + if (ClipboardGetFormatId(clipboard->system, "x-special/mate-copied-files")) + { + clipboard->file_formats_registered = TRUE; + clipboard->clientFormats[n].atom = + XInternAtom(xfc->display, "x-special/mate-copied-files", False); + clipboard->clientFormats[n].formatId = CB_FORMAT_MATECOPIEDFILES; + clipboard->clientFormats[n].formatName = _strdup("FileGroupDescriptorW"); + + if (!clipboard->clientFormats[n].formatName) + goto error; + + n++; + } clipboard->numClientFormats = n; clipboard->targets[0] = XInternAtom(xfc->display, "TIMESTAMP", FALSE); @@ -1786,8 +2408,22 @@ xfClipboard* xf_clipboard_new(xfContext* xfc) clipboard->incr_atom = XInternAtom(xfc->display, "INCR", FALSE); clipboard->delegate = ClipboardGetDelegate(clipboard->system); clipboard->delegate->custom = clipboard; - /* TODO: set up a filesystem base path for local URI */ - /* clipboard->delegate->basePath = "file:///tmp/foo/bar/gaga"; */ + + clipboard->current_stream_id = 0; + clipboard->stream_list = ArrayList_New(TRUE); + wObject* obj = ArrayList_Object(clipboard->stream_list); + obj->fnObjectFree = free; + + clipboard->ino_list = ArrayList_New(TRUE); + obj = ArrayList_Object(clipboard->ino_list); + obj->fnObjectFree = xf_cliprdr_fuse_inode_free; + + if (!(clipboard->fuse_thread = + CreateThread(NULL, 0, xf_cliprdr_fuse_thread, clipboard, 0, NULL))) + { + goto error; + } + clipboard->delegate->ClipboardFileSizeSuccess = xf_cliprdr_clipboard_file_size_success; clipboard->delegate->ClipboardFileSizeFailure = xf_cliprdr_clipboard_file_size_failure; clipboard->delegate->ClipboardFileRangeSuccess = xf_cliprdr_clipboard_file_range_success; @@ -1825,6 +2461,26 @@ void xf_clipboard_free(xfClipboard* clipboard) free(clipboard->clientFormats[i].formatName); } + if (clipboard->fuse_thread) + { + if (clipboard->fuse_sess) + { + fuse_session_exit(clipboard->fuse_sess); + /* not elegant but works for umounting FUSE + fuse_chan must receieve a oper buf to unblock fuse_session_receive_buf function. + */ + PathFileExistsA(clipboard->delegate->basePath); + } + WaitForSingleObject(clipboard->fuse_thread, INFINITE); + CloseHandle(clipboard->fuse_thread); + } + + free(clipboard->delegate->basePath); + + // fuse related + ArrayList_Free(clipboard->stream_list); + ArrayList_Free(clipboard->ino_list); + ClipboardDestroy(clipboard->system); free(clipboard->data); free(clipboard->data_raw); @@ -1845,6 +2501,7 @@ void xf_cliprdr_init(xfContext* xfc, CliprdrClientContext* cliprdr) cliprdr->ServerFormatDataRequest = xf_cliprdr_server_format_data_request; cliprdr->ServerFormatDataResponse = xf_cliprdr_server_format_data_response; cliprdr->ServerFileContentsRequest = xf_cliprdr_server_file_contents_request; + cliprdr->ServerFileContentsResponse = xf_cliprdr_server_file_contents_response; } void xf_cliprdr_uninit(xfContext* xfc, CliprdrClientContext* cliprdr) diff --git a/cmake/FindFUSE.cmake b/cmake/FindFUSE.cmake new file mode 100644 index 000000000..bd178e26a --- /dev/null +++ b/cmake/FindFUSE.cmake @@ -0,0 +1,34 @@ +# Find the FUSE includes and library +# +# FUSE_INCLUDE_DIR - where to find fuse.h, etc. +# FUSE_LIBRARIES - List of libraries when using FUSE. +# FUSE_FOUND - True if FUSE lib is found. + +# check if already in cache, be silent +IF (FUSE_INCLUDE_DIR) + SET (FUSE_FIND_QUIETLY TRUE) +ENDIF (FUSE_INCLUDE_DIR) + +# find includes +FIND_PATH (FUSE_INCLUDE_DIR fuse.h + /usr/local/include/osxfuse + /usr/local/include + /usr/include + ) + +# find lib +if (APPLE) + SET(FUSE_NAMES libosxfuse.dylib fuse) +else (APPLE) + SET(FUSE_NAMES fuse) +endif (APPLE) +FIND_LIBRARY(FUSE_LIBRARIES + NAMES ${FUSE_NAMES} + PATHS /lib64 /lib /usr/lib64 /usr/lib /usr/local/lib64 /usr/local/lib /usr/lib/x86_64-linux-gnu + ) + +include ("FindPackageHandleStandardArgs") +find_package_handle_standard_args ("FUSE" DEFAULT_MSG + FUSE_INCLUDE_DIR FUSE_LIBRARIES) + +mark_as_advanced (FUSE_INCLUDE_DIR FUSE_LIBRARIES) \ No newline at end of file diff --git a/include/freerdp/channels/cliprdr.h b/include/freerdp/channels/cliprdr.h index 857e7af10..2f4571084 100644 --- a/include/freerdp/channels/cliprdr.h +++ b/include/freerdp/channels/cliprdr.h @@ -36,6 +36,8 @@ #define CB_FORMAT_JPEG 0xD012 #define CB_FORMAT_GIF 0xD013 #define CB_FORMAT_TEXTURILIST 0xD014 +#define CB_FORMAT_GNOMECOPIEDFILES 0xD015 +#define CB_FORMAT_MATECOPIEDFILES 0xD016 /* CLIPRDR_HEADER.msgType */ #define CB_MONITOR_READY 0x0001 diff --git a/winpr/libwinpr/clipboard/posix.c b/winpr/libwinpr/clipboard/posix.c index 12c110fed..8a9795405 100644 --- a/winpr/libwinpr/clipboard/posix.c +++ b/winpr/libwinpr/clipboard/posix.c @@ -589,6 +589,7 @@ static void* convert_filedescriptors_to_uri_list(wClipboard* clipboard, UINT32 f size_t count, x, alloc, pos, baseLength = 0; const char* src = (const char*)data; char* dst; + size_t decoration_len; if (!clipboard || !data || !pSize) return NULL; @@ -616,14 +617,24 @@ static void* convert_filedescriptors_to_uri_list(wClipboard* clipboard, UINT32 f if (formatId != ClipboardGetFormatId(clipboard, "FileGroupDescriptorW")) return NULL; + decoration_len = strlen("file:///\n") + baseLength; alloc = 0; - /* Get total size of file names */ + /* Get total size of file/folder names under first level folder only */ for (x = 0; x < count; x++) - alloc += _wcsnlen(descriptors[x].cFileName, ARRAYSIZE(descriptors[x].cFileName)); + { + if (_wcschr(descriptors[x].cFileName, L'\\') == NULL) + { + size_t curLen = _wcsnlen(descriptors[x].cFileName, ARRAYSIZE(descriptors[x].cFileName)); + alloc += WideCharToMultiByte(CP_UTF8, 0, descriptors[x].cFileName, (int)curLen, NULL, 0, + NULL, NULL); + alloc += decoration_len; + } + } - /* Append a prefix file:// and postfix \r\n for each file */ - alloc += (sizeof("/\r\n") + baseLength) * count; + /* Append a prefix file:// and postfix \n for each file */ + /* We need to keep last \n since snprintf is null terminated!! */ + alloc++; dst = calloc(alloc, sizeof(char)); if (!dst) @@ -633,20 +644,18 @@ static void* convert_filedescriptors_to_uri_list(wClipboard* clipboard, UINT32 f for (x = 0; x < count; x++) { + if (_wcschr(descriptors[x].cFileName, L'\\') != NULL) + { + continue; + } int rc; const FILEDESCRIPTORW* cur = &descriptors[x]; size_t curLen = _wcsnlen(cur->cFileName, ARRAYSIZE(cur->cFileName)); char* curName = NULL; rc = ConvertFromUnicode(CP_UTF8, 0, cur->cFileName, (int)curLen, &curName, 0, NULL, NULL); - if (rc != (int)curLen) - { - free(curName); - free(dst); - return NULL; - } - - rc = _snprintf(&dst[pos], alloc - pos, "%s/%s\r\n", clipboard->delegate.basePath, curName); + rc = _snprintf(&dst[pos], alloc - pos, "file://%s/%s\n", clipboard->delegate.basePath, + curName); free(curName); if (rc < 0) @@ -663,15 +672,104 @@ static void* convert_filedescriptors_to_uri_list(wClipboard* clipboard, UINT32 f return dst; } +/* Prepend header of common gnome format to file list*/ +static void* convert_filedescriptors_to_gnome_copied_files(wClipboard* clipboard, UINT32 formatId, + const void* data, UINT32* pSize) +{ + void* pDstData = convert_filedescriptors_to_uri_list(clipboard, formatId, data, pSize); + if (!pDstData) + { + return pDstData; + } + const char* header = "copy\n"; + size_t header_len = strlen(header); + *pSize = *pSize + header_len; + char* pNewDstData = calloc(*pSize, sizeof(char)); + _snprintf(pNewDstData, *pSize, "%s%s", header, (char*)pDstData); + free(pDstData); + return pNewDstData; +} + +/* Prepend header of nautilus based filemanager's format to file list*/ +static void* convert_filedescriptors_to_nautilus_clipboard(wClipboard* clipboard, UINT32 formatId, + const void* data, UINT32* pSize) +{ + void* pDstData = convert_filedescriptors_to_uri_list(clipboard, formatId, data, pSize); + if (!pDstData) + { + return pDstData; + } + const char* header = "x-special/nautilus-clipboard\ncopy\n"; + size_t header_len = strlen(header); + /* Here Nemo (and Caja) have different behavior. They encounter error with the last \n . but + nautilus needs it. So user have to skip Nemo's error dialog to continue. Caja has different + TARGET , so it's easy to fix. see convert_filedescriptors_to_mate_copied_files + */ + /* see nautilus/src/nautilus-clipboard.c:convert_selection_data_to_str_list + see nemo/libnemo-private/nemo-clipboard.c:nemo_clipboard_get_uri_list_from_selection_data + */ + *pSize = *pSize + header_len; + char* pNewDstData = calloc(*pSize, sizeof(char)); + _snprintf(pNewDstData, *pSize, "%s%s", header, (char*)pDstData); + free(pDstData); + return pNewDstData; +} + +static void* convert_filedescriptors_to_mate_copied_files(wClipboard* clipboard, UINT32 formatId, + const void* data, UINT32* pSize) +{ + void* pDstData = convert_filedescriptors_to_uri_list(clipboard, formatId, data, pSize); + if (!pDstData) + { + return pDstData; + } + const char* header = "copy\n"; + size_t header_len = strlen(header); + /* Drop last \n. + see + mate-desktop/caja/libcaja-private/caja-clipboard.c:caja_clipboard_get_uri_list_from_selection_data + */ + *pSize = *pSize + header_len - 1; + char* pNewDstData = calloc(*pSize, sizeof(char)); + _snprintf(pNewDstData, *pSize, "%s%s", header, (char*)pDstData); + free(pDstData); + return pNewDstData; +} + static BOOL register_file_formats_and_synthesizers(wClipboard* clipboard) { wObject* obj; UINT32 file_group_format_id; UINT32 local_file_format_id; + UINT32 local_gnome_file_format_id; + UINT32 local_mate_file_format_id; + UINT32 local_nautilus_file_format_id; file_group_format_id = ClipboardRegisterFormat(clipboard, "FileGroupDescriptorW"); local_file_format_id = ClipboardRegisterFormat(clipboard, "text/uri-list"); - if (!file_group_format_id || !local_file_format_id) + /* + 1. Gnome Nautilus based file manager: + TARGET: UTF8_STRING + format: x-special/nautilus-clipboard\copy\n\file://path\n\0 + 2. Kde Dolpin: + TARGET: text/uri-list + format: file://path\n\0 + 3. Gnome others (Unity/XFCE): + TARGET: x-special/gnome-copied-files + format: copy\nfile://path\n\0 + 4. Mate Caja: + TARGET: x-special/mate-copied-files + format: copy\nfile://path\n + + TODO: other file managers do not use previous targets and formats. + */ + + local_gnome_file_format_id = ClipboardRegisterFormat(clipboard, "x-special/gnome-copied-files"); + local_mate_file_format_id = ClipboardRegisterFormat(clipboard, "x-special/mate-copied-files"); + local_nautilus_file_format_id = ClipboardRegisterFormat(clipboard, "UTF8_STRING"); + + if (!file_group_format_id || !local_file_format_id || !local_gnome_file_format_id || + !local_mate_file_format_id || !local_nautilus_file_format_id) goto error; clipboard->localFiles = ArrayList_New(FALSE); @@ -692,6 +790,18 @@ static BOOL register_file_formats_and_synthesizers(wClipboard* clipboard) convert_filedescriptors_to_uri_list)) goto error_free_local_files; + if (!ClipboardRegisterSynthesizer(clipboard, file_group_format_id, local_gnome_file_format_id, + convert_filedescriptors_to_gnome_copied_files)) + goto error_free_local_files; + + if (!ClipboardRegisterSynthesizer(clipboard, file_group_format_id, local_mate_file_format_id, + convert_filedescriptors_to_mate_copied_files)) + goto error_free_local_files; + if (!ClipboardRegisterSynthesizer(clipboard, file_group_format_id, + local_nautilus_file_format_id, + convert_filedescriptors_to_nautilus_clipboard)) + goto error_free_local_files; + return TRUE; error_free_local_files: ArrayList_Free(clipboard->localFiles);