[SHELL32] Implement DragDropHandlers shell extension context menus (#8143)

CORE-11240 CORE-11238
This commit is contained in:
Whindmar Saksit 2025-06-19 20:54:28 +02:00 committed by GitHub
parent 06414ac85a
commit 59e556f3f3
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
11 changed files with 234 additions and 13 deletions

View file

@ -32,6 +32,112 @@ static BOOL InsertMenuItemAt(HMENU hMenu, UINT Pos, UINT Flags)
return InsertMenuItemW(hMenu, Pos, TRUE, &mii);
}
static void DCMA_DestroyEntry(DCMENTRY &dcme)
{
if (!dcme.pCM)
return;
IUnknown_SetSite(dcme.pCM, NULL);
dcme.pCM->Release();
dcme.pCM = NULL;
}
void DCMA_Destroy(HDCMA hDCMA)
{
UINT i = 0;
for (DCMENTRY *p; (p = DCMA_GetEntry(hDCMA, i)) != NULL; ++i)
DCMA_DestroyEntry(*p);
DSA_Destroy(hDCMA);
}
UINT DCMA_InsertMenuItems(
_In_ HDCMA hDCMA,
_In_ HDCIA hDCIA,
_In_opt_ LPCITEMIDLIST pidlFolder,
_In_opt_ IDataObject *pDO,
_In_opt_ HKEY *pKeys,
_In_opt_ UINT nKeys,
_In_ QCMINFO *pQCMI,
_In_opt_ UINT fCmf,
_In_opt_ IUnknown *pUnkSite)
{
UINT idCmdBase = pQCMI->idCmdFirst, idCmdFirst = idCmdBase;
UINT nOffset = 0;
// Insert in reverse order
for (int iCls = DCIA_GetCount(hDCIA) - 1; iCls >= 0; --iCls)
{
REFCLSID clsid = *DCIA_GetEntry(hDCIA, iCls);
if (fCmf & CMF_DEFAULTONLY)
{
WCHAR szKey[MAX_PATH];
wcscpy(szKey, L"CLSID\\");
StringFromGUID2(clsid, szKey + _countof(L"CLSID\\") - 1, CHARS_IN_GUID);
wcscpy(szKey + _countof(L"CLSID\\") - 1 + CHARS_IN_GUID - 1, L"\\shellex\\MayChangeDefaultMenu");
if (!RegKeyExists(HKEY_CLASSES_ROOT, szKey))
continue;
}
for (UINT iKey = 0; iKey < nKeys; ++iKey)
{
CComPtr<IShellExtInit> pInit;
HRESULT hr = SHExtCoCreateInstance(NULL, &clsid, NULL, IID_PPV_ARG(IShellExtInit, &pInit));
if (FAILED(hr))
break;
if (FAILED(hr = pInit->Initialize(pidlFolder, pDO, pKeys[iKey])))
continue;
IContextMenu *pCM;
if (FAILED(hr = pInit->QueryInterface(IID_PPV_ARG(IContextMenu, &pCM))))
break;
IUnknown_SetSite(pCM, pUnkSite);
hr = pCM->QueryContextMenu(pQCMI->hmenu, pQCMI->indexMenu + nOffset, idCmdFirst, pQCMI->idCmdLast, fCmf);
const UINT nCount = HRESULT_CODE(hr);
const UINT idThisFirst = idCmdFirst - idCmdBase;
DCMENTRY dcme = { pCM, idThisFirst, idThisFirst + nCount - 1 };
if (hr > 0)
{
idCmdFirst += nCount;
if (DSA_AppendItem(hDCMA, &dcme) >= 0)
{
if (nOffset == 0 && GetMenuDefaultItem(pQCMI->hmenu, TRUE, 0) == 0)
nOffset++; // Insert new items below the default
break;
}
}
DCMA_DestroyEntry(dcme);
}
}
return idCmdFirst;
}
HRESULT DCMA_InvokeCommand(HDCMA hDCMA, CMINVOKECOMMANDINFO *pICI)
{
HRESULT hr = S_FALSE;
for (UINT i = 0;; ++i)
{
DCMENTRY *p = DCMA_GetEntry(hDCMA, i);
if (!p)
return hr;
UINT id = LOWORD(pICI->lpVerb);
if (!IS_INTRESOURCE(pICI->lpVerb))
{
if (SUCCEEDED(hr = p->pCM->InvokeCommand(pICI)))
return hr;
}
else if (id >= p->idCmdFirst && id <= p->idCmdLast)
{
CMINVOKECOMMANDINFOEX ici;
CopyMemory(&ici, pICI, min(sizeof(ici), pICI->cbSize));
ici.cbSize = min(sizeof(ici), pICI->cbSize);
ici.lpVerb = MAKEINTRESOURCEA(id - p->idCmdFirst);
ici.lpVerbW = (PWSTR)ici.lpVerb;
return p->pCM->InvokeCommand((CMINVOKECOMMANDINFO*)&ici);
}
}
}
typedef struct _DynamicShellEntry_
{
UINT iIdCmdFirst;

View file

@ -237,7 +237,6 @@ HRESULT CFSDropTarget::_GetEffectFromMenu(IDataObject *pDataObject, POINTL pt, D
HMENU hmenu = LoadMenuW(shell32_hInstance, MAKEINTRESOURCEW(IDM_DRAGFILE));
if (!hmenu)
return E_OUTOFMEMORY;
HMENU hpopupmenu = GetSubMenu(hmenu, 0);
SHELL_LimitDropEffectToItemAttributes(pDataObject, &dwAvailableEffects);
@ -256,7 +255,19 @@ HRESULT CFSDropTarget::_GetEffectFromMenu(IDataObject *pDataObject, POINTL pt, D
else if (dwAvailableEffects & DROPEFFECT_LINK)
SetMenuDefaultItem(hpopupmenu, IDM_LINKHERE, FALSE);
/* FIXME: We need to support shell extensions here */
CRegKeyHandleArray keys;
HDCIA hDCIA = DCIA_Create();
HDCMA hDCMA = DCMA_Create();
CComHeapPtr<ITEMIDLIST_ABSOLUTE> pidlFolder(SHELL32_CreateSimpleIDListFromPath(m_sPathTarget, FILE_ATTRIBUTE_DIRECTORY));
if (hDCMA && hDCIA && pidlFolder)
{
AddPidlClassKeysToArray(pidlFolder, keys, keys);
for (UINT i = 0; i < keys; ++i)
DCIA_AddShellExSubkey(hDCIA, keys[i], L"DragDropHandlers");
QCMINFO qcmi = { hpopupmenu, 0, DROPIDM_EXTFIRST, DROPIDM_EXTLAST };
DCMA_InsertMenuItems(hDCMA, hDCIA, pidlFolder, pDataObject, keys, keys, &qcmi, 0, m_site);
}
/* We shouldn't use the site window here because the menu should work even when we don't have a site */
HWND hwndDummy = CreateWindowEx(0,
@ -279,16 +290,39 @@ HRESULT CFSDropTarget::_GetEffectFromMenu(IDataObject *pDataObject, POINTL pt, D
DestroyWindow(hwndDummy);
if (uCommand == 0)
return S_FALSE;
HRESULT hr = S_FALSE; // S_FALSE means we did not handle the command
C_ASSERT(IDM_COPYHERE < DROPIDM_EXTFIRST && IDM_MOVEHERE < DROPIDM_EXTFIRST &&
IDM_LINKHERE < DROPIDM_EXTFIRST && DROPIDM_EXTFIRST > 0);
if (uCommand >= DROPIDM_EXTFIRST && uCommand <= DROPIDM_EXTLAST)
{
CMINVOKECOMMANDINFO ici = { sizeof(ici), 0, m_hwndSite, MAKEINTRESOURCEA(uCommand - DROPIDM_EXTFIRST) };
ici.nShow = SW_SHOW;
if (m_grfKeyState & MK_SHIFT)
ici.fMask |= CMIC_MASK_SHIFT_DOWN;
if (m_grfKeyState & MK_CONTROL)
ici.fMask |= CMIC_MASK_CONTROL_DOWN;
DCMA_InvokeCommand(hDCMA, &ici);
hr = S_OK;
*pdwEffect = DROPEFFECT_NONE;
}
else if (uCommand == 0)
{
hr = S_OK;
*pdwEffect = DROPEFFECT_NONE;
}
else if (uCommand == IDM_COPYHERE)
*pdwEffect = DROPEFFECT_COPY;
else if (uCommand == IDM_MOVEHERE)
*pdwEffect = DROPEFFECT_MOVE;
else if (uCommand == IDM_LINKHERE)
*pdwEffect = DROPEFFECT_LINK;
else
hr = E_UNEXPECTED;
return S_OK;
DCMA_Destroy(hDCMA);
DCIA_Destroy(hDCIA);
DestroyMenu(hmenu);
return hr;
}
HRESULT CFSDropTarget::_RepositionItems(IShellFolderView *psfv, IDataObject *pdtobj, POINTL pt)
@ -461,7 +495,7 @@ HRESULT WINAPI CFSDropTarget::Drop(IDataObject *pDataObject,
if (m_grfKeyState & MK_RBUTTON)
{
HRESULT hr = _GetEffectFromMenu(pDataObject, pt, pdwEffect, dwAvailableEffects);
if (FAILED_UNEXPECTEDLY(hr) || hr == S_FALSE)
if (FAILED_UNEXPECTEDLY(hr) || hr == S_OK)
return hr;
}

View file

@ -23,6 +23,9 @@
#ifndef _CFSDROPTARGET_H_
#define _CFSDROPTARGET_H_
#define DROPIDM_EXTFIRST 100
#define DROPIDM_EXTLAST 0x7fff
class CFSDropTarget :
public CComObjectRootEx<CComMultiThreadModelNoCS>,
public IDropTarget,

View file

@ -179,6 +179,30 @@ SHELL32_ShowShellExtensionProperties(const CLSID *pClsid, IDataObject *pDO);
HRESULT
SHELL_ShowItemIDListProperties(LPCITEMIDLIST pidl);
typedef HDSA HDCMA; // DynamicContextMenuArray
typedef struct _DCMENTRY
{
IContextMenu *pCM;
UINT idCmdFirst;
UINT idCmdLast;
} DCMENTRY;
#define DCMA_Create() ( (HDCMA)DSA_Create(sizeof(DCMENTRY), 4) )
void DCMA_Destroy(HDCMA hDCMA);
#define DCMA_GetEntry(hDCMA, iItem) ( (DCMENTRY*)DSA_GetItemPtr((HDSA)(hDCMA), (iItem)) )
HRESULT DCMA_InvokeCommand(HDCMA hDCMA, CMINVOKECOMMANDINFO *pICI);
UINT
DCMA_InsertMenuItems(
_In_ HDCMA hDCMA,
_In_ HDCIA hDCIA,
_In_opt_ LPCITEMIDLIST pidlFolder,
_In_opt_ IDataObject *pDO,
_In_opt_ HKEY *pKeys,
_In_opt_ UINT nKeys,
_In_ QCMINFO *pQCMI,
_In_opt_ UINT fCmf,
_In_opt_ IUnknown *pUnkSite);
HRESULT
SHELL32_DefaultContextMenuCallBack(IShellFolder *psf, IDataObject *pdo, UINT msg);
UINT

View file

@ -9,17 +9,17 @@
WINE_DEFAULT_DEBUG_CHANNEL(shell);
static HRESULT
SHELL_GetShellExtensionRegCLSID(HKEY hKey, LPCWSTR KeyName, CLSID &clsid)
HRESULT
SHELL_GetShellExtensionRegCLSID(HKEY hKey, LPCWSTR KeyName, CLSID *pClsId)
{
// First try the key name
if (SUCCEEDED(SHCLSIDFromStringW(KeyName, &clsid)))
if (SUCCEEDED(SHCLSIDFromStringW(KeyName, pClsId)))
return S_OK;
WCHAR buf[42];
DWORD cb = sizeof(buf);
// and then the default value
DWORD err = RegGetValueW(hKey, KeyName, NULL, RRF_RT_REG_SZ, NULL, buf, &cb);
return !err ? SHCLSIDFromStringW(buf, &clsid) : HRESULT_FROM_WIN32(err);
return !err ? SHCLSIDFromStringW(buf, pClsId) : HRESULT_FROM_WIN32(err);
}
static HRESULT
@ -148,7 +148,7 @@ SHELL32_OpenPropSheet(LPCWSTR pszCaption, HKEY *ahKeys, UINT cKeys,
if (err)
break;
CLSID clsid;
if (SUCCEEDED(SHELL_GetShellExtensionRegCLSID(hKey, KeyName, clsid)))
if (SUCCEEDED(SHELL_GetShellExtensionRegCLSID(hKey, KeyName, &clsid)))
AddPropSheetHandlerPages(clsid, pDO, hKeyProgID, psh);
}
RegCloseKey(hKey);

View file

@ -466,5 +466,5 @@
754 stub -noname SHLimitInputEditWithFlags
755 stdcall -noname PathIsEqualOrSubFolder(wstr wstr)
756 stub -noname DeleteFileThumbnail
757 stdcall -version=0x600+ DisplayNameOfW(ptr ptr long ptr long)
866 stdcall -version=0x600+ SHExtCoCreateInstance(wstr ptr ptr ptr ptr)
757 stdcall -noname -version=0x600+ DisplayNameOfW(ptr ptr long ptr long)
866 stdcall -noname -version=0x600+ SHExtCoCreateInstance(wstr ptr ptr ptr ptr)

View file

@ -184,6 +184,7 @@ void CloseRegKeyArray(HKEY* array, UINT cKeys);
LSTATUS AddClassKeyToArray(const WCHAR* szClass, HKEY* array, UINT* cKeys);
LSTATUS AddClsidKeyToArray(REFCLSID clsid, HKEY* array, UINT* cKeys);
void AddFSClassKeysToArray(UINT cidl, PCUITEMID_CHILD_ARRAY apidl, HKEY* array, UINT* cKeys);
void AddPidlClassKeysToArray(LPCITEMIDLIST pidl, HKEY* array, UINT* cKeys);
#ifdef __cplusplus

View file

@ -490,12 +490,23 @@ void AddFSClassKeysToArray(UINT cidl, PCUITEMID_CHILD_ARRAY apidl, HKEY* array,
AddClassKeyToArray(L"AllFilesystemObjects", array, cKeys);
AddClassKeyToArray(L"Directory", array, cKeys);
}
else if (_ILIsDrive(pidl))
{
AddClassKeyToArray(L"Drive", array, cKeys);
AddClassKeyToArray(L"Folder", array, cKeys);
}
else
{
ERR("Got non FS pidl\n");
}
}
void AddPidlClassKeysToArray(LPCITEMIDLIST pidl, HKEY* array, UINT* cKeys)
{
if ((pidl = ILFindLastID(pidl)) != NULL)
AddFSClassKeysToArray(1, &pidl, array, cKeys);
}
HRESULT SH_GetApidlFromDataObject(IDataObject *pDataObject, PIDLIST_ABSOLUTE* ppidlfolder, PUITEMID_CHILD **apidlItems, UINT *pcidl)
{
CDataObjectHIDA cida(pDataObject);

View file

@ -1836,6 +1836,35 @@ SHELL_CreateShell32DefaultExtractIcon(int IconIndex, REFIID riid, LPVOID *ppvOut
return initIcon->QueryInterface(riid, ppvOut);
}
int DCIA_AddEntry(HDCIA hDCIA, REFCLSID rClsId)
{
for (UINT i = 0;; ++i)
{
const CLSID *pClsId = DCIA_GetEntry(hDCIA, i);
if (!pClsId)
break;
if (IsEqualGUID(*pClsId, rClsId))
return i; // Don't allow duplicates
}
return DSA_AppendItem((HDSA)hDCIA, const_cast<CLSID*>(&rClsId));
}
void DCIA_AddShellExSubkey(HDCIA hDCIA, HKEY hProgId, PCWSTR pszSubkey)
{
WCHAR szKey[200];
PathCombineW(szKey, L"shellex", pszSubkey);
HKEY hEnum;
if (RegOpenKeyExW(hProgId, szKey, 0, KEY_READ, &hEnum) != ERROR_SUCCESS)
return;
for (UINT i = 0; RegEnumKeyW(hEnum, i++, szKey, _countof(szKey)) == ERROR_SUCCESS;)
{
CLSID clsid;
if (SUCCEEDED(SHELL_GetShellExtensionRegCLSID(hEnum, szKey, &clsid)))
DCIA_AddEntry(hDCIA, clsid);
}
RegCloseKey(hEnum);
}
/*************************************************************************
* SHIsBadInterfacePtr [SHELL32.84]
*

View file

@ -120,6 +120,14 @@ SHELL_CreateFallbackExtractIconForNoAssocFile(REFIID riid, LPVOID *ppvOut)
return SHELL_CreateShell32DefaultExtractIcon(id > 1 ? -id : 0, riid, ppvOut);
}
typedef HDSA HDCIA; // DynamicClassIdArray
#define DCIA_Create() ( (HDCIA)DSA_Create(sizeof(CLSID), 4) )
#define DCIA_Destroy(hDCIA) DSA_Destroy((HDSA)(hDCIA))
#define DCIA_GetCount(hDCIA) DSA_GetItemCount((HDSA)(hDCIA))
#define DCIA_GetEntry(hDCIA, iItem) ( (const CLSID*)DSA_GetItemPtr((HDSA)(hDCIA), (iItem)) )
int DCIA_AddEntry(HDCIA hDCIA, REFCLSID rClsId);
void DCIA_AddShellExSubkey(HDCIA hDCIA, HKEY hProgId, PCWSTR pszSubkey);
#ifdef __cplusplus
struct ClipboardViewerChain
{

View file

@ -133,6 +133,11 @@ HGLOBAL RenderSHELLIDLIST (LPITEMIDLIST pidlRoot, LPITEMIDLIST * apidl, UINT cid
HGLOBAL RenderFILENAMEA (LPITEMIDLIST pidlRoot, LPITEMIDLIST * apidl, UINT cidl) DECLSPEC_HIDDEN;
HGLOBAL RenderFILENAMEW (LPITEMIDLIST pidlRoot, LPITEMIDLIST * apidl, UINT cidl) DECLSPEC_HIDDEN;
HRESULT SHELL_GetShellExtensionRegCLSID(
HKEY hKey,
LPCWSTR KeyName,
CLSID *pClsId);
/* Change Notification */
void InitChangeNotifications(void) DECLSPEC_HIDDEN;
void FreeChangeNotifications(void) DECLSPEC_HIDDEN;