|
|
#include "stdafx.h"
#include "proglist.h"
#include "uemapp.h"
#include <shdguid.h>
#include "shguidp.h" // IID_IInitializeObject
#include <pshpack4.h>
#include <idhidden.h> // Note! idhidden.h requires pack4
#include <poppack.h>
#include <userenv.h> // GetProfileType
#include <desktray.h>
#include "tray.h"
#define STRSAFE_NO_CB_FUNCTIONS
#define STRSAFE_NO_DEPRECATE
#include <strsafe.h>
// Global cache item being passed from the background task to the start panel ByUsage.
CMenuItemsCache *g_pMenuCache;
// From startmnu.cpp
HRESULT Tray_RegisterHotKey(WORD wHotKey, LPCITEMIDLIST pidlParent, LPCITEMIDLIST pidl);
#define TF_PROGLIST 0x00000020
#define CH_DARWINMARKER TEXT('\1') // Illegal filename character
#define IsDarwinPath(psz) ((psz)[0] == CH_DARWINMARKER)
// Largest date representable in FILETIME - rolls over in the year 30828
static const FILETIME c_ftNever = { 0xFFFFFFFF, 0x7FFFFFFF };
void GetStartTime(FILETIME *pft) { //
// If policy says "Don't offer new apps", then set the New App
// threshold to some impossible time in the future.
//
if (!SHRegGetBoolUSValue(REGSTR_EXPLORER_ADVANCED, REGSTR_VAL_DV2_NOTIFYNEW, FALSE, TRUE)) { *pft = c_ftNever; return; }
FILETIME ftNow; GetSystemTimeAsFileTime(&ftNow);
DWORD dwSize = sizeof(*pft); //Check to see if we have the StartMenu start Time for this user saved in the registry.
if(SHRegGetUSValue(DV2_REGPATH, DV2_SYSTEM_START_TIME, NULL, pft, &dwSize, FALSE, NULL, 0) != ERROR_SUCCESS) { // Get the current system time as the start time. If any app is launched after
// this time, that will result in the OOBE message going away!
*pft = ftNow;
dwSize = sizeof(*pft);
//Save this time as the StartMenu start time for this user.
SHRegSetUSValue(DV2_REGPATH, DV2_SYSTEM_START_TIME, REG_BINARY, pft, dwSize, SHREGSET_FORCE_HKCU); }
//
// Thanks to roaming and reinstalls, the user may have installed a new
// OS since they first turned on the Start Menu, so bump forwards to
// the time the OS was installed (plus some fudge to account for the
// time it takes to run Setup) so all the Accessories don't get marked
// as "New".
//
DWORD dwTime; dwSize = sizeof(dwTime); if (SHGetValue(HKEY_LOCAL_MACHINE, TEXT("Software\\Microsoft\\Windows NT\\CurrentVersion"), TEXT("InstallDate"), NULL, &dwTime, &dwSize) == ERROR_SUCCESS) { // Sigh, InstallDate is stored in UNIX time format, not FILETIME,
// so convert it to FILETIME. Q167296 shows how to do this.
LONGLONG ll = Int32x32To64(dwTime, 10000000) + 116444736000000000;
// Add some fudge to account for how long it takes to run Setup
ll += FT_ONEHOUR * 5; // five hours should be plenty
FILETIME ft; SetFILETIMEfromInt64(&ft, ll);
//
// But don't jump forwards into the future
//
if (::CompareFileTime(&ft, &ftNow) > 0) { ft = ftNow; }
if (::CompareFileTime(pft, &ft) < 0) { *pft = ft; }
}
//
// If this is a roaming profile, then don't count anything that
// happened before the user logged on because we don't want to mark
// apps as new just because it roamed in with the user at logon.
// We actually key off of the start time of Explorer, because
// Explorer starts after the profiles are sync'd so we don't need
// a fudge factor.
//
DWORD dwType; if (GetProfileType(&dwType) && (dwType & (PT_TEMPORARY | PT_ROAMING))) { FILETIME ft, ftIgnore; if (GetProcessTimes(GetCurrentProcess(), &ft, &ftIgnore, &ftIgnore, &ftIgnore)) { if (::CompareFileTime(pft, &ft) < 0) { *pft = ft; } } }
}
//****************************************************************************
//
// How the pieces fit together...
//
//
// Each ByUsageRoot consists of a ByUsageShortcutList.
//
// A ByUsageShortcutList is a list of shortcuts.
//
// Each shortcut references a ByUsageAppInfo. Multiple shortcuts can
// reference the same ByUsageAppInfo if they are all shortcuts to the same
// app.
//
// The ByUsageAppInfo::_cRef is the number of ByUsageShortcut's that
// reference it.
//
// A master list of all ByUsageAppInfo's is kept in _dpaAppInfo.
//
//****************************************************************************
//
// Helper template for destroying DPA's.
//
// DPADELETEANDDESTROY(dpa);
//
// invokes the "delete" method on each pointer in the DPA,
// then destroys the DPA.
//
template<class T> BOOL CALLBACK _DeleteCB(T *self, LPVOID) { delete self; return TRUE; }
template<class T> void DPADELETEANDDESTROY(CDPA<T> &dpa) { if (dpa) { dpa.DestroyCallback(_DeleteCB<T>, NULL); ASSERT(dpa == NULL); } }
//****************************************************************************
void ByUsageRoot::Reset() { DPADELETEANDDESTROY(_sl); DPADELETEANDDESTROY(_slOld);
ILFree(_pidl); _pidl = NULL; };
//****************************************************************************
class ByUsageDir { IShellFolder *_psf; // the folder interface
LPITEMIDLIST _pidl; // the absolute pidl for this folder
LONG _cRef; // Reference count
ByUsageDir() : _cRef(1) { } ~ByUsageDir() { ATOMICRELEASE(_psf); ILFree(_pidl); }
public: // Creates a ByUsageDir for CSIDL_DESKTOP. All other
// ByUsageDir's come from this.
static ByUsageDir *CreateDesktop() { ByUsageDir *self = new ByUsageDir(); if (self) { ASSERT(self->_pidl == NULL); if (SUCCEEDED(SHGetDesktopFolder(&self->_psf))) { // all is well
return self; } delete self; }
return NULL; }
// pdir = parent folder
// pidl = new folder location relative to parent
static ByUsageDir *Create(ByUsageDir *pdir, LPCITEMIDLIST pidl) { ByUsageDir *self = new ByUsageDir(); if (self) { LPCITEMIDLIST pidlRoot = pdir->_pidl; self->_pidl = ILCombine(pidlRoot, pidl); if (self->_pidl) { IShellFolder *psfRoot = pdir->_psf; if (SUCCEEDED(SHBindToObject(psfRoot, IID_X_PPV_ARG(IShellFolder, pidl, &self->_psf)))) { // all is well
return self; } }
delete self; } return NULL; }
void AddRef(); void Release(); IShellFolder *Folder() const { return _psf; } LPCITEMIDLIST Pidl() const { return _pidl; } };
void ByUsageDir::AddRef() { InterlockedIncrement(&_cRef); }
void ByUsageDir::Release() { ASSERT( 0 != _cRef ); if (InterlockedDecrement(&_cRef) == 0) { delete this; } }
//****************************************************************************
class ByUsageItem : public PaneItem { public: LPITEMIDLIST _pidl; // relative pidl
ByUsageDir *_pdir; // Parent directory
UEMINFO _uei; /* Usage info (for sorting) */
static ByUsageItem *Create(ByUsageShortcut *pscut); static ByUsageItem *CreateSeparator();
ByUsageItem() { EnableDropTarget(); } ~ByUsageItem();
ByUsageDir *Dir() const { return _pdir; } IShellFolder *ParentFolder() const { return _pdir->Folder(); } LPCITEMIDLIST RelativePidl() const { return _pidl; } LPITEMIDLIST CreateFullPidl() const { return ILCombine(_pdir->Pidl(), _pidl); } void SetRelativePidl(LPITEMIDLIST pidlNew) { ILFree(_pidl); _pidl = pidlNew; }
virtual BOOL IsEqual(PaneItem *pItem) const { ByUsageItem *pbuItem = reinterpret_cast<ByUsageItem *>(pItem); BOOL fIsEqual = FALSE; if (_pdir == pbuItem->_pdir) { // Do we have identical pidls?
// Note: this test needs to be fast, and does not need to be exact, so we don't use pidl binding here.
UINT usize1 = ILGetSize(_pidl); UINT usize2 = ILGetSize(pbuItem->_pidl); if (usize1 == usize2) { fIsEqual = (memcmp(_pidl, pbuItem->_pidl, usize1) == 0); } }
return fIsEqual; } };
inline BOOL ByUsage::_IsPinned(ByUsageItem *pitem) { return pitem->Dir() == _pdirDesktop; }
//****************************************************************************
// Each app referenced by a command line is saved in one of these
// structures.
class ByUsageAppInfo { // "papp"
public: ByUsageShortcut *_pscutBest;// best candidate so far
ByUsageShortcut *_pscutBestSM;// best candidate so far on Start Menu (excludes Desktop)
UEMINFO _ueiBest; // info about best candidate so far
UEMINFO _ueiTotal; // cumulative information
LPTSTR _pszAppPath; // path to app in question
FILETIME _ftCreated; // when was file created?
bool _fNew; // is app new?
bool _fPinned; // is app referenced by a pinned item?
bool _fIgnoreTimestamp; // Darwin apps have unreliable timestamps
private: LONG _cRef; // reference count
public:
// WARNING! Initialize() is called multiple times, so make sure
// that you don't leak anything
BOOL Initialize(LPCTSTR pszAppPath, CMenuItemsCache *pmenuCache, BOOL fCheckNew, bool fIgnoreTimestamp) { TraceMsg(TF_PROGLIST, "%p.ai.Init(%s)", this, pszAppPath); ASSERT(IsBlank());
_fIgnoreTimestamp = fIgnoreTimestamp || IsDarwinPath(pszAppPath);
// Note! Str_SetPtr is last so there's nothing to free on failure
if (_fIgnoreTimestamp) { // just save the path; ignore the timestamp
if (Str_SetPtr(&_pszAppPath, pszAppPath)) { _fNew = TRUE; return TRUE; } } else if (Str_SetPtr(&_pszAppPath, pszAppPath)) { if (fCheckNew && GetFileCreationTime(pszAppPath, &_ftCreated)) { _fNew = pmenuCache->IsNewlyCreated(&_ftCreated); } return TRUE; }
return FALSE; }
static ByUsageAppInfo *Create() { ByUsageAppInfo *papp = new ByUsageAppInfo; if (papp) { ASSERT(papp->IsBlank()); // will be AddRef()d by caller
ASSERT(papp->_pszAppPath == NULL); } return papp; }
~ByUsageAppInfo() { TraceMsg(TF_PROGLIST, "%p.ai.~", this); ASSERT(IsBlank()); Str_SetPtr(&_pszAppPath, NULL); }
// Notice! When the reference count goes to zero, we do not delete
// the item. That's because there is still a reference to it in
// the _dpaAppInfo DPA. Instead, we'll recycle items whose refcount
// is zero.
// NTRAID:135696 potential race condition against background enumeration
void AddRef() { InterlockedIncrement(&_cRef); } void Release() { ASSERT( 0 != _cRef ); InterlockedDecrement(&_cRef); } inline BOOL IsBlank() { return _cRef == 0; } inline BOOL IsNew() { return _fNew; } inline BOOL IsAppPath(LPCTSTR pszAppPath) { return lstrcmpi(_pszAppPath, pszAppPath) == 0; } const FILETIME& GetCreatedTime() const { return _ftCreated; } inline const FILETIME *GetFileTime() const { return &_ueiTotal.ftExecute; }
inline LPTSTR GetAppPath() const { return _pszAppPath; }
void GetUEMInfo(OUT UEMINFO *puei) { _GetUEMPathInfo(_pszAppPath, puei); }
void CombineUEMInfo(IN const UEMINFO *pueiNew, BOOL fNew = TRUE, BOOL fIsDesktop = FALSE) { // Accumulate the points
_ueiTotal.cHit += pueiNew->cHit;
// Take the most recent execution time
if (CompareFileTime(&pueiNew->ftExecute, &_ueiTotal.ftExecute) > 0) { _ueiTotal.ftExecute = pueiNew->ftExecute; }
// If anybody contributing to those app is no longer new, then
// the app stops being new.
// If the item is on the desktop, we don't track its newness,
// but we don't want to invalidate the newness of the app either
if (!fIsDesktop && !fNew) _fNew = FALSE; }
//
// The app UEM info is "old" if the execution time is more
// than an hour after the install time.
//
inline BOOL _IsUEMINFONew(const UEMINFO *puei) { return FILETIMEtoInt64(puei->ftExecute) < FILETIMEtoInt64(_ftCreated) + ByUsage::FT_NEWAPPGRACEPERIOD(); }
//
// Prepare for a new enumeration.
//
static BOOL CALLBACK EnumResetCB(ByUsageAppInfo *self, CMenuItemsCache *pmenuCache) { self->_pscutBest = NULL; self->_pscutBestSM = NULL; self->_fPinned = FALSE; ZeroMemory(&self->_ueiBest, sizeof(self->_ueiBest)); ZeroMemory(&self->_ueiTotal, sizeof(self->_ueiTotal)); if (self->_fNew && !self->_fIgnoreTimestamp) { self->_fNew = pmenuCache->IsNewlyCreated(&self->_ftCreated); } return TRUE; }
static BOOL CALLBACK EnumGetFileCreationTime(ByUsageAppInfo *self, CMenuItemsCache *pmenuCache) { if (!self->IsBlank() && !self->_fIgnoreTimestamp && GetFileCreationTime(self->_pszAppPath, &self->_ftCreated)) { self->_fNew = pmenuCache->IsNewlyCreated(&self->_ftCreated); } return TRUE; }
ByUsageItem *CreateByUsageItem(); };
//****************************************************************************
class ByUsageShortcut { #ifdef DEBUG
ByUsageShortcut() { } // make constructor private
#endif
ByUsageDir * _pdir; // folder that contains this shortcut
LPITEMIDLIST _pidl; // pidl relative to parent
ByUsageAppInfo * _papp; // associated EXE
FILETIME _ftCreated; // time shortcut was created
bool _fNew; // new app?
bool _fInteresting; // Is this a candidate for the MFU list?
bool _fDarwin; // Is this a Darwin shortcut?
public:
// Accessors
LPCITEMIDLIST RelativePidl() const { return _pidl; } ByUsageDir *Dir() const { return _pdir; } LPCITEMIDLIST ParentPidl() const { return _pdir->Pidl(); } IShellFolder *ParentFolder() const { return _pdir->Folder(); } ByUsageAppInfo *App() const { return _papp; } bool IsNew() const { return _fNew; } bool SetNew(bool fNew) { return _fNew = fNew; } const FILETIME& GetCreatedTime() const { return _ftCreated; } bool IsInteresting() const { return _fInteresting; } bool SetInteresting(bool fInteresting) { return _fInteresting = fInteresting; } bool IsDarwin() { return _fDarwin; }
LPITEMIDLIST CreateFullPidl() const { return ILCombine(ParentPidl(), RelativePidl()); }
LPCITEMIDLIST UpdateRelativePidl(ByUsageHiddenData *phd);
void SetApp(ByUsageAppInfo *papp) { if (_papp) _papp->Release(); _papp = papp; if (papp) papp->AddRef(); }
static ByUsageShortcut *Create(ByUsageDir *pdir, LPCITEMIDLIST pidl, ByUsageAppInfo *papp, bool fDarwin, BOOL fForce = FALSE) { ASSERT(pdir); ASSERT(pidl);
ByUsageShortcut *pscut = new ByUsageShortcut; if (pscut) { TraceMsg(TF_PROGLIST, "%p.scut()", pscut);
pscut->_fNew = TRUE; // will be set to FALSE later
pscut->_pdir = pdir; pdir->AddRef(); pscut->SetApp(papp); pscut->_fDarwin = fDarwin; pscut->_pidl = ILClone(pidl); if (pscut->_pidl) { LPTSTR pszShortcutName = _DisplayNameOf(pscut->ParentFolder(), pscut->RelativePidl(), SHGDN_FORPARSING); if (pszShortcutName && GetFileCreationTime(pszShortcutName, &pscut->_ftCreated)) { // Woo-hoo, all is well
} else if (fForce) { // The item was forced -- create it even though
// we don't know what it is.
} else { delete pscut; pscut = NULL; } SHFree(pszShortcutName); } else { delete pscut; pscut = NULL; } } return pscut; }
~ByUsageShortcut() { TraceMsg(TF_PROGLIST, "%p.scut.~", this); if (_pdir) _pdir->Release(); if (_papp) _papp->Release(); ILFree(_pidl); // ILFree ignores NULL
}
ByUsageItem *CreatePinnedItem(int iPinPos);
void GetUEMInfo(OUT UEMINFO *puei) { _GetUEMPidlInfo(_pdir->Folder(), _pidl, puei); } };
//****************************************************************************
ByUsageItem *ByUsageItem::Create(ByUsageShortcut *pscut) { ASSERT(pscut); ByUsageItem *pitem = new ByUsageItem; if (pitem) { pitem->_pdir = pscut->Dir(); pitem->_pdir->AddRef();
pitem->_pidl = ILClone(pscut->RelativePidl()); if (pitem->_pidl) { return pitem; } } delete pitem; // "delete" can handle NULL
return NULL; }
ByUsageItem *ByUsageItem::CreateSeparator() { ByUsageItem *pitem = new ByUsageItem; if (pitem) { pitem->_iPinPos = PINPOS_SEPARATOR; } return pitem; }
ByUsageItem::~ByUsageItem() { ILFree(_pidl); if (_pdir) _pdir->Release(); }
ByUsageItem *ByUsageAppInfo::CreateByUsageItem() { ASSERT(_pscutBest); ByUsageItem *pitem = ByUsageItem::Create(_pscutBest); if (pitem) { pitem->_uei = _ueiTotal; } return pitem; }
ByUsageItem *ByUsageShortcut::CreatePinnedItem(int iPinPos) { ASSERT(iPinPos >= 0);
ByUsageItem *pitem = ByUsageItem::Create(this); if (pitem) { pitem->_iPinPos = iPinPos; } return pitem; }
//****************************************************************************
//
// The hidden data for the Programs list is of the following form:
//
// WORD cb; // hidden item size
// WORD wPadding; // padding
// IDLHID idl; // IDLHID_STARTPANEDATA
// int iUnused; // (was icon index)
// WCHAR LocalMSIPath[]; // variable-length string
// WCHAR TargetPath[]; // variable-length string
// WCHAR AltName[]; // variable-length string
//
// AltName is the alternate display name for the EXE.
//
// The hidden data is never accessed directly. We always operate on the
// ByUsageHiddenData structure, and there are special methods for
// transferring data between this structure and the pidl.
//
// The hidden data is appended to the pidl, with a "wOffset" backpointer
// at the very end.
//
// The TargetPath is stored as an unexpanded environment string.
// (I.e., you have to ExpandEnvironmentStrings before using them.)
//
// As an added bonus, the TargetPath might be a GUID (product code)
// if it is really a Darwin shortcut.
//
class ByUsageHiddenData { public: WORD _wHotKey; // Hot Key
LPWSTR _pwszMSIPath; // SHAlloc
LPWSTR _pwszTargetPath; // SHAlloc
LPWSTR _pwszAltName; // SHAlloc
public:
void Init() { _wHotKey = 0; _pwszMSIPath = NULL; _pwszTargetPath = NULL; _pwszAltName = NULL; }
BOOL IsClear() // are all fields at initial values?
{ return _wHotKey == 0 && _pwszMSIPath == NULL && _pwszTargetPath == NULL && _pwszAltName == NULL; }
ByUsageHiddenData() { Init(); }
enum { BUHD_HOTKEY = 0x0000, // so cheap we always fetch it
BUHD_MSIPATH = 0x0001, BUHD_TARGETPATH = 0x0002, BUHD_ALTNAME = 0x0004, BUHD_ALL = -1, };
BOOL Get(LPCITEMIDLIST pidl, UINT buhd); // Load from pidl
LPITEMIDLIST Set(LPITEMIDLIST pidl); // Save to pidl
void Clear() { SHFree(_pwszMSIPath); SHFree(_pwszTargetPath); SHFree(_pwszAltName); Init(); }
static LPTSTR GetAltName(LPCITEMIDLIST pidl); static LPITEMIDLIST SetAltName(LPITEMIDLIST pidl, LPCTSTR ptszNewName); void LoadFromShellLink(IShellLink *psl); HRESULT UpdateMSIPath();
private: static LPBYTE _ParseString(LPBYTE pbHidden, LPWSTR *ppwszOut, LPITEMIDLIST pidlMax, BOOL fSave); static LPBYTE _AppendString(LPBYTE pbHidden, LPWSTR pwsz); };
//
// We are parsing a string out of a pidl, so we have to be extra careful
// to watch out for data corruption.
//
// pbHidden = next byte to parse (or NULL to stop parsing)
// ppwszOut receives parsed string if fSave = TRUE
// pidlMax = start of next pidl; do not parse past this point
// fSave = should we save the string in ppwszOut?
//
LPBYTE ByUsageHiddenData::_ParseString(LPBYTE pbHidden, LPWSTR *ppwszOut, LPITEMIDLIST pidlMax, BOOL fSave) { if (!pbHidden) return NULL;
LPNWSTR pwszSrc = (LPNWSTR)pbHidden; LPNWSTR pwsz = pwszSrc; LPNWSTR pwszLast = (LPNWSTR)pidlMax - 1;
//
// We cannot use ualstrlenW because that might scan past pwszLast
// and fault.
//
while (pwsz < pwszLast && *pwsz) { pwsz++; }
// Corrupted data -- no null terminator found.
if (pwsz >= pwszLast) return NULL;
pwsz++; // Step pwsz over the terminating NULL
UINT cb = (UINT)((LPBYTE)pwsz - (LPBYTE)pwszSrc); if (fSave) { *ppwszOut = (LPWSTR)SHAlloc(cb); if (*ppwszOut) { CopyMemory(*ppwszOut, pbHidden, cb); } else { return NULL; } } pbHidden += cb; ASSERT(pbHidden == (LPBYTE)pwsz); return pbHidden; }
BOOL ByUsageHiddenData::Get(LPCITEMIDLIST pidl, UINT buhd) { ASSERT(IsClear());
PCIDHIDDEN pidhid = ILFindHiddenID(pidl, IDLHID_STARTPANEDATA); if (!pidhid) { return FALSE; }
// Do not access bytes after pidlMax
LPITEMIDLIST pidlMax = _ILNext((LPITEMIDLIST)pidhid);
LPBYTE pbHidden = ((LPBYTE)pidhid) + sizeof(HIDDENITEMID);
// Skip the iUnused value
// Note: if you someday choose to use it, you must read it as
// _iWhatever = *(UNALIGNED int *)pbHidden;
pbHidden += sizeof(int);
// HotKey
_wHotKey = *(UNALIGNED WORD *)pbHidden; pbHidden += sizeof(_wHotKey);
pbHidden = _ParseString(pbHidden, &_pwszMSIPath, pidlMax, buhd & BUHD_MSIPATH); pbHidden = _ParseString(pbHidden, &_pwszTargetPath, pidlMax, buhd & BUHD_TARGETPATH); pbHidden = _ParseString(pbHidden, &_pwszAltName, pidlMax, buhd & BUHD_ALTNAME);
if (pbHidden) { return TRUE; } else { Clear(); return FALSE; } }
LPBYTE ByUsageHiddenData::_AppendString(LPBYTE pbHidden, LPWSTR pwsz) { LPWSTR pwszOut = (LPWSTR)pbHidden;
// The pointer had better already be aligned for WCHARs
ASSERT(((ULONG_PTR)pwszOut & 1) == 0);
if (pwsz) { lstrcpyW(pwszOut, pwsz); } else { pwszOut[0] = L'\0'; } return (LPBYTE)(pwszOut + 1 + lstrlenW(pwszOut)); }
//
// Note! On failure, the source pidl is freed!
// (This behavior is inherited from ILAppendHiddenID.)
//
LPITEMIDLIST ByUsageHiddenData::Set(LPITEMIDLIST pidl) { UINT cb = sizeof(HIDDENITEMID); cb += sizeof(int); cb += sizeof(_wHotKey); cb += (UINT)(CbFromCchW(1 + (_pwszMSIPath ? lstrlenW(_pwszMSIPath) : 0))); cb += (UINT)(CbFromCchW(1 + (_pwszTargetPath ? lstrlenW(_pwszTargetPath) : 0))); cb += (UINT)(CbFromCchW(1 + (_pwszAltName ? lstrlenW(_pwszAltName) : 0)));
// We can use the aligned version here since we allocated it ourselves
// and didn't suck it out of a pidl.
HIDDENITEMID *pidhid = (HIDDENITEMID*)alloca(cb);
pidhid->cb = (WORD)cb; pidhid->wVersion = 0; pidhid->id = IDLHID_STARTPANEDATA;
LPBYTE pbHidden = ((LPBYTE)pidhid) + sizeof(HIDDENITEMID);
// The pointer had better already be aligned for ints
ASSERT(((ULONG_PTR)pbHidden & 3) == 0); *(int *)pbHidden = 0; // iUnused
pbHidden += sizeof(int);
*(DWORD *)pbHidden = _wHotKey; pbHidden += sizeof(_wHotKey);
pbHidden = _AppendString(pbHidden, _pwszMSIPath); pbHidden = _AppendString(pbHidden, _pwszTargetPath); pbHidden = _AppendString(pbHidden, _pwszAltName);
// Make sure our math was correct
ASSERT(cb == (UINT)((LPBYTE)pbHidden - (LPBYTE)pidhid));
// Remove and expunge the old data
ILRemoveHiddenID(pidl, IDLHID_STARTPANEDATA); ILExpungeRemovedHiddenIDs(pidl);
return ILAppendHiddenID(pidl, pidhid); }
LPWSTR ByUsageHiddenData::GetAltName(LPCITEMIDLIST pidl) { LPWSTR pszRet = NULL; ByUsageHiddenData hd; if (hd.Get(pidl, BUHD_ALTNAME)) { pszRet = hd._pwszAltName; // Keep this string
hd._pwszAltName = NULL; // Keep the upcoming assert happy
} ASSERT(hd.IsClear()); // make sure we aren't leaking
return pszRet; }
//
// Note! On failure, the source pidl is freed!
// (Propagating weird behavior of ByUsageHiddenData::Set)
//
LPITEMIDLIST ByUsageHiddenData::SetAltName(LPITEMIDLIST pidl, LPCTSTR ptszNewName) { ByUsageHiddenData hd;
// Attempt to overlay the existing values, but if they aren't available,
// don't freak out.
hd.Get(pidl, BUHD_ALL & ~BUHD_ALTNAME);
ASSERT(hd._pwszAltName == NULL); // we excluded it from the hd.Get()
hd._pwszAltName = const_cast<LPTSTR>(ptszNewName);
pidl = hd.Set(pidl); hd._pwszAltName = NULL; // so hd.Clear() won't SHFree() it
hd.Clear(); return pidl;
}
//
// Returns S_OK if the item changed; S_FALSE if it remained the same
//
HRESULT ByUsageHiddenData::UpdateMSIPath() { HRESULT hr = S_FALSE;
if (_pwszTargetPath && IsDarwinPath(_pwszTargetPath)) { LPWSTR pwszMSIPath = NULL; //
// If we can't resolve the Darwin ID to a filename, then leave
// the filename in the HiddenData alone - it's better than
// nothing.
//
if (SUCCEEDED(SHParseDarwinIDFromCacheW(_pwszTargetPath+1, &pwszMSIPath)) && pwszMSIPath) { //
// See if the MSI path has changed...
//
if (_pwszMSIPath == NULL || StrCmpCW(pwszMSIPath, _pwszMSIPath) != 0) { hr = S_OK; SHFree(_pwszMSIPath); _pwszMSIPath = pwszMSIPath; // take ownership
} else { // Unchanged; happy, free the path we aren't going to use
SHFree(pwszMSIPath); } } } return hr; }
LPCITEMIDLIST ByUsageShortcut::UpdateRelativePidl(ByUsageHiddenData *phd) { return _pidl = phd->Set(_pidl); // frees old _pidl even on failure
}
//
// We must key off the Darwin ID and not the product code.
//
// The Darwin ID is unique for each app in an application suite.
// For example, PowerPoint and Outlook have different Darwin IDs.
//
// The product code is the same for all apps in an application suite.
// For example, PowerPoint and Outlook have the same product code.
//
// Since we want to treat PowerPoint and Outlook as two independent
// applications, we want to use the Darwin ID and not the product code.
//
HRESULT _GetDarwinID(IShellLinkDataList *pdl, DWORD dwSig, LPWSTR pszPath, UINT cchPath) { LPEXP_DARWIN_LINK pedl; HRESULT hr; ASSERT(cchPath > 0);
hr = pdl->CopyDataBlock(dwSig, (LPVOID*)&pedl);
if (SUCCEEDED(hr)) { pszPath[0] = CH_DARWINMARKER; hr = StringCchCopy(pszPath+1, cchPath - 1, pedl->szwDarwinID); LocalFree(pedl); }
return hr; }
HRESULT _GetPathOrDarwinID(IShellLink *psl, LPTSTR pszPath, UINT cchPath, DWORD dwFlags) { HRESULT hr;
ASSERT(cchPath); pszPath[0] = TEXT('\0');
//
// See if it's a Darwin thingie.
//
IShellLinkDataList *pdl; hr = psl->QueryInterface(IID_PPV_ARG(IShellLinkDataList, &pdl)); if (SUCCEEDED(hr)) { //
// Maybe this is a Darwin shortcut... If so, then
// use the Darwin ID.
//
DWORD dwSLFlags; hr = pdl->GetFlags(&dwSLFlags); if (SUCCEEDED(hr)) { if (dwSLFlags & SLDF_HAS_DARWINID) { hr = _GetDarwinID(pdl, EXP_DARWIN_ID_SIG, pszPath, cchPath); } else { hr = E_FAIL; // No Darwin ID found
}
pdl->Release(); } }
if (FAILED(hr)) { hr = psl->GetPath(pszPath, cchPath, 0, dwFlags); }
return hr; }
void ByUsageHiddenData::LoadFromShellLink(IShellLink *psl) { ASSERT(_pwszTargetPath == NULL);
HRESULT hr; TCHAR szPath[MAX_PATH]; szPath[0] = TEXT('\0');
hr = _GetPathOrDarwinID(psl, szPath, ARRAYSIZE(szPath), SLGP_RAWPATH); if (SUCCEEDED(hr)) { SHStrDup(szPath, &_pwszTargetPath); }
hr = psl->GetHotkey(&_wHotKey); }
//****************************************************************************
ByUsageUI::ByUsageUI() : _byUsage(this, NULL), // We want to log execs as if they were launched by the Start Menu
SFTBarHost(HOSTF_FIREUEMEVENTS | HOSTF_CANDELETE | HOSTF_CANRENAME) { _iThemePart = SPP_PROGLIST; _iThemePartSep = SPP_PROGLISTSEPARATOR; }
ByUsage::ByUsage(ByUsageUI *pByUsageUI, ByUsageDUI *pByUsageDUI) { _pByUsageUI = pByUsageUI;
_pByUsageDUI = pByUsageDUI;
GetStartTime(&_ftStartTime);
_pidlBrowser = ILCreateFromPath(TEXT("shell:::{2559a1f4-21d7-11d4-bdaf-00c04f60b9f0}")); _pidlEmail = ILCreateFromPath(TEXT("shell:::{2559a1f5-21d7-11d4-bdaf-00c04f60b9f0}")); }
SFTBarHost *ByUsage_CreateInstance() { return new ByUsageUI(); }
ByUsage::~ByUsage() { if (_fUEMRegistered) { // Unregister with UEM DB if necessary
UEMRegisterNotify(NULL, NULL); }
if (_dpaNew) { _dpaNew.DestroyCallback(ILFreeCallback, NULL); }
// Must clear the pinned items before releasing the MenuCache,
// as the pinned items point to AppInfo items in the cache.
_rtPinned.Reset();
if (_pMenuCache) { // Clean up the Menu cache properly.
_pMenuCache->LockPopup(); _pMenuCache->UnregisterNotifyAll(); _pMenuCache->AttachUI(NULL); _pMenuCache->UnlockPopup(); _pMenuCache->Release(); }
ILFree(_pidlBrowser); ILFree(_pidlEmail); ATOMICRELEASE(_psmpin);
if (_pdirDesktop) { _pdirDesktop->Release(); } }
HRESULT ByUsage::Initialize() { HRESULT hr;
hr = CoCreateInstance(CLSID_StartMenuPin, NULL, CLSCTX_INPROC_SERVER, IID_PPV_ARG(IStartMenuPin, &_psmpin)); if (FAILED(hr)) { return hr; }
if (!(_pdirDesktop = ByUsageDir::CreateDesktop())) { return E_OUTOFMEMORY; }
// Use already initialized MenuCache if available
if (g_pMenuCache) { _pMenuCache = g_pMenuCache; _pMenuCache->AttachUI(_pByUsageUI); g_pMenuCache = NULL; // We take ownership here.
} else { hr = CMenuItemsCache::ReCreateMenuItemsCache(_pByUsageUI, &_ftStartTime, &_pMenuCache); if (FAILED(hr)) { return hr; } }
_ulPinChange = -1; // Force first query to re-enumerate
_dpaNew = NULL;
if (_pByUsageUI) { _hwnd = _pByUsageUI->_hwnd;
//
// Register for the "pin list change" event. This is an extended
// event (hence global), so listen in a location that contains
// no objects so the system doesn't waste time sending
// us stuff we don't care about. Our choice: _pidlBrowser.
// It's not even a folder, so it can't contain any objects!
//
ASSERT(!_pMenuCache->IsLocked()); _pByUsageUI->RegisterNotify(NOTIFY_PINCHANGE, SHCNE_EXTENDED_EVENT, _pidlBrowser, FALSE); }
return S_OK; }
void CMenuItemsCache::_InitStringList(HKEY hk, LPCTSTR pszSub, CDPA<TCHAR> dpa) { ASSERT(static_cast<HDPA>(dpa));
LONG lRc; DWORD cb = 0; lRc = RegQueryValueEx(hk, pszSub, NULL, NULL, NULL, &cb); if (lRc == ERROR_SUCCESS) { // Add an extra TCHAR just to be extra-safe. That way, we don't
// barf if there is a non-null-terminated string in the registry.
cb += sizeof(TCHAR); LPTSTR pszKillList = (LPTSTR)LocalAlloc(LPTR, cb); if (pszKillList) { lRc = SHGetValue(hk, NULL, pszSub, NULL, pszKillList, &cb); if (lRc == ERROR_SUCCESS) { // A semicolon-separated list of application names.
LPTSTR psz = pszKillList; LPTSTR pszSemi;
while ((pszSemi = StrChr(psz, TEXT(';'))) != NULL) { *pszSemi = TEXT('\0'); if (*psz) { AppendString(dpa, psz); } psz = pszSemi+1; } if (*psz) { AppendString(dpa, psz); } } LocalFree(pszKillList); } } }
//
// Fill the kill list with the programs that should be ignored
// should they be encountered in the Start Menu or elsewhere.
//
#define REGSTR_PATH_FILEASSOCIATION TEXT("Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\FileAssociation")
void CMenuItemsCache::_InitKillList() { HKEY hk; LONG lRc = RegOpenKeyEx(HKEY_LOCAL_MACHINE, REGSTR_PATH_FILEASSOCIATION, 0, KEY_READ, &hk); if (lRc == ERROR_SUCCESS) { _InitStringList(hk, TEXT("AddRemoveApps"), _dpaKill); _InitStringList(hk, TEXT("AddRemoveNames"), _dpaKillLink); RegCloseKey(hk); } }
//****************************************************************************
//
// Filling the ByUsageShortcutList
//
int CALLBACK PidlSortCallback(LPITEMIDLIST pidl1, LPITEMIDLIST pidl2, IShellFolder *psf) { HRESULT hr = psf->CompareIDs(0, pidl1, pidl2);
// We got them from the ShellFolder; they should still be valid!
ASSERT(SUCCEEDED(hr));
return ShortFromResult(hr); }
// {06C59536-1C66-4301-8387-82FBA3530E8D}
static const GUID TOID_STARTMENUCACHE = { 0x6c59536, 0x1c66, 0x4301, { 0x83, 0x87, 0x82, 0xfb, 0xa3, 0x53, 0xe, 0x8d } };
/*
* Background cache creation stuff... */ class CCreateMenuItemCacheTask : public CRunnableTask { CMenuItemsCache *_pMenuCache; IShellTaskScheduler *_pScheduler; public: CCreateMenuItemCacheTask(CMenuItemsCache *pMenuCache, IShellTaskScheduler *pScheduler) : CRunnableTask(RTF_DEFAULT), _pMenuCache(pMenuCache), _pScheduler(pScheduler) { if (_pScheduler) _pScheduler->AddRef(); }
~CCreateMenuItemCacheTask() { if (_pScheduler) _pScheduler->Release(); }
static void DummyCallBack(LPCITEMIDLIST pidl, LPVOID pvData, LPVOID pvHint, INT iIconIndex, INT iOpenIconIndex){}
STDMETHODIMP RunInitRT() { _pMenuCache->DelayGetFileCreationTimes(); _pMenuCache->DelayGetDarwinInfo(); _pMenuCache->LockPopup(); _pMenuCache->InitCache(); _pMenuCache->UpdateCache(); _pMenuCache->StartEnum();
ByUsageHiddenData hd; // construct once
while (TRUE) { ByUsageShortcut *pscut = _pMenuCache->GetNextShortcut(); if (!pscut) break;
hd.Get(pscut->RelativePidl(), ByUsageHiddenData::BUHD_HOTKEY | ByUsageHiddenData::BUHD_TARGETPATH); if (hd._wHotKey) { Tray_RegisterHotKey(hd._wHotKey, pscut->ParentPidl(), pscut->RelativePidl()); } // Pre-load the icons in the cache
int iIndex; SHMapIDListToImageListIndexAsync(_pScheduler, pscut->ParentFolder(), pscut->RelativePidl(), 0, DummyCallBack, NULL, NULL, &iIndex, NULL); // Register Darwin shortcut so that they can be grayed out if not installed
// and so we can map them to local paths as necessary
if (hd._pwszTargetPath && IsDarwinPath(hd._pwszTargetPath)) { SHRegisterDarwinLink(pscut->CreateFullPidl(), hd._pwszTargetPath +1 /* Exclude the Darwin marker! */, FALSE /* Don't update the Darwin state now, we'll do it later */); } hd.Clear(); } _pMenuCache->EndEnum(); _pMenuCache->UnlockPopup();
// Now determine all new items
// Note: this is safe to do after the Unlock because we never remove anything from the _dpaAppInfo
_pMenuCache->GetFileCreationTimes();
_pMenuCache->AllowGetDarwinInfo(); SHReValidateDarwinCache();
// Refreshing Darwin shortcuts must be done under the popup lock
// to avoid another thread doing a re-enumeration while we are
// studying the dpa. Keep SHReValidateDarwinCache outside of the
// lock since it is slow. (All we do is query the cache that
// SHReValidateDarwinCache created for us.)
_pMenuCache->LockPopup(); _pMenuCache->RefreshCachedDarwinShortcuts(); _pMenuCache->UnlockPopup();
_pMenuCache->Release(); return S_OK; } };
HRESULT AddMenuItemsCacheTask(IShellTaskScheduler* pSystemScheduler, BOOL fKeepCacheWhenFinished) { HRESULT hr;
CMenuItemsCache *pMenuCache = new CMenuItemsCache;
FILETIME ftStart; // Initialize with something.
GetStartTime(&ftStart);
if (pMenuCache) { hr = pMenuCache->Initialize(NULL, &ftStart); if (fKeepCacheWhenFinished) { g_pMenuCache = pMenuCache; g_pMenuCache->AddRef(); } } else { hr = E_OUTOFMEMORY; }
if (SUCCEEDED(hr)) { CCreateMenuItemCacheTask *pTask = new CCreateMenuItemCacheTask(pMenuCache, pSystemScheduler);
if (pTask) { hr = pSystemScheduler->AddTask(pTask, TOID_STARTMENUCACHE, 0, ITSAT_DEFAULT_PRIORITY); } else { hr = E_OUTOFMEMORY; } }
return hr; }
DWORD WINAPI CMenuItemsCache::ReInitCacheThreadProc(void *pv) { HRESULT hr = SHCoInitialize();
if (SUCCEEDED(hr)) { CMenuItemsCache *pMenuCache = reinterpret_cast<CMenuItemsCache *>(pv); pMenuCache->DelayGetFileCreationTimes(); pMenuCache->LockPopup(); pMenuCache->InitCache(); pMenuCache->UpdateCache(); pMenuCache->UnlockPopup();
// Now determine all new items
// Note: this is safe to do after the Unlock because we never remove anything from the _dpaAppInfo
pMenuCache->GetFileCreationTimes(); pMenuCache->Release(); } SHCoUninitialize(hr); return 0; }
HRESULT CMenuItemsCache::ReCreateMenuItemsCache(ByUsageUI *pbuUI, FILETIME *ftOSInstall, CMenuItemsCache **ppMenuCache) { HRESULT hr = E_OUTOFMEMORY; CMenuItemsCache *pMenuCache;
// Create a CMenuItemsCache with ref count 1.
pMenuCache = new CMenuItemsCache; if (pMenuCache) { hr = pMenuCache->Initialize(pbuUI, ftOSInstall); }
if (SUCCEEDED(hr)) { pMenuCache->AddRef(); if (!SHQueueUserWorkItem(ReInitCacheThreadProc, pMenuCache, 0, 0, NULL, NULL, 0)) { // No big deal if we fail here, we'll get another chance at enumerating later.
pMenuCache->Release(); } *ppMenuCache = pMenuCache; } return hr; }
HRESULT CMenuItemsCache::GetFileCreationTimes() { if (CompareFileTime(&_ftOldApps, &c_ftNever) != 0) { // Get all file creation times for our app list.
_dpaAppInfo.EnumCallbackEx(ByUsageAppInfo::EnumGetFileCreationTime, this);
// From now on, we will be checkin newness when we create the app object.
_fCheckNew = TRUE; } return S_OK; }
//
// Enumerate the contents of the folder specified by psfParent.
// pidlParent represents the location of psfParent.
//
// Note that we cannot do a depth-first walk into subfolders because
// many (most?) machines have a timeout on FindFirst handles; if you don't
// call FindNextFile for a few minutes, they secretly do a FindClose for
// you on the assumption that you are a bad app that leaked a handle.
// (There is also a valid DOS-compatibility reason for this behavior:
// The DOS FindFirstFile API doesn't have a FindClose, so the server can
// never tell if you are finished or not, so it has to guess that if you
// don't do a FindNext for a long time, you're probably finished.)
//
// So we have to save all the folders we find into a DPA and then walk
// the folders after we close the enumeration.
//
void CMenuItemsCache::_FillFolderCache(ByUsageDir *pdir, ByUsageRoot *prt) { // Caller should've initialized us
ASSERT(prt->_sl);
//
// Note that we must use a namespace walk instead of FindFirst/FindNext,
// because there might be folder shortcuts in the user's Start Menu.
//
// We do not specify SHCONTF_INCLUDEHIDDEN, so hidden objects are
// automatically excluded
IEnumIDList *peidl; if (S_OK == pdir->Folder()->EnumObjects(NULL, SHCONTF_FOLDERS | SHCONTF_NONFOLDERS, &peidl)) { CDPAPidl dpaDirs; if (dpaDirs.Create(4)) { CDPAPidl dpaFiles; if (dpaFiles.Create(4)) { LPITEMIDLIST pidl;
while (peidl->Next(1, &pidl, NULL) == S_OK) { // _IsExcludedDirectory carse about SFGAO_FILESYSTEM and SFGAO_LINK
DWORD dwAttributes = SFGAO_FOLDER | SFGAO_FILESYSTEM | SFGAO_LINK; if (SUCCEEDED(pdir->Folder()->GetAttributesOf(1, (LPCITEMIDLIST*)(&pidl), &dwAttributes))) { if (dwAttributes & SFGAO_FOLDER) { if (_IsExcludedDirectory(pdir->Folder(), pidl, dwAttributes) || dpaDirs.AppendPtr(pidl) < 0) { ILFree(pidl); } } else { if (dpaFiles.AppendPtr(pidl) < 0) { ILFree(pidl); } } } }
dpaDirs.SortEx(PidlSortCallback, pdir->Folder());
if (dpaFiles.GetPtrCount() > 0) { dpaFiles.SortEx(PidlSortCallback, pdir->Folder());
//
// Now merge the enumerated items with the ones
// in the cache.
//
_MergeIntoFolderCache(prt, pdir, dpaFiles); } dpaFiles.DestroyCallback(ILFreeCallback, NULL); }
// Must release now to force the FindClose to happen
peidl->Release();
// Now go back and handle all the folders we collected
ENUMFOLDERINFO info; info.self = this; info.pdir = pdir; info.prt = prt;
dpaDirs.DestroyCallbackEx(FolderEnumCallback, &info); } } }
BOOL CMenuItemsCache::FolderEnumCallback(LPITEMIDLIST pidl, ENUMFOLDERINFO *pinfo) { ByUsageDir *pdir = ByUsageDir::Create(pinfo->pdir, pidl); if (pdir) { pinfo->self->_FillFolderCache(pdir, pinfo->prt); pdir->Release(); } ILFree(pidl); return TRUE; }
//
// Returns the next element in prt->_slOld that still belongs to the
// directory "pdir", or NULL if no more.
//
ByUsageShortcut *CMenuItemsCache::_NextFromCacheInDir(ByUsageRoot *prt, ByUsageDir *pdir) { if (prt->_iOld < prt->_cOld) { ByUsageShortcut *pscut = prt->_slOld.FastGetPtr(prt->_iOld); if (pscut->Dir() == pdir) { prt->_iOld++; return pscut; } } return NULL; }
void CMenuItemsCache::_MergeIntoFolderCache(ByUsageRoot *prt, ByUsageDir *pdir, CDPAPidl dpaFiles) { //
// Look at prt->_slOld to see if we have cached information about
// this directory already.
//
// If we find directories that are less than us, skip over them.
// These correspond to directories that have been deleted.
//
// For example, if we are "D" and we run across directories
// "B" and "C" in the old cache, that means that directories "B"
// and "C" were deleted and we should continue scanning until we
// find "D" (or maybe we find "E" and stop since E > D).
//
//
ByUsageDir *pdirPrev = NULL;
while (prt->_iOld < prt->_cOld) { ByUsageDir *pdirT = prt->_slOld.FastGetPtr(prt->_iOld)->Dir(); HRESULT hr = _pdirDesktop->Folder()->CompareIDs(0, pdirT->Pidl(), pdir->Pidl()); if (hr == ResultFromShort(0)) { pdirPrev = pdirT; break; } else if (FAILED(hr) || ShortFromResult(hr) < 0) { //
// Skip over this directory
//
while (_NextFromCacheInDir(prt, pdirT)) { } } else { break; } }
if (pdirPrev) { //
// If we have a cached previous directory, then recycle him.
// This keeps us from creating lots of copies of the same IShellFolder.
// It is also essential that all entries from the same directory
// have the same pdir; that's how _NextFromCacheInDir knows when
// to stop.
//
pdir = pdirPrev;
//
// Make sure that this IShellFolder supports SHCIDS_ALLFIELDS.
// If not, then we just have to assume that they all changed.
//
IShellFolder2 *psf2; if (SUCCEEDED(pdir->Folder()->QueryInterface(IID_PPV_ARG(IShellFolder2, &psf2)))) { psf2->Release(); } else { pdirPrev = NULL; } }
//
// Now add all the items in dpaFiles to prt->_sl. If we find a match
// in prt->_slOld, then use that information instead of hitting the disk.
//
int iNew; ByUsageShortcut *pscutNext = _NextFromCacheInDir(prt, pdirPrev); for (iNew = 0; iNew < dpaFiles.GetPtrCount(); iNew++) { LPITEMIDLIST pidl = dpaFiles.FastGetPtr(iNew);
// Look for a match in the cache.
HRESULT hr = S_FALSE; while (pscutNext && (FAILED(hr = pdir->Folder()->CompareIDs(SHCIDS_ALLFIELDS, pscutNext->RelativePidl(), pidl)) || ShortFromResult(hr) < 0)) { pscutNext = _NextFromCacheInDir(prt, pdirPrev); }
// pscutNext, if non-NULL, is the item that made us stop searching.
// If hr == S_OK, then it was a match and we should use the data
// from the cache. Otherwise, we have a new item and should
// fill it in the slow way.
if (hr == ResultFromShort(0)) { // A match from the cache; move it over
_TransferShortcutToCache(prt, pscutNext); pscutNext = _NextFromCacheInDir(prt, pdirPrev); } else { // Brand new item, fill in from scratch
_AddShortcutToCache(pdir, pidl, prt->_sl); dpaFiles.FastGetPtr(iNew) = NULL; // took ownership
} } }
//****************************************************************************
bool CMenuItemsCache::_SetInterestingLink(ByUsageShortcut *pscut) { bool fInteresting = true; if (pscut->App() && !_PathIsInterestingExe(pscut->App()->GetAppPath())) { fInteresting = false; } else if (!_IsInterestingDirectory(pscut->Dir())) { fInteresting = false; } else { LPTSTR pszDisplayName = _DisplayNameOf(pscut->ParentFolder(), pscut->RelativePidl(), SHGDN_NORMAL | SHGDN_INFOLDER); if (pszDisplayName) { // SFGDN_INFOLDER should've returned a relative path
ASSERT(pszDisplayName == PathFindFileName(pszDisplayName));
int i; for (i = 0; i < _dpaKillLink.GetPtrCount(); i++) { if (StrStrI(pszDisplayName, _dpaKillLink.GetPtr(i)) != NULL) { fInteresting = false; break; } } SHFree(pszDisplayName); } }
pscut->SetInteresting(fInteresting); return fInteresting; }
BOOL CMenuItemsCache::_PathIsInterestingExe(LPCTSTR pszPath) { //
// Darwin shortcuts are always interesting.
//
if (IsDarwinPath(pszPath)) { return TRUE; }
LPCTSTR pszExt = PathFindExtension(pszPath);
//
// *.msc files are also always interesting. They aren't
// strictly-speaking EXEs, but they act like EXEs and administrators
// really use them a lot.
//
if (StrCmpICW(pszExt, TEXT(".msc")) == 0) { return TRUE; }
return StrCmpICW(pszExt, TEXT(".exe")) == 0 && !_IsExcludedExe(pszPath); }
BOOL CMenuItemsCache::_IsExcludedExe(LPCTSTR pszPath) { pszPath = PathFindFileName(pszPath);
int i; for (i = 0; i < _dpaKill.GetPtrCount(); i++) { if (StrCmpI(pszPath, _dpaKill.GetPtr(i)) == 0) { return TRUE; } }
HKEY hk; BOOL fRc = FALSE;
if (SUCCEEDED(_pqa->Init(ASSOCF_OPEN_BYEXENAME, pszPath, NULL, NULL)) && SUCCEEDED(_pqa->GetKey(0, ASSOCKEY_APP, NULL, &hk))) { fRc = ERROR_SUCCESS == SHQueryValueEx(hk, TEXT("NoStartPage"), NULL, NULL, NULL, NULL); RegCloseKey(hk); }
return fRc; }
HRESULT ByUsage::_GetShortcutExeTarget(IShellFolder *psf, LPCITEMIDLIST pidl, LPTSTR pszPath, UINT cchPath) { HRESULT hr; IShellLink *psl;
hr = psf->GetUIObjectOf(_hwnd, 1, &pidl, IID_PPV_ARG_NULL(IShellLink, &psl));
if (SUCCEEDED(hr)) { hr = psl->GetPath(pszPath, cchPath, 0, 0); psl->Release(); } return hr; }
void _GetUEMInfo(const GUID *pguidGrp, int eCmd, WPARAM wParam, LPARAM lParam, UEMINFO *pueiOut) { ZeroMemory(pueiOut, sizeof(UEMINFO)); pueiOut->cbSize = sizeof(UEMINFO); pueiOut->dwMask = UEIM_HIT | UEIM_FILETIME;
//
// If this call fails (app / pidl was never run), then we'll
// just use the zeros we pre-initialized with.
//
UEMQueryEvent(pguidGrp, eCmd, wParam, lParam, pueiOut);
//
// The UEM code invents a default usage count if the shortcut
// was never used. We don't want that.
//
if (FILETIMEtoInt64(pueiOut->ftExecute) == 0) { pueiOut->cHit = 0; } }
//
// Returns S_OK if the item changed, S_FALSE if the item stayed the same,
// or an error code
//
HRESULT CMenuItemsCache::_UpdateMSIPath(ByUsageShortcut *pscut) { HRESULT hr = S_FALSE; // Assume nothing happened
if (pscut->IsDarwin()) { ByUsageHiddenData hd; hd.Get(pscut->RelativePidl(), ByUsageHiddenData::BUHD_ALL); if (hd.UpdateMSIPath() == S_OK) { // Redirect to the new target (user may have
// uninstalled then reinstalled to a new location)
ByUsageAppInfo *papp = GetAppInfoFromHiddenData(&hd); pscut->SetApp(papp); if (papp) papp->Release();
if (pscut->UpdateRelativePidl(&hd)) { hr = S_OK; // We changed stuff
} else { hr = E_OUTOFMEMORY; // Couldn't update the relative pidl
} } hd.Clear(); }
return hr; }
//
// Take pscut (which is the ByUsageShortcut most recently enumerated from
// the old cache) and move it to the new cache. NULL out the entry in the
// old cache so that DPADELETEANDDESTROY(prt->_slOld) won't free it.
//
void CMenuItemsCache::_TransferShortcutToCache(ByUsageRoot *prt, ByUsageShortcut *pscut) { ASSERT(pscut); ASSERT(pscut == prt->_slOld.FastGetPtr(prt->_iOld - 1)); if (SUCCEEDED(_UpdateMSIPath(pscut)) && prt->_sl.AppendPtr(pscut) >= 0) { // Take ownership
prt->_slOld.FastGetPtr(prt->_iOld - 1) = NULL; } }
ByUsageAppInfo *CMenuItemsCache::GetAppInfoFromHiddenData(ByUsageHiddenData *phd) { ByUsageAppInfo *papp = NULL; bool fIgnoreTimestamp = false;
TCHAR szPath[MAX_PATH]; LPTSTR pszPath = szPath; szPath[0] = TEXT('\0');
if (phd->_pwszMSIPath && phd->_pwszMSIPath[0]) { pszPath = phd->_pwszMSIPath;
// When MSI installs an app, the timestamp is applies to the app
// is the timestamp on the source media, *not* the time the user
// user installed the app. So ignore the timestamp entirely since
// it's useless information (and in fact makes us think the app
// is older than it really is).
fIgnoreTimestamp = true; } else if (phd->_pwszTargetPath) { if (IsDarwinPath(phd->_pwszTargetPath)) { pszPath = phd->_pwszTargetPath; } else { //
// Need to expand the path because it may contain environment
// variables.
//
SHExpandEnvironmentStrings(phd->_pwszTargetPath, szPath, ARRAYSIZE(szPath)); } }
return GetAppInfo(pszPath, fIgnoreTimestamp); }
ByUsageShortcut *CMenuItemsCache::CreateShortcutFromHiddenData(ByUsageDir *pdir, LPCITEMIDLIST pidl, ByUsageHiddenData *phd, BOOL fForce) { ByUsageAppInfo *papp = GetAppInfoFromHiddenData(phd); bool fDarwin = phd->_pwszTargetPath && IsDarwinPath(phd->_pwszTargetPath); ByUsageShortcut *pscut = ByUsageShortcut::Create(pdir, pidl, papp, fDarwin, fForce); if (papp) papp->Release(); return pscut; }
void CMenuItemsCache::_AddShortcutToCache(ByUsageDir *pdir, LPITEMIDLIST pidl, ByUsageShortcutList slFiles) { HRESULT hr; ByUsageHiddenData hd;
if (pidl) { //
// Juice-up this pidl with cool info about the shortcut target
//
IShellLink *psl; hr = pdir->Folder()->GetUIObjectOf(NULL, 1, const_cast<LPCITEMIDLIST *>(&pidl), IID_PPV_ARG_NULL(IShellLink, &psl)); if (SUCCEEDED(hr)) { hd.LoadFromShellLink(psl);
psl->Release();
if (hd._pwszTargetPath && IsDarwinPath(hd._pwszTargetPath)) { SHRegisterDarwinLink(ILCombine(pdir->Pidl(), pidl), hd._pwszTargetPath +1 /* Exclude the Darwin marker! */, _fCheckDarwin); SHParseDarwinIDFromCacheW(hd._pwszTargetPath+1, &hd._pwszMSIPath); }
// ByUsageHiddenData::Set frees the source pidl on failure
pidl = hd.Set(pidl);
} }
if (pidl) { ByUsageShortcut *pscut = CreateShortcutFromHiddenData(pdir, pidl, &hd);
if (pscut) { if (slFiles.AppendPtr(pscut) >= 0) { _SetInterestingLink(pscut); } else { // Couldn't append; oh well
delete pscut; // "delete" can handle NULL pointer
} }
ILFree(pidl); } hd.Clear(); }
//
// Find an entry in the AppInfo list that matches this application.
// If not found, create a new entry. In either case, bump the
// reference count and return the item.
//
ByUsageAppInfo* CMenuItemsCache::GetAppInfo(LPTSTR pszAppPath, bool fIgnoreTimestamp) { Lock();
ByUsageAppInfo *pappBlank = NULL;
int i; for (i = _dpaAppInfo.GetPtrCount() - 1; i >= 0; i--) { ByUsageAppInfo *papp = _dpaAppInfo.FastGetPtr(i); if (papp->IsBlank()) { // Remember that we found a blank entry we can recycle
pappBlank = papp; } else if (lstrcmpi(papp->_pszAppPath, pszAppPath) == 0) { papp->AddRef(); Unlock(); return papp; } }
// Not found in the list. Try to recycle a blank entry.
if (!pappBlank) { // No blank entries found; must make a new one.
pappBlank = ByUsageAppInfo::Create(); if (pappBlank && _dpaAppInfo.AppendPtr(pappBlank) < 0) { delete pappBlank; pappBlank = NULL; } }
if (pappBlank && pappBlank->Initialize(pszAppPath, this, _fCheckNew, fIgnoreTimestamp)) { ASSERT(pappBlank->IsBlank()); pappBlank->AddRef(); } else { pappBlank = NULL; }
Unlock(); return pappBlank; }
// A shortcut is new if...
//
// the shortcut is newly created, and
// the target is newly created, and
// neither the shortcut nor the target has been run "in an interesting
// way".
//
// An "interesting way" is "more than one hour after the shortcut/target
// was created."
//
// Note that we test the easiest things first, to avoid hitting
// the disk too much.
bool ByUsage::_IsShortcutNew(ByUsageShortcut *pscut, ByUsageAppInfo *papp, const UEMINFO *puei) { //
// Shortcut is new if...
//
// It was run less than an hour after the app was installed.
// It was created relatively recently.
//
//
bool fNew = FILETIMEtoInt64(puei->ftExecute) < FILETIMEtoInt64(papp->_ftCreated) + FT_NEWAPPGRACEPERIOD() && _pMenuCache->IsNewlyCreated(&pscut->GetCreatedTime());
return fNew; }
//****************************************************************************
// See how many pinned items there are, so we can tell our dad
// how big we want to be.
void ByUsage::PrePopulate() { _FillPinnedItemsCache(); _NotifyDesiredSize(); }
//
// Enumerating out of cache.
//
void ByUsage::EnumFolderFromCache() { if(SHRestricted(REST_NOSMMFUPROGRAMS)) //If we don't need MFU list,...
return; // don't enumerate this!
_pMenuCache->StartEnum();
LPITEMIDLIST pidlDesktop, pidlCommonDesktop; (void)SHGetSpecialFolderLocation(NULL, CSIDL_DESKTOPDIRECTORY, &pidlDesktop); (void)SHGetSpecialFolderLocation(NULL, CSIDL_COMMON_DESKTOPDIRECTORY, &pidlCommonDesktop);
while (TRUE) { ByUsageShortcut *pscut = _pMenuCache->GetNextShortcut();
if (!pscut) break;
if (!pscut->IsInteresting()) continue;
// Find out if the item is on the desktop, because we don't track new items on the desktop.
BOOL fIsDesktop = FALSE; if ((pidlDesktop && ILIsEqual(pscut->ParentPidl(), pidlDesktop)) || (pidlCommonDesktop && ILIsEqual(pscut->ParentPidl(), pidlCommonDesktop)) ) { fIsDesktop = TRUE; pscut->SetNew(FALSE); }
TraceMsg(TF_PROGLIST, "%p.scut.enum", pscut);
ByUsageAppInfo *papp = pscut->App();
if (papp) { // Now enumerate the item itself. Enumerating an item consists
// of extracting its UEM data, updating the totals, and possibly
// marking ourselves as the "best" representative of the associated
// application.
//
//
UEMINFO uei; pscut->GetUEMInfo(&uei);
// See if this shortcut is still new. If the app is no longer new,
// then there's no point in keeping track of the shortcut's new-ness.
if (pscut->IsNew() && papp->_fNew) { pscut->SetNew(_IsShortcutNew(pscut, papp, &uei)); }
//
// Maybe we are the "best"... Note that we win ties.
// This ensures that even if an app is never run, *somebody*
// will be chosen as the "best".
//
if (CompareUEMInfo(&uei, &papp->_ueiBest) <= 0) { papp->_ueiBest = uei; papp->_pscutBest = pscut; if (!fIsDesktop) { // Best Start Menu (i.e., non-desktop) item
papp->_pscutBestSM = pscut; } TraceMsg(TF_PROGLIST, "%p.scut.winner papp=%p", pscut, papp); }
// Include this file's UEM info in the total
papp->CombineUEMInfo(&uei, pscut->IsNew(), fIsDesktop); } } _pMenuCache->EndEnum(); ILFree(pidlCommonDesktop); ILFree(pidlDesktop); }
BOOL IsPidlInDPA(LPCITEMIDLIST pidl, CDPAPidl dpa) { int i; for (i = dpa.GetPtrCount()-1; i >= 0; i--) { if (ILIsEqual(pidl, dpa.FastGetPtr(i))) { return TRUE; } } return FALSE; }
BOOL ByUsage::_AfterEnumCB(ByUsageAppInfo *papp, AFTERENUMINFO *paei) { // A ByUsageAppInfo doesn't exist unless there's a ByUsageShortcut
// that references it or it is pinned...
if (!papp->IsBlank() && papp->_pscutBest) { UEMINFO uei; papp->GetUEMInfo(&uei); papp->CombineUEMInfo(&uei, papp->_IsUEMINFONew(&uei));
// A file counts on the list only if it has been used
// and is not pinned. (Pinned items are added to the list
// elsewhere.)
//
// Note that "new" apps are *not* placed on the list until
// they are used. ("new" apps are highlighted on the
// Start Menu.)
if (!papp->_fPinned && papp->_ueiTotal.cHit && FILETIMEtoInt64(papp->_ueiTotal.ftExecute)) { TraceMsg(TF_PROGLIST, "%p.app.add", papp);
ByUsageItem *pitem = papp->CreateByUsageItem(); if (pitem) { LPITEMIDLIST pidl = pitem->CreateFullPidl(); if (paei->self->_pByUsageUI) { paei->self->_pByUsageUI->AddItem(pitem, NULL, pidl); }
if (paei->self->_pByUsageDUI) { paei->self->_pByUsageDUI->AddItem(pitem, NULL, pidl); } ILFree(pidl); } } else { TraceMsg(TF_PROGLIST, "%p.app.skip", papp); }
#if 0
//
// If you enable this code, then holding down Ctrl and Alt
// will cause us to pick a program to be new. This is for
// testing the "new apps" balloon tip.
//
#define DEBUG_ForceNewApp() \
(paei->dpaNew && paei->dpaNew.GetPtrCount() == 0 && \ GetAsyncKeyState(VK_CONTROL) < 0 && GetAsyncKeyState(VK_MENU) < 0) #else
#define DEBUG_ForceNewApp() FALSE
#endif
//
// Must also check _pscutBestSM because if an app is represented
// only on the desktop and not on the start menu, then
// _pscutBestSM will be NULL.
//
if (paei->dpaNew && (papp->IsNew() || DEBUG_ForceNewApp()) && papp->_pscutBestSM) { // NTRAID:193226 We mistakenly treat apps on the desktop
// as if they were "new".
// we should only care about apps in the start menu
TraceMsg(TF_PROGLIST, "%p.app.new(%s)", papp, papp->_pszAppPath); LPITEMIDLIST pidl = papp->_pscutBestSM->CreateFullPidl(); while (pidl) { LPITEMIDLIST pidlParent = NULL;
if (paei->dpaNew.AppendPtr(pidl) >= 0) { pidlParent = ILClone(pidl); pidl = NULL; // ownership of pidl transferred to DPA
if (!ILRemoveLastID(pidlParent) || ILIsEmpty(pidlParent) || IsPidlInDPA(pidlParent, paei->dpaNew)) { // If failure or if we already have it in the list
ILFree(pidlParent); pidlParent = NULL; }
// Remember the creation time of the most recent app
if (CompareFileTime(&paei->self->_ftNewestApp, &papp->GetCreatedTime()) < 0) { paei->self->_ftNewestApp = papp->GetCreatedTime(); }
// If the shortcut is even newer, then use that.
// This happens in the "freshly installed Darwin app"
// case, because Darwin is kinda reluctant to tell
// us where the EXE is so all we have to go on is
// the shortcut.
if (CompareFileTime(&paei->self->_ftNewestApp, &papp->_pscutBestSM->GetCreatedTime()) < 0) { paei->self->_ftNewestApp = papp->_pscutBestSM->GetCreatedTime(); }
} ILFree(pidl);
// Now add the parent to the list also.
pidl = pidlParent; } } } return TRUE; }
BOOL ByUsage::IsSpecialPinnedPidl(LPCITEMIDLIST pidl) { return _pdirDesktop->Folder()->CompareIDs(0, pidl, _pidlEmail) == S_OK || _pdirDesktop->Folder()->CompareIDs(0, pidl, _pidlBrowser) == S_OK; }
BOOL ByUsage::IsSpecialPinnedItem(ByUsageItem *pitem) { return IsSpecialPinnedPidl(pitem->RelativePidl()); }
//
// For each app we found, add it to the list.
//
void ByUsage::AfterEnumItems() { //
// First, all pinned items are enumerated unconditionally.
//
if (_rtPinned._sl && _rtPinned._sl.GetPtrCount()) { int i; for (i = 0; i < _rtPinned._sl.GetPtrCount(); i++) { ByUsageShortcut *pscut = _rtPinned._sl.FastGetPtr(i); ByUsageItem *pitem = pscut->CreatePinnedItem(i); if (pitem) { // Pinned items are relative to the desktop, so we can
// save ourselves an ILClone because the relative pidl
// is equal to the absolute pidl.
ASSERT(pitem->Dir() == _pdirDesktop);
//
// Special handling for E-mail and Internet pinned items
//
if (IsSpecialPinnedItem(pitem)) { pitem->EnableSubtitle(); }
if (_pByUsageUI) _pByUsageUI->AddItem(pitem, NULL, pscut->RelativePidl()); } } }
//
// Now add the separator after the pinned items.
//
ByUsageItem *pitem = ByUsageItem::CreateSeparator(); if (pitem && _pByUsageUI) { _pByUsageUI->AddItem(pitem, NULL, NULL); }
//
// Now walk through all the regular items.
//
// PERF: Can skip this if _cMFUDesired==0 and "highlight new apps" is off
//
AFTERENUMINFO aei; aei.self = this; aei.dpaNew.Create(4); // Will check failure in callback
ByUsageAppInfoList *pdpaAppInfo = _pMenuCache->GetAppList(); pdpaAppInfo->EnumCallbackEx(_AfterEnumCB, &aei);
// Now that we have the official list of new items, tell the
// foreground thread to pick it up. We don't update the master
// copy in-place for three reasons.
//
// 1. It generates contention since both the foreground and
// background threads would be accessing it simultaneously.
// This means more critical sections (yuck).
// 2. It means that items that were new and are still new have
// a brief period where they are no longer new because we
// are rebuilding the list.
// 3. By having only one thread access the master copy, we avoid
// synchronization issues.
if (aei.dpaNew && _pByUsageUI && _pByUsageUI->_hwnd && SendNotifyMessage(_pByUsageUI->_hwnd, BUM_SETNEWITEMS, 0, (LPARAM)(HDPA)aei.dpaNew)) { aei.dpaNew.Detach(); // Successfully delivered
}
// If we were unable to deliver the new HDPA, then destroy it here
// so we don't leak.
if (aei.dpaNew) { aei.dpaNew.DestroyCallback(ILFreeCallback, NULL); }
if (!_fUEMRegistered) { // Register with UEM DB if we haven't done it yet
ASSERT(!_pMenuCache->IsLocked()); _fUEMRegistered = SUCCEEDED(UEMRegisterNotify(UEMNotifyCB, static_cast<void *>(this))); } }
int ByUsage::UEMNotifyCB(void *param, const GUID *pguidGrp, int eCmd) { ByUsage *pbu = reinterpret_cast<ByUsage *>(param); // Refresh our list whenever a new app is started.
// or when the session changes (because that changes all the usage counts)
switch (eCmd) { case UEME_CTLSESSION: if (IsEqualGUID(*pguidGrp, UEMIID_BROWSER)) break;
// Fall thru
case UEME_RUNPIDL: case UEME_RUNPATH:
if (pbu && pbu->_pByUsageUI) { pbu->_pByUsageUI->Invalidate(); pbu->_pByUsageUI->StartRefreshTimer(); } break; default: // Do nothing
; } return 0; }
BOOL CreateExcludedDirectoriesDPA(const int rgcsidlExclude[], CDPA<TCHAR> *pdpaExclude) { if (*pdpaExclude) { pdpaExclude->EnumCallback(LocalFreeCallback, NULL); pdpaExclude->DeleteAllPtrs(); } else if (!pdpaExclude->Create(4)) { return FALSE; }
ASSERT(*pdpaExclude); ASSERT(pdpaExclude->GetPtrCount() == 0);
int i = 0; while (rgcsidlExclude[i] != -1) { TCHAR szPath[MAX_PATH]; // Note: This call can legitimately fail if the corresponding
// folder does not exist, so don't get upset. Less work for us!
if (SUCCEEDED(SHGetFolderPath(NULL, rgcsidlExclude[i], NULL, SHGFP_TYPE_CURRENT, szPath))) { AppendString(*pdpaExclude, szPath); } i++; }
return TRUE; }
BOOL CMenuItemsCache::_GetExcludedDirectories() { //
// The directories we exclude from enumeration - Shortcuts in these
// folders are never candidates for inclusion.
//
static const int c_rgcsidlUninterestingDirectories[] = { CSIDL_ALTSTARTUP, CSIDL_STARTUP, CSIDL_COMMON_ALTSTARTUP, CSIDL_COMMON_STARTUP, -1 // End marker
};
return CreateExcludedDirectoriesDPA(c_rgcsidlUninterestingDirectories, &_dpaNotInteresting); }
BOOL CMenuItemsCache::_IsExcludedDirectory(IShellFolder *psf, LPCITEMIDLIST pidl, DWORD dwAttributes) { if (_enumfl & ENUMFL_NORECURSE) return TRUE;
if (!(dwAttributes & SFGAO_FILESYSTEM)) return TRUE;
// SFGAO_LINK | SFGAO_FOLDER = folder shortcut.
// We want to exclude those because we can get blocked
// on network stuff
if (dwAttributes & SFGAO_LINK) return TRUE;
return FALSE; }
BOOL CMenuItemsCache::_IsInterestingDirectory(ByUsageDir *pdir) { STRRET str; TCHAR szPath[MAX_PATH]; if (SUCCEEDED(_pdirDesktop->Folder()->GetDisplayNameOf(pdir->Pidl(), SHGDN_FORPARSING, &str)) && SUCCEEDED(StrRetToBuf(&str, pdir->Pidl(), szPath, ARRAYSIZE(szPath)))) { int i; for (i = _dpaNotInteresting.GetPtrCount() - 1; i >= 0; i--) { if (lstrcmpi(_dpaNotInteresting.FastGetPtr(i), szPath) == 0) { return FALSE; } } } return TRUE; }
void ByUsage::OnPinListChange() { _pByUsageUI->Invalidate(); PostMessage(_pByUsageUI->_hwnd, ByUsageUI::SFTBM_REFRESH, TRUE, 0); }
void ByUsage::OnChangeNotify(UINT id, LONG lEvent, LPCITEMIDLIST pidl1, LPCITEMIDLIST pidl2) { if (id == NOTIFY_PINCHANGE) { if (lEvent == SHCNE_EXTENDED_EVENT && pidl1) { SHChangeDWORDAsIDList *pdwidl = (SHChangeDWORDAsIDList *)pidl1; if (pdwidl->dwItem1 == SHCNEE_PINLISTCHANGED) { OnPinListChange(); } } } else if (_pMenuCache) { _pMenuCache->OnChangeNotify(id, lEvent, pidl1, pidl2); } }
void CMenuItemsCache::OnChangeNotify(UINT id, LONG lEvent, LPCITEMIDLIST pidl1, LPCITEMIDLIST pidl2) { ASSERT(id < min(MAXNOTIFY, NUM_PROGLIST_ROOTS));
if (id < NUM_PROGLIST_ROOTS) { _rgrt[id].SetNeedRefresh(); _fIsCacheUpToDate = FALSE; // Once we get one notification, there's no point in listening to further
// notifications until our next enumeration. This keeps us from churning
// while Winstones are running.
if (_pByUsageUI) { ASSERT(!IsLocked()); _pByUsageUI->UnregisterNotify(id); _rgrt[id].ClearRegistered(); _pByUsageUI->Invalidate(); _pByUsageUI->RefreshNow(); } } }
void CMenuItemsCache::UnregisterNotifyAll() { if (_pByUsageUI) { UINT id; for (id = 0; id < NUM_PROGLIST_ROOTS; id++) { _rgrt[id].ClearRegistered(); _pByUsageUI->UnregisterNotify(id); } } }
inline LRESULT ByUsage::_OnNotify(LPNMHDR pnm) { switch (pnm->code) { case SMN_MODIFYSMINFO: return _ModifySMInfo(CONTAINING_RECORD(pnm, SMNMMODIFYSMINFO, hdr)); } return 0; }
//
// We need this message to avoid a race condition between the background
// thread (the enumerator) and the foreground thread. So the rule is
// that only the foreground thread is allowd to mess with _dpaNew.
// The background thread collects the information it wants into a
// separate DPA and hands it to us on the foreground thread, where we
// can safely set it into _dpaNew without encountering a race condition.
//
inline LRESULT ByUsage::_OnSetNewItems(HDPA hdpaNew) { CDPAPidl dpaNew(hdpaNew);
//
// Most of the time, there are no new apps and there were no new apps
// last time either. Short-circuit this case...
//
int cNew = _dpaNew ? _dpaNew.GetPtrCount() : 0;
if (cNew == 0 && dpaNew.GetPtrCount() == 0) { // Both old and new are empty. We're finished.
// (Since we own dpaNew, free it to avoid a memory leak.)
dpaNew.DestroyCallback(ILFreeCallback, NULL); return 0; }
// Now swap the new DPA in
if (_dpaNew) { _dpaNew.DestroyCallback(ILFreeCallback, NULL); } _dpaNew.Attach(hdpaNew);
// Tell our dad that we can identify new items
// Also tell him the timestamp of the most recent app
// (so he can tell whether or not to restart the "offer new apps" counter)
SMNMHAVENEWITEMS nmhni; nmhni.ftNewestApp = _ftNewestApp; _SendNotify(_pByUsageUI->_hwnd, SMN_HAVENEWITEMS, &nmhni.hdr);
return 0; }
LRESULT ByUsage::OnWndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { switch (uMsg) { case WM_NOTIFY: return _OnNotify(reinterpret_cast<LPNMHDR>(lParam));
case BUM_SETNEWITEMS: return _OnSetNewItems(reinterpret_cast<HDPA>(lParam));
case WM_SETTINGCHANGE: static const TCHAR c_szClients[] = TEXT("Software\\Clients"); if ((wParam == 0 && lParam == 0) || // wildcard
(lParam && StrCmpNIC((LPCTSTR)lParam, c_szClients, ARRAYSIZE(c_szClients) - 1) == 0)) // client change
{ _pByUsageUI->ForceChange(); // even though the pidls didn't change, their targets did
_ulPinChange = -1; // Force reload even if list didn't change
OnPinListChange(); // reload the pin list (since a client changed)
} break; }
// Else fall back to parent implementation
return _pByUsageUI->SFTBarHost::OnWndProc(hwnd, uMsg, wParam, lParam); }
LRESULT ByUsage::_ModifySMInfo(PSMNMMODIFYSMINFO pmsi) { LPSMDATA psmd = pmsi->psmd;
// Do this only if there is a ShellFolder. We don't want to fault
// on the static menu items.
if ((psmd->dwMask & SMDM_SHELLFOLDER) && _dpaNew) {
// NTRAID:135699: this needs big-time optimization
// E.g., remember the previous folder if there was nothing found
LPITEMIDLIST pidl = NULL;
IAugmentedShellFolder2* pasf2; if (SUCCEEDED(psmd->psf->QueryInterface(IID_PPV_ARG(IAugmentedShellFolder2, &pasf2)))) { LPITEMIDLIST pidlFolder; LPITEMIDLIST pidlItem; if (SUCCEEDED(pasf2->UnWrapIDList(psmd->pidlItem, 1, NULL, &pidlFolder, &pidlItem, NULL))) { pidl = ILCombine(pidlFolder, pidlItem); ILFree(pidlFolder); ILFree(pidlItem); } pasf2->Release(); }
if (!pidl) { pidl = ILCombine(psmd->pidlFolder, psmd->pidlItem); }
if (pidl) { if (IsPidlInDPA(pidl, _dpaNew)) { // Designers say: New items should never be demoted
pmsi->psminfo->dwFlags |= SMIF_NEW; pmsi->psminfo->dwFlags &= ~SMIF_DEMOTED; } ILFree(pidl); } } return 0; }
void ByUsage::_FillPinnedItemsCache() { if(SHRestricted(REST_NOSMPINNEDLIST)) //If no pinned list is allowed,.....
return; //....there is nothing to do!
ULONG ulPinChange; _psmpin->GetChangeCount(&ulPinChange); if (_ulPinChange == ulPinChange) { // No change in pin list; do not need to reload
return; }
_ulPinChange = ulPinChange; _rtPinned.Reset(); if (_rtPinned._sl.Create(4)) { IEnumIDList *penum;
if (SUCCEEDED(_psmpin->EnumObjects(&penum))) { LPITEMIDLIST pidl; while (penum->Next(1, &pidl, NULL) == S_OK) { IShellLink *psl; HRESULT hr; ByUsageHiddenData hd;
//
// If we have a shortcut, do bookkeeping based on the shortcut
// target. Otherwise do it based on the pinned object itself.
// Note that we do not go through _PathIsInterestingExe
// because all pinned items are interesting.
hr = SHGetUIObjectFromFullPIDL(pidl, NULL, IID_PPV_ARG(IShellLink, &psl)); if (SUCCEEDED(hr)) { hd.LoadFromShellLink(psl); psl->Release();
// We do not need to SHRegisterDarwinLink because the only
// reason for getting the MSI path is so pinned items can
// prevent items on the Start Menu from appearing in the MFU.
// So let the shortcut on the Start Menu do the registration.
// (If there is none, then that's even better - no work to do!)
hd.UpdateMSIPath(); }
if (FAILED(hr)) { hr = DisplayNameOfAsOLESTR(_pdirDesktop->Folder(), pidl, SHGDN_FORPARSING, &hd._pwszTargetPath); }
//
// If we were able to figure out what the pinned object is,
// use that information to block the app from also appearing
// in the MFU.
//
// Inability to identify the pinned
// object is not grounds for rejection. A pinned items is
// of great sentimental value to the user.
//
if (FAILED(hr)) { ASSERT(hd.IsClear()); }
ByUsageShortcut *pscut = _pMenuCache->CreateShortcutFromHiddenData(_pdirDesktop, pidl, &hd, TRUE); if (pscut) { if (_rtPinned._sl.AppendPtr(pscut) >= 0) { pscut->SetInteresting(true); // Pinned items are always interesting
if (IsSpecialPinnedPidl(pidl)) { ByUsageAppInfo *papp = _pMenuCache->GetAppInfoFromSpecialPidl(pidl); pscut->SetApp(papp); if (papp) papp->Release(); } } else { // Couldn't append; oh well
delete pscut; // "delete" can handle NULL pointer
} } hd.Clear(); ILFree(pidl); } penum->Release(); } }
}
IAssociationElement *GetAssociationElementFromSpecialPidl(IShellFolder *psf, LPCITEMIDLIST pidlItem) { IAssociationElement *pae = NULL;
// There is no way to get the IAssociationElement directly, so
// we get the IExtractIcon and then ask him for the IAssociationElement.
IExtractIcon *pxi; if (SUCCEEDED(psf->GetUIObjectOf(NULL, 1, &pidlItem, IID_PPV_ARG_NULL(IExtractIcon, &pxi)))) { IUnknown_QueryService(pxi, IID_IAssociationElement, IID_PPV_ARG(IAssociationElement, &pae)); pxi->Release(); }
return pae; }
//
// On success, the returned ByUsageAppInfo has been AddRef()d
//
ByUsageAppInfo *CMenuItemsCache::GetAppInfoFromSpecialPidl(LPCITEMIDLIST pidl) { ByUsageAppInfo *papp = NULL;
IAssociationElement *pae = GetAssociationElementFromSpecialPidl(_pdirDesktop->Folder(), pidl); if (pae) { LPWSTR pszData; if (SUCCEEDED(pae->QueryString(AQVS_APPLICATION_PATH, L"open", &pszData))) { //
// HACK! Outlook puts the short file name in the registry.
// Convert to long file name (if it won't cost too much) so
// people who select Outlook as their default mail client
// won't get a dup copy in the MFU.
//
LPTSTR pszPath = pszData; TCHAR szLFN[MAX_PATH]; if (!PathIsNetworkPath(pszData)) { DWORD dwLen = GetLongPathName(pszData, szLFN, ARRAYSIZE(szLFN)); if (dwLen && dwLen < ARRAYSIZE(szLFN)) { pszPath = szLFN; } }
papp = GetAppInfo(pszPath, true); SHFree(pszData); } pae->Release(); } return papp; }
void ByUsage::_EnumPinnedItemsFromCache() { if (_rtPinned._sl) { int i; for (i = 0; i < _rtPinned._sl.GetPtrCount(); i++) { ByUsageShortcut *pscut = _rtPinned._sl.FastGetPtr(i);
TraceMsg(TF_PROGLIST, "%p.scut.enumC", pscut);
// Enumerating a pinned item consists of marking the corresponding
// application as "I am pinned, do not mess with me!"
ByUsageAppInfo *papp = pscut->App();
if (papp) { papp->_fPinned = TRUE; TraceMsg(TF_PROGLIST, "%p.scut.pin papp=%p", pscut, papp);
} } } }
const struct CMenuItemsCache::ROOTFOLDERINFO CMenuItemsCache::c_rgrfi[] = { { CSIDL_STARTMENU, ENUMFL_RECURSE | ENUMFL_CHECKNEW | ENUMFL_ISSTARTMENU }, { CSIDL_PROGRAMS, ENUMFL_RECURSE | ENUMFL_CHECKNEW | ENUMFL_CHECKISCHILDOFPREVIOUS }, { CSIDL_COMMON_STARTMENU, ENUMFL_RECURSE | ENUMFL_CHECKNEW | ENUMFL_ISSTARTMENU }, { CSIDL_COMMON_PROGRAMS, ENUMFL_RECURSE | ENUMFL_CHECKNEW | ENUMFL_CHECKISCHILDOFPREVIOUS }, { CSIDL_DESKTOPDIRECTORY, ENUMFL_NORECURSE | ENUMFL_NOCHECKNEW }, { CSIDL_COMMON_DESKTOPDIRECTORY, ENUMFL_NORECURSE | ENUMFL_NOCHECKNEW }, // The limit for register notify is currently 5 (slots 0 through 4)
// Changing this requires changing ByUsageUI::SFTHOST_MAXNOTIFY
};
//
// Here's where we decide all the things that should be enumerated
// in the "My Programs" list.
//
void ByUsage::EnumItems() { _FillPinnedItemsCache(); _NotifyDesiredSize();
_pMenuCache->LockPopup(); _pMenuCache->InitCache();
BOOL fNeedUpdateDarwin = !_pMenuCache->IsCacheUpToDate();
// Note! UpdateCache() must occur before _EnumPinnedItemsFromCache()
// because UpdateCache() resets _fPinned.
_pMenuCache->UpdateCache();
if (fNeedUpdateDarwin) { SHReValidateDarwinCache(); }
_pMenuCache->RefreshDarwinShortcuts(&_rtPinned); _EnumPinnedItemsFromCache(); EnumFolderFromCache();
// Finished collecting data; do some postprocessing...
AfterEnumItems();
// Do not unlock before this point, as AfterEnumItems depends on the cache to stay put.
_pMenuCache->UnlockPopup(); }
void ByUsage::_NotifyDesiredSize() { if (_pByUsageUI) { int cPinned = _rtPinned._sl ? _rtPinned._sl.GetPtrCount() : 0;
int cNormal; DWORD cb = sizeof(cNormal); if (SHGetValue(HKEY_CURRENT_USER, REGSTR_PATH_STARTPANE_SETTINGS, REGSTR_VAL_DV2_MINMFU, NULL, &cNormal, &cb) != ERROR_SUCCESS) { cNormal = REGSTR_VAL_DV2_MINMFU_DEFAULT; }
_cMFUDesired = cNormal; _pByUsageUI->SetDesiredSize(cPinned, cNormal); } }
//****************************************************************************
// CMenuItemsCache
CMenuItemsCache::CMenuItemsCache() : _cref(1) { }
LONG CMenuItemsCache::AddRef() { return InterlockedIncrement(&_cref); }
LONG CMenuItemsCache::Release() { ASSERT( 0 != _cref ); LONG cRef = InterlockedDecrement(&_cref); if ( 0 == cRef ) { delete this; } return cRef; }
HRESULT CMenuItemsCache::Initialize(ByUsageUI *pbuUI, FILETIME * pftOSInstall) { HRESULT hr = S_OK;
// Must do this before any of the operations that can fail
// because we unconditionally call DeleteCriticalSection in destructor
_fCSInited = InitializeCriticalSectionAndSpinCount(&_csInUse, 0);
if (!_fCSInited) { return E_OUTOFMEMORY; }
hr = AssocCreate(CLSID_QueryAssociations, IID_PPV_ARG(IQueryAssociations, &_pqa)); if (FAILED(hr)) { return hr; }
_pByUsageUI = pbuUI;
_ftOldApps = *pftOSInstall;
_pdirDesktop = ByUsageDir::CreateDesktop(); if (!_dpaAppInfo.Create(4)) { hr = E_OUTOFMEMORY; }
if (!_GetExcludedDirectories()) { hr = E_OUTOFMEMORY; }
if (!_dpaKill.Create(4) || !_dpaKillLink.Create(4)) { return E_OUTOFMEMORY; }
_InitKillList();
_hPopupReady = CreateMutex(NULL, FALSE, NULL); if (!_hPopupReady) { return E_OUTOFMEMORY; }
// By default, we want to check applications for newness.
_fCheckNew = TRUE;
return hr; } HRESULT CMenuItemsCache::AttachUI(ByUsageUI *pbuUI) { // We do not AddRef here so that destruction always happens on the same thread that created the object
// but beware of lifetime issues: we need to synchronize attachUI/detachUI operations with LockPopup and UnlockPopup.
LockPopup(); _pByUsageUI = pbuUI; UnlockPopup();
return S_OK; }
CMenuItemsCache::~CMenuItemsCache() { if (_fIsCacheUpToDate) { _SaveCache(); }
if (_dpaNotInteresting) { _dpaNotInteresting.DestroyCallback(LocalFreeCallback, NULL); }
if (_dpaKill) { _dpaKill.DestroyCallback(LocalFreeCallback, NULL); }
if (_dpaKillLink) { _dpaKillLink.DestroyCallback(LocalFreeCallback, NULL); }
// Must delete the roots before destroying _dpaAppInfo.
int i; for (i = 0; i < ARRAYSIZE(_rgrt); i++) { _rgrt[i].Reset(); }
ATOMICRELEASE(_pqa); DPADELETEANDDESTROY(_dpaAppInfo);
if (_pdirDesktop) { _pdirDesktop->Release(); }
if (_hPopupReady) { CloseHandle(_hPopupReady); }
if (_fCSInited) { DeleteCriticalSection(&_csInUse); } }
BOOL CMenuItemsCache::_ShouldProcessRoot(int iRoot) { BOOL fRet = TRUE;
if (!_rgrt[iRoot]._pidl) { fRet = FALSE; } else if ((c_rgrfi[iRoot]._enumfl & ENUMFL_CHECKISCHILDOFPREVIOUS) && !SHRestricted(REST_NOSTARTMENUSUBFOLDERS) ) { ASSERT(iRoot >= 1); if (_rgrt[iRoot-1]._pidl && ILIsParent(_rgrt[iRoot-1]._pidl, _rgrt[iRoot]._pidl, FALSE)) { fRet = FALSE; } } return fRet; }
//****************************************************************************
//
// The format of the ProgramsCache is as follows:
//
// [DWORD] dwVersion
//
// If the version is wrong, then ignore. Not worth trying to design
// a persistence format that is forward-compatible since it's just
// a cache.
//
// Don't be stingy about incrementing the dwVersion. We've got room
// for four billion revs.
#define PROGLIST_VERSION 9
//
//
// For each special folder we persist:
//
// [BYTE] CSIDL_xxx (as a sanity check)
//
// Followed by a sequence of segments; either...
//
// [BYTE] 0x00 -- Change directory
// [pidl] directory (relative to CSIDL_xxx)
//
// or
//
// [BYTE] 0x01 -- Add shortcut
// [pidl] item (relative to current directory)
//
// or
//
// [BYTE] 0x02 -- end
//
#define CACHE_CHDIR 0
#define CACHE_ITEM 1
#define CACHE_END 2
BOOL CMenuItemsCache::InitCache() { COMPILETIME_ASSERT(ARRAYSIZE(c_rgrfi) == NUM_PROGLIST_ROOTS);
// Make sure we don't use more than MAXNOTIFY notify slots for the cache
COMPILETIME_ASSERT( NUM_PROGLIST_ROOTS <= MAXNOTIFY);
if (_fIsInited) return TRUE;
BOOL fSuccess = FALSE; int irfi;
IStream *pstm = SHOpenRegStream2(HKEY_CURRENT_USER, REGSTR_PATH_STARTFAVS, REGSTR_VAL_PROGLIST, STGM_READ); if (pstm) { ByUsageDir *pdirRoot = NULL; ByUsageDir *pdir = NULL;
DWORD dwVersion; if (FAILED(IStream_Read(pstm, &dwVersion, sizeof(dwVersion))) || dwVersion != PROGLIST_VERSION) { goto panic; }
for (irfi = 0; irfi < ARRAYSIZE(c_rgrfi); irfi++) { ByUsageRoot *prt = &_rgrt[irfi];
// If SHGetSpecialFolderLocation fails, it could mean that
// the directory was recently restricted. We *could* just
// skip over this block and go to the next csidl, but that
// would be actual work, and this is just a cache, so we may
// as well just panic and re-enumerate from scratch.
//
if (FAILED(SHGetSpecialFolderLocation(NULL, c_rgrfi[irfi]._csidl, &prt->_pidl))) { goto panic; }
if (!_ShouldProcessRoot(irfi)) continue;
if (!prt->_sl.Create(4)) { goto panic; }
BYTE csidl; if (FAILED(IStream_Read(pstm, &csidl, sizeof(csidl))) || csidl != c_rgrfi[irfi]._csidl) { goto panic; }
pdirRoot = ByUsageDir::Create(_pdirDesktop, prt->_pidl);
if (!pdirRoot) { goto panic; }
BYTE bCmd; do { LPITEMIDLIST pidl;
if (FAILED(IStream_Read(pstm, &bCmd, sizeof(bCmd)))) { goto panic; }
switch (bCmd) { case CACHE_CHDIR: // Toss the old directory
if (pdir) { pdir->Release(); pdir = NULL; }
// Figure out where the new directory is
if (FAILED(IStream_ReadPidl(pstm, &pidl))) { goto panic; }
// and create it
pdir = ByUsageDir::Create(pdirRoot, pidl); ILFree(pidl);
if (!pdir) { goto panic; } break;
case CACHE_ITEM: { // Must set a directory befor creating an item
if (!pdir) { goto panic; }
// Get the new item
if (FAILED(IStream_ReadPidl(pstm, &pidl))) { goto panic; }
// Create it
ByUsageShortcut *pscut = _CreateFromCachedPidl(prt, pdir, pidl); ILFree(pidl); if (!pscut) { goto panic; } } break;
case CACHE_END: break;
default: goto panic; } } while (bCmd != CACHE_END);
pdirRoot->Release(); pdirRoot = NULL; if (pdir) { pdir->Release(); pdir = NULL; }
prt->SetNeedRefresh(); }
fSuccess = TRUE;
panic: if (!fSuccess) { for (irfi = 0; irfi < ARRAYSIZE(c_rgrfi); irfi++) { _rgrt[irfi].Reset(); } }
if (pdirRoot) { pdirRoot->Release(); }
if (pdir) { pdir->Release(); }
pstm->Release(); }
_fIsInited = TRUE;
return fSuccess; }
HRESULT CMenuItemsCache::UpdateCache() { FILETIME ft; // Apps are "new" only if installed less than 1 week ago.
// They also must postdate the user's first use of the new Start Menu.
GetSystemTimeAsFileTime(&ft); DecrementFILETIME(&ft, FT_ONEDAY * 7);
// _ftOldApps is the more recent of OS install time, or last week.
if (CompareFileTime(&ft, &_ftOldApps) >= 0) { _ftOldApps = ft; }
_dpaAppInfo.EnumCallbackEx(ByUsageAppInfo::EnumResetCB, this);
if(!SHRestricted(REST_NOSMMFUPROGRAMS)) { int i; for (i = 0; i < ARRAYSIZE(c_rgrfi); i++) { ByUsageRoot *prt = &_rgrt[i]; int csidl = c_rgrfi[i]._csidl; _enumfl = c_rgrfi[i]._enumfl;
if (!prt->_pidl) { (void)SHGetSpecialFolderLocation(NULL, csidl, &prt->_pidl); // void cast to keep prefast happy
prt->SetNeedRefresh(); }
if (!_ShouldProcessRoot(i)) continue;
// Restrictions might deny recursing into subfolders
if ((_enumfl & ENUMFL_ISSTARTMENU) && SHRestricted(REST_NOSTARTMENUSUBFOLDERS)) { _enumfl &= ~ENUMFL_RECURSE; _enumfl |= ENUMFL_NORECURSE; }
// Fill the cache if it is stale
LPITEMIDLIST pidl; if (!IsRestrictedCsidl(csidl) && SUCCEEDED(SHGetSpecialFolderLocation(NULL, csidl, &pidl))) { if (prt->_pidl == NULL || !ILIsEqual(prt->_pidl, pidl) || prt->NeedsRefresh() || prt->NeedsRegister()) { if (!prt->_pidl || prt->NeedsRefresh()) { prt->ClearNeedRefresh(); ASSERT(prt->_slOld == NULL); prt->_slOld = prt->_sl; prt->_cOld = prt->_slOld ? prt->_slOld.GetPtrCount() : 0; prt->_iOld = 0;
// Free previous pidl
ILFree(prt->_pidl); prt->_pidl = NULL;
if (prt->_sl.Create(4)) { ByUsageDir *pdir = ByUsageDir::Create(_pdirDesktop, pidl); if (pdir) { prt->_pidl = pidl; // Take ownership
pidl = NULL; // So ILFree won't nuke it
_FillFolderCache(pdir, prt); pdir->Release(); } } DPADELETEANDDESTROY(prt->_slOld); } if (_pByUsageUI && prt->NeedsRegister() && prt->_pidl) { ASSERT(i < ByUsageUI::SFTHOST_MAXNOTIFY); prt->SetRegistered(); ASSERT(!IsLocked()); _pByUsageUI->RegisterNotify(i, SHCNE_DISKEVENTS, prt->_pidl, TRUE); } } ILFree(pidl);
} else { // Special folder doesn't exist; erase the file list
prt->Reset(); } } // for loop!
} // Restriction!
_fIsCacheUpToDate = TRUE; return S_OK; }
void CMenuItemsCache::RefreshDarwinShortcuts(ByUsageRoot *prt) { if (prt->_sl) { int j = prt->_sl.GetPtrCount(); while (--j >= 0) { ByUsageShortcut *pscut = prt->_sl.FastGetPtr(j); if (FAILED(_UpdateMSIPath(pscut))) { prt->_sl.DeletePtr(j); // remove the bad shortcut so we don't fault
} } } }
void CMenuItemsCache::RefreshCachedDarwinShortcuts() { if(!SHRestricted(REST_NOSMMFUPROGRAMS)) { Lock();
for (int i = 0; i < ARRAYSIZE(c_rgrfi); i++) { RefreshDarwinShortcuts(&_rgrt[i]); } Unlock(); } }
ByUsageShortcut *CMenuItemsCache::_CreateFromCachedPidl(ByUsageRoot *prt, ByUsageDir *pdir, LPITEMIDLIST pidl) { ByUsageHiddenData hd; UINT buhd = ByUsageHiddenData::BUHD_TARGETPATH | ByUsageHiddenData::BUHD_MSIPATH; hd.Get(pidl, buhd);
ByUsageShortcut *pscut = CreateShortcutFromHiddenData(pdir, pidl, &hd); if (pscut) { if (prt->_sl.AppendPtr(pscut) >= 0) { _SetInterestingLink(pscut); } else { // Couldn't append; oh well
delete pscut; // "delete" can handle NULL pointer
} }
hd.Clear();
return pscut; }
HRESULT IStream_WriteByte(IStream *pstm, BYTE b) { return IStream_Write(pstm, &b, sizeof(b)); }
#ifdef DEBUG
//
// Like ILIsParent, but defaults to TRUE if we don't have enough memory
// to determine for sure. (ILIsParent defaults to FALSE on error.)
//
BOOL ILIsProbablyParent(LPCITEMIDLIST pidlParent, LPCITEMIDLIST pidlChild) { BOOL fRc = TRUE; LPITEMIDLIST pidlT = ILClone(pidlChild); if (pidlT) {
// Truncate pidlT to the same depth as pidlParent.
LPCITEMIDLIST pidlParentT = pidlParent; LPITEMIDLIST pidlChildT = pidlT; while (!ILIsEmpty(pidlParentT)) { pidlChildT = _ILNext(pidlChildT); pidlParentT = _ILNext(pidlParentT); }
pidlChildT->mkid.cb = 0;
// Okay, at this point pidlT should equal pidlParent.
IShellFolder *psfDesktop; if (SUCCEEDED(SHGetDesktopFolder(&psfDesktop))) { HRESULT hr = psfDesktop->CompareIDs(0, pidlT, pidlParent); if (SUCCEEDED(hr) && ShortFromResult(hr) != 0) { // Definitely, conclusively different.
fRc = FALSE; } psfDesktop->Release(); }
ILFree(pidlT); } return fRc; } #endif
inline LPITEMIDLIST ILFindKnownChild(LPCITEMIDLIST pidlParent, LPCITEMIDLIST pidlChild) { #ifdef DEBUG
// ILIsParent will give wrong answers in low-memory situations
// (which testers like to simulate) so we roll our own.
// ASSERT(ILIsParent(pidlParent, pidlChild, FALSE));
ASSERT(ILIsProbablyParent(pidlParent, pidlChild)); #endif
while (!ILIsEmpty(pidlParent)) { pidlChild = _ILNext(pidlChild); pidlParent = _ILNext(pidlParent); } return const_cast<LPITEMIDLIST>(pidlChild); }
void CMenuItemsCache::_SaveCache() { int irfi; BOOL fSuccess = FALSE;
IStream *pstm = SHOpenRegStream2(HKEY_CURRENT_USER, REGSTR_PATH_STARTFAVS, REGSTR_VAL_PROGLIST, STGM_WRITE); if (pstm) { DWORD dwVersion = PROGLIST_VERSION; if (FAILED(IStream_Write(pstm, &dwVersion, sizeof(dwVersion)))) { goto panic; }
for (irfi = 0; irfi < ARRAYSIZE(c_rgrfi); irfi++) { if (!_ShouldProcessRoot(irfi)) continue;
ByUsageRoot *prt = &_rgrt[irfi];
if (FAILED(IStream_WriteByte(pstm, (BYTE)c_rgrfi[irfi]._csidl))) { goto panic; }
if (prt->_sl && prt->_pidl) { int i; ByUsageDir *pdir = NULL; for (i = 0; i < prt->_sl.GetPtrCount(); i++) { ByUsageShortcut *pscut = prt->_sl.FastGetPtr(i);
// If the directory changed, write out a chdir entry
if (pdir != pscut->Dir()) { pdir = pscut->Dir();
// Write the new directory
if (FAILED(IStream_WriteByte(pstm, CACHE_CHDIR)) || FAILED(IStream_WritePidl(pstm, ILFindKnownChild(prt->_pidl, pdir->Pidl())))) { goto panic; } }
// Now write out the shortcut
if (FAILED(IStream_WriteByte(pstm, CACHE_ITEM)) || FAILED(IStream_WritePidl(pstm, pscut->RelativePidl()))) { goto panic; } } }
// Now write out the terminator
if (FAILED(IStream_WriteByte(pstm, CACHE_END))) { goto panic; }
}
fSuccess = TRUE;
panic: pstm->Release();
if (!fSuccess) { SHDeleteValue(HKEY_CURRENT_USER, REGSTR_PATH_STARTFAVS, REGSTR_VAL_PROGLIST); } } }
void CMenuItemsCache::StartEnum() { _iCurrentRoot = 0; _iCurrentIndex = 0; }
void CMenuItemsCache::EndEnum() { }
ByUsageShortcut *CMenuItemsCache::GetNextShortcut() { ByUsageShortcut *pscut = NULL;
if (_iCurrentRoot < NUM_PROGLIST_ROOTS) { if (_rgrt[_iCurrentRoot]._sl && _iCurrentIndex < _rgrt[_iCurrentRoot]._sl.GetPtrCount()) { pscut = _rgrt[_iCurrentRoot]._sl.FastGetPtr(_iCurrentIndex); _iCurrentIndex++; } else { // Go to next root
_iCurrentIndex = 0; _iCurrentRoot++; pscut = GetNextShortcut(); } }
return pscut; }
//****************************************************************************
void AppendString(CDPA<TCHAR> dpa, LPCTSTR psz) { LPTSTR pszDup = StrDup(psz); if (pszDup && dpa.AppendPtr(pszDup) < 0) { LocalFree(pszDup); // Append failed
} }
BOOL LocalFreeCallback(LPTSTR psz, LPVOID) { LocalFree(psz); return TRUE; }
BOOL ILFreeCallback(LPITEMIDLIST pidl, LPVOID) { ILFree(pidl); return TRUE; }
int ByUsage::CompareItems(PaneItem *p1, PaneItem *p2) { //
// The separator comes before regular items.
//
if (p1->IsSeparator()) { return -1; }
if (p2->IsSeparator()) { return +1; }
ByUsageItem *pitem1 = static_cast<ByUsageItem *>(p1); ByUsageItem *pitem2 = static_cast<ByUsageItem *>(p2);
return CompareUEMInfo(&pitem1->_uei, &pitem2->_uei); }
// Sort by most frequently used - break ties by most recently used
int ByUsage::CompareUEMInfo(UEMINFO *puei1, UEMINFO *puei2) { int iResult = puei2->cHit - puei1->cHit; if (iResult == 0) { iResult = ::CompareFileTime(&puei2->ftExecute, &puei1->ftExecute); }
return iResult; }
LPITEMIDLIST ByUsage::GetFullPidl(PaneItem *p) { ByUsageItem *pitem = static_cast<ByUsageItem *>(p);
return pitem->CreateFullPidl(); }
HRESULT ByUsage::GetFolderAndPidl(PaneItem *p, IShellFolder **ppsfOut, LPCITEMIDLIST *ppidlOut) { ByUsageItem *pitem = static_cast<ByUsageItem *>(p);
// If a single-level child pidl, then we can short-circuit the
// SHBindToFolderIDListParent
if (_ILNext(pitem->_pidl)->mkid.cb == 0) { *ppsfOut = pitem->_pdir->Folder(); (*ppsfOut)->AddRef(); *ppidlOut = pitem->_pidl; return S_OK; } else { // Multi-level child pidl
return SHBindToFolderIDListParent(pitem->_pdir->Folder(), pitem->_pidl, IID_PPV_ARG(IShellFolder, ppsfOut), ppidlOut); } }
HRESULT ByUsage::ContextMenuDeleteItem(PaneItem *p, IContextMenu *pcm, CMINVOKECOMMANDINFOEX *pici) { IShellFolder *psf; LPCITEMIDLIST pidlItem; ByUsageItem *pitem = static_cast<ByUsageItem *>(p);
HRESULT hr = GetFolderAndPidl(pitem, &psf, &pidlItem); if (SUCCEEDED(hr)) { // Unpin the item - we go directly to the IStartMenuPin because
// the context menu handler might decide not to support pin/unpin
// for this item because it doesn't satisfy some criteria or other.
LPITEMIDLIST pidlFull = pitem->CreateFullPidl(); if (pidlFull) { _psmpin->Modify(pidlFull, NULL); // delete from pin list
ILFree(pidlFull); }
// Set hit count for shortcut to zero
UEMINFO uei; ZeroMemory(&uei, sizeof(UEMINFO)); uei.cbSize = sizeof(UEMINFO); uei.dwMask = UEIM_HIT; uei.cHit = 0;
_SetUEMPidlInfo(psf, pidlItem, &uei);
// Set hit count for target app to zero
TCHAR szPath[MAX_PATH]; if (SUCCEEDED(_GetShortcutExeTarget(psf, pidlItem, szPath, ARRAYSIZE(szPath)))) { _SetUEMPathInfo(szPath, &uei); }
// Set hit count for Darwin target to zero
ByUsageHiddenData hd; hd.Get(pidlItem, ByUsageHiddenData::BUHD_MSIPATH); if (hd._pwszMSIPath && hd._pwszMSIPath[0]) { _SetUEMPathInfo(hd._pwszMSIPath, &uei); } hd.Clear();
psf->Release();
if (IsSpecialPinnedItem(pitem)) { c_tray.CreateStartButtonBalloon(0, IDS_STARTPANE_SPECIALITEMSTIP); }
// If the item wasn't pinned, then all we did was dork some usage
// counts, which does not trigger an automatic refresh. So do a
// manual one.
_pByUsageUI->Invalidate(); PostMessage(_pByUsageUI->_hwnd, ByUsageUI::SFTBM_REFRESH, TRUE, 0);
}
return hr; }
HRESULT ByUsage::ContextMenuInvokeItem(PaneItem *pitem, IContextMenu *pcm, CMINVOKECOMMANDINFOEX *pici, LPCTSTR pszVerb) { ASSERT(_pByUsageUI);
HRESULT hr; if (StrCmpIC(pszVerb, TEXT("delete")) == 0) { hr = ContextMenuDeleteItem(pitem, pcm, pici); } else { // Don't need to refresh explicitly if the command is pin/unpin
// because the changenotify will do it for us
hr = _pByUsageUI->SFTBarHost::ContextMenuInvokeItem(pitem, pcm, pici, pszVerb); }
return hr; }
int ByUsage::ReadIconSize() { COMPILETIME_ASSERT(SFTBarHost::ICONSIZE_SMALL == 0); COMPILETIME_ASSERT(SFTBarHost::ICONSIZE_LARGE == 1); return SHRegGetBoolUSValue(REGSTR_EXPLORER_ADVANCED, REGSTR_VAL_DV2_LARGEICONS, FALSE, TRUE /* default to large*/); }
BOOL ByUsage::_IsPinnedExe(ByUsageItem *pitem, IShellFolder *psf, LPCITEMIDLIST pidlItem) { //
// Early-out: Not even pinned.
//
if (!_IsPinned(pitem)) { return FALSE; }
//
// See if it's an EXE.
//
BOOL fIsExe;
LPTSTR pszFileName = _DisplayNameOf(psf, pidlItem, SHGDN_INFOLDER | SHGDN_FORPARSING);
if (pszFileName) { LPCTSTR pszExt = PathFindExtension(pszFileName); fIsExe = StrCmpICW(pszExt, TEXT(".exe")) == 0; SHFree(pszFileName); } else { fIsExe = FALSE; }
return fIsExe; }
HRESULT ByUsage::ContextMenuRenameItem(PaneItem *p, LPCTSTR ptszNewName) { ByUsageItem *pitem = static_cast<ByUsageItem *>(p);
IShellFolder *psf; LPCITEMIDLIST pidlItem; HRESULT hr;
hr = GetFolderAndPidl(pitem, &psf, &pidlItem); if (SUCCEEDED(hr)) { if (_IsPinnedExe(pitem, psf, pidlItem)) { // Renaming a pinned exe consists merely of changing the
// display name inside the pidl.
//
// Note! SetAltName frees the pidl on failure.
LPITEMIDLIST pidlNew; if ((pidlNew = ILClone(pitem->RelativePidl())) && (pidlNew = ByUsageHiddenData::SetAltName(pidlNew, ptszNewName))) { hr = _psmpin->Modify(pitem->RelativePidl(), pidlNew); if (SUCCEEDED(hr)) { pitem->SetRelativePidl(pidlNew); } else { ILFree(pidlNew); } } else { hr = E_OUTOFMEMORY; } } else { LPITEMIDLIST pidlNew; hr = psf->SetNameOf(_hwnd, pidlItem, ptszNewName, SHGDN_INFOLDER, &pidlNew);
//
// Warning! SetNameOf can set pidlNew == NULL if the rename
// was handled by some means outside of the pidl (so the pidl
// is unchanged). This means that the rename succeeded and
// we can keep using the old pidl.
//
if (SUCCEEDED(hr) && pidlNew) { //
// The old Start Menu renames the UEM data when we rename
// the shortcut, but we cannot guarantee that the old
// Start Menu is around, so we do it ourselves. Fortunately,
// the old Start Menu does not attempt to move the data if
// the hit count is zero, so if it gets moved twice, the
// second person who does the move sees cHit=0 and skips
// the operation.
//
UEMINFO uei; _GetUEMPidlInfo(psf, pidlItem, &uei); if (uei.cHit > 0) { _SetUEMPidlInfo(psf, pidlNew, &uei); uei.cHit = 0; _SetUEMPidlInfo(psf, pidlItem, &uei); }
//
// Update the pitem with the new pidl.
//
if (_IsPinned(pitem)) { LPITEMIDLIST pidlDad = ILCloneParent(pitem->RelativePidl()); if (pidlDad) { LPITEMIDLIST pidlFullNew = ILCombine(pidlDad, pidlNew); if (pidlFullNew) { _psmpin->Modify(pitem->RelativePidl(), pidlFullNew); pitem->SetRelativePidl(pidlFullNew); // takes ownership
} ILFree(pidlDad); } ILFree(pidlNew); } else { ASSERT(pidlItem == pitem->RelativePidl()); pitem->SetRelativePidl(pidlNew); } } } psf->Release(); }
return hr; }
//
// If asking for the display (not for parsing) name of a pinned EXE,
// we need to return the "secret display name". Otherwise, we can
// use the default implementation.
//
LPTSTR ByUsage::DisplayNameOfItem(PaneItem *p, IShellFolder *psf, LPCITEMIDLIST pidlItem, SHGNO shgno) { ByUsageItem *pitem = static_cast<ByUsageItem *>(p);
LPTSTR pszName = NULL;
// Only display (not for-parsing) names of EXEs need to be hooked.
if (!(shgno & SHGDN_FORPARSING) && _IsPinnedExe(pitem, psf, pidlItem)) { //
// EXEs get their name from the hidden data.
//
pszName = ByUsageHiddenData::GetAltName(pidlItem); }
return pszName ? pszName : _pByUsageUI->SFTBarHost::DisplayNameOfItem(p, psf, pidlItem, shgno); }
//
// "Internet" and "Email" get subtitles consisting of the friendly app name.
//
LPTSTR ByUsage::SubtitleOfItem(PaneItem *p, IShellFolder *psf, LPCITEMIDLIST pidlItem) { ASSERT(p->HasSubtitle());
LPTSTR pszName = NULL;
IAssociationElement *pae = GetAssociationElementFromSpecialPidl(psf, pidlItem); if (pae) { // We detect error by looking at pszName
pae->QueryString(AQS_FRIENDLYTYPENAME, NULL, &pszName); pae->Release(); }
return pszName ? pszName : _pByUsageUI->SFTBarHost::SubtitleOfItem(p, psf, pidlItem); }
HRESULT ByUsage::MovePinnedItem(PaneItem *p, int iInsert) { ByUsageItem *pitem = static_cast<ByUsageItem *>(p); ASSERT(_IsPinned(pitem));
return _psmpin->Modify(pitem->RelativePidl(), SMPIN_POS(iInsert)); }
//
// For drag-drop purposes, we let you drop anything, not just EXEs.
// We just reject slow media.
//
BOOL ByUsage::IsInsertable(IDataObject *pdto) { return _psmpin->IsPinnable(pdto, SMPINNABLE_REJECTSLOWMEDIA, NULL) == S_OK; }
HRESULT ByUsage::InsertPinnedItem(IDataObject *pdto, int iInsert) { HRESULT hr = E_FAIL;
LPITEMIDLIST pidlItem; if (_psmpin->IsPinnable(pdto, SMPINNABLE_REJECTSLOWMEDIA, &pidlItem) == S_OK) { if (SUCCEEDED(hr = _psmpin->Modify(NULL, pidlItem)) && SUCCEEDED(hr = _psmpin->Modify(pidlItem, SMPIN_POS(iInsert)))) { // Woo-hoo!
} ILFree(pidlItem); } return hr; }
|