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