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