Leaked source code of windows server 2003
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

3947 lines
117 KiB

#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;
}