2 #include "DropHandler.h"
4 #pragma warning (push) // prevent "warning C4091: 'typedef ': ignored on left of 'tagGPFIDL_FLAGS' when no variable is declared"
5 #pragma warning (disable:4091) // VC bug when using XP enabled toolsets.
10 #include "Environment.h"
15 struct HandleDeleter {
16 typedef HANDLE pointer;
17 void operator()(HANDLE h) { if (h != nullptr && h != INVALID_HANDLE_VALUE) ::CloseHandle(h); }
20 typedef std::unique_ptr<HANDLE, HandleDeleter> unique_handle;
21 typedef _com_ptr_t<_com_IIID<IFileOperation, &__uuidof(IFileOperation)>> IFileOperationPtr;
22 typedef _com_ptr_t<_com_IIID<IShellItem, &__uuidof(IShellItem)>> IShellItemPtr;
24 bool CopyFileOrFolder(const String& src, const String& dst)
26 std::vector<tchar_t> srcpath(src.length() + 2, 0);
27 std::vector<tchar_t> dstpath(dst.length() + 2, 0);
28 memcpy(&srcpath[0], src.c_str(), src.length() * sizeof(tchar_t));
29 memcpy(&dstpath[0], dst.c_str(), dst.length() * sizeof(tchar_t));
30 SHFILEOPSTRUCT fileop = { 0, FO_COPY, &srcpath[0], &dstpath[0], FOF_NOCONFIRMATION, 0, 0, 0 };
31 return SHFileOperation(&fileop) == 0;
35 // OnDropFiles code from CDropEdit
36 // Copyright 1997 Chris Losinger
38 // shortcut expansion code modified from :
39 // CShortcut, 1996 Rob Warner
42 std::vector<String> GetDroppedFiles(HDROP dropInfo)
44 std::vector<String> files;
45 // Get the number of pathnames that have been dropped
46 UINT wNumFilesDropped = DragQueryFile(dropInfo, 0xFFFFFFFF, nullptr, 0);
48 // get all file names. but we'll only need the first one.
49 files.reserve(wNumFilesDropped);
50 for (UINT x = 0; x < wNumFilesDropped; x++)
52 // Get the number of bytes required by the file's full pathname
53 UINT wPathnameSize = DragQueryFile(dropInfo, x, nullptr, 0);
55 // Allocate memory to contain full pathname & zero byte
57 auto npszFile = std::make_unique<tchar_t[]>(wPathnameSize);
59 // Copy the pathname into the buffer
60 DragQueryFile(dropInfo, x, npszFile.get(), wPathnameSize);
62 files.push_back(npszFile.get());
67 std::vector<String> FilterFiles(const std::vector<String>& files_src)
69 std::vector<String> files(files_src);
70 tchar_t szTempPath[MAX_PATH];
71 tchar_t szTempPathShort[MAX_PATH];
72 GetTempPath(sizeof(szTempPath) / sizeof(szTempPath[0]), szTempPath);
73 GetShortPathName(szTempPath, szTempPathShort, sizeof(szTempPathShort) / sizeof(szTempPathShort[0]));
75 for (UINT i = 0; i < files.size(); i++)
77 if (paths::IsShortcut(files[i]))
79 // if this was a shortcut, we need to expand it to the target path
80 String expandedFile = paths::ExpandShortcut(files[i]);
82 // if that worked, we should have a real file name
83 if (!expandedFile.empty())
84 files[i] = expandedFile;
86 else if (paths::IsDecendant(files[i], szTempPath) || paths::IsDecendant(files[i], szTempPathShort))
88 String tmpdir = env::GetTempChildPath();
89 CopyFileOrFolder(files[i], tmpdir);
90 files[i] = paths::ConcatPath(tmpdir, paths::FindFileName(files[i]));
96 HRESULT IStream_WriteToFile(IStream *pStream, const String& filename)
98 unique_handle hFile(CreateFile(filename.c_str(), GENERIC_WRITE, 0, nullptr, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr));
99 if (hFile.get() == INVALID_HANDLE_VALUE)
106 HRESULT hr = pStream->Read(buf, sizeof(buf), &size);
111 WriteFile(hFile.get(), buf, size, &dwWritten, nullptr);
116 HRESULT HGLOBAL_WriteToFile(HGLOBAL hGlobal, const String& filename)
118 unique_handle hFile(CreateFile(filename.c_str(), GENERIC_WRITE, 0, nullptr, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr));
119 if (hFile.get() == INVALID_HANDLE_VALUE)
121 char *p = static_cast<char *>(GlobalLock(hGlobal));
124 SIZE_T size = GlobalSize(hGlobal);
128 if (WriteFile(hFile.get(), p, (size > INT_MAX) ? INT_MAX : static_cast<DWORD>(size), &dwWritten, nullptr) == FALSE)
130 GlobalUnlock(hGlobal);
136 GlobalUnlock(hGlobal);
140 HRESULT SetFileWriteTime(const String& filename, const FILETIME& writetime)
142 unique_handle hFile(CreateFile(filename.c_str(), GENERIC_WRITE, 0, nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr));
143 if (hFile.get() == INVALID_HANDLE_VALUE)
145 return SetFileTime(hFile.get(), nullptr, nullptr, &writetime) ? S_OK : E_FAIL;
148 HRESULT GetFileItemsFromIDataObject_CF_HDROP(IDataObject *pDataObj, std::vector<String>& files)
150 FORMATETC fmtetc_cf_hdrop = { CF_HDROP, nullptr, DVASPECT_CONTENT, -1, TYMED_HGLOBAL };
151 STGMEDIUM medium = { 0 };
153 if ((hr = pDataObj->GetData(&fmtetc_cf_hdrop, &medium)) == S_OK)
155 HDROP hDrop = (HDROP)GlobalLock(medium.hGlobal);
156 if (hDrop != nullptr)
158 files = FilterFiles(GetDroppedFiles(hDrop));
159 GlobalUnlock(medium.hGlobal);
161 ReleaseStgMedium(&medium);
166 #define HIDA_GetPIDLFolder(pida) (LPCITEMIDLIST)(((LPBYTE)pida)+(pida)->aoffset[0])
167 #define HIDA_GetPIDLItem(pida, i) (LPCITEMIDLIST)(((LPBYTE)pida)+(pida)->aoffset[i+1])
169 HRESULT GetFileItemsFromIDataObject_ShellIDList(IDataObject *pDataObj, std::vector<String>& root_files)
172 FORMATETC fmtetc_filedescriptor = { static_cast<WORD>(RegisterClipboardFormat(CFSTR_SHELLIDLIST)), nullptr, DVASPECT_CONTENT, -1, TYMED_HGLOBAL };
173 STGMEDIUM medium = { 0 };
175 if ((hr = pDataObj->GetData(&fmtetc_filedescriptor, &medium)) == S_OK)
177 CIDA *pcida = (CIDA *)GlobalLock(medium.hGlobal);
178 if (pcida != nullptr)
180 LPCITEMIDLIST pidlParent = HIDA_GetPIDLFolder(pcida);
181 for (unsigned i = 0; i < pcida->cidl; ++i)
183 IShellItemPtr pShellItem;
184 if (SUCCEEDED(hr = SHCreateShellItem(pidlParent, nullptr, HIDA_GetPIDLItem(pcida, i), &pShellItem)))
187 if (SUCCEEDED(hr = pShellItem->GetAttributes(SFGAO_FOLDER, &sfgaof)) && (sfgaof & SFGAO_FOLDER))
190 wchar_t *pPath = nullptr;
191 if (SUCCEEDED(hr = pShellItem->GetDisplayName(SIGDN_DESKTOPABSOLUTEPARSING, &pPath)))
193 root_files.push_back(ucr::toTString(pPath));
194 CoTaskMemFree(pPath);
200 IFileOperationPtr pFileOperation;
201 if (SUCCEEDED(hr = pFileOperation.CreateInstance(CLSID_FileOperation, nullptr, CLSCTX_ALL)))
204 tmpdir = env::GetTempChildPath();
205 pFileOperation->SetOperationFlags(0);
206 PIDLIST_ABSOLUTE pidlDest;
207 if (SUCCEEDED(hr = SHParseDisplayName(ucr::toUTF16(tmpdir).c_str(), nullptr, &pidlDest, 0, nullptr)))
209 IShellItemPtr pShellItemDest;
210 SHCreateShellItem(nullptr, nullptr, pidlDest, &pShellItemDest);
211 pFileOperation->CopyItem(pShellItem, pShellItemDest, nullptr, nullptr);
212 if (SUCCEEDED(hr = pFileOperation->PerformOperations()))
215 if (SUCCEEDED(hr = pShellItem->GetDisplayName(SIGDN_PARENTRELATIVEPARSING, &pName)))
217 root_files.push_back(paths::ConcatPath(tmpdir, ucr::toTString(pName)));
218 CoTaskMemFree(pName);
226 GlobalUnlock(medium.hGlobal);
228 ReleaseStgMedium(&medium);
233 HRESULT ExtractFileItemFromIDataObject_FileContents(IDataObject *pDataObj, int lindex, const String& filepath)
235 FORMATETC fmtetc_filecontents = { static_cast<WORD>(RegisterClipboardFormat(CFSTR_FILECONTENTS)), nullptr, DVASPECT_CONTENT, lindex, TYMED_HGLOBAL | TYMED_ISTREAM };
236 STGMEDIUM medium = { 0 };
238 if ((hr = pDataObj->GetData(&fmtetc_filecontents, &medium)) == S_OK)
241 if (medium.tymed == TYMED_HGLOBAL)
242 hr = HGLOBAL_WriteToFile(medium.hGlobal, filepath);
243 else if (medium.tymed == TYMED_ISTREAM)
244 hr = IStream_WriteToFile(medium.pstm, filepath);
245 ReleaseStgMedium(&medium);
250 HRESULT GetFileItemsFromIDataObject_FileDescriptor(IDataObject *pDataObj, std::vector<String>& root_files)
252 String tmpdir = env::GetTempChildPath();
253 FORMATETC fmtetc_filedescriptor = { static_cast<WORD>(RegisterClipboardFormat(CFSTR_FILEDESCRIPTOR)), nullptr, DVASPECT_CONTENT, -1, TYMED_HGLOBAL };
254 STGMEDIUM medium = { 0 };
256 if ((hr = pDataObj->GetData(&fmtetc_filedescriptor, &medium)) == S_OK)
258 FILEGROUPDESCRIPTOR *file_group_descriptor = (FILEGROUPDESCRIPTOR *)GlobalLock(medium.hGlobal);
259 if (file_group_descriptor)
261 for (unsigned i = 0; i < file_group_descriptor->cItems; ++i)
263 String filename = file_group_descriptor->fgd[i].cFileName;
264 String filepath = paths::ConcatPath(tmpdir, filename);
265 if (file_group_descriptor->fgd[i].dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
266 paths::CreateIfNeeded(filepath);
269 ExtractFileItemFromIDataObject_FileContents(pDataObj, i, filepath);
270 if (file_group_descriptor->fgd[i].dwFlags & FD_WRITESTIME)
271 SetFileWriteTime(filepath, file_group_descriptor->fgd[i].ftLastWriteTime);
273 if (filename.find('\\') == String::npos)
274 root_files.push_back(filepath);
276 GlobalUnlock(medium.hGlobal);
278 ReleaseStgMedium(&medium);
285 DropHandler::DropHandler(std::function<void(const std::vector<String>&)> callback)
286 : m_cRef(0), m_callback(callback)
290 DropHandler::~DropHandler() = default;
292 HRESULT STDMETHODCALLTYPE DropHandler::QueryInterface(REFIID riid, void **ppvObject)
294 if (!IsEqualIID(riid, IID_IUnknown) && !IsEqualIID(riid, IID_IDropTarget))
296 *ppvObject = nullptr;
297 return E_NOINTERFACE;
299 *ppvObject = static_cast<IDropTarget *>(this);
304 ULONG STDMETHODCALLTYPE DropHandler::AddRef(void)
306 return InterlockedIncrement(&m_cRef);
309 ULONG STDMETHODCALLTYPE DropHandler::Release(void)
311 ULONG cRef = InterlockedDecrement(&m_cRef);
319 HRESULT STDMETHODCALLTYPE DropHandler::DragEnter(IDataObject* pDataObj, DWORD grfKeyState, POINTL pt, DWORD* pdwEffect)
321 FORMATETC fmtetc_cf_hdrop = { CF_HDROP, nullptr, DVASPECT_CONTENT, -1, TYMED_HGLOBAL };
322 FORMATETC fmtetc_shellidlist = { static_cast<WORD>(RegisterClipboardFormat(CFSTR_SHELLIDLIST)), nullptr, DVASPECT_CONTENT, -1, TYMED_HGLOBAL };
323 FORMATETC fmtetc_filedescriptor = { static_cast<WORD>(RegisterClipboardFormat(CFSTR_FILEDESCRIPTOR)), nullptr, DVASPECT_CONTENT, -1, TYMED_HGLOBAL };
324 if (pDataObj->QueryGetData(&fmtetc_cf_hdrop) == S_OK ||
325 pDataObj->QueryGetData(&fmtetc_shellidlist) == S_OK ||
326 pDataObj->QueryGetData(&fmtetc_filedescriptor) == S_OK)
328 *pdwEffect = DROPEFFECT_COPY;
331 *pdwEffect = DROPEFFECT_NONE;
332 return DRAGDROP_S_CANCEL;
335 HRESULT STDMETHODCALLTYPE DropHandler::DragOver(DWORD, POINTL, DWORD *)
340 HRESULT STDMETHODCALLTYPE DropHandler::DragLeave(void)
345 HRESULT DropHandler::Drop(IDataObject* pDataObj, DWORD grfKeyState, POINTL pt, DWORD* pdwEffect)
348 CWaitCursor waitstatus;
349 std::vector<String> files;
350 FORMATETC fmtetc_cf_hdrop = { CF_HDROP, nullptr, DVASPECT_CONTENT, -1, TYMED_HGLOBAL };
351 FORMATETC fmtetc_shellidlist = { static_cast<WORD>(RegisterClipboardFormat(CFSTR_SHELLIDLIST)), nullptr, DVASPECT_CONTENT, -1, TYMED_HGLOBAL };
352 FORMATETC fmtetc_filedescriptor = { static_cast<WORD>(RegisterClipboardFormat(CFSTR_FILEDESCRIPTOR)), nullptr, DVASPECT_CONTENT, -1, TYMED_HGLOBAL };
353 if (pDataObj->QueryGetData(&fmtetc_cf_hdrop) == S_OK &&
354 GetFileItemsFromIDataObject_CF_HDROP(pDataObj, files) == S_OK && files.size() > 0)
356 else if (pDataObj->QueryGetData(&fmtetc_shellidlist) == S_OK &&
357 GetFileItemsFromIDataObject_ShellIDList(pDataObj, files) == S_OK && files.size() > 0)
359 else if (pDataObj->QueryGetData(&fmtetc_filedescriptor) == S_OK &&
360 GetFileItemsFromIDataObject_FileDescriptor(pDataObj, files) == S_OK && files.size() > 0)
362 if (files.size() > 3)
366 return ok ? S_OK : E_FAIL;