summaryrefslogtreecommitdiff
path: root/PC/pyshellext.cpp
diff options
context:
space:
mode:
authorSteve Dower <steve.dower@microsoft.com>2016-07-16 16:17:33 -0700
committerSteve Dower <steve.dower@microsoft.com>2016-07-16 16:17:33 -0700
commitdf450d1a18ba668874a2353a3870ba99c4848a75 (patch)
tree93d41ac47ec98cfe8d8fe1cc5488efe36aef2ed5 /PC/pyshellext.cpp
parent5f804e387ebc70effb61dc124f52acef357471b3 (diff)
downloadcpython-git-df450d1a18ba668874a2353a3870ba99c4848a75.tar.gz
Issue #27469: Adds a shell extension to the launcher so that drag and drop works correctly.
Diffstat (limited to 'PC/pyshellext.cpp')
-rw-r--r--PC/pyshellext.cpp605
1 files changed, 605 insertions, 0 deletions
diff --git a/PC/pyshellext.cpp b/PC/pyshellext.cpp
new file mode 100644
index 0000000000..04fe61e896
--- /dev/null
+++ b/PC/pyshellext.cpp
@@ -0,0 +1,605 @@
+// 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>
+
+#include "pyshellext_h.h"
+
+#define DDWM_UPDATEWINDOW (WM_USER+3)
+
+static HINSTANCE hModule;
+static CLIPFORMAT cfDropDescription;
+static CLIPFORMAT cfDragWindow;
+
+static const LPCWSTR CLASS_SUBKEY = L"Software\\Classes\\CLSID\\{BEA218D2-6950-497B-9434-61683EC065FE}";
+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 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);
+ 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 = CLSID_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;
+} \ No newline at end of file