// Implements Folder Shortcut. #include "shellprv.h" #include "clsobj.h" // implemented in filefldr.cpp extern LPTSTR PathFindCLSIDExtension(LPCTSTR pszFile, CLSID *pclsid); BOOL CreateFolderDesktopIni(LPCTSTR pszName) { SHFOLDERCUSTOMSETTINGS fcs = {0}; fcs.dwSize = sizeof(fcs); fcs.dwMask = FCSM_CLSID | FCSM_FLAGS; fcs.pclsid = (GUID*)&CLSID_FolderShortcut; fcs.dwFlags = FCS_FLAG_DRAGDROP; return SUCCEEDED(SHGetSetFolderCustomSettings(&fcs, pszName, FCS_FORCEWRITE)); } EXTERN_C BOOL IsFolderShortcut(LPCTSTR pszName) { SHFOLDERCUSTOMSETTINGS fcs = {0}; CLSID clsid = {0}; fcs.dwSize = sizeof(fcs); fcs.dwMask = FCSM_CLSID; fcs.pclsid = &clsid; if (SUCCEEDED(SHGetSetFolderCustomSettings(&fcs, pszName, FCS_READ))) { return IsEqualGUID(clsid, CLSID_FolderShortcut); } return FALSE; } // exported from fsnotify.c STDAPI_(void) SHChangeNotifyRegisterAlias(LPCITEMIDLIST pidlReal, LPCITEMIDLIST pidlAlias); class CFolderShortcut : public IShellFolder2, public IPersistFolder3, public IShellLinkA, public IShellLinkW, public IPersistFile, public IExtractIcon, public IQueryInfo, public IFolderShortcutConvert, public IPersistStreamInit, public IPersistPropertyBag, public IBrowserFrameOptions { public: // IUnknown STDMETHODIMP QueryInterface(REFIID, void **); STDMETHODIMP_(ULONG) AddRef(void); STDMETHODIMP_(ULONG) Release(void); // IShellFolder STDMETHODIMP ParseDisplayName(HWND hwnd, LPBC pbc, LPOLESTR pszDisplayName, ULONG *pchEaten, LPITEMIDLIST *ppidl, ULONG *pdwAttributes); STDMETHODIMP EnumObjects(HWND hwnd, DWORD grfFlags, IEnumIDList ** ppenumIDList); STDMETHODIMP BindToObject(LPCITEMIDLIST pidl, LPBC pbc, REFIID riid, void **ppv); STDMETHODIMP BindToStorage(LPCITEMIDLIST pidl, LPBC pbc, REFIID riid, void **ppv); STDMETHODIMP CompareIDs(LPARAM lParam, LPCITEMIDLIST pidl1, LPCITEMIDLIST pidl2); STDMETHODIMP CreateViewObject (HWND hwnd, REFIID riid, void **ppv); STDMETHODIMP GetAttributesOf(UINT cidl, LPCITEMIDLIST * apidl, ULONG *rgfInOut); STDMETHODIMP GetUIObjectOf(HWND hwnd, UINT cidl, LPCITEMIDLIST * apidl, REFIID riid, UINT * prgfInOut, void **ppv); STDMETHODIMP GetDisplayNameOf(LPCITEMIDLIST pidl, DWORD uFlags, LPSTRRET lpName); STDMETHODIMP SetNameOf(HWND hwnd, LPCITEMIDLIST pidl, LPCOLESTR pszName, DWORD uFlags, LPITEMIDLIST *ppidlOut); // IShellFolder2 STDMETHODIMP GetDefaultSearchGUID(GUID *pGuid); STDMETHODIMP EnumSearches(IEnumExtraSearch **ppenum); STDMETHODIMP GetDefaultColumn(DWORD dwRes, ULONG *pSort, ULONG *pDisplay); STDMETHODIMP GetDefaultColumnState(UINT iColumn, DWORD *pbState); STDMETHODIMP GetDetailsEx(LPCITEMIDLIST pidl, const SHCOLUMNID *pscid, VARIANT *pv); STDMETHODIMP GetDetailsOf(LPCITEMIDLIST pidl, UINT iColumn, SHELLDETAILS *pDetails); STDMETHODIMP MapColumnToSCID(UINT iColumn, SHCOLUMNID *pscid); // IPersist STDMETHODIMP GetClassID(CLSID *pClassID); // IPersistFolder STDMETHODIMP Initialize(LPCITEMIDLIST pidl); // IPersistFolder2 STDMETHODIMP GetCurFolder(LPITEMIDLIST *ppidl); // IPersistFolder3 STDMETHODIMP InitializeEx(IBindCtx *pbc, LPCITEMIDLIST pidlRoot, const PERSIST_FOLDER_TARGET_INFO *pfti); STDMETHODIMP GetFolderTargetInfo(PERSIST_FOLDER_TARGET_INFO *pfti); // IPersistStream STDMETHODIMP Load(IStream *pStm); STDMETHODIMP Save(IStream *pStm,int fClearDirty); STDMETHODIMP GetSizeMax(ULARGE_INTEGER * pcbSize); // IPersistPropertyBag STDMETHODIMP Save(IPropertyBag* pPropBag, BOOL fClearDirty, BOOL fSaveAllProperties); STDMETHODIMP Load(IPropertyBag* pPropBag, IErrorLog* pErrorLog); // IPersistPropertyBag/IPersistStreamInit STDMETHODIMP InitNew(void); // IPersistFile STDMETHODIMP Load(LPCOLESTR pszFileName, DWORD dwMode); STDMETHODIMP Save(LPCOLESTR pszFileName, BOOL fRemember); STDMETHODIMP IsDirty() { return E_NOTIMPL; }; STDMETHODIMP SaveCompleted(LPCOLESTR pszFileName) { return E_NOTIMPL; }; STDMETHODIMP GetCurFile(LPOLESTR *ppszFileName); // IShellLinkW STDMETHODIMP GetPath(LPWSTR pszFile, int cchMaxPath, WIN32_FIND_DATAW *pfd, DWORD flags); STDMETHODIMP SetPath(LPCWSTR pszFile); STDMETHODIMP GetIDList(LPITEMIDLIST *ppidl); STDMETHODIMP SetIDList(LPCITEMIDLIST pidl); STDMETHODIMP GetDescription(LPWSTR pszName, int cchMaxName); STDMETHODIMP SetDescription(LPCWSTR pszName); STDMETHODIMP GetWorkingDirectory(LPWSTR pszDir, int cchMaxPath); STDMETHODIMP SetWorkingDirectory(LPCWSTR pszDir); STDMETHODIMP GetArguments(LPWSTR pszArgs, int cchMaxPath); STDMETHODIMP SetArguments(LPCWSTR pszArgs); STDMETHODIMP GetHotkey(WORD *pwHotkey); STDMETHODIMP SetHotkey(WORD wHotkey); STDMETHODIMP GetShowCmd(int *piShowCmd); STDMETHODIMP SetShowCmd(int iShowCmd); STDMETHODIMP GetIconLocation(LPWSTR pszIconPath, int cchIconPath, int *piIcon); STDMETHODIMP SetIconLocation(LPCWSTR pszIconPath, int iIcon); STDMETHODIMP Resolve(HWND hwnd, DWORD fFlags); STDMETHODIMP SetRelativePath(LPCWSTR pszPathRel, DWORD dwReserved); // IShellLinkA STDMETHODIMP GetPath(LPSTR pszFile, int cchMaxPath, WIN32_FIND_DATAA *pfd, DWORD flags); STDMETHODIMP SetPath(LPCSTR pszFile); STDMETHODIMP GetDescription(LPSTR pszName, int cchMaxName); STDMETHODIMP SetDescription(LPCSTR pszName); STDMETHODIMP GetWorkingDirectory(LPSTR pszDir, int cchMaxPath); STDMETHODIMP SetWorkingDirectory(LPCSTR pszDir); STDMETHODIMP GetArguments(LPSTR pszArgs, int cchMaxPath); STDMETHODIMP SetArguments(LPCSTR pszArgs); STDMETHODIMP GetIconLocation(LPSTR pszIconPath, int cchIconPath, int *piIcon); STDMETHODIMP SetIconLocation(LPCSTR pszIconPath, int iIcon); STDMETHODIMP SetRelativePath(LPCSTR pszPathRel, DWORD dwReserved); // IFolderShortcutConvert STDMETHODIMP ConvertToLink(LPCOLESTR pszPathLNK, DWORD fFlags); STDMETHODIMP ConvertToFolderShortcut(LPCOLESTR pszPathLNK, DWORD fFlags); // IExtractIcon STDMETHODIMP GetIconLocation(UINT uFlags, LPTSTR pszIconFile, UINT ucchMax, INT *pniIcon, UINT *puFlags); STDMETHODIMP Extract(LPCTSTR pcszFile, UINT uIconIndex, HICON *phiconLarge, HICON *phiconSmall, UINT ucIconSize); // IQueryInfo STDMETHODIMP GetInfoTip(DWORD dwFlags, WCHAR** ppwszTip); STDMETHODIMP GetInfoFlags(DWORD *pdwFlags); // IBrowserFrameOptions STDMETHODIMP GetFrameOptions(IN BROWSERFRAMEOPTIONS dwMask, IN BROWSERFRAMEOPTIONS * pdwOptions); CFolderShortcut(); protected: ~CFolderShortcut(); void _ClearState(); void _ClearTargetFolder(); private: HRESULT _LoadShortcut(); HRESULT _GetTargetIDList(BOOL fResolve); HRESULT _BindFolder(BOOL fResolve); HRESULT _GetFolder(BOOL fForceResolve); HRESULT _GetFolder2(); HRESULT _GetLink(); HRESULT _GetLinkA(); HRESULT _GetLinkQI(REFIID riid, void **ppv); HRESULT _PreBindCtxHelper(IBindCtx **ppbc); LONG _cRef; LPITEMIDLIST _pidlRoot; LPITEMIDLIST _pidlTarget; LPITEMIDLIST _pidlTargetFldrFromInit; IShellFolder* _psfTarget; IShellFolder2* _psf2Target; IShellLinkW* _pslTarget; IShellLinkA* _pslTargetA; LPTSTR _pszLastSave; BOOL _fHaveResolved; DWORD _dwAttributesTarget; TCHAR _szFolderPath[MAX_PATH]; }; //constructor/destructor and related functions CFolderShortcut::CFolderShortcut() : _cRef(1), _dwAttributesTarget(FILE_ATTRIBUTE_DIRECTORY) { ASSERT(_pidlRoot == NULL); ASSERT(_pidlTarget == NULL); ASSERT(_psfTarget == NULL); ASSERT(_psf2Target == NULL); ASSERT(_szFolderPath[0] == 0); ASSERT(_pidlTargetFldrFromInit == NULL); DllAddRef(); } CFolderShortcut::~CFolderShortcut() { _ClearState(); DllRelease(); } void CFolderShortcut::_ClearTargetFolder() { ATOMICRELEASE(_psfTarget); ATOMICRELEASE(_psf2Target); } void CFolderShortcut::_ClearState() { _fHaveResolved = FALSE; Pidl_Set(&_pidlRoot, NULL); Pidl_Set(&_pidlTarget, NULL); Pidl_Set(&_pidlTargetFldrFromInit, NULL); Str_SetPtr(&_pszLastSave, NULL); _ClearTargetFolder(); ATOMICRELEASE(_pslTarget); ATOMICRELEASE(_pslTargetA); } STDAPI CFolderShortcut_CreateInstance(IUnknown* pUnkOuter, REFIID riid, void **ppv) { HRESULT hr = E_OUTOFMEMORY; *ppv = NULL; // aggregation checking is handled in class factory CFolderShortcut* pfolder = new CFolderShortcut(); if (pfolder) { hr = pfolder->QueryInterface(riid, ppv); pfolder->Release(); } return hr; } // ensure that _pslTarget has been created and loaded HRESULT CFolderShortcut::_LoadShortcut() { HRESULT hr; if (_pslTarget) { hr = S_OK; } else if (_szFolderPath[0]) { TCHAR szPath[MAX_PATH]; // leave this shortcut visible so down level clients see it and can // navigate through it. if (PathCombine(szPath, _szFolderPath, TEXT("target.lnk"))) { hr = LoadFromFile(CLSID_ShellLink, szPath, IID_PPV_ARG(IShellLinkW, &_pslTarget)); if (SUCCEEDED(hr)) { LPITEMIDLIST pidlTarget; // Prevalidate to prevent recusion // If GetIDList fails, that's okay; I guess it doesn't point to us after all if (_pslTarget->GetIDList(&pidlTarget) == S_OK) { SHGetPathFromIDList(pidlTarget, szPath); // Does this point to itself? if (StrCmpI(szPath, _szFolderPath) == 0) { _pslTarget->Release(); _pslTarget = NULL; hr = E_FAIL; } ILFree(pidlTarget); } } } else { hr = E_FAIL; } } else { hr = E_FAIL; } return hr; } // ensure that _pidlTarget is inited (requres _pslTarget) HRESULT CFolderShortcut::_GetTargetIDList(BOOL bResolve) { HRESULT hr = _LoadShortcut(); if (SUCCEEDED(hr)) { if (_pidlTarget) { hr = S_OK; } else { if (bResolve) _pslTarget->Resolve(NULL, SLR_UPDATE | SLR_NO_UI); hr = _pslTarget->GetIDList(&_pidlTarget); if (hr == S_FALSE) hr = E_FAIL; // convert empty to failure if (SUCCEEDED(hr)) { // make sure we dont have another shortcut here IShellLink *psl; if (SUCCEEDED(SHBindToObject(NULL, IID_IShellLink, _pidlTarget, (void**)&psl))) { ILFree(_pidlTarget); hr = psl->GetIDList(&_pidlTarget); if (SUCCEEDED(hr)) { hr = _pslTarget->SetIDList(_pidlTarget); } psl->Release(); } } if (FAILED(hr) && _pidlTarget) { ILFree(_pidlTarget); _pidlTarget = NULL; } } } return hr; } // create _psfTarget (requires _pidlTarget) HRESULT CFolderShortcut::_BindFolder(BOOL bResolve) { ASSERT(_psfTarget == NULL); HRESULT hr = _GetTargetIDList(bResolve); if (SUCCEEDED(hr)) { IBindCtx *pbc = NULL; // in/out param below hr = _PreBindCtxHelper(&pbc); // avoid loops in the name space if (SUCCEEDED(hr)) { if (SUCCEEDED(hr)) { IShellFolder *psfDesktop; hr = SHGetDesktopFolder(&psfDesktop); if (SUCCEEDED(hr)) { // Are we trying to bind to the desktop folder? if (ILIsEmpty(_pidlTarget)) { // Yes; Clone the desktop shell folder. _psfTarget = psfDesktop; _psfTarget->AddRef(); hr = S_OK; } else { // No. Bind to it. hr = psfDesktop->BindToObject(_pidlTarget, pbc, IID_PPV_ARG(IShellFolder, &_psfTarget)); } if (SUCCEEDED(hr)) { // optionally re-target the folder (if he is a file system folder) // to separate the location in the name space (_pidlRoot) // and the folder being viewed (pfsfi.szFolderPath). IPersistFolder3 *ppf; if (SUCCEEDED(_psfTarget->QueryInterface(IID_PPV_ARG(IPersistFolder3, &ppf)))) { PERSIST_FOLDER_TARGET_INFO pfti = { 0 }; pfti.pidlTargetFolder = _pidlTarget; pfti.dwAttributes = _dwAttributesTarget; pfti.csidl = -1; hr = ppf->InitializeEx(pbc, _pidlRoot, &pfti); ppf->Release(); } } psfDesktop->Release(); } } pbc->Release(); } } return hr; } // ensure that _psfTarget is inited HRESULT CFolderShortcut::_GetFolder(BOOL fForceResolve) { HRESULT hr; if (fForceResolve) { if (_fHaveResolved) { hr = _psfTarget ? S_OK : E_FAIL; } else { _fHaveResolved = TRUE; // don't do this again _ClearTargetFolder(); Pidl_Set(&_pidlTarget, NULL); hr = _BindFolder(fForceResolve); } } else if (_psfTarget) { hr = S_OK; } else { hr = _BindFolder(fForceResolve); } return hr; } // ensure that _psf2Target is inited HRESULT CFolderShortcut::_GetFolder2() { if (_psf2Target) return S_OK; HRESULT hr = _GetFolder(FALSE); if (SUCCEEDED(hr)) hr = _psfTarget->QueryInterface(IID_PPV_ARG(IShellFolder2, &_psf2Target)); return hr; } STDMETHODIMP CFolderShortcut::QueryInterface(REFIID riid, void **ppvObj) { static const QITAB qit[] = { QITABENTMULTI(CFolderShortcut, IShellFolder, IShellFolder2), QITABENT(CFolderShortcut, IShellFolder2), QITABENTMULTI(CFolderShortcut, IPersist, IPersistFolder3), QITABENTMULTI(CFolderShortcut, IPersistFolder, IPersistFolder3), QITABENTMULTI(CFolderShortcut, IPersistFolder2, IPersistFolder3), QITABENT(CFolderShortcut, IPersistFolder3), QITABENT(CFolderShortcut, IPersistStreamInit), QITABENTMULTI(CFolderShortcut, IPersistStream, IPersistStreamInit), QITABENT(CFolderShortcut, IShellLinkA), QITABENT(CFolderShortcut, IShellLinkW), QITABENT(CFolderShortcut, IPersistFile), QITABENT(CFolderShortcut, IFolderShortcutConvert), QITABENT(CFolderShortcut, IExtractIcon), QITABENT(CFolderShortcut, IQueryInfo), QITABENT(CFolderShortcut, IPersistPropertyBag), QITABENT(CFolderShortcut, IBrowserFrameOptions), { 0 }, }; return QISearch(this, qit, riid, ppvObj); } STDMETHODIMP_(ULONG) CFolderShortcut::AddRef() { return InterlockedIncrement(&_cRef); } STDMETHODIMP_(ULONG) CFolderShortcut::Release() { ASSERT( 0 != _cRef ); ULONG cRef = InterlockedDecrement(&_cRef); if ( 0 == cRef ) { delete this; } return cRef; } // either create or init the passed bind ctx with the params to avoid loops in the name space HRESULT CFolderShortcut::_PreBindCtxHelper(IBindCtx **ppbc) { HRESULT hr; if (*ppbc) { (*ppbc)->AddRef(); hr = S_OK; } else { hr = BindCtx_CreateWithMode(STGM_READ | STGM_SHARE_DENY_WRITE, ppbc); } if (SUCCEEDED(hr)) (*ppbc)->RegisterObjectParam(STR_SKIP_BINDING_CLSID, SAFECAST(this, IShellFolder2 *)); return hr; } // IShellFolder methods HRESULT CFolderShortcut::ParseDisplayName(HWND hwnd, LPBC pbc, LPOLESTR pwszDisplayName, ULONG * pchEaten, LPITEMIDLIST * ppidl, ULONG *pdwAttributes) { if (!ppidl) return E_INVALIDARG; *ppidl = NULL; if (!pwszDisplayName) return E_INVALIDARG; HRESULT hr = _GetFolder(FALSE); if (SUCCEEDED(hr)) { hr = _PreBindCtxHelper(&pbc); if (SUCCEEDED(hr)) { hr = _psfTarget->ParseDisplayName(hwnd, pbc, pwszDisplayName, pchEaten, ppidl, pdwAttributes); pbc->Release(); } } return hr; } HRESULT CFolderShortcut::EnumObjects(HWND hwnd, DWORD grfFlags, IEnumIDList **ppenumIDList) { HRESULT hr = _GetFolder(TRUE); if (SUCCEEDED(hr)) hr = _psfTarget->EnumObjects(hwnd, grfFlags, ppenumIDList); if (SUCCEEDED(hr)) SHChangeNotifyRegisterAlias(_pidlTarget, _pidlRoot); return hr; } HRESULT CFolderShortcut::BindToObject(LPCITEMIDLIST pidl, LPBC pbc, REFIID riid, void **ppv) { HRESULT hr = _GetFolder(TRUE); if (SUCCEEDED(hr)) { hr = _PreBindCtxHelper(&pbc); if (SUCCEEDED(hr)) { hr = _psfTarget->BindToObject(pidl, pbc, riid, ppv); pbc->Release(); if (SUCCEEDED(hr)) SHChangeNotifyRegisterAlias(_pidlTarget, _pidlRoot); } } return hr; } HRESULT CFolderShortcut::BindToStorage(LPCITEMIDLIST pidl, LPBC pbc, REFIID riid, void **ppv) { return BindToObject(pidl, pbc, riid, ppv); } HRESULT CFolderShortcut::CompareIDs(LPARAM lParam, LPCITEMIDLIST pidl1, LPCITEMIDLIST pidl2) { HRESULT hr = _GetFolder(FALSE); if (SUCCEEDED(hr)) hr = _psfTarget->CompareIDs(lParam, pidl1, pidl2); return hr; } HRESULT CFolderShortcut::CreateViewObject(HWND hwnd, REFIID riid, void **ppv) { HRESULT hr = _GetFolder(TRUE); if ( SUCCEEDED(hr) ) hr = _psfTarget->CreateViewObject(hwnd, riid, ppv); if ( SUCCEEDED(hr) && (IsEqualIID(riid, IID_IShellView) || IsEqualIID(riid, IID_IShellView2)) ) SHChangeNotifyRegisterAlias(_pidlTarget, _pidlRoot); return hr; } HRESULT CFolderShortcut::GetAttributesOf(UINT cidl, LPCITEMIDLIST *apidl, ULONG *rgfInOut) { if (IsSelf (cidl, apidl)) { // since our folder is marked "CallForAttributes" we get to report // our attributes at runtime instead of the normal way via the registry if (SHGetAppCompatFlags (ACF_STRIPFOLDERBIT) & ACF_STRIPFOLDERBIT) { *rgfInOut = SFGAO_LINK | SFGAO_CAPABILITYMASK | SFGAO_FILESYSTEM; } else { *rgfInOut = SFGAO_FOLDER | SFGAO_FILESYSTEM | SFGAO_STORAGE | SFGAO_LINK | SFGAO_DROPTARGET | SFGAO_CANRENAME | SFGAO_CANDELETE | SFGAO_CANLINK | SFGAO_CANCOPY | SFGAO_CANMOVE | SFGAO_HASSUBFOLDER; } return S_OK; } HRESULT hr = _GetFolder(FALSE); if (SUCCEEDED(hr)) hr = _psfTarget->GetAttributesOf(cidl, apidl, rgfInOut); return hr; } HRESULT CFolderShortcut::GetUIObjectOf(HWND hwnd, UINT cidl, LPCITEMIDLIST *apidl, REFIID riid, UINT *prgfInOut, void **ppv) { HRESULT hr = _GetFolder(FALSE); if (SUCCEEDED(hr)) hr = _psfTarget->GetUIObjectOf(hwnd, cidl, apidl, riid, prgfInOut, ppv); return hr; } HRESULT CFolderShortcut::GetDisplayNameOf(LPCITEMIDLIST pidl, DWORD uFlags, STRRET *pName) { HRESULT hr = _GetFolder(FALSE); if (SUCCEEDED(hr)) hr = _psfTarget->GetDisplayNameOf(pidl, uFlags, pName); return hr; } HRESULT CFolderShortcut::SetNameOf(HWND hwnd, LPCITEMIDLIST pidl, LPCOLESTR pszName, DWORD uFlags, LPITEMIDLIST *ppidlOut) { HRESULT hr = _GetFolder(FALSE); if (SUCCEEDED(hr)) hr = _psfTarget->SetNameOf(hwnd, pidl, pszName, uFlags, ppidlOut); return hr; } STDMETHODIMP CFolderShortcut::GetDefaultSearchGUID(LPGUID lpGuid) { HRESULT hr = _GetFolder2(); if (SUCCEEDED(hr)) hr = _psf2Target->GetDefaultSearchGUID(lpGuid); return hr; } STDMETHODIMP CFolderShortcut::EnumSearches(LPENUMEXTRASEARCH *ppenum) { HRESULT hr = _GetFolder2(); if (SUCCEEDED(hr)) hr = _psf2Target->EnumSearches(ppenum); return hr; } STDMETHODIMP CFolderShortcut::GetDefaultColumn(DWORD dwRes, ULONG *pSort, ULONG *pDisplay) { HRESULT hr = _GetFolder2(); if (SUCCEEDED(hr)) hr = _psf2Target->GetDefaultColumn(dwRes, pSort, pDisplay); return hr; } STDMETHODIMP CFolderShortcut::GetDefaultColumnState(UINT iColumn, DWORD *pbState) { HRESULT hr = _GetFolder2(); if (SUCCEEDED(hr)) hr = _psf2Target->GetDefaultColumnState(iColumn, pbState); return hr; } STDMETHODIMP CFolderShortcut::GetDetailsEx(LPCITEMIDLIST pidl, const SHCOLUMNID *pscid, VARIANT *pv) { HRESULT hr = _GetFolder2(); if (SUCCEEDED(hr)) hr = _psf2Target->GetDetailsEx(pidl, pscid, pv); return hr; } STDMETHODIMP CFolderShortcut::GetDetailsOf(LPCITEMIDLIST pidl, UINT iColumn, LPSHELLDETAILS pDetail) { HRESULT hr = _GetFolder2(); if (SUCCEEDED(hr)) hr = _psf2Target->GetDetailsOf(pidl, iColumn, pDetail); return hr; } STDMETHODIMP CFolderShortcut::MapColumnToSCID(UINT iColumn, SHCOLUMNID *pscid) { HRESULT hr = _GetFolder2(); if (SUCCEEDED(hr)) hr = _psf2Target->MapColumnToSCID(iColumn, pscid); return hr; } // IPersist HRESULT CFolderShortcut::GetClassID(CLSID *pCLSID) { *pCLSID = CLSID_FolderShortcut; return S_OK; } // IPersistFolder HRESULT CFolderShortcut::Initialize(LPCITEMIDLIST pidl) { HRESULT hr; // is the link loaded (could have been loaded through IPersistStream::Load)? if (_pslTarget) { // Yes, it's loaded so re-initialize // note, _szFolderPath will be empty since we are not loaded from the file system hr = Pidl_Set(&_pidlRoot, pidl) ? S_OK : E_OUTOFMEMORY; } else { // we explictly require initialization through // IPersistFolder3::InitializeEx, if we don't do these we can // not defent against loops in the name space hr = E_FAIL; } return hr; } // IPersistFolder2 STDMETHODIMP CFolderShortcut::GetCurFolder(LPITEMIDLIST *ppidl) { return GetCurFolderImpl(this->_pidlRoot, ppidl); } // IPersistFolder3 STDMETHODIMP CFolderShortcut::InitializeEx(IBindCtx *pbc, LPCITEMIDLIST pidlRoot, const PERSIST_FOLDER_TARGET_INFO *pfti) { HRESULT hr = E_INVALIDARG; // assume failure if ( NULL == pbc || (pbc && !SHSkipJunction(pbc, &CLSID_FolderShortcut)) ) { _ClearState(); if (pidlRoot) hr = SHILClone(pidlRoot, &_pidlRoot); if (pfti && pfti->pidlTargetFolder) { if ( SUCCEEDED(hr) ) hr = SHILClone(pfti->pidlTargetFolder, &_pidlTargetFldrFromInit); if ( SUCCEEDED(hr) && !_szFolderPath[0] ) hr = SHGetPathFromIDList(pfti->pidlTargetFolder, _szFolderPath) ? S_OK : E_FAIL; } else { if ( SUCCEEDED(hr) && !_szFolderPath[0] ) hr = SHGetPathFromIDList(_pidlRoot, _szFolderPath) ? S_OK : E_FAIL; } if ( SUCCEEDED(hr) ) hr = _LoadShortcut(); } return hr; } HRESULT CFolderShortcut::GetFolderTargetInfo(PERSIST_FOLDER_TARGET_INFO *pfti) { HRESULT hr = S_OK; ZeroMemory(pfti, sizeof(*pfti)); if ( _pidlTargetFldrFromInit ) hr = SHILClone(_pidlTargetFldrFromInit, &pfti->pidlTargetFolder); pfti->dwAttributes = -1; pfti->csidl = -1; return hr; } HRESULT CFolderShortcut::_GetLink() { HRESULT hr = _LoadShortcut(); if (FAILED(hr)) { // get an empty one in case we are going to be asked to save hr = SHCoCreateInstance(NULL, &CLSID_ShellLink, NULL, IID_PPV_ARG(IShellLinkW, &_pslTarget)); } return hr; } HRESULT CFolderShortcut::_GetLinkQI(REFIID riid, void **ppv) { HRESULT hr = _GetLink(); if (SUCCEEDED(hr)) hr = _pslTarget->QueryInterface(riid, ppv); return hr; } HRESULT CFolderShortcut::_GetLinkA() { return _pslTargetA ? S_OK : _GetLinkQI(IID_PPV_ARG(IShellLinkA, &_pslTargetA)); } // IPersistFile STDMETHODIMP CFolderShortcut::Load(LPCOLESTR pszFileName, DWORD dwMode) { _ClearState(); SHUnicodeToTChar(pszFileName, _szFolderPath, ARRAYSIZE(_szFolderPath)); return _LoadShortcut(); } BOOL _IsFolder(LPCITEMIDLIST pidl) { ULONG rgInfo = SFGAO_FOLDER; HRESULT hr = SHGetNameAndFlags(pidl, SHGDN_NORMAL, NULL, 0, &rgInfo); return SUCCEEDED(hr) && (rgInfo & SFGAO_FOLDER); } void PathStripTrailingDots(LPTSTR szPath) { if (szPath[0] == TEXT('\0')) return; LPTSTR psz = &szPath[lstrlen(szPath) - 1]; while ((*psz == TEXT('.')) && (psz >= szPath)) { *psz-- = TEXT('\0'); } } STDMETHODIMP CFolderShortcut::Save(LPCOLESTR pszFileName, BOOL fRemember) { HRESULT hr = _GetTargetIDList(FALSE); // We need to make sure the folder shortcut can be saved keeping in mind the MAX_PATH limitation // cchFSReserved is the number of characters to reserve for the largest file that will be created // in the foldershortcut directory, in this case, it is the ARRAYSIZE of "\\desktop.ini" static const int cchFSReserved = ARRAYSIZE(TEXT("\\desktop.ini")); LPITEMIDLIST pidlInternet; // Don't create a folder shortcut to the internet folder. if (SUCCEEDED(hr) && SUCCEEDED(SHGetSpecialFolderLocation(NULL, CSIDL_INTERNET, &pidlInternet))) { if (ILIsEqual(_pidlTarget, pidlInternet)) { hr = E_INVALIDARG; } ILFree(pidlInternet); } if (SUCCEEDED(hr) && _IsFolder(_pidlTarget)) { // we know the target is a folder, create a folder shortcut. BOOL fCreatedDir; TCHAR szName[MAX_PATH]; SHUnicodeToTChar(pszFileName, szName, ARRAYSIZE(szName)); // Remove any exisiting extension. // We dont want "Shortcut To My Documents.lnk.{GUID} if (PathFindCLSIDExtension(szName,NULL)) { PathRemoveExtension(szName); } PathStripTrailingDots(szName); // Can't create a fldrshcut with too long a path if ((MAX_PATH - cchFSReserved) < lstrlen(szName)) { hr = CO_E_PATHTOOLONG; } if (SUCCEEDED(hr)) { if (PathIsDirectory(szName)) fCreatedDir = FALSE; else fCreatedDir = SHCreateDirectory(NULL, szName) == 0; CreateFolderDesktopIni(szName); // Now initialize the child link IPersistFile *ppf; hr = _pslTarget->QueryInterface(IID_PPV_ARG(IPersistFile, &ppf)); if (SUCCEEDED(hr)) { WCHAR wszName[MAX_PATH]; SHTCharToUnicode(szName, wszName, ARRAYSIZE(wszName)); if (PathAppendW(wszName, L"target.lnk")) { hr = ppf->Save(wszName, fRemember); if (SUCCEEDED(hr)) { if (fRemember) Str_SetPtr(&_pszLastSave, szName); } } else { hr = HRESULT_FROM_WIN32(ERROR_FILENAME_EXCED_RANGE); } ppf->Release(); } if (FAILED(hr) && fCreatedDir) { RemoveDirectory(szName); // cleanup after ourselves. } } } else { // ensure that if we save as a file we do so with the right extension WCHAR szFile[MAX_PATH]; hr = StringCchCopy(szFile, ARRAYSIZE(szFile), pszFileName); if (SUCCEEDED(hr)) { PathRenameExtension(szFile, L".lnk"); // the target is not a folder, create a normal shortcut in this case IPersistFile *ppf; hr = _pslTarget->QueryInterface(IID_PPV_ARG(IPersistFile, &ppf)); if (SUCCEEDED(hr)) { hr = ppf->Save(szFile, fRemember); ppf->Release(); } } } return hr; } STDMETHODIMP CFolderShortcut::GetCurFile(LPOLESTR *ppszFileName) { HRESULT hr = E_FAIL; if (_pszLastSave) hr = SHStrDup(_pszLastSave, ppszFileName); else if (_pslTarget) { IPersistFile *ppf; hr = _pslTarget->QueryInterface(IID_PPV_ARG(IPersistFile, &ppf)); if (SUCCEEDED(hr)) { hr = ppf->GetCurFile(ppszFileName); ppf->Release(); } } return hr; } // IShellLinkW STDMETHODIMP CFolderShortcut::GetPath(LPWSTR pszFile, int cchMaxPath, WIN32_FIND_DATAW *pfd, DWORD flags) { HRESULT hr = _GetLink(); if (SUCCEEDED(hr)) hr = _pslTarget->GetPath(pszFile, cchMaxPath, pfd, flags); return hr; } STDMETHODIMP CFolderShortcut::SetPath(LPCWSTR pwszFile) { HRESULT hr = _GetLink(); if (SUCCEEDED(hr) && PathIsDirectoryW(pwszFile)) { hr = _pslTarget->SetPath(pwszFile); Pidl_Set(&_pidlTarget, NULL); } return hr; } STDMETHODIMP CFolderShortcut::GetIDList(LPITEMIDLIST *ppidl) { HRESULT hr = _GetLink(); if (SUCCEEDED(hr)) hr = _pslTarget->GetIDList(ppidl); else *ppidl = NULL; return hr; } STDMETHODIMP CFolderShortcut::SetIDList(LPCITEMIDLIST pidl) { HRESULT hr = _GetLink(); if (SUCCEEDED(hr)) { hr = _pslTarget->SetIDList(pidl); Pidl_Set(&_pidlTarget, NULL); } return hr; } STDMETHODIMP CFolderShortcut::GetDescription(LPWSTR wszName, int cchMaxName) { HRESULT hr = _GetLink(); if (SUCCEEDED(hr)) hr = _pslTarget->GetDescription(wszName, cchMaxName); return hr; } STDMETHODIMP CFolderShortcut::SetDescription(LPCWSTR wszName) { HRESULT hr = _GetLink(); if (SUCCEEDED(hr)) hr = _pslTarget->SetDescription(wszName); return hr; } STDMETHODIMP CFolderShortcut::GetWorkingDirectory(LPWSTR wszDir, int cchMaxPath) { HRESULT hr = _GetLink(); if (SUCCEEDED(hr)) hr = _pslTarget->GetWorkingDirectory(wszDir, cchMaxPath); return hr; } STDMETHODIMP CFolderShortcut::SetWorkingDirectory(LPCWSTR wszDir) { HRESULT hr = _GetLink(); if (SUCCEEDED(hr)) hr = _pslTarget->SetWorkingDirectory(wszDir); return hr; } STDMETHODIMP CFolderShortcut::GetArguments(LPWSTR wszArgs, int cchMaxPath) { HRESULT hr = _GetLink(); if (SUCCEEDED(hr)) hr = _pslTarget->GetArguments(wszArgs, cchMaxPath);//this is probably not at all useful. return hr; } STDMETHODIMP CFolderShortcut::SetArguments(LPCWSTR wszArgs) { HRESULT hr = _GetLink(); if (SUCCEEDED(hr)) hr = _pslTarget->SetArguments(wszArgs);//this is probably not at all useful. return hr; } STDMETHODIMP CFolderShortcut::GetHotkey(WORD *pwHotkey) { HRESULT hr = _GetLink(); if (SUCCEEDED(hr)) hr = _pslTarget->GetHotkey(pwHotkey); return hr; } STDMETHODIMP CFolderShortcut::SetHotkey(WORD wHotkey) { HRESULT hr = _GetLink(); if (SUCCEEDED(hr)) hr = _pslTarget->SetHotkey(wHotkey); return hr; } STDMETHODIMP CFolderShortcut::GetShowCmd(int *piShowCmd) { HRESULT hr = _GetLink(); if (SUCCEEDED(hr)) hr = _pslTarget->GetShowCmd(piShowCmd); return hr; } STDMETHODIMP CFolderShortcut::SetShowCmd(int iShowCmd) { HRESULT hr = _GetLink(); if (SUCCEEDED(hr)) hr = _pslTarget->SetShowCmd(iShowCmd); return hr; } STDMETHODIMP CFolderShortcut::GetIconLocation(LPWSTR wszIconPath, int cchIconPath, int *piIcon) { HRESULT hr = _GetLink(); if (SUCCEEDED(hr)) hr = _pslTarget->GetIconLocation(wszIconPath, cchIconPath, piIcon); return hr; } STDMETHODIMP CFolderShortcut::SetIconLocation(LPCWSTR wszIconPath, int iIcon) { HRESULT hr = _GetLink(); if (SUCCEEDED(hr)) hr = _pslTarget->SetIconLocation(wszIconPath, iIcon); return hr; } STDMETHODIMP CFolderShortcut::Resolve(HWND hwnd, DWORD fFlags) { HRESULT hr = _GetLink(); if (SUCCEEDED(hr)) hr = _pslTarget->Resolve(hwnd, fFlags); return hr; } STDMETHODIMP CFolderShortcut::SetRelativePath(LPCWSTR wszPathRel, DWORD dwReserved) { HRESULT hr = _GetLink(); if (SUCCEEDED(hr)) hr = _pslTarget->SetRelativePath(wszPathRel, dwReserved); return hr; } // IShellLinkA STDMETHODIMP CFolderShortcut::GetPath(LPSTR pszFile, int cchMaxPath, WIN32_FIND_DATAA *pfd, DWORD flags) { HRESULT hr = _GetLinkA(); if (SUCCEEDED(hr)) hr = _pslTargetA->GetPath(pszFile, cchMaxPath, pfd, flags); return hr; } STDMETHODIMP CFolderShortcut::GetDescription(LPSTR pszName, int cchMaxName) { HRESULT hr = _GetLinkA(); if (SUCCEEDED(hr)) hr = _pslTargetA->GetDescription(pszName, cchMaxName); return hr; } STDMETHODIMP CFolderShortcut::GetWorkingDirectory(LPSTR pszDir, int cchMaxPath) { HRESULT hr = _GetLinkA(); if (SUCCEEDED(hr)) hr = _pslTargetA->GetWorkingDirectory(pszDir, cchMaxPath); return hr; } STDMETHODIMP CFolderShortcut::GetArguments(LPSTR pszArgs, int cchMaxPath) { HRESULT hr = _GetLinkA(); if (SUCCEEDED(hr)) hr = _pslTargetA->GetArguments(pszArgs, cchMaxPath);//this is probably not at all useful. return hr; } STDMETHODIMP CFolderShortcut::GetIconLocation(LPSTR pszIconPath, int cchIconPath, int *piIcon) { HRESULT hr = _GetLinkA(); if (SUCCEEDED(hr)) hr = _pslTargetA->GetIconLocation(pszIconPath, cchIconPath, piIcon); return hr; } STDMETHODIMP CFolderShortcut::SetPath(LPCSTR pszFile) { HRESULT hr = _GetLinkA(); if (SUCCEEDED(hr) && PathIsDirectoryA(pszFile)) { hr = _pslTargetA->SetPath(pszFile); Pidl_Set(&_pidlTarget, NULL); } return hr; } STDMETHODIMP CFolderShortcut::SetDescription(LPCSTR pszName) { HRESULT hr = _GetLinkA(); if (SUCCEEDED(hr)) hr = _pslTargetA->SetDescription(pszName); return hr; } STDMETHODIMP CFolderShortcut::SetWorkingDirectory(LPCSTR pszDir) { HRESULT hr = _GetLinkA(); if (SUCCEEDED(hr)) hr = _pslTargetA->SetWorkingDirectory(pszDir); return hr; } STDMETHODIMP CFolderShortcut::SetArguments(LPCSTR pszArgs) { HRESULT hr = _GetLinkA(); if (SUCCEEDED(hr)) hr = _pslTargetA->SetArguments(pszArgs); return hr; } STDMETHODIMP CFolderShortcut::SetIconLocation(LPCSTR pszIconPath, int iIcon) { HRESULT hr = _GetLinkA(); if (SUCCEEDED(hr)) hr = _pslTargetA->SetIconLocation(pszIconPath, iIcon); return hr; } STDMETHODIMP CFolderShortcut::SetRelativePath(LPCSTR pszPathRel, DWORD dwReserved) { HRESULT hr = _GetLinkA(); if (SUCCEEDED(hr)) hr = _pslTargetA->SetRelativePath(pszPathRel, dwReserved); return hr; } STDMETHODIMP CFolderShortcut::GetIconLocation(UINT uFlags, LPTSTR pszIconFile, UINT ucchMax, PINT pniIcon, PUINT puFlags) { IExtractIcon *pxi; HRESULT hr = _GetLinkQI(IID_PPV_ARG(IExtractIcon, &pxi)); if (SUCCEEDED(hr)) { hr = pxi->GetIconLocation(uFlags, pszIconFile, ucchMax, pniIcon, puFlags); pxi->Release(); } return hr; } STDMETHODIMP CFolderShortcut::Extract(LPCTSTR pcszFile, UINT uIconIndex, HICON *phiconLarge, HICON *phiconSmall, UINT ucIconSize) { IExtractIcon *pxi; HRESULT hr = _GetLinkQI(IID_PPV_ARG(IExtractIcon, &pxi)); if (SUCCEEDED(hr)) { hr = pxi->Extract(pcszFile, uIconIndex, phiconLarge, phiconSmall, ucIconSize); pxi->Release(); } return hr; } HRESULT CFolderShortcut::GetInfoTip(DWORD dwFlags, WCHAR** ppwszText) { IQueryInfo *pqi; HRESULT hr = _GetLinkQI(IID_PPV_ARG(IQueryInfo, &pqi)); if (SUCCEEDED(hr)) { hr = pqi->GetInfoTip(dwFlags | QITIPF_LINKUSETARGET, ppwszText); pqi->Release(); } return hr; } HRESULT CFolderShortcut::GetInfoFlags(DWORD *pdwFlags) { IQueryInfo *pqi; HRESULT hr = _GetLinkQI(IID_PPV_ARG(IQueryInfo, &pqi)); if (SUCCEEDED(hr)) { hr = pqi->GetInfoFlags(pdwFlags); pqi->Release(); } return hr; } // IBrowserFrameOptions HRESULT CFolderShortcut::GetFrameOptions(IN BROWSERFRAMEOPTIONS dwMask, IN BROWSERFRAMEOPTIONS * pdwOptions) { HRESULT hr = _GetFolder(FALSE); *pdwOptions = BFO_NONE; if (SUCCEEDED(hr)) { IBrowserFrameOptions *pbfo; hr = _psfTarget->QueryInterface(IID_PPV_ARG(IBrowserFrameOptions, &pbfo)); if (SUCCEEDED(hr)) { hr = pbfo->GetFrameOptions(dwMask, pdwOptions); pbfo->Release(); } } return hr; } // IPersistStream STDMETHODIMP CFolderShortcut::Load(IStream *pStm) { _ClearState(); IPersistStream *pps; HRESULT hr = SHCoCreateInstance(NULL, &CLSID_ShellLink, NULL, IID_PPV_ARG(IPersistStream, &pps)); if (SUCCEEDED(hr)) { hr = pps->Load(pStm); if (SUCCEEDED(hr)) pps->QueryInterface(IID_PPV_ARG(IShellLinkW, &_pslTarget)); // keep this guy pps->Release(); } return hr; } // IPersistStream STDMETHODIMP CFolderShortcut::Save(IStream *pStm, int fClearDirty) { return E_NOTIMPL; } // IPersistStream STDMETHODIMP CFolderShortcut::GetSizeMax(ULARGE_INTEGER * pcbSize) { return E_NOTIMPL; } // // IFolderShortcut::ConvertToLink. // // destructively convert a Folder Shortcut into a Shell Link. // // pszFolderShortcut is the path to an existing folder shortcut // c:\Folder Shortcut.{guid} - deleted // c:\Folder Shortcut.lnk - created // STDMETHODIMP CFolderShortcut::ConvertToLink(LPCOLESTR pszFolderShortcut, DWORD fFlags) { HRESULT hr = E_FAIL; TCHAR szName[MAX_PATH]; SHUnicodeToTChar(pszFolderShortcut, szName, ARRAYSIZE(szName)); if (PathIsDirectory(szName) && IsFolderShortcut(szName)) { TCHAR szLinkName[MAX_PATH]; // c:\Folder Shortcut\target.lnk hr = StringCchCopy(szLinkName, ARRAYSIZE(szLinkName), szName); if (SUCCEEDED(hr)) { hr = E_FAIL; if (PathAppend(szLinkName, TEXT("target.lnk"))) { PathRenameExtension(szName, TEXT(".lnk")); // FS.{guid} -> FS.lnk if (CopyFile(szLinkName, szName, FALSE)) { PathRemoveExtension(szName); if (DeleteFile(szLinkName) && PathAppend(szName, TEXT("desktop.ini")) && DeleteFile(szName) && PathRemoveFileSpec(szName) && RemoveDirectory(szName)) { hr = S_OK; } } } } } return hr; } // // IFolderShortcut::ConvertToFolderShortcut. // // destructively convert a Shell Link (.lnk) -> Folder Shortcut (Folder.{guid}). // pszPathLNK is the path to an existing .lnk file // c:\Folder Shortcut.lnk - deleted // c:\Folder Shortcut.{guid} - created // STDMETHODIMP CFolderShortcut::ConvertToFolderShortcut(LPCOLESTR pszPathLNK, DWORD fFlags) { //must bind to the link, resolve it, and make sure it points to a folder. IShellLink *psl; HRESULT hr = SHCoCreateInstance(NULL, &CLSID_ShellLink, NULL, IID_PPV_ARG(IShellLink, &psl)); if (SUCCEEDED(hr)) { IPersistFile *ppf; hr = psl->QueryInterface(IID_PPV_ARG(IPersistFile, &ppf)); if (SUCCEEDED(hr)) { hr = ppf->Load(pszPathLNK, STGM_READ); if (SUCCEEDED(hr)) { hr = psl->Resolve(NULL, SLR_NO_UI); // make sure the link is real if (SUCCEEDED(hr)) { LPITEMIDLIST pidl; hr = psl->GetIDList(&pidl); if (hr == S_OK) { // this should maybe work on the pidl so that // it doesn't have to worry about files. if (_IsFolder(pidl)) { TCHAR szPath[MAX_PATH], szName[MAX_PATH]; SHUnicodeToTChar(pszPathLNK, szName, ARRAYSIZE(szName)); hr = StringCchCopy(szPath, ARRAYSIZE(szPath), szName); if (SUCCEEDED(hr)) { hr = E_FAIL; PathRemoveExtension(szName); BOOL fCreatedDir = SHCreateDirectory(NULL, szName) == 0; if (CreateFolderDesktopIni(szName) && PathAppend(szName, TEXT("target.lnk"))) { //copy the link file into the new directory. if (CopyFile(szPath, szName, FALSE)) { if (DeleteFile(szPath)) //if all goes well, delete the old. hr = S_OK; } else { PathRemoveFileSpec(szName); if (fCreatedDir) RemoveDirectory(szName); } } } } else hr = E_FAIL; ILFree(pidl); } else hr = E_FAIL; } } ppf->Release(); } psl->Release(); } return hr; } // IPersistPropertyBag STDMETHODIMP CFolderShortcut::Save(IPropertyBag* pPropBag, BOOL fClearDirty, BOOL fSaveAllProperties) { return E_NOTIMPL; } // IPersistPropertyBag STDMETHODIMP CFolderShortcut::Load(IPropertyBag* pPropBag, IErrorLog* pErrorLog) { _ClearState(); IPersistPropertyBag* pppb; HRESULT hr = SHCoCreateInstance(NULL, &CLSID_ShellLink, NULL, IID_PPV_ARG(IPersistPropertyBag, &pppb)); if (SUCCEEDED(hr)) { hr = pppb->Load(pPropBag, pErrorLog); if (SUCCEEDED(hr)) { hr = pppb->QueryInterface(IID_PPV_ARG(IShellLinkW, &_pslTarget)); DWORD dwFlags; if (SUCCEEDED(SHPropertyBag_ReadDWORD(pPropBag, L"Attributes", &dwFlags))) _dwAttributesTarget = dwFlags; } pppb->Release(); } return hr; } STDMETHODIMP CFolderShortcut::InitNew(void) { _ClearState(); return S_OK; }