1 /*
2  * PROJECT:     shell32
3  * LICENSE:     GPL - See COPYING in the top level directory
4  * FILE:        dll/win32/shell32/shv_item_new.c
5  * PURPOSE:     provides default context menu implementation
6  * PROGRAMMERS: Johannes Anderwald (johannes.anderwald@reactos.org)
7  */
8 
9 #include "precomp.h"
10 #include <compat_undoc.h>
11 
12 WINE_DEFAULT_DEBUG_CHANNEL(dmenu);
13 
14 // FIXME: 260 is correct, but should this be part of the SDK or just MAX_PATH?
15 #define MAX_VERB 260
16 
17 static HRESULT
SHELL_GetRegCLSID(HKEY hKey,LPCWSTR SubKey,LPCWSTR Value,CLSID & clsid)18 SHELL_GetRegCLSID(HKEY hKey, LPCWSTR SubKey, LPCWSTR Value, CLSID &clsid)
19 {
20     WCHAR buf[42];
21     DWORD cb = sizeof(buf);
22     DWORD err = RegGetValueW(hKey, SubKey, Value, RRF_RT_REG_SZ, NULL, buf, &cb);
23     return !err ? CLSIDFromString(buf, &clsid) : HRESULT_FROM_WIN32(err);
24 }
25 
InsertMenuItemAt(HMENU hMenu,UINT Pos,UINT Flags)26 static BOOL InsertMenuItemAt(HMENU hMenu, UINT Pos, UINT Flags)
27 {
28     MENUITEMINFOW mii;
29     mii.cbSize = FIELD_OFFSET(MENUITEMINFOW, hbmpItem); // USER32 version agnostic
30     mii.fMask = MIIM_TYPE;
31     mii.fType = Flags;
32     return InsertMenuItemW(hMenu, Pos, TRUE, &mii);
33 }
34 
35 typedef struct _DynamicShellEntry_
36 {
37     UINT iIdCmdFirst;
38     UINT NumIds;
39     CLSID ClassID;
40     CComPtr<IContextMenu> pCM;
41 } DynamicShellEntry, *PDynamicShellEntry;
42 
43 typedef struct _StaticShellEntry_
44 {
45     CStringW Verb;
46     HKEY hkClass;
47 } StaticShellEntry, *PStaticShellEntry;
48 
49 #define DCM_FCIDM_SHVIEW_OFFSET 0x7000 // Offset from the menu ids in the menu resource to FCIDM_SHVIEW_*
50 
51 //
52 // verbs for InvokeCommandInfo
53 //
54 static const struct _StaticInvokeCommandMap_
55 {
56     LPCSTR szStringVerb;
57     WORD IntVerb;
58     SHORT DfmCmd;
59 } g_StaticInvokeCmdMap[] =
60 {
61     { "RunAs", 0 },  // Unimplemented
62     { "Print", 0 },  // Unimplemented
63     { "Preview", 0 }, // Unimplemented
64     { "Open",            FCIDM_SHVIEW_OPEN },
65     { CMDSTR_NEWFOLDERA, FCIDM_SHVIEW_NEWFOLDER,  (SHORT)DFM_CMD_NEWFOLDER },
66     { "cut",             FCIDM_SHVIEW_CUT,        /* ? */ },
67     { "copy",            FCIDM_SHVIEW_COPY,       (SHORT)DFM_CMD_COPY },
68     { "paste",           FCIDM_SHVIEW_INSERT,     (SHORT)DFM_CMD_PASTE },
69     { "link",            FCIDM_SHVIEW_CREATELINK, (SHORT)DFM_CMD_LINK },
70     { "delete",          FCIDM_SHVIEW_DELETE,     (SHORT)DFM_CMD_DELETE },
71     { "properties",      FCIDM_SHVIEW_PROPERTIES, (SHORT)DFM_CMD_PROPERTIES },
72     { "rename",          FCIDM_SHVIEW_RENAME,     (SHORT)DFM_CMD_RENAME },
73     { "copyto",          FCIDM_SHVIEW_COPYTO },
74     { "moveto",          FCIDM_SHVIEW_MOVETO },
75 };
76 
MapVerbToDfmCmd(_In_ LPCSTR verba)77 UINT MapVerbToDfmCmd(_In_ LPCSTR verba)
78 {
79     for (UINT i = 0; i < _countof(g_StaticInvokeCmdMap); ++i)
80     {
81         if (!lstrcmpiA(g_StaticInvokeCmdMap[i].szStringVerb, verba))
82             return (int)g_StaticInvokeCmdMap[i].DfmCmd;
83     }
84     return 0;
85 }
86 
87 static HRESULT
MapVerbToCmdId(PVOID Verb,BOOL IsUnicode,IContextMenu * pCM,UINT idFirst,UINT idLast)88 MapVerbToCmdId(PVOID Verb, BOOL IsUnicode, IContextMenu *pCM, UINT idFirst, UINT idLast)
89 {
90     const UINT gcs = IsUnicode ? GCS_VERBW : GCS_VERBA;
91     for (UINT id = idFirst; id <= idLast; ++id)
92     {
93         WCHAR buf[MAX_PATH];
94         *buf = UNICODE_NULL;
95         HRESULT hr = pCM->GetCommandString(id, gcs, NULL, (LPSTR)buf, _countof(buf));
96         if (FAILED(hr) || !*buf)
97             continue;
98         else if (IsUnicode && !_wcsicmp((LPWSTR)Verb, buf))
99             return id;
100         else if (!IsUnicode && !lstrcmpiA((LPSTR)Verb, (LPSTR)buf))
101             return id;
102     }
103     return HRESULT_FROM_WIN32(ERROR_NOT_FOUND);
104 }
105 
IsVerbListSeparator(WCHAR Ch)106 static inline bool IsVerbListSeparator(WCHAR Ch)
107 {
108     return Ch == L' ' || Ch == L','; // learn.microsoft.com/en-us/windows/win32/shell/context-menu-handlers
109 }
110 
FindVerbInDefaultVerbList(LPCWSTR List,LPCWSTR Verb)111 static int FindVerbInDefaultVerbList(LPCWSTR List, LPCWSTR Verb)
112 {
113     for (UINT index = 0; *List; ++index)
114     {
115         while (IsVerbListSeparator(*List))
116             List++;
117         LPCWSTR Start = List;
118         while (*List && !IsVerbListSeparator(*List))
119             List++;
120         // "List > Start" to verify that the list item is non-empty to avoid the edge case where Verb is "" and the list contains ",,"
121         if (!_wcsnicmp(Verb, Start, List - Start) && List > Start)
122             return index;
123     }
124     return -1;
125 }
126 
SHELL32_EnumDefaultVerbList(LPCWSTR List,UINT Index,LPWSTR Verb,SIZE_T cchMax)127 EXTERN_C HRESULT SHELL32_EnumDefaultVerbList(LPCWSTR List, UINT Index, LPWSTR Verb, SIZE_T cchMax)
128 {
129     for (UINT i = 0; *List; ++i)
130     {
131         while (IsVerbListSeparator(*List))
132             List++;
133         LPCWSTR Start = List;
134         while (*List && !IsVerbListSeparator(*List))
135             List++;
136         if (List > Start && i == Index)
137             return StringCchCopyNW(Verb, cchMax, Start, List - Start);
138     }
139     return HRESULT_FROM_WIN32(ERROR_NO_MORE_ITEMS);
140 }
141 
142 class CDefaultContextMenu :
143     public CComObjectRootEx<CComMultiThreadModelNoCS>,
144     public IContextMenu3,
145     public IObjectWithSite,
146     public IServiceProvider
147 {
148     private:
149         CComPtr<IUnknown> m_site;
150         CComPtr<IShellFolder> m_psf;
151         CComPtr<IContextMenuCB> m_pmcb;
152         LPFNDFMCALLBACK m_pfnmcb;
153         UINT m_cidl;
154         PCUITEMID_CHILD_ARRAY m_apidl;
155         CComPtr<IDataObject> m_pDataObj;
156         HKEY* m_aKeys;
157         UINT m_cKeys;
158         PIDLIST_ABSOLUTE m_pidlFolder;
159         DWORD m_bGroupPolicyActive;
160         CAtlList<DynamicShellEntry> m_DynamicEntries;
161         UINT m_iIdSHEFirst; /* first used id */
162         UINT m_iIdSHELast; /* last used id */
163         CAtlList<StaticShellEntry> m_StaticEntries;
164         UINT m_iIdSCMFirst; /* first static used id */
165         UINT m_iIdSCMLast; /* last static used id */
166         UINT m_iIdCBFirst; /* first callback used id */
167         UINT m_iIdCBLast;  /* last callback used id */
168         UINT m_iIdDfltFirst; /* first default part id */
169         UINT m_iIdDfltLast; /* last default part id */
170         HWND m_hwnd; /* window passed to callback */
171         WCHAR m_DefVerbs[MAX_PATH];
172 
173         HRESULT _DoCallback(UINT uMsg, WPARAM wParam, LPVOID lParam);
174         HRESULT _DoInvokeCommandCallback(LPCMINVOKECOMMANDINFOEX lpcmi, WPARAM CmdId);
175         void AddStaticEntry(const HKEY hkeyClass, const WCHAR *szVerb, UINT uFlags);
176         void AddStaticEntriesForKey(HKEY hKey, UINT uFlags);
177         void TryPickDefault(HMENU hMenu, UINT idCmdFirst, UINT DfltOffset, UINT uFlags);
178         BOOL IsShellExtensionAlreadyLoaded(REFCLSID clsid);
179         HRESULT LoadDynamicContextMenuHandler(HKEY hKey, REFCLSID clsid);
180         BOOL EnumerateDynamicContextHandlerForKey(HKEY hRootKey);
181         UINT AddShellExtensionsToMenu(HMENU hMenu, UINT* pIndexMenu, UINT idCmdFirst, UINT idCmdLast, UINT uFlags);
182         UINT AddStaticContextMenusToMenu(HMENU hMenu, UINT* IndexMenu, UINT iIdCmdFirst, UINT iIdCmdLast, UINT uFlags);
183         HRESULT DoPaste(LPCMINVOKECOMMANDINFOEX lpcmi, BOOL bLink);
184         HRESULT DoOpenOrExplore(LPCMINVOKECOMMANDINFOEX lpcmi);
185         HRESULT DoCreateLink(LPCMINVOKECOMMANDINFOEX lpcmi);
186         HRESULT DoDelete(LPCMINVOKECOMMANDINFOEX lpcmi);
187         HRESULT DoCopyOrCut(LPCMINVOKECOMMANDINFOEX lpcmi, BOOL bCopy);
188         HRESULT DoRename(LPCMINVOKECOMMANDINFOEX lpcmi);
189         HRESULT DoProperties(LPCMINVOKECOMMANDINFOEX lpcmi);
190         HRESULT DoUndo(LPCMINVOKECOMMANDINFOEX lpcmi);
191         HRESULT DoCreateNewFolder(LPCMINVOKECOMMANDINFOEX lpici);
192         HRESULT DoCopyToMoveToFolder(LPCMINVOKECOMMANDINFOEX lpici, BOOL bCopy);
193         HRESULT InvokeShellExt(LPCMINVOKECOMMANDINFOEX lpcmi);
194         HRESULT InvokeRegVerb(LPCMINVOKECOMMANDINFOEX lpcmi);
195         DWORD BrowserFlagsFromVerb(LPCMINVOKECOMMANDINFOEX lpcmi, PStaticShellEntry pEntry);
196         HRESULT TryToBrowse(LPCMINVOKECOMMANDINFOEX lpcmi, LPCITEMIDLIST pidl, DWORD wFlags);
197         HRESULT InvokePidl(LPCMINVOKECOMMANDINFOEX lpcmi, LPCITEMIDLIST pidl, PStaticShellEntry pEntry);
198         PDynamicShellEntry GetDynamicEntry(UINT idCmd);
199         BOOL MapVerbToCmdId(PVOID Verb, PUINT idCmd, BOOL IsUnicode);
200 
201     public:
202         CDefaultContextMenu();
203         ~CDefaultContextMenu();
204         HRESULT WINAPI Initialize(const DEFCONTEXTMENU *pdcm, LPFNDFMCALLBACK lpfn);
205 
206         // IContextMenu
207         STDMETHOD(QueryContextMenu)(HMENU hMenu, UINT indexMenu, UINT idCmdFirst, UINT idCmdLast, UINT uFlags) override;
208         STDMETHOD(InvokeCommand)(LPCMINVOKECOMMANDINFO lpcmi) override;
209         STDMETHOD(GetCommandString)(UINT_PTR idCommand, UINT uFlags, UINT *lpReserved, LPSTR lpszName, UINT uMaxNameLen) override;
210 
211         // IContextMenu2
212         STDMETHOD(HandleMenuMsg)(UINT uMsg, WPARAM wParam, LPARAM lParam) override;
213 
214         // IContextMenu3
215         STDMETHOD(HandleMenuMsg2)(UINT uMsg, WPARAM wParam, LPARAM lParam, LRESULT *plResult) override;
216 
217         // IObjectWithSite
218         STDMETHOD(SetSite)(IUnknown *pUnkSite) override;
219         STDMETHOD(GetSite)(REFIID riid, void **ppvSite) override;
220 
221         // IServiceProvider
STDMETHOD(QueryService)222         STDMETHOD(QueryService)(REFGUID svc, REFIID riid, void**ppv) override
223         {
224             return IUnknown_QueryService(m_site, svc, riid, ppv);
225         }
226 
227         BEGIN_COM_MAP(CDefaultContextMenu)
228         COM_INTERFACE_ENTRY_IID(IID_IContextMenu, IContextMenu)
229         COM_INTERFACE_ENTRY_IID(IID_IContextMenu2, IContextMenu2)
230         COM_INTERFACE_ENTRY_IID(IID_IContextMenu3, IContextMenu3)
231         COM_INTERFACE_ENTRY_IID(IID_IObjectWithSite, IObjectWithSite)
232         COM_INTERFACE_ENTRY_IID(IID_IServiceProvider, IServiceProvider)
233         END_COM_MAP()
234 };
235 
CDefaultContextMenu()236 CDefaultContextMenu::CDefaultContextMenu() :
237     m_psf(NULL),
238     m_pmcb(NULL),
239     m_pfnmcb(NULL),
240     m_cidl(0),
241     m_apidl(NULL),
242     m_pDataObj(NULL),
243     m_aKeys(NULL),
244     m_cKeys(NULL),
245     m_pidlFolder(NULL),
246     m_bGroupPolicyActive(0),
247     m_iIdSHEFirst(0),
248     m_iIdSHELast(0),
249     m_iIdSCMFirst(0),
250     m_iIdSCMLast(0),
251     m_iIdCBFirst(0),
252     m_iIdCBLast(0),
253     m_iIdDfltFirst(0),
254     m_iIdDfltLast(0),
255     m_hwnd(NULL)
256 {
257     *m_DefVerbs = UNICODE_NULL;
258 }
259 
~CDefaultContextMenu()260 CDefaultContextMenu::~CDefaultContextMenu()
261 {
262     for (POSITION it = m_DynamicEntries.GetHeadPosition(); it != NULL;)
263     {
264         const DynamicShellEntry& info = m_DynamicEntries.GetNext(it);
265         IUnknown_SetSite(info.pCM.p, NULL);
266     }
267     m_DynamicEntries.RemoveAll();
268     m_StaticEntries.RemoveAll();
269 
270     for (UINT i = 0; i < m_cKeys; i++)
271         RegCloseKey(m_aKeys[i]);
272     HeapFree(GetProcessHeap(), 0, m_aKeys);
273 
274     if (m_pidlFolder)
275         CoTaskMemFree(m_pidlFolder);
276     _ILFreeaPidl(const_cast<PITEMID_CHILD *>(m_apidl), m_cidl);
277 }
278 
Initialize(const DEFCONTEXTMENU * pdcm,LPFNDFMCALLBACK lpfn)279 HRESULT WINAPI CDefaultContextMenu::Initialize(const DEFCONTEXTMENU *pdcm, LPFNDFMCALLBACK lpfn)
280 {
281     TRACE("cidl %u\n", pdcm->cidl);
282 
283     if (!pdcm->pcmcb && !lpfn)
284     {
285         ERR("CDefaultContextMenu needs a callback!\n");
286         return E_INVALIDARG;
287     }
288 
289     m_cidl = pdcm->cidl;
290     m_apidl = const_cast<PCUITEMID_CHILD_ARRAY>(_ILCopyaPidl(pdcm->apidl, m_cidl));
291     if (m_cidl && !m_apidl)
292         return E_OUTOFMEMORY;
293     m_psf = pdcm->psf;
294     m_pmcb = pdcm->pcmcb;
295     m_pfnmcb = lpfn;
296     m_hwnd = pdcm->hwnd;
297 
298     m_cKeys = pdcm->cKeys;
299     if (pdcm->cKeys)
300     {
301         m_aKeys = (HKEY*)HeapAlloc(GetProcessHeap(), 0, sizeof(HKEY) * pdcm->cKeys);
302         if (!m_aKeys)
303             return E_OUTOFMEMORY;
304         memcpy(m_aKeys, pdcm->aKeys, sizeof(HKEY) * pdcm->cKeys);
305     }
306 
307     m_psf->GetUIObjectOf(pdcm->hwnd, m_cidl, m_apidl, IID_NULL_PPV_ARG(IDataObject, &m_pDataObj));
308 
309     if (pdcm->pidlFolder)
310     {
311         m_pidlFolder = ILClone(pdcm->pidlFolder);
312     }
313     else
314     {
315         CComPtr<IPersistFolder2> pf = NULL;
316         if (SUCCEEDED(m_psf->QueryInterface(IID_PPV_ARG(IPersistFolder2, &pf))))
317         {
318             if (FAILED(pf->GetCurFolder(&m_pidlFolder)))
319                 ERR("GetCurFolder failed\n");
320         }
321         TRACE("pidlFolder %p\n", m_pidlFolder);
322     }
323 
324     return S_OK;
325 }
326 
_DoCallback(UINT uMsg,WPARAM wParam,LPVOID lParam)327 HRESULT CDefaultContextMenu::_DoCallback(UINT uMsg, WPARAM wParam, LPVOID lParam)
328 {
329     if (m_pmcb)
330     {
331         return m_pmcb->CallBack(m_psf, m_hwnd, m_pDataObj, uMsg, wParam, (LPARAM)lParam);
332     }
333     else if(m_pfnmcb)
334     {
335         return m_pfnmcb(m_psf, m_hwnd, m_pDataObj, uMsg, wParam, (LPARAM)lParam);
336     }
337 
338     return E_FAIL;
339 }
340 
AddStaticEntry(const HKEY hkeyClass,const WCHAR * szVerb,UINT uFlags)341 void CDefaultContextMenu::AddStaticEntry(const HKEY hkeyClass, const WCHAR *szVerb, UINT uFlags)
342 {
343     POSITION it = m_StaticEntries.GetHeadPosition();
344     while (it != NULL)
345     {
346         const StaticShellEntry& info = m_StaticEntries.GetNext(it);
347         if (info.Verb.CompareNoCase(szVerb) == 0)
348         {
349             /* entry already exists */
350             return;
351         }
352     }
353 
354     TRACE("adding verb %s\n", debugstr_w(szVerb));
355 
356     if (!_wcsicmp(szVerb, L"open") && !(uFlags & CMF_NODEFAULT))
357     {
358         /* open verb is always inserted in front */
359         m_StaticEntries.AddHead({ szVerb, hkeyClass });
360     }
361     else
362     {
363         m_StaticEntries.AddTail({ szVerb, hkeyClass });
364     }
365 }
366 
AddStaticEntriesForKey(HKEY hKey,UINT uFlags)367 void CDefaultContextMenu::AddStaticEntriesForKey(HKEY hKey, UINT uFlags)
368 {
369     WCHAR wszName[VERBKEY_CCHMAX];
370     DWORD cchName, dwIndex = 0;
371     HKEY hShellKey;
372 
373     LRESULT lres = RegOpenKeyExW(hKey, L"shell", 0, KEY_READ, &hShellKey);
374     if (lres != STATUS_SUCCESS)
375         return;
376 
377     if (!*m_DefVerbs)
378     {
379         DWORD cb = sizeof(m_DefVerbs);
380         RegGetValueW(hShellKey, NULL, NULL, RRF_RT_REG_SZ, NULL, m_DefVerbs, &cb);
381     }
382 
383     while(TRUE)
384     {
385         cchName = _countof(wszName);
386         if (RegEnumKeyExW(hShellKey, dwIndex++, wszName, &cchName, NULL, NULL, NULL, NULL) != ERROR_SUCCESS)
387             break;
388 
389         AddStaticEntry(hKey, wszName, uFlags);
390     }
391 
392     RegCloseKey(hShellKey);
393 }
394 
395 static
396 BOOL
HasClipboardData()397 HasClipboardData()
398 {
399     BOOL bRet = FALSE;
400     CComPtr<IDataObject> pDataObj;
401 
402     if (SUCCEEDED(OleGetClipboard(&pDataObj)))
403     {
404         FORMATETC formatetc;
405 
406         TRACE("pDataObj=%p\n", pDataObj.p);
407 
408         /* Set the FORMATETC structure*/
409         InitFormatEtc(formatetc, RegisterClipboardFormatW(CFSTR_SHELLIDLIST), TYMED_HGLOBAL);
410         bRet = SUCCEEDED(pDataObj->QueryGetData(&formatetc));
411     }
412 
413     return bRet;
414 }
415 
416 BOOL
IsShellExtensionAlreadyLoaded(REFCLSID clsid)417 CDefaultContextMenu::IsShellExtensionAlreadyLoaded(REFCLSID clsid)
418 {
419     POSITION it = m_DynamicEntries.GetHeadPosition();
420     while (it != NULL)
421     {
422         const DynamicShellEntry& info = m_DynamicEntries.GetNext(it);
423         if (info.ClassID == clsid)
424             return TRUE;
425     }
426 
427     return FALSE;
428 }
429 
430 HRESULT
LoadDynamicContextMenuHandler(HKEY hKey,REFCLSID clsid)431 CDefaultContextMenu::LoadDynamicContextMenuHandler(HKEY hKey, REFCLSID clsid)
432 {
433     HRESULT hr;
434     TRACE("LoadDynamicContextMenuHandler entered with This %p hKey %p pclsid %s\n", this, hKey, wine_dbgstr_guid(&clsid));
435 
436     if (IsShellExtensionAlreadyLoaded(clsid))
437         return S_OK;
438 
439     CComPtr<IContextMenu> pcm;
440     hr = SHCoCreateInstance(NULL, &clsid, NULL, IID_PPV_ARG(IContextMenu, &pcm));
441     if (FAILED(hr))
442     {
443         ERR("SHCoCreateInstance(IContextMenu) failed.clsid %s hr 0x%x\n", wine_dbgstr_guid(&clsid), hr);
444         return hr;
445     }
446 
447     CComPtr<IShellExtInit> pExtInit;
448     hr = pcm->QueryInterface(IID_PPV_ARG(IShellExtInit, &pExtInit));
449     if (FAILED(hr))
450     {
451         ERR("IContextMenu->QueryInterface(IShellExtInit) failed.clsid %s hr 0x%x\n", wine_dbgstr_guid(&clsid), hr);
452         return hr;
453     }
454 
455     hr = pExtInit->Initialize(m_pDataObj ? NULL : m_pidlFolder, m_pDataObj, hKey);
456     if (FAILED(hr))
457     {
458         WARN("IShellExtInit::Initialize failed.clsid %s hr 0x%x\n", wine_dbgstr_guid(&clsid), hr);
459         return hr;
460     }
461 
462     if (m_site)
463         IUnknown_SetSite(pcm, m_site);
464 
465     m_DynamicEntries.AddTail({ 0, 0, clsid, pcm });
466 
467     return S_OK;
468 }
469 
470 BOOL
EnumerateDynamicContextHandlerForKey(HKEY hRootKey)471 CDefaultContextMenu::EnumerateDynamicContextHandlerForKey(HKEY hRootKey)
472 {
473     WCHAR wszName[MAX_PATH], wszBuf[MAX_PATH], *pwszClsid;
474     DWORD cchName;
475     HRESULT hr;
476     HKEY hKey;
477 
478     if (RegOpenKeyExW(hRootKey, L"shellex\\ContextMenuHandlers", 0, KEY_READ, &hKey) != ERROR_SUCCESS)
479     {
480         TRACE("RegOpenKeyExW failed\n");
481         return FALSE;
482     }
483 
484     DWORD dwIndex = 0;
485     while (TRUE)
486     {
487         cchName = _countof(wszName);
488         if (RegEnumKeyExW(hKey, dwIndex++, wszName, &cchName, NULL, NULL, NULL, NULL) != ERROR_SUCCESS)
489             break;
490 
491         /* Key name or key value is CLSID */
492         CLSID clsid;
493         hr = CLSIDFromString(wszName, &clsid);
494         if (hr == S_OK)
495             pwszClsid = wszName;
496         else
497         {
498             DWORD cchBuf = _countof(wszBuf);
499             if (RegGetValueW(hKey, wszName, NULL, RRF_RT_REG_SZ, NULL, wszBuf, &cchBuf) == ERROR_SUCCESS)
500                 hr = CLSIDFromString(wszBuf, &clsid);
501             pwszClsid = wszBuf;
502         }
503 
504         if (FAILED(hr))
505         {
506             ERR("CLSIDFromString failed for clsid %S hr 0x%x\n", pwszClsid, hr);
507             continue;
508         }
509 
510         if (m_bGroupPolicyActive)
511         {
512             if (RegGetValueW(HKEY_LOCAL_MACHINE,
513                              L"Software\\Microsoft\\Windows\\CurrentVersion\\Shell Extensions\\Approved",
514                              pwszClsid,
515                              RRF_RT_REG_SZ,
516                              NULL,
517                              NULL,
518                              NULL) != ERROR_SUCCESS)
519             {
520                 ERR("Shell extension %s not approved!\n", pwszClsid);
521                 continue;
522             }
523         }
524 
525         hr = LoadDynamicContextMenuHandler(hRootKey, clsid);
526         if (FAILED(hr))
527             WARN("Failed to get context menu entires from shell extension! clsid: %S\n", pwszClsid);
528     }
529 
530     RegCloseKey(hKey);
531     return TRUE;
532 }
533 
534 UINT
AddShellExtensionsToMenu(HMENU hMenu,UINT * pIndexMenu,UINT idCmdFirst,UINT idCmdLast,UINT uFlags)535 CDefaultContextMenu::AddShellExtensionsToMenu(HMENU hMenu, UINT* pIndexMenu, UINT idCmdFirst, UINT idCmdLast, UINT uFlags)
536 {
537     UINT cIds = 0;
538 
539     if (m_DynamicEntries.IsEmpty())
540         return cIds;
541 
542     POSITION it = m_DynamicEntries.GetHeadPosition();
543     while (it != NULL)
544     {
545         DynamicShellEntry& info = m_DynamicEntries.GetNext(it);
546 
547         HRESULT hr = info.pCM->QueryContextMenu(hMenu, *pIndexMenu, idCmdFirst + cIds, idCmdLast, uFlags);
548         if (SUCCEEDED(hr))
549         {
550             info.iIdCmdFirst = cIds;
551             info.NumIds = HRESULT_CODE(hr);
552             (*pIndexMenu) += info.NumIds;
553 
554             cIds += info.NumIds;
555             if (idCmdFirst + cIds >= idCmdLast)
556                 break;
557         }
558         TRACE("pEntry hr %x contextmenu %p cmdfirst %x num ids %x\n", hr, info.pCM.p, info.iIdCmdFirst, info.NumIds);
559     }
560     return cIds;
561 }
562 
563 UINT
AddStaticContextMenusToMenu(HMENU hMenu,UINT * pIndexMenu,UINT iIdCmdFirst,UINT iIdCmdLast,UINT uFlags)564 CDefaultContextMenu::AddStaticContextMenusToMenu(
565     HMENU hMenu,
566     UINT* pIndexMenu,
567     UINT iIdCmdFirst,
568     UINT iIdCmdLast,
569     UINT uFlags)
570 {
571     UINT ntver = RosGetProcessEffectiveVersion();
572     MENUITEMINFOW mii = { sizeof(mii) };
573     UINT idResource;
574     WCHAR wszDispVerb[80]; // The limit on XP. If the friendly string is longer, it falls back to the verb key.
575     UINT fState;
576     UINT cIds = 0, indexFirst = *pIndexMenu, indexDefault;
577     int iDefVerbIndex = -1;
578 
579     mii.fMask = MIIM_ID | MIIM_TYPE | MIIM_STATE | MIIM_DATA;
580     mii.fType = MFT_STRING;
581 
582     POSITION it = m_StaticEntries.GetHeadPosition();
583     bool first = true;
584     while (it != NULL)
585     {
586         StaticShellEntry& info = m_StaticEntries.GetNext(it);
587         BOOL forceFirstPos = FALSE;
588 
589         fState = MFS_ENABLED;
590 
591         /* set first entry as default */
592         if (first)
593         {
594             fState |= MFS_DEFAULT;
595             first = false;
596         }
597 
598         if (info.Verb.CompareNoCase(L"open") == 0)
599         {
600             idResource = IDS_OPEN_VERB;
601             fState |= MFS_DEFAULT; /* override default when open verb is found */
602             forceFirstPos++;
603         }
604         else if (info.Verb.CompareNoCase(L"explore") == 0)
605         {
606             idResource = IDS_EXPLORE_VERB;
607             if (uFlags & CMF_EXPLORE)
608             {
609                 fState |= MFS_DEFAULT;
610                 forceFirstPos++;
611             }
612         }
613         else if (info.Verb.CompareNoCase(L"runas") == 0)
614             idResource = IDS_RUNAS_VERB;
615         else if (info.Verb.CompareNoCase(L"edit") == 0)
616             idResource = IDS_EDIT_VERB;
617         else if (info.Verb.CompareNoCase(L"find") == 0)
618             idResource = IDS_FIND_VERB;
619         else if (info.Verb.CompareNoCase(L"print") == 0)
620             idResource = IDS_PRINT_VERB;
621         else if (info.Verb.CompareNoCase(L"printto") == 0)
622             continue;
623         else
624             idResource = 0;
625 
626         /* By default use verb for menu item name */
627         mii.dwTypeData = (LPWSTR)info.Verb.GetString();
628 
629         WCHAR wszKey[sizeof("shell\\") + MAX_VERB];
630         HRESULT hr;
631         hr = StringCbPrintfW(wszKey, sizeof(wszKey), L"shell\\%s", info.Verb.GetString());
632         if (FAILED_UNEXPECTEDLY(hr))
633         {
634             continue;
635         }
636 
637         UINT cmdFlags = 0;
638         bool hide = false;
639         HKEY hkVerb;
640         if (idResource > 0)
641         {
642             if (!(uFlags & CMF_OPTIMIZEFORINVOKE))
643             {
644                 if (LoadStringW(shell32_hInstance, idResource, wszDispVerb, _countof(wszDispVerb)))
645                     mii.dwTypeData = wszDispVerb; /* use translated verb */
646                 else
647                     ERR("Failed to load string\n");
648             }
649 
650             if (RegOpenKeyW(info.hkClass, wszKey, &hkVerb) != ERROR_SUCCESS)
651                 hkVerb = NULL;
652         }
653         else
654         {
655             if (RegOpenKeyW(info.hkClass, wszKey, &hkVerb) == ERROR_SUCCESS)
656             {
657                 if (!(uFlags & CMF_OPTIMIZEFORINVOKE))
658                 {
659                     DWORD cbVerb = sizeof(wszDispVerb);
660                     LONG res = RegLoadMUIStringW(hkVerb, L"MUIVerb", wszDispVerb, cbVerb, NULL, 0, NULL);
661                     if (res || !*wszDispVerb)
662                         res = RegLoadMUIStringW(hkVerb, NULL, wszDispVerb, cbVerb, NULL, 0, NULL);
663 
664                     if (res == ERROR_SUCCESS && *wszDispVerb)
665                     {
666                         /* use description for the menu entry */
667                         mii.dwTypeData = wszDispVerb;
668                     }
669                 }
670             }
671             else
672             {
673                 hkVerb = NULL;
674             }
675         }
676 
677         if (hkVerb)
678         {
679             if (!(uFlags & CMF_EXTENDEDVERBS))
680                 hide = RegValueExists(hkVerb, L"Extended");
681 
682             if (!hide)
683                 hide = RegValueExists(hkVerb, L"ProgrammaticAccessOnly");
684 
685             if (!hide && !(uFlags & CMF_DISABLEDVERBS))
686                 hide = RegValueExists(hkVerb, L"LegacyDisable");
687 
688             if (RegValueExists(hkVerb, L"NeverDefault"))
689                 fState &= ~MFS_DEFAULT;
690 
691             if (RegValueExists(hkVerb, L"SeparatorBefore"))
692                 cmdFlags |= ECF_SEPARATORBEFORE;
693             if (RegValueExists(hkVerb, L"SeparatorAfter"))
694                 cmdFlags |= ECF_SEPARATORAFTER;
695 
696             RegCloseKey(hkVerb);
697         }
698 
699         if (((uFlags & CMF_NODEFAULT) && ntver >= _WIN32_WINNT_VISTA) ||
700             ((uFlags & CMF_DONOTPICKDEFAULT) && ntver >= _WIN32_WINNT_WIN7))
701         {
702             fState &= ~MFS_DEFAULT;
703         }
704 
705         if (!hide)
706         {
707             if (cmdFlags & ECF_SEPARATORBEFORE)
708             {
709                 if (InsertMenuItemAt(hMenu, *pIndexMenu, MF_SEPARATOR))
710                     (*pIndexMenu)++;
711             }
712 
713             UINT pos = *pIndexMenu;
714             int verbIndex = hkVerb ? FindVerbInDefaultVerbList(m_DefVerbs, info.Verb) : -1;
715             if (verbIndex >= 0)
716             {
717                 if (verbIndex < iDefVerbIndex || iDefVerbIndex < 0)
718                 {
719                     iDefVerbIndex = verbIndex;
720                     fState |= MFS_DEFAULT;
721                     forceFirstPos = TRUE;
722                 }
723                 else
724                 {
725                     fState &= ~MFS_DEFAULT; // We have already set a better default
726                     pos = indexDefault;
727                 }
728             }
729             else if (iDefVerbIndex >= 0)
730             {
731                 fState &= ~MFS_DEFAULT; // We have already set the default
732                 if (forceFirstPos)
733                     pos = indexDefault;
734                 forceFirstPos = FALSE;
735             }
736 
737             mii.fState = fState;
738             mii.wID = iIdCmdFirst + cIds;
739             if (InsertMenuItemW(hMenu, forceFirstPos ? indexFirst : pos, TRUE, &mii))
740                 (*pIndexMenu)++;
741 
742             if (cmdFlags & ECF_SEPARATORAFTER)
743             {
744                 if (InsertMenuItemAt(hMenu, *pIndexMenu, MF_SEPARATOR))
745                     (*pIndexMenu)++;
746             }
747 
748             if (fState & MFS_DEFAULT)
749                 indexDefault = *pIndexMenu; // This is where we want to insert "high priority" verbs
750         }
751         cIds++; // Always increment the id because it acts as the index into m_StaticEntries
752 
753         if (mii.wID >= iIdCmdLast)
754             break;
755     }
756 
757     return cIds;
758 }
759 
_InsertMenuItemW(HMENU hMenu,UINT indexMenu,BOOL fByPosition,UINT wID,UINT fType,LPCWSTR dwTypeData,UINT fState)760 BOOL WINAPI _InsertMenuItemW(
761     HMENU hMenu,
762     UINT indexMenu,
763     BOOL fByPosition,
764     UINT wID,
765     UINT fType,
766     LPCWSTR dwTypeData,
767     UINT fState)
768 {
769     MENUITEMINFOW mii;
770     WCHAR wszText[100];
771 
772     ZeroMemory(&mii, sizeof(mii));
773     mii.cbSize = sizeof(mii);
774     if (fType == MFT_SEPARATOR)
775         mii.fMask = MIIM_ID | MIIM_TYPE;
776     else if (fType == MFT_STRING)
777     {
778         mii.fMask = MIIM_ID | MIIM_TYPE | MIIM_STATE;
779         if (IS_INTRESOURCE(dwTypeData))
780         {
781             if (LoadStringW(shell32_hInstance, LOWORD((ULONG_PTR)dwTypeData), wszText, _countof(wszText)))
782                 mii.dwTypeData = wszText;
783             else
784             {
785                 ERR("failed to load string %p\n", dwTypeData);
786                 return FALSE;
787             }
788         }
789         else
790             mii.dwTypeData = (LPWSTR)dwTypeData;
791         mii.fState = fState;
792     }
793 
794     mii.wID = wID;
795     mii.fType = fType;
796     return InsertMenuItemW(hMenu, indexMenu, fByPosition, &mii);
797 }
798 
799 void
TryPickDefault(HMENU hMenu,UINT idCmdFirst,UINT DfltOffset,UINT uFlags)800 CDefaultContextMenu::TryPickDefault(HMENU hMenu, UINT idCmdFirst, UINT DfltOffset, UINT uFlags)
801 {
802     // Are we allowed to pick a default?
803     if ((uFlags & CMF_NODEFAULT) ||
804         ((uFlags & CMF_DONOTPICKDEFAULT) && RosGetProcessEffectiveVersion() >= _WIN32_WINNT_WIN7))
805     {
806         return;
807     }
808 
809     // Do we already have a default?
810     if ((int)GetMenuDefaultItem(hMenu, MF_BYPOSITION, 0) != -1)
811         return;
812 
813     // Does the view want to pick one?
814     INT_PTR forceDfm = 0;
815     if (SUCCEEDED(_DoCallback(DFM_GETDEFSTATICID, 0, &forceDfm)) && forceDfm)
816     {
817         for (UINT i = 0; i < _countof(g_StaticInvokeCmdMap); ++i)
818         {
819             UINT menuItemId = g_StaticInvokeCmdMap[i].IntVerb + DfltOffset - DCM_FCIDM_SHVIEW_OFFSET;
820             if (g_StaticInvokeCmdMap[i].DfmCmd == forceDfm &&
821                 SetMenuDefaultItem(hMenu, menuItemId, MF_BYCOMMAND))
822             {
823                 return;
824             }
825         }
826     }
827 
828     // Don't want to pick something like cut or delete as the default but
829     // a static or dynamic verb is a good default.
830     if (m_iIdSCMLast > m_iIdSCMFirst || m_iIdSHELast > m_iIdSHEFirst)
831         SetMenuDefaultItem(hMenu, idCmdFirst, MF_BYCOMMAND);
832 }
833 
834 HRESULT
835 WINAPI
QueryContextMenu(HMENU hMenu,UINT IndexMenu,UINT idCmdFirst,UINT idCmdLast,UINT uFlags)836 CDefaultContextMenu::QueryContextMenu(
837     HMENU hMenu,
838     UINT IndexMenu,
839     UINT idCmdFirst,
840     UINT idCmdLast,
841     UINT uFlags)
842 {
843     HRESULT hr;
844     UINT idCmdNext = idCmdFirst;
845     UINT cIds = 0;
846 
847     TRACE("BuildShellItemContextMenu entered\n");
848 
849     /* Load static verbs and shell extensions from registry */
850     for (UINT i = 0; i < m_cKeys && !(uFlags & CMF_NOVERBS); i++)
851     {
852         AddStaticEntriesForKey(m_aKeys[i], uFlags);
853         EnumerateDynamicContextHandlerForKey(m_aKeys[i]);
854     }
855 
856     /* Add static context menu handlers */
857     cIds = AddStaticContextMenusToMenu(hMenu, &IndexMenu, idCmdFirst, idCmdLast, uFlags);
858     m_iIdSCMFirst = 0; // FIXME: This should be = idCmdFirst?
859     m_iIdSCMLast = cIds;
860     idCmdNext = idCmdFirst + cIds;
861 
862     /* Add dynamic context menu handlers */
863     cIds += AddShellExtensionsToMenu(hMenu, &IndexMenu, idCmdNext, idCmdLast, uFlags);
864     m_iIdSHEFirst = m_iIdSCMLast;
865     m_iIdSHELast = cIds;
866     idCmdNext = idCmdFirst + cIds;
867     TRACE("SH_LoadContextMenuHandlers first %x last %x\n", m_iIdSHEFirst, m_iIdSHELast);
868 
869     /* Now let the callback add its own items */
870     QCMINFO qcminfo = {hMenu, IndexMenu, idCmdNext, idCmdLast, NULL};
871     if (SUCCEEDED(_DoCallback(DFM_MERGECONTEXTMENU, uFlags, &qcminfo)))
872     {
873         UINT added = qcminfo.idCmdFirst - idCmdNext;
874         cIds += added;
875         IndexMenu += added;
876         m_iIdCBFirst = m_iIdSHELast;
877         m_iIdCBLast = cIds;
878         idCmdNext = idCmdFirst + cIds;
879     }
880 
881     //TODO: DFM_MERGECONTEXTMENU_BOTTOM
882 
883     UINT idDefaultOffset = 0;
884     BOOL isBackgroundMenu = !m_cidl;
885     if (!(uFlags & CMF_VERBSONLY) && !isBackgroundMenu)
886     {
887         /* Get the attributes of the items */
888         SFGAOF rfg = SFGAO_BROWSABLE | SFGAO_CANCOPY | SFGAO_CANLINK | SFGAO_CANMOVE | SFGAO_CANDELETE | SFGAO_CANRENAME | SFGAO_HASPROPSHEET | SFGAO_FILESYSTEM | SFGAO_FOLDER;
889         hr = m_psf->GetAttributesOf(m_cidl, m_apidl, &rfg);
890         if (FAILED_UNEXPECTEDLY(hr))
891             return MAKE_HRESULT(SEVERITY_SUCCESS, 0, cIds);
892 
893         /* Add the default part of the menu */
894         HMENU hmenuDefault = LoadMenuW(_AtlBaseModule.GetResourceInstance(), L"MENU_SHV_FILE");
895 
896         /* Remove uneeded entries */
897         if (!(rfg & SFGAO_CANMOVE))
898             DeleteMenu(hmenuDefault, IDM_CUT, MF_BYCOMMAND);
899         if (!(rfg & SFGAO_CANCOPY))
900             DeleteMenu(hmenuDefault, IDM_COPY, MF_BYCOMMAND);
901         if (!((rfg & SFGAO_FILESYSTEM) && HasClipboardData()))
902             DeleteMenu(hmenuDefault, IDM_INSERT, MF_BYCOMMAND);
903         if (!(rfg & SFGAO_CANLINK))
904             DeleteMenu(hmenuDefault, IDM_CREATELINK, MF_BYCOMMAND);
905         if (!(rfg & SFGAO_CANDELETE))
906             DeleteMenu(hmenuDefault, IDM_DELETE, MF_BYCOMMAND);
907         if (!(rfg & SFGAO_CANRENAME) || !(uFlags & CMF_CANRENAME))
908             DeleteMenu(hmenuDefault, IDM_RENAME, MF_BYCOMMAND);
909         if (!(rfg & SFGAO_HASPROPSHEET))
910             DeleteMenu(hmenuDefault, IDM_PROPERTIES, MF_BYCOMMAND);
911 
912         idDefaultOffset = idCmdNext;
913         UINT idMax = Shell_MergeMenus(hMenu, GetSubMenu(hmenuDefault, 0), IndexMenu, idCmdNext, idCmdLast, 0);
914         m_iIdDfltFirst = cIds;
915         cIds += idMax - idCmdNext;
916         m_iIdDfltLast = cIds;
917 
918         DestroyMenu(hmenuDefault);
919     }
920 
921     TryPickDefault(hMenu, idCmdFirst, idDefaultOffset, uFlags);
922 
923     // TODO: DFM_MERGECONTEXTMENU_TOP
924 
925     return MAKE_HRESULT(SEVERITY_SUCCESS, 0, cIds);
926 }
927 
DoPaste(LPCMINVOKECOMMANDINFOEX lpcmi,BOOL bLink)928 HRESULT CDefaultContextMenu::DoPaste(LPCMINVOKECOMMANDINFOEX lpcmi, BOOL bLink)
929 {
930     HRESULT hr;
931 
932     CComPtr<IDataObject> pda;
933     hr = OleGetClipboard(&pda);
934     if (FAILED_UNEXPECTEDLY(hr))
935         return hr;
936 
937     FORMATETC formatetc2;
938     STGMEDIUM medium2;
939     InitFormatEtc(formatetc2, RegisterClipboardFormatW(CFSTR_PREFERREDDROPEFFECT), TYMED_HGLOBAL);
940 
941     DWORD dwKey= 0;
942 
943     if (SUCCEEDED(pda->GetData(&formatetc2, &medium2)))
944     {
945         DWORD * pdwFlag = (DWORD*)GlobalLock(medium2.hGlobal);
946         if (pdwFlag)
947         {
948             if (*pdwFlag == DROPEFFECT_COPY)
949                 dwKey = MK_CONTROL;
950             else
951                 dwKey = MK_SHIFT;
952         }
953         else
954         {
955             ERR("No drop effect obtained\n");
956         }
957         GlobalUnlock(medium2.hGlobal);
958     }
959 
960     if (bLink)
961     {
962         dwKey = MK_CONTROL|MK_SHIFT;
963     }
964 
965     CComPtr<IDropTarget> pdrop;
966     if (m_cidl)
967         hr = m_psf->GetUIObjectOf(NULL, 1, &m_apidl[0], IID_NULL_PPV_ARG(IDropTarget, &pdrop));
968     else
969         hr = m_psf->CreateViewObject(NULL, IID_PPV_ARG(IDropTarget, &pdrop));
970 
971     if (FAILED_UNEXPECTEDLY(hr))
972         return hr;
973 
974     SHSimulateDrop(pdrop, pda, dwKey, NULL, NULL);
975 
976     TRACE("CP result %x\n", hr);
977     return S_OK;
978 }
979 
980 HRESULT
DoOpenOrExplore(LPCMINVOKECOMMANDINFOEX lpcmi)981 CDefaultContextMenu::DoOpenOrExplore(LPCMINVOKECOMMANDINFOEX lpcmi)
982 {
983     UNIMPLEMENTED;
984     return E_FAIL;
985 }
986 
DoCreateLink(LPCMINVOKECOMMANDINFOEX lpcmi)987 HRESULT CDefaultContextMenu::DoCreateLink(LPCMINVOKECOMMANDINFOEX lpcmi)
988 {
989     if (!m_cidl || !m_pDataObj)
990         return E_FAIL;
991 
992     CComPtr<IDropTarget> pDT;
993     HRESULT hr = m_psf->CreateViewObject(NULL, IID_PPV_ARG(IDropTarget, &pDT));
994     if (FAILED_UNEXPECTEDLY(hr))
995         return hr;
996 
997     SHSimulateDrop(pDT, m_pDataObj, MK_CONTROL|MK_SHIFT, NULL, NULL);
998 
999     return S_OK;
1000 }
1001 
DoDelete(LPCMINVOKECOMMANDINFOEX lpcmi)1002 HRESULT CDefaultContextMenu::DoDelete(LPCMINVOKECOMMANDINFOEX lpcmi)
1003 {
1004     if (!m_cidl || !m_pDataObj)
1005         return E_FAIL;
1006 
1007     CComPtr<IDropTarget> pDT;
1008     HRESULT hr = CRecyclerDropTarget_CreateInstance(IID_PPV_ARG(IDropTarget, &pDT));
1009     if (FAILED_UNEXPECTEDLY(hr))
1010         return hr;
1011 
1012     DWORD grfKeyState = (lpcmi->fMask & CMIC_MASK_SHIFT_DOWN) ? MK_SHIFT : 0;
1013     SHSimulateDrop(pDT, m_pDataObj, grfKeyState, NULL, NULL);
1014 
1015     return S_OK;
1016 }
1017 
DoCopyOrCut(LPCMINVOKECOMMANDINFOEX lpcmi,BOOL bCopy)1018 HRESULT CDefaultContextMenu::DoCopyOrCut(LPCMINVOKECOMMANDINFOEX lpcmi, BOOL bCopy)
1019 {
1020     if (!m_cidl || !m_pDataObj)
1021         return E_FAIL;
1022 
1023     FORMATETC formatetc;
1024     InitFormatEtc(formatetc, RegisterClipboardFormatW(CFSTR_PREFERREDDROPEFFECT), TYMED_HGLOBAL);
1025     STGMEDIUM medium = {0};
1026     medium.tymed = TYMED_HGLOBAL;
1027     medium.hGlobal = GlobalAlloc(GHND, sizeof(DWORD));
1028     DWORD* pdwFlag = (DWORD*)GlobalLock(medium.hGlobal);
1029     if (pdwFlag)
1030         *pdwFlag = bCopy ? DROPEFFECT_COPY : DROPEFFECT_MOVE;
1031     GlobalUnlock(medium.hGlobal);
1032     m_pDataObj->SetData(&formatetc, &medium, TRUE);
1033 
1034     CComPtr<IShellFolderView> psfv;
1035     if (SUCCEEDED(IUnknown_QueryService(m_site, SID_SFolderView, IID_PPV_ARG(IShellFolderView, &psfv))))
1036         psfv->SetPoints(m_pDataObj);
1037 
1038     HRESULT hr = OleSetClipboard(m_pDataObj);
1039     if (FAILED_UNEXPECTEDLY(hr))
1040         return hr;
1041 
1042     if (psfv)
1043         psfv->SetClipboard(!bCopy);
1044     return S_OK;
1045 }
1046 
DoRename(LPCMINVOKECOMMANDINFOEX lpcmi)1047 HRESULT CDefaultContextMenu::DoRename(LPCMINVOKECOMMANDINFOEX lpcmi)
1048 {
1049     CComPtr<IShellBrowser> psb;
1050     HRESULT hr;
1051 
1052     if (!m_site || !m_cidl)
1053         return E_FAIL;
1054 
1055     /* Get a pointer to the shell browser */
1056     hr = IUnknown_QueryService(m_site, SID_IShellBrowser, IID_PPV_ARG(IShellBrowser, &psb));
1057     if (FAILED_UNEXPECTEDLY(hr))
1058         return hr;
1059 
1060     CComPtr<IShellView> lpSV;
1061     hr = psb->QueryActiveShellView(&lpSV);
1062     if (FAILED_UNEXPECTEDLY(hr))
1063         return hr;
1064 
1065     SVSIF selFlags = SVSI_DESELECTOTHERS | SVSI_EDIT | SVSI_ENSUREVISIBLE | SVSI_FOCUSED | SVSI_SELECT;
1066     hr = lpSV->SelectItem(m_apidl[0], selFlags);
1067     if (FAILED_UNEXPECTEDLY(hr))
1068         return hr;
1069 
1070     return S_OK;
1071 }
1072 
1073 HRESULT
DoProperties(LPCMINVOKECOMMANDINFOEX lpcmi)1074 CDefaultContextMenu::DoProperties(
1075     LPCMINVOKECOMMANDINFOEX lpcmi)
1076 {
1077     HRESULT hr = _DoInvokeCommandCallback(lpcmi, DFM_CMD_PROPERTIES);
1078 
1079     // We are asked to run the default property sheet
1080     if (hr == S_FALSE)
1081     {
1082         return SHELL32_ShowPropertiesDialog(m_pDataObj);
1083     }
1084 
1085     return hr;
1086 }
1087 
1088 HRESULT
DoUndo(LPCMINVOKECOMMANDINFOEX lpcmi)1089 CDefaultContextMenu::DoUndo(LPCMINVOKECOMMANDINFOEX lpcmi)
1090 {
1091     ERR("TODO: Undo\n");
1092     return E_NOTIMPL;
1093 }
1094 
1095 HRESULT
DoCopyToMoveToFolder(LPCMINVOKECOMMANDINFOEX lpici,BOOL bCopy)1096 CDefaultContextMenu::DoCopyToMoveToFolder(LPCMINVOKECOMMANDINFOEX lpici, BOOL bCopy)
1097 {
1098     HRESULT hr = E_FAIL;
1099     if (!m_pDataObj)
1100     {
1101         ERR("m_pDataObj is NULL\n");
1102         return hr;
1103     }
1104 
1105     CComPtr<IContextMenu> pContextMenu;
1106     if (bCopy)
1107         hr = SHCoCreateInstance(NULL, &CLSID_CopyToMenu, NULL,
1108                                 IID_PPV_ARG(IContextMenu, &pContextMenu));
1109     else
1110         hr = SHCoCreateInstance(NULL, &CLSID_MoveToMenu, NULL,
1111                                 IID_PPV_ARG(IContextMenu, &pContextMenu));
1112     if (FAILED_UNEXPECTEDLY(hr))
1113         return hr;
1114 
1115     CComPtr<IShellExtInit> pInit;
1116     hr = pContextMenu->QueryInterface(IID_PPV_ARG(IShellExtInit, &pInit));
1117     if (FAILED_UNEXPECTEDLY(hr))
1118         return hr;
1119 
1120     hr = pInit->Initialize(m_pidlFolder, m_pDataObj, NULL);
1121     if (FAILED_UNEXPECTEDLY(hr))
1122         return hr;
1123 
1124     if (bCopy)
1125         lpici->lpVerb = "copyto";
1126     else
1127         lpici->lpVerb = "moveto";
1128 
1129     return pContextMenu->InvokeCommand((LPCMINVOKECOMMANDINFO)lpici);
1130 }
1131 
1132 // This code is taken from CNewMenu and should be shared between the 2 classes
1133 HRESULT
DoCreateNewFolder(LPCMINVOKECOMMANDINFOEX lpici)1134 CDefaultContextMenu::DoCreateNewFolder(
1135     LPCMINVOKECOMMANDINFOEX lpici)
1136 {
1137     WCHAR wszPath[MAX_PATH];
1138     WCHAR wszName[MAX_PATH];
1139     WCHAR wszNewFolder[25];
1140     HRESULT hr;
1141 
1142     /* Get folder path */
1143     hr = SHGetPathFromIDListW(m_pidlFolder, wszPath);
1144     if (FAILED_UNEXPECTEDLY(hr))
1145         return hr;
1146 
1147     if (!LoadStringW(shell32_hInstance, IDS_NEWFOLDER, wszNewFolder, _countof(wszNewFolder)))
1148         return E_FAIL;
1149 
1150     /* Create the name of the new directory */
1151     if (!PathYetAnotherMakeUniqueName(wszName, wszPath, NULL, wszNewFolder))
1152         return E_FAIL;
1153 
1154     /* Create the new directory and show the appropriate dialog in case of error */
1155     if (SHCreateDirectory(lpici->hwnd, wszName) != ERROR_SUCCESS)
1156         return E_FAIL;
1157 
1158     /* Show and select the new item in the def view */
1159     LPITEMIDLIST pidl;
1160     PITEMID_CHILD pidlNewItem;
1161     CComPtr<IShellView> psv;
1162 
1163     /* Notify the view object about the new item */
1164     SHChangeNotify(SHCNE_MKDIR, SHCNF_PATHW | SHCNF_FLUSH, (LPCVOID)wszName, NULL);
1165 
1166     if (!m_site)
1167         return S_OK;
1168 
1169     /* Get a pointer to the shell view */
1170     hr = IUnknown_QueryService(m_site, SID_SFolderView, IID_PPV_ARG(IShellView, &psv));
1171     if (FAILED_UNEXPECTEDLY(hr))
1172         return S_OK;
1173 
1174     /* Attempt to get the pidl of the new item */
1175     hr = SHILCreateFromPathW(wszName, &pidl, NULL);
1176     if (FAILED_UNEXPECTEDLY(hr))
1177         return hr;
1178 
1179     pidlNewItem = ILFindLastID(pidl);
1180 
1181     hr = psv->SelectItem(pidlNewItem, SVSI_DESELECTOTHERS | SVSI_EDIT | SVSI_ENSUREVISIBLE |
1182                           SVSI_FOCUSED | SVSI_SELECT);
1183     if (FAILED_UNEXPECTEDLY(hr))
1184         return hr;
1185 
1186     SHFree(pidl);
1187 
1188     return S_OK;
1189 }
1190 
GetDynamicEntry(UINT idCmd)1191 PDynamicShellEntry CDefaultContextMenu::GetDynamicEntry(UINT idCmd)
1192 {
1193     POSITION it = m_DynamicEntries.GetHeadPosition();
1194     while (it != NULL)
1195     {
1196         DynamicShellEntry& info = m_DynamicEntries.GetNext(it);
1197 
1198         if (idCmd >= info.iIdCmdFirst + info.NumIds)
1199             continue;
1200 
1201         if (idCmd < info.iIdCmdFirst || idCmd > info.iIdCmdFirst + info.NumIds)
1202             return NULL;
1203 
1204         return &info;
1205     }
1206 
1207     return NULL;
1208 }
1209 
1210 BOOL
MapVerbToCmdId(PVOID Verb,PUINT idCmd,BOOL IsUnicode)1211 CDefaultContextMenu::MapVerbToCmdId(PVOID Verb, PUINT idCmd, BOOL IsUnicode)
1212 {
1213     WCHAR UnicodeStr[MAX_VERB];
1214 
1215     /* Loop through all the static verbs looking for a match */
1216     for (UINT i = 0; i < _countof(g_StaticInvokeCmdMap); i++)
1217     {
1218         /* We can match both ANSI and unicode strings */
1219         if (IsUnicode)
1220         {
1221             /* The static verbs are ANSI, get a unicode version before doing the compare */
1222             SHAnsiToUnicode(g_StaticInvokeCmdMap[i].szStringVerb, UnicodeStr, MAX_VERB);
1223             if (!_wcsicmp(UnicodeStr, (LPWSTR)Verb))
1224             {
1225                 /* Return the Corresponding Id */
1226                 *idCmd = g_StaticInvokeCmdMap[i].IntVerb;
1227                 return TRUE;
1228             }
1229         }
1230         else
1231         {
1232             if (!_stricmp(g_StaticInvokeCmdMap[i].szStringVerb, (LPSTR)Verb))
1233             {
1234                 *idCmd = g_StaticInvokeCmdMap[i].IntVerb;
1235                 return TRUE;
1236             }
1237         }
1238     }
1239 
1240     for (POSITION it = m_DynamicEntries.GetHeadPosition(); it != NULL;)
1241     {
1242         DynamicShellEntry& entry = m_DynamicEntries.GetNext(it);
1243         if (!entry.NumIds)
1244             continue;
1245         HRESULT hr = ::MapVerbToCmdId(Verb, IsUnicode, entry.pCM, 0, entry.NumIds - 1);
1246         if (SUCCEEDED(hr))
1247         {
1248             *idCmd = m_iIdSHEFirst + entry.iIdCmdFirst + hr;
1249             return TRUE;
1250         }
1251     }
1252     return FALSE;
1253 }
1254 
1255 HRESULT
InvokeShellExt(LPCMINVOKECOMMANDINFOEX lpcmi)1256 CDefaultContextMenu::InvokeShellExt(
1257     LPCMINVOKECOMMANDINFOEX lpcmi)
1258 {
1259     TRACE("verb %p first %x last %x\n", lpcmi->lpVerb, m_iIdSHEFirst, m_iIdSHELast);
1260 
1261     UINT idCmd = LOWORD(lpcmi->lpVerb);
1262     PDynamicShellEntry pEntry = GetDynamicEntry(idCmd);
1263     if (!pEntry)
1264         return E_FAIL;
1265 
1266     /* invoke the dynamic context menu */
1267     lpcmi->lpVerb = MAKEINTRESOURCEA(idCmd - pEntry->iIdCmdFirst);
1268     return pEntry->pCM->InvokeCommand((LPCMINVOKECOMMANDINFO)lpcmi);
1269 }
1270 
1271 DWORD
BrowserFlagsFromVerb(LPCMINVOKECOMMANDINFOEX lpcmi,PStaticShellEntry pEntry)1272 CDefaultContextMenu::BrowserFlagsFromVerb(LPCMINVOKECOMMANDINFOEX lpcmi, PStaticShellEntry pEntry)
1273 {
1274     CComPtr<IShellBrowser> psb;
1275     HWND hwndTree;
1276     LPCWSTR FlagsName;
1277     WCHAR wszKey[sizeof("shell\\") + MAX_VERB];
1278     HRESULT hr;
1279     DWORD wFlags;
1280     DWORD cbVerb;
1281 
1282     if (!m_site)
1283         return 0;
1284 
1285     /* Get a pointer to the shell browser */
1286     hr = IUnknown_QueryService(m_site, SID_IShellBrowser, IID_PPV_ARG(IShellBrowser, &psb));
1287     if (FAILED(hr))
1288         return 0;
1289 
1290     /* See if we are in Explore or Browse mode. If the browser's tree is present, we are in Explore mode.*/
1291     if (SUCCEEDED(psb->GetControlWindow(FCW_TREE, &hwndTree)) && hwndTree)
1292         FlagsName = L"ExplorerFlags";
1293     else
1294         FlagsName = L"BrowserFlags";
1295 
1296     CComPtr<ICommDlgBrowser> pcdb;
1297     if (SUCCEEDED(psb->QueryInterface(IID_PPV_ARG(ICommDlgBrowser, &pcdb))))
1298     {
1299         if (LOBYTE(GetVersion()) < 6 || FlagsName[0] == 'E')
1300             return 0; // Don't browse in-place
1301     }
1302 
1303     /* Try to get the flag from the verb */
1304     hr = StringCbPrintfW(wszKey, sizeof(wszKey), L"shell\\%s", pEntry->Verb.GetString());
1305     if (FAILED_UNEXPECTEDLY(hr))
1306         return 0;
1307 
1308     cbVerb = sizeof(wFlags);
1309     if (RegGetValueW(pEntry->hkClass, wszKey, FlagsName, RRF_RT_REG_DWORD, NULL, &wFlags, &cbVerb) == ERROR_SUCCESS)
1310     {
1311         return wFlags;
1312     }
1313 
1314     return 0;
1315 }
1316 
1317 HRESULT
TryToBrowse(LPCMINVOKECOMMANDINFOEX lpcmi,LPCITEMIDLIST pidlChild,DWORD wFlags)1318 CDefaultContextMenu::TryToBrowse(
1319     LPCMINVOKECOMMANDINFOEX lpcmi, LPCITEMIDLIST pidlChild, DWORD wFlags)
1320 {
1321     CComPtr<IShellBrowser> psb;
1322     HRESULT hr;
1323 
1324     if (!m_site)
1325         return E_FAIL;
1326 
1327     /* Get a pointer to the shell browser */
1328     hr = IUnknown_QueryService(m_site, SID_IShellBrowser, IID_PPV_ARG(IShellBrowser, &psb));
1329     if (FAILED(hr))
1330         return hr;
1331 
1332     PIDLIST_ABSOLUTE pidl;
1333     hr = SHILCombine(m_pidlFolder, pidlChild, &pidl);
1334     if (FAILED_UNEXPECTEDLY(hr))
1335         return hr;
1336 
1337     hr = psb->BrowseObject(pidl, wFlags & ~SBSP_RELATIVE);
1338     ILFree(pidl);
1339     return hr;
1340 }
1341 
1342 HRESULT
InvokePidl(LPCMINVOKECOMMANDINFOEX lpcmi,LPCITEMIDLIST pidl,PStaticShellEntry pEntry)1343 CDefaultContextMenu::InvokePidl(LPCMINVOKECOMMANDINFOEX lpcmi, LPCITEMIDLIST pidl, PStaticShellEntry pEntry)
1344 {
1345     const BOOL unicode = IsUnicode(*lpcmi);
1346 
1347     LPITEMIDLIST pidlFull = ILCombine(m_pidlFolder, pidl);
1348     if (pidlFull == NULL)
1349     {
1350         return E_FAIL;
1351     }
1352 
1353     WCHAR wszPath[MAX_PATH];
1354     BOOL bHasPath = SHGetPathFromIDListW(pidlFull, wszPath);
1355 
1356     WCHAR wszDir[MAX_PATH];
1357 
1358     SHELLEXECUTEINFOW sei = { sizeof(sei) };
1359     sei.fMask = SEE_MASK_CLASSKEY | SEE_MASK_IDLIST | (CmicFlagsToSeeFlags(lpcmi->fMask) & ~SEE_MASK_INVOKEIDLIST);
1360     sei.hwnd = lpcmi->hwnd;
1361     sei.nShow = lpcmi->nShow;
1362     sei.lpVerb = pEntry->Verb;
1363     sei.lpIDList = pidlFull;
1364     sei.hkeyClass = pEntry->hkClass;
1365     sei.dwHotKey = lpcmi->dwHotKey;
1366     sei.hIcon = lpcmi->hIcon;
1367     sei.lpDirectory = wszDir;
1368 
1369     if (unicode && !StrIsNullOrEmpty(lpcmi->lpDirectoryW))
1370     {
1371         sei.lpDirectory = lpcmi->lpDirectoryW;
1372     }
1373     else if (bHasPath)
1374     {
1375         wcscpy(wszDir, wszPath);
1376         PathRemoveFileSpec(wszDir);
1377     }
1378     else
1379     {
1380         if (!SHGetPathFromIDListW(m_pidlFolder, wszDir))
1381             *wszDir = UNICODE_NULL;
1382     }
1383 
1384     if (bHasPath)
1385         sei.lpFile = wszPath;
1386 
1387     CComHeapPtr<WCHAR> pszParamsW;
1388     if (unicode && !StrIsNullOrEmpty(lpcmi->lpParametersW))
1389         sei.lpParameters = lpcmi->lpParametersW;
1390     else if (!StrIsNullOrEmpty(lpcmi->lpParameters) && __SHCloneStrAtoW(&pszParamsW, lpcmi->lpParameters))
1391         sei.lpParameters = pszParamsW;
1392 
1393     if (!sei.lpClass && (lpcmi->fMask & (CMIC_MASK_HASLINKNAME | CMIC_MASK_HASTITLE)) && unicode)
1394         sei.lpClass = lpcmi->lpTitleW; // Forward .lnk path from CShellLink::DoOpen (for consrv STARTF_TITLEISLINKNAME)
1395 
1396     ShellExecuteExW(&sei);
1397     ILFree(pidlFull);
1398 
1399     return S_OK;
1400 }
1401 
1402 HRESULT
InvokeRegVerb(LPCMINVOKECOMMANDINFOEX lpcmi)1403 CDefaultContextMenu::InvokeRegVerb(
1404     LPCMINVOKECOMMANDINFOEX lpcmi)
1405 {
1406     INT iCmd = LOWORD(lpcmi->lpVerb);
1407     HRESULT hr;
1408     UINT i;
1409 
1410     POSITION it = m_StaticEntries.FindIndex(iCmd);
1411 
1412     if (it == NULL)
1413         return E_INVALIDARG;
1414 
1415     PStaticShellEntry pEntry = &m_StaticEntries.GetAt(it);
1416 
1417     CRegKey VerbKey;
1418     WCHAR VerbKeyPath[sizeof("shell\\") + MAX_VERB];
1419     hr = StringCbPrintfW(VerbKeyPath, sizeof(VerbKeyPath), L"shell\\%s", pEntry->Verb.GetString());
1420     if (SUCCEEDED(hr) && m_pDataObj &&
1421         VerbKey.Open(pEntry->hkClass, VerbKeyPath, KEY_READ) == ERROR_SUCCESS)
1422     {
1423         CLSID clsid;
1424 
1425         DWORD KeyState = 0;
1426         if (lpcmi->fMask & CMIC_MASK_SHIFT_DOWN)
1427             KeyState |= MK_SHIFT;
1428         if (lpcmi->fMask & CMIC_MASK_CONTROL_DOWN)
1429             KeyState |= MK_CONTROL;
1430 
1431         POINTL *pPtl = NULL;
1432         C_ASSERT(sizeof(POINT) == sizeof(POINTL));
1433         if (lpcmi->fMask & CMIC_MASK_PTINVOKE)
1434             pPtl = (POINTL*)&lpcmi->ptInvoke;
1435 
1436         CComPtr<IExecuteCommand> pEC;
1437         hr = SHELL_GetRegCLSID(VerbKey, L"command", L"DelegateExecute", clsid);
1438         if (SUCCEEDED(hr))
1439             hr = CoCreateInstance(clsid, NULL, CLSCTX_ALL, IID_PPV_ARG(IExecuteCommand, &pEC));
1440         if (SUCCEEDED(hr))
1441         {
1442             CComPtr<IPropertyBag> pPB;
1443             SHCreatePropertyBagOnRegKey(VerbKey, NULL, STGM_READ, IID_PPV_ARG(IPropertyBag, &pPB));
1444             return InvokeIExecuteCommandWithDataObject(pEC, pEntry->Verb.GetString(), pPB, m_pDataObj,
1445                                                        lpcmi, static_cast<IContextMenu*>(this));
1446         }
1447 
1448         CComPtr<IDropTarget> pDT;
1449         hr = SHELL_GetRegCLSID(VerbKey, L"DropTarget", L"CLSID", clsid);
1450         if (SUCCEEDED(hr))
1451             hr = CoCreateInstance(clsid, NULL, CLSCTX_ALL, IID_PPV_ARG(IDropTarget, &pDT));
1452         if (SUCCEEDED(hr))
1453         {
1454             CComPtr<IPropertyBag> pPB;
1455             SHCreatePropertyBagOnRegKey(VerbKey, NULL, STGM_READ, IID_PPV_ARG(IPropertyBag, &pPB));
1456             IUnknown_SetSite(pDT, static_cast<IContextMenu*>(this));
1457             IUnknown_InitializeCommand(pDT, pEntry->Verb.GetString(), pPB);
1458             hr = SHSimulateDrop(pDT, m_pDataObj, KeyState, pPtl, NULL);
1459             IUnknown_SetSite(pDT, NULL);
1460             return hr;
1461         }
1462     }
1463 
1464     /* Get the browse flags to see if we need to browse */
1465     DWORD wFlags = BrowserFlagsFromVerb(lpcmi, pEntry);
1466 
1467     for (i=0; i < m_cidl; i++)
1468     {
1469         /* Check if we need to browse */
1470         if (wFlags)
1471         {
1472             hr = TryToBrowse(lpcmi, m_apidl[i], wFlags);
1473             if (SUCCEEDED(hr))
1474             {
1475                 /* In WinXP if we have browsed, we don't open any more folders.
1476                  * In Win7 we browse to the first folder we find and
1477                  * open new windows for each of the rest of the folders */
1478                 UINT ntver = RosGetProcessEffectiveVersion();
1479                 if (ntver >= _WIN32_WINNT_VISTA)
1480                     wFlags = 0; // FIXME: = SBSP_NEWBROWSER | (wFlags & ~SBSP_SAMEBROWSER);
1481                 else
1482                     i = m_cidl;
1483 
1484                 continue;
1485             }
1486         }
1487 
1488         InvokePidl(lpcmi, m_apidl[i], pEntry);
1489     }
1490 
1491     return S_OK;
1492 }
1493 
1494 HRESULT
_DoInvokeCommandCallback(LPCMINVOKECOMMANDINFOEX lpcmi,WPARAM CmdId)1495 CDefaultContextMenu::_DoInvokeCommandCallback(
1496     LPCMINVOKECOMMANDINFOEX lpcmi, WPARAM CmdId)
1497 {
1498     BOOL Unicode = IsUnicode(*lpcmi);
1499     WCHAR lParamBuf[MAX_PATH];
1500     LPARAM lParam = 0;
1501 
1502     if (Unicode && lpcmi->lpParametersW)
1503         lParam = (LPARAM)lpcmi->lpParametersW;
1504     else if (lpcmi->lpParameters)
1505         lParam = SHAnsiToUnicode(lpcmi->lpParameters, lParamBuf, _countof(lParamBuf)) ? (LPARAM)lParamBuf : 0;
1506 
1507     HRESULT hr;
1508 #if 0 // TODO: Try DFM_INVOKECOMMANDEX first.
1509     DFMICS dfmics = { sizeof(DFMICS), lpcmi->fMask, lParam, m_iIdSCMFirst?, m_iIdDfltLast?, (LPCMINVOKECOMMANDINFO)lpcmi, m_site };
1510     hr = _DoCallback(DFM_INVOKECOMMANDEX, CmdId, &dfmics);
1511     if (hr == E_NOTIMPL)
1512 #endif
1513         hr = _DoCallback(DFM_INVOKECOMMAND, CmdId, (void*)lParam);
1514     return hr;
1515 }
1516 
1517 HRESULT
1518 WINAPI
InvokeCommand(LPCMINVOKECOMMANDINFO lpcmi)1519 CDefaultContextMenu::InvokeCommand(
1520     LPCMINVOKECOMMANDINFO lpcmi)
1521 {
1522     CMINVOKECOMMANDINFOEX LocalInvokeInfo = {};
1523     HRESULT Result;
1524     UINT CmdId;
1525 
1526     /* Take a local copy of the fixed members of the
1527        struct as we might need to modify the verb */
1528     memcpy(&LocalInvokeInfo, lpcmi, min(sizeof(LocalInvokeInfo), lpcmi->cbSize));
1529 
1530     /* Check if this is a string verb */
1531     if (!IS_INTRESOURCE(LocalInvokeInfo.lpVerb))
1532     {
1533         /* Get the ID which corresponds to this verb, and update our local copy */
1534         if (MapVerbToCmdId((LPVOID)LocalInvokeInfo.lpVerb, &CmdId, FALSE))
1535             LocalInvokeInfo.lpVerb = MAKEINTRESOURCEA(CmdId);
1536         else
1537             return E_INVALIDARG;
1538     }
1539 
1540     CmdId = LOWORD(LocalInvokeInfo.lpVerb);
1541 
1542     if (!m_DynamicEntries.IsEmpty() && CmdId >= m_iIdSHEFirst && CmdId < m_iIdSHELast)
1543     {
1544         LocalInvokeInfo.lpVerb -= m_iIdSHEFirst;
1545         Result = InvokeShellExt(&LocalInvokeInfo);
1546         return Result;
1547     }
1548 
1549     if (!m_StaticEntries.IsEmpty() && CmdId >= m_iIdSCMFirst && CmdId < m_iIdSCMLast)
1550     {
1551         LocalInvokeInfo.lpVerb -= m_iIdSCMFirst;
1552         Result = InvokeRegVerb(&LocalInvokeInfo);
1553         // TODO: if (FAILED(Result) && !(lpcmi->fMask & CMIC_MASK_FLAG_NO_UI)) SHELL_ErrorBox(m_pSite, Result);
1554         return Result;
1555     }
1556 
1557     if (m_iIdCBFirst != m_iIdCBLast && CmdId >= m_iIdCBFirst && CmdId < m_iIdCBLast)
1558     {
1559         Result = _DoInvokeCommandCallback(&LocalInvokeInfo, CmdId - m_iIdCBFirst);
1560         return Result;
1561     }
1562 
1563     if (m_iIdDfltFirst != m_iIdDfltLast && CmdId >= m_iIdDfltFirst && CmdId < m_iIdDfltLast)
1564     {
1565         CmdId -= m_iIdDfltFirst;
1566         /* See the definitions of IDM_CUT and co to see how this works */
1567         CmdId += DCM_FCIDM_SHVIEW_OFFSET;
1568     }
1569 
1570     if (LocalInvokeInfo.cbSize >= sizeof(CMINVOKECOMMANDINFOEX) && (LocalInvokeInfo.fMask & CMIC_MASK_PTINVOKE))
1571     {
1572         if (m_pDataObj && FAILED_UNEXPECTEDLY(DataObject_SetOffset(m_pDataObj, &LocalInvokeInfo.ptInvoke)))
1573         {
1574             ERR("Unable to add OFFSET to DataObject!\n");
1575         }
1576     }
1577 
1578     /* Check if this is a Id */
1579     switch (CmdId)
1580     {
1581     case FCIDM_SHVIEW_INSERT:
1582         Result = DoPaste(&LocalInvokeInfo, FALSE);
1583         break;
1584     case FCIDM_SHVIEW_INSERTLINK:
1585         Result = DoPaste(&LocalInvokeInfo, TRUE);
1586         break;
1587     case FCIDM_SHVIEW_OPEN:
1588     case FCIDM_SHVIEW_EXPLORE:
1589         Result = DoOpenOrExplore(&LocalInvokeInfo);
1590         break;
1591     case FCIDM_SHVIEW_COPY:
1592     case FCIDM_SHVIEW_CUT:
1593         Result = DoCopyOrCut(&LocalInvokeInfo, CmdId == FCIDM_SHVIEW_COPY);
1594         break;
1595     case FCIDM_SHVIEW_CREATELINK:
1596         Result = DoCreateLink(&LocalInvokeInfo);
1597         break;
1598     case FCIDM_SHVIEW_DELETE:
1599         Result = DoDelete(&LocalInvokeInfo);
1600         break;
1601     case FCIDM_SHVIEW_RENAME:
1602         Result = DoRename(&LocalInvokeInfo);
1603         break;
1604     case FCIDM_SHVIEW_PROPERTIES:
1605         Result = DoProperties(&LocalInvokeInfo);
1606         break;
1607     case FCIDM_SHVIEW_NEWFOLDER:
1608         Result = DoCreateNewFolder(&LocalInvokeInfo);
1609         break;
1610     case FCIDM_SHVIEW_COPYTO:
1611         Result = DoCopyToMoveToFolder(&LocalInvokeInfo, TRUE);
1612         break;
1613     case FCIDM_SHVIEW_MOVETO:
1614         Result = DoCopyToMoveToFolder(&LocalInvokeInfo, FALSE);
1615         break;
1616     case FCIDM_SHVIEW_UNDO:
1617         Result = DoUndo(&LocalInvokeInfo);
1618         break;
1619     default:
1620         Result = E_INVALIDARG;
1621         ERR("Unhandled Verb %xl\n", LOWORD(LocalInvokeInfo.lpVerb));
1622         break;
1623     }
1624 
1625     return Result;
1626 }
1627 
1628 HRESULT
1629 WINAPI
GetCommandString(UINT_PTR idCommand,UINT uFlags,UINT * lpReserved,LPSTR lpszName,UINT uMaxNameLen)1630 CDefaultContextMenu::GetCommandString(
1631     UINT_PTR idCommand,
1632     UINT uFlags,
1633     UINT* lpReserved,
1634     LPSTR lpszName,
1635     UINT uMaxNameLen)
1636 {
1637     /* We don't handle the help text yet */
1638     if (uFlags == GCS_HELPTEXTA ||
1639         uFlags == GCS_HELPTEXTW ||
1640         HIWORD(idCommand) != 0)
1641     {
1642         return E_NOTIMPL;
1643     }
1644 
1645     UINT CmdId = LOWORD(idCommand);
1646 
1647     if (!m_DynamicEntries.IsEmpty() && CmdId >= m_iIdSHEFirst && CmdId < m_iIdSHELast)
1648     {
1649         idCommand -= m_iIdSHEFirst;
1650         PDynamicShellEntry pEntry = GetDynamicEntry(idCommand);
1651         if (!pEntry)
1652             return E_FAIL;
1653 
1654         idCommand -= pEntry->iIdCmdFirst;
1655         return pEntry->pCM->GetCommandString(idCommand,
1656                                              uFlags,
1657                                              lpReserved,
1658                                              lpszName,
1659                                              uMaxNameLen);
1660     }
1661 
1662     if (!m_StaticEntries.IsEmpty() && CmdId >= m_iIdSCMFirst && CmdId < m_iIdSCMLast)
1663     {
1664         /* Validation just returns S_OK on a match. The id exists. */
1665         if (uFlags == GCS_VALIDATEA || uFlags == GCS_VALIDATEW)
1666             return S_OK;
1667 
1668         CmdId -= m_iIdSCMFirst;
1669 
1670         POSITION it = m_StaticEntries.FindIndex(CmdId);
1671 
1672         if (it == NULL)
1673             return E_INVALIDARG;
1674 
1675         PStaticShellEntry pEntry = &m_StaticEntries.GetAt(it);
1676 
1677         if (uFlags == GCS_VERBW)
1678             return StringCchCopyW((LPWSTR)lpszName, uMaxNameLen, pEntry->Verb);
1679 
1680         if (uFlags == GCS_VERBA)
1681         {
1682             if (SHUnicodeToAnsi(pEntry->Verb, lpszName, uMaxNameLen))
1683                 return S_OK;
1684         }
1685 
1686         return E_INVALIDARG;
1687     }
1688 
1689     //FIXME: Should we handle callbacks here?
1690     if (m_iIdDfltFirst != m_iIdDfltLast && CmdId >= m_iIdDfltFirst && CmdId < m_iIdDfltLast)
1691     {
1692         CmdId -= m_iIdDfltFirst;
1693         /* See the definitions of IDM_CUT and co to see how this works */
1694         CmdId += DCM_FCIDM_SHVIEW_OFFSET;
1695     }
1696 
1697     /* Loop looking for a matching Id */
1698     for (UINT i = 0; i < _countof(g_StaticInvokeCmdMap); i++)
1699     {
1700         if (g_StaticInvokeCmdMap[i].IntVerb == CmdId)
1701         {
1702             /* Validation just returns S_OK on a match */
1703             if (uFlags == GCS_VALIDATEA || uFlags == GCS_VALIDATEW)
1704                 return S_OK;
1705 
1706             /* Return a copy of the ANSI verb */
1707             if (uFlags == GCS_VERBA)
1708                 return StringCchCopyA(lpszName, uMaxNameLen, g_StaticInvokeCmdMap[i].szStringVerb);
1709 
1710             /* Convert the ANSI verb to unicode and return that */
1711             if (uFlags == GCS_VERBW)
1712             {
1713                 if (SHAnsiToUnicode(g_StaticInvokeCmdMap[i].szStringVerb, (LPWSTR)lpszName, uMaxNameLen))
1714                     return S_OK;
1715             }
1716         }
1717     }
1718 
1719     return E_INVALIDARG;
1720 }
1721 
1722 HRESULT
1723 WINAPI
HandleMenuMsg(UINT uMsg,WPARAM wParam,LPARAM lParam)1724 CDefaultContextMenu::HandleMenuMsg(
1725     UINT uMsg,
1726     WPARAM wParam,
1727     LPARAM lParam)
1728 {
1729     /* FIXME: Should we implement this as well? */
1730     return S_OK;
1731 }
1732 
SHGetMenuIdFromMenuMsg(UINT uMsg,LPARAM lParam,UINT * CmdId)1733 HRESULT SHGetMenuIdFromMenuMsg(UINT uMsg, LPARAM lParam, UINT *CmdId)
1734 {
1735     if (uMsg == WM_DRAWITEM)
1736     {
1737         DRAWITEMSTRUCT* pDrawStruct = reinterpret_cast<DRAWITEMSTRUCT*>(lParam);
1738         *CmdId = pDrawStruct->itemID;
1739         return S_OK;
1740     }
1741     else if (uMsg == WM_MEASUREITEM)
1742     {
1743         MEASUREITEMSTRUCT* pMeasureStruct = reinterpret_cast<MEASUREITEMSTRUCT*>(lParam);
1744         *CmdId = pMeasureStruct->itemID;
1745         return S_OK;
1746     }
1747 
1748     return E_FAIL;
1749 }
1750 
SHSetMenuIdInMenuMsg(UINT uMsg,LPARAM lParam,UINT CmdId)1751 HRESULT SHSetMenuIdInMenuMsg(UINT uMsg, LPARAM lParam, UINT CmdId)
1752 {
1753     if (uMsg == WM_DRAWITEM)
1754     {
1755         DRAWITEMSTRUCT* pDrawStruct = reinterpret_cast<DRAWITEMSTRUCT*>(lParam);
1756         pDrawStruct->itemID = CmdId;
1757         return S_OK;
1758     }
1759     else if (uMsg == WM_MEASUREITEM)
1760     {
1761         MEASUREITEMSTRUCT* pMeasureStruct = reinterpret_cast<MEASUREITEMSTRUCT*>(lParam);
1762         pMeasureStruct->itemID = CmdId;
1763         return S_OK;
1764     }
1765 
1766     return E_FAIL;
1767 }
1768 
1769 HRESULT
1770 WINAPI
HandleMenuMsg2(UINT uMsg,WPARAM wParam,LPARAM lParam,LRESULT * plResult)1771 CDefaultContextMenu::HandleMenuMsg2(
1772     UINT uMsg,
1773     WPARAM wParam,
1774     LPARAM lParam,
1775     LRESULT *plResult)
1776 {
1777     if (uMsg == WM_INITMENUPOPUP)
1778     {
1779         POSITION it = m_DynamicEntries.GetHeadPosition();
1780         while (it != NULL)
1781         {
1782             DynamicShellEntry& info = m_DynamicEntries.GetNext(it);
1783             SHForwardContextMenuMsg(info.pCM, uMsg, wParam, lParam, plResult, TRUE);
1784         }
1785         return S_OK;
1786     }
1787 
1788     UINT CmdId;
1789     HRESULT hr = SHGetMenuIdFromMenuMsg(uMsg, lParam, &CmdId);
1790     if (FAILED(hr))
1791         return S_FALSE;
1792 
1793     if (CmdId < m_iIdSHEFirst || CmdId >= m_iIdSHELast)
1794         return S_FALSE;
1795 
1796     CmdId -= m_iIdSHEFirst;
1797     PDynamicShellEntry pEntry = GetDynamicEntry(CmdId);
1798     if (pEntry)
1799     {
1800         SHSetMenuIdInMenuMsg(uMsg, lParam, CmdId - pEntry->iIdCmdFirst);
1801         SHForwardContextMenuMsg(pEntry->pCM, uMsg, wParam, lParam, plResult, TRUE);
1802     }
1803 
1804    return S_OK;
1805 }
1806 
1807 HRESULT
1808 WINAPI
SetSite(IUnknown * pUnkSite)1809 CDefaultContextMenu::SetSite(IUnknown *pUnkSite)
1810 {
1811     m_site = pUnkSite;
1812     return S_OK;
1813 }
1814 
1815 HRESULT
1816 WINAPI
GetSite(REFIID riid,void ** ppvSite)1817 CDefaultContextMenu::GetSite(REFIID riid, void **ppvSite)
1818 {
1819     if (!m_site)
1820         return E_FAIL;
1821 
1822     return m_site->QueryInterface(riid, ppvSite);
1823 }
1824 
1825 static
1826 HRESULT
CDefaultContextMenu_CreateInstance(const DEFCONTEXTMENU * pdcm,LPFNDFMCALLBACK lpfn,REFIID riid,void ** ppv)1827 CDefaultContextMenu_CreateInstance(const DEFCONTEXTMENU *pdcm, LPFNDFMCALLBACK lpfn, REFIID riid, void **ppv)
1828 {
1829     return ShellObjectCreatorInit<CDefaultContextMenu>(pdcm, lpfn, riid, ppv);
1830 }
1831 
1832 /*************************************************************************
1833  * SHCreateDefaultContextMenu            [SHELL32.325] Vista API
1834  *
1835  */
1836 
1837 HRESULT
1838 WINAPI
SHCreateDefaultContextMenu(const DEFCONTEXTMENU * pdcm,REFIID riid,void ** ppv)1839 SHCreateDefaultContextMenu(const DEFCONTEXTMENU *pdcm, REFIID riid, void **ppv)
1840 {
1841     HRESULT hr;
1842 
1843     if (!ppv)
1844         return E_INVALIDARG;
1845 
1846     hr = CDefaultContextMenu_CreateInstance(pdcm, NULL, riid, ppv);
1847     if (FAILED_UNEXPECTEDLY(hr))
1848         return hr;
1849 
1850     return S_OK;
1851 }
1852 
1853 /*************************************************************************
1854  * CDefFolderMenu_Create2            [SHELL32.701]
1855  *
1856  */
1857 
1858 HRESULT
1859 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)1860 CDefFolderMenu_Create2(
1861     PCIDLIST_ABSOLUTE pidlFolder,
1862     HWND hwnd,
1863     UINT cidl,
1864     PCUITEMID_CHILD_ARRAY apidl,
1865     IShellFolder *psf,
1866     LPFNDFMCALLBACK lpfn,
1867     UINT nKeys,
1868     const HKEY *ahkeyClsKeys,
1869     IContextMenu **ppcm)
1870 {
1871     DEFCONTEXTMENU dcm;
1872     dcm.hwnd = hwnd;
1873     dcm.pcmcb = NULL;
1874     dcm.pidlFolder = pidlFolder;
1875     dcm.psf = psf;
1876     dcm.cidl = cidl;
1877     dcm.apidl = apidl;
1878     dcm.punkAssociationInfo = NULL;
1879     dcm.cKeys = nKeys;
1880     dcm.aKeys = ahkeyClsKeys;
1881 
1882     HRESULT hr = CDefaultContextMenu_CreateInstance(&dcm, lpfn, IID_PPV_ARG(IContextMenu, ppcm));
1883     if (FAILED_UNEXPECTEDLY(hr))
1884         return hr;
1885 
1886     return S_OK;
1887 }
1888