#include "stdafx.h" #pragma hdrstop #include #include #include "apithk.h" #include "resource.h" #include #include #include #define REGSTR_EXPLORER_WINUPDATE REGSTR_PATH_EXPLORER TEXT("\\WindowsUpdate") #define IDM_TOPLEVELSTARTMENU 0 // StartMenuInit Flags #define STARTMENU_DISPLAYEDBEFORE 0x00000001 #define STARTMENU_CHEVRONCLICKED 0x00000002 // New item counts for UEM stuff #define UEM_NEWITEMCOUNT 2 // Menuband per pane user data typedef struct { BITBOOL _fInitialized; } SMUSERDATA; // for g_hdpaDarwinAds EXTERN_C CRITICAL_SECTION g_csDarwinAds = {0}; #define ENTERCRITICAL_DARWINADS EnterCriticalSection(&g_csDarwinAds) #define LEAVECRITICAL_DARWINADS LeaveCriticalSection(&g_csDarwinAds) // The threading concern with this variable is create/delete/add/remove. We will only remove an item // and delete the hdpa on the main thread. We will however add and create on both threads. // We need to serialize access to the dpa, so we're going to grab the shell crisec. HDPA g_hdpaDarwinAds = NULL; class CDarwinAd { public: LPITEMIDLIST _pidl; LPTSTR _pszDescriptor; LPTSTR _pszLocalPath; INSTALLSTATE _state; CDarwinAd(LPITEMIDLIST pidl, LPTSTR psz) { // I take ownership of this pidl _pidl = pidl; Str_SetPtr(&_pszDescriptor, psz); } void CheckInstalled() { TCHAR szProduct[GUIDSTR_MAX]; TCHAR szFeature[MAX_FEATURE_CHARS+1]; TCHAR szComponent[GUIDSTR_MAX]; if (MsiDecomposeDescriptor(_pszDescriptor, szProduct, szFeature, szComponent, NULL) == ERROR_SUCCESS) { _state = MsiQueryFeatureState(szProduct, szFeature); } else { _state = INSTALLSTATE_INVALIDARG; } // Note: Cannot use ParseDarwinID since that bumps the usage count // for the app and we're not running the app, just looking at it. // Also because ParseDarwinID tries to install the app (eek!) // // Must ignore INSTALLSTATE_SOURCE because MsiGetComponentPath will // try to install the app even though we're just querying... TCHAR szCommand[MAX_PATH]; DWORD cch = ARRAYSIZE(szCommand); if (_state == INSTALLSTATE_LOCAL && MsiGetComponentPath(szProduct, szComponent, szCommand, &cch) == _state) { PathUnquoteSpaces(szCommand); Str_SetPtr(&_pszLocalPath, szCommand); } else { Str_SetPtr(&_pszLocalPath, NULL); } } BOOL IsAd() { return _state == INSTALLSTATE_ADVERTISED; } ~CDarwinAd() { ILFree(_pidl); Str_SetPtr(&_pszDescriptor, NULL); Str_SetPtr(&_pszLocalPath, NULL); } }; int GetDarwinIndex(LPCITEMIDLIST pidlFull, CDarwinAd** ppda); HRESULT GetMyPicsDisplayName(LPTSTR pszBuffer, UINT cchBuffer) { LPITEMIDLIST pidlMyPics = SHCloneSpecialIDList(NULL, CSIDL_MYPICTURES, FALSE); if (pidlMyPics) { HRESULT hRet = SHGetNameAndFlags(pidlMyPics, SHGDN_NORMAL, pszBuffer, cchBuffer, NULL); ILFree(pidlMyPics); return hRet; } return E_FAIL; } BOOL AreIntelliMenusEnabled() { DWORD dwRest = SHRestricted(REST_INTELLIMENUS); if (dwRest != RESTOPT_INTELLIMENUS_USER) return (dwRest == RESTOPT_INTELLIMENUS_ENABLED); return SHRegGetBoolUSValue(REGSTR_EXPLORER_ADVANCED, TEXT("IntelliMenus"), FALSE, TRUE); // Don't ignore HKCU, Enable Menus by default } BOOL FeatureEnabled(LPTSTR pszFeature) { return SHRegGetBoolUSValue(REGSTR_EXPLORER_ADVANCED, pszFeature, FALSE, // Don't ignore HKCU FALSE); // Disable this cool feature. } // Since we can be presented with an Augmented shellfolder and we need a Full pidl, // we have been given the responsibility to unwrap it for perf reasons. LPITEMIDLIST FullPidlFromSMData(LPSMDATA psmd) { LPITEMIDLIST pidlItem; LPITEMIDLIST pidlFolder = NULL; LPITEMIDLIST pidlFull = NULL; IAugmentedShellFolder2* pasf2; if (SUCCEEDED(psmd->psf->QueryInterface(IID_PPV_ARG(IAugmentedShellFolder2, &pasf2)))) { if (SUCCEEDED(pasf2->UnWrapIDList(psmd->pidlItem, 1, NULL, &pidlFolder, &pidlItem, NULL))) { pidlFull = ILCombine(pidlFolder, pidlItem); ILFree(pidlFolder); ILFree(pidlItem); } pasf2->Release(); } if (!pidlFolder) { pidlFull = ILCombine(psmd->pidlFolder, psmd->pidlItem); } return pidlFull; } // // Determine whether a namespace pidl in a merged shellfolder came // from the specified object GUID. // BOOL IsMergedFolderGUID(IShellFolder *psf, LPCITEMIDLIST pidl, REFGUID rguid) { IAugmentedShellFolder* pasf; BOOL fMatch = FALSE; if (SUCCEEDED(psf->QueryInterface(IID_PPV_ARG(IAugmentedShellFolder, &pasf)))) { GUID guid; if (SUCCEEDED(pasf->GetNameSpaceID(pidl, &guid))) { fMatch = IsEqualGUID(guid, rguid); } pasf->Release(); } return fMatch; } STDMETHODIMP_(int) s_DarwinAdsDestroyCallback(LPVOID pData1, LPVOID pData2) { CDarwinAd* pda = (CDarwinAd*)pData1; if (pda) delete pda; return TRUE; } // SHRegisterDarwinLink takes ownership of the pidl BOOL SHRegisterDarwinLink(LPITEMIDLIST pidlFull, LPWSTR pszDarwinID, BOOL fUpdate) { BOOL fRetVal = FALSE; ENTERCRITICAL_DARWINADS; if (pidlFull) { CDarwinAd *pda = NULL; if (GetDarwinIndex(pidlFull, &pda) != -1 && pda) { // We already know about this link; don't need to add it fRetVal = TRUE; } else { pda = new CDarwinAd(pidlFull, pszDarwinID); if (pda) { pidlFull = NULL; // take ownership // Do we have a global cache? if (g_hdpaDarwinAds == NULL) { // No; This is either the first time this is called, or we // failed the last time. g_hdpaDarwinAds = DPA_Create(5); } if (g_hdpaDarwinAds) { // DPA_AppendPtr returns the zero based index it inserted it at. if(DPA_AppendPtr(g_hdpaDarwinAds, (void*)pda) >= 0) { fRetVal = TRUE; } } } } if (!fRetVal) { // if we failed to create a dpa, delete this. delete pda; } else if (fUpdate) { // update the entry if requested pda->CheckInstalled(); } ILFree(pidlFull); } else if (!pszDarwinID) { // NULL, NULL means "destroy darwin info, we're shutting down" HDPA hdpa = g_hdpaDarwinAds; g_hdpaDarwinAds = NULL; if (hdpa) DPA_DestroyCallback(hdpa, s_DarwinAdsDestroyCallback, NULL); } LEAVECRITICAL_DARWINADS; return fRetVal; } BOOL ProcessDarwinAd(IShellLinkDataList* psldl, LPCITEMIDLIST pidlFull) { // This function does not check for the existance of a member before adding it, // so it is entirely possible for there to be duplicates in the list.... BOOL fIsLoaded = FALSE; BOOL fFreesldl = FALSE; BOOL fRetVal = FALSE; if (!psldl) { // We will detect failure of this at use time. if (FAILED(CoCreateInstance(CLSID_ShellLink, NULL, CLSCTX_INPROC_SERVER, IID_PPV_ARG(IShellLinkDataList, &psldl)))) { return FALSE; } fFreesldl = TRUE; IPersistFile* ppf; OLECHAR sz[MAX_PATH]; if (SHGetPathFromIDListW(pidlFull, sz)) { if (SUCCEEDED(psldl->QueryInterface(IID_PPV_ARG(IPersistFile, &ppf)))) { // shelllink's job to load correctly. if (SUCCEEDED(ppf->Load(sz, 0))) { fIsLoaded = TRUE; } ppf->Release(); } } } else fIsLoaded = TRUE; CDarwinAd* pda = NULL; if (fIsLoaded) { EXP_DARWIN_LINK* pexpDarwin; if (SUCCEEDED(psldl->CopyDataBlock(EXP_DARWIN_ID_SIG, (void**)&pexpDarwin))) { fRetVal = SHRegisterDarwinLink(ILClone(pidlFull), pexpDarwin->szwDarwinID, TRUE); LocalFree(pexpDarwin); } } if (fFreesldl) psldl->Release(); return fRetVal; } // This routine creates the IShellFolder and pidl for one of the many // merged folders on the Start Menu / Start Panel. typedef struct { UINT csidl; UINT uANSFlags; // Flags for AddNameSpace LPCGUID pguidObj; // optional object tag } MERGEDFOLDERINFO, *LPMERGEDFOLDERINFO; typedef const MERGEDFOLDERINFO *LPCMERGEDFOLDERINFO; HRESULT GetMergedFolder(IShellFolder **ppsf, LPITEMIDLIST *ppidl, LPCMERGEDFOLDERINFO rgmfi, UINT cmfi) { *ppidl = NULL; *ppsf = NULL; IShellFolder2 *psf; IAugmentedShellFolder2 *pasf; HRESULT hr = CoCreateInstance(CLSID_MergedFolder, NULL, CLSCTX_INPROC_SERVER, IID_PPV_ARG(IAugmentedShellFolder2, &pasf)); for (UINT imfi = 0; SUCCEEDED(hr) && imfi < cmfi; imfi++) { // If this is a common group and common groups are restricted, then // skip this item if ((rgmfi[imfi].uANSFlags & ASFF_COMMON) && SHRestricted(REST_NOCOMMONGROUPS)) { continue; } psf = NULL; // in/out param below hr = SHCacheTrackingFolder(MAKEINTIDLIST(rgmfi[imfi].csidl), rgmfi[imfi].csidl, &psf); if (SUCCEEDED(hr)) { // If this is a Start Menu folder, then apply the // "do not enumerate subfolders" restriction if the policy says so. // In which case, we cannot use the tracking folder cache. // (Perf note: We compare pointers directly.) if (rgmfi[imfi].pguidObj == &CLSID_StartMenu) { if (SHRestricted(REST_NOSTARTMENUSUBFOLDERS)) { ISetFolderEnumRestriction *prest; if (SUCCEEDED(psf->QueryInterface(IID_PPV_ARG(ISetFolderEnumRestriction, &prest)))) { prest->SetEnumRestriction(0, SHCONTF_FOLDERS); // disallow subfolders prest->Release(); } } } else { // If this assert fires, then our perf optimization above failed. ASSERT(rgmfi[imfi].pguidObj == NULL || !IsEqualGUID(*rgmfi[imfi].pguidObj, CLSID_StartMenu)); } hr = pasf->AddNameSpace(rgmfi[imfi].pguidObj, psf, NULL, rgmfi[imfi].uANSFlags); if (SUCCEEDED(hr)) { if (rgmfi[imfi].uANSFlags & ASFF_DEFNAMESPACE_DISPLAYNAME) { // If this assert fires, it means somebody marked two // folders as ASFF_DEFNAMESPACE_DISPLAYNAME, which is // illegal (you can have only one default) ASSERT(*ppidl == NULL); hr = SHGetIDListFromUnk(psf, ppidl); // copy out the pidl for this guy } } psf->Release(); } } if (SUCCEEDED(hr)) *ppsf = pasf; // copy out the ref else ATOMICRELEASE(pasf); return hr; } HRESULT CreateMergedFolderHelper(LPCMERGEDFOLDERINFO rgmfi, UINT cmfi, REFIID riid, void **ppv) { IShellFolder *psf; LPITEMIDLIST pidl; HRESULT hr = GetMergedFolder(&psf, &pidl, rgmfi, cmfi); if (SUCCEEDED(hr)) { hr = psf->QueryInterface(riid, ppv); if (SUCCEEDED(hr)) { IPersistPropertyBag *pppb; if (SUCCEEDED(psf->QueryInterface(IID_PPV_ARG(IPersistPropertyBag, &pppb)))) { IPropertyBag *ppb; if (SUCCEEDED(SHCreatePropertyBagOnMemory(STGM_READWRITE, IID_PPV_ARG(IPropertyBag, &ppb)))) { // these merged folders have to be told to use new changenotification SHPropertyBag_WriteBOOL(ppb, L"MergedFolder\\ShellView", TRUE); pppb->Load(ppb, NULL); ppb->Release(); } pppb->Release(); } } psf->Release(); ILFree(pidl); } return hr; } const MERGEDFOLDERINFO c_rgmfiStartMenu[] = { { CSIDL_STARTMENU | CSIDL_FLAG_CREATE, ASFF_DEFNAMESPACE_ALL, &CLSID_StartMenu }, { CSIDL_COMMON_STARTMENU, ASFF_COMMON, &CLSID_StartMenu }, }; const MERGEDFOLDERINFO c_rgmfiProgramsFolder[] = { { CSIDL_PROGRAMS | CSIDL_FLAG_CREATE, ASFF_DEFNAMESPACE_ALL, NULL }, { CSIDL_COMMON_PROGRAMS, ASFF_COMMON, NULL }, }; // // On the Start Panel, we want the fast items to sort above the Programs, // so we mark the Programs folders as ASFF_SORTDOWN so they go to the bottom. // We also list the Fast Items first so SMSET_SEPARATEMERGEFOLDER picks // them off properly. And we only want to let Start Menu merge with // Common Start Menu (and Programs with Common Programs) so pass // ASFF_MERGESAMEGUID. const MERGEDFOLDERINFO c_rgmfiProgramsFolderAndFastItems[] = { { CSIDL_STARTMENU | CSIDL_FLAG_CREATE, ASFF_DEFAULT | ASFF_MERGESAMEGUID, &CLSID_StartMenu}, { CSIDL_COMMON_STARTMENU, ASFF_COMMON | ASFF_MERGESAMEGUID, &CLSID_StartMenu}, { CSIDL_PROGRAMS | CSIDL_FLAG_CREATE, ASFF_DEFNAMESPACE_ALL | ASFF_MERGESAMEGUID | ASFF_SORTDOWN, NULL }, { CSIDL_COMMON_PROGRAMS, ASFF_COMMON | ASFF_MERGESAMEGUID | ASFF_SORTDOWN, NULL }, }; STDAPI CStartMenuFolder_CreateInstance(IUnknown *punkOuter, REFIID riid, void **ppv) { return CreateMergedFolderHelper(c_rgmfiStartMenu, ARRAYSIZE(c_rgmfiStartMenu), riid, ppv); } STDAPI CProgramsFolder_CreateInstance(IUnknown *punkOuter, REFIID riid, void **ppv) { return CreateMergedFolderHelper(c_rgmfiProgramsFolder, ARRAYSIZE(c_rgmfiProgramsFolder), riid, ppv); } HRESULT GetFilesystemInfo(IShellFolder* psf, LPITEMIDLIST* ppidlRoot, int* pcsidl) { ASSERT(psf); IPersistFolder3* ppf; HRESULT hr = E_FAIL; *pcsidl = 0; *ppidlRoot = 0; if (SUCCEEDED(psf->QueryInterface(IID_PPV_ARG(IPersistFolder3, &ppf)))) { PERSIST_FOLDER_TARGET_INFO pfti = {0}; if (SUCCEEDED(ppf->GetFolderTargetInfo(&pfti))) { *pcsidl = pfti.csidl; if (-1 != pfti.csidl) hr = S_OK; ILFree(pfti.pidlTargetFolder); } if (SUCCEEDED(hr)) hr = ppf->GetCurFolder(ppidlRoot); ppf->Release(); } return hr; } HRESULT ExecStaticStartMenuItem(int idCmd, BOOL fAllUsers, BOOL fOpen) { int csidl = -1; HRESULT hr = E_OUTOFMEMORY; SHELLEXECUTEINFO shei = {0}; switch (idCmd) { case IDM_PROGRAMS: csidl = fAllUsers ? CSIDL_COMMON_PROGRAMS : CSIDL_PROGRAMS; break; case IDM_FAVORITES: csidl = CSIDL_FAVORITES; break; case IDM_MYDOCUMENTS: csidl = CSIDL_PERSONAL; break; case IDM_MYPICTURES: csidl = CSIDL_MYPICTURES; break; case IDM_CONTROLS: csidl = CSIDL_CONTROLS; break; case IDM_PRINTERS: csidl = CSIDL_PRINTERS; break; case IDM_NETCONNECT: csidl = CSIDL_CONNECTIONS; break; default: return E_FAIL; } if (csidl != -1) { SHGetFolderLocation(NULL, csidl, NULL, 0, (LPITEMIDLIST*)&shei.lpIDList); } if (shei.lpIDList) { shei.cbSize = sizeof(shei); shei.fMask = SEE_MASK_IDLIST; shei.nShow = SW_SHOWNORMAL; shei.lpVerb = fOpen ? TEXT("open") : TEXT("explore"); hr = ShellExecuteEx(&shei) ? S_OK: E_FAIL; // only opening and exploring special folder pidl ILFree((LPITEMIDLIST)shei.lpIDList); } return hr; } // // Base class for Classic and Personal start menus. // class CStartMenuCallbackBase : public IShellMenuCallback, public CObjectWithSite { public: // *** IUnknown methods *** STDMETHODIMP QueryInterface (REFIID riid, void ** ppvObj); STDMETHODIMP_(ULONG) AddRef(); STDMETHODIMP_(ULONG) Release(); // derived class is expected to implement IShellMenuCallback // IObjectWithSite inherited from CObjectWithSite protected: CStartMenuCallbackBase(BOOL fIsStartPanel = FALSE); ~CStartMenuCallbackBase(); void _InitializePrograms(); HRESULT _FilterPidl(UINT uParent, IShellFolder* psf, LPCITEMIDLIST pidl); HRESULT _Promote(LPSMDATA psmd, DWORD dwFlags); BOOL _IsTopLevelStartMenu(UINT uParent, IShellFolder *psf, LPCITEMIDLIST pidl); HRESULT _HandleNew(LPSMDATA psmd); HRESULT _GetSFInfo(SMDATA* psmd, SMINFO* psminfo); HRESULT _ProcessChangeNotify(SMDATA* psmd, LONG lEvent, LPCITEMIDLIST pidl1, LPCITEMIDLIST pidl2); HRESULT InitializeProgramsShellMenu(IShellMenu* psm); virtual DWORD _GetDemote(SMDATA* psmd) { return 0; } BOOL _IsDarwinAdvertisement(LPCITEMIDLIST pidlFull); void _RefreshSettings(); protected: int _cRef; DEBUG_CODE( DWORD _dwThreadID; ) // Cache the thread of the object LPTSTR _pszPrograms; LPTSTR _pszWindowsUpdate; LPTSTR _pszConfigurePrograms; LPTSTR _pszAdminTools; ITrayPriv2* _ptp2; BOOL _fExpandoMenus; BOOL _fShowAdminTools; BOOL _fIsStartPanel; BOOL _fInitPrograms; }; // IShellMenuCallback implementation class CStartMenuCallback : public CStartMenuCallbackBase { public: // *** IUnknown methods *** inherited from CStartMenuBase // *** IShellMenuCallback methods *** STDMETHODIMP CallbackSM(LPSMDATA psmd, UINT uMsg, WPARAM wParam, LPARAM lParam); // *** IObjectWithSite methods *** (overriding CObjectWithSite) STDMETHODIMP SetSite(IUnknown* punk); STDMETHODIMP GetSite(REFIID riid, void** ppvOut); CStartMenuCallback(); private: virtual ~CStartMenuCallback(); IContextMenu* _pcmFind; ITrayPriv* _ptp; IUnknown* _punkSite; IOleCommandTarget* _poct; BITBOOL _fAddOpenFolder: 1; BITBOOL _fCascadeMyDocuments: 1; BITBOOL _fCascadePrinters: 1; BITBOOL _fCascadeControlPanel: 1; BITBOOL _fFindMenuInvalid: 1; BITBOOL _fCascadeNetConnections: 1; BITBOOL _fShowInfoTip: 1; BITBOOL _fInitedShowTopLevelStartMenu: 1; BITBOOL _fCascadeMyPictures: 1; BITBOOL _fHasMyDocuments: 1; BITBOOL _fHasMyPictures: 1; TCHAR _szFindMnemonic[2]; HWND _hwnd; IMruDataList * _pmruRecent; DWORD _cRecentDocs; DWORD _dwFlags; DWORD _dwChevronCount; HRESULT _ExecHmenuItem(LPSMDATA psmdata); HRESULT _Init(SMDATA* psmdata); HRESULT _Create(SMDATA* psmdata, void** pvUserData); HRESULT _Destroy(SMDATA* psmdata); HRESULT _GetHmenuInfo(SMDATA* psmd, SMINFO*sminfo); HRESULT _GetObject(LPSMDATA psmd, REFIID riid, void** ppvObj); HRESULT _CheckRestricted(DWORD dwRestrict, BOOL* fRestricted); HRESULT _FilterRecentPidl(IShellFolder* psf, LPCITEMIDLIST pidl); HRESULT _Demote(LPSMDATA psmd); HRESULT _GetTip(LPWSTR pstrTitle, LPWSTR pstrTip); DWORD _GetDemote(SMDATA* psmd); HRESULT _HandleAccelerator(TCHAR ch, SMDATA* psmdata); HRESULT _GetDefaultIcon(LPWSTR psz, int* piIndex); void _GetStaticStartMenu(HMENU* phmenu, HWND* phwnd); HRESULT _GetStaticInfoTip(SMDATA* psmd, LPWSTR pszTip, int cch); // helper functions DWORD GetInitFlags(); void SetInitFlags(DWORD dwFlags); HRESULT _InitializeFindMenu(IShellMenu* psm); HRESULT _ExecItem(LPSMDATA, UINT); HRESULT VerifyCSIDL(int idCmd, int csidl, IShellMenu* psm); HRESULT VerifyMergedGuy(BOOL fPrograms, IShellMenu* psm); void _UpdateDocsMenuItemNames(IShellMenu* psm); void _UpdateDocumentsShellMenu(IShellMenu* psm); public: // Make these public to this file. This is for the CreateInstance // Sub Menu creation HRESULT InitializeFastItemsShellMenu(IShellMenu* psm); HRESULT InitializeCSIDLShellMenu(int uId, int csidl, LPTSTR pszRoot, LPTSTR pszValue, DWORD dwPassInitFlags, DWORD dwSetFlags, BOOL fAddOpen, IShellMenu* psm); HRESULT InitializeDocumentsShellMenu(IShellMenu* psm); HRESULT InitializeSubShellMenu(int idCmd, IShellMenu* psm); }; class CStartContextMenu : IContextMenu { public: // IUnknown STDMETHOD(QueryInterface)(REFIID riid, void **ppvObj); STDMETHOD_(ULONG,AddRef)(void); STDMETHOD_(ULONG,Release)(void); // IContextMenu STDMETHOD(QueryContextMenu)(HMENU hmenu, UINT indexMenu, UINT idCmdFirst, UINT idCmdLast, UINT uFlags); STDMETHOD(InvokeCommand)(LPCMINVOKECOMMANDINFO lpici); STDMETHOD(GetCommandString)(UINT_PTR idCmd, UINT uType, UINT *pRes, LPSTR pszName, UINT cchMax); CStartContextMenu(int idCmd) : _idCmd(idCmd), _cRef(1) {}; private: int _cRef; virtual ~CStartContextMenu() {}; int _idCmd; }; void CStartMenuCallbackBase::_RefreshSettings() { _fShowAdminTools = FeatureEnabled(TEXT("StartMenuAdminTools")); } CStartMenuCallbackBase::CStartMenuCallbackBase(BOOL fIsStartPanel) : _cRef(1), _fIsStartPanel(fIsStartPanel) { DEBUG_CODE( _dwThreadID = GetCurrentThreadId() ); TCHAR szBuf[MAX_PATH]; DWORD cbSize = sizeof(szBuf); // SHGetValue wants sizeof if (ERROR_SUCCESS == SHGetValue(HKEY_LOCAL_MACHINE, REGSTR_EXPLORER_WINUPDATE, TEXT("ShortcutName"), NULL, szBuf, &cbSize)) { // Add ".lnk" if the file doesn't have an extension PathAddExtension(szBuf, TEXT(".lnk")); Str_SetPtr(&_pszWindowsUpdate, szBuf); } cbSize = sizeof(szBuf); // SHGetValue wants sizeof if (ERROR_SUCCESS == SHGetValue(HKEY_LOCAL_MACHINE, REGSTR_PATH_SETUP, TEXT("SM_ConfigureProgramsName"), NULL, szBuf, &cbSize)) { PathAddExtension(szBuf, TEXT(".lnk")); Str_SetPtr(&_pszConfigurePrograms, szBuf); } if (SUCCEEDED(SHGetFolderPath(NULL, CSIDL_COMMON_ADMINTOOLS | CSIDL_FLAG_CREATE, NULL, 0, szBuf))) { Str_SetPtr(&_pszAdminTools, PathFindFileName(szBuf)); } _RefreshSettings(); SHReValidateDarwinCache(); } CStartMenuCallback::CStartMenuCallback() : _cRecentDocs(-1) { LoadString(g_hinst, IDS_FIND_MNEMONIC, _szFindMnemonic, ARRAYSIZE(_szFindMnemonic)); } CStartMenuCallbackBase::~CStartMenuCallbackBase() { ASSERT( _dwThreadID == GetCurrentThreadId() ); Str_SetPtr(&_pszWindowsUpdate, NULL); Str_SetPtr(&_pszConfigurePrograms, NULL); Str_SetPtr(&_pszAdminTools, NULL); Str_SetPtr(&_pszPrograms, NULL); ATOMICRELEASE(_ptp2); } CStartMenuCallback::~CStartMenuCallback() { ATOMICRELEASE(_pcmFind); ATOMICRELEASE(_ptp); ATOMICRELEASE(_pmruRecent); } // *** IUnknown methods *** STDMETHODIMP CStartMenuCallbackBase::QueryInterface(REFIID riid, void ** ppvObj) { static const QITAB qit[] = { QITABENT(CStartMenuCallbackBase, IShellMenuCallback), QITABENT(CStartMenuCallbackBase, IObjectWithSite), { 0 }, }; return QISearch(this, qit, riid, ppvObj); } STDMETHODIMP_(ULONG) CStartMenuCallbackBase::AddRef() { return ++_cRef; } STDMETHODIMP_(ULONG) CStartMenuCallbackBase::Release() { ASSERT(_cRef > 0); _cRef--; if( _cRef > 0) return _cRef; delete this; return 0; } STDMETHODIMP CStartMenuCallback::SetSite(IUnknown* punk) { ATOMICRELEASE(_punkSite); _punkSite = punk; if (punk) { _punkSite->AddRef(); } return S_OK; } STDMETHODIMP CStartMenuCallback::GetSite(REFIID riid, void**ppvOut) { if (_ptp) return _ptp->QueryInterface(riid, ppvOut); else return E_NOINTERFACE; } #ifdef DEBUG void DBUEMQueryEvent(const IID *pguidGrp, int eCmd, WPARAM wParam, LPARAM lParam) { #if 1 return; #else UEMINFO uei; uei.cbSize = sizeof(uei); uei.dwMask = ~0; // UEIM_HIT etc. UEMQueryEvent(pguidGrp, eCmd, wParam, lParam, &uei); TCHAR szBuf[20]; wnsprintf(szBuf, ARRAYSIZE(szBuf), TEXT("hit=%d"), uei.cHit); MessageBox(NULL, szBuf, TEXT("UEM"), MB_OKCANCEL); return; #endif } #endif DWORD CStartMenuCallback::GetInitFlags() { DWORD dwType; DWORD cbSize = sizeof(DWORD); DWORD dwFlags = 0; SHGetValue(HKEY_CURRENT_USER, REGSTR_EXPLORER_ADVANCED, TEXT("StartMenuInit"), &dwType, (BYTE*)&dwFlags, &cbSize); return dwFlags; } void CStartMenuCallback::SetInitFlags(DWORD dwFlags) { SHSetValue(HKEY_CURRENT_USER, REGSTR_EXPLORER_ADVANCED, TEXT("StartMenuInit"), REG_DWORD, &dwFlags, sizeof(DWORD)); } DWORD GetClickCount() { //This function retrieves the number of times the user has clicked on the chevron item. DWORD dwType; DWORD cbSize = sizeof(DWORD); DWORD dwCount = 1; // Default to three clicks before we give up. // PMs what it to 1 now. Leaving back end in case they change their mind. SHGetValue(HKEY_CURRENT_USER, REGSTR_EXPLORER_ADVANCED, TEXT("StartMenuChevron"), &dwType, (BYTE*)&dwCount, &cbSize); return dwCount; } void SetClickCount(DWORD dwClickCount) { SHSetValue(HKEY_CURRENT_USER, REGSTR_EXPLORER_ADVANCED, TEXT("StartMenuChevron"), REG_DWORD, &dwClickCount, sizeof(dwClickCount)); } BOOL CStartMenuCallbackBase::_IsTopLevelStartMenu(UINT uParent, IShellFolder *psf, LPCITEMIDLIST pidl) { return uParent == IDM_TOPLEVELSTARTMENU || (uParent == IDM_PROGRAMS && _fIsStartPanel && IsMergedFolderGUID(psf, pidl, CLSID_StartMenu)); }; STDMETHODIMP CStartMenuCallback::CallbackSM(LPSMDATA psmd, UINT uMsg, WPARAM wParam, LPARAM lParam) { HRESULT hr = S_FALSE; switch (uMsg) { case SMC_CREATE: hr = _Create(psmd, (void**)lParam); break; case SMC_DESTROY: hr = _Destroy(psmd); break; case SMC_INITMENU: hr = _Init(psmd); break; case SMC_SFEXEC: hr = _ExecItem(psmd, uMsg); break; case SMC_EXEC: hr = _ExecHmenuItem(psmd); break; case SMC_GETOBJECT: hr = _GetObject(psmd, (GUID)*((GUID*)wParam), (void**)lParam); break; case SMC_GETINFO: hr = _GetHmenuInfo(psmd, (SMINFO*)lParam); break; case SMC_GETSFINFOTIP: if (!_fShowInfoTip) hr = E_FAIL; // E_FAIL means don't show. S_FALSE means show default break; case SMC_GETINFOTIP: hr = _GetStaticInfoTip(psmd, (LPWSTR)wParam, (int)lParam); break; case SMC_GETSFINFO: hr = _GetSFInfo(psmd, (SMINFO*)lParam); break; case SMC_BEGINENUM: if (psmd->uIdParent == IDM_RECENT) { ASSERT(_cRecentDocs == -1); ASSERT(!_pmruRecent); CreateRecentMRUList(&_pmruRecent); _cRecentDocs = 0; hr = S_OK; } break; case SMC_ENDENUM: if (psmd->uIdParent == IDM_RECENT) { ASSERT(_cRecentDocs != -1); ATOMICRELEASE(_pmruRecent); _cRecentDocs = -1; hr = S_OK; } break; case SMC_DUMPONUPDATE: if (psmd->uIdParent == IDM_RECENT) { hr = S_OK; } break; case SMC_FILTERPIDL: ASSERT(psmd->dwMask & SMDM_SHELLFOLDER); if (psmd->uIdParent == IDM_RECENT) { // we need to filter out all but the first MAXRECENTITEMS // and no folders allowed! hr = _FilterRecentPidl(psmd->psf, psmd->pidlItem); } else { hr = _FilterPidl(psmd->uIdParent, psmd->psf, psmd->pidlItem); } break; case SMC_INSERTINDEX: ASSERT(lParam && IS_VALID_WRITE_PTR(lParam, int)); *((int*)lParam) = 0; hr = S_OK; break; case SMC_SHCHANGENOTIFY: { PSMCSHCHANGENOTIFYSTRUCT pshf = (PSMCSHCHANGENOTIFYSTRUCT)lParam; hr = _ProcessChangeNotify(psmd, pshf->lEvent, pshf->pidl1, pshf->pidl2); } break; case SMC_REFRESH: if (psmd->uIdParent == IDM_TOPLEVELSTARTMENU) { hr = S_OK; // Refresh is only called on the top level. HMENU hmenu; IShellMenu* psm; _GetStaticStartMenu(&hmenu, &_hwnd); if (hmenu && psmd->punk && SUCCEEDED(psmd->punk->QueryInterface(IID_PPV_ARG(IShellMenu, &psm)))) { hr = psm->SetMenu(hmenu, _hwnd, SMSET_BOTTOM | SMSET_MERGE); psm->Release(); } _RefreshSettings(); _fExpandoMenus = !_fIsStartPanel && AreIntelliMenusEnabled(); _fCascadeMyDocuments = FeatureEnabled(TEXT("CascadeMyDocuments")); _fCascadePrinters = FeatureEnabled(TEXT("CascadePrinters")); _fCascadeControlPanel = FeatureEnabled(TEXT("CascadeControlPanel")); _fCascadeNetConnections = FeatureEnabled(TEXT("CascadeNetworkConnections")); _fAddOpenFolder = FeatureEnabled(TEXT("StartMenuOpen")); _fShowInfoTip = FeatureEnabled(TEXT("ShowInfoTip")); _fCascadeMyPictures = FeatureEnabled(TEXT("CascadeMyPictures")); _fFindMenuInvalid = TRUE; _dwFlags = GetInitFlags(); } break; case SMC_DEMOTE: hr = _Demote(psmd); break; case SMC_PROMOTE: hr = _Promote(psmd, (DWORD)wParam); break; case SMC_NEWITEM: hr = _HandleNew(psmd); break; case SMC_MAPACCELERATOR: hr = _HandleAccelerator((TCHAR)wParam, (SMDATA*)lParam); break; case SMC_DEFAULTICON: ASSERT(psmd->uIdAncestor == IDM_FAVORITES); // This is only valid for the Favorites menu hr = _GetDefaultIcon((LPWSTR)wParam, (int*)lParam); break; case SMC_GETMINPROMOTED: // Only do this for the programs menu if (psmd->uIdParent == IDM_PROGRAMS) *((int*)lParam) = 4; // 4 was choosen by RichSt 9.15.98 break; case SMC_CHEVRONEXPAND: // Has the user already seen the chevron tip enough times? (We set the bit when the count goes to zero. if (!(_dwFlags & STARTMENU_CHEVRONCLICKED)) { // No; Then get the current count from the registry. We set a default of 3, but an admin can set this // to -1, that would make it so that they user sees it all the time. DWORD dwClickCount = GetClickCount(); if (dwClickCount > 0) { // Since they clicked, take one off. dwClickCount--; // Set it back in. SetClickCount(dwClickCount); } if (dwClickCount == 0) { // Ah, the user has seen the chevron tip enought times... Stop being annoying. _dwFlags |= STARTMENU_CHEVRONCLICKED; SetInitFlags(_dwFlags); } } hr = S_OK; break; case SMC_DISPLAYCHEVRONTIP: // We only want to see the tip on the top level programs case, no where else. We also don't // want to see it if they've had enough. if (psmd->uIdParent == IDM_PROGRAMS && !(_dwFlags & STARTMENU_CHEVRONCLICKED) && !SHRestricted(REST_NOSMBALLOONTIP)) { hr = S_OK; } break; case SMC_CHEVRONGETTIP: if (!SHRestricted(REST_NOSMBALLOONTIP)) hr = _GetTip((LPWSTR)wParam, (LPWSTR)lParam); break; } return hr; } // For the Favorites menu, since their icon handler is SO slow, we're going to fake the icon // and have it get the real ones on the background thread... HRESULT CStartMenuCallback::_GetDefaultIcon(LPWSTR psz, int* piIndex) { DWORD cchSize = MAX_PATH; HRESULT hr = AssocQueryString(0, ASSOCSTR_DEFAULTICON, TEXT("InternetShortcut"), NULL, psz, &cchSize); if (SUCCEEDED(hr)) { *piIndex = PathParseIconLocation(psz); } return hr; } HRESULT CStartMenuCallback::_ExecItem(LPSMDATA psmd, UINT uMsg) { ASSERT( _dwThreadID == GetCurrentThreadId() ); return _ptp->ExecItem(psmd->psf, psmd->pidlItem); } HRESULT CStartMenuCallback::_Demote(LPSMDATA psmd) { //We want to for the UEM to demote pidlFolder, // then tell the Parent menuband (If there is one) // to invalidate this pidl. HRESULT hr = S_FALSE; if (_fExpandoMenus && (psmd->uIdAncestor == IDM_PROGRAMS || psmd->uIdAncestor == IDM_FAVORITES)) { UEMINFO uei; uei.cbSize = sizeof(uei); uei.dwMask = UEIM_HIT; uei.cHit = 0; hr = UEMSetEvent(psmd->uIdAncestor == IDM_PROGRAMS? &UEMIID_SHELL : &UEMIID_BROWSER, UEME_RUNPIDL, (WPARAM)psmd->psf, (LPARAM)psmd->pidlItem, &uei); } return hr; } // Even if intellimenus are off, fire a UEM event if it was an Exec from // the More Programs menu of the Start Panel [SMINV_FORCE will be set] // so we can detect which are the user's most popular apps. HRESULT CStartMenuCallbackBase::_Promote(LPSMDATA psmd, DWORD dwFlags) { if ((_fExpandoMenus || (_fIsStartPanel && (dwFlags & SMINV_FORCE))) && (psmd->uIdAncestor == IDM_PROGRAMS || psmd->uIdAncestor == IDM_FAVORITES)) { UEMFireEvent(psmd->uIdAncestor == IDM_PROGRAMS? &UEMIID_SHELL : &UEMIID_BROWSER, UEME_RUNPIDL, UEMF_XEVENT, (WPARAM)psmd->psf, (LPARAM)psmd->pidlItem); } return S_OK; } HRESULT CStartMenuCallbackBase::_HandleNew(LPSMDATA psmd) { HRESULT hr = S_FALSE; if (_fExpandoMenus && (psmd->uIdAncestor == IDM_PROGRAMS || psmd->uIdAncestor == IDM_FAVORITES)) { UEMINFO uei; uei.cbSize = sizeof(uei); uei.dwMask = UEIM_HIT; uei.cHit = UEM_NEWITEMCOUNT; hr = UEMSetEvent(psmd->uIdAncestor == IDM_PROGRAMS? &UEMIID_SHELL : &UEMIID_BROWSER, UEME_RUNPIDL, (WPARAM)psmd->psf, (LPARAM)psmd->pidlItem, &uei); } if (psmd->uIdAncestor == IDM_PROGRAMS) { LPITEMIDLIST pidlFull = FullPidlFromSMData(psmd); if (pidlFull) { ProcessDarwinAd(NULL, pidlFull); ILFree(pidlFull); } } return hr; } HRESULT ShowFolder(UINT csidl) { LPITEMIDLIST pidl; if (SUCCEEDED(SHGetFolderLocation(NULL, csidl, NULL, 0, &pidl))) { SHELLEXECUTEINFO shei = { 0 }; shei.cbSize = sizeof(shei); shei.fMask = SEE_MASK_IDLIST; shei.nShow = SW_SHOWNORMAL; shei.lpVerb = TEXT("open"); shei.lpIDList = pidl; ShellExecuteEx(&shei); // only executing open on pidl ILFree(pidl); } return S_OK; } void _ExecRegValue(LPCTSTR pszValue) { TCHAR szPath[MAX_PATH]; DWORD cbSize = sizeof(szPath); if (ERROR_SUCCESS == SHGetValue(HKEY_LOCAL_MACHINE, REGSTR_EXPLORER_ADVANCED, pszValue, NULL, szPath, &cbSize)) { SHELLEXECUTEINFO shei= { 0 }; shei.cbSize = sizeof(shei); shei.nShow = SW_SHOWNORMAL; shei.lpParameters = PathGetArgs(szPath); PathRemoveArgs(szPath); shei.lpFile = szPath; ShellExecuteEx(&shei); // raw shellexec but info comes from HKLM } } HRESULT CStartMenuCallback::_ExecHmenuItem(LPSMDATA psmd) { HRESULT hr = S_FALSE; if (IsInRange(psmd->uId, TRAY_IDM_FINDFIRST, TRAY_IDM_FINDLAST) && _pcmFind) { CMINVOKECOMMANDINFOEX ici = { 0 }; ici.cbSize = sizeof(CMINVOKECOMMANDINFOEX); ici.lpVerb = (LPSTR)MAKEINTRESOURCE(psmd->uId - TRAY_IDM_FINDFIRST); ici.nShow = SW_NORMAL; // record if shift or control was being held down SetICIKeyModifiers(&ici.fMask); _pcmFind->InvokeCommand((LPCMINVOKECOMMANDINFO)&ici); hr = S_OK; } else { switch (psmd->uId) { case IDM_OPEN_FOLDER: switch(psmd->uIdParent) { case IDM_CONTROLS: hr = ShowFolder(CSIDL_CONTROLS); break; case IDM_PRINTERS: hr = ShowFolder(CSIDL_PRINTERS); break; case IDM_NETCONNECT: hr = ShowFolder(CSIDL_CONNECTIONS); break; case IDM_MYPICTURES: hr = ShowFolder(CSIDL_MYPICTURES); break; case IDM_MYDOCUMENTS: hr = ShowFolder(CSIDL_PERSONAL); break; } break; case IDM_NETCONNECT: hr = ShowFolder(CSIDL_CONNECTIONS); break; case IDM_MYDOCUMENTS: hr = ShowFolder(CSIDL_PERSONAL); break; case IDM_MYPICTURES: hr = ShowFolder(CSIDL_MYPICTURES); break; case IDM_CSC: _ExecRegValue(TEXT("StartMenuSyncAll")); break; default: hr = ExecStaticStartMenuItem(psmd->uId, FALSE, TRUE); break; } } return hr; } void CStartMenuCallback::_GetStaticStartMenu(HMENU* phmenu, HWND* phwnd) { *phmenu = NULL; *phwnd = NULL; IMenuPopup* pmp; // The first one should be the bar that the start menu is sitting in. if (SUCCEEDED(IUnknown_QueryService(_punkSite, SID_SMenuPopup, IID_PPV_ARG(IMenuPopup, &pmp)))) { // Its site should be CStartMenuHost; if (SUCCEEDED(IUnknown_GetSite(pmp, IID_PPV_ARG(ITrayPriv, &_ptp)))) { // Don't get upset if this fails _ptp->QueryInterface(IID_PPV_ARG(ITrayPriv2, &_ptp2)); _ptp->GetStaticStartMenu(phmenu); IUnknown_GetWindow(_ptp, phwnd); if (!_poct) _ptp->QueryInterface(IID_PPV_ARG(IOleCommandTarget, &_poct)); } else TraceMsg(TF_MENUBAND, "CStartMenuCallback::_SetSite : Failed to aquire CStartMenuHost"); pmp->Release(); } } HRESULT CStartMenuCallback::_Create(SMDATA* psmdata, void** ppvUserData) { *ppvUserData = new SMUSERDATA; return S_OK; } HRESULT CStartMenuCallback::_Destroy(SMDATA* psmdata) { if (psmdata->pvUserData) { delete (SMUSERDATA*)psmdata->pvUserData; psmdata->pvUserData = NULL; } return S_OK; } void CStartMenuCallbackBase::_InitializePrograms() { if (!_fInitPrograms) { // We're either initing these, or reseting them. TCHAR szTemp[MAX_PATH]; SHGetFolderPath(NULL, CSIDL_PROGRAMS, NULL, 0, szTemp); Str_SetPtr(&_pszPrograms, PathFindFileName(szTemp)); _fInitPrograms = TRUE; } } // Given a CSIDL and a Shell menu, this will verify if the IShellMenu // is pointing at the same place as the CSIDL is. If not, then it will // update the shell menu to the new location. HRESULT CStartMenuCallback::VerifyCSIDL(int idCmd, int csidl, IShellMenu* psm) { DWORD dwFlags; LPITEMIDLIST pidl; IShellFolder* psf; HRESULT hr = S_OK; if (SUCCEEDED(psm->GetShellFolder(&dwFlags, &pidl, IID_PPV_ARG(IShellFolder, &psf)))) { psf->Release(); LPITEMIDLIST pidlCSIDL; if (SUCCEEDED(SHGetFolderLocation(NULL, csidl, NULL, 0, &pidlCSIDL))) { // If the pidl of the IShellMenu is not equal to the // SpecialFolder Location, then we need to update it so they are... if (!ILIsEqual(pidlCSIDL, pidl)) { hr = InitializeSubShellMenu(idCmd, psm); } ILFree(pidlCSIDL); } ILFree(pidl); } return hr; } // This code special cases the Programs and Fast items shell menus. It // understands Merging and will check both shell folders in a merged case // to verify that the shell folder is still pointing at that location HRESULT CStartMenuCallback::VerifyMergedGuy(BOOL fPrograms, IShellMenu* psm) { DWORD dwFlags; LPITEMIDLIST pidl; HRESULT hr = S_OK; IAugmentedShellFolder2* pasf; if (SUCCEEDED(psm->GetShellFolder(&dwFlags, &pidl, IID_PPV_ARG(IAugmentedShellFolder2, &pasf)))) { IShellFolder* psf; // There are 2 things in the merged namespace: CSIDL_PROGRAMS and CSIDL_COMMON_PROGRAMS for (int i = 0; i < 2; i++) { if (SUCCEEDED(pasf->QueryNameSpace(i, 0, &psf))) { int csidl; LPITEMIDLIST pidlFolder; if (SUCCEEDED(GetFilesystemInfo(psf, &pidlFolder, &csidl))) { LPITEMIDLIST pidlCSIDL; if (SUCCEEDED(SHGetFolderLocation(NULL, csidl, NULL, 0, &pidlCSIDL))) { // If the pidl of the IShellMenu is not equal to the // SpecialFolder Location, then we need to update it so they are... if (!ILIsEqual(pidlCSIDL, pidlFolder)) { // Since one of these things has changed, // we need to update the string cache // so that we do proper filtering of // the programs item. _fInitPrograms = FALSE; if (fPrograms) hr = InitializeProgramsShellMenu(psm); else hr = InitializeFastItemsShellMenu(psm); i = 100; // break out of the loop. } ILFree(pidlCSIDL); } ILFree(pidlFolder); } psf->Release(); } } ILFree(pidl); pasf->Release(); } return hr; } void _FixMenuItemName(IShellMenu *psm, UINT uID, LPTSTR pszNewMenuName) { HMENU hMenu; ASSERT(NULL != psm); if (SUCCEEDED(psm->GetMenu(&hMenu, NULL, NULL))) { MENUITEMINFO mii = {0}; TCHAR szMenuName[256]; mii.cbSize = sizeof(mii); mii.fMask = MIIM_TYPE; mii.dwTypeData = szMenuName; mii.cch = ARRAYSIZE(szMenuName); szMenuName[0] = TEXT('\0'); if (::GetMenuItemInfo(hMenu, uID, FALSE, &mii)) { if (0 != StrCmp(szMenuName, pszNewMenuName)) { // The mydocs name has changed, update the menu item: mii.dwTypeData = pszNewMenuName; if (::SetMenuItemInfo(hMenu, uID, FALSE, &mii)) { SMDATA smd; smd.dwMask = SMDM_HMENU; smd.uId = uID; psm->InvalidateItem(&smd, SMINV_ID | SMINV_REFRESH); } } } } } void CStartMenuCallback::_UpdateDocumentsShellMenu(IShellMenu* psm) { // Add/Remove My Documents and My Pictures items of menu BOOL fMyDocs = !SHRestricted(REST_NOSMMYDOCS); if (fMyDocs) { LPITEMIDLIST pidl; fMyDocs = SUCCEEDED(SHGetFolderLocation(NULL, CSIDL_PERSONAL, NULL, 0, &pidl)); if (fMyDocs) ILFree(pidl); } BOOL fMyPics = !SHRestricted(REST_NOSMMYPICS); if (fMyPics) { LPITEMIDLIST pidl; fMyPics = SUCCEEDED(SHGetFolderLocation(NULL, CSIDL_MYPICTURES, NULL, 0, &pidl)); if (fMyPics) ILFree(pidl); } // Do not update menu if not different than currently have if (fMyDocs != (BOOL)_fHasMyDocuments || fMyPics != (BOOL)_fHasMyPictures) { HMENU hMenu = SHLoadMenuPopup(HINST_THISDLL, MENU_STARTMENU_MYDOCS); if (hMenu) { if (!fMyDocs) DeleteMenu(hMenu, IDM_MYDOCUMENTS, MF_BYCOMMAND); if (!fMyPics) DeleteMenu(hMenu, IDM_MYPICTURES, MF_BYCOMMAND); // Reset section of menu psm->SetMenu(hMenu, _hwnd, SMSET_TOP); } // Cache what folders are available _fHasMyDocuments = fMyDocs; _fHasMyPictures = fMyPics; } } void CStartMenuCallback::_UpdateDocsMenuItemNames(IShellMenu* psm) { TCHAR szBuffer[MAX_PATH]; if (_fHasMyDocuments) { if ( SUCCEEDED(GetMyDocumentsDisplayName(szBuffer, ARRAYSIZE(szBuffer))) ) _FixMenuItemName(psm, IDM_MYDOCUMENTS, szBuffer); } if (_fHasMyPictures) { if ( SUCCEEDED(GetMyPicsDisplayName(szBuffer, ARRAYSIZE(szBuffer))) ) _FixMenuItemName(psm, IDM_MYPICTURES, szBuffer); } } HRESULT CStartMenuCallback::_Init(SMDATA* psmdata) { HRESULT hr = S_FALSE; IShellMenu* psm; if (psmdata->punk && SUCCEEDED(hr = psmdata->punk->QueryInterface(IID_PPV_ARG(IShellMenu, &psm)))) { switch(psmdata->uIdParent) { case IDM_TOPLEVELSTARTMENU: { if (psmdata->pvUserData && !((SMUSERDATA*)psmdata->pvUserData)->_fInitialized) { TraceMsg(TF_MENUBAND, "CStartMenuCallback::_Init : Initializing Toplevel Start Menu"); ((SMUSERDATA*)psmdata->pvUserData)->_fInitialized = TRUE; HMENU hmenu; TraceMsg(TF_MENUBAND, "CStartMenuCallback::_Init : First Time, and correct parameters"); _GetStaticStartMenu(&hmenu, &_hwnd); if (hmenu) { HMENU hmenuOld = NULL; HWND hwnd; DWORD dwFlags; psm->GetMenu(&hmenuOld, &hwnd, &dwFlags); if (hmenuOld != NULL) { TBOOL(DestroyMenu(hmenuOld)); } hr = psm->SetMenu(hmenu, _hwnd, SMSET_BOTTOM); TraceMsg(TF_MENUBAND, "CStartMenuCallback::_Init : SetMenu(HMENU 0x%x, HWND 0x%x", hmenu, _hwnd); } _fExpandoMenus = !_fIsStartPanel && AreIntelliMenusEnabled(); _fCascadeMyDocuments = FeatureEnabled(TEXT("CascadeMyDocuments")); _fCascadePrinters = FeatureEnabled(TEXT("CascadePrinters")); _fCascadeControlPanel = FeatureEnabled(TEXT("CascadeControlPanel")); _fCascadeNetConnections = FeatureEnabled(TEXT("CascadeNetworkConnections")); _fAddOpenFolder = FeatureEnabled(TEXT("StartMenuOpen")); _fShowInfoTip = FeatureEnabled(TEXT("ShowInfoTip")); _fCascadeMyPictures = FeatureEnabled(TEXT("CascadeMyPictures")); _dwFlags = GetInitFlags(); } else if (!_fInitedShowTopLevelStartMenu) { _fInitedShowTopLevelStartMenu = TRUE; psm->InvalidateItem(NULL, SMINV_REFRESH); } // Verify that the Fast items is still pointing to the right location if (SUCCEEDED(hr)) { hr = VerifyMergedGuy(FALSE, psm); } break; } case IDM_MENU_FIND: if (_fFindMenuInvalid) { hr = _InitializeFindMenu(psm); _fFindMenuInvalid = FALSE; } break; case IDM_PROGRAMS: // Verify the programs menu is still pointing to the right location hr = VerifyMergedGuy(TRUE, psm); break; case IDM_FAVORITES: hr = VerifyCSIDL(IDM_FAVORITES, CSIDL_FAVORITES, psm); break; case IDM_MYDOCUMENTS: hr = VerifyCSIDL(IDM_MYDOCUMENTS, CSIDL_PERSONAL, psm); break; case IDM_MYPICTURES: hr = VerifyCSIDL(IDM_MYPICTURES, CSIDL_MYPICTURES, psm); break; case IDM_RECENT: _UpdateDocumentsShellMenu(psm); _UpdateDocsMenuItemNames(psm); hr = VerifyCSIDL(IDM_RECENT, CSIDL_RECENT, psm); break; case IDM_CONTROLS: hr = VerifyCSIDL(IDM_CONTROLS, CSIDL_CONTROLS, psm); break; case IDM_PRINTERS: hr = VerifyCSIDL(IDM_PRINTERS, CSIDL_PRINTERS, psm); break; } psm->Release(); } return hr; } HRESULT CStartMenuCallback::_GetStaticInfoTip(SMDATA* psmd, LPWSTR pszTip, int cch) { if (!_fShowInfoTip) return E_FAIL; HRESULT hr = E_FAIL; const static struct { UINT idCmd; UINT idInfoTip; } s_mpcmdTip[] = { #if 0 // No tips for the Toplevel. Keep this here because I bet that someone will want them... { IDM_PROGRAMS, IDS_PROGRAMS_TIP }, { IDM_FAVORITES, IDS_FAVORITES_TIP }, { IDM_RECENT, IDS_RECENT_TIP }, { IDM_SETTINGS, IDS_SETTINGS_TIP }, { IDM_MENU_FIND, IDS_FIND_TIP }, { IDM_HELPSEARCH, IDS_HELP_TIP }, // Redundant? { IDM_FILERUN, IDS_RUN_TIP }, { IDM_LOGOFF, IDS_LOGOFF_TIP }, { IDM_EJECTPC, IDS_EJECT_TIP }, { IDM_EXITWIN, IDS_SHUTDOWN_TIP }, #endif // Settings Submenu { IDM_CONTROLS, IDS_CONTROL_TIP }, { IDM_PRINTERS, IDS_PRINTERS_TIP }, { IDM_TRAYPROPERTIES, IDS_TRAYPROP_TIP }, { IDM_NETCONNECT, IDS_NETCONNECT_TIP }, // Recent Folder { IDM_MYDOCUMENTS, IDS_MYDOCS_TIP }, { IDM_MYPICTURES, IDS_MYPICS_TIP }, }; for (int i = 0; i < ARRAYSIZE(s_mpcmdTip); i++) { if (s_mpcmdTip[i].idCmd == psmd->uId) { TCHAR szTip[MAX_PATH]; if (LoadString(g_hinst, s_mpcmdTip[i].idInfoTip, szTip, ARRAYSIZE(szTip))) { SHTCharToUnicode(szTip, pszTip, cch); hr = S_OK; } break; } } return hr; } typedef struct { WCHAR wszMenuText[MAX_PATH]; WCHAR wszHelpText[MAX_PATH]; int iIcon; } SEARCHEXTDATA, *LPSEARCHEXTDATA; HRESULT CStartMenuCallback::_GetHmenuInfo(SMDATA* psmd, SMINFO* psminfo) { const static struct { UINT idCmd; int iImage; } s_mpcmdimg[] = { // Top level menu { IDM_PROGRAMS, -IDI_CLASSICSM_PROGS }, { IDM_FAVORITES, -IDI_CLASSICSM_FAVORITES }, { IDM_RECENT, -IDI_CLASSICSM_RECENTDOCS }, { IDM_SETTINGS, -IDI_CLASSICSM_SETTINGS }, { IDM_MENU_FIND, -IDI_CLASSICSM_FIND }, { IDM_HELPSEARCH, -IDI_CLASSICSM_HELP }, { IDM_FILERUN, -IDI_CLASSICSM_RUN }, { IDM_LOGOFF, -IDI_CLASSICSM_LOGOFF }, { IDM_EJECTPC, -IDI_CLASSICSM_UNDOCK }, { IDM_EXITWIN, -IDI_CLASSICSM_SHUTDOWN }, { IDM_MU_SECURITY, II_MU_STSECURITY }, { IDM_MU_DISCONNECT, II_MU_STDISCONN }, { IDM_SETTINGSASSIST, -IDI_CLASSICSM_SETTINGS }, { IDM_CONTROLS, II_STCPANEL }, { IDM_PRINTERS, II_STPRNTRS }, { IDM_TRAYPROPERTIES, II_STTASKBR }, { IDM_MYDOCUMENTS, -IDI_MYDOCS}, { IDM_CSC, -IDI_CSC}, { IDM_NETCONNECT, -IDI_NETCONNECT}, }; ASSERT(IS_VALID_WRITE_PTR(psminfo, SMINFO)); int iIcon = -1; DWORD dwFlags = psminfo->dwFlags; MENUITEMINFO mii = {0}; HRESULT hr = S_FALSE; if (psminfo->dwMask & SMIM_ICON) { if (IsInRange(psmd->uId, TRAY_IDM_FINDFIRST, TRAY_IDM_FINDLAST)) { // The find menu extensions pack their icon into their data member of // Menuiteminfo.... mii.cbSize = sizeof(mii); mii.fMask = MIIM_DATA; if (GetMenuItemInfo(psmd->hmenu, psmd->uId, MF_BYCOMMAND, &mii)) { LPSEARCHEXTDATA psed = (LPSEARCHEXTDATA)mii.dwItemData; if (psed) psminfo->iIcon = psed->iIcon; else psminfo->iIcon = -1; dwFlags |= SMIF_ICON; hr = S_OK; } } else { if (psmd->uId == IDM_MYPICTURES) { LPITEMIDLIST pidlMyPics = SHCloneSpecialIDList(NULL, CSIDL_MYPICTURES, FALSE); if (pidlMyPics) { LPCITEMIDLIST pidlObject; IShellFolder *psf; hr = SHBindToParent(pidlMyPics, IID_PPV_ARG(IShellFolder, &psf), &pidlObject); if (SUCCEEDED(hr)) { SHMapPIDLToSystemImageListIndex(psf, pidlObject, &psminfo->iIcon); dwFlags |= SMIF_ICON; psf->Release(); } ILFree(pidlMyPics); } } else { UINT uIdLocal = psmd->uId; if (uIdLocal == IDM_OPEN_FOLDER) uIdLocal = psmd->uIdAncestor; for (int i = 0; i < ARRAYSIZE(s_mpcmdimg); i++) { if (s_mpcmdimg[i].idCmd == uIdLocal) { iIcon = s_mpcmdimg[i].iImage; break; } } if (iIcon != -1) { dwFlags |= SMIF_ICON; psminfo->iIcon = Shell_GetCachedImageIndex(TEXT("shell32.dll"), iIcon, 0); hr = S_OK; } } } } if (psminfo->dwMask & SMIM_FLAGS) { psminfo->dwFlags = dwFlags; if ( (psmd->uId == IDM_CONTROLS && _fCascadeControlPanel ) || (psmd->uId == IDM_PRINTERS && _fCascadePrinters ) || (psmd->uId == IDM_MYDOCUMENTS && _fCascadeMyDocuments ) || (psmd->uId == IDM_NETCONNECT && _fCascadeNetConnections ) || (psmd->uId == IDM_MYPICTURES && _fCascadeMyPictures ) ) { psminfo->dwFlags |= SMIF_SUBMENU; hr = S_OK; } else switch (psmd->uId) { case IDM_FAVORITES: case IDM_PROGRAMS: psminfo->dwFlags |= SMIF_DROPCASCADE; hr = S_OK; break; } } return hr; } DWORD CStartMenuCallback::_GetDemote(SMDATA* psmd) { UEMINFO uei; DWORD dwFlags = 0; uei.cbSize = sizeof(uei); uei.dwMask = UEIM_HIT; if (SUCCEEDED(UEMQueryEvent(psmd->uIdAncestor == IDM_PROGRAMS? &UEMIID_SHELL : &UEMIID_BROWSER, UEME_RUNPIDL, (WPARAM)psmd->psf, (LPARAM)psmd->pidlItem, &uei))) { if (uei.cHit == 0) { dwFlags |= SMIF_DEMOTED; } } return dwFlags; } // // WARNING! Since this function returns a pointer from our Darwin cache, // it must be called while the Darwin critical section is held. // int GetDarwinIndex(LPCITEMIDLIST pidlFull, CDarwinAd** ppda) { int iRet = -1; if (g_hdpaDarwinAds) { int chdpa = DPA_GetPtrCount(g_hdpaDarwinAds); for (int ihdpa = 0; ihdpa < chdpa; ihdpa++) { *ppda = (CDarwinAd*)DPA_FastGetPtr(g_hdpaDarwinAds, ihdpa); if (*ppda) { if (ILIsEqual((*ppda)->_pidl, pidlFull)) { iRet = ihdpa; break; } } } } return iRet; } BOOL CStartMenuCallbackBase::_IsDarwinAdvertisement(LPCITEMIDLIST pidlFull) { // What this is doing is comparing the passed in pidl with the // list of pidls in g_hdpaDarwinAds. That hdpa contains a list of // pidls that are darwin ads. // If the background thread is not done, then we must assume that // it has not processed the shortcut that we are on. That is why we process it // in line. ENTERCRITICAL_DARWINADS; // NOTE: There can be two items in the hdpa. This is ok. BOOL fAd = FALSE; CDarwinAd* pda = NULL; int iIndex = GetDarwinIndex(pidlFull, &pda); // Are there any ads? if (iIndex != -1 && pda != NULL) { //This is a Darwin pidl. Is it installed? fAd = pda->IsAd(); } LEAVECRITICAL_DARWINADS; return fAd; } STDAPI SHParseDarwinIDFromCacheW(LPWSTR pszDarwinDescriptor, LPWSTR *ppwszOut) { HRESULT hr = HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND); if (g_hdpaDarwinAds) { ENTERCRITICAL_DARWINADS; int chdpa = DPA_GetPtrCount(g_hdpaDarwinAds); for (int ihdpa = 0; ihdpa < chdpa; ihdpa++) { CDarwinAd *pda = (CDarwinAd*)DPA_FastGetPtr(g_hdpaDarwinAds, ihdpa); if (pda && pda->_pszLocalPath && pda->_pszDescriptor && StrCmpCW(pszDarwinDescriptor, pda->_pszDescriptor) == 0) { hr = SHStrDupW(pda->_pszLocalPath, ppwszOut); break; } } LEAVECRITICAL_DARWINADS; } return hr; } // REARCHITECT (lamadio): There is a duplicate of this helper in browseui\browmenu.cpp // When modifying this, rev that one as well. void UEMRenamePidl(const GUID *pguidGrp1, IShellFolder* psf1, LPCITEMIDLIST pidl1, const GUID *pguidGrp2, IShellFolder* psf2, LPCITEMIDLIST pidl2) { UEMINFO uei; uei.cbSize = sizeof(uei); uei.dwMask = UEIM_HIT | UEIM_FILETIME; if (SUCCEEDED(UEMQueryEvent(pguidGrp1, UEME_RUNPIDL, (WPARAM)psf1, (LPARAM)pidl1, &uei)) && uei.cHit > 0) { UEMSetEvent(pguidGrp2, UEME_RUNPIDL, (WPARAM)psf2, (LPARAM)pidl2, &uei); uei.cHit = 0; UEMSetEvent(pguidGrp1, UEME_RUNPIDL, (WPARAM)psf1, (LPARAM)pidl1, &uei); } } // REARCHITECT (lamadio): There is a duplicate of this helper in browseui\browmenu.cpp // When modifying this, rev that one as well. void UEMDeletePidl(const GUID *pguidGrp, IShellFolder* psf, LPCITEMIDLIST pidl) { UEMINFO uei; uei.cbSize = sizeof(uei); uei.dwMask = UEIM_HIT; uei.cHit = 0; UEMSetEvent(pguidGrp, UEME_RUNPIDL, (WPARAM)psf, (LPARAM)pidl, &uei); } // // Sortof safe version of ILIsParent which catches when pidlParent or // pidlBelow is NULL. pidlParent can be NULL on systems that don't // have a Common Program Files folder. pidlBelow should never be NULL // but it doesn't hurt to check. // STDAPI_(BOOL) SMILIsAncestor(LPCITEMIDLIST pidlParent, LPCITEMIDLIST pidlBelow) { if (pidlParent && pidlBelow) return ILIsParent(pidlParent, pidlBelow, FALSE); else return FALSE; } HRESULT CStartMenuCallbackBase::_ProcessChangeNotify(SMDATA* psmd, LONG lEvent, LPCITEMIDLIST pidl1, LPCITEMIDLIST pidl2) { switch (lEvent) { case SHCNE_ASSOCCHANGED: SHReValidateDarwinCache(); return S_OK; case SHCNE_RENAMEFOLDER: // NTRAID89654-2000/03/13 (lamadio): We should move the MenuOrder stream as well. 5.5.99 case SHCNE_RENAMEITEM: { LPITEMIDLIST pidlPrograms; LPITEMIDLIST pidlProgramsCommon; LPITEMIDLIST pidlFavorites; SHGetFolderLocation(NULL, CSIDL_PROGRAMS, NULL, 0, &pidlPrograms); SHGetFolderLocation(NULL, CSIDL_COMMON_PROGRAMS, NULL, 0, &pidlProgramsCommon); SHGetFolderLocation(NULL, CSIDL_FAVORITES, NULL, 0, &pidlFavorites); BOOL fPidl1InStartMenu = SMILIsAncestor(pidlPrograms, pidl1) || SMILIsAncestor(pidlProgramsCommon, pidl1); BOOL fPidl1InFavorites = SMILIsAncestor(pidlFavorites, pidl1); // If we're renaming something from the Start Menu if ( fPidl1InStartMenu ||fPidl1InFavorites) { IShellFolder* psfFrom; LPCITEMIDLIST pidlFrom; if (SUCCEEDED(SHBindToParent(pidl1, IID_PPV_ARG(IShellFolder, &psfFrom), &pidlFrom))) { // Into the Start Menu BOOL fPidl2InStartMenu = SMILIsAncestor(pidlPrograms, pidl2) || SMILIsAncestor(pidlProgramsCommon, pidl2); BOOL fPidl2InFavorites = SMILIsAncestor(pidlFavorites, pidl2); if (fPidl2InStartMenu || fPidl2InFavorites) { IShellFolder* psfTo; LPCITEMIDLIST pidlTo; if (SUCCEEDED(SHBindToParent(pidl2, IID_PPV_ARG(IShellFolder, &psfTo), &pidlTo))) { // Then we need to rename it UEMRenamePidl(fPidl1InStartMenu ? &UEMIID_SHELL: &UEMIID_BROWSER, psfFrom, pidlFrom, fPidl2InStartMenu ? &UEMIID_SHELL: &UEMIID_BROWSER, psfTo, pidlTo); psfTo->Release(); } } else { // Otherwise, we delete it. UEMDeletePidl(fPidl1InStartMenu ? &UEMIID_SHELL : &UEMIID_BROWSER, psfFrom, pidlFrom); } psfFrom->Release(); } } ILFree(pidlPrograms); ILFree(pidlProgramsCommon); ILFree(pidlFavorites); } break; case SHCNE_DELETE: // NTRAID89654-2000/03/13 (lamadio): We should nuke the MenuOrder stream as well. 5.5.99 case SHCNE_RMDIR: { IShellFolder* psf; LPCITEMIDLIST pidl; if (SUCCEEDED(SHBindToParent(pidl1, IID_PPV_ARG(IShellFolder, &psf), &pidl))) { // NOTE favorites is the only that will be initialized UEMDeletePidl(psmd->uIdAncestor == IDM_FAVORITES ? &UEMIID_BROWSER : &UEMIID_SHELL, psf, pidl); psf->Release(); } } break; case SHCNE_CREATE: case SHCNE_MKDIR: { IShellFolder* psf; LPCITEMIDLIST pidl; if (SUCCEEDED(SHBindToParent(pidl1, IID_PPV_ARG(IShellFolder, &psf), &pidl))) { UEMINFO uei; uei.cbSize = sizeof(uei); uei.dwMask = UEIM_HIT; uei.cHit = UEM_NEWITEMCOUNT; UEMSetEvent(psmd->uIdAncestor == IDM_FAVORITES? &UEMIID_BROWSER: &UEMIID_SHELL, UEME_RUNPIDL, (WPARAM)psf, (LPARAM)pidl, &uei); psf->Release(); } } break; } return S_FALSE; } HRESULT CStartMenuCallbackBase::_GetSFInfo(SMDATA* psmd, SMINFO* psminfo) { if (psminfo->dwMask & SMIM_FLAGS && (psmd->uIdAncestor == IDM_PROGRAMS || psmd->uIdAncestor == IDM_FAVORITES)) { if (_fExpandoMenus) { psminfo->dwFlags |= _GetDemote(psmd); } // This is a little backwards. If the Restriction is On, Then we allow the feature. if (SHRestricted(REST_GREYMSIADS) && psmd->uIdAncestor == IDM_PROGRAMS) { LPITEMIDLIST pidlFull = FullPidlFromSMData(psmd); if (pidlFull) { if (_IsDarwinAdvertisement(pidlFull)) { psminfo->dwFlags |= SMIF_ALTSTATE; } ILFree(pidlFull); } } if (_ptp2) { _ptp2->ModifySMInfo(psmd, psminfo); } } return S_OK; } STDAPI_(void) SHReValidateDarwinCache() { if (g_hdpaDarwinAds) { ENTERCRITICAL_DARWINADS; int chdpa = DPA_GetPtrCount(g_hdpaDarwinAds); for (int ihdpa = 0; ihdpa < chdpa; ihdpa++) { CDarwinAd* pda = (CDarwinAd*)DPA_FastGetPtr(g_hdpaDarwinAds, ihdpa); if (pda) { pda->CheckInstalled(); } } LEAVECRITICAL_DARWINADS; } } // Determines if a CSIDL is a child of another CSIDL // e.g. // CSIDL_STARTMENU = c:\foo\bar\Start Menu // CSIDL_PROGRAMS = c:\foo\bar\Start Menu\Programs // Return true BOOL IsCSIDLChild(int csidlParent, int csidlChild) { BOOL fChild = FALSE; TCHAR sz1[MAX_PATH]; if (SUCCEEDED(SHGetFolderPath(NULL, csidlParent, NULL, 0, sz1))) { TCHAR sz2[MAX_PATH]; if (SUCCEEDED(SHGetFolderPath(NULL, csidlChild, NULL, 0, sz2))) { TCHAR szCommonRoot[MAX_PATH]; if (PathCommonPrefix(sz1, sz2, szCommonRoot) == lstrlen(sz1)) { fChild = TRUE; } } } return fChild; } // // Now StartMenuChange value is stored seperately for classic startmenu and new startmenu. // So, we need to check which one is currently on before we read the value. // BOOL IsStartMenuChangeNotAllowed(BOOL fStartPanel) { return(IsRestrictedOrUserSetting(HKEY_CURRENT_USER, REST_NOCHANGESTARMENU, TEXT("Advanced"), (fStartPanel ? TEXT("Start_EnableDragDrop") : TEXT("StartMenuChange")), ROUS_DEFAULTALLOW | ROUS_KEYALLOWS)); } // Creates the "Start Menu\\Programs" section of the start menu by // generating a Merged Shell folder, setting the locations into that item // then sets it into the passed IShellMenu. HRESULT CStartMenuCallbackBase::InitializeProgramsShellMenu(IShellMenu* psm) { HKEY hkeyPrograms = NULL; LPITEMIDLIST pidl = NULL; DWORD dwInitFlags = SMINIT_VERTICAL; if (!FeatureEnabled(_fIsStartPanel ? TEXT("Start_ScrollPrograms") : TEXT("StartMenuScrollPrograms"))) dwInitFlags |= SMINIT_MULTICOLUMN; if (IsStartMenuChangeNotAllowed(_fIsStartPanel)) dwInitFlags |= SMINIT_RESTRICT_DRAGDROP | SMINIT_RESTRICT_CONTEXTMENU; if (_fIsStartPanel) dwInitFlags |= SMINIT_TOPLEVEL; HRESULT hr = psm->Initialize(this, IDM_PROGRAMS, IDM_PROGRAMS, dwInitFlags); if (SUCCEEDED(hr)) { _InitializePrograms(); LPCTSTR pszOrderKey = _fIsStartPanel ? STRREG_STARTMENU2 TEXT("\\Programs") : STRREG_STARTMENU TEXT("\\Programs"); // setshellfolder calls need read and write key RegCreateKeyEx(HKEY_CURRENT_USER, pszOrderKey, NULL, NULL, REG_OPTION_NON_VOLATILE, KEY_READ | KEY_WRITE, NULL, &hkeyPrograms, NULL); IShellFolder* psf; BOOL fOptimize = FALSE; DWORD dwSmset = SMSET_TOP; if (_fIsStartPanel) { // Start Panel: Menu: The Programs section is a merge of the // Fast Items and Programs folders with a separator between them. dwSmset |= SMSET_SEPARATEMERGEFOLDER; hr = GetMergedFolder(&psf, &pidl, c_rgmfiProgramsFolderAndFastItems, ARRAYSIZE(c_rgmfiProgramsFolderAndFastItems)); } else { // Classic Start Menu: The Programs section is just the per-user // and common Programs folders merged together hr = GetMergedFolder(&psf, &pidl, c_rgmfiProgramsFolder, ARRAYSIZE(c_rgmfiProgramsFolder)); // We used to register for change notify at CSIDL_STARTMENU and assumed // that CSIDL_PROGRAMS was a child of CSIDL_STARTMENU. Since this wasn't always the // case, I removed the optimization. // Both panes are registered recursive. So, When CSIDL_PROGRAMS _IS_ a // child of CSIDL_STARTMENU we can enter a code path where when destroying // CSIDL_PROGRAMS, we unregister it. This will flush the change nofiy queue // of CSIDL_STARTMENU, and blow away all of the children, including CSIDL_PROGRAMS, // while we are in the middle of destroying it... See the problem? I have been adding // reentrance "Blockers" but this only delayed where we crashed. // What was needed was to determine if Programs was a child of the Start Menu directory. // if it was we need to add the optmimization. If it's not we don't have a problem. // WINDOWS BUG 135156(tybeam): If one of the two is redirected, then this will get optimized // we can't do better than this because both are registed recursive, and this will fault... fOptimize = IsCSIDLChild(CSIDL_STARTMENU, CSIDL_PROGRAMS) || IsCSIDLChild(CSIDL_COMMON_STARTMENU, CSIDL_COMMON_PROGRAMS); if (fOptimize) { dwSmset |= SMSET_DONTREGISTERCHANGENOTIFY; } } if (SUCCEEDED(hr)) { // We should have a pidl from CSIDL_Programs ASSERT(pidl); // We should have a shell folder from the bind. ASSERT(psf); hr = psm->SetShellFolder(psf, pidl, hkeyPrograms, dwSmset); psf->Release(); ILFree(pidl); } if (FAILED(hr)) RegCloseKey(hkeyPrograms); } return hr; } HRESULT GetFolderAndPidl(UINT csidl, IShellFolder **ppsf, LPITEMIDLIST *ppidl) { *ppsf = NULL; HRESULT hr = SHGetFolderLocation(NULL, csidl, NULL, 0, ppidl); if (SUCCEEDED(hr)) { hr = SHBindToObject(NULL, IID_X_PPV_ARG(IShellFolder, *ppidl, ppsf)); if (FAILED(hr)) { ILFree(*ppidl); *ppidl = NULL; } } return hr; } // Creates the "Start Menu\\" section of the start menu by // looking up the csidl, generating the Hkey from HKCU\pszRoot\pszValue, // Initializing the IShellMenu with dwPassInitFlags, then setting the locations // into the passed IShellMenu passing the flags dwSetFlags. HRESULT CStartMenuCallback::InitializeCSIDLShellMenu(int uId, int csidl, LPTSTR pszRoot, LPTSTR pszValue, DWORD dwPassInitFlags, DWORD dwSetFlags, BOOL fAddOpen, IShellMenu* psm) { DWORD dwInitFlags = SMINIT_VERTICAL | dwPassInitFlags; if (IsStartMenuChangeNotAllowed(_fIsStartPanel)) dwInitFlags |= SMINIT_RESTRICT_DRAGDROP | SMINIT_RESTRICT_CONTEXTMENU; psm->Initialize(this, uId, uId, dwInitFlags); LPITEMIDLIST pidl; IShellFolder* psfFolder; HRESULT hr = GetFolderAndPidl(csidl, &psfFolder, &pidl); if (SUCCEEDED(hr)) { HKEY hKey = NULL; if (pszRoot) { TCHAR szPath[MAX_PATH]; StrCpyN(szPath, pszRoot, ARRAYSIZE(szPath)); if (pszValue) { PathAppend(szPath, pszValue); } // setshellfolder calls need read and write key RegCreateKeyEx(HKEY_CURRENT_USER, szPath, NULL, NULL, REG_OPTION_NON_VOLATILE, KEY_READ | KEY_WRITE, NULL, &hKey, NULL); } // Point the menu to the shellfolder hr = psm->SetShellFolder(psfFolder, pidl, hKey, dwSetFlags); if (SUCCEEDED(hr)) { if (fAddOpen && _fAddOpenFolder) { HMENU hMenu = SHLoadMenuPopup(HINST_THISDLL, MENU_STARTMENU_OPENFOLDER); if (hMenu) { psm->SetMenu(hMenu, _hwnd, SMSET_BOTTOM); } } } else RegCloseKey(hKey); psfFolder->Release(); ILFree(pidl); } return hr; } // This generates the Recent | My Documents, My Pictures sub menu. HRESULT CStartMenuCallback::InitializeDocumentsShellMenu(IShellMenu* psm) { HRESULT hr = InitializeCSIDLShellMenu(IDM_RECENT, CSIDL_RECENT, NULL, NULL, SMINIT_RESTRICT_DRAGDROP, SMSET_BOTTOM, FALSE, psm); // Initializing, reset cache bits for top part of menu _fHasMyDocuments = FALSE; _fHasMyPictures = FALSE; return hr; } HRESULT CStartMenuCallback::InitializeFastItemsShellMenu(IShellMenu* psm) { DWORD dwFlags = SMINIT_TOPLEVEL | SMINIT_VERTICAL; if (IsStartMenuChangeNotAllowed(_fIsStartPanel)) dwFlags |= SMINIT_RESTRICT_DRAGDROP | SMINIT_RESTRICT_CONTEXTMENU; HRESULT hr = psm->Initialize(this, 0, ANCESTORDEFAULT, dwFlags); if (SUCCEEDED(hr)) { _InitializePrograms(); // Add the fast item folder to the top of the menu IShellFolder* psfFast; LPITEMIDLIST pidlFast; hr = GetMergedFolder(&psfFast, &pidlFast, c_rgmfiStartMenu, ARRAYSIZE(c_rgmfiStartMenu)); if (SUCCEEDED(hr)) { HKEY hMenuKey = NULL; // WARNING: pmb2->Initialize() will always owns hMenuKey, so don't close it // setshellfolder calls need read and write key RegCreateKeyEx(HKEY_CURRENT_USER, STRREG_STARTMENU, NULL, NULL, REG_OPTION_NON_VOLATILE, KEY_READ | KEY_WRITE, NULL, &hMenuKey, NULL); TraceMsg(TF_MENUBAND, "Root Start Menu Key Is %d", hMenuKey); hr = psm->SetShellFolder(psfFast, pidlFast, hMenuKey, SMSET_TOP | SMSET_NOEMPTY); psfFast->Release(); ILFree(pidlFast); } } return hr; } HRESULT CStartMenuCallback::_InitializeFindMenu(IShellMenu* psm) { HRESULT hr = E_FAIL; psm->Initialize(this, IDM_MENU_FIND, IDM_MENU_FIND, SMINIT_VERTICAL); HMENU hmenu = CreatePopupMenu(); if (hmenu) { ATOMICRELEASE(_pcmFind); if (_ptp) { if (SUCCEEDED(_ptp->GetFindCM(hmenu, TRAY_IDM_FINDFIRST, TRAY_IDM_FINDLAST, &_pcmFind))) { IContextMenu2 *pcm2; _pcmFind->QueryInterface(IID_PPV_ARG(IContextMenu2, &pcm2)); if (pcm2) { pcm2->HandleMenuMsg(WM_INITMENUPOPUP, (WPARAM)hmenu, 0); pcm2->Release(); } } if (_pcmFind) { hr = psm->SetMenu(hmenu, NULL, SMSET_TOP); // Don't Release _pcmFind } } // Since we failed to create the ShellMenu // we need to dispose of this HMENU if (FAILED(hr)) DestroyMenu(hmenu); } return hr; } HRESULT CStartMenuCallback::InitializeSubShellMenu(int idCmd, IShellMenu* psm) { HRESULT hr = E_FAIL; switch(idCmd) { case IDM_PROGRAMS: hr = InitializeProgramsShellMenu(psm); break; case IDM_RECENT: hr = InitializeDocumentsShellMenu(psm); break; case IDM_MENU_FIND: hr = _InitializeFindMenu(psm); break; case IDM_FAVORITES: hr = InitializeCSIDLShellMenu(IDM_FAVORITES, CSIDL_FAVORITES, STRREG_FAVORITES, NULL, 0, SMSET_HASEXPANDABLEFOLDERS | SMSET_USEBKICONEXTRACTION, FALSE, psm); break; case IDM_CONTROLS: hr = InitializeCSIDLShellMenu(IDM_CONTROLS, CSIDL_CONTROLS, STRREG_STARTMENU, TEXT("ControlPanel"), 0, 0, TRUE, psm); break; case IDM_PRINTERS: hr = InitializeCSIDLShellMenu(IDM_PRINTERS, CSIDL_PRINTERS, STRREG_STARTMENU, TEXT("Printers"), 0, 0, TRUE, psm); break; case IDM_MYDOCUMENTS: hr = InitializeCSIDLShellMenu(IDM_MYDOCUMENTS, CSIDL_PERSONAL, STRREG_STARTMENU, TEXT("MyDocuments"), 0, 0, TRUE, psm); break; case IDM_MYPICTURES: hr = InitializeCSIDLShellMenu(IDM_MYPICTURES, CSIDL_MYPICTURES, STRREG_STARTMENU, TEXT("MyPictures"), 0, 0, TRUE, psm); break; case IDM_NETCONNECT: hr = InitializeCSIDLShellMenu(IDM_NETCONNECT, CSIDL_CONNECTIONS, STRREG_STARTMENU, TEXT("NetConnections"), 0, 0, TRUE, psm); break; } return hr; } HRESULT CStartMenuCallback::_GetObject(LPSMDATA psmd, REFIID riid, void** ppvOut) { HRESULT hr = E_FAIL; UINT uId = psmd->uId; ASSERT(ppvOut); ASSERT(IS_VALID_READ_PTR(psmd, SMDATA)); *ppvOut = NULL; if (IsEqualGUID(riid, IID_IShellMenu)) { IShellMenu* psm = NULL; hr = CoCreateInstance(CLSID_MenuBand, NULL, CLSCTX_INPROC_SERVER, IID_PPV_ARG(IShellMenu, &psm)); if (SUCCEEDED(hr)) { hr = InitializeSubShellMenu(uId, psm); if (FAILED(hr)) { psm->Release(); psm = NULL; } } *ppvOut = psm; } else if (IsEqualGUID(riid, IID_IContextMenu)) { // // NOTE - we dont allow users to open the recent folder this way - ZekeL - 1-JUN-99 // because this is really an internal folder and not a user folder. // switch (uId) { case IDM_PROGRAMS: case IDM_FAVORITES: case IDM_MYDOCUMENTS: case IDM_MYPICTURES: case IDM_CONTROLS: case IDM_PRINTERS: case IDM_NETCONNECT: { CStartContextMenu* pcm = new CStartContextMenu(uId); if (pcm) { hr = pcm->QueryInterface(riid, ppvOut); pcm->Release(); } else hr = E_OUTOFMEMORY; } } } return hr; } // // Return S_OK to remove the pidl from enumeration // HRESULT CStartMenuCallbackBase::_FilterPidl(UINT uParent, IShellFolder* psf, LPCITEMIDLIST pidl) { HRESULT hr = S_FALSE; ASSERT(IS_VALID_PIDL(pidl)); ASSERT(IS_VALID_CODE_PTR(psf, IShellFolder)); if (uParent == IDM_PROGRAMS || uParent == IDM_TOPLEVELSTARTMENU) { TCHAR szChild[MAX_PATH]; if (SUCCEEDED(DisplayNameOf(psf, pidl, SHGDN_INFOLDER | SHGDN_FORPARSING, szChild, ARRAYSIZE(szChild)))) { // HACKHACK (lamadio): This code assumes that the Display name // of the Programs and Commons Programs folders are the same. It // also assumes that the "programs" folder in the Start Menu folder // is the same name as the one pointed to by CSIDL_PROGRAMS. // Filter from top level start menu: // Programs, Windows Update, Configure Programs if (_IsTopLevelStartMenu(uParent, psf, pidl)) { if ((_pszPrograms && (0 == lstrcmpi(szChild, _pszPrograms))) || (SHRestricted(REST_NOUPDATEWINDOWS) && _pszWindowsUpdate && (0 == lstrcmpi(szChild, _pszWindowsUpdate))) || (SHRestricted(REST_NOSMCONFIGUREPROGRAMS) && _pszConfigurePrograms && (0 == lstrcmpi(szChild, _pszConfigurePrograms)))) { hr = S_OK; } } else { // IDM_PROGRAMS // Filter from Programs: Administrative tools. if (!_fShowAdminTools && _pszAdminTools && lstrcmpi(szChild, _pszAdminTools) == 0) { hr = S_OK; } } } } return hr; } BOOL LinkGetInnerPidl(IShellFolder *psf, LPCITEMIDLIST pidl, LPITEMIDLIST *ppidlOut, DWORD *pdwAttr) { *ppidlOut = NULL; IShellLink *psl; HRESULT hr = psf->GetUIObjectOf(NULL, 1, &pidl, IID_X_PPV_ARG(IShellLink, NULL, &psl)); if (SUCCEEDED(hr)) { psl->GetIDList(ppidlOut); if (*ppidlOut) { if (FAILED(SHGetAttributesOf(*ppidlOut, pdwAttr))) { ILFree(*ppidlOut); *ppidlOut = NULL; } } psl->Release(); } return (*ppidlOut != NULL); } // // _FilterRecentPidl() // the Recent Documents folder can now (NT5) have more than 15 or // so documents, but we only want to show the 15 most recent that we always have on // the start menu. this means that we need to filter out all folders and // anything more than MAXRECENTDOCS // HRESULT CStartMenuCallback::_FilterRecentPidl(IShellFolder* psf, LPCITEMIDLIST pidl) { HRESULT hr = S_OK; ASSERT(IS_VALID_PIDL(pidl)); ASSERT(IS_VALID_CODE_PTR(psf, IShellFolder)); ASSERT(_cRecentDocs != -1); ASSERT(_cRecentDocs <= MAXRECENTDOCS); // if we already reached our limit, dont go over... if (_pmruRecent && (_cRecentDocs < MAXRECENTDOCS)) { // we now must take a looksee for it... int iItem; DWORD dwAttr = SFGAO_FOLDER | SFGAO_BROWSABLE; LPITEMIDLIST pidlTrue; // need to find out if the link points to a folder... // because we dont want if (SUCCEEDED(_pmruRecent->FindData((BYTE *) pidl, ILGetSize(pidl), &iItem)) && LinkGetInnerPidl(psf, pidl, &pidlTrue, &dwAttr)) { if (!(dwAttr & SFGAO_FOLDER)) { // we have a link to something that isnt a folder hr = S_FALSE; _cRecentDocs++; } ILFree(pidlTrue); } } // return S_OK if you dont want to show this item... return hr; } HRESULT CStartMenuCallback::_HandleAccelerator(TCHAR ch, SMDATA* psmdata) { // Since we renamed the 'Find' menu to 'Search' the PMs wanted to have // an upgrade path for users (So they can continue to use the old accelerator // on the new menu item.) // To enable this, when toolbar detects that there is not an item in the menu // that contains the key that has been pressed, then it sends a TBN_ACCL. // This is intercepted by mnbase, and translated into SMC_ACCEL. if (CharUpper((LPTSTR)ch) == CharUpper((LPTSTR)_szFindMnemonic[0])) { psmdata->uId = IDM_MENU_FIND; return S_OK; } return S_FALSE; } HRESULT CStartMenuCallback::_GetTip(LPWSTR pstrTitle, LPWSTR pstrTip) { if (pstrTitle == NULL || pstrTip == NULL) { return S_FALSE; } // all callers must pass MAX_PATH LoadString(HINST_THISDLL, IDS_CHEVRONTIPTITLE, pstrTitle, MAX_PATH); LoadString(HINST_THISDLL, IDS_CHEVRONTIP, pstrTip, MAX_PATH); // Why would this fail? ASSERT(pstrTitle[0] != L'\0' && pstrTip[0] != L'\0'); return S_OK; } STDAPI CStartMenu_CreateInstance(LPUNKNOWN punkOuter, REFIID riid, void **ppvOut) { HRESULT hr = E_FAIL; IMenuPopup* pmp = NULL; *ppvOut = NULL; CStartMenuCallback* psmc = new CStartMenuCallback(); if (psmc) { IShellMenu* psm; hr = CoCreateInstance(CLSID_MenuBand, NULL, CLSCTX_INPROC_SERVER, IID_PPV_ARG(IShellMenu, &psm)); if (SUCCEEDED(hr)) { hr = CoCreateInstance(CLSID_MenuDeskBar, punkOuter, CLSCTX_INPROC_SERVER, IID_PPV_ARG(IMenuPopup, &pmp)); if (SUCCEEDED(hr)) { IBandSite* pbs; hr = CoCreateInstance(CLSID_MenuBandSite, NULL, CLSCTX_INPROC_SERVER, IID_PPV_ARG(IBandSite, &pbs)); if (SUCCEEDED(hr)) { hr = pmp->SetClient(pbs); if (SUCCEEDED(hr)) { IDeskBand* pdb; hr = psm->QueryInterface(IID_PPV_ARG(IDeskBand, &pdb)); if (SUCCEEDED(hr)) { hr = pbs->AddBand(pdb); pdb->Release(); } } pbs->Release(); } // Don't free pmp. We're using it below. } if (SUCCEEDED(hr)) { // This is so the ref counting happens correctly. hr = psm->Initialize(psmc, 0, 0, SMINIT_VERTICAL | SMINIT_TOPLEVEL); if (SUCCEEDED(hr)) { // if this fails, we don't get that part of the menu // this is okay since it can happen if the start menu is redirected // to where we dont have access. psmc->InitializeFastItemsShellMenu(psm); } } psm->Release(); } psmc->Release(); } if (SUCCEEDED(hr)) { hr = pmp->QueryInterface(riid, ppvOut); } else { // We need to do this so that it does a cascading delete IUnknown_SetSite(pmp, NULL); } if (pmp) pmp->Release(); return hr; } // IUnknown STDMETHODIMP CStartContextMenu::QueryInterface(REFIID riid, void **ppvObj) { static const QITAB qit[] = { QITABENT(CStartContextMenu, IContextMenu), { 0 }, }; return QISearch(this, qit, riid, ppvObj); } STDMETHODIMP_(ULONG) CStartContextMenu::AddRef(void) { return ++_cRef; } STDMETHODIMP_(ULONG) CStartContextMenu::Release(void) { ASSERT(_cRef > 0); _cRef--; if( _cRef > 0) return _cRef; delete this; return 0; } // IContextMenu STDMETHODIMP CStartContextMenu::QueryContextMenu(HMENU hmenu, UINT indexMenu, UINT idCmdFirst, UINT idCmdLast, UINT uFlags) { HRESULT hr = E_FAIL; HMENU hmenuStartMenu = SHLoadMenuPopup(HINST_THISDLL, MENU_STARTMENUSTATICITEMS); if (hmenuStartMenu) { TCHAR szCommon[MAX_PATH]; BOOL fAddCommon = (S_OK == SHGetFolderPath(NULL, CSIDL_COMMON_STARTMENU, NULL, 0, szCommon)); if (fAddCommon) fAddCommon = IsUserAnAdmin(); // Since we don't show this on the start button when the user is not an admin, don't show it here... I guess... if (_idCmd != IDM_PROGRAMS || !fAddCommon) { DeleteMenu(hmenuStartMenu, SMCM_OPEN_ALLUSERS, MF_BYCOMMAND); DeleteMenu(hmenuStartMenu, SMCM_EXPLORE_ALLUSERS, MF_BYCOMMAND); } if (Shell_MergeMenus(hmenu, hmenuStartMenu, 0, indexMenu, idCmdLast, uFlags)) { SetMenuDefaultItem(hmenu, 0, MF_BYPOSITION); _SHPrettyMenu(hmenu); hr = ResultFromShort(GetMenuItemCount(hmenuStartMenu)); } DestroyMenu(hmenuStartMenu); } return hr; } STDMETHODIMP CStartContextMenu::InvokeCommand(LPCMINVOKECOMMANDINFO lpici) { HRESULT hr = E_FAIL; if (HIWORD64(lpici->lpVerb) == 0) { BOOL fAllUsers = FALSE; BOOL fOpen = TRUE; switch(LOWORD(lpici->lpVerb)) { case SMCM_OPEN_ALLUSERS: fAllUsers = TRUE; case SMCM_OPEN: // fOpen = TRUE; break; case SMCM_EXPLORE_ALLUSERS: fAllUsers = TRUE; case SMCM_EXPLORE: fOpen = FALSE; break; default: return S_FALSE; } hr = ExecStaticStartMenuItem(_idCmd, fAllUsers, fOpen); } // Ahhh Don't handle verbs!!! return hr; } STDMETHODIMP CStartContextMenu::GetCommandString(UINT_PTR idCmd, UINT uType, UINT *pRes, LPSTR pszName, UINT cchMax) { return E_NOTIMPL; } //**************************************************************************** // // CPersonalStartMenuCallback class CPersonalProgramsMenuCallback : public CStartMenuCallbackBase { public: CPersonalProgramsMenuCallback() : CStartMenuCallbackBase(TRUE) { } // *** IShellMenuCallback methods *** STDMETHODIMP CallbackSM(LPSMDATA psmd, UINT uMsg, WPARAM wParam, LPARAM lParam); // *** IObjectWithSite methods *** (overriding CObjectWithSite) STDMETHODIMP SetSite(IUnknown* punk); public: HRESULT Initialize(IShellMenu *psm) { return InitializeProgramsShellMenu(psm); } private: void _UpdateTrayPriv(); }; // // Throw away any previous TrayPriv2 and try to find a new one. // // Throwing it away is important to break circular reference loops. // // Trying to find it will typically fail at SetSite since when we are // given our site, CDesktopHost hasn't connected at the top yet so // we are unable to find him. But he will be there by the time // SMC_INITMENU arrives, so we try again then. // void CPersonalProgramsMenuCallback::_UpdateTrayPriv() { ATOMICRELEASE(_ptp2); IObjectWithSite *pows; if (SUCCEEDED(IUnknown_QueryService(_punkSite, SID_SMenuPopup, IID_PPV_ARG(IObjectWithSite, &pows)))) { pows->GetSite(IID_PPV_ARG(ITrayPriv2, &_ptp2)); pows->Release(); } } STDMETHODIMP CPersonalProgramsMenuCallback::SetSite(IUnknown* punk) { HRESULT hr = CObjectWithSite::SetSite(punk); _UpdateTrayPriv(); return hr; } STDMETHODIMP CPersonalProgramsMenuCallback::CallbackSM(LPSMDATA psmd, UINT uMsg, WPARAM wParam, LPARAM lParam) { HRESULT hr = S_FALSE; switch (uMsg) { case SMC_INITMENU: _UpdateTrayPriv(); break; case SMC_GETSFINFO: hr = _GetSFInfo(psmd, (SMINFO*)lParam); break; case SMC_NEWITEM: hr = _HandleNew(psmd); break; case SMC_FILTERPIDL: ASSERT(psmd->dwMask & SMDM_SHELLFOLDER); hr = _FilterPidl(psmd->uIdParent, psmd->psf, psmd->pidlItem); break; case SMC_GETSFINFOTIP: if (!FeatureEnabled(TEXT("ShowInfoTip"))) hr = E_FAIL; // E_FAIL means don't show. S_FALSE means show default break; case SMC_PROMOTE: hr = _Promote(psmd, (DWORD)wParam); break; case SMC_SHCHANGENOTIFY: { PSMCSHCHANGENOTIFYSTRUCT pshf = (PSMCSHCHANGENOTIFYSTRUCT)lParam; hr = _ProcessChangeNotify(psmd, pshf->lEvent, pshf->pidl1, pshf->pidl2); } break; case SMC_REFRESH: _RefreshSettings(); break; } return hr; } STDAPI CPersonalStartMenu_CreateInstance(LPUNKNOWN punkOuter, REFIID riid, void **ppvOut) { HRESULT hr; *ppvOut = NULL; IShellMenu *psm; hr = CoCreateInstance(CLSID_MenuBand, NULL, CLSCTX_INPROC_SERVER, IID_PPV_ARG(IShellMenu, &psm)); if (SUCCEEDED(hr)) { CPersonalProgramsMenuCallback *psmc = new CPersonalProgramsMenuCallback(); if (psmc) { hr = psmc->Initialize(psm); if (SUCCEEDED(hr)) { // SetShellFolder takes ownership of hkCustom hr = psm->QueryInterface(riid, ppvOut); } psmc->Release(); } else { hr = E_OUTOFMEMORY; } psm->Release(); } return hr; }