reactos/dll/win32/shell32/CDefaultContextMenu.cpp
Carl J. Bialorucki 78dc504e72
[SHELL32] Initialize MenuItemInfoW struct in AddStaticContextMenusToMenu (#6737)
This prevents an issue where clicking on most of the icons on the desktop
that isn't the file explorer causes explorer to throw an exception,
and also prevents another issue where most applications won't launch from the start menu;
at least on MSVC builds running on VirtualBox 5.2.44.

^- I am pretty sure, that this was not always the case. The guilty rev is yet unknown though.
2024-04-14 00:12:35 +02:00

1713 lines
50 KiB
C++

/*
* PROJECT: shell32
* LICENSE: GPL - See COPYING in the top level directory
* FILE: dll/win32/shell32/shv_item_new.c
* PURPOSE: provides default context menu implementation
* PROGRAMMERS: Johannes Anderwald (johannes.anderwald@reactos.org)
*/
#include "precomp.h"
#include <compat_undoc.h>
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;
}
static BOOL InsertMenuItemAt(HMENU hMenu, UINT Pos, UINT Flags)
{
MENUITEMINFOW mii;
mii.cbSize = FIELD_OFFSET(MENUITEMINFOW, hbmpItem); // USER32 version agnostic
mii.fMask = MIIM_TYPE;
mii.fType = Flags;
return InsertMenuItemW(hMenu, Pos, TRUE, &mii);
}
typedef struct _DynamicShellEntry_
{
UINT iIdCmdFirst;
UINT NumIds;
CLSID ClassID;
CComPtr<IContextMenu> pCM;
} DynamicShellEntry, *PDynamicShellEntry;
typedef struct _StaticShellEntry_
{
CStringW Verb;
HKEY hkClass;
} StaticShellEntry, *PStaticShellEntry;
#define DCM_FCIDM_SHVIEW_OFFSET 0x7000 // Offset from the menu ids in the menu resource to FCIDM_SHVIEW_*
//
// verbs for InvokeCommandInfo
//
static const struct _StaticInvokeCommandMap_
{
LPCSTR szStringVerb;
WORD IntVerb;
SHORT DfmCmd;
} g_StaticInvokeCmdMap[] =
{
{ "RunAs", 0 }, // Unimplemented
{ "Print", 0 }, // Unimplemented
{ "Preview", 0 }, // Unimplemented
{ "Open", FCIDM_SHVIEW_OPEN },
{ CMDSTR_NEWFOLDERA, FCIDM_SHVIEW_NEWFOLDER, (SHORT)DFM_CMD_NEWFOLDER },
{ "cut", FCIDM_SHVIEW_CUT, /* ? */ },
{ "copy", FCIDM_SHVIEW_COPY, (SHORT)DFM_CMD_COPY },
{ "paste", FCIDM_SHVIEW_INSERT, (SHORT)DFM_CMD_PASTE },
{ "link", FCIDM_SHVIEW_CREATELINK, (SHORT)DFM_CMD_LINK },
{ "delete", FCIDM_SHVIEW_DELETE, (SHORT)DFM_CMD_DELETE },
{ "properties", FCIDM_SHVIEW_PROPERTIES, (SHORT)DFM_CMD_PROPERTIES },
{ "rename", FCIDM_SHVIEW_RENAME, (SHORT)DFM_CMD_RENAME },
{ "copyto", FCIDM_SHVIEW_COPYTO },
{ "moveto", FCIDM_SHVIEW_MOVETO },
};
class CDefaultContextMenu :
public CComObjectRootEx<CComMultiThreadModelNoCS>,
public IContextMenu3,
public IObjectWithSite,
public IServiceProvider
{
private:
CComPtr<IUnknown> m_site;
CComPtr<IShellFolder> m_psf;
CComPtr<IContextMenuCB> m_pmcb;
LPFNDFMCALLBACK m_pfnmcb;
UINT m_cidl;
PCUITEMID_CHILD_ARRAY m_apidl;
CComPtr<IDataObject> m_pDataObj;
HKEY* m_aKeys;
UINT m_cKeys;
PIDLIST_ABSOLUTE m_pidlFolder;
DWORD m_bGroupPolicyActive;
CAtlList<DynamicShellEntry> m_DynamicEntries;
UINT m_iIdSHEFirst; /* first used id */
UINT m_iIdSHELast; /* last used id */
CAtlList<StaticShellEntry> m_StaticEntries;
UINT m_iIdSCMFirst; /* first static used id */
UINT m_iIdSCMLast; /* last static used id */
UINT m_iIdCBFirst; /* first callback used id */
UINT m_iIdCBLast; /* last callback used id */
UINT m_iIdDfltFirst; /* first default part id */
UINT m_iIdDfltLast; /* last default part id */
HRESULT _DoCallback(UINT uMsg, WPARAM wParam, LPVOID lParam);
void AddStaticEntry(const HKEY hkeyClass, const WCHAR *szVerb, UINT uFlags);
void AddStaticEntriesForKey(HKEY hKey, UINT uFlags);
void TryPickDefault(HMENU hMenu, UINT idCmdFirst, UINT DfltOffset, UINT uFlags);
BOOL IsShellExtensionAlreadyLoaded(REFCLSID clsid);
HRESULT LoadDynamicContextMenuHandler(HKEY hKey, REFCLSID clsid);
BOOL EnumerateDynamicContextHandlerForKey(HKEY hRootKey);
UINT AddShellExtensionsToMenu(HMENU hMenu, UINT* pIndexMenu, UINT idCmdFirst, UINT idCmdLast, UINT uFlags);
UINT AddStaticContextMenusToMenu(HMENU hMenu, UINT* IndexMenu, UINT iIdCmdFirst, UINT iIdCmdLast, UINT uFlags);
HRESULT DoPaste(LPCMINVOKECOMMANDINFOEX lpcmi, BOOL bLink);
HRESULT DoOpenOrExplore(LPCMINVOKECOMMANDINFOEX lpcmi);
HRESULT DoCreateLink(LPCMINVOKECOMMANDINFOEX lpcmi);
HRESULT DoDelete(LPCMINVOKECOMMANDINFOEX lpcmi);
HRESULT DoCopyOrCut(LPCMINVOKECOMMANDINFOEX lpcmi, BOOL bCopy);
HRESULT DoRename(LPCMINVOKECOMMANDINFOEX lpcmi);
HRESULT DoProperties(LPCMINVOKECOMMANDINFOEX lpcmi);
HRESULT DoUndo(LPCMINVOKECOMMANDINFOEX lpcmi);
HRESULT DoCreateNewFolder(LPCMINVOKECOMMANDINFOEX lpici);
HRESULT DoCopyToMoveToFolder(LPCMINVOKECOMMANDINFOEX lpici, BOOL bCopy);
HRESULT InvokeShellExt(LPCMINVOKECOMMANDINFOEX lpcmi);
HRESULT InvokeRegVerb(LPCMINVOKECOMMANDINFOEX lpcmi);
DWORD BrowserFlagsFromVerb(LPCMINVOKECOMMANDINFOEX lpcmi, PStaticShellEntry pEntry);
HRESULT TryToBrowse(LPCMINVOKECOMMANDINFOEX lpcmi, LPCITEMIDLIST pidl, DWORD wFlags);
HRESULT InvokePidl(LPCMINVOKECOMMANDINFOEX lpcmi, LPCITEMIDLIST pidl, PStaticShellEntry pEntry);
PDynamicShellEntry GetDynamicEntry(UINT idCmd);
BOOL MapVerbToCmdId(PVOID Verb, PUINT idCmd, BOOL IsUnicode);
public:
CDefaultContextMenu();
~CDefaultContextMenu();
HRESULT WINAPI Initialize(const DEFCONTEXTMENU *pdcm, LPFNDFMCALLBACK lpfn);
// IContextMenu
STDMETHOD(QueryContextMenu)(HMENU hMenu, UINT indexMenu, UINT idCmdFirst, UINT idCmdLast, UINT uFlags) override;
STDMETHOD(InvokeCommand)(LPCMINVOKECOMMANDINFO lpcmi) override;
STDMETHOD(GetCommandString)(UINT_PTR idCommand, UINT uFlags, UINT *lpReserved, LPSTR lpszName, UINT uMaxNameLen) override;
// IContextMenu2
STDMETHOD(HandleMenuMsg)(UINT uMsg, WPARAM wParam, LPARAM lParam) override;
// IContextMenu3
STDMETHOD(HandleMenuMsg2)(UINT uMsg, WPARAM wParam, LPARAM lParam, LRESULT *plResult) override;
// IObjectWithSite
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()
};
CDefaultContextMenu::CDefaultContextMenu() :
m_psf(NULL),
m_pmcb(NULL),
m_pfnmcb(NULL),
m_cidl(0),
m_apidl(NULL),
m_pDataObj(NULL),
m_aKeys(NULL),
m_cKeys(NULL),
m_pidlFolder(NULL),
m_bGroupPolicyActive(0),
m_iIdSHEFirst(0),
m_iIdSHELast(0),
m_iIdSCMFirst(0),
m_iIdSCMLast(0),
m_iIdCBFirst(0),
m_iIdCBLast(0),
m_iIdDfltFirst(0),
m_iIdDfltLast(0)
{
}
CDefaultContextMenu::~CDefaultContextMenu()
{
m_DynamicEntries.RemoveAll();
m_StaticEntries.RemoveAll();
for (UINT i = 0; i < m_cKeys; i++)
RegCloseKey(m_aKeys[i]);
HeapFree(GetProcessHeap(), 0, m_aKeys);
if (m_pidlFolder)
CoTaskMemFree(m_pidlFolder);
_ILFreeaPidl(const_cast<PITEMID_CHILD *>(m_apidl), m_cidl);
}
HRESULT WINAPI CDefaultContextMenu::Initialize(const DEFCONTEXTMENU *pdcm, LPFNDFMCALLBACK lpfn)
{
TRACE("cidl %u\n", pdcm->cidl);
if (!pdcm->pcmcb && !lpfn)
{
ERR("CDefaultContextMenu needs a callback!\n");
return E_INVALIDARG;
}
m_cidl = pdcm->cidl;
m_apidl = const_cast<PCUITEMID_CHILD_ARRAY>(_ILCopyaPidl(pdcm->apidl, m_cidl));
if (m_cidl && !m_apidl)
return E_OUTOFMEMORY;
m_psf = pdcm->psf;
m_pmcb = pdcm->pcmcb;
m_pfnmcb = lpfn;
m_cKeys = pdcm->cKeys;
if (pdcm->cKeys)
{
m_aKeys = (HKEY*)HeapAlloc(GetProcessHeap(), 0, sizeof(HKEY) * pdcm->cKeys);
if (!m_aKeys)
return E_OUTOFMEMORY;
memcpy(m_aKeys, pdcm->aKeys, sizeof(HKEY) * pdcm->cKeys);
}
m_psf->GetUIObjectOf(pdcm->hwnd, m_cidl, m_apidl, IID_NULL_PPV_ARG(IDataObject, &m_pDataObj));
if (pdcm->pidlFolder)
{
m_pidlFolder = ILClone(pdcm->pidlFolder);
}
else
{
CComPtr<IPersistFolder2> pf = NULL;
if (SUCCEEDED(m_psf->QueryInterface(IID_PPV_ARG(IPersistFolder2, &pf))))
{
if (FAILED(pf->GetCurFolder(&m_pidlFolder)))
ERR("GetCurFolder failed\n");
}
TRACE("pidlFolder %p\n", m_pidlFolder);
}
return S_OK;
}
HRESULT CDefaultContextMenu::_DoCallback(UINT uMsg, WPARAM wParam, LPVOID lParam)
{
if (m_pmcb)
{
return m_pmcb->CallBack(m_psf, NULL, m_pDataObj, uMsg, wParam, (LPARAM)lParam);
}
else if(m_pfnmcb)
{
return m_pfnmcb(m_psf, NULL, m_pDataObj, uMsg, wParam, (LPARAM)lParam);
}
return E_FAIL;
}
void CDefaultContextMenu::AddStaticEntry(const HKEY hkeyClass, const WCHAR *szVerb, UINT uFlags)
{
POSITION it = m_StaticEntries.GetHeadPosition();
while (it != NULL)
{
const StaticShellEntry& info = m_StaticEntries.GetNext(it);
if (info.Verb.CompareNoCase(szVerb) == 0)
{
/* entry already exists */
return;
}
}
TRACE("adding verb %s\n", debugstr_w(szVerb));
if (!wcsicmp(szVerb, L"open") && !(uFlags & CMF_NODEFAULT))
{
/* open verb is always inserted in front */
m_StaticEntries.AddHead({ szVerb, hkeyClass });
}
else
{
m_StaticEntries.AddTail({ szVerb, hkeyClass });
}
}
void CDefaultContextMenu::AddStaticEntriesForKey(HKEY hKey, UINT uFlags)
{
WCHAR wszName[40];
DWORD cchName, dwIndex = 0;
HKEY hShellKey;
LRESULT lres = RegOpenKeyExW(hKey, L"shell", 0, KEY_READ, &hShellKey);
if (lres != STATUS_SUCCESS)
return;
while(TRUE)
{
cchName = _countof(wszName);
if (RegEnumKeyExW(hShellKey, dwIndex++, wszName, &cchName, NULL, NULL, NULL, NULL) != ERROR_SUCCESS)
break;
AddStaticEntry(hKey, wszName, uFlags);
}
RegCloseKey(hShellKey);
}
static
BOOL
HasClipboardData()
{
BOOL bRet = FALSE;
CComPtr<IDataObject> pDataObj;
if (SUCCEEDED(OleGetClipboard(&pDataObj)))
{
FORMATETC formatetc;
TRACE("pDataObj=%p\n", pDataObj.p);
/* Set the FORMATETC structure*/
InitFormatEtc(formatetc, RegisterClipboardFormatW(CFSTR_SHELLIDLIST), TYMED_HGLOBAL);
bRet = SUCCEEDED(pDataObj->QueryGetData(&formatetc));
}
return bRet;
}
BOOL
CDefaultContextMenu::IsShellExtensionAlreadyLoaded(REFCLSID clsid)
{
POSITION it = m_DynamicEntries.GetHeadPosition();
while (it != NULL)
{
const DynamicShellEntry& info = m_DynamicEntries.GetNext(it);
if (info.ClassID == clsid)
return TRUE;
}
return FALSE;
}
HRESULT
CDefaultContextMenu::LoadDynamicContextMenuHandler(HKEY hKey, REFCLSID clsid)
{
HRESULT hr;
TRACE("LoadDynamicContextMenuHandler entered with This %p hKey %p pclsid %s\n", this, hKey, wine_dbgstr_guid(&clsid));
if (IsShellExtensionAlreadyLoaded(clsid))
return S_OK;
CComPtr<IContextMenu> pcm;
hr = SHCoCreateInstance(NULL, &clsid, NULL, IID_PPV_ARG(IContextMenu, &pcm));
if (FAILED(hr))
{
ERR("SHCoCreateInstance(IContextMenu) failed.clsid %s hr 0x%x\n", wine_dbgstr_guid(&clsid), hr);
return hr;
}
CComPtr<IShellExtInit> pExtInit;
hr = pcm->QueryInterface(IID_PPV_ARG(IShellExtInit, &pExtInit));
if (FAILED(hr))
{
ERR("IContextMenu->QueryInterface(IShellExtInit) failed.clsid %s hr 0x%x\n", wine_dbgstr_guid(&clsid), hr);
return hr;
}
hr = pExtInit->Initialize(m_pidlFolder, m_pDataObj, hKey);
if (FAILED(hr))
{
WARN("IShellExtInit::Initialize failed.clsid %s hr 0x%x\n", wine_dbgstr_guid(&clsid), hr);
return hr;
}
if (m_site)
IUnknown_SetSite(pcm, m_site);
m_DynamicEntries.AddTail({ 0, 0, clsid, pcm });
return S_OK;
}
BOOL
CDefaultContextMenu::EnumerateDynamicContextHandlerForKey(HKEY hRootKey)
{
WCHAR wszName[MAX_PATH], wszBuf[MAX_PATH], *pwszClsid;
DWORD cchName;
HRESULT hr;
HKEY hKey;
if (RegOpenKeyExW(hRootKey, L"shellex\\ContextMenuHandlers", 0, KEY_READ, &hKey) != ERROR_SUCCESS)
{
TRACE("RegOpenKeyExW failed\n");
return FALSE;
}
DWORD dwIndex = 0;
while (TRUE)
{
cchName = _countof(wszName);
if (RegEnumKeyExW(hKey, dwIndex++, wszName, &cchName, NULL, NULL, NULL, NULL) != ERROR_SUCCESS)
break;
/* Key name or key value is CLSID */
CLSID clsid;
hr = CLSIDFromString(wszName, &clsid);
if (hr == S_OK)
pwszClsid = wszName;
else
{
DWORD cchBuf = _countof(wszBuf);
if (RegGetValueW(hKey, wszName, NULL, RRF_RT_REG_SZ, NULL, wszBuf, &cchBuf) == ERROR_SUCCESS)
hr = CLSIDFromString(wszBuf, &clsid);
pwszClsid = wszBuf;
}
if (FAILED(hr))
{
ERR("CLSIDFromString failed for clsid %S hr 0x%x\n", pwszClsid, hr);
continue;
}
if (m_bGroupPolicyActive)
{
if (RegGetValueW(HKEY_LOCAL_MACHINE,
L"Software\\Microsoft\\Windows\\CurrentVersion\\Shell Extensions\\Approved",
pwszClsid,
RRF_RT_REG_SZ,
NULL,
NULL,
NULL) != ERROR_SUCCESS)
{
ERR("Shell extension %s not approved!\n", pwszClsid);
continue;
}
}
hr = LoadDynamicContextMenuHandler(hKey, clsid);
if (FAILED(hr))
WARN("Failed to get context menu entires from shell extension! clsid: %S\n", pwszClsid);
}
RegCloseKey(hKey);
return TRUE;
}
UINT
CDefaultContextMenu::AddShellExtensionsToMenu(HMENU hMenu, UINT* pIndexMenu, UINT idCmdFirst, UINT idCmdLast, UINT uFlags)
{
UINT cIds = 0;
if (m_DynamicEntries.IsEmpty())
return cIds;
POSITION it = m_DynamicEntries.GetHeadPosition();
while (it != NULL)
{
DynamicShellEntry& info = m_DynamicEntries.GetNext(it);
HRESULT hr = info.pCM->QueryContextMenu(hMenu, *pIndexMenu, idCmdFirst + cIds, idCmdLast, uFlags);
if (SUCCEEDED(hr))
{
info.iIdCmdFirst = cIds;
info.NumIds = HRESULT_CODE(hr);
(*pIndexMenu) += info.NumIds;
cIds += info.NumIds;
if (idCmdFirst + cIds >= idCmdLast)
break;
}
TRACE("pEntry hr %x contextmenu %p cmdfirst %x num ids %x\n", hr, info.pCM.p, info.iIdCmdFirst, info.NumIds);
}
return cIds;
}
UINT
CDefaultContextMenu::AddStaticContextMenusToMenu(
HMENU hMenu,
UINT* pIndexMenu,
UINT iIdCmdFirst,
UINT iIdCmdLast,
UINT uFlags)
{
UINT ntver = RosGetProcessEffectiveVersion();
MENUITEMINFOW mii = { sizeof(mii) };
UINT idResource;
WCHAR wszVerb[40];
UINT fState;
UINT cIds = 0, indexFirst = *pIndexMenu;
mii.fMask = MIIM_ID | MIIM_TYPE | MIIM_STATE | MIIM_DATA;
mii.fType = MFT_STRING;
mii.dwTypeData = NULL;
POSITION it = m_StaticEntries.GetHeadPosition();
bool first = true;
while (it != NULL)
{
StaticShellEntry& info = m_StaticEntries.GetNext(it);
BOOL forceFirstPos = FALSE;
fState = MFS_ENABLED;
mii.dwTypeData = NULL;
/* set first entry as default */
if (first)
{
fState |= MFS_DEFAULT;
first = false;
}
if (info.Verb.CompareNoCase(L"open") == 0)
{
idResource = IDS_OPEN_VERB;
fState |= MFS_DEFAULT; /* override default when open verb is found */
forceFirstPos++;
}
else if (info.Verb.CompareNoCase(L"explore") == 0)
{
idResource = IDS_EXPLORE_VERB;
if (uFlags & CMF_EXPLORE)
{
fState |= MFS_DEFAULT;
forceFirstPos++;
}
}
else if (info.Verb.CompareNoCase(L"runas") == 0)
idResource = IDS_RUNAS_VERB;
else if (info.Verb.CompareNoCase(L"edit") == 0)
idResource = IDS_EDIT_VERB;
else if (info.Verb.CompareNoCase(L"find") == 0)
idResource = IDS_FIND_VERB;
else if (info.Verb.CompareNoCase(L"print") == 0)
idResource = IDS_PRINT_VERB;
else if (info.Verb.CompareNoCase(L"printto") == 0)
continue;
else
idResource = 0;
/* By default use verb for menu item name */
mii.dwTypeData = (LPWSTR)info.Verb.GetString();
WCHAR wszKey[sizeof("shell\\") + MAX_VERB];
HRESULT hr;
hr = StringCbPrintfW(wszKey, sizeof(wszKey), L"shell\\%s", info.Verb.GetString());
if (FAILED_UNEXPECTEDLY(hr))
{
continue;
}
UINT cmdFlags = 0;
bool hide = false;
HKEY hkVerb;
if (idResource > 0)
{
if (!(uFlags & CMF_OPTIMIZEFORINVOKE))
{
if (LoadStringW(shell32_hInstance, idResource, wszVerb, _countof(wszVerb)))
mii.dwTypeData = wszVerb; /* use translated verb */
else
ERR("Failed to load string\n");
}
if (RegOpenKeyW(info.hkClass, wszKey, &hkVerb) != ERROR_SUCCESS)
hkVerb = NULL;
}
else
{
if (RegOpenKeyW(info.hkClass, wszKey, &hkVerb) == ERROR_SUCCESS)
{
if (!(uFlags & CMF_OPTIMIZEFORINVOKE))
{
DWORD cbVerb = sizeof(wszVerb);
LONG res = RegLoadMUIStringW(hkVerb, L"MUIVerb", wszVerb, cbVerb, NULL, 0, NULL);
if (res || !*wszVerb)
res = RegLoadMUIStringW(hkVerb, NULL, wszVerb, cbVerb, NULL, 0, NULL);
if (res == ERROR_SUCCESS && *wszVerb)
{
/* use description for the menu entry */
mii.dwTypeData = wszVerb;
}
}
}
else
{
hkVerb = NULL;
}
}
if (hkVerb)
{
// FIXME: GetAsyncKeyState should not be called here, clients
// need to be updated to set the CMF_EXTENDEDVERBS flag.
if (!(uFlags & CMF_EXTENDEDVERBS) && GetAsyncKeyState(VK_SHIFT) >= 0)
hide = RegValueExists(hkVerb, L"Extended");
if (!hide)
hide = RegValueExists(hkVerb, L"ProgrammaticAccessOnly");
if (!hide && !(uFlags & CMF_DISABLEDVERBS))
hide = RegValueExists(hkVerb, L"LegacyDisable");
if (RegValueExists(hkVerb, L"NeverDefault"))
fState &= ~MFS_DEFAULT;
if (RegValueExists(hkVerb, L"SeparatorBefore"))
cmdFlags |= ECF_SEPARATORBEFORE;
if (RegValueExists(hkVerb, L"SeparatorAfter"))
cmdFlags |= ECF_SEPARATORAFTER;
RegCloseKey(hkVerb);
}
if (((uFlags & CMF_NODEFAULT) && ntver >= _WIN32_WINNT_VISTA) ||
((uFlags & CMF_DONOTPICKDEFAULT) && ntver >= _WIN32_WINNT_WIN7))
{
fState &= ~MFS_DEFAULT;
}
if (!hide)
{
if (cmdFlags & ECF_SEPARATORBEFORE)
{
if (InsertMenuItemAt(hMenu, *pIndexMenu, MF_SEPARATOR))
(*pIndexMenu)++;
}
mii.fState = fState;
mii.wID = iIdCmdFirst + cIds;
if (InsertMenuItemW(hMenu, forceFirstPos ? indexFirst : *pIndexMenu, TRUE, &mii))
(*pIndexMenu)++;
if (cmdFlags & ECF_SEPARATORAFTER)
{
if (InsertMenuItemAt(hMenu, *pIndexMenu, MF_SEPARATOR))
(*pIndexMenu)++;
}
}
cIds++; // Always increment the id because it acts as the index into m_StaticEntries
if (mii.wID >= iIdCmdLast)
break;
}
return cIds;
}
void WINAPI _InsertMenuItemW(
HMENU hMenu,
UINT indexMenu,
BOOL fByPosition,
UINT wID,
UINT fType,
LPCWSTR dwTypeData,
UINT fState)
{
MENUITEMINFOW mii;
WCHAR wszText[100];
ZeroMemory(&mii, sizeof(mii));
mii.cbSize = sizeof(mii);
if (fType == MFT_SEPARATOR)
mii.fMask = MIIM_ID | MIIM_TYPE;
else if (fType == MFT_STRING)
{
mii.fMask = MIIM_ID | MIIM_TYPE | MIIM_STATE;
if (IS_INTRESOURCE(dwTypeData))
{
if (LoadStringW(shell32_hInstance, LOWORD((ULONG_PTR)dwTypeData), wszText, _countof(wszText)))
mii.dwTypeData = wszText;
else
{
ERR("failed to load string %p\n", dwTypeData);
return;
}
}
else
mii.dwTypeData = (LPWSTR)dwTypeData;
mii.fState = fState;
}
mii.wID = wID;
mii.fType = fType;
InsertMenuItemW(hMenu, indexMenu, fByPosition, &mii);
}
void
CDefaultContextMenu::TryPickDefault(HMENU hMenu, UINT idCmdFirst, UINT DfltOffset, UINT uFlags)
{
// Are we allowed to pick a default?
UINT ntver = RosGetProcessEffectiveVersion();
if (((uFlags & CMF_NODEFAULT) && ntver >= _WIN32_WINNT_VISTA) ||
((uFlags & CMF_DONOTPICKDEFAULT) && ntver >= _WIN32_WINNT_WIN7))
{
return;
}
// Do we already have a default?
if ((int)GetMenuDefaultItem(hMenu, MF_BYPOSITION, 0) != -1)
return;
// Does the view want to pick one?
INT_PTR forceDfm = 0;
if (_DoCallback(DFM_GETDEFSTATICID, 0, &forceDfm) == S_OK && forceDfm)
{
for (UINT i = 0; i < _countof(g_StaticInvokeCmdMap); ++i)
{
UINT menuItemId = g_StaticInvokeCmdMap[i].IntVerb + DfltOffset - DCM_FCIDM_SHVIEW_OFFSET;
if (g_StaticInvokeCmdMap[i].DfmCmd == forceDfm &&
SetMenuDefaultItem(hMenu, menuItemId, MF_BYCOMMAND))
{
return;
}
}
}
// Don't want to pick something like cut or delete as the default but
// a static or dynamic verb is a good default.
if (m_iIdSCMLast > m_iIdSCMFirst || m_iIdSHELast > m_iIdSHEFirst)
SetMenuDefaultItem(hMenu, idCmdFirst, MF_BYCOMMAND);
}
HRESULT
WINAPI
CDefaultContextMenu::QueryContextMenu(
HMENU hMenu,
UINT IndexMenu,
UINT idCmdFirst,
UINT idCmdLast,
UINT uFlags)
{
HRESULT hr;
UINT idCmdNext = idCmdFirst;
UINT cIds = 0;
TRACE("BuildShellItemContextMenu entered\n");
/* Load static verbs and shell extensions from registry */
for (UINT i = 0; i < m_cKeys && !(uFlags & CMF_NOVERBS); i++)
{
AddStaticEntriesForKey(m_aKeys[i], uFlags);
EnumerateDynamicContextHandlerForKey(m_aKeys[i]);
}
/* Add static context menu handlers */
cIds = AddStaticContextMenusToMenu(hMenu, &IndexMenu, idCmdFirst, idCmdLast, uFlags);
m_iIdSCMFirst = 0; // FIXME: This should be = idCmdFirst?
m_iIdSCMLast = cIds;
idCmdNext = idCmdFirst + cIds;
/* Add dynamic context menu handlers */
cIds += AddShellExtensionsToMenu(hMenu, &IndexMenu, idCmdNext, idCmdLast, uFlags);
m_iIdSHEFirst = m_iIdSCMLast;
m_iIdSHELast = cIds;
idCmdNext = idCmdFirst + cIds;
TRACE("SH_LoadContextMenuHandlers first %x last %x\n", m_iIdSHEFirst, m_iIdSHELast);
/* Now let the callback add its own items */
QCMINFO qcminfo = {hMenu, IndexMenu, idCmdNext, idCmdLast, NULL};
if (SUCCEEDED(_DoCallback(DFM_MERGECONTEXTMENU, uFlags, &qcminfo)))
{
UINT added = qcminfo.idCmdFirst - idCmdNext;
cIds += added;
IndexMenu += added;
m_iIdCBFirst = m_iIdSHELast;
m_iIdCBLast = cIds;
idCmdNext = idCmdFirst + cIds;
}
//TODO: DFM_MERGECONTEXTMENU_BOTTOM
UINT idDefaultOffset = 0;
BOOL isBackgroundMenu = !m_cidl;
if (!(uFlags & CMF_VERBSONLY) && !isBackgroundMenu)
{
/* Get the attributes of the items */
SFGAOF rfg = SFGAO_BROWSABLE | SFGAO_CANCOPY | SFGAO_CANLINK | SFGAO_CANMOVE | SFGAO_CANDELETE | SFGAO_CANRENAME | SFGAO_HASPROPSHEET | SFGAO_FILESYSTEM | SFGAO_FOLDER;
hr = m_psf->GetAttributesOf(m_cidl, m_apidl, &rfg);
if (FAILED_UNEXPECTEDLY(hr))
return MAKE_HRESULT(SEVERITY_SUCCESS, 0, cIds);
/* Add the default part of the menu */
HMENU hmenuDefault = LoadMenu(_AtlBaseModule.GetResourceInstance(), L"MENU_SHV_FILE");
/* Remove uneeded entries */
if (!(rfg & SFGAO_CANMOVE))
DeleteMenu(hmenuDefault, IDM_CUT, MF_BYCOMMAND);
if (!(rfg & SFGAO_CANCOPY))
DeleteMenu(hmenuDefault, IDM_COPY, MF_BYCOMMAND);
if (!((rfg & SFGAO_FILESYSTEM) && HasClipboardData()))
DeleteMenu(hmenuDefault, IDM_INSERT, MF_BYCOMMAND);
if (!(rfg & SFGAO_CANLINK))
DeleteMenu(hmenuDefault, IDM_CREATELINK, MF_BYCOMMAND);
if (!(rfg & SFGAO_CANDELETE))
DeleteMenu(hmenuDefault, IDM_DELETE, MF_BYCOMMAND);
if (!(rfg & SFGAO_CANRENAME))
DeleteMenu(hmenuDefault, IDM_RENAME, MF_BYCOMMAND);
if (!(rfg & SFGAO_HASPROPSHEET))
DeleteMenu(hmenuDefault, IDM_PROPERTIES, MF_BYCOMMAND);
idDefaultOffset = idCmdNext;
UINT idMax = Shell_MergeMenus(hMenu, GetSubMenu(hmenuDefault, 0), IndexMenu, idCmdNext, idCmdLast, 0);
m_iIdDfltFirst = cIds;
cIds += idMax - idCmdNext;
m_iIdDfltLast = cIds;
DestroyMenu(hmenuDefault);
}
TryPickDefault(hMenu, idCmdFirst, idDefaultOffset, uFlags);
// TODO: DFM_MERGECONTEXTMENU_TOP
return MAKE_HRESULT(SEVERITY_SUCCESS, 0, cIds);
}
HRESULT CDefaultContextMenu::DoPaste(LPCMINVOKECOMMANDINFOEX lpcmi, BOOL bLink)
{
HRESULT hr;
CComPtr<IDataObject> pda;
hr = OleGetClipboard(&pda);
if (FAILED_UNEXPECTEDLY(hr))
return hr;
FORMATETC formatetc2;
STGMEDIUM medium2;
InitFormatEtc(formatetc2, RegisterClipboardFormatW(CFSTR_PREFERREDDROPEFFECT), TYMED_HGLOBAL);
DWORD dwKey= 0;
if (SUCCEEDED(pda->GetData(&formatetc2, &medium2)))
{
DWORD * pdwFlag = (DWORD*)GlobalLock(medium2.hGlobal);
if (pdwFlag)
{
if (*pdwFlag == DROPEFFECT_COPY)
dwKey = MK_CONTROL;
else
dwKey = MK_SHIFT;
}
else
{
ERR("No drop effect obtained\n");
}
GlobalUnlock(medium2.hGlobal);
}
if (bLink)
{
dwKey = MK_CONTROL|MK_SHIFT;
}
CComPtr<IDropTarget> pdrop;
if (m_cidl)
hr = m_psf->GetUIObjectOf(NULL, 1, &m_apidl[0], IID_NULL_PPV_ARG(IDropTarget, &pdrop));
else
hr = m_psf->CreateViewObject(NULL, IID_PPV_ARG(IDropTarget, &pdrop));
if (FAILED_UNEXPECTEDLY(hr))
return hr;
SHSimulateDrop(pdrop, pda, dwKey, NULL, NULL);
TRACE("CP result %x\n", hr);
return S_OK;
}
HRESULT
CDefaultContextMenu::DoOpenOrExplore(LPCMINVOKECOMMANDINFOEX lpcmi)
{
UNIMPLEMENTED;
return E_FAIL;
}
HRESULT CDefaultContextMenu::DoCreateLink(LPCMINVOKECOMMANDINFOEX lpcmi)
{
if (!m_cidl || !m_pDataObj)
return E_FAIL;
CComPtr<IDropTarget> pDT;
HRESULT hr = m_psf->CreateViewObject(NULL, IID_PPV_ARG(IDropTarget, &pDT));
if (FAILED_UNEXPECTEDLY(hr))
return hr;
SHSimulateDrop(pDT, m_pDataObj, MK_CONTROL|MK_SHIFT, NULL, NULL);
return S_OK;
}
HRESULT CDefaultContextMenu::DoDelete(LPCMINVOKECOMMANDINFOEX lpcmi)
{
if (!m_cidl || !m_pDataObj)
return E_FAIL;
CComPtr<IDropTarget> pDT;
HRESULT hr = CRecyclerDropTarget_CreateInstance(IID_PPV_ARG(IDropTarget, &pDT));
if (FAILED_UNEXPECTEDLY(hr))
return hr;
DWORD grfKeyState = (lpcmi->fMask & CMIC_MASK_SHIFT_DOWN) ? MK_SHIFT : 0;
SHSimulateDrop(pDT, m_pDataObj, grfKeyState, NULL, NULL);
return S_OK;
}
HRESULT CDefaultContextMenu::DoCopyOrCut(LPCMINVOKECOMMANDINFOEX lpcmi, BOOL bCopy)
{
if (!m_cidl || !m_pDataObj)
return E_FAIL;
FORMATETC formatetc;
InitFormatEtc(formatetc, RegisterClipboardFormatW(CFSTR_PREFERREDDROPEFFECT), TYMED_HGLOBAL);
STGMEDIUM medium = {0};
medium.tymed = TYMED_HGLOBAL;
medium.hGlobal = GlobalAlloc(GHND, sizeof(DWORD));
DWORD* pdwFlag = (DWORD*)GlobalLock(medium.hGlobal);
if (pdwFlag)
*pdwFlag = bCopy ? DROPEFFECT_COPY : DROPEFFECT_MOVE;
GlobalUnlock(medium.hGlobal);
m_pDataObj->SetData(&formatetc, &medium, TRUE);
HRESULT hr = OleSetClipboard(m_pDataObj);
if (FAILED_UNEXPECTEDLY(hr))
return hr;
return S_OK;
}
HRESULT CDefaultContextMenu::DoRename(LPCMINVOKECOMMANDINFOEX lpcmi)
{
CComPtr<IShellBrowser> psb;
HRESULT hr;
if (!m_site || !m_cidl)
return E_FAIL;
/* Get a pointer to the shell browser */
hr = IUnknown_QueryService(m_site, SID_IShellBrowser, IID_PPV_ARG(IShellBrowser, &psb));
if (FAILED_UNEXPECTEDLY(hr))
return hr;
CComPtr<IShellView> lpSV;
hr = psb->QueryActiveShellView(&lpSV);
if (FAILED_UNEXPECTEDLY(hr))
return hr;
SVSIF selFlags = SVSI_DESELECTOTHERS | SVSI_EDIT | SVSI_ENSUREVISIBLE | SVSI_FOCUSED | SVSI_SELECT;
hr = lpSV->SelectItem(m_apidl[0], selFlags);
if (FAILED_UNEXPECTEDLY(hr))
return hr;
return S_OK;
}
HRESULT
CDefaultContextMenu::DoProperties(
LPCMINVOKECOMMANDINFOEX lpcmi)
{
HRESULT hr = _DoCallback(DFM_INVOKECOMMAND, DFM_CMD_PROPERTIES, NULL);
// We are asked to run the default property sheet
if (hr == S_FALSE)
{
return Shell_DefaultContextMenuCallBack(m_psf, m_pDataObj);
}
return hr;
}
HRESULT
CDefaultContextMenu::DoUndo(LPCMINVOKECOMMANDINFOEX lpcmi)
{
ERR("TODO: Undo\n");
return E_NOTIMPL;
}
HRESULT
CDefaultContextMenu::DoCopyToMoveToFolder(LPCMINVOKECOMMANDINFOEX lpici, BOOL bCopy)
{
HRESULT hr = E_FAIL;
if (!m_pDataObj)
{
ERR("m_pDataObj is NULL\n");
return hr;
}
CComPtr<IContextMenu> pContextMenu;
if (bCopy)
hr = SHCoCreateInstance(NULL, &CLSID_CopyToMenu, NULL,
IID_PPV_ARG(IContextMenu, &pContextMenu));
else
hr = SHCoCreateInstance(NULL, &CLSID_MoveToMenu, NULL,
IID_PPV_ARG(IContextMenu, &pContextMenu));
if (FAILED_UNEXPECTEDLY(hr))
return hr;
CComPtr<IShellExtInit> pInit;
hr = pContextMenu->QueryInterface(IID_PPV_ARG(IShellExtInit, &pInit));
if (FAILED_UNEXPECTEDLY(hr))
return hr;
hr = pInit->Initialize(m_pidlFolder, m_pDataObj, NULL);
if (FAILED_UNEXPECTEDLY(hr))
return hr;
if (bCopy)
lpici->lpVerb = "copyto";
else
lpici->lpVerb = "moveto";
return pContextMenu->InvokeCommand((LPCMINVOKECOMMANDINFO)lpici);
}
// This code is taken from CNewMenu and should be shared between the 2 classes
HRESULT
CDefaultContextMenu::DoCreateNewFolder(
LPCMINVOKECOMMANDINFOEX lpici)
{
WCHAR wszPath[MAX_PATH];
WCHAR wszName[MAX_PATH];
WCHAR wszNewFolder[25];
HRESULT hr;
/* Get folder path */
hr = SHGetPathFromIDListW(m_pidlFolder, wszPath);
if (FAILED_UNEXPECTEDLY(hr))
return hr;
if (!LoadStringW(shell32_hInstance, IDS_NEWFOLDER, wszNewFolder, _countof(wszNewFolder)))
return E_FAIL;
/* Create the name of the new directory */
if (!PathYetAnotherMakeUniqueName(wszName, wszPath, NULL, wszNewFolder))
return E_FAIL;
/* Create the new directory and show the appropriate dialog in case of error */
if (SHCreateDirectory(lpici->hwnd, wszName) != ERROR_SUCCESS)
return E_FAIL;
/* Show and select the new item in the def view */
LPITEMIDLIST pidl;
PITEMID_CHILD pidlNewItem;
CComPtr<IShellView> psv;
/* Notify the view object about the new item */
SHChangeNotify(SHCNE_MKDIR, SHCNF_PATHW | SHCNF_FLUSH, (LPCVOID)wszName, NULL);
if (!m_site)
return S_OK;
/* Get a pointer to the shell view */
hr = IUnknown_QueryService(m_site, SID_IFolderView, IID_PPV_ARG(IShellView, &psv));
if (FAILED_UNEXPECTEDLY(hr))
return S_OK;
/* Attempt to get the pidl of the new item */
hr = SHILCreateFromPathW(wszName, &pidl, NULL);
if (FAILED_UNEXPECTEDLY(hr))
return hr;
pidlNewItem = ILFindLastID(pidl);
hr = psv->SelectItem(pidlNewItem, SVSI_DESELECTOTHERS | SVSI_EDIT | SVSI_ENSUREVISIBLE |
SVSI_FOCUSED | SVSI_SELECT);
if (FAILED_UNEXPECTEDLY(hr))
return hr;
SHFree(pidl);
return S_OK;
}
PDynamicShellEntry CDefaultContextMenu::GetDynamicEntry(UINT idCmd)
{
POSITION it = m_DynamicEntries.GetHeadPosition();
while (it != NULL)
{
DynamicShellEntry& info = m_DynamicEntries.GetNext(it);
if (idCmd >= info.iIdCmdFirst + info.NumIds)
continue;
if (idCmd < info.iIdCmdFirst || idCmd > info.iIdCmdFirst + info.NumIds)
return NULL;
return &info;
}
return NULL;
}
BOOL
CDefaultContextMenu::MapVerbToCmdId(PVOID Verb, PUINT idCmd, BOOL IsUnicode)
{
WCHAR UnicodeStr[MAX_VERB];
/* Loop through all the static verbs looking for a match */
for (UINT i = 0; i < _countof(g_StaticInvokeCmdMap); i++)
{
/* We can match both ANSI and unicode strings */
if (IsUnicode)
{
/* The static verbs are ANSI, get a unicode version before doing the compare */
SHAnsiToUnicode(g_StaticInvokeCmdMap[i].szStringVerb, UnicodeStr, MAX_VERB);
if (!wcscmp(UnicodeStr, (LPWSTR)Verb))
{
/* Return the Corresponding Id */
*idCmd = g_StaticInvokeCmdMap[i].IntVerb;
return TRUE;
}
}
else
{
if (!strcmp(g_StaticInvokeCmdMap[i].szStringVerb, (LPSTR)Verb))
{
*idCmd = g_StaticInvokeCmdMap[i].IntVerb;
return TRUE;
}
}
}
return FALSE;
}
HRESULT
CDefaultContextMenu::InvokeShellExt(
LPCMINVOKECOMMANDINFOEX lpcmi)
{
TRACE("verb %p first %x last %x\n", lpcmi->lpVerb, m_iIdSHEFirst, m_iIdSHELast);
UINT idCmd = LOWORD(lpcmi->lpVerb);
PDynamicShellEntry pEntry = GetDynamicEntry(idCmd);
if (!pEntry)
return E_FAIL;
/* invoke the dynamic context menu */
lpcmi->lpVerb = MAKEINTRESOURCEA(idCmd - pEntry->iIdCmdFirst);
return pEntry->pCM->InvokeCommand((LPCMINVOKECOMMANDINFO)lpcmi);
}
DWORD
CDefaultContextMenu::BrowserFlagsFromVerb(LPCMINVOKECOMMANDINFOEX lpcmi, PStaticShellEntry pEntry)
{
CComPtr<IShellBrowser> psb;
HWND hwndTree;
LPCWSTR FlagsName;
WCHAR wszKey[sizeof("shell\\") + MAX_VERB];
HRESULT hr;
DWORD wFlags;
DWORD cbVerb;
if (!m_site)
return 0;
/* Get a pointer to the shell browser */
hr = IUnknown_QueryService(m_site, SID_IShellBrowser, IID_PPV_ARG(IShellBrowser, &psb));
if (FAILED(hr))
return 0;
/* See if we are in Explore or Browse mode. If the browser's tree is present, we are in Explore mode.*/
if (SUCCEEDED(psb->GetControlWindow(FCW_TREE, &hwndTree)) && hwndTree)
FlagsName = L"ExplorerFlags";
else
FlagsName = L"BrowserFlags";
/* Try to get the flag from the verb */
hr = StringCbPrintfW(wszKey, sizeof(wszKey), L"shell\\%s", pEntry->Verb.GetString());
if (FAILED_UNEXPECTEDLY(hr))
return 0;
cbVerb = sizeof(wFlags);
if (RegGetValueW(pEntry->hkClass, wszKey, FlagsName, RRF_RT_REG_DWORD, NULL, &wFlags, &cbVerb) == ERROR_SUCCESS)
{
return wFlags;
}
return 0;
}
HRESULT
CDefaultContextMenu::TryToBrowse(
LPCMINVOKECOMMANDINFOEX lpcmi, LPCITEMIDLIST pidlChild, DWORD wFlags)
{
CComPtr<IShellBrowser> psb;
HRESULT hr;
if (!m_site)
return E_FAIL;
/* Get a pointer to the shell browser */
hr = IUnknown_QueryService(m_site, SID_IShellBrowser, IID_PPV_ARG(IShellBrowser, &psb));
if (FAILED(hr))
return hr;
PIDLIST_ABSOLUTE pidl;
hr = SHILCombine(m_pidlFolder, pidlChild, &pidl);
if (FAILED_UNEXPECTEDLY(hr))
return hr;
hr = psb->BrowseObject(pidl, wFlags & ~SBSP_RELATIVE);
ILFree(pidl);
return hr;
}
HRESULT
CDefaultContextMenu::InvokePidl(LPCMINVOKECOMMANDINFOEX lpcmi, LPCITEMIDLIST pidl, PStaticShellEntry pEntry)
{
LPITEMIDLIST pidlFull = ILCombine(m_pidlFolder, pidl);
if (pidlFull == NULL)
{
return E_FAIL;
}
WCHAR wszPath[MAX_PATH];
BOOL bHasPath = SHGetPathFromIDListW(pidlFull, wszPath);
WCHAR wszDir[MAX_PATH];
if (bHasPath)
{
wcscpy(wszDir, wszPath);
PathRemoveFileSpec(wszDir);
}
else
{
if (!SHGetPathFromIDListW(m_pidlFolder, wszDir))
*wszDir = UNICODE_NULL;
}
SHELLEXECUTEINFOW sei;
ZeroMemory(&sei, sizeof(sei));
sei.cbSize = sizeof(sei);
sei.hwnd = lpcmi->hwnd;
sei.nShow = SW_SHOWNORMAL;
sei.lpVerb = pEntry->Verb;
sei.lpDirectory = wszDir;
sei.lpIDList = pidlFull;
sei.hkeyClass = pEntry->hkClass;
sei.fMask = SEE_MASK_CLASSKEY | SEE_MASK_IDLIST;
if (bHasPath)
{
sei.lpFile = wszPath;
}
ShellExecuteExW(&sei);
ILFree(pidlFull);
return S_OK;
}
HRESULT
CDefaultContextMenu::InvokeRegVerb(
LPCMINVOKECOMMANDINFOEX lpcmi)
{
INT iCmd = LOWORD(lpcmi->lpVerb);
HRESULT hr;
UINT i;
POSITION it = m_StaticEntries.FindIndex(iCmd);
if (it == NULL)
return E_INVALIDARG;
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);
for (i=0; i < m_cidl; i++)
{
/* Check if we need to browse */
if (wFlags)
{
hr = TryToBrowse(lpcmi, m_apidl[i], wFlags);
if (SUCCEEDED(hr))
{
/* In WinXP if we have browsed, we don't open any more folders.
* In Win7 we browse to the first folder we find and
* open new windows for each of the rest of the folders */
UINT ntver = RosGetProcessEffectiveVersion();
if (ntver >= _WIN32_WINNT_VISTA)
wFlags = 0; // FIXME: = SBSP_NEWBROWSER | (wFlags & ~SBSP_SAMEBROWSER);
else
i = m_cidl;
continue;
}
}
InvokePidl(lpcmi, m_apidl[i], pEntry);
}
return S_OK;
}
HRESULT
WINAPI
CDefaultContextMenu::InvokeCommand(
LPCMINVOKECOMMANDINFO lpcmi)
{
CMINVOKECOMMANDINFOEX LocalInvokeInfo = {};
HRESULT Result;
UINT CmdId;
/* Take a local copy of the fixed members of the
struct as we might need to modify the verb */
memcpy(&LocalInvokeInfo, lpcmi, min(sizeof(LocalInvokeInfo), lpcmi->cbSize));
/* Check if this is a string verb */
if (!IS_INTRESOURCE(LocalInvokeInfo.lpVerb))
{
/* Get the ID which corresponds to this verb, and update our local copy */
if (MapVerbToCmdId((LPVOID)LocalInvokeInfo.lpVerb, &CmdId, FALSE))
LocalInvokeInfo.lpVerb = MAKEINTRESOURCEA(CmdId);
}
CmdId = LOWORD(LocalInvokeInfo.lpVerb);
if (!m_DynamicEntries.IsEmpty() && CmdId >= m_iIdSHEFirst && CmdId < m_iIdSHELast)
{
LocalInvokeInfo.lpVerb -= m_iIdSHEFirst;
Result = InvokeShellExt(&LocalInvokeInfo);
return Result;
}
if (!m_StaticEntries.IsEmpty() && CmdId >= m_iIdSCMFirst && CmdId < m_iIdSCMLast)
{
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;
}
if (m_iIdCBFirst != m_iIdCBLast && CmdId >= m_iIdCBFirst && CmdId < m_iIdCBLast)
{
Result = _DoCallback(DFM_INVOKECOMMAND, CmdId - m_iIdCBFirst, NULL);
return Result;
}
if (m_iIdDfltFirst != m_iIdDfltLast && CmdId >= m_iIdDfltFirst && CmdId < m_iIdDfltLast)
{
CmdId -= m_iIdDfltFirst;
/* See the definitions of IDM_CUT and co to see how this works */
CmdId += DCM_FCIDM_SHVIEW_OFFSET;
}
if (LocalInvokeInfo.cbSize >= sizeof(CMINVOKECOMMANDINFOEX) && (LocalInvokeInfo.fMask & CMIC_MASK_PTINVOKE))
{
if (m_pDataObj && FAILED_UNEXPECTEDLY(DataObject_SetOffset(m_pDataObj, &LocalInvokeInfo.ptInvoke)))
{
ERR("Unable to add OFFSET to DataObject!\n");
}
}
/* Check if this is a Id */
switch (CmdId)
{
case FCIDM_SHVIEW_INSERT:
Result = DoPaste(&LocalInvokeInfo, FALSE);
break;
case FCIDM_SHVIEW_INSERTLINK:
Result = DoPaste(&LocalInvokeInfo, TRUE);
break;
case FCIDM_SHVIEW_OPEN:
case FCIDM_SHVIEW_EXPLORE:
Result = DoOpenOrExplore(&LocalInvokeInfo);
break;
case FCIDM_SHVIEW_COPY:
case FCIDM_SHVIEW_CUT:
Result = DoCopyOrCut(&LocalInvokeInfo, CmdId == FCIDM_SHVIEW_COPY);
break;
case FCIDM_SHVIEW_CREATELINK:
Result = DoCreateLink(&LocalInvokeInfo);
break;
case FCIDM_SHVIEW_DELETE:
Result = DoDelete(&LocalInvokeInfo);
break;
case FCIDM_SHVIEW_RENAME:
Result = DoRename(&LocalInvokeInfo);
break;
case FCIDM_SHVIEW_PROPERTIES:
Result = DoProperties(&LocalInvokeInfo);
break;
case FCIDM_SHVIEW_NEWFOLDER:
Result = DoCreateNewFolder(&LocalInvokeInfo);
break;
case FCIDM_SHVIEW_COPYTO:
Result = DoCopyToMoveToFolder(&LocalInvokeInfo, TRUE);
break;
case FCIDM_SHVIEW_MOVETO:
Result = DoCopyToMoveToFolder(&LocalInvokeInfo, FALSE);
break;
case FCIDM_SHVIEW_UNDO:
Result = DoUndo(&LocalInvokeInfo);
break;
default:
Result = E_INVALIDARG;
ERR("Unhandled Verb %xl\n", LOWORD(LocalInvokeInfo.lpVerb));
break;
}
return Result;
}
HRESULT
WINAPI
CDefaultContextMenu::GetCommandString(
UINT_PTR idCommand,
UINT uFlags,
UINT* lpReserved,
LPSTR lpszName,
UINT uMaxNameLen)
{
/* We don't handle the help text yet */
if (uFlags == GCS_HELPTEXTA ||
uFlags == GCS_HELPTEXTW ||
HIWORD(idCommand) != 0)
{
return E_NOTIMPL;
}
UINT CmdId = LOWORD(idCommand);
if (!m_DynamicEntries.IsEmpty() && CmdId >= m_iIdSHEFirst && CmdId < m_iIdSHELast)
{
idCommand -= m_iIdSHEFirst;
PDynamicShellEntry pEntry = GetDynamicEntry(idCommand);
if (!pEntry)
return E_FAIL;
idCommand -= pEntry->iIdCmdFirst;
return pEntry->pCM->GetCommandString(idCommand,
uFlags,
lpReserved,
lpszName,
uMaxNameLen);
}
if (!m_StaticEntries.IsEmpty() && CmdId >= m_iIdSCMFirst && CmdId < m_iIdSCMLast)
{
/* Validation just returns S_OK on a match. The id exists. */
if (uFlags == GCS_VALIDATEA || uFlags == GCS_VALIDATEW)
return S_OK;
CmdId -= m_iIdSCMFirst;
POSITION it = m_StaticEntries.FindIndex(CmdId);
if (it == NULL)
return E_INVALIDARG;
PStaticShellEntry pEntry = &m_StaticEntries.GetAt(it);
if (uFlags == GCS_VERBW)
return StringCchCopyW((LPWSTR)lpszName, uMaxNameLen, pEntry->Verb);
if (uFlags == GCS_VERBA)
{
if (SHUnicodeToAnsi(pEntry->Verb, lpszName, uMaxNameLen))
return S_OK;
}
return E_INVALIDARG;
}
//FIXME: Should we handle callbacks here?
if (m_iIdDfltFirst != m_iIdDfltLast && CmdId >= m_iIdDfltFirst && CmdId < m_iIdDfltLast)
{
CmdId -= m_iIdDfltFirst;
/* See the definitions of IDM_CUT and co to see how this works */
CmdId += DCM_FCIDM_SHVIEW_OFFSET;
}
/* Loop looking for a matching Id */
for (UINT i = 0; i < _countof(g_StaticInvokeCmdMap); i++)
{
if (g_StaticInvokeCmdMap[i].IntVerb == CmdId)
{
/* Validation just returns S_OK on a match */
if (uFlags == GCS_VALIDATEA || uFlags == GCS_VALIDATEW)
return S_OK;
/* Return a copy of the ANSI verb */
if (uFlags == GCS_VERBA)
return StringCchCopyA(lpszName, uMaxNameLen, g_StaticInvokeCmdMap[i].szStringVerb);
/* Convert the ANSI verb to unicode and return that */
if (uFlags == GCS_VERBW)
{
if (SHAnsiToUnicode(g_StaticInvokeCmdMap[i].szStringVerb, (LPWSTR)lpszName, uMaxNameLen))
return S_OK;
}
}
}
return E_INVALIDARG;
}
HRESULT
WINAPI
CDefaultContextMenu::HandleMenuMsg(
UINT uMsg,
WPARAM wParam,
LPARAM lParam)
{
/* FIXME: Should we implement this as well? */
return S_OK;
}
HRESULT SHGetMenuIdFromMenuMsg(UINT uMsg, LPARAM lParam, UINT *CmdId)
{
if (uMsg == WM_DRAWITEM)
{
DRAWITEMSTRUCT* pDrawStruct = reinterpret_cast<DRAWITEMSTRUCT*>(lParam);
*CmdId = pDrawStruct->itemID;
return S_OK;
}
else if (uMsg == WM_MEASUREITEM)
{
MEASUREITEMSTRUCT* pMeasureStruct = reinterpret_cast<MEASUREITEMSTRUCT*>(lParam);
*CmdId = pMeasureStruct->itemID;
return S_OK;
}
return E_FAIL;
}
HRESULT SHSetMenuIdInMenuMsg(UINT uMsg, LPARAM lParam, UINT CmdId)
{
if (uMsg == WM_DRAWITEM)
{
DRAWITEMSTRUCT* pDrawStruct = reinterpret_cast<DRAWITEMSTRUCT*>(lParam);
pDrawStruct->itemID = CmdId;
return S_OK;
}
else if (uMsg == WM_MEASUREITEM)
{
MEASUREITEMSTRUCT* pMeasureStruct = reinterpret_cast<MEASUREITEMSTRUCT*>(lParam);
pMeasureStruct->itemID = CmdId;
return S_OK;
}
return E_FAIL;
}
HRESULT
WINAPI
CDefaultContextMenu::HandleMenuMsg2(
UINT uMsg,
WPARAM wParam,
LPARAM lParam,
LRESULT *plResult)
{
if (uMsg == WM_INITMENUPOPUP)
{
POSITION it = m_DynamicEntries.GetHeadPosition();
while (it != NULL)
{
DynamicShellEntry& info = m_DynamicEntries.GetNext(it);
SHForwardContextMenuMsg(info.pCM, uMsg, wParam, lParam, plResult, TRUE);
}
return S_OK;
}
UINT CmdId;
HRESULT hr = SHGetMenuIdFromMenuMsg(uMsg, lParam, &CmdId);
if (FAILED(hr))
return S_FALSE;
if (CmdId < m_iIdSHEFirst || CmdId >= m_iIdSHELast)
return S_FALSE;
CmdId -= m_iIdSHEFirst;
PDynamicShellEntry pEntry = GetDynamicEntry(CmdId);
if (pEntry)
{
SHSetMenuIdInMenuMsg(uMsg, lParam, CmdId - pEntry->iIdCmdFirst);
SHForwardContextMenuMsg(pEntry->pCM, uMsg, wParam, lParam, plResult, TRUE);
}
return S_OK;
}
HRESULT
WINAPI
CDefaultContextMenu::SetSite(IUnknown *pUnkSite)
{
m_site = pUnkSite;
return S_OK;
}
HRESULT
WINAPI
CDefaultContextMenu::GetSite(REFIID riid, void **ppvSite)
{
if (!m_site)
return E_FAIL;
return m_site->QueryInterface(riid, ppvSite);
}
static
HRESULT
CDefaultContextMenu_CreateInstance(const DEFCONTEXTMENU *pdcm, LPFNDFMCALLBACK lpfn, REFIID riid, void **ppv)
{
return ShellObjectCreatorInit<CDefaultContextMenu>(pdcm, lpfn, riid, ppv);
}
/*************************************************************************
* SHCreateDefaultContextMenu [SHELL32.325] Vista API
*
*/
HRESULT
WINAPI
SHCreateDefaultContextMenu(const DEFCONTEXTMENU *pdcm, REFIID riid, void **ppv)
{
HRESULT hr;
if (!ppv)
return E_INVALIDARG;
hr = CDefaultContextMenu_CreateInstance(pdcm, NULL, riid, ppv);
if (FAILED_UNEXPECTEDLY(hr))
return hr;
return S_OK;
}
/*************************************************************************
* CDefFolderMenu_Create2 [SHELL32.701]
*
*/
HRESULT
WINAPI
CDefFolderMenu_Create2(
PCIDLIST_ABSOLUTE pidlFolder,
HWND hwnd,
UINT cidl,
PCUITEMID_CHILD_ARRAY apidl,
IShellFolder *psf,
LPFNDFMCALLBACK lpfn,
UINT nKeys,
const HKEY *ahkeyClsKeys,
IContextMenu **ppcm)
{
DEFCONTEXTMENU dcm;
dcm.hwnd = hwnd;
dcm.pcmcb = NULL;
dcm.pidlFolder = pidlFolder;
dcm.psf = psf;
dcm.cidl = cidl;
dcm.apidl = apidl;
dcm.punkAssociationInfo = NULL;
dcm.cKeys = nKeys;
dcm.aKeys = ahkeyClsKeys;
HRESULT hr = CDefaultContextMenu_CreateInstance(&dcm, lpfn, IID_PPV_ARG(IContextMenu, ppcm));
if (FAILED_UNEXPECTEDLY(hr))
return hr;
return S_OK;
}