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, UINT uFlags);
88         UINT AddStaticContextMenusToMenu(HMENU hMenu, UINT* IndexMenu, UINT iIdCmdFirst, UINT iIdCmdLast);
89         HRESULT DoPaste(LPCMINVOKECOMMANDINFOEX lpcmi, BOOL bLink);
90         HRESULT DoOpenOrExplore(LPCMINVOKECOMMANDINFOEX lpcmi);
91         HRESULT DoCreateLink(LPCMINVOKECOMMANDINFOEX lpcmi);
92         HRESULT DoDelete(LPCMINVOKECOMMANDINFOEX lpcmi);
93         HRESULT DoCopyOrCut(LPCMINVOKECOMMANDINFOEX lpcmi, BOOL bCopy);
94         HRESULT DoRename(LPCMINVOKECOMMANDINFOEX lpcmi);
95         HRESULT DoProperties(LPCMINVOKECOMMANDINFOEX lpcmi);
96         HRESULT DoUndo(LPCMINVOKECOMMANDINFOEX lpcmi);
97         HRESULT DoCreateNewFolder(LPCMINVOKECOMMANDINFOEX lpici);
98         HRESULT DoCopyToMoveToFolder(LPCMINVOKECOMMANDINFOEX lpici, BOOL bCopy);
99         HRESULT InvokeShellExt(LPCMINVOKECOMMANDINFOEX lpcmi);
100         HRESULT InvokeRegVerb(LPCMINVOKECOMMANDINFOEX lpcmi);
101         DWORD BrowserFlagsFromVerb(LPCMINVOKECOMMANDINFOEX lpcmi, PStaticShellEntry pEntry);
102         HRESULT TryToBrowse(LPCMINVOKECOMMANDINFOEX lpcmi, LPCITEMIDLIST pidl, DWORD wFlags);
103         HRESULT InvokePidl(LPCMINVOKECOMMANDINFOEX 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, UINT uFlags)
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, uFlags);
433         if (SUCCEEDED(hr))
434         {
435             info.iIdCmdFirst = cIds;
436             info.NumIds = HRESULT_CODE(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, uFlags);
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         UINT added = qcminfo.idCmdFirst - idCmdNext;
650         cIds += added;
651         IndexMenu += added;
652         m_iIdCBFirst = m_iIdSHELast;
653         m_iIdCBLast = cIds;
654         idCmdNext = idCmdFirst + cIds;
655     }
656 
657     if (uFlags & CMF_VERBSONLY)
658         return MAKE_HRESULT(SEVERITY_SUCCESS, 0, cIds);
659 
660     /* If this is a background context menu we are done */
661     if (!m_cidl)
662         return MAKE_HRESULT(SEVERITY_SUCCESS, 0, cIds);
663 
664     /* Get the attributes of the items */
665     SFGAOF rfg = SFGAO_BROWSABLE | SFGAO_CANCOPY | SFGAO_CANLINK | SFGAO_CANMOVE | SFGAO_CANDELETE | SFGAO_CANRENAME | SFGAO_HASPROPSHEET | SFGAO_FILESYSTEM | SFGAO_FOLDER;
666     hr = m_psf->GetAttributesOf(m_cidl, m_apidl, &rfg);
667     if (FAILED_UNEXPECTEDLY(hr))
668         return MAKE_HRESULT(SEVERITY_SUCCESS, 0, cIds);
669 
670     /* Add the default part of the menu */
671     HMENU hmenuDefault = LoadMenu(_AtlBaseModule.GetResourceInstance(), L"MENU_SHV_FILE");
672 
673     /* Remove uneeded entries */
674     if (!(rfg & SFGAO_CANMOVE))
675         DeleteMenu(hmenuDefault, IDM_CUT, MF_BYCOMMAND);
676     if (!(rfg & SFGAO_CANCOPY))
677         DeleteMenu(hmenuDefault, IDM_COPY, MF_BYCOMMAND);
678     if (!((rfg & SFGAO_FILESYSTEM) && HasClipboardData()))
679         DeleteMenu(hmenuDefault, IDM_INSERT, MF_BYCOMMAND);
680     if (!(rfg & SFGAO_CANLINK))
681         DeleteMenu(hmenuDefault, IDM_CREATELINK, MF_BYCOMMAND);
682     if (!(rfg & SFGAO_CANDELETE))
683         DeleteMenu(hmenuDefault, IDM_DELETE, MF_BYCOMMAND);
684     if (!(rfg & SFGAO_CANRENAME))
685         DeleteMenu(hmenuDefault, IDM_RENAME, MF_BYCOMMAND);
686     if (!(rfg & SFGAO_HASPROPSHEET))
687         DeleteMenu(hmenuDefault, IDM_PROPERTIES, MF_BYCOMMAND);
688 
689     UINT idMax = Shell_MergeMenus(hMenu, GetSubMenu(hmenuDefault, 0), IndexMenu, idCmdNext, idCmdLast, 0);
690     m_iIdDfltFirst = cIds;
691     cIds += idMax - idCmdNext;
692     m_iIdDfltLast = cIds;
693 
694     DestroyMenu(hmenuDefault);
695 
696     return MAKE_HRESULT(SEVERITY_SUCCESS, 0, cIds);
697 }
698 
699 HRESULT CDefaultContextMenu::DoPaste(LPCMINVOKECOMMANDINFOEX lpcmi, BOOL bLink)
700 {
701     HRESULT hr;
702 
703     CComPtr<IDataObject> pda;
704     hr = OleGetClipboard(&pda);
705     if (FAILED_UNEXPECTEDLY(hr))
706         return hr;
707 
708     FORMATETC formatetc2;
709     STGMEDIUM medium2;
710     InitFormatEtc(formatetc2, RegisterClipboardFormatW(CFSTR_PREFERREDDROPEFFECT), TYMED_HGLOBAL);
711 
712     DWORD dwKey= 0;
713 
714     if (SUCCEEDED(pda->GetData(&formatetc2, &medium2)))
715     {
716         DWORD * pdwFlag = (DWORD*)GlobalLock(medium2.hGlobal);
717         if (pdwFlag)
718         {
719             if (*pdwFlag == DROPEFFECT_COPY)
720                 dwKey = MK_CONTROL;
721             else
722                 dwKey = MK_SHIFT;
723         }
724         else
725         {
726             ERR("No drop effect obtained\n");
727         }
728         GlobalUnlock(medium2.hGlobal);
729     }
730 
731     if (bLink)
732     {
733         dwKey = MK_CONTROL|MK_SHIFT;
734     }
735 
736     CComPtr<IDropTarget> pdrop;
737     if (m_cidl)
738         hr = m_psf->GetUIObjectOf(NULL, 1, &m_apidl[0], IID_NULL_PPV_ARG(IDropTarget, &pdrop));
739     else
740         hr = m_psf->CreateViewObject(NULL, IID_PPV_ARG(IDropTarget, &pdrop));
741 
742     if (FAILED_UNEXPECTEDLY(hr))
743         return hr;
744 
745     SHSimulateDrop(pdrop, pda, dwKey, NULL, NULL);
746 
747     TRACE("CP result %x\n", hr);
748     return S_OK;
749 }
750 
751 HRESULT
752 CDefaultContextMenu::DoOpenOrExplore(LPCMINVOKECOMMANDINFOEX lpcmi)
753 {
754     UNIMPLEMENTED;
755     return E_FAIL;
756 }
757 
758 HRESULT CDefaultContextMenu::DoCreateLink(LPCMINVOKECOMMANDINFOEX lpcmi)
759 {
760     if (!m_cidl || !m_pDataObj)
761         return E_FAIL;
762 
763     CComPtr<IDropTarget> pDT;
764     HRESULT hr = m_psf->CreateViewObject(NULL, IID_PPV_ARG(IDropTarget, &pDT));
765     if (FAILED_UNEXPECTEDLY(hr))
766         return hr;
767 
768     SHSimulateDrop(pDT, m_pDataObj, MK_CONTROL|MK_SHIFT, NULL, NULL);
769 
770     return S_OK;
771 }
772 
773 HRESULT CDefaultContextMenu::DoDelete(LPCMINVOKECOMMANDINFOEX lpcmi)
774 {
775     if (!m_cidl || !m_pDataObj)
776         return E_FAIL;
777 
778     CComPtr<IDropTarget> pDT;
779     HRESULT hr = CRecyclerDropTarget_CreateInstance(IID_PPV_ARG(IDropTarget, &pDT));
780     if (FAILED_UNEXPECTEDLY(hr))
781         return hr;
782 
783     DWORD grfKeyState = (lpcmi->fMask & CMIC_MASK_SHIFT_DOWN) ? MK_SHIFT : 0;
784     SHSimulateDrop(pDT, m_pDataObj, grfKeyState, NULL, NULL);
785 
786     return S_OK;
787 }
788 
789 HRESULT CDefaultContextMenu::DoCopyOrCut(LPCMINVOKECOMMANDINFOEX lpcmi, BOOL bCopy)
790 {
791     if (!m_cidl || !m_pDataObj)
792         return E_FAIL;
793 
794     FORMATETC formatetc;
795     InitFormatEtc(formatetc, RegisterClipboardFormatW(CFSTR_PREFERREDDROPEFFECT), TYMED_HGLOBAL);
796     STGMEDIUM medium = {0};
797     medium.tymed = TYMED_HGLOBAL;
798     medium.hGlobal = GlobalAlloc(GHND, sizeof(DWORD));
799     DWORD* pdwFlag = (DWORD*)GlobalLock(medium.hGlobal);
800     if (pdwFlag)
801         *pdwFlag = bCopy ? DROPEFFECT_COPY : DROPEFFECT_MOVE;
802     GlobalUnlock(medium.hGlobal);
803     m_pDataObj->SetData(&formatetc, &medium, TRUE);
804 
805     HRESULT hr = OleSetClipboard(m_pDataObj);
806     if (FAILED_UNEXPECTEDLY(hr))
807         return hr;
808 
809     return S_OK;
810 }
811 
812 HRESULT CDefaultContextMenu::DoRename(LPCMINVOKECOMMANDINFOEX lpcmi)
813 {
814     CComPtr<IShellBrowser> psb;
815     HRESULT hr;
816 
817     if (!m_site || !m_cidl)
818         return E_FAIL;
819 
820     /* Get a pointer to the shell browser */
821     hr = IUnknown_QueryService(m_site, SID_IShellBrowser, IID_PPV_ARG(IShellBrowser, &psb));
822     if (FAILED_UNEXPECTEDLY(hr))
823         return hr;
824 
825     CComPtr<IShellView> lpSV;
826     hr = psb->QueryActiveShellView(&lpSV);
827     if (FAILED_UNEXPECTEDLY(hr))
828         return hr;
829 
830     SVSIF selFlags = SVSI_DESELECTOTHERS | SVSI_EDIT | SVSI_ENSUREVISIBLE | SVSI_FOCUSED | SVSI_SELECT;
831     hr = lpSV->SelectItem(m_apidl[0], selFlags);
832     if (FAILED_UNEXPECTEDLY(hr))
833         return hr;
834 
835     return S_OK;
836 }
837 
838 HRESULT
839 CDefaultContextMenu::DoProperties(
840     LPCMINVOKECOMMANDINFOEX lpcmi)
841 {
842     HRESULT hr = _DoCallback(DFM_INVOKECOMMAND, DFM_CMD_PROPERTIES, NULL);
843 
844     // We are asked to run the default property sheet
845     if (hr == S_FALSE)
846     {
847         return Shell_DefaultContextMenuCallBack(m_psf, m_pDataObj);
848     }
849 
850     return hr;
851 }
852 
853 HRESULT
854 CDefaultContextMenu::DoUndo(LPCMINVOKECOMMANDINFOEX lpcmi)
855 {
856     ERR("TODO: Undo\n");
857     return E_NOTIMPL;
858 }
859 
860 HRESULT
861 CDefaultContextMenu::DoCopyToMoveToFolder(LPCMINVOKECOMMANDINFOEX lpici, BOOL bCopy)
862 {
863     HRESULT hr = E_FAIL;
864     if (!m_pDataObj)
865     {
866         ERR("m_pDataObj is NULL\n");
867         return hr;
868     }
869 
870     CComPtr<IContextMenu> pContextMenu;
871     if (bCopy)
872         hr = SHCoCreateInstance(NULL, &CLSID_CopyToMenu, NULL,
873                                 IID_PPV_ARG(IContextMenu, &pContextMenu));
874     else
875         hr = SHCoCreateInstance(NULL, &CLSID_MoveToMenu, NULL,
876                                 IID_PPV_ARG(IContextMenu, &pContextMenu));
877     if (FAILED_UNEXPECTEDLY(hr))
878         return hr;
879 
880     CComPtr<IShellExtInit> pInit;
881     hr = pContextMenu->QueryInterface(IID_PPV_ARG(IShellExtInit, &pInit));
882     if (FAILED_UNEXPECTEDLY(hr))
883         return hr;
884 
885     hr = pInit->Initialize(m_pidlFolder, m_pDataObj, NULL);
886     if (FAILED_UNEXPECTEDLY(hr))
887         return hr;
888 
889     if (bCopy)
890         lpici->lpVerb = "copyto";
891     else
892         lpici->lpVerb = "moveto";
893 
894     return pContextMenu->InvokeCommand((LPCMINVOKECOMMANDINFO)lpici);
895 }
896 
897 // This code is taken from CNewMenu and should be shared between the 2 classes
898 HRESULT
899 CDefaultContextMenu::DoCreateNewFolder(
900     LPCMINVOKECOMMANDINFOEX lpici)
901 {
902     WCHAR wszPath[MAX_PATH];
903     WCHAR wszName[MAX_PATH];
904     WCHAR wszNewFolder[25];
905     HRESULT hr;
906 
907     /* Get folder path */
908     hr = SHGetPathFromIDListW(m_pidlFolder, wszPath);
909     if (FAILED_UNEXPECTEDLY(hr))
910         return hr;
911 
912     if (!LoadStringW(shell32_hInstance, IDS_NEWFOLDER, wszNewFolder, _countof(wszNewFolder)))
913         return E_FAIL;
914 
915     /* Create the name of the new directory */
916     if (!PathYetAnotherMakeUniqueName(wszName, wszPath, NULL, wszNewFolder))
917         return E_FAIL;
918 
919     /* Create the new directory and show the appropriate dialog in case of error */
920     if (SHCreateDirectory(lpici->hwnd, wszName) != ERROR_SUCCESS)
921         return E_FAIL;
922 
923     /* Show and select the new item in the def view */
924     LPITEMIDLIST pidl;
925     PITEMID_CHILD pidlNewItem;
926     CComPtr<IShellView> psv;
927 
928     /* Notify the view object about the new item */
929     SHChangeNotify(SHCNE_MKDIR, SHCNF_PATHW, (LPCVOID)wszName, NULL);
930 
931     if (!m_site)
932         return S_OK;
933 
934     /* Get a pointer to the shell view */
935     hr = IUnknown_QueryService(m_site, SID_IFolderView, IID_PPV_ARG(IShellView, &psv));
936     if (FAILED_UNEXPECTEDLY(hr))
937         return S_OK;
938 
939     /* Attempt to get the pidl of the new item */
940     hr = SHILCreateFromPathW(wszName, &pidl, NULL);
941     if (FAILED_UNEXPECTEDLY(hr))
942         return hr;
943 
944     pidlNewItem = ILFindLastID(pidl);
945 
946     hr = psv->SelectItem(pidlNewItem, SVSI_DESELECTOTHERS | SVSI_EDIT | SVSI_ENSUREVISIBLE |
947                           SVSI_FOCUSED | SVSI_SELECT);
948     if (FAILED_UNEXPECTEDLY(hr))
949         return hr;
950 
951     SHFree(pidl);
952 
953     return S_OK;
954 }
955 
956 PDynamicShellEntry CDefaultContextMenu::GetDynamicEntry(UINT idCmd)
957 {
958     POSITION it = m_DynamicEntries.GetHeadPosition();
959     while (it != NULL)
960     {
961         DynamicShellEntry& info = m_DynamicEntries.GetNext(it);
962 
963         if (idCmd >= info.iIdCmdFirst + info.NumIds)
964             continue;
965 
966         if (idCmd < info.iIdCmdFirst || idCmd > info.iIdCmdFirst + info.NumIds)
967             return NULL;
968 
969         return &info;
970     }
971 
972     return NULL;
973 }
974 
975 // FIXME: 260 is correct, but should this be part of the SDK or just MAX_PATH?
976 #define MAX_VERB 260
977 
978 BOOL
979 CDefaultContextMenu::MapVerbToCmdId(PVOID Verb, PUINT idCmd, BOOL IsUnicode)
980 {
981     WCHAR UnicodeStr[MAX_VERB];
982 
983     /* Loop through all the static verbs looking for a match */
984     for (UINT i = 0; i < _countof(g_StaticInvokeCmdMap); i++)
985     {
986         /* We can match both ANSI and unicode strings */
987         if (IsUnicode)
988         {
989             /* The static verbs are ANSI, get a unicode version before doing the compare */
990             SHAnsiToUnicode(g_StaticInvokeCmdMap[i].szStringVerb, UnicodeStr, MAX_VERB);
991             if (!wcscmp(UnicodeStr, (LPWSTR)Verb))
992             {
993                 /* Return the Corresponding Id */
994                 *idCmd = g_StaticInvokeCmdMap[i].IntVerb;
995                 return TRUE;
996             }
997         }
998         else
999         {
1000             if (!strcmp(g_StaticInvokeCmdMap[i].szStringVerb, (LPSTR)Verb))
1001             {
1002                 *idCmd = g_StaticInvokeCmdMap[i].IntVerb;
1003                 return TRUE;
1004             }
1005         }
1006     }
1007 
1008     return FALSE;
1009 }
1010 
1011 HRESULT
1012 CDefaultContextMenu::InvokeShellExt(
1013     LPCMINVOKECOMMANDINFOEX lpcmi)
1014 {
1015     TRACE("verb %p first %x last %x\n", lpcmi->lpVerb, m_iIdSHEFirst, m_iIdSHELast);
1016 
1017     UINT idCmd = LOWORD(lpcmi->lpVerb);
1018     PDynamicShellEntry pEntry = GetDynamicEntry(idCmd);
1019     if (!pEntry)
1020         return E_FAIL;
1021 
1022     /* invoke the dynamic context menu */
1023     lpcmi->lpVerb = MAKEINTRESOURCEA(idCmd - pEntry->iIdCmdFirst);
1024     return pEntry->pCM->InvokeCommand((LPCMINVOKECOMMANDINFO)lpcmi);
1025 }
1026 
1027 DWORD
1028 CDefaultContextMenu::BrowserFlagsFromVerb(LPCMINVOKECOMMANDINFOEX lpcmi, PStaticShellEntry pEntry)
1029 {
1030     CComPtr<IShellBrowser> psb;
1031     HWND hwndTree;
1032     LPCWSTR FlagsName;
1033     WCHAR wszKey[256];
1034     HRESULT hr;
1035     DWORD wFlags;
1036     DWORD cbVerb;
1037 
1038     if (!m_site)
1039         return 0;
1040 
1041     /* Get a pointer to the shell browser */
1042     hr = IUnknown_QueryService(m_site, SID_IShellBrowser, IID_PPV_ARG(IShellBrowser, &psb));
1043     if (FAILED_UNEXPECTEDLY(hr))
1044         return 0;
1045 
1046     /* See if we are in Explore or Browse mode. If the browser's tree is present, we are in Explore mode.*/
1047     if (SUCCEEDED(psb->GetControlWindow(FCW_TREE, &hwndTree)) && hwndTree)
1048         FlagsName = L"ExplorerFlags";
1049     else
1050         FlagsName = L"BrowserFlags";
1051 
1052     /* Try to get the flag from the verb */
1053     hr = StringCbPrintfW(wszKey, sizeof(wszKey), L"shell\\%s", pEntry->Verb.GetString());
1054     if (FAILED_UNEXPECTEDLY(hr))
1055         return 0;
1056 
1057     cbVerb = sizeof(wFlags);
1058     if (RegGetValueW(pEntry->hkClass, wszKey, FlagsName, RRF_RT_REG_DWORD, NULL, &wFlags, &cbVerb) == ERROR_SUCCESS)
1059     {
1060         return wFlags;
1061     }
1062 
1063     return 0;
1064 }
1065 
1066 HRESULT
1067 CDefaultContextMenu::TryToBrowse(
1068     LPCMINVOKECOMMANDINFOEX lpcmi, LPCITEMIDLIST pidl, DWORD wFlags)
1069 {
1070     CComPtr<IShellBrowser> psb;
1071     HRESULT hr;
1072 
1073     if (!m_site)
1074         return E_FAIL;
1075 
1076     /* Get a pointer to the shell browser */
1077     hr = IUnknown_QueryService(m_site, SID_IShellBrowser, IID_PPV_ARG(IShellBrowser, &psb));
1078     if (FAILED_UNEXPECTEDLY(hr))
1079         return 0;
1080 
1081     return psb->BrowseObject(ILCombine(m_pidlFolder, pidl), wFlags);
1082 }
1083 
1084 HRESULT
1085 CDefaultContextMenu::InvokePidl(LPCMINVOKECOMMANDINFOEX lpcmi, LPCITEMIDLIST pidl, PStaticShellEntry pEntry)
1086 {
1087     LPITEMIDLIST pidlFull = ILCombine(m_pidlFolder, pidl);
1088     if (pidlFull == NULL)
1089     {
1090         return E_FAIL;
1091     }
1092 
1093     WCHAR wszPath[MAX_PATH];
1094     BOOL bHasPath = SHGetPathFromIDListW(pidlFull, wszPath);
1095 
1096     WCHAR wszDir[MAX_PATH];
1097     if (bHasPath)
1098     {
1099         wcscpy(wszDir, wszPath);
1100         PathRemoveFileSpec(wszDir);
1101     }
1102     else
1103     {
1104         SHGetPathFromIDListW(m_pidlFolder, wszDir);
1105     }
1106 
1107     SHELLEXECUTEINFOW sei;
1108     ZeroMemory(&sei, sizeof(sei));
1109     sei.cbSize = sizeof(sei);
1110     sei.hwnd = lpcmi->hwnd;
1111     sei.nShow = SW_SHOWNORMAL;
1112     sei.lpVerb = pEntry->Verb;
1113     sei.lpDirectory = wszDir;
1114     sei.lpIDList = pidlFull;
1115     sei.hkeyClass = pEntry->hkClass;
1116     sei.fMask = SEE_MASK_CLASSKEY | SEE_MASK_IDLIST;
1117     if (bHasPath)
1118     {
1119         sei.lpFile = wszPath;
1120     }
1121 
1122     ShellExecuteExW(&sei);
1123 
1124     ILFree(pidlFull);
1125 
1126     return S_OK;
1127 }
1128 
1129 HRESULT
1130 CDefaultContextMenu::InvokeRegVerb(
1131     LPCMINVOKECOMMANDINFOEX lpcmi)
1132 {
1133     INT iCmd = LOWORD(lpcmi->lpVerb);
1134     HRESULT hr;
1135     UINT i;
1136 
1137     POSITION it = m_StaticEntries.FindIndex(iCmd);
1138 
1139     if (it == NULL)
1140         return E_INVALIDARG;
1141 
1142     PStaticShellEntry pEntry = &m_StaticEntries.GetAt(it);
1143 
1144     /* Get the browse flags to see if we need to browse */
1145     DWORD wFlags = BrowserFlagsFromVerb(lpcmi, pEntry);
1146     BOOL bBrowsed = FALSE;
1147 
1148     for (i=0; i < m_cidl; i++)
1149     {
1150         /* Check if we need to browse */
1151         if (wFlags > 0)
1152         {
1153             /* In xp if we have browsed, we don't open any more folders.
1154              * In win7 we browse to the first folder we find and
1155              * open new windows for each of the rest of the folders */
1156             if (bBrowsed)
1157                 continue;
1158 
1159             hr = TryToBrowse(lpcmi, m_apidl[i], wFlags);
1160             if (SUCCEEDED(hr))
1161             {
1162                 bBrowsed = TRUE;
1163                 continue;
1164             }
1165         }
1166 
1167         InvokePidl(lpcmi, m_apidl[i], pEntry);
1168     }
1169 
1170     return S_OK;
1171 }
1172 
1173 HRESULT
1174 WINAPI
1175 CDefaultContextMenu::InvokeCommand(
1176     LPCMINVOKECOMMANDINFO lpcmi)
1177 {
1178     CMINVOKECOMMANDINFOEX LocalInvokeInfo = {};
1179     HRESULT Result;
1180     UINT CmdId;
1181 
1182     /* Take a local copy of the fixed members of the
1183        struct as we might need to modify the verb */
1184     memcpy(&LocalInvokeInfo, lpcmi, min(sizeof(LocalInvokeInfo), lpcmi->cbSize));
1185 
1186     /* Check if this is a string verb */
1187     if (HIWORD(LocalInvokeInfo.lpVerb))
1188     {
1189         /* Get the ID which corresponds to this verb, and update our local copy */
1190         if (MapVerbToCmdId((LPVOID)LocalInvokeInfo.lpVerb, &CmdId, FALSE))
1191             LocalInvokeInfo.lpVerb = MAKEINTRESOURCEA(CmdId);
1192     }
1193 
1194     CmdId = LOWORD(LocalInvokeInfo.lpVerb);
1195 
1196     if (!m_DynamicEntries.IsEmpty() && CmdId >= m_iIdSHEFirst && CmdId < m_iIdSHELast)
1197     {
1198         LocalInvokeInfo.lpVerb -= m_iIdSHEFirst;
1199         Result = InvokeShellExt(&LocalInvokeInfo);
1200         return Result;
1201     }
1202 
1203     if (!m_StaticEntries.IsEmpty() && CmdId >= m_iIdSCMFirst && CmdId < m_iIdSCMLast)
1204     {
1205         LocalInvokeInfo.lpVerb -= m_iIdSCMFirst;
1206         Result = InvokeRegVerb(&LocalInvokeInfo);
1207         return Result;
1208     }
1209 
1210     if (m_iIdCBFirst != m_iIdCBLast && CmdId >= m_iIdCBFirst && CmdId < m_iIdCBLast)
1211     {
1212         Result = _DoCallback(DFM_INVOKECOMMAND, CmdId - m_iIdCBFirst, NULL);
1213         return Result;
1214     }
1215 
1216     if (m_iIdDfltFirst != m_iIdDfltLast && CmdId >= m_iIdDfltFirst && CmdId < m_iIdDfltLast)
1217     {
1218         CmdId -= m_iIdDfltFirst;
1219         /* See the definitions of IDM_CUT and co to see how this works */
1220         CmdId += 0x7000;
1221     }
1222 
1223     if (LocalInvokeInfo.cbSize >= sizeof(CMINVOKECOMMANDINFOEX) && (LocalInvokeInfo.fMask & CMIC_MASK_PTINVOKE))
1224     {
1225         if (m_pDataObj && FAILED_UNEXPECTEDLY(DataObject_SetOffset(m_pDataObj, &LocalInvokeInfo.ptInvoke)))
1226         {
1227             ERR("Unable to add OFFSET to DataObject!\n");
1228         }
1229     }
1230 
1231     /* Check if this is a Id */
1232     switch (CmdId)
1233     {
1234     case FCIDM_SHVIEW_INSERT:
1235         Result = DoPaste(&LocalInvokeInfo, FALSE);
1236         break;
1237     case FCIDM_SHVIEW_INSERTLINK:
1238         Result = DoPaste(&LocalInvokeInfo, TRUE);
1239         break;
1240     case FCIDM_SHVIEW_OPEN:
1241     case FCIDM_SHVIEW_EXPLORE:
1242         Result = DoOpenOrExplore(&LocalInvokeInfo);
1243         break;
1244     case FCIDM_SHVIEW_COPY:
1245     case FCIDM_SHVIEW_CUT:
1246         Result = DoCopyOrCut(&LocalInvokeInfo, CmdId == FCIDM_SHVIEW_COPY);
1247         break;
1248     case FCIDM_SHVIEW_CREATELINK:
1249         Result = DoCreateLink(&LocalInvokeInfo);
1250         break;
1251     case FCIDM_SHVIEW_DELETE:
1252         Result = DoDelete(&LocalInvokeInfo);
1253         break;
1254     case FCIDM_SHVIEW_RENAME:
1255         Result = DoRename(&LocalInvokeInfo);
1256         break;
1257     case FCIDM_SHVIEW_PROPERTIES:
1258         Result = DoProperties(&LocalInvokeInfo);
1259         break;
1260     case FCIDM_SHVIEW_NEWFOLDER:
1261         Result = DoCreateNewFolder(&LocalInvokeInfo);
1262         break;
1263     case FCIDM_SHVIEW_COPYTO:
1264         Result = DoCopyToMoveToFolder(&LocalInvokeInfo, TRUE);
1265         break;
1266     case FCIDM_SHVIEW_MOVETO:
1267         Result = DoCopyToMoveToFolder(&LocalInvokeInfo, FALSE);
1268         break;
1269     case FCIDM_SHVIEW_UNDO:
1270         Result = DoUndo(&LocalInvokeInfo);
1271         break;
1272     default:
1273         Result = E_INVALIDARG;
1274         ERR("Unhandled Verb %xl\n", LOWORD(LocalInvokeInfo.lpVerb));
1275         break;
1276     }
1277 
1278     return Result;
1279 }
1280 
1281 HRESULT
1282 WINAPI
1283 CDefaultContextMenu::GetCommandString(
1284     UINT_PTR idCommand,
1285     UINT uFlags,
1286     UINT* lpReserved,
1287     LPSTR lpszName,
1288     UINT uMaxNameLen)
1289 {
1290     /* We don't handle the help text yet */
1291     if (uFlags == GCS_HELPTEXTA ||
1292         uFlags == GCS_HELPTEXTW ||
1293         HIWORD(idCommand) != 0)
1294     {
1295         return E_NOTIMPL;
1296     }
1297 
1298     UINT CmdId = LOWORD(idCommand);
1299 
1300     if (!m_DynamicEntries.IsEmpty() && CmdId >= m_iIdSHEFirst && CmdId < m_iIdSHELast)
1301     {
1302         idCommand -= m_iIdSHEFirst;
1303         PDynamicShellEntry pEntry = GetDynamicEntry(idCommand);
1304         if (!pEntry)
1305             return E_FAIL;
1306 
1307         idCommand -= pEntry->iIdCmdFirst;
1308         return pEntry->pCM->GetCommandString(idCommand,
1309                                              uFlags,
1310                                              lpReserved,
1311                                              lpszName,
1312                                              uMaxNameLen);
1313     }
1314 
1315     if (!m_StaticEntries.IsEmpty() && CmdId >= m_iIdSCMFirst && CmdId < m_iIdSCMLast)
1316     {
1317         /* Validation just returns S_OK on a match. The id exists. */
1318         if (uFlags == GCS_VALIDATEA || uFlags == GCS_VALIDATEW)
1319             return S_OK;
1320 
1321         CmdId -= m_iIdSCMFirst;
1322 
1323         POSITION it = m_StaticEntries.FindIndex(CmdId);
1324 
1325         if (it == NULL)
1326             return E_INVALIDARG;
1327 
1328         PStaticShellEntry pEntry = &m_StaticEntries.GetAt(it);
1329 
1330         if (uFlags == GCS_VERBW)
1331             return StringCchCopyW((LPWSTR)lpszName, uMaxNameLen, pEntry->Verb);
1332 
1333         if (uFlags == GCS_VERBA)
1334         {
1335             if (SHUnicodeToAnsi(pEntry->Verb, lpszName, uMaxNameLen))
1336                 return S_OK;
1337         }
1338 
1339         return E_INVALIDARG;
1340     }
1341 
1342     //FIXME: Should we handle callbacks here?
1343     if (m_iIdDfltFirst != m_iIdDfltLast && CmdId >= m_iIdDfltFirst && CmdId < m_iIdDfltLast)
1344     {
1345         CmdId -= m_iIdDfltFirst;
1346         /* See the definitions of IDM_CUT and co to see how this works */
1347         CmdId += 0x7000;
1348     }
1349 
1350     /* Loop looking for a matching Id */
1351     for (UINT i = 0; i < _countof(g_StaticInvokeCmdMap); i++)
1352     {
1353         if (g_StaticInvokeCmdMap[i].IntVerb == CmdId)
1354         {
1355             /* Validation just returns S_OK on a match */
1356             if (uFlags == GCS_VALIDATEA || uFlags == GCS_VALIDATEW)
1357                 return S_OK;
1358 
1359             /* Return a copy of the ANSI verb */
1360             if (uFlags == GCS_VERBA)
1361                 return StringCchCopyA(lpszName, uMaxNameLen, g_StaticInvokeCmdMap[i].szStringVerb);
1362 
1363             /* Convert the ANSI verb to unicode and return that */
1364             if (uFlags == GCS_VERBW)
1365             {
1366                 if (SHAnsiToUnicode(g_StaticInvokeCmdMap[i].szStringVerb, (LPWSTR)lpszName, uMaxNameLen))
1367                     return S_OK;
1368             }
1369         }
1370     }
1371 
1372     return E_INVALIDARG;
1373 }
1374 
1375 HRESULT
1376 WINAPI
1377 CDefaultContextMenu::HandleMenuMsg(
1378     UINT uMsg,
1379     WPARAM wParam,
1380     LPARAM lParam)
1381 {
1382     /* FIXME: Should we implement this as well? */
1383     return S_OK;
1384 }
1385 
1386 HRESULT SHGetMenuIdFromMenuMsg(UINT uMsg, LPARAM lParam, UINT *CmdId)
1387 {
1388     if (uMsg == WM_DRAWITEM)
1389     {
1390         DRAWITEMSTRUCT* pDrawStruct = reinterpret_cast<DRAWITEMSTRUCT*>(lParam);
1391         *CmdId = pDrawStruct->itemID;
1392         return S_OK;
1393     }
1394     else if (uMsg == WM_MEASUREITEM)
1395     {
1396         MEASUREITEMSTRUCT* pMeasureStruct = reinterpret_cast<MEASUREITEMSTRUCT*>(lParam);
1397         *CmdId = pMeasureStruct->itemID;
1398         return S_OK;
1399     }
1400 
1401     return E_FAIL;
1402 }
1403 
1404 HRESULT SHSetMenuIdInMenuMsg(UINT uMsg, LPARAM lParam, UINT CmdId)
1405 {
1406     if (uMsg == WM_DRAWITEM)
1407     {
1408         DRAWITEMSTRUCT* pDrawStruct = reinterpret_cast<DRAWITEMSTRUCT*>(lParam);
1409         pDrawStruct->itemID = CmdId;
1410         return S_OK;
1411     }
1412     else if (uMsg == WM_MEASUREITEM)
1413     {
1414         MEASUREITEMSTRUCT* pMeasureStruct = reinterpret_cast<MEASUREITEMSTRUCT*>(lParam);
1415         pMeasureStruct->itemID = CmdId;
1416         return S_OK;
1417     }
1418 
1419     return E_FAIL;
1420 }
1421 
1422 HRESULT
1423 WINAPI
1424 CDefaultContextMenu::HandleMenuMsg2(
1425     UINT uMsg,
1426     WPARAM wParam,
1427     LPARAM lParam,
1428     LRESULT *plResult)
1429 {
1430     if (uMsg == WM_INITMENUPOPUP)
1431     {
1432         POSITION it = m_DynamicEntries.GetHeadPosition();
1433         while (it != NULL)
1434         {
1435             DynamicShellEntry& info = m_DynamicEntries.GetNext(it);
1436             SHForwardContextMenuMsg(info.pCM, uMsg, wParam, lParam, plResult, TRUE);
1437         }
1438         return S_OK;
1439     }
1440 
1441     UINT CmdId;
1442     HRESULT hr = SHGetMenuIdFromMenuMsg(uMsg, lParam, &CmdId);
1443     if (FAILED(hr))
1444         return S_FALSE;
1445 
1446     if (CmdId < m_iIdSHEFirst || CmdId >= m_iIdSHELast)
1447         return S_FALSE;
1448 
1449     CmdId -= m_iIdSHEFirst;
1450     PDynamicShellEntry pEntry = GetDynamicEntry(CmdId);
1451     if (pEntry)
1452     {
1453         SHSetMenuIdInMenuMsg(uMsg, lParam, CmdId - pEntry->iIdCmdFirst);
1454         SHForwardContextMenuMsg(pEntry->pCM, uMsg, wParam, lParam, plResult, TRUE);
1455     }
1456 
1457    return S_OK;
1458 }
1459 
1460 HRESULT
1461 WINAPI
1462 CDefaultContextMenu::SetSite(IUnknown *pUnkSite)
1463 {
1464     m_site = pUnkSite;
1465     return S_OK;
1466 }
1467 
1468 HRESULT
1469 WINAPI
1470 CDefaultContextMenu::GetSite(REFIID riid, void **ppvSite)
1471 {
1472     if (!m_site)
1473         return E_FAIL;
1474 
1475     return m_site->QueryInterface(riid, ppvSite);
1476 }
1477 
1478 static
1479 HRESULT
1480 CDefaultContextMenu_CreateInstance(const DEFCONTEXTMENU *pdcm, LPFNDFMCALLBACK lpfn, REFIID riid, void **ppv)
1481 {
1482     return ShellObjectCreatorInit<CDefaultContextMenu>(pdcm, lpfn, riid, ppv);
1483 }
1484 
1485 /*************************************************************************
1486  * SHCreateDefaultContextMenu            [SHELL32.325] Vista API
1487  *
1488  */
1489 
1490 HRESULT
1491 WINAPI
1492 SHCreateDefaultContextMenu(const DEFCONTEXTMENU *pdcm, REFIID riid, void **ppv)
1493 {
1494     HRESULT hr;
1495 
1496     if (!ppv)
1497         return E_INVALIDARG;
1498 
1499     hr = CDefaultContextMenu_CreateInstance(pdcm, NULL, riid, ppv);
1500     if (FAILED_UNEXPECTEDLY(hr))
1501         return hr;
1502 
1503     return S_OK;
1504 }
1505 
1506 /*************************************************************************
1507  * CDefFolderMenu_Create2            [SHELL32.701]
1508  *
1509  */
1510 
1511 HRESULT
1512 WINAPI
1513 CDefFolderMenu_Create2(
1514     PCIDLIST_ABSOLUTE pidlFolder,
1515     HWND hwnd,
1516     UINT cidl,
1517     PCUITEMID_CHILD_ARRAY apidl,
1518     IShellFolder *psf,
1519     LPFNDFMCALLBACK lpfn,
1520     UINT nKeys,
1521     const HKEY *ahkeyClsKeys,
1522     IContextMenu **ppcm)
1523 {
1524     DEFCONTEXTMENU dcm;
1525     dcm.hwnd = hwnd;
1526     dcm.pcmcb = NULL;
1527     dcm.pidlFolder = pidlFolder;
1528     dcm.psf = psf;
1529     dcm.cidl = cidl;
1530     dcm.apidl = apidl;
1531     dcm.punkAssociationInfo = NULL;
1532     dcm.cKeys = nKeys;
1533     dcm.aKeys = ahkeyClsKeys;
1534 
1535     HRESULT hr = CDefaultContextMenu_CreateInstance(&dcm, lpfn, IID_PPV_ARG(IContextMenu, ppcm));
1536     if (FAILED_UNEXPECTEDLY(hr))
1537         return hr;
1538 
1539     return S_OK;
1540 }
1541