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> srcpath(src.length() + 2, 0);
27 std::vector<TCHAR> dstpath(dst.length() + 2, 0);
28 memcpy(&srcpath[0], src.c_str(), src.length() * sizeof(TCHAR));
29 memcpy(&dstpath[0], dst.c_str(), dst.length() * sizeof(TCHAR));
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 for (WORD x = 0; x < wNumFilesDropped; x++)
51 // Get the number of bytes required by the file's full pathname
52 UINT wPathnameSize = DragQueryFile(dropInfo, x, nullptr, 0);
54 // Allocate memory to contain full pathname & zero byte
56 std::unique_ptr<TCHAR[]> npszFile(new TCHAR[wPathnameSize]);
58 // Copy the pathname into the buffer
59 DragQueryFile(dropInfo, x, npszFile.get(), wPathnameSize);
61 files.push_back(npszFile.get());
66 std::vector<String> FilterFiles(const std::vector<String>& files_src)
68 std::vector<String> files(files_src);
69 TCHAR szTempPath[MAX_PATH];
70 TCHAR szTempPathShort[MAX_PATH];
71 GetTempPath(sizeof(szTempPath) / sizeof(szTempPath[0]), szTempPath);
72 GetShortPathName(szTempPath, szTempPathShort, sizeof(szTempPathShort) / sizeof(szTempPathShort[0]));
74 for (UINT i = 0; i < files.size(); i++)
76 if (paths::IsShortcut(files[i]))
78 // if this was a shortcut, we need to expand it to the target path
79 String expandedFile = paths::ExpandShortcut(files[i]);
81 // if that worked, we should have a real file name
82 if (!expandedFile.empty())
83 files[i] = expandedFile;
85 else if (paths::IsDecendant(files[i], szTempPath) || paths::IsDecendant(files[i], szTempPathShort))
87 String tmpdir = env::GetTempChildPath();
88 CopyFileOrFolder(files[i], tmpdir);
89 files[i] = paths::ConcatPath(tmpdir, paths::FindFileName(files[i]));
95 HRESULT IStream_WriteToFile(IStream *pStream, const String& filename)
97 unique_handle hFile(CreateFile(filename.c_str(), GENERIC_WRITE, 0, nullptr, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr));
98 if (hFile.get() == INVALID_HANDLE_VALUE)
105 HRESULT hr = pStream->Read(buf, sizeof(buf), &size);
110 WriteFile(hFile.get(), buf, size, &dwWritten, nullptr);
115 HRESULT HGLOBAL_WriteToFile(HGLOBAL hGlobal, const String& filename)
117 unique_handle hFile(CreateFile(filename.c_str(), GENERIC_WRITE, 0, nullptr, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr));
118 if (hFile.get() == INVALID_HANDLE_VALUE)
120 char *p = static_cast<char *>(GlobalLock(hGlobal));
123 SIZE_T size = GlobalSize(hGlobal);
127 if (WriteFile(hFile.get(), p, (size > INT_MAX) ? INT_MAX : static_cast<DWORD>(size), &dwWritten, nullptr) == FALSE)
129 GlobalUnlock(hGlobal);
135 GlobalUnlock(hGlobal);
139 HRESULT SetFileWriteTime(const String& filename, const FILETIME& writetime)
141 unique_handle hFile(CreateFile(filename.c_str(), GENERIC_WRITE, 0, nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr));
142 if (hFile.get() == INVALID_HANDLE_VALUE)
144 return SetFileTime(hFile.get(), nullptr, nullptr, &writetime) ? S_OK : E_FAIL;
147 HRESULT GetFileItemsFromIDataObject_CF_HDROP(IDataObject *pDataObj, std::vector<String>& files)
149 FORMATETC fmtetc_cf_hdrop = { CF_HDROP, nullptr, DVASPECT_CONTENT, -1, TYMED_HGLOBAL };
150 STGMEDIUM medium = { 0 };
152 if ((hr = pDataObj->GetData(&fmtetc_cf_hdrop, &medium)) == S_OK)
154 HDROP hDrop = (HDROP)GlobalLock(medium.hGlobal);
155 if (hDrop != nullptr)
157 files = FilterFiles(GetDroppedFiles(hDrop));
158 GlobalUnlock(medium.hGlobal);
160 ReleaseStgMedium(&medium);
165 #define HIDA_GetPIDLFolder(pida) (LPCITEMIDLIST)(((LPBYTE)pida)+(pida)->aoffset[0])
166 #define HIDA_GetPIDLItem(pida, i) (LPCITEMIDLIST)(((LPBYTE)pida)+(pida)->aoffset[i+1])
168 HRESULT GetFileItemsFromIDataObject_ShellIDList(IDataObject *pDataObj, std::vector<String>& root_files)
171 FORMATETC fmtetc_filedescriptor = { static_cast<WORD>(RegisterClipboardFormat(CFSTR_SHELLIDLIST)), nullptr, DVASPECT_CONTENT, -1, TYMED_HGLOBAL };
172 STGMEDIUM medium = { 0 };
174 if ((hr = pDataObj->GetData(&fmtetc_filedescriptor, &medium)) == S_OK)
176 CIDA *pcida = (CIDA *)GlobalLock(medium.hGlobal);
177 if (pcida != nullptr)
179 LPCITEMIDLIST pidlParent = HIDA_GetPIDLFolder(pcida);
180 for (unsigned i = 0; i < pcida->cidl; ++i)
182 IShellItemPtr pShellItem;
183 if (SUCCEEDED(hr = SHCreateShellItem(pidlParent, nullptr, HIDA_GetPIDLItem(pcida, i), &pShellItem)))
186 if (SUCCEEDED(hr = pShellItem->GetAttributes(SFGAO_FOLDER, &sfgaof)) && (sfgaof & SFGAO_FOLDER))
189 wchar_t *pPath = nullptr;
190 if (SUCCEEDED(hr = pShellItem->GetDisplayName(SIGDN_DESKTOPABSOLUTEPARSING, &pPath)))
192 root_files.push_back(ucr::toTString(pPath));
193 CoTaskMemFree(pPath);
199 IFileOperationPtr pFileOperation;
200 if (SUCCEEDED(hr = pFileOperation.CreateInstance(CLSID_FileOperation, nullptr, CLSCTX_ALL)))
203 tmpdir = env::GetTempChildPath();
204 pFileOperation->SetOperationFlags(0);
205 PIDLIST_ABSOLUTE pidlDest;
206 if (SUCCEEDED(hr = SHParseDisplayName(ucr::toUTF16(tmpdir).c_str(), nullptr, &pidlDest, 0, nullptr)))
208 IShellItemPtr pShellItemDest;
209 SHCreateShellItem(nullptr, nullptr, pidlDest, &pShellItemDest);
210 pFileOperation->CopyItem(pShellItem, pShellItemDest, nullptr, nullptr);
211 if (SUCCEEDED(hr = pFileOperation->PerformOperations()))
214 if (SUCCEEDED(hr = pShellItem->GetDisplayName(SIGDN_PARENTRELATIVEPARSING, &pName)))
216 root_files.push_back(paths::ConcatPath(tmpdir, ucr::toTString(pName)));
217 CoTaskMemFree(pName);
225 GlobalUnlock(medium.hGlobal);
227 ReleaseStgMedium(&medium);
232 HRESULT ExtractFileItemFromIDataObject_FileContents(IDataObject *pDataObj, int lindex, const String& filepath)
234 FORMATETC fmtetc_filecontents = { static_cast<WORD>(RegisterClipboardFormat(CFSTR_FILECONTENTS)), nullptr, DVASPECT_CONTENT, lindex, TYMED_HGLOBAL | TYMED_ISTREAM };
235 STGMEDIUM medium = { 0 };
237 if ((hr = pDataObj->GetData(&fmtetc_filecontents, &medium)) == S_OK)
240 if (medium.tymed == TYMED_HGLOBAL)
241 hr = HGLOBAL_WriteToFile(medium.hGlobal, filepath);
242 else if (medium.tymed == TYMED_ISTREAM)
243 hr = IStream_WriteToFile(medium.pstm, filepath);
244 ReleaseStgMedium(&medium);
249 HRESULT GetFileItemsFromIDataObject_FileDescriptor(IDataObject *pDataObj, std::vector<String>& root_files)
251 String tmpdir = env::GetTempChildPath();
252 FORMATETC fmtetc_filedescriptor = { static_cast<WORD>(RegisterClipboardFormat(CFSTR_FILEDESCRIPTOR)), nullptr, DVASPECT_CONTENT, -1, TYMED_HGLOBAL };
253 STGMEDIUM medium = { 0 };
255 if ((hr = pDataObj->GetData(&fmtetc_filedescriptor, &medium)) == S_OK)
257 FILEGROUPDESCRIPTOR *file_group_descriptor = (FILEGROUPDESCRIPTOR *)GlobalLock(medium.hGlobal);
258 if (file_group_descriptor)
260 for (unsigned i = 0; i < file_group_descriptor->cItems; ++i)
262 String filename = file_group_descriptor->fgd[i].cFileName;
263 String filepath = paths::ConcatPath(tmpdir, filename);
264 if (file_group_descriptor->fgd[i].dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
265 paths::CreateIfNeeded(filepath);
268 ExtractFileItemFromIDataObject_FileContents(pDataObj, i, filepath);
269 if (file_group_descriptor->fgd[i].dwFlags & FD_WRITESTIME)
270 SetFileWriteTime(filepath, file_group_descriptor->fgd[i].ftLastWriteTime);
272 if (filename.find('\\') == String::npos)
273 root_files.push_back(filepath);
275 GlobalUnlock(medium.hGlobal);
277 ReleaseStgMedium(&medium);
284 DropHandler::DropHandler(std::function<void(const std::vector<String>&)> callback)
285 : m_cRef(0), m_callback(callback)
289 DropHandler::~DropHandler()
293 HRESULT STDMETHODCALLTYPE DropHandler::QueryInterface(REFIID riid, void **ppvObject)
295 if (!IsEqualIID(riid, IID_IUnknown) && !IsEqualIID(riid, IID_IDropTarget))
297 *ppvObject = nullptr;
298 return E_NOINTERFACE;
300 *ppvObject = static_cast<IDropTarget *>(this);
305 ULONG STDMETHODCALLTYPE DropHandler::AddRef(void)
307 return InterlockedIncrement(&m_cRef);
310 ULONG STDMETHODCALLTYPE DropHandler::Release(void)
312 ULONG cRef = InterlockedDecrement(&m_cRef);
320 HRESULT STDMETHODCALLTYPE DropHandler::DragEnter(IDataObject* pDataObj, DWORD grfKeyState, POINTL pt, DWORD* pdwEffect)
322 FORMATETC fmtetc_cf_hdrop = { CF_HDROP, nullptr, DVASPECT_CONTENT, -1, TYMED_HGLOBAL };
323 FORMATETC fmtetc_shellidlist = { static_cast<WORD>(RegisterClipboardFormat(CFSTR_SHELLIDLIST)), nullptr, DVASPECT_CONTENT, -1, TYMED_HGLOBAL };
324 FORMATETC fmtetc_filedescriptor = { static_cast<WORD>(RegisterClipboardFormat(CFSTR_FILEDESCRIPTOR)), nullptr, DVASPECT_CONTENT, -1, TYMED_HGLOBAL };
325 if (pDataObj->QueryGetData(&fmtetc_cf_hdrop) == S_OK ||
326 pDataObj->QueryGetData(&fmtetc_shellidlist) == S_OK ||
327 pDataObj->QueryGetData(&fmtetc_filedescriptor) == S_OK)
329 *pdwEffect = DROPEFFECT_COPY;
332 *pdwEffect = DROPEFFECT_NONE;
333 return DRAGDROP_S_CANCEL;
336 HRESULT STDMETHODCALLTYPE DropHandler::DragOver(DWORD, POINTL, DWORD *)
341 HRESULT STDMETHODCALLTYPE DropHandler::DragLeave(void)
346 HRESULT DropHandler::Drop(IDataObject* pDataObj, DWORD grfKeyState, POINTL pt, DWORD* pdwEffect)
349 CWaitCursor waitstatus;
350 std::vector<String> files;
351 FORMATETC fmtetc_cf_hdrop = { CF_HDROP, nullptr, DVASPECT_CONTENT, -1, TYMED_HGLOBAL };
352 FORMATETC fmtetc_shellidlist = { static_cast<WORD>(RegisterClipboardFormat(CFSTR_SHELLIDLIST)), nullptr, DVASPECT_CONTENT, -1, TYMED_HGLOBAL };
353 FORMATETC fmtetc_filedescriptor = { static_cast<WORD>(RegisterClipboardFormat(CFSTR_FILEDESCRIPTOR)), nullptr, DVASPECT_CONTENT, -1, TYMED_HGLOBAL };
354 if (pDataObj->QueryGetData(&fmtetc_cf_hdrop) == S_OK &&
355 GetFileItemsFromIDataObject_CF_HDROP(pDataObj, files) == S_OK && files.size() > 0)
357 else if (pDataObj->QueryGetData(&fmtetc_shellidlist) == S_OK &&
358 GetFileItemsFromIDataObject_ShellIDList(pDataObj, files) == S_OK && files.size() > 0)
360 else if (pDataObj->QueryGetData(&fmtetc_filedescriptor) == S_OK &&
361 GetFileItemsFromIDataObject_FileDescriptor(pDataObj, files) == S_OK && files.size() > 0)
363 if (files.size() > 3)
367 return ok ? S_OK : E_FAIL;