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. + } } }