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