[SHELL32]
[reactos.git] / reactos / dll / win32 / shell32 / shellmenu / CStartMenu.cpp
1 /*
2 * ReactOS Explorer
3 *
4 * Copyright 2014 Giannis Adamopoulos
5 *
6 * This library is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU Lesser General Public
8 * License as published by the Free Software Foundation; either
9 * version 2.1 of the License, or (at your option) any later version.
10 *
11 * This library is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * Lesser General Public License for more details.
15 *
16 * You should have received a copy of the GNU Lesser General Public
17 * License along with this library; if not, write to the Free Software
18 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
19 */
20 #include "shellmenu.h"
21
22 #include "CMergedFolder.h"
23
24 WINE_DEFAULT_DEBUG_CHANNEL(CStartMenu);
25
26 //#define TEST_TRACKPOPUPMENU_SUBMENUS
27
28
29 /* NOTE: The following constants *MUST NOT* be changed because
30 they're hardcoded and need to be the exact values
31 in order to get the start menu to work! */
32 #define IDM_RUN 401
33 #define IDM_LOGOFF 402
34 #define IDM_UNDOCKCOMPUTER 410
35 #define IDM_TASKBARANDSTARTMENU 413
36 #define IDM_LASTSTARTMENU_SEPARATOR 450
37 #define IDM_DOCUMENTS 501
38 #define IDM_HELPANDSUPPORT 503
39 #define IDM_PROGRAMS 504
40 #define IDM_CONTROLPANEL 505
41 #define IDM_SHUTDOWN 506
42 #define IDM_FAVORITES 507
43 #define IDM_SETTINGS 508
44 #define IDM_PRINTERSANDFAXES 510
45 #define IDM_SEARCH 520
46 #define IDM_SYNCHRONIZE 553
47 #define IDM_NETWORKCONNECTIONS 557
48 #define IDM_DISCONNECT 5000
49 #define IDM_SECURITY 5001
50
51 /*
52 * TODO:
53 * 1. append the start menu contents from all users
54 * 2. implement the context menu for start menu entries (programs, control panel, network connetions, printers)
55 * 3. filter out programs folder from the shell folder part of the start menu
56 * 4. showing the programs start menu is SLOW compared to windows. this needs some investigation
57 */
58
59 class CShellMenuCallback :
60 public CComObjectRootEx<CComMultiThreadModelNoCS>,
61 public IShellMenuCallback
62 {
63 private:
64
65 HWND m_hwndTray;
66 CComPtr<IShellMenu> m_pShellMenu;
67 CComPtr<IBandSite> m_pBandSite;
68 CComPtr<IDeskBar> m_pDeskBar;
69 CComPtr<ITrayPriv> m_pTrayPriv;
70 CComPtr<IShellFolder> m_psfPrograms;
71
72 LPITEMIDLIST m_pidlPrograms;
73
74 HRESULT OnInitMenu()
75 {
76 HMENU hmenu;
77 HRESULT hr;
78
79 if (m_pTrayPriv.p)
80 return S_OK;
81
82 hr = IUnknown_GetSite(m_pDeskBar, IID_PPV_ARG(ITrayPriv, &m_pTrayPriv));
83 if (FAILED_UNEXPECTEDLY(hr))
84 return hr;
85
86 hr = IUnknown_GetWindow(m_pTrayPriv, &m_hwndTray);
87 if (FAILED_UNEXPECTEDLY(hr))
88 return hr;
89
90 hr = m_pTrayPriv->AppendMenuW(&hmenu);
91 if (FAILED_UNEXPECTEDLY(hr))
92 return hr;
93
94 hr = m_pShellMenu->SetMenu(hmenu, NULL, SMSET_BOTTOM);
95 if (FAILED_UNEXPECTEDLY(hr))
96 return hr;
97
98 return hr;
99 }
100
101 HRESULT OnGetInfo(LPSMDATA psmd, SMINFO *psminfo)
102 {
103 int iconIndex = 0;
104
105 switch (psmd->uId)
106 {
107 // Smaller "24x24" icons used for the start menu
108 // The bitmaps are still 32x32, but the image is centered
109 case IDM_FAVORITES: iconIndex = -322; break;
110 case IDM_SEARCH: iconIndex = -323; break;
111 case IDM_HELPANDSUPPORT: iconIndex = -324; break;
112 case IDM_LOGOFF: iconIndex = -325; break;
113 case IDM_PROGRAMS: iconIndex = -326; break;
114 case IDM_DOCUMENTS: iconIndex = -327; break;
115 case IDM_RUN: iconIndex = -328; break;
116 case IDM_SHUTDOWN: iconIndex = -329; break;
117 case IDM_SETTINGS: iconIndex = -330; break;
118
119 case IDM_CONTROLPANEL: iconIndex = -22; break;
120 case IDM_NETWORKCONNECTIONS: iconIndex = -257; break;
121 case IDM_PRINTERSANDFAXES: iconIndex = -138; break;
122 case IDM_TASKBARANDSTARTMENU: iconIndex = -40; break;
123 //case IDM_SECURITY: iconIndex = -21; break;
124 //case IDM_SYNCHRONIZE: iconIndex = -21; break;
125 //case IDM_DISCONNECT: iconIndex = -21; break;
126 //case IDM_UNDOCKCOMPUTER: iconIndex = -21; break;
127 default:
128 return S_FALSE;
129 }
130
131 if (iconIndex)
132 {
133 if ((psminfo->dwMask & SMIM_TYPE) != 0)
134 psminfo->dwType = SMIT_STRING;
135 if ((psminfo->dwMask & SMIM_ICON) != 0)
136 psminfo->iIcon = Shell_GetCachedImageIndex(L"shell32.dll", iconIndex, FALSE);
137 if ((psminfo->dwMask & SMIM_FLAGS) != 0)
138 psminfo->dwFlags |= SMIF_ICON;
139 #ifdef TEST_TRACKPOPUPMENU_SUBMENUS
140 if ((psminfo->dwMask & SMIM_FLAGS) != 0)
141 psminfo->dwFlags |= SMIF_TRACKPOPUP;
142 #endif
143 }
144 else
145 {
146 if ((psminfo->dwMask & SMIM_TYPE) != 0)
147 psminfo->dwType = SMIT_SEPARATOR;
148 }
149 return S_OK;
150 }
151
152 HRESULT OnGetSubMenu(LPSMDATA psmd, REFIID iid, void ** pv)
153 {
154 HRESULT hr;
155 int csidl = 0;
156 IShellMenu *pShellMenu;
157
158 #if USE_SYSTEM_MENUBAND
159 hr = CoCreateInstance(CLSID_MenuBand,
160 NULL,
161 CLSCTX_INPROC_SERVER,
162 IID_PPV_ARG(IShellMenu, &pShellMenu));
163 #else
164 hr = CMenuBand_Constructor(IID_PPV_ARG(IShellMenu, &pShellMenu));
165 #endif
166 if (FAILED_UNEXPECTEDLY(hr))
167 return hr;
168
169 hr = pShellMenu->Initialize(this, 0, ANCESTORDEFAULT, SMINIT_VERTICAL);
170
171 switch (psmd->uId)
172 {
173 case IDM_PROGRAMS: csidl = CSIDL_PROGRAMS; break;
174 case IDM_FAVORITES: csidl = CSIDL_FAVORITES; break;
175 case IDM_DOCUMENTS: csidl = CSIDL_RECENT; break;
176 }
177
178 if (csidl)
179 {
180 IShellFolder *psfStartMenu;
181
182 if (csidl == CSIDL_PROGRAMS && m_psfPrograms)
183 {
184 psfStartMenu = m_psfPrograms;
185 }
186 else
187 {
188 LPITEMIDLIST pidlStartMenu;
189 IShellFolder *psfDestop;
190 hr = SHGetFolderLocation(NULL, csidl, 0, 0, &pidlStartMenu);
191 hr = SHGetDesktopFolder(&psfDestop);
192 hr = psfDestop->BindToObject(pidlStartMenu, NULL, IID_PPV_ARG(IShellFolder, &psfStartMenu));
193 }
194
195 hr = pShellMenu->SetShellFolder(psfStartMenu, NULL, NULL, 0);
196 }
197 else
198 {
199 MENUITEMINFO mii;
200 mii.cbSize = sizeof(mii);
201 mii.fMask = MIIM_SUBMENU;
202 if (GetMenuItemInfoW(psmd->hmenu, psmd->uId, FALSE, &mii))
203 {
204 hr = pShellMenu->SetMenu(mii.hSubMenu, NULL, SMSET_BOTTOM);
205 }
206 }
207 return pShellMenu->QueryInterface(iid, pv);
208 }
209
210 HRESULT OnGetContextMenu(LPSMDATA psmd, REFIID iid, void ** pv)
211 {
212 if (psmd->uId == IDM_PROGRAMS ||
213 psmd->uId == IDM_CONTROLPANEL ||
214 psmd->uId == IDM_NETWORKCONNECTIONS ||
215 psmd->uId == IDM_PRINTERSANDFAXES)
216 {
217 //UNIMPLEMENTED
218 }
219
220 return S_FALSE;
221 }
222
223 HRESULT OnGetObject(LPSMDATA psmd, REFIID iid, void ** pv)
224 {
225 if (IsEqualIID(iid, IID_IShellMenu))
226 return OnGetSubMenu(psmd, iid, pv);
227 else if (IsEqualIID(iid, IID_IContextMenu))
228 return OnGetContextMenu(psmd, iid, pv);
229
230 return S_FALSE;
231 }
232
233 HRESULT OnExec(LPSMDATA psmd)
234 {
235 // HACK: Because our ShellExecute can't handle CLSID components in paths, we can't launch the paths using the "open" verb.
236 // FIXME: Change this back to using the path as the filename and the "open" verb, once ShellExecute can handle CLSID path components.
237
238 if (psmd->uId == IDM_CONTROLPANEL)
239 ShellExecuteW(NULL, NULL, L"explorer.exe", L"::{20D04FE0-3AEA-1069-A2D8-08002B30309D}\\::{21EC2020-3AEA-1069-A2DD-08002B30309D}", NULL, SW_SHOWNORMAL);
240 else if (psmd->uId == IDM_NETWORKCONNECTIONS)
241 ShellExecuteW(NULL, NULL, L"explorer.exe", L"::{20D04FE0-3AEA-1069-A2D8-08002B30309D}\\::{21EC2020-3AEA-1069-A2DD-08002B30309D}\\::{7007ACC7-3202-11D1-AAD2-00805FC1270E}", NULL, SW_SHOWNORMAL);
242 else if (psmd->uId == IDM_PRINTERSANDFAXES)
243 ShellExecuteW(NULL, NULL, L"explorer.exe", L"::{20D04FE0-3AEA-1069-A2D8-08002B30309D}\\::{21EC2020-3AEA-1069-A2DD-08002B30309D}\\::{2227A280-3AEA-1069-A2DE-08002B30309D}", NULL, SW_SHOWNORMAL);
244 else
245 PostMessageW(m_hwndTray, WM_COMMAND, psmd->uId, 0);
246
247 return S_OK;
248 }
249
250 public:
251
252 DECLARE_NOT_AGGREGATABLE(CShellMenuCallback)
253 DECLARE_PROTECT_FINAL_CONSTRUCT()
254 BEGIN_COM_MAP(CShellMenuCallback)
255 COM_INTERFACE_ENTRY_IID(IID_IShellMenuCallback, IShellMenuCallback)
256 END_COM_MAP()
257
258 void Initialize(
259 IShellMenu* pShellMenu,
260 IBandSite* pBandSite,
261 IDeskBar* pDeskBar)
262 {
263 m_pShellMenu = pShellMenu;
264 m_pBandSite = pBandSite;
265 m_pDeskBar = pDeskBar;
266 }
267
268 ~CShellMenuCallback()
269 {
270 }
271
272 HRESULT _SetProgramsFolder(IShellFolder * psf, LPITEMIDLIST pidl)
273 {
274 m_psfPrograms = psf;
275 m_pidlPrograms = pidl;
276 return S_OK;
277 }
278
279 HRESULT STDMETHODCALLTYPE CallbackSM(
280 LPSMDATA psmd,
281 UINT uMsg,
282 WPARAM wParam,
283 LPARAM lParam)
284 {
285 switch (uMsg)
286 {
287 case SMC_INITMENU:
288 return OnInitMenu();
289 case SMC_GETINFO:
290 return OnGetInfo(psmd, reinterpret_cast<SMINFO*>(lParam));
291 case SMC_GETOBJECT:
292 return OnGetObject(psmd, *reinterpret_cast<IID *>(wParam), reinterpret_cast<void **>(lParam));
293 case SMC_EXEC:
294 return OnExec(psmd);
295 case SMC_SFEXEC:
296 m_pTrayPriv->Execute(psmd->psf, psmd->pidlItem);
297 break;
298 case 0x10000000: // _FilterPIDL from CMenuSFToolbar
299 if (psmd->psf->CompareIDs(0, psmd->pidlItem, m_pidlPrograms) == 0)
300 return S_OK;
301 return S_FALSE;
302 }
303
304 return S_FALSE;
305 }
306 };
307
308 HRESULT BindToDesktop(LPCITEMIDLIST pidl, IShellFolder ** ppsfResult)
309 {
310 HRESULT hr;
311 CComPtr<IShellFolder> psfDesktop;
312
313 *ppsfResult = NULL;
314
315 hr = SHGetDesktopFolder(&psfDesktop);
316 if (FAILED(hr))
317 return hr;
318
319 hr = psfDesktop->BindToObject(pidl, NULL, IID_PPV_ARG(IShellFolder, ppsfResult));
320
321 return hr;
322 }
323
324 static HRESULT GetMergedFolder(int folder1, int folder2, IShellFolder ** ppsfStartMenu)
325 {
326 HRESULT hr;
327 LPITEMIDLIST pidlUserStartMenu;
328 LPITEMIDLIST pidlCommonStartMenu;
329 CComPtr<IShellFolder> psfUserStartMenu;
330 CComPtr<IShellFolder> psfCommonStartMenu;
331 CComPtr<IAugmentedShellFolder> pasf;
332
333 *ppsfStartMenu = NULL;
334
335 hr = SHGetSpecialFolderLocation(NULL, folder1, &pidlUserStartMenu);
336 if (FAILED(hr))
337 {
338 WARN("Failed to get the USER start menu folder. Trying to run with just the COMMON one.\n");
339
340 hr = SHGetSpecialFolderLocation(NULL, folder2, &pidlCommonStartMenu);
341 if (FAILED_UNEXPECTEDLY(hr))
342 return hr;
343
344 TRACE("COMMON start menu obtained.\n");
345 hr = BindToDesktop(pidlCommonStartMenu, ppsfStartMenu);
346 ILFree(pidlCommonStartMenu);
347 return hr;
348 }
349 #if MERGE_FOLDERS
350 hr = SHGetSpecialFolderLocation(NULL, folder2, &pidlCommonStartMenu);
351 if (FAILED_UNEXPECTEDLY(hr))
352 #else
353 else
354 #endif
355 {
356 WARN("Failed to get the COMMON start menu folder. Will use only the USER contents.\n");
357 hr = BindToDesktop(pidlUserStartMenu, ppsfStartMenu);
358 ILFree(pidlUserStartMenu);
359 return hr;
360 }
361
362 TRACE("Both COMMON and USER statr menu folders obtained, merging them...\n");
363
364 hr = BindToDesktop(pidlUserStartMenu, &psfUserStartMenu);
365 if (FAILED_UNEXPECTEDLY(hr))
366 return hr;
367
368 hr = BindToDesktop(pidlCommonStartMenu, &psfCommonStartMenu);
369 if (FAILED_UNEXPECTEDLY(hr))
370 return hr;
371
372 #if !USE_SYSTEM_MERGED_FOLDERS
373 hr = CMergedFolder_Constructor(IID_PPV_ARG(IAugmentedShellFolder, &pasf));
374 #else
375 hr = CoCreateInstance(CLSID_MergedFolder, NULL, CLSCTX_INPROC_SERVER, IID_PPV_ARG(IAugmentedShellFolder, &pasf));
376 #endif
377 if (FAILED_UNEXPECTEDLY(hr))
378 {
379 *ppsfStartMenu = psfUserStartMenu.Detach();
380 ILFree(pidlCommonStartMenu);
381 ILFree(pidlUserStartMenu);
382 return hr;
383 }
384
385 hr = pasf->AddNameSpace(NULL, psfUserStartMenu, pidlUserStartMenu, 0xFF00);
386 if (FAILED_UNEXPECTEDLY(hr))
387 return hr;
388
389 hr = pasf->AddNameSpace(NULL, psfCommonStartMenu, pidlCommonStartMenu, 0);
390 if (FAILED_UNEXPECTEDLY(hr))
391 return hr;
392
393 hr = pasf->QueryInterface(IID_PPV_ARG(IShellFolder, ppsfStartMenu));
394 pasf.Release();
395
396 ILFree(pidlCommonStartMenu);
397 ILFree(pidlUserStartMenu);
398
399 return hr;
400 }
401
402 static HRESULT GetStartMenuFolder(IShellFolder ** ppsfStartMenu)
403 {
404 return GetMergedFolder(CSIDL_STARTMENU, CSIDL_COMMON_STARTMENU, ppsfStartMenu);
405 }
406
407 static HRESULT GetProgramsFolder(IShellFolder ** ppsfStartMenu)
408 {
409 return GetMergedFolder(CSIDL_PROGRAMS, CSIDL_COMMON_PROGRAMS, ppsfStartMenu);
410 }
411
412 extern "C"
413 HRESULT WINAPI
414 CStartMenu_Constructor(REFIID riid, void **ppv)
415 {
416 CComPtr<IShellMenu> pShellMenu;
417 CComPtr<IBandSite> pBandSite;
418 CComPtr<IDeskBar> pDeskBar;
419
420 HRESULT hr;
421 IShellFolder * psf;
422
423 LPITEMIDLIST pidlProgramsAbsolute;
424 LPITEMIDLIST pidlPrograms;
425 CComPtr<IShellFolder> psfPrograms;
426
427 #if USE_SYSTEM_MENUBAND
428 hr = CoCreateInstance(CLSID_MenuBand,
429 NULL,
430 CLSCTX_INPROC_SERVER,
431 IID_PPV_ARG(IShellMenu, &pShellMenu));
432 #else
433 hr = CMenuBand_Constructor(IID_PPV_ARG(IShellMenu, &pShellMenu));
434 #endif
435 if (FAILED_UNEXPECTEDLY(hr))
436 return hr;
437
438 #if USE_SYSTEM_MENUSITE
439 hr = CoCreateInstance(CLSID_MenuBandSite,
440 NULL,
441 CLSCTX_INPROC_SERVER,
442 IID_PPV_ARG(IBandSite, &pBandSite));
443 #else
444 hr = CMenuSite_Constructor(IID_PPV_ARG(IBandSite, &pBandSite));
445 #endif
446 if (FAILED_UNEXPECTEDLY(hr))
447 return hr;
448
449 #if USE_SYSTEM_MENUDESKBAR
450 hr = CoCreateInstance(CLSID_MenuDeskBar,
451 NULL,
452 CLSCTX_INPROC_SERVER,
453 IID_PPV_ARG(IDeskBar, &pDeskBar));
454 #else
455 hr = CMenuDeskBar_Constructor(IID_PPV_ARG(IDeskBar, &pDeskBar));
456 #endif
457 if (FAILED_UNEXPECTEDLY(hr))
458 return hr;
459
460 CComObject<CShellMenuCallback> *pCallback;
461 hr = CComObject<CShellMenuCallback>::CreateInstance(&pCallback);
462 if (FAILED_UNEXPECTEDLY(hr))
463 return hr;
464 pCallback->AddRef(); // CreateInstance returns object with 0 ref count */
465 pCallback->Initialize(pShellMenu, pBandSite, pDeskBar);
466
467 pShellMenu->Initialize(pCallback, (UINT) -1, 0, SMINIT_TOPLEVEL | SMINIT_VERTICAL);
468 if (FAILED_UNEXPECTEDLY(hr))
469 return hr;
470
471 hr = GetStartMenuFolder(&psf);
472 if (FAILED_UNEXPECTEDLY(hr))
473 return hr;
474
475 /* psf is a merged folder, so now we want to get the pidl of the programs item from the merged folder */
476 {
477 hr = SHGetSpecialFolderLocation(NULL, CSIDL_PROGRAMS, &pidlProgramsAbsolute);
478 if (FAILED(hr))
479 {
480 WARN("USER Programs folder not found.");
481 hr = SHGetSpecialFolderLocation(NULL, CSIDL_COMMON_PROGRAMS, &pidlProgramsAbsolute);
482 if (FAILED_UNEXPECTEDLY(hr))
483 return hr;
484 }
485
486 LPCITEMIDLIST pcidlPrograms;
487 CComPtr<IShellFolder> psfParent;
488 STRRET str;
489 TCHAR szDisplayName[MAX_PATH];
490
491 hr = SHBindToParent(pidlProgramsAbsolute, IID_PPV_ARG(IShellFolder, &psfParent), &pcidlPrograms);
492 if (FAILED(hr))
493 return hr;
494
495 hr = psfParent->GetDisplayNameOf(pcidlPrograms, SHGDN_FORPARSING | SHGDN_INFOLDER, &str);
496 if (FAILED(hr))
497 return hr;
498
499 StrRetToBuf(&str, pcidlPrograms, szDisplayName, _countof(szDisplayName));
500 ILFree(pidlProgramsAbsolute);
501
502 /* We got the display name from the fs folder and we parse it with the merged folder here */
503 hr = psf->ParseDisplayName(NULL, NULL, szDisplayName, NULL, &pidlPrograms, NULL);
504 if (FAILED(hr))
505 return hr;
506 }
507
508 hr = GetProgramsFolder(&psfPrograms);
509 if (FAILED_UNEXPECTEDLY(hr))
510 return hr;
511
512 hr = pCallback->_SetProgramsFolder(psfPrograms, pidlPrograms);
513 if (FAILED_UNEXPECTEDLY(hr))
514 return hr;
515
516 hr = pShellMenu->SetShellFolder(psf, NULL, NULL, 0);
517 if (FAILED_UNEXPECTEDLY(hr))
518 return hr;
519
520 hr = pDeskBar->SetClient(pBandSite);
521 if (FAILED_UNEXPECTEDLY(hr))
522 return hr;
523
524 hr = pBandSite->AddBand(pShellMenu);
525 if (FAILED_UNEXPECTEDLY(hr))
526 return hr;
527
528 return pDeskBar->QueryInterface(riid, ppv);
529 }