| // Support back to Vista | |
| #define _WIN32_WINNT _WIN32_WINNT_VISTA | |
| #include <sdkddkver.h> | |
| // Use WRL to define a classic COM class | |
| #define __WRL_CLASSIC_COM__ | |
| #include <wrl.h> | |
| #include <windows.h> | |
| #include <shlobj.h> | |
| #include <shlwapi.h> | |
| #include <olectl.h> | |
| #include <strsafe.h> | |
| #define DDWM_UPDATEWINDOW (WM_USER+3) | |
| static HINSTANCE hModule; | |
| static CLIPFORMAT cfDropDescription; | |
| static CLIPFORMAT cfDragWindow; | |
| #define CLASS_GUID "{BEA218D2-6950-497B-9434-61683EC065FE}" | |
| static const LPCWSTR CLASS_SUBKEY = L"Software\\Classes\\CLSID\\" CLASS_GUID; | |
| static const LPCWSTR DRAG_MESSAGE = L"Open with %1"; | |
| using namespace Microsoft::WRL; | |
| HRESULT FilenameListCchLengthA(LPCSTR pszSource, size_t cchMax, size_t *pcchLength, size_t *pcchCount) { | |
| HRESULT hr = S_OK; | |
| size_t count = 0; | |
| size_t length = 0; | |
| while (pszSource && pszSource[0]) { | |
| size_t oneLength; | |
| hr = StringCchLengthA(pszSource, cchMax - length, &oneLength); | |
| if (FAILED(hr)) { | |
| return hr; | |
| } | |
| count += 1; | |
| length += oneLength + (strchr(pszSource, ' ') ? 3 : 1); | |
| pszSource = &pszSource[oneLength + 1]; | |
| } | |
| *pcchCount = count; | |
| *pcchLength = length; | |
| return hr; | |
| } | |
| HRESULT FilenameListCchLengthW(LPCWSTR pszSource, size_t cchMax, size_t *pcchLength, size_t *pcchCount) { | |
| HRESULT hr = S_OK; | |
| size_t count = 0; | |
| size_t length = 0; | |
| while (pszSource && pszSource[0]) { | |
| size_t oneLength; | |
| hr = StringCchLengthW(pszSource, cchMax - length, &oneLength); | |
| if (FAILED(hr)) { | |
| return hr; | |
| } | |
| count += 1; | |
| length += oneLength + (wcschr(pszSource, ' ') ? 3 : 1); | |
| pszSource = &pszSource[oneLength + 1]; | |
| } | |
| *pcchCount = count; | |
| *pcchLength = length; | |
| return hr; | |
| } | |
| HRESULT FilenameListCchCopyA(STRSAFE_LPSTR pszDest, size_t cchDest, LPCSTR pszSource, LPCSTR pszSeparator) { | |
| HRESULT hr = S_OK; | |
| size_t count = 0; | |
| size_t length = 0; | |
| while (pszSource[0]) { | |
| STRSAFE_LPSTR newDest; | |
| hr = StringCchCopyExA(pszDest, cchDest, pszSource, &newDest, &cchDest, 0); | |
| if (FAILED(hr)) { | |
| return hr; | |
| } | |
| pszSource += (newDest - pszDest) + 1; | |
| pszDest = PathQuoteSpacesA(pszDest) ? newDest + 2 : newDest; | |
| if (pszSource[0]) { | |
| hr = StringCchCopyExA(pszDest, cchDest, pszSeparator, &newDest, &cchDest, 0); | |
| if (FAILED(hr)) { | |
| return hr; | |
| } | |
| pszDest = newDest; | |
| } | |
| } | |
| return hr; | |
| } | |
| HRESULT FilenameListCchCopyW(STRSAFE_LPWSTR pszDest, size_t cchDest, LPCWSTR pszSource, LPCWSTR pszSeparator) { | |
| HRESULT hr = S_OK; | |
| size_t count = 0; | |
| size_t length = 0; | |
| while (pszSource[0]) { | |
| STRSAFE_LPWSTR newDest; | |
| hr = StringCchCopyExW(pszDest, cchDest, pszSource, &newDest, &cchDest, 0); | |
| if (FAILED(hr)) { | |
| return hr; | |
| } | |
| pszSource += (newDest - pszDest) + 1; | |
| pszDest = PathQuoteSpacesW(pszDest) ? newDest + 2 : newDest; | |
| if (pszSource[0]) { | |
| hr = StringCchCopyExW(pszDest, cchDest, pszSeparator, &newDest, &cchDest, 0); | |
| if (FAILED(hr)) { | |
| return hr; | |
| } | |
| pszDest = newDest; | |
| } | |
| } | |
| return hr; | |
| } | |
| class DECLSPEC_UUID(CLASS_GUID) PyShellExt : public RuntimeClass< | |
| RuntimeClassFlags<ClassicCom>, | |
| IDropTarget, | |
| IPersistFile | |
| > | |
| { | |
| LPOLESTR target, target_dir; | |
| DWORD target_mode; | |
| IDataObject *data_obj; | |
| public: | |
| PyShellExt() : target(NULL), target_dir(NULL), target_mode(0), data_obj(NULL) { | |
| OutputDebugString(L"PyShellExt::PyShellExt"); | |
| } | |
| ~PyShellExt() { | |
| if (target) { | |
| CoTaskMemFree(target); | |
| } | |
| if (target_dir) { | |
| CoTaskMemFree(target_dir); | |
| } | |
| if (data_obj) { | |
| data_obj->Release(); | |
| } | |
| } | |
| private: | |
| HRESULT UpdateDropDescription(IDataObject *pDataObj) { | |
| STGMEDIUM medium; | |
| FORMATETC fmt = { | |
| cfDropDescription, | |
| NULL, | |
| DVASPECT_CONTENT, | |
| -1, | |
| TYMED_HGLOBAL | |
| }; | |
| auto hr = pDataObj->GetData(&fmt, &medium); | |
| if (FAILED(hr)) { | |
| OutputDebugString(L"PyShellExt::UpdateDropDescription - failed to get DROPDESCRIPTION format"); | |
| return hr; | |
| } | |
| if (!medium.hGlobal) { | |
| OutputDebugString(L"PyShellExt::UpdateDropDescription - DROPDESCRIPTION format had NULL hGlobal"); | |
| ReleaseStgMedium(&medium); | |
| return E_FAIL; | |
| } | |
| auto dd = (DROPDESCRIPTION*)GlobalLock(medium.hGlobal); | |
| if (!dd) { | |
| OutputDebugString(L"PyShellExt::UpdateDropDescription - failed to lock DROPDESCRIPTION hGlobal"); | |
| ReleaseStgMedium(&medium); | |
| return E_FAIL; | |
| } | |
| StringCchCopy(dd->szMessage, sizeof(dd->szMessage) / sizeof(dd->szMessage[0]), DRAG_MESSAGE); | |
| StringCchCopy(dd->szInsert, sizeof(dd->szInsert) / sizeof(dd->szInsert[0]), PathFindFileNameW(target)); | |
| dd->type = DROPIMAGE_MOVE; | |
| GlobalUnlock(medium.hGlobal); | |
| ReleaseStgMedium(&medium); | |
| return S_OK; | |
| } | |
| HRESULT GetDragWindow(IDataObject *pDataObj, HWND *phWnd) { | |
| HRESULT hr; | |
| HWND *pMem; | |
| STGMEDIUM medium; | |
| FORMATETC fmt = { | |
| cfDragWindow, | |
| NULL, | |
| DVASPECT_CONTENT, | |
| -1, | |
| TYMED_HGLOBAL | |
| }; | |
| hr = pDataObj->GetData(&fmt, &medium); | |
| if (FAILED(hr)) { | |
| OutputDebugString(L"PyShellExt::GetDragWindow - failed to get DragWindow format"); | |
| return hr; | |
| } | |
| if (!medium.hGlobal) { | |
| OutputDebugString(L"PyShellExt::GetDragWindow - DragWindow format had NULL hGlobal"); | |
| ReleaseStgMedium(&medium); | |
| return E_FAIL; | |
| } | |
| pMem = (HWND*)GlobalLock(medium.hGlobal); | |
| if (!pMem) { | |
| OutputDebugString(L"PyShellExt::GetDragWindow - failed to lock DragWindow hGlobal"); | |
| ReleaseStgMedium(&medium); | |
| return E_FAIL; | |
| } | |
| *phWnd = *pMem; | |
| GlobalUnlock(medium.hGlobal); | |
| ReleaseStgMedium(&medium); | |
| return S_OK; | |
| } | |
| HRESULT GetArguments(IDataObject *pDataObj, LPCWSTR *pArguments) { | |
| HRESULT hr; | |
| DROPFILES *pdropfiles; | |
| STGMEDIUM medium; | |
| FORMATETC fmt = { | |
| CF_HDROP, | |
| NULL, | |
| DVASPECT_CONTENT, | |
| -1, | |
| TYMED_HGLOBAL | |
| }; | |
| hr = pDataObj->GetData(&fmt, &medium); | |
| if (FAILED(hr)) { | |
| OutputDebugString(L"PyShellExt::GetArguments - failed to get CF_HDROP format"); | |
| return hr; | |
| } | |
| if (!medium.hGlobal) { | |
| OutputDebugString(L"PyShellExt::GetArguments - CF_HDROP format had NULL hGlobal"); | |
| ReleaseStgMedium(&medium); | |
| return E_FAIL; | |
| } | |
| pdropfiles = (DROPFILES*)GlobalLock(medium.hGlobal); | |
| if (!pdropfiles) { | |
| OutputDebugString(L"PyShellExt::GetArguments - failed to lock CF_HDROP hGlobal"); | |
| ReleaseStgMedium(&medium); | |
| return E_FAIL; | |
| } | |
| if (pdropfiles->fWide) { | |
| LPCWSTR files = (LPCWSTR)((char*)pdropfiles + pdropfiles->pFiles); | |
| size_t len, count; | |
| hr = FilenameListCchLengthW(files, 32767, &len, &count); | |
| if (SUCCEEDED(hr)) { | |
| LPWSTR args = (LPWSTR)CoTaskMemAlloc(sizeof(WCHAR) * (len + 1)); | |
| if (args) { | |
| hr = FilenameListCchCopyW(args, 32767, files, L" "); | |
| if (SUCCEEDED(hr)) { | |
| *pArguments = args; | |
| } else { | |
| CoTaskMemFree(args); | |
| } | |
| } else { | |
| hr = E_OUTOFMEMORY; | |
| } | |
| } | |
| } else { | |
| LPCSTR files = (LPCSTR)((char*)pdropfiles + pdropfiles->pFiles); | |
| size_t len, count; | |
| hr = FilenameListCchLengthA(files, 32767, &len, &count); | |
| if (SUCCEEDED(hr)) { | |
| LPSTR temp = (LPSTR)CoTaskMemAlloc(sizeof(CHAR) * (len + 1)); | |
| if (temp) { | |
| hr = FilenameListCchCopyA(temp, 32767, files, " "); | |
| if (SUCCEEDED(hr)) { | |
| int wlen = MultiByteToWideChar(CP_ACP, 0, temp, (int)len, NULL, 0); | |
| if (wlen) { | |
| LPWSTR args = (LPWSTR)CoTaskMemAlloc(sizeof(WCHAR) * (wlen + 1)); | |
| if (MultiByteToWideChar(CP_ACP, 0, temp, (int)len, args, wlen + 1)) { | |
| *pArguments = args; | |
| } else { | |
| OutputDebugString(L"PyShellExt::GetArguments - failed to convert multi-byte to wide-char path"); | |
| CoTaskMemFree(args); | |
| hr = E_FAIL; | |
| } | |
| } else { | |
| OutputDebugString(L"PyShellExt::GetArguments - failed to get length of wide-char path"); | |
| hr = E_FAIL; | |
| } | |
| } | |
| CoTaskMemFree(temp); | |
| } else { | |
| hr = E_OUTOFMEMORY; | |
| } | |
| } | |
| } | |
| GlobalUnlock(medium.hGlobal); | |
| ReleaseStgMedium(&medium); | |
| return hr; | |
| } | |
| HRESULT NotifyDragWindow(HWND hwnd) { | |
| LRESULT res; | |
| if (!hwnd) { | |
| return S_FALSE; | |
| } | |
| res = SendMessage(hwnd, DDWM_UPDATEWINDOW, 0, NULL); | |
| if (res) { | |
| OutputDebugString(L"PyShellExt::NotifyDragWindow - failed to post DDWM_UPDATEWINDOW"); | |
| return E_FAIL; | |
| } | |
| return S_OK; | |
| } | |
| public: | |
| // IDropTarget implementation | |
| STDMETHODIMP DragEnter(IDataObject *pDataObj, DWORD grfKeyState, POINTL pt, DWORD *pdwEffect) { | |
| HWND hwnd; | |
| OutputDebugString(L"PyShellExt::DragEnter"); | |
| pDataObj->AddRef(); | |
| data_obj = pDataObj; | |
| *pdwEffect = DROPEFFECT_MOVE; | |
| if (FAILED(UpdateDropDescription(data_obj))) { | |
| OutputDebugString(L"PyShellExt::DragEnter - failed to update drop description"); | |
| } | |
| if (FAILED(GetDragWindow(data_obj, &hwnd))) { | |
| OutputDebugString(L"PyShellExt::DragEnter - failed to get drag window"); | |
| } | |
| if (FAILED(NotifyDragWindow(hwnd))) { | |
| OutputDebugString(L"PyShellExt::DragEnter - failed to notify drag window"); | |
| } | |
| return S_OK; | |
| } | |
| STDMETHODIMP DragLeave() { | |
| return S_OK; | |
| } | |
| STDMETHODIMP DragOver(DWORD grfKeyState, POINTL pt, DWORD *pdwEffect) { | |
| return S_OK; | |
| } | |
| STDMETHODIMP Drop(IDataObject *pDataObj, DWORD grfKeyState, POINTL pt, DWORD *pdwEffect) { | |
| LPCWSTR args; | |
| OutputDebugString(L"PyShellExt::Drop"); | |
| *pdwEffect = DROPEFFECT_NONE; | |
| if (pDataObj != data_obj) { | |
| OutputDebugString(L"PyShellExt::Drop - unexpected data object"); | |
| return E_FAIL; | |
| } | |
| data_obj->Release(); | |
| data_obj = NULL; | |
| if (SUCCEEDED(GetArguments(pDataObj, &args))) { | |
| OutputDebugString(args); | |
| ShellExecute(NULL, NULL, target, args, target_dir, SW_NORMAL); | |
| CoTaskMemFree((LPVOID)args); | |
| } else { | |
| OutputDebugString(L"PyShellExt::Drop - failed to get launch arguments"); | |
| } | |
| return S_OK; | |
| } | |
| // IPersistFile implementation | |
| STDMETHODIMP GetCurFile(LPOLESTR *ppszFileName) { | |
| HRESULT hr; | |
| size_t len; | |
| if (!ppszFileName) { | |
| return E_POINTER; | |
| } | |
| hr = StringCchLength(target, STRSAFE_MAX_CCH - 1, &len); | |
| if (FAILED(hr)) { | |
| return E_FAIL; | |
| } | |
| *ppszFileName = (LPOLESTR)CoTaskMemAlloc(sizeof(WCHAR) * (len + 1)); | |
| if (!*ppszFileName) { | |
| return E_OUTOFMEMORY; | |
| } | |
| hr = StringCchCopy(*ppszFileName, len + 1, target); | |
| if (FAILED(hr)) { | |
| CoTaskMemFree(*ppszFileName); | |
| *ppszFileName = NULL; | |
| return E_FAIL; | |
| } | |
| return S_OK; | |
| } | |
| STDMETHODIMP IsDirty() { | |
| return S_FALSE; | |
| } | |
| STDMETHODIMP Load(LPCOLESTR pszFileName, DWORD dwMode) { | |
| HRESULT hr; | |
| size_t len; | |
| OutputDebugString(L"PyShellExt::Load"); | |
| OutputDebugString(pszFileName); | |
| hr = StringCchLength(pszFileName, STRSAFE_MAX_CCH - 1, &len); | |
| if (FAILED(hr)) { | |
| OutputDebugString(L"PyShellExt::Load - failed to get string length"); | |
| return hr; | |
| } | |
| if (target) { | |
| CoTaskMemFree(target); | |
| } | |
| if (target_dir) { | |
| CoTaskMemFree(target_dir); | |
| } | |
| target = (LPOLESTR)CoTaskMemAlloc(sizeof(WCHAR) * (len + 1)); | |
| if (!target) { | |
| OutputDebugString(L"PyShellExt::Load - E_OUTOFMEMORY"); | |
| return E_OUTOFMEMORY; | |
| } | |
| target_dir = (LPOLESTR)CoTaskMemAlloc(sizeof(WCHAR) * (len + 1)); | |
| if (!target_dir) { | |
| OutputDebugString(L"PyShellExt::Load - E_OUTOFMEMORY"); | |
| return E_OUTOFMEMORY; | |
| } | |
| hr = StringCchCopy(target, len + 1, pszFileName); | |
| if (FAILED(hr)) { | |
| OutputDebugString(L"PyShellExt::Load - failed to copy string"); | |
| return hr; | |
| } | |
| hr = StringCchCopy(target_dir, len + 1, pszFileName); | |
| if (FAILED(hr)) { | |
| OutputDebugString(L"PyShellExt::Load - failed to copy string"); | |
| return hr; | |
| } | |
| if (!PathRemoveFileSpecW(target_dir)) { | |
| OutputDebugStringW(L"PyShellExt::Load - failed to remove filespec from target"); | |
| return E_FAIL; | |
| } | |
| OutputDebugString(target); | |
| target_mode = dwMode; | |
| OutputDebugString(L"PyShellExt::Load - S_OK"); | |
| return S_OK; | |
| } | |
| STDMETHODIMP Save(LPCOLESTR pszFileName, BOOL fRemember) { | |
| return E_NOTIMPL; | |
| } | |
| STDMETHODIMP SaveCompleted(LPCOLESTR pszFileName) { | |
| return E_NOTIMPL; | |
| } | |
| STDMETHODIMP GetClassID(CLSID *pClassID) { | |
| *pClassID = __uuidof(PyShellExt); | |
| return S_OK; | |
| } | |
| }; | |
| CoCreatableClass(PyShellExt); | |
| STDAPI DllGetClassObject(REFCLSID rclsid, REFIID riid, _COM_Outptr_ void** ppv) { | |
| return Module<InProc>::GetModule().GetClassObject(rclsid, riid, ppv); | |
| } | |
| STDAPI DllCanUnloadNow() { | |
| return Module<InProc>::GetModule().Terminate() ? S_OK : S_FALSE; | |
| } | |
| STDAPI DllRegisterServer() { | |
| LONG res; | |
| SECURITY_ATTRIBUTES secattr = { sizeof(SECURITY_ATTRIBUTES), NULL, FALSE }; | |
| LPSECURITY_ATTRIBUTES psecattr = NULL; | |
| HKEY key, ipsKey; | |
| WCHAR modname[MAX_PATH]; | |
| DWORD modname_len; | |
| OutputDebugString(L"PyShellExt::DllRegisterServer"); | |
| if (!hModule) { | |
| OutputDebugString(L"PyShellExt::DllRegisterServer - module handle was not set"); | |
| return SELFREG_E_CLASS; | |
| } | |
| modname_len = GetModuleFileName(hModule, modname, MAX_PATH); | |
| if (modname_len == 0 || | |
| (modname_len == MAX_PATH && GetLastError() == ERROR_INSUFFICIENT_BUFFER)) { | |
| OutputDebugString(L"PyShellExt::DllRegisterServer - failed to get module file name"); | |
| return SELFREG_E_CLASS; | |
| } | |
| DWORD disp; | |
| res = RegCreateKeyEx(HKEY_LOCAL_MACHINE, CLASS_SUBKEY, 0, NULL, 0, | |
| KEY_ALL_ACCESS, psecattr, &key, &disp); | |
| if (res == ERROR_ACCESS_DENIED) { | |
| OutputDebugString(L"PyShellExt::DllRegisterServer - failed to write per-machine registration. Attempting per-user instead."); | |
| res = RegCreateKeyEx(HKEY_CURRENT_USER, CLASS_SUBKEY, 0, NULL, 0, | |
| KEY_ALL_ACCESS, psecattr, &key, &disp); | |
| } | |
| if (res != ERROR_SUCCESS) { | |
| OutputDebugString(L"PyShellExt::DllRegisterServer - failed to create class key"); | |
| return SELFREG_E_CLASS; | |
| } | |
| res = RegCreateKeyEx(key, L"InProcServer32", 0, NULL, 0, | |
| KEY_ALL_ACCESS, psecattr, &ipsKey, NULL); | |
| if (res != ERROR_SUCCESS) { | |
| RegCloseKey(key); | |
| OutputDebugString(L"PyShellExt::DllRegisterServer - failed to create InProcServer32 key"); | |
| return SELFREG_E_CLASS; | |
| } | |
| res = RegSetValueEx(ipsKey, NULL, 0, | |
| REG_SZ, (LPBYTE)modname, modname_len * sizeof(modname[0])); | |
| if (res != ERROR_SUCCESS) { | |
| RegCloseKey(ipsKey); | |
| RegCloseKey(key); | |
| OutputDebugString(L"PyShellExt::DllRegisterServer - failed to set server path"); | |
| return SELFREG_E_CLASS; | |
| } | |
| res = RegSetValueEx(ipsKey, L"ThreadingModel", 0, | |
| REG_SZ, (LPBYTE)(L"Apartment"), sizeof(L"Apartment")); | |
| RegCloseKey(ipsKey); | |
| RegCloseKey(key); | |
| if (res != ERROR_SUCCESS) { | |
| OutputDebugString(L"PyShellExt::DllRegisterServer - failed to set threading model"); | |
| return SELFREG_E_CLASS; | |
| } | |
| SHChangeNotify(SHCNE_ASSOCCHANGED, SHCNF_IDLIST, NULL, NULL); | |
| OutputDebugString(L"PyShellExt::DllRegisterServer - S_OK"); | |
| return S_OK; | |
| } | |
| STDAPI DllUnregisterServer() { | |
| LONG res_lm, res_cu; | |
| res_lm = RegDeleteTree(HKEY_LOCAL_MACHINE, CLASS_SUBKEY); | |
| if (res_lm != ERROR_SUCCESS && res_lm != ERROR_FILE_NOT_FOUND) { | |
| OutputDebugString(L"PyShellExt::DllUnregisterServer - failed to delete per-machine registration"); | |
| return SELFREG_E_CLASS; | |
| } | |
| res_cu = RegDeleteTree(HKEY_CURRENT_USER, CLASS_SUBKEY); | |
| if (res_cu != ERROR_SUCCESS && res_cu != ERROR_FILE_NOT_FOUND) { | |
| OutputDebugString(L"PyShellExt::DllUnregisterServer - failed to delete per-user registration"); | |
| return SELFREG_E_CLASS; | |
| } | |
| if (res_lm == ERROR_FILE_NOT_FOUND && res_cu == ERROR_FILE_NOT_FOUND) { | |
| OutputDebugString(L"PyShellExt::DllUnregisterServer - extension was not registered"); | |
| return SELFREG_E_CLASS; | |
| } | |
| SHChangeNotify(SHCNE_ASSOCCHANGED, SHCNF_IDLIST, NULL, NULL); | |
| OutputDebugString(L"PyShellExt::DllUnregisterServer - S_OK"); | |
| return S_OK; | |
| } | |
| STDAPI_(BOOL) DllMain(_In_opt_ HINSTANCE hinst, DWORD reason, _In_opt_ void*) { | |
| if (reason == DLL_PROCESS_ATTACH) { | |
| hModule = hinst; | |
| cfDropDescription = RegisterClipboardFormat(CFSTR_DROPDESCRIPTION); | |
| if (!cfDropDescription) { | |
| OutputDebugString(L"PyShellExt::DllMain - failed to get CFSTR_DROPDESCRIPTION format"); | |
| } | |
| cfDragWindow = RegisterClipboardFormat(L"DragWindow"); | |
| if (!cfDragWindow) { | |
| OutputDebugString(L"PyShellExt::DllMain - failed to get DragWindow format"); | |
| } | |
| DisableThreadLibraryCalls(hinst); | |
| } | |
| return TRUE; | |
| } |