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