[Spice-commits] CMakeLists.txt Makefile.am spice-protocol vdagent/vdagent.cpp

GitLab Mirror gitlab-mirror at kemper.freedesktop.org
Fri Jul 31 11:49:32 UTC 2020


 CMakeLists.txt      |    2 
 Makefile.am         |    2 
 spice-protocol      |    2 
 vdagent/vdagent.cpp |  191 ++++++++++++++++++++++++++++++++++++++++++++++++++++
 4 files changed, 195 insertions(+), 2 deletions(-)

New commits:
commit 630da73bf869a69b906b1d3e0aa1f6d00c9c632d
Author: Jakub Janků <jjanku at redhat.com>
Date:   Fri Jul 3 18:30:08 2020 +0200

    clipboard: enable copying files from client using webdav
    
    When the user wants to copy files, new spice-gtk can share those
    files using the existing phodav server. In that case, it advertises
    the VD_AGENT_CLIPBOARD_FILE_LIST type in the clipboard grab message.
    
    Upon request of the clipboard data in the mentioned type, spice-gtk
    provides a list of absolute paths in the phodav server - these are
    the files that are supposed to be copied/moved.
    
    The role of the vdagent is to adjust this data given the drive
    letter of the mapped webdav share.
    
    Files can be both copied and moved, although move was tested
    only with Windows' File Explorer.
    
    Copying files from the vdagnet side to the client is not
    supported yet.
    
    Signed-off-by: Jakub Janků <jjanku at redhat.com>
    Acked-by: Frediano Ziglio <fziglio at redhat.com>

diff --git a/CMakeLists.txt b/CMakeLists.txt
index c8f441c..bf67b4a 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -113,6 +113,8 @@ target_link_libraries(vdagent
     uuid
     ole32
     oleaut32
+    mpr
+    shlwapi
     ${COMMSUPPW_LIBRARY}
 )
 
diff --git a/Makefile.am b/Makefile.am
index 6e464c7..425b052 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -19,7 +19,7 @@ endif
 
 bin_PROGRAMS = vdagent vdservice
 
-vdagent_LDADD = $(LIBPNG_LIBS) $(ZLIB_LIBS) -lwtsapi32 -lgdi32 -luuid -lole32 -loleaut32 vdagent_rc.$(OBJEXT)
+vdagent_LDADD = $(LIBPNG_LIBS) $(ZLIB_LIBS) -lwtsapi32 -lgdi32 -luuid -lole32 -loleaut32 -lmpr -lshlwapi vdagent_rc.$(OBJEXT)
 vdagent_CXXFLAGS = $(AM_CXXFLAGS) $(LIBPNG_CFLAGS)
 vdagent_LDFLAGS = $(AM_LDFLAGS) -Wl,--subsystem,windows
 vdagent_SOURCES =			\
diff --git a/spice-protocol b/spice-protocol
index 2d3324b..7689b69 160000
--- a/spice-protocol
+++ b/spice-protocol
@@ -1 +1 @@
-Subproject commit 2d3324b8999b06442fd92c03b260c08a51101521
+Subproject commit 7689b6922be9099aab3327e8b2dfb890f69799e3
diff --git a/vdagent/vdagent.cpp b/vdagent/vdagent.cpp
index 4d73676..e8bb974 100644
--- a/vdagent/vdagent.cpp
+++ b/vdagent/vdagent.cpp
@@ -29,6 +29,9 @@
 #include <set>
 #include <vector>
 #include <array>
+#include <shlobj.h>
+#include <shlwapi.h>
+#include <algorithm>
 
 #define VD_AGENT_LOG_PATH       TEXT("%svdagent.log")
 #define VD_AGENT_WINCLASS_NAME  TEXT("VDAGENT")
@@ -52,6 +55,7 @@ static const VDClipboardFormat clipboard_formats[] = {
     {CF_UNICODETEXT, {VD_AGENT_CLIPBOARD_UTF8_TEXT, 0}},
     //FIXME: support more image types
     {CF_DIB, {VD_AGENT_CLIPBOARD_IMAGE_PNG, VD_AGENT_CLIPBOARD_IMAGE_BMP, 0}},
+    {CF_HDROP, {VD_AGENT_CLIPBOARD_FILE_LIST, 0}},
 };
 
 #define clipboard_formats_count SPICE_N_ELEMENTS(clipboard_formats)
@@ -166,6 +170,8 @@ private:
     std::set<uint32_t> _grab_types;
 
     VDLog* _log;
