/* * ReactOS Explorer * * Copyright 2014 Giannis Adamopoulos * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include "shellmenu.h" #include "CMergedFolder.h" WINE_DEFAULT_DEBUG_CHANNEL(CStartMenu); //#define TEST_TRACKPOPUPMENU_SUBMENUS /* NOTE: The following constants *MUST NOT* be changed because they're hardcoded and need to be the exact values in order to get the start menu to work! */ #define IDM_RUN 401 #define IDM_LOGOFF 402 #define IDM_UNDOCKCOMPUTER 410 #define IDM_TASKBARANDSTARTMENU 413 #define IDM_LASTSTARTMENU_SEPARATOR 450 #define IDM_DOCUMENTS 501 #define IDM_HELPANDSUPPORT 503 #define IDM_PROGRAMS 504 #define IDM_CONTROLPANEL 505 #define IDM_SHUTDOWN 506 #define IDM_FAVORITES 507 #define IDM_SETTINGS 508 #define IDM_PRINTERSANDFAXES 510 #define IDM_SEARCH 520 #define IDM_SYNCHRONIZE 553 #define IDM_NETWORKCONNECTIONS 557 #define IDM_DISCONNECT 5000 #define IDM_SECURITY 5001 /* * TODO: * 1. append the start menu contents from all users * 2. implement the context menu for start menu entries (programs, control panel, network connetions, printers) * 3. filter out programs folder from the shell folder part of the start menu * 4. showing the programs start menu is SLOW compared to windows. this needs some investigation */ class CShellMenuCallback : public CComObjectRootEx, public IShellMenuCallback { private: HWND m_hwndTray; CComPtr m_pShellMenu; CComPtr m_pBandSite; CComPtr m_pDeskBar; CComPtr m_pTrayPriv; CComPtr m_psfPrograms; LPITEMIDLIST m_pidlPrograms; HRESULT OnInitMenu() { HMENU hmenu; HRESULT hr; if (m_pTrayPriv.p) return S_OK; hr = IUnknown_GetSite(m_pDeskBar, IID_PPV_ARG(ITrayPriv, &m_pTrayPriv)); if (FAILED_UNEXPECTEDLY(hr)) return hr; hr = IUnknown_GetWindow(m_pTrayPriv, &m_hwndTray); if (FAILED_UNEXPECTEDLY(hr)) return hr; hr = m_pTrayPriv->AppendMenuW(&hmenu); if (FAILED_UNEXPECTEDLY(hr)) return hr; hr = m_pShellMenu->SetMenu(hmenu, NULL, SMSET_BOTTOM); if (FAILED_UNEXPECTEDLY(hr)) return hr; return hr; } HRESULT OnGetInfo(LPSMDATA psmd, SMINFO *psminfo) { int iconIndex = 0; switch (psmd->uId) { // Smaller "24x24" icons used for the start menu // The bitmaps are still 32x32, but the image is centered case IDM_FAVORITES: iconIndex = -322; break; case IDM_SEARCH: iconIndex = -323; break; case IDM_HELPANDSUPPORT: iconIndex = -324; break; case IDM_LOGOFF: iconIndex = -325; break; case IDM_PROGRAMS: iconIndex = -326; break; case IDM_DOCUMENTS: iconIndex = -327; break; case IDM_RUN: iconIndex = -328; break; case IDM_SHUTDOWN: iconIndex = -329; break; case IDM_SETTINGS: iconIndex = -330; break; case IDM_CONTROLPANEL: iconIndex = -22; break; case IDM_NETWORKCONNECTIONS: iconIndex = -257; break; case IDM_PRINTERSANDFAXES: iconIndex = -138; break; case IDM_TASKBARANDSTARTMENU: iconIndex = -40; break; //case IDM_SECURITY: iconIndex = -21; break; //case IDM_SYNCHRONIZE: iconIndex = -21; break; //case IDM_DISCONNECT: iconIndex = -21; break; //case IDM_UNDOCKCOMPUTER: iconIndex = -21; break; default: return S_FALSE; } if (iconIndex) { if ((psminfo->dwMask & SMIM_TYPE) != 0) psminfo->dwType = SMIT_STRING; if ((psminfo->dwMask & SMIM_ICON) != 0) psminfo->iIcon = Shell_GetCachedImageIndex(L"shell32.dll", iconIndex, FALSE); if ((psminfo->dwMask & SMIM_FLAGS) != 0) psminfo->dwFlags |= SMIF_ICON; #ifdef TEST_TRACKPOPUPMENU_SUBMENUS if ((psminfo->dwMask & SMIM_FLAGS) != 0) psminfo->dwFlags |= SMIF_TRACKPOPUP; #endif } else { if ((psminfo->dwMask & SMIM_TYPE) != 0) psminfo->dwType = SMIT_SEPARATOR; } return S_OK; } HRESULT OnGetSubMenu(LPSMDATA psmd, REFIID iid, void ** pv) { HRESULT hr; int csidl = 0; IShellMenu *pShellMenu; hr = CMenuBand_CreateInstance(IID_PPV_ARG(IShellMenu, &pShellMenu)); if (FAILED_UNEXPECTEDLY(hr)) return hr; hr = pShellMenu->Initialize(this, 0, ANCESTORDEFAULT, SMINIT_VERTICAL); if (FAILED_UNEXPECTEDLY(hr)) return hr; switch (psmd->uId) { case IDM_PROGRAMS: csidl = CSIDL_PROGRAMS; break; case IDM_FAVORITES: csidl = CSIDL_FAVORITES; break; case IDM_DOCUMENTS: csidl = CSIDL_RECENT; break; } if (csidl) { IShellFolder *psfStartMenu; if (csidl == CSIDL_PROGRAMS && m_psfPrograms) { psfStartMenu = m_psfPrograms; } else { LPITEMIDLIST pidlStartMenu; IShellFolder *psfDestop; hr = SHGetFolderLocation(NULL, csidl, 0, 0, &pidlStartMenu); if (FAILED_UNEXPECTEDLY(hr)) return hr; hr = SHGetDesktopFolder(&psfDestop); if (FAILED_UNEXPECTEDLY(hr)) return hr; hr = psfDestop->BindToObject(pidlStartMenu, NULL, IID_PPV_ARG(IShellFolder, &psfStartMenu)); if (FAILED_UNEXPECTEDLY(hr)) return hr; } hr = pShellMenu->SetShellFolder(psfStartMenu, NULL, NULL, 0); if (FAILED_UNEXPECTEDLY(hr)) return hr; } else { MENUITEMINFO mii; mii.cbSize = sizeof(mii); mii.fMask = MIIM_SUBMENU; if (GetMenuItemInfoW(psmd->hmenu, psmd->uId, FALSE, &mii)) { hr = pShellMenu->SetMenu(mii.hSubMenu, NULL, SMSET_BOTTOM); if (FAILED_UNEXPECTEDLY(hr)) return hr; } } return pShellMenu->QueryInterface(iid, pv); } HRESULT OnGetContextMenu(LPSMDATA psmd, REFIID iid, void ** pv) { if (psmd->uId == IDM_PROGRAMS || psmd->uId == IDM_CONTROLPANEL || psmd->uId == IDM_NETWORKCONNECTIONS || psmd->uId == IDM_PRINTERSANDFAXES) { //UNIMPLEMENTED } return S_FALSE; } HRESULT OnGetObject(LPSMDATA psmd, REFIID iid, void ** pv) { if (IsEqualIID(iid, IID_IShellMenu)) return OnGetSubMenu(psmd, iid, pv); else if (IsEqualIID(iid, IID_IContextMenu)) return OnGetContextMenu(psmd, iid, pv); return S_FALSE; } HRESULT OnExec(LPSMDATA psmd) { // HACK: Because our ShellExecute can't handle CLSID components in paths, we can't launch the paths using the "open" verb. // FIXME: Change this back to using the path as the filename and the "open" verb, once ShellExecute can handle CLSID path components. if (psmd->uId == IDM_CONTROLPANEL) ShellExecuteW(NULL, NULL, L"explorer.exe", L"::{20D04FE0-3AEA-1069-A2D8-08002B30309D}\\::{21EC2020-3AEA-1069-A2DD-08002B30309D}", NULL, SW_SHOWNORMAL); else if (psmd->uId == IDM_NETWORKCONNECTIONS) 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); else if (psmd->uId == IDM_PRINTERSANDFAXES) 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); else PostMessageW(m_hwndTray, WM_COMMAND, psmd->uId, 0); return S_OK; } public: DECLARE_NOT_AGGREGATABLE(CShellMenuCallback) DECLARE_PROTECT_FINAL_CONSTRUCT() BEGIN_COM_MAP(CShellMenuCallback) COM_INTERFACE_ENTRY_IID(IID_IShellMenuCallback, IShellMenuCallback) END_COM_MAP() void Initialize( IShellMenu* pShellMenu, IBandSite* pBandSite, IDeskBar* pDeskBar) { m_pShellMenu = pShellMenu; m_pBandSite = pBandSite; m_pDeskBar = pDeskBar; } ~CShellMenuCallback() { } HRESULT _SetProgramsFolder(IShellFolder * psf, LPITEMIDLIST pidl) { m_psfPrograms = psf; m_pidlPrograms = pidl; return S_OK; } HRESULT STDMETHODCALLTYPE CallbackSM( LPSMDATA psmd, UINT uMsg, WPARAM wParam, LPARAM lParam) { switch (uMsg) { case SMC_INITMENU: return OnInitMenu(); case SMC_GETINFO: return OnGetInfo(psmd, reinterpret_cast(lParam)); case SMC_GETOBJECT: return OnGetObject(psmd, *reinterpret_cast(wParam), reinterpret_cast(lParam)); case SMC_EXEC: return OnExec(psmd); case SMC_SFEXEC: m_pTrayPriv->Execute(psmd->psf, psmd->pidlItem); break; case 0x10000000: // _FilterPIDL from CMenuSFToolbar if (psmd->psf->CompareIDs(0, psmd->pidlItem, m_pidlPrograms) == 0) return S_OK; return S_FALSE; } return S_FALSE; } }; HRESULT BindToDesktop(LPCITEMIDLIST pidl, IShellFolder ** ppsfResult) { HRESULT hr; CComPtr psfDesktop; *ppsfResult = NULL; hr = SHGetDesktopFolder(&psfDesktop); if (FAILED(hr)) return hr; hr = psfDesktop->BindToObject(pidl, NULL, IID_PPV_ARG(IShellFolder, ppsfResult)); return hr; } static HRESULT GetMergedFolder(int folder1, int folder2, IShellFolder ** ppsfStartMenu) { HRESULT hr; LPITEMIDLIST pidlUserStartMenu; LPITEMIDLIST pidlCommonStartMenu; CComPtr psfUserStartMenu; CComPtr psfCommonStartMenu; CComPtr pasf; *ppsfStartMenu = NULL; hr = SHGetSpecialFolderLocation(NULL, folder1, &pidlUserStartMenu); if (FAILED(hr)) { WARN("Failed to get the USER start menu folder. Trying to run with just the COMMON one.\n"); hr = SHGetSpecialFolderLocation(NULL, folder2, &pidlCommonStartMenu); if (FAILED_UNEXPECTEDLY(hr)) return hr; TRACE("COMMON start menu obtained.\n"); hr = BindToDesktop(pidlCommonStartMenu, ppsfStartMenu); ILFree(pidlCommonStartMenu); return hr; } #if MERGE_FOLDERS hr = SHGetSpecialFolderLocation(NULL, folder2, &pidlCommonStartMenu); if (FAILED_UNEXPECTEDLY(hr)) #else else #endif { WARN("Failed to get the COMMON start menu folder. Will use only the USER contents.\n"); hr = BindToDesktop(pidlUserStartMenu, ppsfStartMenu); ILFree(pidlUserStartMenu); return hr; } TRACE("Both COMMON and USER statr menu folders obtained, merging them...\n"); hr = BindToDesktop(pidlUserStartMenu, &psfUserStartMenu); if (FAILED_UNEXPECTEDLY(hr)) return hr; hr = BindToDesktop(pidlCommonStartMenu, &psfCommonStartMenu); if (FAILED_UNEXPECTEDLY(hr)) return hr; hr = CMergedFolder_CreateInstance(IID_PPV_ARG(IAugmentedShellFolder, &pasf)); if (FAILED_UNEXPECTEDLY(hr)) { *ppsfStartMenu = psfUserStartMenu.Detach(); ILFree(pidlCommonStartMenu); ILFree(pidlUserStartMenu); return hr; } hr = pasf->AddNameSpace(NULL, psfUserStartMenu, pidlUserStartMenu, 0xFF00); if (FAILED_UNEXPECTEDLY(hr)) return hr; hr = pasf->AddNameSpace(NULL, psfCommonStartMenu, pidlCommonStartMenu, 0); if (FAILED_UNEXPECTEDLY(hr)) return hr; hr = pasf->QueryInterface(IID_PPV_ARG(IShellFolder, ppsfStartMenu)); pasf.Release(); ILFree(pidlCommonStartMenu); ILFree(pidlUserStartMenu); return hr; } static HRESULT GetStartMenuFolder(IShellFolder ** ppsfStartMenu) { return GetMergedFolder(CSIDL_STARTMENU, CSIDL_COMMON_STARTMENU, ppsfStartMenu); } static HRESULT GetProgramsFolder(IShellFolder ** ppsfStartMenu) { return GetMergedFolder(CSIDL_PROGRAMS, CSIDL_COMMON_PROGRAMS, ppsfStartMenu); } extern "C" HRESULT WINAPI RSHELL_CStartMenu_CreateInstance(REFIID riid, void **ppv) { CComPtr pShellMenu; CComPtr pBandSite; CComPtr pDeskBar; HRESULT hr; IShellFolder * psf; LPITEMIDLIST pidlProgramsAbsolute; LPITEMIDLIST pidlPrograms; CComPtr psfPrograms; hr = CMenuBand_CreateInstance(IID_PPV_ARG(IShellMenu, &pShellMenu)); if (FAILED_UNEXPECTEDLY(hr)) return hr; hr = CMenuSite_CreateInstance(IID_PPV_ARG(IBandSite, &pBandSite)); if (FAILED_UNEXPECTEDLY(hr)) return hr; hr = CMenuDeskBar_CreateInstance(IID_PPV_ARG(IDeskBar, &pDeskBar)); if (FAILED_UNEXPECTEDLY(hr)) return hr; CComObject *pCallback; hr = CComObject::CreateInstance(&pCallback); if (FAILED_UNEXPECTEDLY(hr)) return hr; pCallback->AddRef(); // CreateInstance returns object with 0 ref count */ pCallback->Initialize(pShellMenu, pBandSite, pDeskBar); pShellMenu->Initialize(pCallback, (UINT) -1, 0, SMINIT_TOPLEVEL | SMINIT_VERTICAL); if (FAILED_UNEXPECTEDLY(hr)) return hr; hr = GetStartMenuFolder(&psf); if (FAILED_UNEXPECTEDLY(hr)) return hr; /* psf is a merged folder, so now we want to get the pidl of the programs item from the merged folder */ { hr = SHGetSpecialFolderLocation(NULL, CSIDL_PROGRAMS, &pidlProgramsAbsolute); if (FAILED_UNEXPECTEDLY(hr)) { WARN("USER Programs folder not found."); hr = SHGetSpecialFolderLocation(NULL, CSIDL_COMMON_PROGRAMS, &pidlProgramsAbsolute); if (FAILED_UNEXPECTEDLY(hr)) return hr; } LPCITEMIDLIST pcidlPrograms; CComPtr psfParent; STRRET str; TCHAR szDisplayName[MAX_PATH]; hr = SHBindToParent(pidlProgramsAbsolute, IID_PPV_ARG(IShellFolder, &psfParent), &pcidlPrograms); if (FAILED_UNEXPECTEDLY(hr)) return hr; hr = psfParent->GetDisplayNameOf(pcidlPrograms, SHGDN_FORPARSING | SHGDN_INFOLDER, &str); if (FAILED_UNEXPECTEDLY(hr)) return hr; StrRetToBuf(&str, pcidlPrograms, szDisplayName, _countof(szDisplayName)); ILFree(pidlProgramsAbsolute); /* We got the display name from the fs folder and we parse it with the merged folder here */ hr = psf->ParseDisplayName(NULL, NULL, szDisplayName, NULL, &pidlPrograms, NULL); if (FAILED_UNEXPECTEDLY(hr)) return hr; } hr = GetProgramsFolder(&psfPrograms); if (FAILED_UNEXPECTEDLY(hr)) return hr; hr = pCallback->_SetProgramsFolder(psfPrograms, pidlPrograms); if (FAILED_UNEXPECTEDLY(hr)) return hr; hr = pShellMenu->SetShellFolder(psf, NULL, NULL, 0); if (FAILED_UNEXPECTEDLY(hr)) return hr; hr = pDeskBar->SetClient(pBandSite); if (FAILED_UNEXPECTEDLY(hr)) return hr; hr = pBandSite->AddBand(pShellMenu); if (FAILED_UNEXPECTEDLY(hr)) return hr; return pDeskBar->QueryInterface(riid, ppv); }