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
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 
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 
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
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 
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 
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 
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
222         virtual HRESULT STDMETHODCALLTYPE QueryService(REFGUID svc, REFIID riid, void**ppv)
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 
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 
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 
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 
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 
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 
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
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
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
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
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
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
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 
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
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
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 
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
981 CDefaultContextMenu::DoOpenOrExplore(LPCMINVOKECOMMANDINFOEX lpcmi)
982 {
983     UNIMPLEMENTED;
984     return E_FAIL;
985 }
986 
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 
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 
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     HRESULT hr = OleSetClipboard(m_pDataObj);
1035     if (FAILED_UNEXPECTEDLY(hr))
1036         return hr;
1037 
1038     return S_OK;
1039 }
1040 
1041 HRESULT CDefaultContextMenu::DoRename(LPCMINVOKECOMMANDINFOEX lpcmi)
1042 {
1043     CComPtr<IShellBrowser> psb;
1044     HRESULT hr;
1045 
1046     if (!m_site || !m_cidl)
1047         return E_FAIL;
1048 
1049     /* Get a pointer to the shell browser */
1050     hr = IUnknown_QueryService(m_site, SID_IShellBrowser, IID_PPV_ARG(IShellBrowser, &psb));
1051     if (FAILED_UNEXPECTEDLY(hr))
1052         return hr;
1053 
1054     CComPtr<IShellView> lpSV;
1055     hr = psb->QueryActiveShellView(&lpSV);
1056     if (FAILED_UNEXPECTEDLY(hr))
1057         return hr;
1058 
1059     SVSIF selFlags = SVSI_DESELECTOTHERS | SVSI_EDIT | SVSI_ENSUREVISIBLE | SVSI_FOCUSED | SVSI_SELECT;
1060     hr = lpSV->SelectItem(m_apidl[0], selFlags);
1061     if (FAILED_UNEXPECTEDLY(hr))
1062         return hr;
1063 
1064     return S_OK;
1065 }
1066 
1067 HRESULT
1068 CDefaultContextMenu::DoProperties(
1069     LPCMINVOKECOMMANDINFOEX lpcmi)
1070 {
1071     HRESULT hr = _DoInvokeCommandCallback(lpcmi, DFM_CMD_PROPERTIES);
1072 
1073     // We are asked to run the default property sheet
1074     if (hr == S_FALSE)
1075     {
1076         return SHELL32_ShowPropertiesDialog(m_pDataObj);
1077     }
1078 
1079     return hr;
1080 }
1081 
1082 HRESULT
1083 CDefaultContextMenu::DoUndo(LPCMINVOKECOMMANDINFOEX lpcmi)
1084 {
1085     ERR("TODO: Undo\n");
1086     return E_NOTIMPL;
1087 }
1088 
1089 HRESULT
1090 CDefaultContextMenu::DoCopyToMoveToFolder(LPCMINVOKECOMMANDINFOEX lpici, BOOL bCopy)
1091 {
1092     HRESULT hr = E_FAIL;
1093     if (!m_pDataObj)
1094     {
1095         ERR("m_pDataObj is NULL\n");
1096         return hr;
1097     }
1098 
1099     CComPtr<IContextMenu> pContextMenu;
1100     if (bCopy)
1101         hr = SHCoCreateInstance(NULL, &CLSID_CopyToMenu, NULL,
1102                                 IID_PPV_ARG(IContextMenu, &pContextMenu));
1103     else
1104         hr = SHCoCreateInstance(NULL, &CLSID_MoveToMenu, NULL,
1105                                 IID_PPV_ARG(IContextMenu, &pContextMenu));
1106     if (FAILED_UNEXPECTEDLY(hr))
1107         return hr;
1108 
1109     CComPtr<IShellExtInit> pInit;
1110     hr = pContextMenu->QueryInterface(IID_PPV_ARG(IShellExtInit, &pInit));
1111     if (FAILED_UNEXPECTEDLY(hr))
1112         return hr;
1113 
1114     hr = pInit->Initialize(m_pidlFolder, m_pDataObj, NULL);
1115     if (FAILED_UNEXPECTEDLY(hr))
1116         return hr;
1117 
1118     if (bCopy)
1119         lpici->lpVerb = "copyto";
1120     else
1121         lpici->lpVerb = "moveto";
1122 
1123     return pContextMenu->InvokeCommand((LPCMINVOKECOMMANDINFO)lpici);
1124 }
1125 
1126 // This code is taken from CNewMenu and should be shared between the 2 classes
1127 HRESULT
1128 CDefaultContextMenu::DoCreateNewFolder(
1129     LPCMINVOKECOMMANDINFOEX lpici)
1130 {
1131     WCHAR wszPath[MAX_PATH];
1132     WCHAR wszName[MAX_PATH];
1133     WCHAR wszNewFolder[25];
1134     HRESULT hr;
1135 
1136     /* Get folder path */
1137     hr = SHGetPathFromIDListW(m_pidlFolder, wszPath);
1138     if (FAILED_UNEXPECTEDLY(hr))
1139         return hr;
1140 
1141     if (!LoadStringW(shell32_hInstance, IDS_NEWFOLDER, wszNewFolder, _countof(wszNewFolder)))
1142         return E_FAIL;
1143 
1144     /* Create the name of the new directory */
1145     if (!PathYetAnotherMakeUniqueName(wszName, wszPath, NULL, wszNewFolder))
1146         return E_FAIL;
1147 
1148     /* Create the new directory and show the appropriate dialog in case of error */
1149     if (SHCreateDirectory(lpici->hwnd, wszName) != ERROR_SUCCESS)
1150         return E_FAIL;
1151 
1152     /* Show and select the new item in the def view */
1153     LPITEMIDLIST pidl;
1154     PITEMID_CHILD pidlNewItem;
1155     CComPtr<IShellView> psv;
1156 
1157     /* Notify the view object about the new item */
1158     SHChangeNotify(SHCNE_MKDIR, SHCNF_PATHW | SHCNF_FLUSH, (LPCVOID)wszName, NULL);
1159 
1160     if (!m_site)
1161         return S_OK;
1162 
1163     /* Get a pointer to the shell view */
1164     hr = IUnknown_QueryService(m_site, SID_IFolderView, IID_PPV_ARG(IShellView, &psv));
1165     if (FAILED_UNEXPECTEDLY(hr))
1166         return S_OK;
1167 
1168     /* Attempt to get the pidl of the new item */
1169     hr = SHILCreateFromPathW(wszName, &pidl, NULL);
1170     if (FAILED_UNEXPECTEDLY(hr))
1171         return hr;
1172 
1173     pidlNewItem = ILFindLastID(pidl);
1174 
1175     hr = psv->SelectItem(pidlNewItem, SVSI_DESELECTOTHERS | SVSI_EDIT | SVSI_ENSUREVISIBLE |
1176                           SVSI_FOCUSED | SVSI_SELECT);
1177     if (FAILED_UNEXPECTEDLY(hr))
1178         return hr;
1179 
1180     SHFree(pidl);
1181 
1182     return S_OK;
1183 }
1184 
1185 PDynamicShellEntry CDefaultContextMenu::GetDynamicEntry(UINT idCmd)
1186 {
1187     POSITION it = m_DynamicEntries.GetHeadPosition();
1188     while (it != NULL)
1189     {
1190         DynamicShellEntry& info = m_DynamicEntries.GetNext(it);
1191 
1192         if (idCmd >= info.iIdCmdFirst + info.NumIds)
1193             continue;
1194 
1195         if (idCmd < info.iIdCmdFirst || idCmd > info.iIdCmdFirst + info.NumIds)
1196             return NULL;
1197 
1198         return &info;
1199     }
1200 
1201     return NULL;
1202 }
1203 
1204 BOOL
1205 CDefaultContextMenu::MapVerbToCmdId(PVOID Verb, PUINT idCmd, BOOL IsUnicode)
1206 {
1207     WCHAR UnicodeStr[MAX_VERB];
1208 
1209     /* Loop through all the static verbs looking for a match */
1210     for (UINT i = 0; i < _countof(g_StaticInvokeCmdMap); i++)
1211     {
1212         /* We can match both ANSI and unicode strings */
1213         if (IsUnicode)
1214         {
1215             /* The static verbs are ANSI, get a unicode version before doing the compare */
1216             SHAnsiToUnicode(g_StaticInvokeCmdMap[i].szStringVerb, UnicodeStr, MAX_VERB);
1217             if (!wcscmp(UnicodeStr, (LPWSTR)Verb))
1218             {
1219                 /* Return the Corresponding Id */
1220                 *idCmd = g_StaticInvokeCmdMap[i].IntVerb;
1221                 return TRUE;
1222             }
1223         }
1224         else
1225         {
1226             if (!strcmp(g_StaticInvokeCmdMap[i].szStringVerb, (LPSTR)Verb))
1227             {
1228                 *idCmd = g_StaticInvokeCmdMap[i].IntVerb;
1229                 return TRUE;
1230             }
1231         }
1232     }
1233 
1234     for (POSITION it = m_DynamicEntries.GetHeadPosition(); it != NULL;)
1235     {
1236         DynamicShellEntry& entry = m_DynamicEntries.GetNext(it);
1237         if (!entry.NumIds)
1238             continue;
1239         HRESULT hr = ::MapVerbToCmdId(Verb, IsUnicode, entry.pCM, 0, entry.NumIds - 1);
1240         if (SUCCEEDED(hr))
1241         {
1242             *idCmd = m_iIdSHEFirst + entry.iIdCmdFirst + hr;
1243             return TRUE;
1244         }
1245     }
1246     return FALSE;
1247 }
1248 
1249 HRESULT
1250 CDefaultContextMenu::InvokeShellExt(
1251     LPCMINVOKECOMMANDINFOEX lpcmi)
1252 {
1253     TRACE("verb %p first %x last %x\n", lpcmi->lpVerb, m_iIdSHEFirst, m_iIdSHELast);
1254 
1255     UINT idCmd = LOWORD(lpcmi->lpVerb);
1256     PDynamicShellEntry pEntry = GetDynamicEntry(idCmd);
1257     if (!pEntry)
1258         return E_FAIL;
1259 
1260     /* invoke the dynamic context menu */
1261     lpcmi->lpVerb = MAKEINTRESOURCEA(idCmd - pEntry->iIdCmdFirst);
1262     return pEntry->pCM->InvokeCommand((LPCMINVOKECOMMANDINFO)lpcmi);
1263 }
1264 
1265 DWORD
1266 CDefaultContextMenu::BrowserFlagsFromVerb(LPCMINVOKECOMMANDINFOEX lpcmi, PStaticShellEntry pEntry)
1267 {
1268     CComPtr<IShellBrowser> psb;
1269     HWND hwndTree;
1270     LPCWSTR FlagsName;
1271     WCHAR wszKey[sizeof("shell\\") + MAX_VERB];
1272     HRESULT hr;
1273     DWORD wFlags;
1274     DWORD cbVerb;
1275 
1276     if (!m_site)
1277         return 0;
1278 
1279     /* Get a pointer to the shell browser */
1280     hr = IUnknown_QueryService(m_site, SID_IShellBrowser, IID_PPV_ARG(IShellBrowser, &psb));
1281     if (FAILED(hr))
1282         return 0;
1283 
1284     /* See if we are in Explore or Browse mode. If the browser's tree is present, we are in Explore mode.*/
1285     if (SUCCEEDED(psb->GetControlWindow(FCW_TREE, &hwndTree)) && hwndTree)
1286         FlagsName = L"ExplorerFlags";
1287     else
1288         FlagsName = L"BrowserFlags";
1289 
1290     CComPtr<ICommDlgBrowser> pcdb;
1291     if (SUCCEEDED(psb->QueryInterface(IID_PPV_ARG(ICommDlgBrowser, &pcdb))))
1292     {
1293         if (LOBYTE(GetVersion()) < 6 || FlagsName[0] == 'E')
1294             return 0; // Don't browse in-place
1295     }
1296 
1297     /* Try to get the flag from the verb */
1298     hr = StringCbPrintfW(wszKey, sizeof(wszKey), L"shell\\%s", pEntry->Verb.GetString());
1299     if (FAILED_UNEXPECTEDLY(hr))
1300         return 0;
1301 
1302     cbVerb = sizeof(wFlags);
1303     if (RegGetValueW(pEntry->hkClass, wszKey, FlagsName, RRF_RT_REG_DWORD, NULL, &wFlags, &cbVerb) == ERROR_SUCCESS)
1304     {
1305         return wFlags;
1306     }
1307 
1308     return 0;
1309 }
1310 
1311 HRESULT
1312 CDefaultContextMenu::TryToBrowse(
1313     LPCMINVOKECOMMANDINFOEX lpcmi, LPCITEMIDLIST pidlChild, DWORD wFlags)
1314 {
1315     CComPtr<IShellBrowser> psb;
1316     HRESULT hr;
1317 
1318     if (!m_site)
1319         return E_FAIL;
1320 
1321     /* Get a pointer to the shell browser */
1322     hr = IUnknown_QueryService(m_site, SID_IShellBrowser, IID_PPV_ARG(IShellBrowser, &psb));
1323     if (FAILED(hr))
1324         return hr;
1325 
1326     PIDLIST_ABSOLUTE pidl;
1327     hr = SHILCombine(m_pidlFolder, pidlChild, &pidl);
1328     if (FAILED_UNEXPECTEDLY(hr))
1329         return hr;
1330 
1331     hr = psb->BrowseObject(pidl, wFlags & ~SBSP_RELATIVE);
1332     ILFree(pidl);
1333     return hr;
1334 }
1335 
1336 HRESULT
1337 CDefaultContextMenu::InvokePidl(LPCMINVOKECOMMANDINFOEX lpcmi, LPCITEMIDLIST pidl, PStaticShellEntry pEntry)
1338 {
1339     const BOOL unicode = IsUnicode(*lpcmi);
1340 
1341     LPITEMIDLIST pidlFull = ILCombine(m_pidlFolder, pidl);
1342     if (pidlFull == NULL)
1343     {
1344         return E_FAIL;
1345     }
1346 
1347     WCHAR wszPath[MAX_PATH];
1348     BOOL bHasPath = SHGetPathFromIDListW(pidlFull, wszPath);
1349 
1350     WCHAR wszDir[MAX_PATH];
1351 
1352     SHELLEXECUTEINFOW sei = { sizeof(sei) };
1353     sei.fMask = SEE_MASK_CLASSKEY | SEE_MASK_IDLIST | (CmicFlagsToSeeFlags(lpcmi->fMask) & ~SEE_MASK_INVOKEIDLIST);
1354     sei.hwnd = lpcmi->hwnd;
1355     sei.nShow = lpcmi->nShow;
1356     sei.lpVerb = pEntry->Verb;
1357     sei.lpIDList = pidlFull;
1358     sei.hkeyClass = pEntry->hkClass;
1359     sei.dwHotKey = lpcmi->dwHotKey;
1360     sei.hIcon = lpcmi->hIcon;
1361     sei.lpDirectory = wszDir;
1362 
1363     if (unicode && !StrIsNullOrEmpty(lpcmi->lpDirectoryW))
1364     {
1365         sei.lpDirectory = lpcmi->lpDirectoryW;
1366     }
1367     else if (bHasPath)
1368     {
1369         wcscpy(wszDir, wszPath);
1370         PathRemoveFileSpec(wszDir);
1371     }
1372     else
1373     {
1374         if (!SHGetPathFromIDListW(m_pidlFolder, wszDir))
1375             *wszDir = UNICODE_NULL;
1376     }
1377 
1378     if (bHasPath)
1379         sei.lpFile = wszPath;
1380 
1381     CComHeapPtr<WCHAR> pszParamsW;
1382     if (unicode && !StrIsNullOrEmpty(lpcmi->lpParametersW))
1383         sei.lpParameters = lpcmi->lpParametersW;
1384     else if (!StrIsNullOrEmpty(lpcmi->lpParameters) && __SHCloneStrAtoW(&pszParamsW, lpcmi->lpParameters))
1385         sei.lpParameters = pszParamsW;
1386 
1387     if (!sei.lpClass && (lpcmi->fMask & (CMIC_MASK_HASLINKNAME | CMIC_MASK_HASTITLE)) && unicode)
1388         sei.lpClass = lpcmi->lpTitleW; // Forward .lnk path from CShellLink::DoOpen (for consrv STARTF_TITLEISLINKNAME)
1389 
1390     ShellExecuteExW(&sei);
1391     ILFree(pidlFull);
1392 
1393     return S_OK;
1394 }
1395 
1396 HRESULT
1397 CDefaultContextMenu::InvokeRegVerb(
1398     LPCMINVOKECOMMANDINFOEX lpcmi)
1399 {
1400     INT iCmd = LOWORD(lpcmi->lpVerb);
1401     HRESULT hr;
1402     UINT i;
1403 
1404     POSITION it = m_StaticEntries.FindIndex(iCmd);
1405 
1406     if (it == NULL)
1407         return E_INVALIDARG;
1408 
1409     PStaticShellEntry pEntry = &m_StaticEntries.GetAt(it);
1410 
1411     CRegKey VerbKey;
1412     WCHAR VerbKeyPath[sizeof("shell\\") + MAX_VERB];
1413     hr = StringCbPrintfW(VerbKeyPath, sizeof(VerbKeyPath), L"shell\\%s", pEntry->Verb.GetString());
1414     if (SUCCEEDED(hr) && m_pDataObj &&
1415         VerbKey.Open(pEntry->hkClass, VerbKeyPath, KEY_READ) == ERROR_SUCCESS)
1416     {
1417         CLSID clsid;
1418 
1419         DWORD KeyState = 0;
1420         if (lpcmi->fMask & CMIC_MASK_SHIFT_DOWN)
1421             KeyState |= MK_SHIFT;
1422         if (lpcmi->fMask & CMIC_MASK_CONTROL_DOWN)
1423             KeyState |= MK_CONTROL;
1424 
1425         POINTL *pPtl = NULL;
1426         C_ASSERT(sizeof(POINT) == sizeof(POINTL));
1427         if (lpcmi->fMask & CMIC_MASK_PTINVOKE)
1428             pPtl = (POINTL*)&lpcmi->ptInvoke;
1429 
1430         CComPtr<IExecuteCommand> pEC;
1431         hr = SHELL_GetRegCLSID(VerbKey, L"command", L"DelegateExecute", clsid);
1432         if (SUCCEEDED(hr))
1433             hr = CoCreateInstance(clsid, NULL, CLSCTX_ALL, IID_PPV_ARG(IExecuteCommand, &pEC));
1434         if (SUCCEEDED(hr))
1435         {
1436             CComPtr<IPropertyBag> pPB;
1437             SHCreatePropertyBagOnRegKey(VerbKey, NULL, STGM_READ, IID_PPV_ARG(IPropertyBag, &pPB));
1438             return InvokeIExecuteCommandWithDataObject(pEC, pEntry->Verb.GetString(), pPB, m_pDataObj,
1439                                                        lpcmi, static_cast<IContextMenu*>(this));
1440         }
1441 
1442         CComPtr<IDropTarget> pDT;
1443         hr = SHELL_GetRegCLSID(VerbKey, L"DropTarget", L"CLSID", clsid);
1444         if (SUCCEEDED(hr))
1445             hr = CoCreateInstance(clsid, NULL, CLSCTX_ALL, IID_PPV_ARG(IDropTarget, &pDT));
1446         if (SUCCEEDED(hr))
1447         {
1448             CComPtr<IPropertyBag> pPB;
1449             SHCreatePropertyBagOnRegKey(VerbKey, NULL, STGM_READ, IID_PPV_ARG(IPropertyBag, &pPB));
1450             IUnknown_SetSite(pDT, static_cast<IContextMenu*>(this));
1451             IUnknown_InitializeCommand(pDT, pEntry->Verb.GetString(), pPB);
1452             hr = SHSimulateDrop(pDT, m_pDataObj, KeyState, pPtl, NULL);
1453             IUnknown_SetSite(pDT, NULL);
1454             return hr;
1455         }
1456     }
1457 
1458     /* Get the browse flags to see if we need to browse */
1459     DWORD wFlags = BrowserFlagsFromVerb(lpcmi, pEntry);
1460 
1461     for (i=0; i < m_cidl; i++)
1462     {
1463         /* Check if we need to browse */
1464         if (wFlags)
1465         {
1466             hr = TryToBrowse(lpcmi, m_apidl[i], wFlags);
1467             if (SUCCEEDED(hr))
1468             {
1469                 /* In WinXP if we have browsed, we don't open any more folders.
1470                  * In Win7 we browse to the first folder we find and
1471                  * open new windows for each of the rest of the folders */
1472                 UINT ntver = RosGetProcessEffectiveVersion();
1473                 if (ntver >= _WIN32_WINNT_VISTA)
1474                     wFlags = 0; // FIXME: = SBSP_NEWBROWSER | (wFlags & ~SBSP_SAMEBROWSER);
1475                 else
1476                     i = m_cidl;
1477 
1478                 continue;
1479             }
1480         }
1481 
1482         InvokePidl(lpcmi, m_apidl[i], pEntry);
1483     }
1484 
1485     return S_OK;
1486 }
1487 
1488 HRESULT
1489 CDefaultContextMenu::_DoInvokeCommandCallback(
1490     LPCMINVOKECOMMANDINFOEX lpcmi, WPARAM CmdId)
1491 {
1492     BOOL Unicode = IsUnicode(*lpcmi);
1493     WCHAR lParamBuf[MAX_PATH];
1494     LPARAM lParam = 0;
1495 
1496     if (Unicode && lpcmi->lpParametersW)
1497         lParam = (LPARAM)lpcmi->lpParametersW;
1498     else if (lpcmi->lpParameters)
1499         lParam = SHAnsiToUnicode(lpcmi->lpParameters, lParamBuf, _countof(lParamBuf)) ? (LPARAM)lParamBuf : 0;
1500 
1501     HRESULT hr;
1502 #if 0 // TODO: Try DFM_INVOKECOMMANDEX first.
1503     DFMICS dfmics = { sizeof(DFMICS), lpcmi->fMask, lParam, m_iIdSCMFirst?, m_iIdDfltLast?, (LPCMINVOKECOMMANDINFO)lpcmi, m_site };
1504     hr = _DoCallback(DFM_INVOKECOMMANDEX, CmdId, &dfmics);
1505     if (hr == E_NOTIMPL)
1506 #endif
1507         hr = _DoCallback(DFM_INVOKECOMMAND, CmdId, (void*)lParam);
1508     return hr;
1509 }
1510 
1511 HRESULT
1512 WINAPI
1513 CDefaultContextMenu::InvokeCommand(
1514     LPCMINVOKECOMMANDINFO lpcmi)
1515 {
1516     CMINVOKECOMMANDINFOEX LocalInvokeInfo = {};
1517     HRESULT Result;
1518     UINT CmdId;
1519 
1520     /* Take a local copy of the fixed members of the
1521        struct as we might need to modify the verb */
1522     memcpy(&LocalInvokeInfo, lpcmi, min(sizeof(LocalInvokeInfo), lpcmi->cbSize));
1523 
1524     /* Check if this is a string verb */
1525     if (!IS_INTRESOURCE(LocalInvokeInfo.lpVerb))
1526     {
1527         /* Get the ID which corresponds to this verb, and update our local copy */
1528         if (MapVerbToCmdId((LPVOID)LocalInvokeInfo.lpVerb, &CmdId, FALSE))
1529             LocalInvokeInfo.lpVerb = MAKEINTRESOURCEA(CmdId);
1530     }
1531 
1532     CmdId = LOWORD(LocalInvokeInfo.lpVerb);
1533 
1534     if (!m_DynamicEntries.IsEmpty() && CmdId >= m_iIdSHEFirst && CmdId < m_iIdSHELast)
1535     {
1536         LocalInvokeInfo.lpVerb -= m_iIdSHEFirst;
1537         Result = InvokeShellExt(&LocalInvokeInfo);
1538         return Result;
1539     }
1540 
1541     if (!m_StaticEntries.IsEmpty() && CmdId >= m_iIdSCMFirst && CmdId < m_iIdSCMLast)
1542     {
1543         LocalInvokeInfo.lpVerb -= m_iIdSCMFirst;
1544         Result = InvokeRegVerb(&LocalInvokeInfo);
1545         // TODO: if (FAILED(Result) && !(lpcmi->fMask & CMIC_MASK_FLAG_NO_UI)) SHELL_ErrorBox(m_pSite, Result);
1546         return Result;
1547     }
1548 
1549     if (m_iIdCBFirst != m_iIdCBLast && CmdId >= m_iIdCBFirst && CmdId < m_iIdCBLast)
1550     {
1551         Result = _DoInvokeCommandCallback(&LocalInvokeInfo, CmdId - m_iIdCBFirst);
1552         return Result;
1553     }
1554 
1555     if (m_iIdDfltFirst != m_iIdDfltLast && CmdId >= m_iIdDfltFirst && CmdId < m_iIdDfltLast)
1556     {
1557         CmdId -= m_iIdDfltFirst;
1558         /* See the definitions of IDM_CUT and co to see how this works */
1559         CmdId += DCM_FCIDM_SHVIEW_OFFSET;
1560     }
1561 
1562     if (LocalInvokeInfo.cbSize >= sizeof(CMINVOKECOMMANDINFOEX) && (LocalInvokeInfo.fMask & CMIC_MASK_PTINVOKE))
1563     {
1564         if (m_pDataObj && FAILED_UNEXPECTEDLY(DataObject_SetOffset(m_pDataObj, &LocalInvokeInfo.ptInvoke)))
1565         {
1566             ERR("Unable to add OFFSET to DataObject!\n");
1567         }
1568     }
1569 
1570     /* Check if this is a Id */
1571     switch (CmdId)
1572     {
1573     case FCIDM_SHVIEW_INSERT:
1574         Result = DoPaste(&LocalInvokeInfo, FALSE);
1575         break;
1576     case FCIDM_SHVIEW_INSERTLINK:
1577         Result = DoPaste(&LocalInvokeInfo, TRUE);
1578         break;
1579     case FCIDM_SHVIEW_OPEN:
1580     case FCIDM_SHVIEW_EXPLORE:
1581         Result = DoOpenOrExplore(&LocalInvokeInfo);
1582         break;
1583     case FCIDM_SHVIEW_COPY:
1584     case FCIDM_SHVIEW_CUT:
1585         Result = DoCopyOrCut(&LocalInvokeInfo, CmdId == FCIDM_SHVIEW_COPY);
1586         break;
1587     case FCIDM_SHVIEW_CREATELINK:
1588         Result = DoCreateLink(&LocalInvokeInfo);
1589         break;
1590     case FCIDM_SHVIEW_DELETE:
1591         Result = DoDelete(&LocalInvokeInfo);
1592         break;
1593     case FCIDM_SHVIEW_RENAME:
1594         Result = DoRename(&LocalInvokeInfo);
1595         break;
1596     case FCIDM_SHVIEW_PROPERTIES:
1597         Result = DoProperties(&LocalInvokeInfo);
1598         break;
1599     case FCIDM_SHVIEW_NEWFOLDER:
1600         Result = DoCreateNewFolder(&LocalInvokeInfo);
1601         break;
1602     case FCIDM_SHVIEW_COPYTO:
1603         Result = DoCopyToMoveToFolder(&LocalInvokeInfo, TRUE);
1604         break;
1605     case FCIDM_SHVIEW_MOVETO:
1606         Result = DoCopyToMoveToFolder(&LocalInvokeInfo, FALSE);
1607         break;
1608     case FCIDM_SHVIEW_UNDO:
1609         Result = DoUndo(&LocalInvokeInfo);
1610         break;
1611     default:
1612         Result = E_INVALIDARG;
1613         ERR("Unhandled Verb %xl\n", LOWORD(LocalInvokeInfo.lpVerb));
1614         break;
1615     }
1616 
1617     return Result;
1618 }
1619 
1620 HRESULT
1621 WINAPI
1622 CDefaultContextMenu::GetCommandString(
1623     UINT_PTR idCommand,
1624     UINT uFlags,
1625     UINT* lpReserved,
1626     LPSTR lpszName,
1627     UINT uMaxNameLen)
1628 {
1629     /* We don't handle the help text yet */
1630     if (uFlags == GCS_HELPTEXTA ||
1631         uFlags == GCS_HELPTEXTW ||
1632         HIWORD(idCommand) != 0)
1633     {
1634         return E_NOTIMPL;
1635     }
1636 
1637     UINT CmdId = LOWORD(idCommand);
1638 
1639     if (!m_DynamicEntries.IsEmpty() && CmdId >= m_iIdSHEFirst && CmdId < m_iIdSHELast)
1640     {
1641         idCommand -= m_iIdSHEFirst;
1642         PDynamicShellEntry pEntry = GetDynamicEntry(idCommand);
1643         if (!pEntry)
1644             return E_FAIL;
1645 
1646         idCommand -= pEntry->iIdCmdFirst;
1647         return pEntry->pCM->GetCommandString(idCommand,
1648                                              uFlags,
1649                                              lpReserved,
1650                                              lpszName,
1651                                              uMaxNameLen);
1652     }
1653 
1654     if (!m_StaticEntries.IsEmpty() && CmdId >= m_iIdSCMFirst && CmdId < m_iIdSCMLast)
1655     {
1656         /* Validation just returns S_OK on a match. The id exists. */
1657         if (uFlags == GCS_VALIDATEA || uFlags == GCS_VALIDATEW)
1658             return S_OK;
1659 
1660         CmdId -= m_iIdSCMFirst;
1661 
1662         POSITION it = m_StaticEntries.FindIndex(CmdId);
1663 
1664         if (it == NULL)
1665             return E_INVALIDARG;
1666 
1667         PStaticShellEntry pEntry = &m_StaticEntries.GetAt(it);
1668 
1669         if (uFlags == GCS_VERBW)
1670             return StringCchCopyW((LPWSTR)lpszName, uMaxNameLen, pEntry->Verb);
1671 
1672         if (uFlags == GCS_VERBA)
1673         {
1674             if (SHUnicodeToAnsi(pEntry->Verb, lpszName, uMaxNameLen))
1675                 return S_OK;
1676         }
1677 
1678         return E_INVALIDARG;
1679     }
1680 
1681     //FIXME: Should we handle callbacks here?
1682     if (m_iIdDfltFirst != m_iIdDfltLast && CmdId >= m_iIdDfltFirst && CmdId < m_iIdDfltLast)
1683     {
1684         CmdId -= m_iIdDfltFirst;
1685         /* See the definitions of IDM_CUT and co to see how this works */
1686         CmdId += DCM_FCIDM_SHVIEW_OFFSET;
1687     }
1688 
1689     /* Loop looking for a matching Id */
1690     for (UINT i = 0; i < _countof(g_StaticInvokeCmdMap); i++)
1691     {
1692         if (g_StaticInvokeCmdMap[i].IntVerb == CmdId)
1693         {
1694             /* Validation just returns S_OK on a match */
1695             if (uFlags == GCS_VALIDATEA || uFlags == GCS_VALIDATEW)
1696                 return S_OK;
1697 
1698             /* Return a copy of the ANSI verb */
1699             if (uFlags == GCS_VERBA)
1700                 return StringCchCopyA(lpszName, uMaxNameLen, g_StaticInvokeCmdMap[i].szStringVerb);
1701 
1702             /* Convert the ANSI verb to unicode and return that */
1703             if (uFlags == GCS_VERBW)
1704             {
1705                 if (SHAnsiToUnicode(g_StaticInvokeCmdMap[i].szStringVerb, (LPWSTR)lpszName, uMaxNameLen))
1706                     return S_OK;
1707             }
1708         }
1709     }
1710 
1711     return E_INVALIDARG;
1712 }
1713 
1714 HRESULT
1715 WINAPI
1716 CDefaultContextMenu::HandleMenuMsg(
1717     UINT uMsg,
1718     WPARAM wParam,
1719     LPARAM lParam)
1720 {
1721     /* FIXME: Should we implement this as well? */
1722     return S_OK;
1723 }
1724 
1725 HRESULT SHGetMenuIdFromMenuMsg(UINT uMsg, LPARAM lParam, UINT *CmdId)
1726 {
1727     if (uMsg == WM_DRAWITEM)
1728     {
1729         DRAWITEMSTRUCT* pDrawStruct = reinterpret_cast<DRAWITEMSTRUCT*>(lParam);
1730         *CmdId = pDrawStruct->itemID;
1731         return S_OK;
1732     }
1733     else if (uMsg == WM_MEASUREITEM)
1734     {
1735         MEASUREITEMSTRUCT* pMeasureStruct = reinterpret_cast<MEASUREITEMSTRUCT*>(lParam);
1736         *CmdId = pMeasureStruct->itemID;
1737         return S_OK;
1738     }
1739 
1740     return E_FAIL;
1741 }
1742 
1743 HRESULT SHSetMenuIdInMenuMsg(UINT uMsg, LPARAM lParam, UINT CmdId)
1744 {
1745     if (uMsg == WM_DRAWITEM)
1746     {
1747         DRAWITEMSTRUCT* pDrawStruct = reinterpret_cast<DRAWITEMSTRUCT*>(lParam);
1748         pDrawStruct->itemID = CmdId;
1749         return S_OK;
1750     }
1751     else if (uMsg == WM_MEASUREITEM)
1752     {
1753         MEASUREITEMSTRUCT* pMeasureStruct = reinterpret_cast<MEASUREITEMSTRUCT*>(lParam);
1754         pMeasureStruct->itemID = CmdId;
1755         return S_OK;
1756     }
1757 
1758     return E_FAIL;
1759 }
1760 
1761 HRESULT
1762 WINAPI
1763 CDefaultContextMenu::HandleMenuMsg2(
1764     UINT uMsg,
1765     WPARAM wParam,
1766     LPARAM lParam,
1767     LRESULT *plResult)
1768 {
1769     if (uMsg == WM_INITMENUPOPUP)
1770     {
1771         POSITION it = m_DynamicEntries.GetHeadPosition();
1772         while (it != NULL)
1773         {
1774             DynamicShellEntry& info = m_DynamicEntries.GetNext(it);
1775             SHForwardContextMenuMsg(info.pCM, uMsg, wParam, lParam, plResult, TRUE);
1776         }
1777         return S_OK;
1778     }
1779 
1780     UINT CmdId;
1781     HRESULT hr = SHGetMenuIdFromMenuMsg(uMsg, lParam, &CmdId);
1782     if (FAILED(hr))
1783         return S_FALSE;
1784 
1785     if (CmdId < m_iIdSHEFirst || CmdId >= m_iIdSHELast)
1786         return S_FALSE;
1787 
1788     CmdId -= m_iIdSHEFirst;
1789     PDynamicShellEntry pEntry = GetDynamicEntry(CmdId);
1790     if (pEntry)
1791     {
1792         SHSetMenuIdInMenuMsg(uMsg, lParam, CmdId - pEntry->iIdCmdFirst);
1793         SHForwardContextMenuMsg(pEntry->pCM, uMsg, wParam, lParam, plResult, TRUE);
1794     }
1795 
1796    return S_OK;
1797 }
1798 
1799 HRESULT
1800 WINAPI
1801 CDefaultContextMenu::SetSite(IUnknown *pUnkSite)
1802 {
1803     m_site = pUnkSite;
1804     return S_OK;
1805 }
1806 
1807 HRESULT
1808 WINAPI
1809 CDefaultContextMenu::GetSite(REFIID riid, void **ppvSite)
1810 {
1811     if (!m_site)
1812         return E_FAIL;
1813 
1814     return m_site->QueryInterface(riid, ppvSite);
1815 }
1816 
1817 static
1818 HRESULT
1819 CDefaultContextMenu_CreateInstance(const DEFCONTEXTMENU *pdcm, LPFNDFMCALLBACK lpfn, REFIID riid, void **ppv)
1820 {
1821     return ShellObjectCreatorInit<CDefaultContextMenu>(pdcm, lpfn, riid, ppv);
1822 }
1823 
1824 /*************************************************************************
1825  * SHCreateDefaultContextMenu            [SHELL32.325] Vista API
1826  *
1827  */
1828 
1829 HRESULT
1830 WINAPI
1831 SHCreateDefaultContextMenu(const DEFCONTEXTMENU *pdcm, REFIID riid, void **ppv)
1832 {
1833     HRESULT hr;
1834 
1835     if (!ppv)
1836         return E_INVALIDARG;
1837 
1838     hr = CDefaultContextMenu_CreateInstance(pdcm, NULL, riid, ppv);
1839     if (FAILED_UNEXPECTEDLY(hr))
1840         return hr;
1841 
1842     return S_OK;
1843 }
1844 
1845 /*************************************************************************
1846  * CDefFolderMenu_Create2            [SHELL32.701]
1847  *
1848  */
1849 
1850 HRESULT
1851 WINAPI
1852 CDefFolderMenu_Create2(
1853     PCIDLIST_ABSOLUTE pidlFolder,
1854     HWND hwnd,
1855     UINT cidl,
1856     PCUITEMID_CHILD_ARRAY apidl,
1857     IShellFolder *psf,
1858     LPFNDFMCALLBACK lpfn,
1859     UINT nKeys,
1860     const HKEY *ahkeyClsKeys,
1861     IContextMenu **ppcm)
1862 {
1863     DEFCONTEXTMENU dcm;
1864     dcm.hwnd = hwnd;
1865     dcm.pcmcb = NULL;
1866     dcm.pidlFolder = pidlFolder;
1867     dcm.psf = psf;
1868     dcm.cidl = cidl;
1869     dcm.apidl = apidl;
1870     dcm.punkAssociationInfo = NULL;
1871     dcm.cKeys = nKeys;
1872     dcm.aKeys = ahkeyClsKeys;
1873 
1874     HRESULT hr = CDefaultContextMenu_CreateInstance(&dcm, lpfn, IID_PPV_ARG(IContextMenu, ppcm));
1875     if (FAILED_UNEXPECTEDLY(hr))
1876         return hr;
1877 
1878     return S_OK;
1879 }
1880