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