+
+    UINT _cb_format_drop_effect;
 };
 
 VDAgent* VDAgent::_singleton = NULL;
@@ -337,6 +343,7 @@ bool VDAgent::run()
     send_announce_capabilities(true);
     vd_printf("Connected to server");
 
+    _cb_format_drop_effect = RegisterClipboardFormat(CFSTR_PREFERREDDROPEFFECT);
     while (_running) {
         input_desktop_message_loop();
         if (_clipboard_owner == owner_guest) {
@@ -716,6 +723,159 @@ bool VDAgent::handle_mon_config(const VDAgentMonitorsConfig* mon_config, uint32_
     return true;
 }
 
+static std::wstring spice_webdav_get_drive_letter()
+{
+    std::wstring drive(L"Z:");
+    static const wchar_t spice_folder[] = L"\\\\localhost at 9843\\DavWWWRoot";
+    wchar_t remote[MAX_PATH];
+
+    DWORD drives = GetLogicalDrives();
+
+    /* spice-webdavd assigns drive letter from the end of the alphabet */
+    for (int i = 25; i >= 0; i--) {
+        int mask = 1 << i;
+        if (drives & mask) {
+            drive[0] = 'A' + i;
+            DWORD size = SPICE_N_ELEMENTS(remote);
+
+            if (WNetGetConnection(drive.c_str(), remote, &size) == NO_ERROR &&
+                _wcsicmp(remote, spice_folder) == 0) {
+                return drive;
+            }
+        }
+    }
+
+    return L"";
+}
+
+static std::wstring utf8_to_wchar(LPCSTR utf8)
+{
+    int len = MultiByteToWideChar(CP_UTF8, 0, utf8, -1, nullptr, 0);
+    if (len == 0) {
+        return {};
+    }
+
+    std::wstring wchr;
+    wchr.resize(len);
+
+    len = MultiByteToWideChar(CP_UTF8, 0, utf8, -1, &wchr[0], len);
+    if (len == 0) {
+        DWORD err = GetLastError();
+        vd_printf("err converting to wchar: %lu", err);
+        return {};
+    }
+    wchr.resize(len-1);
+
+    return wchr;
+}
+
+static std::vector<wchar_t> paths_to_wchar_array(const std::vector<std::wstring> &paths)
+{
+    std::vector<wchar_t> arr;
+
+    for (auto path: paths) {
+        arr.insert(arr.end(), path.begin(), path.end());
+        arr.push_back(L'\0');
+    }
+    /* Windows requires the array to be double-NULL-terminated */
+    arr.push_back(L'\0');
+
+    return arr;
+}
+
+static std::vector<wchar_t> clipboard_data_to_path_array(
+    std::wstring drive, LPCSTR data, uint32_t size, DWORD &drop_effect_out)
+{
+    drop_effect_out = DROPEFFECT_NONE;
+
+    if (!data || size < 1) {
+        return {};
+    }
+
+    if (data[size-1]) {
+        vd_printf("received list of paths that is not null-terminated");
+        return {};
+    }
+
+    if (!strcmp(data, "copy")) {
+        drop_effect_out = DROPEFFECT_COPY;
+    } else if (!strcmp(data, "cut")) {
+        drop_effect_out = DROPEFFECT_MOVE;
+    } else {
+        vd_printf("first line of path list must specify clipboard action");
+        return {};
+    }
+    /* skip the action line, since we're only interested in
+     * the actual paths from this point forward  */
+    int len = strlen(data) + 1;
+    data += len;
+    size -= len;
+
+    wchar_t buf[MAX_PATH];
+    std::vector<std::wstring> paths;
+
+    for (LPCSTR item = data; item < data + size; item += strlen(item) + 1) {
+        auto wpath = utf8_to_wchar(item);
+
+        std::replace(wpath.begin(), wpath.end(), L'/', L'\\');
+        if (!wpath.length() || !PathCombine(buf, drive.c_str(), wpath.c_str())) {
+            continue;
+        }
+
+        paths.push_back(std::wstring(buf));
+    }
+    vd_printf("received %zu paths", paths.size());
+
+    return paths_to_wchar_array(paths);
+}
+
+static HANDLE get_dropfiles_handle(const std::vector<wchar_t>& path_arr)
+{
+    HANDLE clip_data = GlobalAlloc(GHND, sizeof(DROPFILES) +
+        path_arr.size() * sizeof(wchar_t));
+
+    if (!clip_data) {
+        return NULL;
+    }
+
+    uint8_t *d = (uint8_t *)GlobalLock(clip_data);
+    LPDROPFILES drop = (LPDROPFILES)d;
+    if (!drop) {
+        GlobalFree(clip_data);
+        return NULL;
+    }
+
+    drop->pFiles = sizeof(DROPFILES); /* offset of the file array */
+    drop->pt.x = 0; /* drop point, not used */
+    drop->pt.y = 0;
+    drop->fNC = FALSE; /* related to pt, not used */
+    drop->fWide = TRUE; /* contains wide chars */
+    std::copy(path_arr.begin(), path_arr.end(), (wchar_t *)(d + sizeof(DROPFILES)));
+
+    GlobalUnlock(clip_data);
+
+    return clip_data;
+}
+
+static HANDLE get_drop_effect_handle(DWORD effect)
+{
+    HANDLE clip_data = GlobalAlloc(GHND, sizeof(DWORD));
+
+    if (!clip_data) {
+        return NULL;
+    }
+
+    DWORD *e = (DWORD *)GlobalLock(clip_data);
+    if (!e) {
+        GlobalFree(clip_data);
+        return NULL;
+    }
+    *e = effect;
+    GlobalUnlock(clip_data);
+
+    return clip_data;
+}
+
 bool VDAgent::handle_clipboard(const VDAgentClipboard* clipboard, uint32_t size)
 {
     HANDLE clip_data;
@@ -738,6 +898,21 @@ bool VDAgent::handle_clipboard(const VDAgentClipboard* clipboard, uint32_t size)
     case VD_AGENT_CLIPBOARD_IMAGE_BMP:
         clip_data = get_image_handle(*clipboard, size, format);
         break;
+    case VD_AGENT_CLIPBOARD_FILE_LIST: {
+        /* FIXME: does this block? cache it? */
+        std::wstring drive = spice_webdav_get_drive_letter();
+        if (drive.empty()) {
+            vd_printf("Spice webdav not mapped");
+            goto fin;
+        }
+        DWORD drop_effect;
+        std::vector<wchar_t> path_arr = clipboard_data_to_path_array(drive,
+            (LPCSTR)clipboard->data, size, drop_effect);
+        SetClipboardData(_cb_format_drop_effect, get_drop_effect_handle(drop_effect));
+        clip_data = get_dropfiles_handle(path_arr);
+        format = CF_HDROP;
+        break;
+    }
     default:
         vd_printf("Unsupported clipboard type %u", clipboard->type);
         goto fin;
@@ -979,6 +1154,10 @@ void VDAgent::on_clipboard_grab()
         return;
     }
     for (unsigned int i = 0; i < clipboard_formats_count; i++) {
+        if (clipboard_formats[i].format == CF_HDROP) {
+            /* only unidirectional support atm */
+            continue;
+        }
         if (IsClipboardFormatAvailable(clipboard_formats[i].format)) {
             for (const uint32_t* ptype = clipboard_formats[i].types; *ptype; ptype++) {
                 types[count++] = *ptype;
@@ -1077,6 +1256,9 @@ bool VDAgent::handle_clipboard_grab(const VDAgentClipboardGrab* clipboard_grab,
         vd_printf("No supported clipboard types in client grab");
         return true;
     }
+    if (grab_formats.find(CF_HDROP) != grab_formats.end()) {
+        SetClipboardData(_cb_format_drop_effect, NULL);
+    }
     CloseClipboard();
     set_clipboard_owner(owner_client);
     return true;
@@ -1103,6 +1285,10 @@ bool VDAgent::handle_clipboard_request(const VDAgentClipboardRequest* clipboard_
         vd_printf("Unsupported clipboard type %u", clipboard_request->type);
         return false;
     }
+    if (format == CF_HDROP) {
+        /* only unidirectional support atm */
+        return false;
+    }
     // on encoding only, we use HBITMAP to keep the correct palette
     if (format == CF_DIB) {
         format = CF_BITMAP;
@@ -1198,6 +1384,11 @@ uint32_t VDAgent::get_clipboard_type(uint32_t format) const
 {
     const uint32_t* types = NULL;
 
+    if (format == _cb_format_drop_effect) {
+        /* type is the same for both of these formats */
+        format = CF_HDROP;
+    }
+
     for (unsigned int i = 0; i < clipboard_formats_count && !types; i++) {
         if (clipboard_formats[i].format == format) {
             types = clipboard_formats[i].types;


More information about the Spice-commits mailing list