From f33c02f4f7d2faa1d5e88b52410fdc718c5de68c Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Fri, 9 Jul 2021 20:49:54 +0200 Subject: [PATCH] Add a proof of concept drag-and-drop snooper This doesn't do anything useful yet, but it will print the names of files that are being dragged and dropped on the Wine server (using the OLE API) while the plugin is open. --- src/wine-host/xdnd-proxy.cpp | 115 ++++++++++++++++++++++++++++++++--- 1 file changed, 107 insertions(+), 8 deletions(-) diff --git a/src/wine-host/xdnd-proxy.cpp b/src/wine-host/xdnd-proxy.cpp index 725a5d03..f30f9cc2 100644 --- a/src/wine-host/xdnd-proxy.cpp +++ b/src/wine-host/xdnd-proxy.cpp @@ -16,17 +16,116 @@ #include "xdnd-proxy.h" -void CALLBACK dnd_winevent_callback(HWINEVENTHOOK hWinEventHook, +// FIXME: Remove +#include + +/** + * The window class name Wine uses for its `DoDragDrop()` tracker window. + * + * https://github.com/wine-mirror/wine/blob/d10887b8f56792ebcca717ccc28a289f7bcaf107/dlls/ole32/ole2.c#L101-L104 + */ +static constexpr char OLEDD_DRAGTRACKERCLASS[] = "WineDragDropTracker32"; + +/** + * Part of the struct Wine uses to keep track of the data during an OLE + * drag-and-drop operation. We only really care about the first field that + * contains the actual data. + * + * https://github.com/wine-mirror/wine/blob/d10887b8f56792ebcca717ccc28a289f7bcaf107/dlls/ole32/ole2.c#L54-L73 + */ +struct TrackerWindowInfo { + IDataObject* dataObject; + IDropSource* dropSource; + // ... more fields that we don't need +}; + +void CALLBACK dnd_winevent_callback(HWINEVENTHOOK /*hWinEventHook*/, DWORD event, HWND hwnd, LONG idObject, - LONG idChild, - DWORD idEventThread, - DWORD dwmsEventTime) { - // TODO: `EVENT_OBJECT_DESTROY` doesn't seem to be implemented by Wine, so - // we can't rely on that. - if (event == EVENT_OBJECT_CREATE && idObject == OBJID_WINDOW) { - // TODO + LONG /*idChild*/, + DWORD /*idEventThread*/, + DWORD /*dwmsEventTime*/) { + // FIXME: Prevent this from being run by multiple processes at the same time + + // XXX: `EVENT_OBJECT_DESTROY` doesn't seem to be implemented by Wine, so we + // can't rely on that. + if (!(event == EVENT_OBJECT_CREATE && idObject == OBJID_WINDOW)) { + return; + } + + // Wine's drag-and-drop tracker windows always have the same window + // class name, so we can easily identify them + { + std::array window_class_name{0}; + GetClassName(hwnd, window_class_name.data(), window_class_name.size()); + if (strcmp(window_class_name.data(), OLEDD_DRAGTRACKERCLASS) != 0) { + return; + } + } + + // They apaprently use 0 instead of `GWLP_USERDATA` to store the tracker + // data + auto tracker_info = + reinterpret_cast(GetWindowLongPtr(hwnd, 0)); + if (!tracker_info) { + return; + } + + IEnumFORMATETC* enumerator = nullptr; + tracker_info->dataObject->EnumFormatEtc(DATADIR_GET, &enumerator); + if (!enumerator) { + return; + } + + std::array supported_formats; + unsigned int num_formats = 0; + enumerator->Next(supported_formats.size(), supported_formats.data(), + &num_formats); + enumerator->Release(); + for (unsigned int format_idx = 0; format_idx < num_formats; format_idx++) { + STGMEDIUM storage{}; + if (HRESULT result = tracker_info->dataObject->GetData( + &supported_formats[format_idx], &storage); + result == S_OK) { + switch (storage.tymed) { + case TYMED_HGLOBAL: { + auto drop = static_cast(GlobalLock(storage.hGlobal)); + + std::array file_name{0}; + const uint32_t num_files = DragQueryFileW( + drop, 0xFFFFFFFF, file_name.data(), file_name.size()); + + std::cerr << "Plugin wanted to drag-and-drop " << num_files + << " files:" << std::endl; + for (uint32_t file_idx = 0; file_idx < num_files; + file_idx++) { + file_name[0] = 0; + DragQueryFileW(drop, file_idx, file_name.data(), + file_name.size()); + + std::cerr << "- " + << wine_get_unix_file_name(file_name.data()) + << std::endl; + } + + GlobalUnlock(GlobalLock(storage.hGlobal)); + } break; + case TYMED_FILE: { + std::cerr << "Plugin wanted to drag-and-drop '" + << wine_get_unix_file_name(storage.lpszFileName) + << "'" << std::endl; + } break; + default: { + std::cerr << "Unknown format " << storage.tymed + << std::endl; + } break; + } + + // We won't release the storage, because we're not actually doing + // the Windows drag and drop. We're just snooping in on the data + // that's being dragged. + } } }