windows-nt/Source/XPSP1/NT/shell/shell32/startpin.cpp
2020-09-26 16:20:57 +08:00

1360 lines
37 KiB
C++

#include "shellprv.h"
#include "ids.h"
#include "datautil.h"
#include "resource.h" // main symbols
#include "cowsite.h" // CObjectWithSite
#include "dpa.h" // CDPA
class CStartMenuPin;
//
// PINENTRY - A single entry in the pin list
//
// The _liPos/_cbLink point back into the CPinList._pstmLink
//
class PINENTRY {
public:
LPITEMIDLIST _pidl;
IShellLink * _psl; // a live IShellLink
LARGE_INTEGER _liPos; // location of the shell link inside the stream
DWORD _cbSize; // size of the buffer pointed to by _liPos
HRESULT UpdateShellLink();
void FreeShellLink()
{
_cbSize = 0;
ATOMICRELEASE(_psl);
}
void Destruct()
{
ILFree(_pidl);
FreeShellLink();
}
static BOOL DestroyCallback(PINENTRY *self, LPVOID)
{
self->Destruct();
return TRUE;
}
};
//
// CPinList
//
class CPinList
{
public:
CPinList() : _dsaEntries(NULL), _pstmLink(NULL) { }
~CPinList()
{
ATOMICRELEASE(_pstmLink);
if (_dsaEntries)
{
_dsaEntries.DestroyCallbackEx(PINENTRY::DestroyCallback, (void *)NULL);
}
}
BOOL Initialize() { return _dsaEntries.Create(4); }
HRESULT Load(CStartMenuPin *psmpin);
HRESULT Save(CStartMenuPin *psmpin);
int AppendPidl(LPITEMIDLIST pidl)
{
PINENTRY entry = { pidl };
return _dsaEntries.AppendItem(&entry);
}
PINENTRY *GetItemPtr(int i) { return _dsaEntries.GetItemPtr(i); }
HRESULT SaveShellLink(PINENTRY *pentry, IStream *pstm);
HRESULT LoadShellLink(PINENTRY *pentry, IShellLink **ppsl);
HRESULT UpdateShellLink(PINENTRY *pentry) { return pentry->UpdateShellLink(); }
PINENTRY *FindPidl(LPCITEMIDLIST pidl, int *pi);
HRESULT ReplacePidl(LPCITEMIDLIST pidlOld, LPCITEMIDLIST pidlNew);
private:
struct ILWRITEINFO {
IStream *pstmPidlWrite;
IStream *pstmLinkWrite;
CPinList *ppl;
HRESULT hr;
LPITEMIDLIST rgpidl[20]; // Must match ARRAYSIZE(c_rgcsidlRelative)
};
static BOOL ILWriteCallback(PINENTRY *pentry, ILWRITEINFO *pwi);
CDSA<PINENTRY> _dsaEntries; // The items themselves
IStream * _pstmLink; // PINENTRY._liPos points into this stream
};
class ATL_NO_VTABLE CStartMenuPin
: public IShellExtInit
, public IContextMenu
, public IStartMenuPin
, public CObjectWithSite
, public CComObjectRootEx<CComSingleThreadModel>
, public CComCoClass<CStartMenuPin, &CLSID_StartMenuPin>
{
public:
~CStartMenuPin();
BEGIN_COM_MAP(CStartMenuPin)
COM_INTERFACE_ENTRY(IShellExtInit)
// Need to use COM_INTERFACE_ENTRY_IID for the interfaces
// that don't have an idl
COM_INTERFACE_ENTRY_IID(IID_IContextMenu, IContextMenu)
COM_INTERFACE_ENTRY(IStartMenuPin)
COM_INTERFACE_ENTRY(IObjectWithSite)
END_COM_MAP()
DECLARE_NO_REGISTRY()
// *** IShellExtInit ***
STDMETHODIMP Initialize(LPCITEMIDLIST pidlFolder, IDataObject *pdto, HKEY hkProgID);
// *** IContextMenu ***
STDMETHODIMP QueryContextMenu(HMENU hmenu, UINT indexMenu, UINT idCmdFirst, UINT idCmdLast, UINT uFlags);
STDMETHODIMP InvokeCommand(LPCMINVOKECOMMANDINFO pici);
STDMETHODIMP GetCommandString(UINT_PTR idCmd, UINT uType, UINT *pwRes, LPSTR pszName, UINT cchMax);
// *** IStartMenuPin ***
STDMETHODIMP EnumObjects(IEnumIDList **ppenum);
STDMETHODIMP Modify(LPCITEMIDLIST pidlFrom, LPCITEMIDLIST pidlTo);
STDMETHODIMP GetChangeCount(ULONG *pulOut);
STDMETHODIMP IsPinnable(IDataObject *pdtobj, DWORD dwFlags, OPTIONAL LPITEMIDLIST *ppidl);
STDMETHODIMP Resolve(HWND hwnd, DWORD dwFlags, LPCITEMIDLIST pidl, LPITEMIDLIST *ppidlResolved);
// *** IObjectWithSite ***
// Inherited from CObjectWithSite
public:
HRESULT SetChangeCount(ULONG ul);
protected:
BOOL _IsAcceptableTarget(LPCTSTR pszPath, DWORD dwAttrib, DWORD dwFlags);
enum {
IDM_PIN = 0,
IDM_UNPIN = 1,
IDM_MAX,
};
// These "seem" backwards, but remember: If the item is pinned,
// then the command is "unpin". If the item is unpinned, then
// the command is "pin".
inline void _SetPinned() { _idmPinCmd = IDM_UNPIN; }
inline void _SetUnpinned() { _idmPinCmd = IDM_PIN; }
inline BOOL _IsPinned() const { return _idmPinCmd != IDM_PIN; }
inline BOOL _DoPin() const { return _idmPinCmd == IDM_PIN; }
inline BOOL _DoUnpin() const { return _idmPinCmd != IDM_PIN; }
inline UINT _GetMenuStringID() const
{
COMPILETIME_ASSERT(IDS_STARTPIN_UNPINME == IDS_STARTPIN_PINME + IDM_UNPIN);
return IDS_STARTPIN_PINME + _idmPinCmd;
}
static BOOL ILFreeCallback(LPITEMIDLIST pidl, void *)
{ ILFree(pidl); return TRUE; }
HRESULT _ShouldAddMenu(UINT uFlags);
HRESULT _InitPinRegStream();
protected:
IDataObject *_pdtobj;
UINT _idmPinCmd; // Which command did we add?
LPITEMIDLIST _pidl; // IContextMenu identity
};
#define REGSTR_PATH_STARTFAVS TEXT("Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\StartPage")
#define REGSTR_VAL_STARTFAVS TEXT("Favorites")
#define REGSTR_VAL_STARTFAVCHANGES TEXT("FavoritesChanges")
#define REGSTR_VAL_STARTFAVLINKS TEXT("FavoritesResolve")
IStream *_OpenPinRegStream(DWORD grfMode)
{
return SHOpenRegStream2(HKEY_CURRENT_USER, REGSTR_PATH_STARTFAVS, REGSTR_VAL_STARTFAVS, grfMode);
}
IStream *_OpenLinksRegStream(DWORD grfMode)
{
return SHOpenRegStream2(HKEY_CURRENT_USER, REGSTR_PATH_STARTFAVS, REGSTR_VAL_STARTFAVLINKS, grfMode);
}
const LARGE_INTEGER c_li0 = { 0, 0 };
const ULARGE_INTEGER& c_uli0 = (ULARGE_INTEGER&)c_li0;
HRESULT IStream_GetPos(IStream *pstm, LARGE_INTEGER *pliPos)
{
return pstm->Seek(c_li0, STREAM_SEEK_CUR, (ULARGE_INTEGER*)pliPos);
}
HRESULT IStream_Copy(IStream *pstmFrom, IStream *pstmTo, DWORD cb)
{
ULARGE_INTEGER uliToCopy, uliCopied;
uliToCopy.QuadPart = cb;
HRESULT hr = pstmFrom->CopyTo(pstmTo, uliToCopy, NULL, &uliCopied);
if (SUCCEEDED(hr) && uliToCopy.QuadPart != uliCopied.QuadPart)
{
hr = E_FAIL;
}
return hr;
}
class ATL_NO_VTABLE CStartMenuPinEnum
: public IEnumIDList
, public CComObjectRootEx<CComSingleThreadModel>
, public CComCoClass<CStartMenuPinEnum>
{
public:
~CStartMenuPinEnum()
{
ATOMICRELEASE(_pstm);
}
BEGIN_COM_MAP(CStartMenuPinEnum)
COM_INTERFACE_ENTRY(IEnumIDList)
END_COM_MAP()
/// *** IEnumIDList ***
STDMETHODIMP Next(ULONG celt, LPITEMIDLIST *rgelt, ULONG *pceltFetched);
STDMETHODIMP Skip(ULONG celt);
STDMETHODIMP Reset();
STDMETHODIMP Clone(IEnumIDList **ppenum);
private:
HRESULT _NextPidlFromStream(LPITEMIDLIST *ppidl);
HRESULT _InitPinRegStream();
private:
HRESULT _hrLastEnum; // Result of last IEnumIDList::Next
IStream * _pstm;
};
CStartMenuPin::~CStartMenuPin()
{
ILFree(_pidl);
if (_pdtobj)
_pdtobj->Release();
}
BOOL _IsLocalHardDisk(LPCTSTR pszPath)
{
// Reject CDs, floppies, network drives, etc.
//
int iDrive = PathGetDriveNumber(pszPath);
if (iDrive < 0 || // reject UNCs
RealDriveType(iDrive, /* fOkToHitNet = */ FALSE) != DRIVE_FIXED) // reject slow media
{
return FALSE;
}
return TRUE;
}
BOOL CStartMenuPin::_IsAcceptableTarget(LPCTSTR pszPath, DWORD dwAttrib, DWORD dwFlags)
{
// Regitems ("Internet" or "Email" for example) are acceptable
// provided we aren't restricted to EXEs only.
if (!(dwAttrib & SFGAO_FILESYSTEM))
{
return !(dwFlags & SMPINNABLE_EXEONLY);
}
// Otherwise, it's a file.
// If requested, reject non-EXEs.
// (Like the Start Menu, we treat MSC files as if they were EXEs)
if (dwFlags & SMPINNABLE_EXEONLY)
{
LPCTSTR pszExt = PathFindExtension(pszPath);
if (StrCmpIC(pszExt, TEXT(".EXE")) != 0 &&
StrCmpIC(pszExt, TEXT(".MSC")) != 0)
{
return FALSE;
}
}
// If requested, reject slow media
if (dwFlags & SMPINNABLE_REJECTSLOWMEDIA)
{
if (!_IsLocalHardDisk(pszPath))
{
return FALSE;
}
// If it's a shortcut, then apply the same rule to the shortcut.
if (PathIsLnk(pszPath))
{
BOOL fLocal = TRUE;
IShellLink *psl;
if (SUCCEEDED(LoadFromFile(CLSID_ShellLink, pszPath, IID_PPV_ARG(IShellLink, &psl))))
{
// IShellLink::GetPath returns S_FALSE if target is not a path
TCHAR szPath[MAX_PATH];
if (S_OK == psl->GetPath(szPath, ARRAYSIZE(szPath), NULL, 0))
{
fLocal = _IsLocalHardDisk(szPath);
}
psl->Release();
}
if (!fLocal)
{
return FALSE;
}
}
}
// All tests pass!
return TRUE;
}
BOOL IsStartPanelOn()
{
SHELLSTATE ss = { 0 };
SHGetSetSettings(&ss, SSF_STARTPANELON, FALSE);
return ss.fStartPanelOn;
}
HRESULT CStartMenuPin::IsPinnable(IDataObject *pdtobj, DWORD dwFlags, OPTIONAL LPITEMIDLIST *ppidl)
{
HRESULT hr = S_FALSE;
LPITEMIDLIST pidlRet = NULL;
if (pdtobj && // must have a data object
!SHRestricted(REST_NOSMPINNEDLIST) && // cannot be restricted
IsStartPanelOn()) // start panel must be on
{
STGMEDIUM medium = {0};
LPIDA pida = DataObj_GetHIDA(pdtobj, &medium);
if (pida)
{
if (pida->cidl == 1)
{
pidlRet = IDA_FullIDList(pida, 0);
if (pidlRet)
{
DWORD dwAttr = SFGAO_FILESYSTEM; // only SFGAO_FILESYSTEM is valid
TCHAR szPath[MAX_PATH];
if (SUCCEEDED(SHGetNameAndFlags(pidlRet, SHGDN_FORPARSING,
szPath, ARRAYSIZE(szPath), &dwAttr)) &&
_IsAcceptableTarget(szPath, dwAttr, dwFlags))
{
hr = S_OK;
}
}
}
HIDA_ReleaseStgMedium(pida, &medium);
}
}
// Return pidlRet only if the call succeeded and the caller requested it
if (hr != S_OK || !ppidl)
{
ILFree(pidlRet);
pidlRet = NULL;
}
if (ppidl)
{
*ppidl = pidlRet;
}
return hr;
}
// Returns S_OK if should add, S_FALSE if not
HRESULT CStartMenuPin::_ShouldAddMenu(UINT uFlags)
{
// "Pin" is never a default verb
if (uFlags & CMF_DEFAULTONLY)
return S_FALSE;
HRESULT hr;
// The context menu appears only for fast media
//
// If extended verbs are disabled, then show the menu only for EXEs
DWORD dwFlags = SMPINNABLE_REJECTSLOWMEDIA;
if (!(uFlags & CMF_EXTENDEDVERBS))
{
dwFlags |= SMPINNABLE_EXEONLY;
}
hr = IsPinnable(_pdtobj, dwFlags, &_pidl);
if (S_OK == hr)
{
// If we are enclosed inside a shortcut, change our identity to the
// enclosing shortcut.
IPersistFile *ppf;
if (SUCCEEDED(IUnknown_QueryService(_punkSite, SID_LinkSite, IID_PPV_ARG(IPersistFile, &ppf))))
{
LPOLESTR pszFile = NULL;
if (ppf->GetCurFile(&pszFile) == S_OK && pszFile)
{
// ILCreateFromPathEx turns %USERPROFILE%\Desktop\foo.lnk
// into CSIDL_DESKTOP\foo.lnk for us.
LPITEMIDLIST pidl;
if (SUCCEEDED(ILCreateFromPathEx(pszFile, NULL, ILCFP_FLAG_NORMAL, &pidl, NULL)))
if (pidl)
{
ILFree(_pidl);
_pidl = pidl;
hr = S_OK;
}
CoTaskMemFree(pszFile);
}
ppf->Release();
}
}
return hr;
}
// IShellExtInit::Initialize
HRESULT CStartMenuPin::Initialize(LPCITEMIDLIST, IDataObject *pdtobj, HKEY)
{
IUnknown_Set((IUnknown **)&_pdtobj, pdtobj); // just grab this guy
return S_OK;
}
// IContextMenu::QueryContextMenu
HRESULT CStartMenuPin::QueryContextMenu(HMENU hmenu, UINT indexMenu, UINT idCmdFirst, UINT idCmdLast, UINT uFlags)
{
HRESULT hr = _ShouldAddMenu(uFlags);
if (S_OK == hr)
{
_SetUnpinned();
// Determine whether this item is already on the Start Page or not.
IEnumIDList *penum;
hr = EnumObjects(&penum);
if (SUCCEEDED(hr))
{
LPITEMIDLIST pidl;
while (penum->Next(1, &pidl, NULL) == S_OK)
{
BOOL bSame = ILIsEqual(pidl, _pidl);
ILFree(pidl);
if (bSame)
{
_SetPinned();
break;
}
}
penum->Release();
TCHAR szCommand[MAX_PATH];
if (LoadString(g_hinst, _GetMenuStringID(), szCommand, ARRAYSIZE(szCommand)))
{
InsertMenu(hmenu, indexMenu, MF_STRING | MF_BYPOSITION,
idCmdFirst + _idmPinCmd, szCommand);
}
hr = ResultFromShort(IDM_MAX);
}
}
return hr;
}
const LPCTSTR c_rgpszVerb[] =
{
TEXT("pin"), // IDM_PIN
TEXT("unpin"), // IDM_UNPIN
};
// *** IContextMenu::InvokeCommand
HRESULT CStartMenuPin::InvokeCommand(LPCMINVOKECOMMANDINFO pici)
{
LPCMINVOKECOMMANDINFOEX picix = reinterpret_cast<LPCMINVOKECOMMANDINFOEX>(pici);
HRESULT hr = E_INVALIDARG;
UINT idmCmd;
if (IS_INTRESOURCE(pici->lpVerb))
{
idmCmd = PtrToInt(pici->lpVerb);
}
else
{
// Convert the string to an ID (or out of range if invalid)
LPCTSTR pszVerb;
#ifdef UNICODE
WCHAR szVerb[MAX_PATH];
if (pici->cbSize >= CMICEXSIZE_NT4 &&
(pici->fMask & CMIC_MASK_UNICODE) &&
picix->lpVerbW)
{
pszVerb = picix->lpVerbW;
}
else
{
SHAnsiToTChar(pici->lpVerb, szVerb, ARRAYSIZE(szVerb));
pszVerb = szVerb;
}
#else
pszVerb = pici->lpVerb;
#endif
for (idmCmd = 0; idmCmd < ARRAYSIZE(c_rgpszVerb); idmCmd++)
{
if (lstrcmpi(pszVerb, c_rgpszVerb[idmCmd]) == 0)
{
break;
}
}
}
if (idmCmd == _idmPinCmd)
{
if (_idmPinCmd == IDM_PIN)
{
hr = Modify(NULL, _pidl);
}
else
{
hr = Modify(_pidl, NULL);
}
}
return hr;
}
// *** IContextMenu::GetCommandString
HRESULT CStartMenuPin::GetCommandString(UINT_PTR idCmd, UINT uType, UINT *pwRes, LPSTR pszName, UINT cchMax)
{
TCHAR szBuf[MAX_PATH];
LPCTSTR pszResult = NULL;
switch (uType & ~GCS_UNICODE)
{
case GCS_VERBA:
if (idCmd < ARRAYSIZE(c_rgpszVerb))
{
pszResult = c_rgpszVerb[idCmd];
}
break;
case GCS_HELPTEXTA:
if (idCmd < ARRAYSIZE(c_rgpszVerb))
{
COMPILETIME_ASSERT(IDS_STARTPIN_PINME_HELP + IDM_UNPIN == IDS_STARTPIN_UNPINME_HELP);
if (LoadString(g_hinst, IDS_STARTPIN_PINME_HELP + (UINT)idCmd, szBuf, ARRAYSIZE(szBuf)))
{
pszResult = szBuf;
}
}
break;
}
if (pszResult)
{
if (uType & GCS_UNICODE)
{
SHTCharToUnicode(pszResult, (LPWSTR)pszName, cchMax);
}
else
{
SHTCharToAnsi(pszResult, pszName, cchMax);
}
return S_OK;
}
return E_NOTIMPL;
}
PINENTRY *CPinList::FindPidl(LPCITEMIDLIST pidl, int *pi)
{
for (int i = _dsaEntries.GetItemCount() - 1; i >= 0; i--)
{
PINENTRY *pentry = _dsaEntries.GetItemPtr(i);
if (ILIsEqual(pentry->_pidl, pidl))
{
if (pi)
{
*pi = i;
}
return pentry;
}
}
return NULL;
}
HRESULT CPinList::ReplacePidl(LPCITEMIDLIST pidlOld, LPCITEMIDLIST pidlNew)
{
int i;
PINENTRY *pentry = FindPidl(pidlOld, &i);
if (pentry)
{
if (pidlNew == NULL) // Delete
{
pentry->Destruct();
_dsaEntries.DeleteItem(i);
return S_OK;
}
else
if (IS_INTRESOURCE(pidlNew)) // Move
{
// Move the pidl from i to iPos
PINENTRY entry = *pentry;
int iPos = ((int)(INT_PTR)pidlNew) - 1;
if (i < iPos)
{
// Moving down; others move up
iPos--;
// Must use MoveMemory because the memory blocks overlap
MoveMemory(_dsaEntries.GetItemPtr(i),
_dsaEntries.GetItemPtr(i+1),
sizeof(PINENTRY) * (iPos-i));
}
else if (i > iPos)
{
// Moving up; others move down
// Must use MoveMemory because the memory blocks overlap
MoveMemory(_dsaEntries.GetItemPtr(iPos+1),
_dsaEntries.GetItemPtr(iPos),
sizeof(PINENTRY) * (i-iPos));
}
_dsaEntries.SetItem(iPos, &entry);
return S_OK;
}
else // Replace
{
if (Pidl_Set(&pentry->_pidl, pidlNew))
{
// Failure to update the shell link is not fatal;
// it just means we won't be able to repair the
// shortcut if it breaks.
pentry->UpdateShellLink();
return S_OK;
}
else
{
return E_OUTOFMEMORY;
}
}
}
return HRESULT_FROM_WIN32(ERROR_NOT_FOUND);
}
HRESULT CStartMenuPin::Modify(LPCITEMIDLIST pidlFrom, LPCITEMIDLIST pidlTo)
{
HRESULT hr;
if(SHRestricted(REST_NOSMPINNEDLIST))
return E_ACCESSDENIED;
// Remap pidls to logical pidls (change CSIDL_DESKTOPDIRECTORY
// to CSIDL_DESKTOP, etc.) so we don't get faked out when people
// access objects sometimes directly on the desktop and sometimes
// via their full filesystem name.
LPITEMIDLIST pidlFromFree = NULL;
LPITEMIDLIST pidlToFree = NULL;
if (!IS_INTRESOURCE(pidlFrom))
{
pidlFromFree = SHLogILFromFSIL(pidlFrom);
if (pidlFromFree) {
pidlFrom = pidlFromFree;
}
}
if (!IS_INTRESOURCE(pidlTo))
{
pidlToFree = SHLogILFromFSIL(pidlTo);
if (pidlToFree) {
pidlTo = pidlToFree;
}
}
CPinList pl;
hr = pl.Load(this);
if (SUCCEEDED(hr))
{
if (SUCCEEDED(hr))
{
if (pidlFrom)
{
hr = pl.ReplacePidl(pidlFrom, pidlTo);
}
else if (pidlTo)
{
LPITEMIDLIST pidl = ILClone(pidlTo);
if (pidl)
{
int iPos = pl.AppendPidl(pidl);
if (iPos >= 0)
{
// Failure to update the shell link is not fatal;
// it just means we won't be able to repair the
// shortcut if it breaks.
pl.GetItemPtr(iPos)->UpdateShellLink();
}
else
{
ILFree(pidl);
hr = E_OUTOFMEMORY;
}
}
else
{
hr = E_OUTOFMEMORY;
}
}
else
{
// pidlFrom == pidlTo == NULL? What does that mean?
hr = E_INVALIDARG;
}
if (SUCCEEDED(hr))
{
hr = pl.Save(this);
}
}
}
else
{
hr = E_OUTOFMEMORY; // could not create dpa
}
ILFree(pidlFromFree);
ILFree(pidlToFree);
return hr;
}
//
// Find the pidl on the pin list and resolve the shortcut that
// tracks it.
//
// Returns S_OK if the pidl changed and was resolved.
// Returns S_FALSE if the pidl did not change.
// Returns an error if the Resolve failed.
//
HRESULT CStartMenuPin::Resolve(HWND hwnd, DWORD dwFlags, LPCITEMIDLIST pidl, LPITEMIDLIST *ppidlResolved)
{
*ppidlResolved = NULL;
if(SHRestricted(REST_NOSMPINNEDLIST))
return E_ACCESSDENIED;
// Remap pidls to logical pidls (change CSIDL_DESKTOPDIRECTORY
// to CSIDL_DESKTOP, etc.) so we don't get faked out when people
// access objects sometimes directly on the desktop and sometimes
// via their full filesystem name.
LPITEMIDLIST pidlFree = SHLogILFromFSIL(pidl);
if (pidlFree) {
pidl = pidlFree;
}
CPinList pl;
HRESULT hr = pl.Load(this);
if (SUCCEEDED(hr))
{
PINENTRY *pentry = pl.FindPidl(pidl, NULL);
if (pentry)
{
IShellLink *psl;
hr = pl.LoadShellLink(pentry, &psl);
if (SUCCEEDED(hr))
{
hr = psl->Resolve(hwnd, dwFlags);
if (hr == S_OK)
{
IPersistStream *pps;
hr = psl->QueryInterface(IID_PPV_ARG(IPersistStream, &pps));
if (SUCCEEDED(hr))
{
if (pps->IsDirty() == S_OK)
{
LPITEMIDLIST pidlNew;
hr = psl->GetIDList(&pidlNew);
if (SUCCEEDED(hr) && hr != S_OK)
{
// GetIDList returns S_FALSE on failure...
hr = E_FAIL;
}
if (SUCCEEDED(hr))
{
ILFree(pentry->_pidl);
pentry->_pidl = pidlNew;
hr = SHILClone(pidlNew, ppidlResolved);
}
}
pps->Release();
}
}
else if (SUCCEEDED(hr))
{
// S_FALSE means "cancelled by user"
hr = HRESULT_FROM_WIN32(ERROR_CANCELLED);
}
psl->Release();
}
}
else
{
hr = HRESULT_FROM_WIN32(ERROR_NOT_FOUND);
}
if (hr == S_OK)
{
pl.Save(this); // if this fails, tough
}
}
ILFree(pidlFree);
return hr;
}
//
// The target pidl has changed (or it's brand new). Create an IShellLink
// around it so we can resolve it later.
//
HRESULT PINENTRY::UpdateShellLink()
{
ASSERT(_pidl);
// Pitch the old link; it's useless now.
FreeShellLink();
HRESULT hr = SHCoCreateInstance(NULL, &CLSID_ShellLink, NULL, IID_PPV_ARG(IShellLink, &_psl));
if (SUCCEEDED(hr))
{
hr = _psl->SetIDList(_pidl);
if (FAILED(hr))
{
FreeShellLink(); // pitch it; it's no good
}
}
return hr;
}
HRESULT CPinList::SaveShellLink(PINENTRY *pentry, IStream *pstm)
{
HRESULT hr;
if (pentry->_psl)
{
// It's still in the form of an IShellLink.
// Save it to the stream, then go back and update the size information.
LARGE_INTEGER liPos, liPosAfter;
DWORD cbSize = 0;
IPersistStream *pps = NULL;
if (SUCCEEDED(hr = IStream_GetPos(pstm, &liPos)) &&
// Write a dummy DWORD; we will come back and patch it up later
SUCCEEDED(hr = IStream_Write(pstm, &cbSize, sizeof(cbSize))) &&
SUCCEEDED(hr = pentry->_psl->QueryInterface(IID_PPV_ARG(IPersistStream, &pps))))
{
if (SUCCEEDED(hr = pps->Save(pstm, TRUE)) &&
SUCCEEDED(hr = IStream_GetPos(pstm, &liPosAfter)) &&
SUCCEEDED(hr = pstm->Seek(liPos, STREAM_SEEK_SET, NULL)))
{
cbSize = liPosAfter.LowPart - liPos.LowPart - sizeof(DWORD);
if (SUCCEEDED(hr = IStream_Write(pstm, &cbSize, sizeof(cbSize))) &&
SUCCEEDED(hr = pstm->Seek(liPosAfter, STREAM_SEEK_SET, NULL)))
{
// Hooray! All got saved okay
}
}
pps->Release();
}
}
else
{
// It's just a reference back into our parent stream; copy it
if (SUCCEEDED(hr = IStream_Write(pstm, &pentry->_cbSize, sizeof(pentry->_cbSize))))
{
// If _cbSize == 0 then _pstmLink might be NULL, so guard against it
if (pentry->_cbSize)
{
if (SUCCEEDED(hr = _pstmLink->Seek(pentry->_liPos, STREAM_SEEK_SET, NULL)) &&
SUCCEEDED(hr = IStream_Copy(_pstmLink, pstm, pentry->_cbSize)))
{
// Hooray! All got saved okay
}
}
else
{
// Entry was blank - nothing to do, vacuous success
}
}
}
return hr;
}
HRESULT CPinList::LoadShellLink(PINENTRY *pentry, IShellLink **ppsl)
{
HRESULT hr;
if (pentry->_psl)
{
hr = S_OK; // We already have the link
}
else if (pentry->_cbSize == 0)
{
hr = E_FAIL; // no link available
}
else
{ // gotta make it
IPersistStream *pps;
hr = SHCoCreateInstance(NULL, &CLSID_ShellLink, NULL, IID_PPV_ARG(IPersistStream, &pps));
if (SUCCEEDED(hr))
{
if (SUCCEEDED(hr = _pstmLink->Seek(pentry->_liPos, STREAM_SEEK_SET, NULL)) &&
SUCCEEDED(hr = pps->Load(_pstmLink)) &&
SUCCEEDED(hr = pps->QueryInterface(IID_PPV_ARG(IShellLink, &pentry->_psl))))
{
// woo-hoo! All got loaded okay
}
pps->Release();
}
}
*ppsl = pentry->_psl;
if (SUCCEEDED(hr))
{
pentry->_psl->AddRef();
hr = S_OK;
}
return hr;
}
HRESULT CStartMenuPin::GetChangeCount(ULONG *pulOut)
{
DWORD cb = sizeof(*pulOut);
if (SHGetValue(HKEY_CURRENT_USER, REGSTR_PATH_STARTFAVS,
REGSTR_VAL_STARTFAVCHANGES, NULL, pulOut, &cb) != ERROR_SUCCESS)
{
*pulOut = 0;
}
return S_OK;
}
HRESULT CStartMenuPin::SetChangeCount(ULONG ulChange)
{
SHSetValue(HKEY_CURRENT_USER, REGSTR_PATH_STARTFAVS,
REGSTR_VAL_STARTFAVCHANGES, REG_DWORD, &ulChange,
sizeof(ulChange));
return S_OK;
}
//
// We scan this list in order, so if there is a CSIDL that is a subdirectory
// of another CSIDL, we must put the subdirectory first. For example,
// CSIDL_PROGRAMS is typically a subdirectory of CSIDL_STARTMENU, so we
// must put CSIDL_PROGRAMS first so we get the best match.
//
// Furthermore, directories pinned items are more likely to be found in
// should come before less popular directories.
//
const int c_rgcsidlRelative[] = {
// Most common: Start Menu stuff
CSIDL_PROGRAMS, // Programs must come before StartMenu
CSIDL_STARTMENU, // Programs must come before StartMenu
// Next most common: My Documents stuff
CSIDL_MYPICTURES, // MyXxx must come before Personal
CSIDL_MYMUSIC, // MyXxx must come before Personal
CSIDL_MYVIDEO, // MyXxx must come before Personal
CSIDL_PERSONAL, // MyXxx must come before Personal
CSIDL_COMMON_PROGRAMS, // Programs must come before StartMenu
CSIDL_COMMON_STARTMENU, // Programs must come before StartMenu
// Next most common: Desktop stuff
CSIDL_DESKTOPDIRECTORY,
CSIDL_COMMON_DESKTOPDIRECTORY,
// Next most common: Program files stuff
CSIDL_PROGRAM_FILES_COMMON, // ProgramFilesCommon must come before ProgramFiles
CSIDL_PROGRAM_FILES, // ProgramFilesCommon must come before ProgramFiles
CSIDL_PROGRAM_FILES_COMMONX86, // ProgramFilesCommon must come before ProgramFiles
CSIDL_PROGRAM_FILESX86, // ProgramFilesCommon must come before ProgramFiles
// Other stuff (less common)
CSIDL_APPDATA,
CSIDL_COMMON_APPDATA,
CSIDL_SYSTEM,
CSIDL_SYSTEMX86,
CSIDL_WINDOWS,
CSIDL_PROFILE, // Must come after all other profile-relative directories
};
BOOL CPinList::ILWriteCallback(PINENTRY *pentry, ILWRITEINFO *pwi)
{
BYTE csidl = CSIDL_DESKTOP; // Assume nothing interesting
LPITEMIDLIST pidlWrite = pentry->_pidl; // Assume nothing interesting
for (int i = 0; i < ARRAYSIZE(pwi->rgpidl); i++)
{
LPITEMIDLIST pidlT;
if (pwi->rgpidl[i] &&
(pidlT = ILFindChild(pwi->rgpidl[i], pentry->_pidl)))
{
csidl = (BYTE)c_rgcsidlRelative[i];
pidlWrite = pidlT;
break;
}
}
if (SUCCEEDED(pwi->hr = IStream_Write(pwi->pstmPidlWrite, &csidl, sizeof(csidl))) &&
SUCCEEDED(pwi->hr = IStream_WritePidl(pwi->pstmPidlWrite, pidlWrite)) &&
SUCCEEDED(pwi->hr = pwi->ppl->SaveShellLink(pentry, pwi->pstmLinkWrite)))
{
// woo-hoo, all written successfully
}
return SUCCEEDED(pwi->hr);
}
#define CSIDL_END ((BYTE)0xFF)
HRESULT CPinList::Save(CStartMenuPin *psmpin)
{
ILWRITEINFO wi;
COMPILETIME_ASSERT(ARRAYSIZE(c_rgcsidlRelative) == ARRAYSIZE(wi.rgpidl));
for (int i = 0; i < ARRAYSIZE(c_rgcsidlRelative); i++)
{
SHGetSpecialFolderLocation(NULL, c_rgcsidlRelative[i], &wi.rgpidl[i]);
}
wi.pstmPidlWrite = _OpenPinRegStream(STGM_WRITE);
if (wi.pstmPidlWrite)
{
wi.pstmLinkWrite = _OpenLinksRegStream(STGM_WRITE);
if (wi.pstmLinkWrite)
{
wi.hr = S_OK;
wi.ppl = this;
_dsaEntries.EnumCallbackEx(ILWriteCallback, &wi);
if (SUCCEEDED(wi.hr))
{
BYTE csidlEnd = CSIDL_END;
wi.hr = IStream_Write(wi.pstmPidlWrite, &csidlEnd, sizeof(csidlEnd));
}
if (FAILED(wi.hr))
{
wi.pstmPidlWrite->SetSize(c_uli0);
wi.pstmLinkWrite->SetSize(c_uli0);
}
wi.pstmLinkWrite->Release();
}
wi.pstmPidlWrite->Release();
}
else
{
wi.hr = E_ACCESSDENIED; // Most common reason is lack of write permission
}
for (i = 0; i < ARRAYSIZE(c_rgcsidlRelative); i++)
{
ILFree(wi.rgpidl[i]);
}
// Bump the change count so people can detect and refresh
ULONG ulChange;
psmpin->GetChangeCount(&ulChange);
psmpin->SetChangeCount(ulChange + 1);
// Notify everyone that the pin list changed
SHChangeDWORDAsIDList dwidl;
dwidl.cb = SIZEOF(dwidl) - SIZEOF(dwidl.cbZero);
dwidl.dwItem1 = SHCNEE_PINLISTCHANGED;
dwidl.dwItem2 = 0;
dwidl.cbZero = 0;
SHChangeNotify(SHCNE_EXTENDED_EVENT, SHCNF_FLUSH, (LPCITEMIDLIST)&dwidl, NULL);
return wi.hr;
}
HRESULT CPinList::Load(CStartMenuPin *psmpin)
{
HRESULT hr;
if (Initialize())
{
IEnumIDList *penum;
hr = psmpin->EnumObjects(&penum);
if (SUCCEEDED(hr))
{
LPITEMIDLIST pidl;
while (penum->Next(1, &pidl, NULL) == S_OK)
{
if (AppendPidl(pidl) < 0)
{
ILFree(pidl);
hr = E_OUTOFMEMORY;
break;
}
}
penum->Release();
}
if (SUCCEEDED(hr))
{
//
// Now read the persisted shortcuts.
//
_pstmLink = _OpenLinksRegStream(STGM_READ);
if (_pstmLink)
{
for (int i = 0; i < _dsaEntries.GetItemCount(); i++)
{
PINENTRY *pentry = _dsaEntries.GetItemPtr(i);
LARGE_INTEGER liSeek = { 0, 0 };
if (SUCCEEDED(hr = IStream_Read(_pstmLink, &liSeek.LowPart, sizeof(liSeek.LowPart))) && // read size
SUCCEEDED(hr = IStream_GetPos(_pstmLink, &pentry->_liPos)) && // read current pos
SUCCEEDED(hr = _pstmLink->Seek(liSeek, STREAM_SEEK_CUR, NULL))) // skip over link
{
pentry->_cbSize = liSeek.LowPart; // set this only on success
}
else
{
break;
}
}
}
// If we encountered an error,
// then throw all the shortcuts away because they are
// probably corrupted.
if (FAILED(hr))
{
for (int i = 0; i < _dsaEntries.GetItemCount(); i++)
{
_dsaEntries.GetItemPtr(i)->FreeShellLink();
}
}
// Problems reading the persisted shortcuts are ignored
// since they are merely advisory.
hr = S_OK;
}
}
else
{
hr = E_OUTOFMEMORY;
}
return hr;
}
//
// Reading a pidl from a stream is a dangerous proposition because
// a corrupted pidl can cause a shell extension to go haywire.
//
// A pinned item is stored in the stream in the form
//
// [byte:csidl] [dword:cbPidl] [size_is(cbPidl):pidl]
//
// With the special csidl = -1 indicating the end of the list.
//
// We use a byte for the csidl so a corrupted stream won't accidentally
// pass "CSIDL_FLAG_CREATE" as a csidl to SHGetSpecialFolderLocation.
HRESULT CStartMenuPinEnum::_NextPidlFromStream(LPITEMIDLIST *ppidl)
{
BYTE csidl;
HRESULT hr = IStream_Read(_pstm, &csidl, sizeof(csidl));
if (SUCCEEDED(hr))
{
if (csidl == CSIDL_END)
{
hr = S_FALSE; // end of enumeration
}
else
{
LPITEMIDLIST pidlRoot;
hr = SHGetSpecialFolderLocation(NULL, csidl, &pidlRoot);
if (SUCCEEDED(hr))
{
LPITEMIDLIST pidl;
hr = IStream_ReadPidl(_pstm, &pidl);
if (SUCCEEDED(hr))
{
hr = SHILCombine(pidlRoot, pidl, ppidl);
ILFree(pidl);
}
ILFree(pidlRoot);
}
}
}
return hr;
}
// *** IEnumIDList::Next
HRESULT CStartMenuPinEnum::Next(ULONG celt, LPITEMIDLIST *rgelt, ULONG *pceltFetched)
{
HRESULT hr;
ASSERT(celt > 0);
// If there was an error or EOF on the last call to IEnumIDList::Next,
// then that result is sticky. Once an enumeration has errored, it stays
// in the error state; once it has reached EOF, it stays at EOF. The
// only way to clear the state is to perform a Reset().
if (_hrLastEnum != S_OK)
{
return _hrLastEnum;
}
if (!_pstm)
{
_pstm = _OpenPinRegStream(STGM_READ);
}
if (_pstm)
{
rgelt[0] = NULL;
hr = _NextPidlFromStream(rgelt);
}
else
{
hr = S_FALSE; // No stream therefore no items
}
if (pceltFetched)
{
*pceltFetched = hr == S_OK ? 1 : 0;
}
// Remember the return code for next time. If an error occured or EOF,
// then free the memory used for enumeration.
_hrLastEnum = hr;
if (_hrLastEnum != S_OK)
{
ATOMICRELEASE(_pstm);
}
return hr;
}
// *** IEnumIDList::Skip
HRESULT CStartMenuPinEnum::Skip(ULONG)
{
return E_NOTIMPL;
}
// *** IEnumIDList::Reset
HRESULT CStartMenuPinEnum::Reset()
{
_hrLastEnum = S_OK;
ATOMICRELEASE(_pstm);
return S_OK;
}
// *** IEnumIDList::Clone
STDMETHODIMP CStartMenuPinEnum::Clone(IEnumIDList **ppenum)
{
*ppenum = NULL;
return E_NOTIMPL;
}
// *** IStartMenuPin::EnumObjects
STDMETHODIMP CStartMenuPin::EnumObjects(IEnumIDList **ppenum)
{
_InitPinRegStream();
*ppenum = NULL;
return CStartMenuPinEnum::CreateInstance(ppenum);
}
STDAPI CStartMenuPin_CreateInstance(IUnknown* punkOuter, REFIID riid, void** ppunk)
{
return CStartMenuPin::_CreatorClass::CreateInstance(punkOuter, riid, ppunk);
}
// *** IStartMenuPin::_InitPinRegStream
//
// If the pin list has not yet been created, then create a default one.
//
static LPCTSTR c_rgszDefaultPin[] = {
TEXT("shell:::{2559a1f4-21d7-11d4-bdaf-00c04f60b9f0}"), // CLSID_AutoCMClientInet
TEXT("shell:::{2559a1f5-21d7-11d4-bdaf-00c04f60b9f0}"), // CLSID_AutoCMClientMail
#ifdef NOTYET
// NOTE! Before you turn this on, make sure wmp is installed on ia64
TEXT("shell:::{2559a1f2-21d7-11d4-bdaf-00c04f60b9f0}"), // CLSID_AutoCMClientMedia
#endif
};
HRESULT CStartMenuPin::_InitPinRegStream()
{
HRESULT hr = S_OK;
if(SHRestricted(REST_NOSMPINNEDLIST))
return hr; //Nothing to initialize.
IStream *pstm = _OpenPinRegStream(STGM_READ);
BOOL fEmpty = pstm == NULL || SHIsEmptyStream(pstm);
ATOMICRELEASE(pstm);
if (fEmpty)
{
// Create a default pin list
CPinList pl;
// Do not call pl.Load() because that will recurse back into us!
if (pl.Initialize())
{
for (int i = 0; i < ARRAYSIZE(c_rgszDefaultPin); i++)
{
LPITEMIDLIST pidl = ILCreateFromPath(c_rgszDefaultPin[i]);
if (pidl && pl.AppendPidl(pidl) < 0)
{
ILFree(pidl);
}
}
hr = pl.Save(this);
}
else
{
hr = E_OUTOFMEMORY;
}
}
return hr;
}