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