[SHELL32] Support IDropTarget handler in IContextMenu static verbs (#5860)
authorWhindmar Saksit <whindsaks@proton.me>
Sun, 24 Mar 2024 17:23:07 +0000 (18:23 +0100)
committerGitHub <noreply@github.com>
Sun, 24 Mar 2024 17:23:07 +0000 (18:23 +0100)
This adds support for the `DropHandler` subkey in static verbs. `IDropTarget` is called when the verb is invoked. The advantage of this is that no library needs to be loaded until the verb is actually invoked unlike `IContextMenu` shell extensions that have to be loaded during menu creation.

This method is preferred for verb implementations that must work on Windows XP, see
https://learn.microsoft.com/en-us/previous-versions/windows/desktop/legacy/dd940354%28v%3Dvs.85%29?redirectedfrom=MSDN#description

dll/win32/shell32/CDefaultContextMenu.cpp

index cfd43b9..31231af 100644 (file)
 
 WINE_DEFAULT_DEBUG_CHANNEL(dmenu);
 
+
+// FIXME: 260 is correct, but should this be part of the SDK or just MAX_PATH?
+#define MAX_VERB 260
+
+static HRESULT
+SHELL_GetRegCLSID(HKEY hKey, LPCWSTR SubKey, LPCWSTR Value, CLSID &clsid)
+{
+    WCHAR buf[42];
+    DWORD cb = sizeof(buf);
+    DWORD err = RegGetValueW(hKey, SubKey, Value, RRF_RT_REG_SZ, NULL, buf, &cb);
+    return !err ? CLSIDFromString(buf, &clsid) : HRESULT_FROM_WIN32(err);
+}
+
 static inline bool RegValueExists(HKEY hKey, LPCWSTR Name)
 {
     return RegQueryValueExW(hKey, Name, NULL, NULL, NULL, NULL) == ERROR_SUCCESS;
@@ -70,7 +83,8 @@ static const struct _StaticInvokeCommandMap_
 class CDefaultContextMenu :
     public CComObjectRootEx<CComMultiThreadModelNoCS>,
     public IContextMenu3,
-    public IObjectWithSite
+    public IObjectWithSite,
+    public IServiceProvider
 {
     private:
         CComPtr<IUnknown> m_site;
@@ -142,11 +156,18 @@ class CDefaultContextMenu :
         STDMETHOD(SetSite)(IUnknown *pUnkSite) override;
         STDMETHOD(GetSite)(REFIID riid, void **ppvSite) override;
 
+        // IServiceProvider
+        virtual HRESULT STDMETHODCALLTYPE QueryService(REFGUID svc, REFIID riid, void**ppv)
+        {
+            return IUnknown_QueryService(m_site, svc, riid, ppv);
+        }
+
         BEGIN_COM_MAP(CDefaultContextMenu)
         COM_INTERFACE_ENTRY_IID(IID_IContextMenu, IContextMenu)
         COM_INTERFACE_ENTRY_IID(IID_IContextMenu2, IContextMenu2)
         COM_INTERFACE_ENTRY_IID(IID_IContextMenu3, IContextMenu3)
         COM_INTERFACE_ENTRY_IID(IID_IObjectWithSite, IObjectWithSite)
+        COM_INTERFACE_ENTRY_IID(IID_IServiceProvider, IServiceProvider)
         END_COM_MAP()
 };
 
@@ -531,7 +552,7 @@ CDefaultContextMenu::AddStaticContextMenusToMenu(
         /* By default use verb for menu item name */
         mii.dwTypeData = (LPWSTR)info.Verb.GetString();
 
-        WCHAR wszKey[256];
+        WCHAR wszKey[sizeof("shell\\") + MAX_VERB];
         HRESULT hr;
         hr = StringCbPrintfW(wszKey, sizeof(wszKey), L"shell\\%s", info.Verb.GetString());
         if (FAILED_UNEXPECTEDLY(hr))
@@ -1083,9 +1104,6 @@ PDynamicShellEntry CDefaultContextMenu::GetDynamicEntry(UINT idCmd)
     return NULL;
 }
 
-// FIXME: 260 is correct, but should this be part of the SDK or just MAX_PATH?
-#define MAX_VERB 260
-
 BOOL
 CDefaultContextMenu::MapVerbToCmdId(PVOID Verb, PUINT idCmd, BOOL IsUnicode)
 {
@@ -1141,7 +1159,7 @@ CDefaultContextMenu::BrowserFlagsFromVerb(LPCMINVOKECOMMANDINFOEX lpcmi, PStatic
     CComPtr<IShellBrowser> psb;
     HWND hwndTree;
     LPCWSTR FlagsName;
-    WCHAR wszKey[256];
+    WCHAR wszKey[sizeof("shell\\") + MAX_VERB];
     HRESULT hr;
     DWORD wFlags;
     DWORD cbVerb;
@@ -1260,6 +1278,40 @@ CDefaultContextMenu::InvokeRegVerb(
 
     PStaticShellEntry pEntry = &m_StaticEntries.GetAt(it);
 
+    CRegKey VerbKey;
+    WCHAR VerbKeyPath[sizeof("shell\\") + MAX_VERB];
+    hr = StringCbPrintfW(VerbKeyPath, sizeof(VerbKeyPath), L"shell\\%s", pEntry->Verb.GetString());
+    if (SUCCEEDED(hr) && m_pDataObj &&
+        VerbKey.Open(pEntry->hkClass, VerbKeyPath, KEY_READ) == ERROR_SUCCESS)
+    {
+        CLSID clsid;
+
+        DWORD KeyState = 0;
+        if (lpcmi->fMask & CMIC_MASK_SHIFT_DOWN)
+            KeyState |= MK_SHIFT;
+        if (lpcmi->fMask & CMIC_MASK_CONTROL_DOWN)
+            KeyState |= MK_CONTROL;
+
+        POINTL *pPtl = NULL;
+        C_ASSERT(sizeof(POINT) == sizeof(POINTL));
+        if (lpcmi->fMask & CMIC_MASK_PTINVOKE)
+            pPtl = (POINTL*)&lpcmi->ptInvoke;
+
+        // TODO: IExecuteCommand
+
+        CComPtr<IDropTarget> pDT;
+        hr = SHELL_GetRegCLSID(VerbKey, L"DropTarget", L"CLSID", clsid);
+        if (SUCCEEDED(hr))
+            hr = CoCreateInstance(clsid, NULL, CLSCTX_ALL, IID_PPV_ARG(IDropTarget, &pDT));
+        if (SUCCEEDED(hr))
+        {
+            IUnknown_SetSite(pDT, static_cast<IContextMenu*>(this));
+            hr = SHSimulateDrop(pDT, m_pDataObj, KeyState, pPtl, NULL);
+            IUnknown_SetSite(pDT, NULL);
+            return hr;
+        }
+    }
+
     /* Get the browse flags to see if we need to browse */
     DWORD wFlags = BrowserFlagsFromVerb(lpcmi, pEntry);
 
@@ -1324,6 +1376,7 @@ CDefaultContextMenu::InvokeCommand(
     {
         LocalInvokeInfo.lpVerb -= m_iIdSCMFirst;
         Result = InvokeRegVerb(&LocalInvokeInfo);
+        // TODO: if (FAILED(Result) && !(lpcmi->fMask & CMIC_MASK_FLAG_NO_UI)) SHELL_ErrorBox(m_pSite, Result);
         return Result;
     }