From 7b049c13791400294524625fe4e4e25282b30352 Mon Sep 17 00:00:00 2001 From: Steve Dower Date: Wed, 12 Nov 2025 00:09:28 +0000 Subject: [PATCH 1/2] Adds drop handler and MSI registration. Fixes #217 --- _msbuild.py | 2 +- _msbuild_test.py | 5 +- src/pymanager/appxmanifest.xml | 7 + src/pymanager/msi.wxs | 13 + src/pyshellext/shellext.cpp | 439 ++++++++++++++++++++++++++++++- src/pyshellext/shellext.h | 3 + src/pyshellext/shellext_test.cpp | 59 +++++ tests/test_shellext.py | 16 ++ 8 files changed, 538 insertions(+), 6 deletions(-) diff --git a/_msbuild.py b/_msbuild.py index 1ed48e2..9773db6 100644 --- a/_msbuild.py +++ b/_msbuild.py @@ -164,7 +164,7 @@ def launcher_exe(name, platform, windowed=False): def pyshellext(ext='.exe', **props): link_opts = ItemDefinition( 'Link', - AdditionalDependencies=Prepend('RuntimeObject.lib;'), + AdditionalDependencies=Prepend('RuntimeObject.lib;pathcch.lib;'), ) if ext != '.exe': link_opts.options['ModuleDefinitionFile'] = '$(SourceRootDir)src\\pyshellext\\pyshellext.def' diff --git a/_msbuild_test.py b/_msbuild_test.py index 2b28a4b..de2cff9 100644 --- a/_msbuild_test.py +++ b/_msbuild_test.py @@ -63,7 +63,7 @@ PreprocessorDefinitions=Prepend("PYSHELLEXT_TEST=1;"), LanguageStandard='stdcpp20', ), - ItemDefinition('Link', AdditionalDependencies=Prepend("RuntimeObject.lib;")), + ItemDefinition('Link', AdditionalDependencies=Prepend("RuntimeObject.lib;pathcch.lib;")), CSourceFile('pyshellext/shellext.cpp'), CSourceFile('pyshellext/shellext_test.cpp'), IncludeFile('pyshellext/shellext.h'), @@ -74,6 +74,9 @@ CFunction('shellext_ReadAllIdleInstalls'), CFunction('shellext_PassthroughTitle'), CFunction('shellext_IdleCommand'), + CFunction('shellext_GetDropArgumentsW'), + CFunction('shellext_GetDropArgumentsA'), + CFunction('shellext_GetDropDescription'), source='src', ) ) diff --git a/src/pymanager/appxmanifest.xml b/src/pymanager/appxmanifest.xml index 959faec..f178c51 100644 --- a/src/pymanager/appxmanifest.xml +++ b/src/pymanager/appxmanifest.xml @@ -272,6 +272,13 @@ + + + + + + + diff --git a/src/pymanager/msi.wxs b/src/pymanager/msi.wxs index c0f5617..0d02adb 100644 --- a/src/pymanager/msi.wxs +++ b/src/pymanager/msi.wxs @@ -63,12 +63,23 @@ Name="ExplorerCommandHandler" Value="{C7E29CB0-9691-4DE8-B72B-6719DDC0B4A1}" Type="string" /> + + + + @@ -111,6 +122,7 @@ Condition="WIX_NATIVE_MACHINE = 34404"> + @@ -119,6 +131,7 @@ Condition="WIX_NATIVE_MACHINE = 43620"> + diff --git a/src/pyshellext/shellext.cpp b/src/pyshellext/shellext.cpp index 3d360e1..6275e6b 100644 --- a/src/pyshellext/shellext.cpp +++ b/src/pyshellext/shellext.cpp @@ -1,18 +1,35 @@ +#include +#include + #define _WIN32_WINNT _WIN32_WINNT_WIN10 #include #define __WRL_CLASSIC_COM__ #include +#include +#include + using namespace Microsoft::WRL; #include "shellext.h" static HINSTANCE hModule; + #define CLSID_IDLE_COMMAND "{C7E29CB0-9691-4DE8-B72B-6719DDC0B4A1}" #define CLSID_LAUNCH_COMMAND "{F7209EE3-FC96-40F4-8C3F-4B7D3994370D}" #define CLSID_COMMAND_ENUMERATOR "{F82C8CD5-A69C-45CC-ADC6-87FC5F4A7429}" +#define CLSID_DRAGDROPSUPPORT "{EAF5E48F-F54A-4A03-824B-CA880772EE20}" + +#ifndef _DEBUG +#undef OutputDebugString +#define OutputDebugString(x) +#endif + + +static const WPARAM DDWM_UPDATEWINDOW = WM_USER + 3; +static const LPCWSTR DRAG_MESSAGE = L"Open with %1"; LRESULT RegReadStr(HKEY key, LPCWSTR valueName, std::wstring& result) @@ -251,9 +268,9 @@ class DECLSPEC_UUID(CLSID_LAUNCH_COMMAND) LaunchCommand parameters.c_str(), NULL }; - OutputDebugStringW(L"IdleCommand::Invoke"); - OutputDebugStringW(exe.c_str()); - OutputDebugStringW(parameters.c_str()); + OutputDebugString(L"IdleCommand::Invoke"); + OutputDebugString(exe.c_str()); + OutputDebugString(parameters.c_str()); ShellExecuteExW(&sei); return S_OK; } @@ -402,7 +419,7 @@ class DECLSPEC_UUID(CLSID_IDLE_COMMAND) IdleCommand if (FAILED(hr)) { wchar_t buffer[512]; swprintf_s(buffer, L"IdleCommand error 0x%08X", (DWORD)hr); - OutputDebugStringW(buffer); + OutputDebugString(buffer); idles.clear(); } } @@ -514,7 +531,380 @@ class DECLSPEC_UUID(CLSID_IDLE_COMMAND) IdleCommand }; +class DECLSPEC_UUID(CLSID_DRAGDROPSUPPORT) DragDropSupport : public RuntimeClass< + RuntimeClassFlags, IDropTarget, IPersistFile> +{ + std::wstring target, target_name, target_dir; + DWORD target_mode; + + static CLIPFORMAT cfDropDescription; + static CLIPFORMAT cfDragWindow; + + IDataObject *data_obj; + +public: + DragDropSupport() : data_obj(NULL) { + if (!cfDropDescription) { + cfDropDescription = RegisterClipboardFormat(CFSTR_DROPDESCRIPTION); + } + if (!cfDropDescription) { + OutputDebugString(L"PyShellExt::DllMain - failed to get CFSTR_DROPDESCRIPTION format"); + } + if (!cfDragWindow) { + cfDragWindow = RegisterClipboardFormat(L"DragWindow"); + } + if (!cfDragWindow) { + OutputDebugString(L"PyShellExt::DllMain - failed to get DragWindow format"); + } + } + + ~DragDropSupport() { + if (data_obj) { + data_obj->Release(); + } + } + + HRESULT UpdateDropDescription(DROPDESCRIPTION *drop_desc) { + StringCchCopy(drop_desc->szMessage, sizeof(drop_desc->szMessage) / sizeof(drop_desc->szMessage[0]), DRAG_MESSAGE); + StringCchCopy(drop_desc->szInsert, sizeof(drop_desc->szInsert) / sizeof(drop_desc->szInsert[0]), target_name.c_str()); + drop_desc->type = DROPIMAGE_MOVE; + return S_OK; + } + + 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::DragDropSupport::UpdateDropDescription - failed to get DROPDESCRIPTION format"); + return hr; + } + if (!medium.hGlobal) { + OutputDebugString(L"PyShellExt::DragDropSupport::UpdateDropDescription - DROPDESCRIPTION format had NULL hGlobal"); + ReleaseStgMedium(&medium); + return E_FAIL; + } + auto drop_desc = (DROPDESCRIPTION*)GlobalLock(medium.hGlobal); + if (!drop_desc) { + OutputDebugString(L"PyShellExt::DragDropSupport::UpdateDropDescription - failed to lock DROPDESCRIPTION hGlobal"); + ReleaseStgMedium(&medium); + return E_FAIL; + } + hr = UpdateDropDescription(drop_desc); + + GlobalUnlock(medium.hGlobal); + ReleaseStgMedium(&medium); + + return hr; + } + + 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::DragDropSupport::GetDragWindow - failed to get DragWindow format"); + return hr; + } + if (!medium.hGlobal) { + OutputDebugString(L"PyShellExt::DragDropSupport::GetDragWindow - DragWindow format had NULL hGlobal"); + ReleaseStgMedium(&medium); + return E_FAIL; + } + + pMem = (HWND*)GlobalLock(medium.hGlobal); + if (!pMem) { + OutputDebugString(L"PyShellExt::DragDropSupport::GetDragWindow - failed to lock DragWindow hGlobal"); + ReleaseStgMedium(&medium); + return E_FAIL; + } + + *phWnd = *pMem; + + GlobalUnlock(medium.hGlobal); + ReleaseStgMedium(&medium); + + return S_OK; + } + + HRESULT GetArgumentsW(LPCWSTR files, LPCWSTR *pArguments) { + std::wstring arg_str; + while (arg_str.size() < 32767 && *files) { + std::wstring wfile(files); + files += wfile.size() + 1; + + if (wfile.find(L' ') != wfile.npos) { + wfile.insert(wfile.begin(), L'"'); + wfile.push_back(L'"'); + } + if (arg_str.size()) { + arg_str.push_back(L' '); + } + arg_str += wfile; + } + + LPWSTR args = (LPWSTR)CoTaskMemAlloc(sizeof(WCHAR) * (arg_str.size() + 1)); + *pArguments = args; + if (!args) { + return E_OUTOFMEMORY; + } + wcscpy_s(args, arg_str.size() + 1, arg_str.c_str()); + + return S_OK; + } + + HRESULT GetArgumentsA(LPCSTR files, LPCWSTR *pArguments) { + std::string arg_str; + while (arg_str.size() < 32767 && *files) { + std::string file(files); + files += file.size() + 1; + + if (file.find(' ') != file.npos) { + file.insert(file.begin(), '"'); + file.push_back('"'); + } + if (arg_str.size()) { + arg_str.push_back(' '); + } + arg_str += file; + } + + int wlen = MultiByteToWideChar(CP_ACP, 0, arg_str.data(), arg_str.size(), NULL, 0); + if (!wlen) { + OutputDebugString(L"PyShellExt::DragDropSupport::GetArguments - failed to get length of wide-char path"); + return E_FAIL; + } + + LPWSTR args = (LPWSTR)CoTaskMemAlloc(sizeof(WCHAR) * (wlen + 1)); + if (!args) { + return E_OUTOFMEMORY; + } + wlen = MultiByteToWideChar(CP_ACP, 0, arg_str.data(), arg_str.size(), args, wlen + 1); + if (!wlen) { + OutputDebugString(L"PyShellExt::DragDropSupport::GetArguments - failed to convert multi-byte to wide-char path"); + CoTaskMemFree(args); + return E_FAIL; + } + args[wlen] = '\0'; + *pArguments = args; + 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::DragDropSupport::GetArguments - failed to get CF_HDROP format"); + return hr; + } + if (!medium.hGlobal) { + OutputDebugString(L"PyShellExt::DragDropSupport::GetArguments - CF_HDROP format had NULL hGlobal"); + ReleaseStgMedium(&medium); + return E_FAIL; + } + + pdropfiles = (DROPFILES*)GlobalLock(medium.hGlobal); + if (!pdropfiles) { + OutputDebugString(L"PyShellExt::DragDropSupport::GetArguments - failed to lock CF_HDROP hGlobal"); + ReleaseStgMedium(&medium); + return E_FAIL; + } + + if (pdropfiles->fWide) { + LPCWSTR files = (LPCWSTR)((char*)pdropfiles + pdropfiles->pFiles); + hr = GetArgumentsW(files, pArguments); + } else { + LPCSTR files = (LPCSTR)((char*)pdropfiles + pdropfiles->pFiles); + hr = GetArgumentsA(files, pArguments); + } + + 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::DragDropSupport::NotifyDragWindow - failed to post DDWM_UPDATEWINDOW"); + return E_FAIL; + } + + return S_OK; + } + + // IDropTarget implementation + + STDMETHODIMP DragEnter(IDataObject *pDataObj, DWORD grfKeyState, POINTL pt, DWORD *pdwEffect) { + HWND hwnd; + + OutputDebugString(L"PyShellExt::DragDropSupport::DragEnter"); + + pDataObj->AddRef(); + data_obj = pDataObj; + + *pdwEffect = DROPEFFECT_MOVE; + + if (FAILED(UpdateDropDescription(data_obj))) { + OutputDebugString(L"PyShellExt::DragDropSupport::DragEnter - failed to update drop description"); + } + if (FAILED(GetDragWindow(data_obj, &hwnd))) { + OutputDebugString(L"PyShellExt::DragDropSupport::DragEnter - failed to get drag window"); + } + if (FAILED(NotifyDragWindow(hwnd))) { + OutputDebugString(L"PyShellExt::DragDropSupport::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::DragDropSupport::Drop"); + *pdwEffect = DROPEFFECT_NONE; + + if (pDataObj != data_obj) { + OutputDebugString(L"PyShellExt::DragDropSupport::Drop - unexpected data object"); + return E_FAIL; + } + + data_obj->Release(); + data_obj = NULL; + + if (SUCCEEDED(GetArguments(pDataObj, &args))) { + OutputDebugString(args); + ShellExecute(NULL, NULL, target.c_str(), args, target_dir.c_str(), SW_NORMAL); + + CoTaskMemFree((LPVOID)args); + } else { + OutputDebugString(L"PyShellExt::DragDropSupport::Drop - failed to get launch arguments"); + } + + return S_OK; + } + + // IPersistFile implementation + + STDMETHODIMP GetCurFile(LPOLESTR *ppszFileName) { + HRESULT hr; + size_t len = target.size(); + + if (!ppszFileName) { + return E_POINTER; + } + + *ppszFileName = (LPOLESTR)CoTaskMemAlloc(sizeof(WCHAR) * (len + 1)); + if (!*ppszFileName) { + return E_OUTOFMEMORY; + } + + hr = StringCchCopy(*ppszFileName, len + 1, target.c_str()); + if (FAILED(hr)) { + CoTaskMemFree(*ppszFileName); + *ppszFileName = NULL; + return E_FAIL; + } + + return S_OK; + } + + STDMETHODIMP IsDirty() { + return S_FALSE; + } + + STDMETHODIMP Load(LPCOLESTR pszFileName, DWORD dwMode) { + OutputDebugString(L"PyShellExt::DragDropSupport::Load"); + OutputDebugString(pszFileName); + + target = pszFileName; + target_dir = pszFileName; + switch (PathCchRemoveFileSpec(target_dir.data(), target_dir.size())) { + case S_OK: + target_dir.resize(wcsnlen_s(target_dir.data(), target_dir.size())); + target_name = { target.begin() + target_dir.size(), target.end() }; + while (!target_name.empty() && (target_name.front() == L'\\' || target_name.front() == L'/')) { + target_name.erase(0, 1); + } + break; + case S_FALSE: + target_name = L"script"; + break; + default: + OutputDebugString(L"PyShellExt::DragDropSupport::Load - failed to remove filespec from target"); + return E_FAIL; + } + + OutputDebugString(target.c_str()); + target_mode = dwMode; + OutputDebugString(L"PyShellExt::DragDropSupport::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(DragDropSupport); + return S_OK; + } +}; + +CLIPFORMAT DragDropSupport::cfDropDescription = 0; +CLIPFORMAT DragDropSupport::cfDragWindow = 0; + + CoCreatableClass(IdleCommand); +CoCreatableClass(DragDropSupport); + #ifdef PYSHELLEXT_TEST @@ -530,6 +920,47 @@ IExplorerCommand *MakeIdleCommand(HKEY hive, LPCWSTR root) return Make(hive, root).Detach(); } +HRESULT GetDropArgumentsW(LPCWSTR args, std::wstring &parsed) +{ + LPCWSTR p; + auto o = Make(); + HRESULT hr = o->GetArgumentsW(args, &p); + if (SUCCEEDED(hr)) { + parsed = p; + CoTaskMemFree((LPVOID)p); + } + return hr; +} + +HRESULT GetDropArgumentsA(LPCSTR args, std::wstring &parsed) +{ + LPCWSTR p; + auto o = Make(); + HRESULT hr = o->GetArgumentsA(args, &p); + if (SUCCEEDED(hr)) { + parsed = p; + CoTaskMemFree((LPVOID)p); + } + return hr; +} + +HRESULT GetDropDescription(LPCOLESTR pszFileName, DWORD dwMode, std::wstring &message, std::wstring &insert) +{ + auto o = Make(); + HRESULT hr = o->Load(pszFileName, dwMode); + if (FAILED(hr)) { + return hr; + } + DROPDESCRIPTION drop_desc; + ZeroMemory(&drop_desc, sizeof(drop_desc)); + hr = o->UpdateDropDescription(&drop_desc); + if (SUCCEEDED(hr)) { + message = drop_desc.szMessage; + insert = drop_desc.szInsert; + } + return hr; +} + #elif defined(_WINDLL) STDAPI DllGetClassObject(REFCLSID rclsid, REFIID riid, _COM_Outptr_ void** ppv) diff --git a/src/pyshellext/shellext.h b/src/pyshellext/shellext.h index 12e1219..034be26 100644 --- a/src/pyshellext/shellext.h +++ b/src/pyshellext/shellext.h @@ -21,3 +21,6 @@ HRESULT ReadAllIdleInstalls(std::vector &idles, HKEY hive, LPCWSTR roo IExplorerCommand *MakeIdleCommand(HKEY hive, LPCWSTR root); IExplorerCommand *MakeLaunchCommand(std::wstring title, std::wstring exe, std::wstring idle); +HRESULT GetDropArgumentsW(LPCWSTR args, std::wstring &parsed); +HRESULT GetDropArgumentsA(LPCSTR args, std::wstring &parsed); +HRESULT GetDropDescription(LPCOLESTR pszFileName, DWORD dwMode, std::wstring &message, std::wstring &insert); diff --git a/src/pyshellext/shellext_test.cpp b/src/pyshellext/shellext_test.cpp index a574f90..df03210 100644 --- a/src/pyshellext/shellext_test.cpp +++ b/src/pyshellext/shellext_test.cpp @@ -238,4 +238,63 @@ PyObject *shellext_IdleCommand(PyObject *, PyObject *args, PyObject *) } +PyObject *shellext_GetDropArgumentsW(PyObject *, PyObject *args, PyObject *) +{ + Py_buffer value; + if (!PyArg_ParseTuple(args, "y*", &value)) { + return NULL; + } + PyObject *r = NULL; + std::wstring parsed; + HRESULT hr = GetDropArgumentsW((wchar_t *)value.buf, parsed); + PyBuffer_Release(&value); + if (SUCCEEDED(hr)) { + r = PyUnicode_FromWideChar(parsed.data(), parsed.size()); + } else { + PyErr_SetFromWindowsErr((int)hr); + } + return r; +} + +PyObject *shellext_GetDropArgumentsA(PyObject *, PyObject *args, PyObject *) +{ + Py_buffer value; + if (!PyArg_ParseTuple(args, "y*", &value)) { + return NULL; + } + PyObject *r = NULL; + std::wstring parsed; + HRESULT hr = GetDropArgumentsA((char *)value.buf, parsed); + PyBuffer_Release(&value); + if (SUCCEEDED(hr)) { + r = PyUnicode_FromWideChar(parsed.data(), parsed.size()); + } else { + PyErr_SetFromWindowsErr((int)hr); + } + return r; +} + +PyObject *shellext_GetDropDescription(PyObject *, PyObject *args, PyObject *) +{ + wchar_t *value1; + Py_ssize_t value2; + if (!PyArg_ParseTuple(args, "O&n", as_utf16, &value1, &value2)) { + return NULL; + } + PyObject *r = NULL; + std::wstring parsed1, parsed2; + HRESULT hr = GetDropDescription(value1, value2, parsed1, parsed2); + if (SUCCEEDED(hr)) { + r = Py_BuildValue( + "u#u#", + parsed1.data(), (Py_ssize_t)parsed1.size(), + parsed2.data(), (Py_ssize_t)parsed2.size() + ); + } else { + PyErr_SetFromWindowsErr((int)hr); + } + return r; +} + + } diff --git a/tests/test_shellext.py b/tests/test_shellext.py index d865d25..17a06cc 100644 --- a/tests/test_shellext.py +++ b/tests/test_shellext.py @@ -101,3 +101,19 @@ def test_IdleCommand(idle_reg): f"{sys._base_executable},-4", *(i[0] for i in reversed(idle_reg.all)), ] + + +def test_DragDropDescription(): + assert ("Open with %1", "test.exe") == SE.shellext_GetDropDescription( + r"C:\Fake\Path\test.exe", 0 + ) + + +def test_GetDropArgumentsW(): + actual = SE.shellext_GetDropArgumentsW("arg 1\0arg2\0arg 3\0\0".encode("utf-16-le")) + assert actual == '"arg 1" arg2 "arg 3"' + + +def test_GetDropArgumentsA(): + actual = SE.shellext_GetDropArgumentsA("arg 1\0arg2\0arg 3\0\0".encode("ascii")) + assert actual == '"arg 1" arg2 "arg 3"' From 13168c050db6cb83f330bca3ae5e38ff60f270e3 Mon Sep 17 00:00:00 2001 From: Steve Dower Date: Thu, 13 Nov 2025 20:10:20 +0000 Subject: [PATCH 2/2] Review comments --- src/pyshellext/shellext.cpp | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/src/pyshellext/shellext.cpp b/src/pyshellext/shellext.cpp index 6275e6b..94f592f 100644 --- a/src/pyshellext/shellext.cpp +++ b/src/pyshellext/shellext.cpp @@ -548,13 +548,13 @@ class DECLSPEC_UUID(CLSID_DRAGDROPSUPPORT) DragDropSupport : public RuntimeClass cfDropDescription = RegisterClipboardFormat(CFSTR_DROPDESCRIPTION); } if (!cfDropDescription) { - OutputDebugString(L"PyShellExt::DllMain - failed to get CFSTR_DROPDESCRIPTION format"); + OutputDebugString(L"PyShellExt::DragDropSupport - failed to get CFSTR_DROPDESCRIPTION format"); } if (!cfDragWindow) { cfDragWindow = RegisterClipboardFormat(L"DragWindow"); } if (!cfDragWindow) { - OutputDebugString(L"PyShellExt::DllMain - failed to get DragWindow format"); + OutputDebugString(L"PyShellExt::DragDropSupport - failed to get DragWindow format"); } } @@ -771,10 +771,13 @@ class DECLSPEC_UUID(CLSID_DRAGDROPSUPPORT) DragDropSupport : public RuntimeClass // IDropTarget implementation STDMETHODIMP DragEnter(IDataObject *pDataObj, DWORD grfKeyState, POINTL pt, DWORD *pdwEffect) { - HWND hwnd; + HWND hwnd = NULL; OutputDebugString(L"PyShellExt::DragDropSupport::DragEnter"); + if (data_obj) { + data_obj->Release(); + } pDataObj->AddRef(); data_obj = pDataObj; @@ -794,10 +797,15 @@ class DECLSPEC_UUID(CLSID_DRAGDROPSUPPORT) DragDropSupport : public RuntimeClass } STDMETHODIMP DragLeave() { + if (data_obj) { + data_obj->Release(); + data_obj = NULL; + } return S_OK; } STDMETHODIMP DragOver(DWORD grfKeyState, POINTL pt, DWORD *pdwEffect) { + *pdwEffect = DROPEFFECT_MOVE; return S_OK; }