summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJakub Janků <jjanku@redhat.com>2020-07-03 18:30:08 +0200
committerFrediano Ziglio <freddy77@gmail.com>2020-07-31 12:48:28 +0100
commit630da73bf869a69b906b1d3e0aa1f6d00c9c632d (patch)
tree27b52c858adf3802d778bd10890938f303aabf47
parent6fea9f59a760cbc1655de0d11a8abfa6567ae951 (diff)
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@redhat.com> Acked-by: Frediano Ziglio <fziglio@redhat.com>
-rw-r--r--CMakeLists.txt2
-rw-r--r--Makefile.am2
m---------spice-protocol0
-rw-r--r--vdagent/vdagent.cpp191
4 files changed, 194 insertions, 1 deletions
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
-Subproject 2d3324b8999b06442fd92c03b260c08a5110152
+Subproject 7689b6922be9099aab3327e8b2dfb890f69799e
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@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;