WindowsXP-SP1/shell/shell32/filefldr.cpp

7525 lines
232 KiB
C++

#include "shellprv.h"
#pragma hdrstop
#include "ole2dup.h"
#include "copy.h"
#include <regstr.h>
#include <comcat.h>
#include <intshcut.h>
#include "_security.h"
#include "ovrlaymn.h"
#include "filefldr.h"
#include "drives.h"
#include "netview.h"
#include <filetype.h>
#include "shitemid.h"
#include "infotip.h"
#include "recdocs.h"
#include <idhidden.h>
#include "datautil.h"
#include "deskfldr.h"
#include "prop.h" // COLUMN_INFO
#include <oledb.h> // IFilter stuff
#include <query.h>
#include <ntquery.h>
#include <filterr.h>
#include <ciintf.h>
#include "folder.h"
#include "ids.h"
#include "category.h"
#include "stgenum.h"
#include "clsobj.h"
#include "stgutil.h"
#include "sfstorage.h"
#include "mtpt.h"
#include "defcm.h"
STDAPI CFolderInfoTip_CreateInstance(IUnknown *punkOutter, LPCTSTR pszFolder, REFIID riid, void **ppv);
#define SHCF_IS_BROWSABLE (SHCF_IS_SHELLEXT | SHCF_IS_DOCOBJECT)
#define CSIDL_NORMAL ((UINT)-2) // has to not be -1
// File-scope pointer to a ShellIconOverlayManager
// Callers access this pointer through GetIconOverlayManager().
static IShellIconOverlayManager * g_psiom = NULL;
// #define FULL_DEBUG
TCHAR const c_szCLSIDSlash[] = TEXT("CLSID\\");
TCHAR const c_szShellOpenCmd[] = TEXT("shell\\open\\command");
TCHAR g_szFolderTypeName[32] = TEXT(""); // "Folder"
TCHAR g_szFileTypeName[32] = TEXT(""); // "File"
TCHAR g_szFileTemplate[32] = TEXT(""); // "ext File"
enum
{
FS_ICOL_NAME = 0,
FS_ICOL_SIZE,
FS_ICOL_TYPE,
FS_ICOL_WRITETIME,
FS_ICOL_CREATETIME,
FS_ICOL_ACCESSTIME,
FS_ICOL_ATTRIB,
FS_ICOL_CSC_STATUS,
};
const COLUMN_INFO c_fs_cols[] =
{
DEFINE_COL_STR_ENTRY(SCID_NAME, 30, IDS_NAME_COL),
DEFINE_COL_SIZE_ENTRY(SCID_SIZE, IDS_SIZE_COL),
DEFINE_COL_STR_ENTRY(SCID_TYPE, 20, IDS_TYPE_COL),
DEFINE_COL_DATE_ENTRY(SCID_WRITETIME, IDS_MODIFIED_COL),
// these are off by default (don't have SHCOLSTATE_ONBYDEFAULT) set
DEFINE_COL_ENTRY(SCID_CREATETIME, VT_DATE, LVCFMT_LEFT, 20, SHCOLSTATE_TYPE_DATE, IDS_EXCOL_CREATE),
DEFINE_COL_ENTRY(SCID_ACCESSTIME, VT_DATE, LVCFMT_LEFT, 20, SHCOLSTATE_TYPE_DATE | SHCOLSTATE_SECONDARYUI, IDS_EXCOL_ACCESSTIME),
DEFINE_COL_ENTRY(SCID_ATTRIBUTES, VT_LPWSTR, LVCFMT_LEFT, 10, SHCOLSTATE_TYPE_STR, IDS_ATTRIB_COL),
DEFINE_COL_STR_DLG_ENTRY(SCID_CSC_STATUS, 10, IDS_CSC_STATUS),
};
//
// List of file attribute bit values. The order (with respect
// to meaning) must match that of the characters in g_szAttributeChars[].
//
const DWORD g_adwAttributeBits[] =
{
FILE_ATTRIBUTE_READONLY,
FILE_ATTRIBUTE_HIDDEN,
FILE_ATTRIBUTE_SYSTEM,
FILE_ATTRIBUTE_ARCHIVE,
FILE_ATTRIBUTE_COMPRESSED,
FILE_ATTRIBUTE_ENCRYPTED,
FILE_ATTRIBUTE_OFFLINE
};
//
// Buffer for characters that represent attributes in Details View attributes
// column. Must provide room for 1 character for each bit a NUL. The current 5
// represent Read-only, Archive, Compressed, Hidden and System in that order.
// This can't be const because we overwrite it using LoadString.
//
TCHAR g_szAttributeChars[ARRAYSIZE(g_adwAttributeBits) + 1] = { 0 } ;
// order here is important, first one found will terminate the search
const int c_csidlSpecial[] = {
CSIDL_STARTMENU | TEST_SUBFOLDER,
CSIDL_COMMON_STARTMENU | TEST_SUBFOLDER,
CSIDL_RECENT,
CSIDL_WINDOWS,
CSIDL_SYSTEM,
CSIDL_PERSONAL,
CSIDL_FONTS
};
BOOL CFSFolder::_IsCSIDL(UINT csidl)
{
BOOL bRet = (_csidl == csidl);
if (!bRet)
{
TCHAR szPath[MAX_PATH];
_GetPath(szPath);
bRet = PathIsEqualOrSubFolder(MAKEINTRESOURCE(csidl), szPath);
if (bRet)
_csidl = csidl;
}
return bRet;
}
UINT CFSFolder::_GetCSIDL()
{
// Cache the special folder ID, if it is not cached yet.
if (_csidl == -1)
{
TCHAR szPath[MAX_PATH];
_GetPath(szPath);
// Always cache the real Csidl.
_csidl = GetSpecialFolderID(szPath, c_csidlSpecial, ARRAYSIZE(c_csidlSpecial));
if (_csidl == -1)
{
_csidl = CSIDL_NORMAL; // default
}
}
return _csidl;
}
STDAPI_(LPCIDFOLDER) CFSFolder::_IsValidID(LPCITEMIDLIST pidl)
{
if (pidl && pidl->mkid.cb && (((LPCIDFOLDER)pidl)->bFlags & SHID_GROUPMASK) == SHID_FS)
return (LPCIDFOLDER)pidl;
return NULL;
}
// folder.{guid} or file.{guid}
// system | readonly folder with desktop.ini and CLSID={guid} in the desktop.ini
// file.ext where ext corresponds to a shell extension (such as .cab/.zip)
// see _MarkAsJunction
inline BOOL CFSFolder::_IsJunction(LPCIDFOLDER pidf)
{
return pidf->bFlags & SHID_JUNCTION;
}
inline BYTE CFSFolder::_GetType(LPCIDFOLDER pidf)
{
return pidf->bFlags & SHID_FS_TYPEMASK;
}
// this tests for old simple pidls that use SHID_FS
// typically this only happens with persisted pidls in upgrade scenarios (shortcuts in the start menu)
inline BOOL CFSFolder::_IsSimpleID(LPCIDFOLDER pidf)
{
return _GetType(pidf) == SHID_FS;
}
inline LPIDFOLDER CFSFolder::_FindLastID(LPCIDFOLDER pidf)
{
return (LPIDFOLDER)ILFindLastID((LPITEMIDLIST)pidf);
}
inline LPIDFOLDER CFSFolder::_Next(LPCIDFOLDER pidf)
{
return (LPIDFOLDER)_ILNext((LPITEMIDLIST)pidf);
}
// special marking for "All Users" items on the desktop (this is a hack to support the desktop
// folder delegating to the approprate shell folder and is not generally useful)
BOOL CFSFolder::_IsCommonItem(LPCITEMIDLIST pidl)
{
if (pidl && pidl->mkid.cb && (((LPCIDFOLDER)pidl)->bFlags & (SHID_GROUPMASK | SHID_FS_COMMONITEM)) == SHID_FS_COMMONITEM)
return TRUE;
return FALSE;
}
// a win32 file (might be a shell extension .cab/.zip that behaves like a folder)
BOOL CFSFolder::_IsFile(LPCIDFOLDER pidf)
{
BOOL bRet = _GetType(pidf) == SHID_FS_FILE || _GetType(pidf) == SHID_FS_FILEUNICODE;
// if it's a file, it shouldn't be a folder.
// if it's not a file, usually it's a folder -- except if the type is SHID_FS,
// that's okay too because it's a simple pidl in a .lnk from a downlevel shell.
ASSERT(bRet ? !_IsFolder(pidf) : (_IsFolder(pidf) || _IsSimpleID(pidf)));
return bRet;
}
// it is a win32 file system folder (maybe a junction, maybe not)
BOOL CFSFolder::_IsFolder(LPCIDFOLDER pidf)
{
BOOL bRet = _GetType(pidf) == SHID_FS_DIRECTORY || _GetType(pidf) == SHID_FS_DIRUNICODE;
ASSERT(bRet ? (pidf->wAttrs & FILE_ATTRIBUTE_DIRECTORY) : !(pidf->wAttrs & FILE_ATTRIBUTE_DIRECTORY));
return bRet;
}
// is it a file system folder that is not a junction
BOOL CFSFolder::_IsFileFolder(LPCIDFOLDER pidf)
{
return _IsFolder(pidf) && !_IsJunction(pidf);
}
// non junction, but has the system or readonly bit (regular folder marked special for us)
BOOL CFSFolder::_IsSystemFolder(LPCIDFOLDER pidf)
{
return _IsFileFolder(pidf) && (pidf->wAttrs & (FILE_ATTRIBUTE_SYSTEM | FILE_ATTRIBUTE_READONLY));
}
// this is a heuristic to determine if the IDList was created
// normally or with a simple bind context (null size/mod date)
BOOL CFSFolder::_IsReal(LPCIDFOLDER pidf)
{
return pidf->dwSize || pidf->dateModified ? TRUE : FALSE;
}
DWORD CFSFolder::_GetUID(LPCIDFOLDER pidf)
{
return pidf->dwSize + ((DWORD)pidf->dateModified << 8) + ((DWORD)pidf->timeModified << 12);
}
void CFSFolder::_GetSize(LPCITEMIDLIST pidlParent, LPCIDFOLDER pidf, ULONGLONG *pcbSize)
{
ULONGLONG cbSize = pidf->dwSize;
if (cbSize != 0xFFFFFFFF)
*pcbSize = cbSize;
else if (pidlParent == NULL)
*pcbSize = 0;
else
{
HANDLE hfind;
WIN32_FIND_DATA wfd = {0};
TCHAR szPath[MAX_PATH];
// Get the real size by asking the file system
SHGetPathFromIDList(pidlParent, szPath);
_AppendItemToPath(szPath, pidf);
if (SHFindFirstFileRetry(NULL, NULL, szPath, &wfd, &hfind, SHPPFW_NONE) != S_OK)
{
*pcbSize = 0;
}
else
{
FindClose(hfind);
ULARGE_INTEGER uli;
uli.LowPart = wfd.nFileSizeLow;
uli.HighPart = wfd.nFileSizeHigh;
*pcbSize = uli.QuadPart;
}
}
}
ULONGLONG CFSFolder::_Size(LPCIDFOLDER pidf)
{
ULONGLONG cbSize;
_GetSize(_pidl, pidf, &cbSize);
return cbSize;
}
LPWSTR CFSFolder::_CopyName(LPCIDFOLDER pidf, LPWSTR pszName, UINT cchName)
{
CFileSysItem fsi(pidf);
return (LPWSTR) fsi.MayCopyFSName(TRUE, pszName, cchName);
}
BOOL CFSFolder::_ShowExtension(LPCIDFOLDER pidf)
{
CFileSysItemString fsi(pidf);
return fsi.ShowExtension(_DefaultShowExt());
}
BOOL CFSFolder::_DefaultShowExt()
{
if (_tbDefShowExt == TRIBIT_UNDEFINED)
{
SHELLSTATE ss;
SHGetSetSettings(&ss, SSF_SHOWEXTENSIONS, FALSE);
_tbDefShowExt = ss.fShowExtensions ? TRIBIT_TRUE : TRIBIT_FALSE;
}
return _tbDefShowExt == TRIBIT_TRUE;
}
BOOL CFileSysItemString::ShowExtension(BOOL fDefaultShowExt)
{
DWORD dwFlags = ClassFlags(FALSE);
if (dwFlags & SHCF_NEVER_SHOW_EXT)
return FALSE;
if (fDefaultShowExt)
return TRUE;
return dwFlags & (SHCF_ALWAYS_SHOW_EXT | SHCF_UNKNOWN);
}
//
// get the type name from the registry, if the name is blank make
// up a default.
//
// directory ==> "Folder"
// foo ==> "File"
// foo.xyz ==> "XYZ File"
//
void SHGetTypeName(LPCTSTR pszFile, HKEY hkey, BOOL fFolder, LPTSTR pszName, int cchNameMax)
{
LONG cb = cchNameMax * sizeof(TCHAR);
if (hkey == NULL || SHRegQueryValue(hkey, NULL, pszName, &cb) != ERROR_SUCCESS || pszName[0] == 0)
{
if (fFolder)
{
// NOTE the registry doesn't have a name for Folder
// because old apps would think it was a file type.
lstrcpy(pszName, g_szFolderTypeName);
}
else
{
LPTSTR pszExt = PathFindExtension(pszFile);
ASSERT(pszExt);
if (*pszExt == 0)
{
// Probably don't need the cchmax here, but...
lstrcpyn(pszName, g_szFileTypeName, cchNameMax);
}
else
{
TCHAR szExt[_MAX_EXT];
int cchMaxExtCopy = (int)min((cchNameMax - lstrlen(g_szFileTemplate)), ARRAYSIZE(szExt));
// Compose '<ext> File' (or what ever the template defines we do)
lstrcpyn(szExt, pszExt + 1, cchMaxExtCopy);
CharUpperNoDBCS(szExt);
wsprintf(pszName, g_szFileTemplate, szExt);
}
}
}
}
//
// return a pointer to the type name for the given PIDL
// the pointer is only valid while in a critical section
//
LPCTSTR CFSFolder::_GetTypeName(LPCIDFOLDER pidf)
{
CFileSysItemString fsi(pidf);
ASSERTCRITICAL
LPCTSTR pszClassName = LookupFileClassName(fsi.Class());
if (pszClassName == NULL)
{
WCHAR sz[80];
IQueryAssociations *pqa;
HRESULT hr = fsi.AssocCreate(NULL, FALSE, IID_PPV_ARG(IQueryAssociations, &pqa));
if (SUCCEEDED(hr))
{
DWORD cch = ARRAYSIZE(sz);
hr = pqa->GetString(0, ASSOCSTR_FRIENDLYDOCNAME, NULL, sz, &cch);
if (SUCCEEDED(hr))
{
pszClassName = AddFileClassName(fsi.Class(), sz);
}
pqa->Release();
}
}
return pszClassName;
}
//
// return the type name for the given PIDL
//
HRESULT CFSFolder::_GetTypeNameBuf(LPCIDFOLDER pidf, LPTSTR pszName, int cchNameMax)
{
HRESULT hr = S_OK;
ENTERCRITICAL;
LPCTSTR pszSource = _GetTypeName(pidf);
// pszSource will be NULL if the file does not have an extension.
if (!pszSource)
{
pszSource = TEXT(""); // Terminate Buffer
hr = E_FAIL;
}
StrCpyN(pszName, pszSource, cchNameMax);
LEAVECRITICAL;
return hr;
}
//
// Build a text string containing characters that represent attributes of a file.
// The attribute characters are assigned as follows:
// (R)eadonly, (H)idden, (S)ystem, (A)rchive, (H)idden.
//
void BuildAttributeString(DWORD dwAttributes, LPTSTR pszString, UINT nChars)
{
// Make sure we have attribute chars to build this string out of
if (!g_szAttributeChars[0])
LoadString(HINST_THISDLL, IDS_ATTRIB_CHARS, g_szAttributeChars, ARRAYSIZE(g_szAttributeChars));
// Make sure buffer is big enough to hold worst-case attributes
ASSERT(nChars >= ARRAYSIZE(g_adwAttributeBits) + 1);
for (int i = 0; i < ARRAYSIZE(g_adwAttributeBits); i++)
{
if (dwAttributes & g_adwAttributeBits[i])
*pszString++ = g_szAttributeChars[i];
}
*pszString = 0; // null terminate
}
// BryanSt: This doesn't work with FRAGMENTs. We should return the path
// without the Fragment for backward compatibility and then callers that care,
// can later determine that and take care of it.
//
// in/out:
// pszPath path to append pidf names to
// in:
// pidf relative pidl fragment
HRESULT CFSFolder::_AppendItemToPath(LPTSTR pszPath, LPCIDFOLDER pidf)
{
HRESULT hr = S_OK;
LPTSTR pszPathCur = pszPath + lstrlen(pszPath);
// e want to do this, but we stil have broken code in SHGetPathFromIDList
// ASSERT(_FindJunctionNext(pidf) == NULL); // no extra goo please
for (; SUCCEEDED(hr) && !ILIsEmpty((LPITEMIDLIST)pidf); pidf = _Next(pidf))
{
CFileSysItemString fsi(pidf);
int cchName = lstrlen(fsi.FSName()); // store the length of szName, to avoid calculating it twice
// mil 142338: handle bogus pidls that have multiple "C:"s in them
// due to bad shortcut creation.
if ((cchName == 2) && (fsi.FSName()[1] == TEXT(':')))
{
pszPathCur = pszPath;
}
else
{
// ASSERT(lstrlen(pszPath)+lstrlen(szName)+2 <= MAX_PATH);
if (((pszPathCur - pszPath) + cchName + 2) > MAX_PATH)
{
hr = HRESULT_FROM_WIN32(ERROR_BUFFER_OVERFLOW); // FormatMessage = "The file name is too long"
break;
}
LPTSTR pszTmp = CharPrev(pszPath, pszPathCur);
if (*pszTmp != TEXT('\\'))
*(pszPathCur++) = TEXT('\\');
}
// don't need lstrncpy cause we verified size above
lstrcpy(pszPathCur, fsi.FSName());
pszPathCur += cchName;
}
if (FAILED(hr))
*pszPath = 0;
return hr;
}
// get the file system folder path for this
//
// HRESULT_FROM_WIN32(ERROR_PATH_NOT_FOUND) is returned if we are a tracking
// folder that does not (yet) have a valid target.
HRESULT CFSFolder::_GetPath(LPTSTR pszPath)
{
HRESULT hr = E_FAIL;
if (_csidlTrack >= 0)
{
hr = SHGetFolderPath(NULL, _csidlTrack | CSIDL_FLAG_DONT_VERIFY, NULL, SHGFP_TYPE_CURRENT, pszPath);
if (hr == S_FALSE || FAILED(hr))
hr = HRESULT_FROM_WIN32(ERROR_PATH_NOT_FOUND);
}
else if (_pszPath)
{
lstrcpyn(pszPath, _pszPath, MAX_PATH);
hr = S_OK;
}
else
{
if (_pidlTarget &&
SUCCEEDED(SHGetNameAndFlags(_pidlTarget, SHGDN_FORPARSING, pszPath, MAX_PATH, NULL)))
{
_pszPath = StrDup(pszPath);
hr = S_OK;
}
else if (SUCCEEDED(SHGetNameAndFlags(_pidl, SHGDN_FORPARSING, pszPath, MAX_PATH, NULL)))
{
_pszPath = StrDup(pszPath);
hr = S_OK;
}
}
if (hr==S_OK && !(*pszPath))
hr= E_FAIL; // old behavior was to fail if pszPath was empty
return hr;
}
// Will fail (return FALSE) if not a mount point
BOOL CFSFolder::_GetMountingPointInfo(LPCIDFOLDER pidf, LPTSTR pszMountPoint, DWORD cchMountPoint)
{
BOOL bRet = FALSE;
// Is this a reparse point?
if (FILE_ATTRIBUTE_REPARSE_POINT & pidf->wAttrs)
{
TCHAR szLocalMountPoint[MAX_PATH];
if (SUCCEEDED(_GetPathForItem(pidf, szLocalMountPoint)))
{
int iDrive = PathGetDriveNumber(szLocalMountPoint);
if (-1 != iDrive)
{
TCHAR szDrive[4];
if (DRIVE_REMOTE != GetDriveType(PathBuildRoot(szDrive, iDrive)))
{
TCHAR szVolumeName[50]; //50 according to doc
PathAddBackslash(szLocalMountPoint);
// Check if it is a mounting point
if (GetVolumeNameForVolumeMountPoint(szLocalMountPoint, szVolumeName,
ARRAYSIZE(szVolumeName)))
{
bRet = TRUE;
if (pszMountPoint && cchMountPoint)
lstrcpyn(pszMountPoint, szLocalMountPoint, cchMountPoint);
}
}
}
}
}
return bRet;
}
// in:
// pidf may be NULL, or multi level item to append to path for this folder
// out:
// pszPath MAX_PATH buffer to receive the fully qualified file path for the item
//
HRESULT CFSFolder::_GetPathForItem(LPCIDFOLDER pidf, LPWSTR pszPath)
{
if (SUCCEEDED(_GetPath(pszPath)))
{
if (pidf)
{
return _AppendItemToPath(pszPath, pidf);
}
return S_OK;
}
return E_FAIL;
}
HRESULT CFSFolder::_GetPathForItems(LPCIDFOLDER pidfParent, LPCIDFOLDER pidfLast, LPTSTR pszPath)
{
HRESULT hr = _GetPathForItem(pidfParent ? pidfParent : pidfLast, pszPath);
if (SUCCEEDED(hr) && pidfParent)
hr = _AppendItemToPath(pszPath, pidfLast);
return hr;
}
BOOL _GetIniPath(BOOL fCreate, LPCTSTR pszFolder, LPCTSTR pszProvider, LPTSTR pszPath)
{
BOOL fExists = FALSE;
PathCombine(pszPath, pszFolder, c_szDesktopIni);
// CHECK for PathFileExists BEFORE calling to GetPrivateProfileString
// because if the file isn't there (which is the majority of cases)
// GetPrivateProfileString hits the disk twice looking for the file
if (pszProvider && *pszProvider)
{
union {
NETRESOURCE nr;
TCHAR buf[512];
} nrb;
LPTSTR lpSystem;
DWORD dwRes, dwSize = sizeof(nrb);
nrb.nr.dwType = RESOURCETYPE_ANY;
nrb.nr.lpRemoteName = pszPath;
nrb.nr.lpProvider = (LPTSTR)pszProvider; // const -> non const
dwRes = WNetGetResourceInformation(&nrb.nr, &nrb, &dwSize, &lpSystem);
fExists = (dwRes == WN_SUCCESS) || (dwRes == WN_MORE_DATA);
}
else
{
fExists = PathFileExists(pszPath);
}
if (fCreate && !fExists)
{
// we need to touch this file first
HANDLE h = CreateFile(pszPath, 0, FILE_SHARE_DELETE, NULL, CREATE_NEW, FILE_ATTRIBUTE_HIDDEN | FILE_ATTRIBUTE_SYSTEM, NULL);
if (INVALID_HANDLE_VALUE != h)
{
PathMakeSystemFolder(pszFolder);
fExists = TRUE;
CloseHandle(h);
}
}
return fExists;
}
STDAPI_(BOOL) SetFolderString(BOOL fCreate, LPCTSTR pszFolder, LPCTSTR pszProvider, LPCTSTR pszSection, LPCTSTR pszKey, LPCTSTR pszData)
{
TCHAR szPath[MAX_PATH];
if (_GetIniPath(fCreate, pszFolder, pszProvider, szPath))
{
return SHSetIniStringUTF7(pszSection, pszKey, pszData, szPath);
}
return FALSE;
}
//
// This function retrieves the private profile strings from the desktop.ini file and
// return it through pszOut
//
// This function uses SHGetIniStringUTF7 to get the string, so it is valid
// to use SZ_CANBEUNICODE on the key name.
BOOL GetFolderStringEx(LPCTSTR pszFolder, LPCTSTR pszProvider, LPCTSTR pszSection, LPCTSTR pszKey, LPTSTR pszOut, int cch)
{
BOOL fRet = FALSE;
TCHAR szPath[MAX_PATH];
if (_GetIniPath(FALSE, pszFolder, pszProvider, szPath))
{
TCHAR szTemp[INFOTIPSIZE];
fRet = SHGetIniStringUTF7(pszSection, pszKey, szTemp, ARRAYSIZE(szTemp), szPath);
if (fRet)
{
SHExpandEnvironmentStrings(szTemp, pszOut, cch); // This could be a path, so expand the env vars in it
}
}
return fRet;
}
int GetFolderInt(LPCTSTR pszFolder, LPCTSTR pszProvider, LPCTSTR pszSection, LPCTSTR pszKey, int iDefault)
{
BOOL fRet = FALSE;
TCHAR szPath[MAX_PATH];
if (_GetIniPath(FALSE, pszFolder, pszProvider, szPath))
{
return GetPrivateProfileInt(pszSection, pszKey, iDefault, szPath);
}
return iDefault;
}
STDAPI_(BOOL) GetFolderString(LPCTSTR pszFolder, LPCTSTR pszProvider, LPTSTR pszOut, int cch, LPCTSTR pszKey)
{
return GetFolderStringEx(pszFolder, pszProvider, STRINI_CLASSINFO, pszKey, pszOut, cch);
}
// This function retrieves the specifice GUID from desktop.ini file.
// replace this with property bag access on the folder
STDAPI_(BOOL) GetFolderGUID(LPCTSTR pszFolder, LPCTSTR pszProvider, CLSID* pclsid, LPCTSTR pszKey)
{
TCHAR szCLSID[40];
if (GetFolderString(pszFolder, pszProvider, szCLSID, ARRAYSIZE(szCLSID), pszKey))
{
return SUCCEEDED(SHCLSIDFromString(szCLSID, pclsid));
}
return FALSE;
}
//
// This function retrieves the correct CLSID from desktop.ini file.
//
BOOL _GetFolderCLSID(LPCTSTR pszFolder, LPCTSTR pszProvider, CLSID* pclsid)
{
BOOL bFound = FALSE;
WCHAR szPath[MAX_PATH];
if (_GetIniPath(FALSE, pszFolder, pszProvider, szPath))
{
DWORD dwChars;
WCHAR szSectionValue[1024];
dwChars = GetPrivateProfileSection(STRINI_CLASSINFO, szSectionValue, sizeof(szSectionValue), szPath);
if (dwChars != (sizeof(szSectionValue) - 2) && (dwChars != 0))
{
static WCHAR *const c_rgpsz[] = {TEXT("CLSID2"),
TEXT("CLSID"),
TEXT("UICLSID")};
int iFoundIndex = ARRAYSIZE(c_rgpsz);
// We look for CLSID2, CLSID, then UICLSID, since there could be multiple kes in this section.
// CLSID2 makes folders work on Win95 if the CLSID does not exist on the machine
for (WCHAR *pNextKeyPointer = szSectionValue; *pNextKeyPointer; pNextKeyPointer += lstrlen(pNextKeyPointer) + 1)
{
PWCHAR pBuffer = pNextKeyPointer;
PWCHAR pEqual = StrChrW(pBuffer, L'=');
if (pEqual && (*(pEqual+1) != L'\0'))
{
*pEqual = L'\0';
for (int i = 0; i < ARRAYSIZE(c_rgpsz); i++)
{
if (StrCmpIC(c_rgpsz[i], pBuffer) == 0)
{
CLSID clsid;
if ((iFoundIndex < i) && bFound)
{
break;
}
pBuffer += lstrlen(pBuffer) + 1;
if (SUCCEEDED(SHCLSIDFromString(pBuffer, &clsid)))
{
if (i == ARRAYSIZE(c_rgpsz) - 1)
{
// hack for "Temporary Internet Files"
if (clsid == CLSID_CacheFolder)
{
*pclsid = CLSID_CacheFolder2;
bFound = TRUE;
}
}
else
{
*pclsid = clsid;
bFound = TRUE;
}
iFoundIndex = i;
}
break;
}
} // end of for
} // end of if
} //end of for
}
}
return bFound;
}
LPTSTR PathFindCLSIDExtension(LPCTSTR pszFile, CLSID *pclsid)
{
LPCTSTR pszExt = PathFindExtension(pszFile);
ASSERT(pszExt);
if (*pszExt == TEXT('.') && *(pszExt + 1) == TEXT('{') /* '}' */)
{
CLSID clsid;
if (pclsid == NULL)
pclsid = &clsid;
if (SUCCEEDED(SHCLSIDFromString(pszExt + 1, pclsid)))
return (LPTSTR)pszExt; // const -> non const
}
return NULL;
}
//
// This function retrieves the CLSID from a filename
// file.{GUID}
//
BOOL _GetFileCLSID(LPCTSTR pszFile, CLSID* pclsid)
{
return PathFindCLSIDExtension(pszFile, pclsid) != NULL;
}
// test pidf for properties that make make it a junction, mark it as a junction
// as needed, see _IsJunction usage
BOOL _ClsidExists(REFGUID clsid)
{
HKEY hk;
if (SUCCEEDED(SHRegGetCLSIDKey(clsid, NULL, FALSE, FALSE, &hk)))
{
RegCloseKey(hk);
return TRUE;
}
return FALSE;
}
LPIDFOLDER CFSFolder::_MarkAsJunction(LPCIDFOLDER pidfSimpleParent, LPIDFOLDER pidf, LPCTSTR pszName)
{
CLSID clsid;
BOOL fJunction = FALSE;
// check for a junction point, junctions are either
// Folder.{guid} or File.{guid} both fall into this case
if (_GetFileCLSID(pszName, &clsid))
{
fJunction = TRUE;
}
else if (_IsSystemFolder(pidf))
{
// system (read only or system bit) look for the desktop.ini in a folder
TCHAR szPath[MAX_PATH];
if (SUCCEEDED(_GetPathForItems(pidfSimpleParent, pidf, szPath)))
{
// CLSID2 makes folders work on Win95 if the CLSID does not exist on the machine
if (_GetFolderCLSID(szPath, _pszNetProvider, &clsid))
{
fJunction = TRUE;
}
}
}
if (fJunction && _ClsidExists(clsid))
{
pidf->bFlags |= SHID_JUNCTION;
pidf = (LPIDFOLDER) ILAppendHiddenClsid((LPITEMIDLIST)pidf, IDLHID_JUNCTION, &clsid);
}
return pidf;
}
BOOL CFSFolder::_GetJunctionClsid(LPCIDFOLDER pidf, CLSID *pclsid)
{
CFileSysItemString fsi(pidf);
return fsi.GetJunctionClsid(pclsid, TRUE);
}
BOOL CFileSysItemString::GetJunctionClsid(CLSID *pclsid, BOOL fShellExtOk)
{
BOOL bRet = FALSE;
*pclsid = CLSID_NULL;
if (CFSFolder::_IsJunction(_pidf))
{
// if this is a junction point that was created with a hidden CLSID
// then it should be stored with IDLHID_JUNCTION
if (ILGetHiddenClsid((LPCITEMIDLIST)_pidf, IDLHID_JUNCTION, pclsid))
bRet = TRUE;
else
{
// it might be an oldstyle JUNCTION point that was persisted out or a ROOT_REGITEM
if (SIL_GetType((LPITEMIDLIST)_pidf) == SHID_ROOT_REGITEM)
{
const UNALIGNED CLSID *pc = (UNALIGNED CLSID *)(((BYTE *)_pidf) + _pidf->cb - sizeof(CLSID));
*pclsid = *pc;
bRet = TRUE;
}
}
}
else if (fShellExtOk)
{
if (ClassFlags(FALSE) & SHCF_IS_SHELLEXT)
{
IAssociationArray *paa;
// must pass NULL for CFSFolder to avoid recursion
if (SUCCEEDED(AssocCreate(NULL, FALSE, IID_PPV_ARG(IAssociationArray, &paa))))
{
CSmartCoTaskMem<WCHAR> spsz;
if (SUCCEEDED(paa->QueryString(ASSOCELEM_MASK_QUERYNORMAL, AQS_CLSID, NULL, &spsz)))
{
bRet = GUIDFromString(spsz, pclsid);
}
paa->Release();
}
}
}
else if (CFSFolder::_IsFolder(_pidf))
{
// directory.{guid} is always of Class() {guid}
bRet = _GetFileCLSID(FSName(), pclsid);
}
return bRet;
}
//
// returns a unique name for a class, dont use this function to get
// the ProgID for a class call SHGetClassKey() for that
//
// Returns: class name in pszClass
//
// foo.ext ".ext"
// foo "."
// (empty) "Folder"
// directory "Directory"
// junction "CLSID\{clsid}"
//
BOOL CFSFolder::_GetClass(LPCIDFOLDER pidf, LPTSTR pszClass, UINT cch)
{
CFileSysItemString fsi(pidf);
StrCpyN(pszClass, fsi.Class(), cch);
return TRUE;
}
LPCWSTR CFileSysItemString::_Class()
{
if (_pidf->cb == 0) // ILIsEmpty()
{
// the desktop. Always use the "Folder" class.
_pszClass = c_szFolderClass;
}
// else if (ILGetHiddenString(IDLHID_TREATASCLASS))
else
{
CLSID clsid;
if (GetJunctionClsid(&clsid, FALSE))
{
// This is a junction point, get the CLSID from it.
CSmartCoTaskMem<OLECHAR> spsz;
if (SUCCEEDED(ProgIDFromCLSID(clsid, &spsz)))
{
StrCpyN(_sz, spsz, ARRAYSIZE(_sz));
}
else
SHStringFromGUID(clsid, _sz , ARRAYSIZE(_sz));
_fsin = FSINAME_CLASS;
}
else if (CFSFolder::_IsFolder(_pidf))
{
// This is a directory. Always use the "Directory" class.
// This can also be a Drive id.
_pszClass = TEXT("Directory");
}
else
{
// This is a file. Get the class based on the extension.
LPCWSTR pszFile = FSName();
LPCWSTR pszExt = PathFindExtension(pszFile);
ASSERT(pszExt);
ASSERT(!(_pidf->wAttrs & FILE_ATTRIBUTE_DIRECTORY));
if (*pszExt == 0)
{
if (_pidf->wAttrs & FILE_ATTRIBUTE_SYSTEM)
_pszClass = TEXT(".sys");
else
_pszClass = TEXT(".");
}
else if (pszFile == _sz)
{
// we need the buffer to be setup correctly
MoveMemory(_sz, pszExt, CbFromCchW(lstrlen(pszExt) + 1));
_fsin = FSINAME_CLASS;
}
else
_pszClass = pszExt;
}
}
ASSERT(_pszClass || *_sz);
return _pszClass ? _pszClass : _sz;
}
LPCWSTR CFileSysItemString::Class()
{
if (!_pszClass)
{
if (!(_fsin & FSINAME_CLASS))
{
return _Class();
}
else
{
return _sz;
}
}
return _pszClass;
}
CFSAssocEnumData::CFSAssocEnumData(BOOL fIsUnknown, CFSFolder *pfs, LPCIDFOLDER pidf) : _fIsUnknown(fIsUnknown)
{
_fIsSystemFolder = pfs->_IsSystemFolder(pidf);
pfs->_GetPathForItem(pidf, _szPath);
if (_fIsUnknown)
_fIsUnknown = !(FILE_ATTRIBUTE_OFFLINE & pidf->wAttrs);
else
{
if (CFSFolder::_IsFileFolder(pidf))
_pidl = ILCombine(pfs->_GetIDList(), (LPCITEMIDLIST)pidf);
}
}
LPCWSTR _GetDirectoryClass(LPCWSTR pszPath, LPCITEMIDLIST pidl, BOOL fIsSystemFolder);
BOOL CFSAssocEnumData::_Next(IAssociationElement **ppae)
{
HRESULT hr = E_FAIL;
if (_fIsUnknown)
{
CLSID clsid;
hr = GetClassFile(_szPath, &clsid);
if (SUCCEEDED(hr))
{
CSmartCoTaskMem<OLECHAR> spszProgid;
hr = ProgIDFromCLSID(clsid, &spszProgid);
if (SUCCEEDED(hr))
{
hr = AssocElemCreateForClass(&CLSID_AssocProgidElement, spszProgid, ppae);
}
if (FAILED(hr))
{
WCHAR sz[GUIDSTR_MAX];
SHStringFromGUIDW(clsid, sz, ARRAYSIZE(sz));
hr = AssocElemCreateForClass(&CLSID_AssocClsidElement, sz, ppae);
}
}
if (FAILED(hr))
{
hr = AssocElemCreateForClass(&CLSID_AssocShellElement, L"Unknown", ppae);
}
_fIsUnknown = FALSE;
}
if (FAILED(hr) && _pidl)
{
PCWSTR psz = _GetDirectoryClass(_szPath, _pidl, _fIsSystemFolder);
if (psz)
hr = AssocElemCreateForClass(&CLSID_AssocSystemElement, psz, ppae);
ILFree(_pidl);
_pidl = NULL;
}
return SUCCEEDED(hr);
}
class CFSAssocEnumExtra : public CEnumAssociationElements
{
public:
protected:
BOOL _Next(IAssociationElement **ppae);
protected:
};
BOOL CFSAssocEnumExtra::_Next(IAssociationElement **ppae)
{
if (_cNext == 0)
{
// corel wp suite 7 relies on the fact that send to menu is hard coded
// not an extension so do not insert it (and the similar items)
if (!(SHGetAppCompatFlags(ACF_CONTEXTMENU) & ACF_CONTEXTMENU))
{
AssocElemCreateForClass(&CLSID_AssocShellElement, L"AllFilesystemObjects", ppae);
}
}
return *ppae != NULL;
}
HRESULT CFileSysItemString::AssocCreate(CFSFolder *pfs, BOOL fForCtxMenu, REFIID riid, void **ppv)
{
// WARNING - the pfs keeps us from recursing.
*ppv = NULL;
IAssociationArrayInitialize *paai;
HRESULT hr = ::AssocCreate(CLSID_QueryAssociations, IID_PPV_ARG(IAssociationArrayInitialize, &paai));
if (SUCCEEDED(hr))
{
// the base class for directory's is always Folder
ASSOCELEM_MASK base;
if (CFSFolder::_IsFolder(_pidf))
base = ASSOCELEM_BASEIS_FOLDER;
else
{
// for files it is always *
base = ASSOCELEM_BASEIS_STAR;
if (pfs)
{
CLSID clsid;
if (GetJunctionClsid(&clsid, TRUE))
{
// but if this file is also a folder (like .zip and .cab)
// then we should also use Folder
if (SHGetAttributesFromCLSID2(&clsid, 0, SFGAO_FOLDER) & SFGAO_FOLDER)
base |= ASSOCELEM_BASEIS_FOLDER;
}
}
}
hr = paai->InitClassElements(base, Class());
if (SUCCEEDED(hr) && pfs)
{
BOOL fIsLink = fForCtxMenu && (_ClassFlags(paai, FALSE) & SHCF_IS_LINK);
if (fIsLink)
{
// we dont like to do everything for LINK, but
// maybe we should be adding BASEIS_STAR?
paai->FilterElements(ASSOCELEM_DEFAULT | ASSOCELEM_EXTRA);
}
IEnumAssociationElements *penum = new CFSAssocEnumExtra();
if (penum)
{
paai->InsertElements(ASSOCELEM_EXTRA, penum);
penum->Release();
}
if (!fIsLink)
{
penum = new CFSAssocEnumData(hr == S_FALSE, pfs, _pidf);
if (penum)
{
paai->InsertElements(ASSOCELEM_DATA | ASSOCELEMF_INCLUDE_SLOW, penum);
penum->Release();
}
}
}
if (SUCCEEDED(hr))
hr = paai->QueryInterface(riid, ppv);
paai->Release();
}
return hr;
}
HRESULT CFSFolder::_AssocCreate(LPCIDFOLDER pidf, REFIID riid, void **ppv)
{
CFileSysItemString fsi(pidf);
return fsi.AssocCreate(this, FALSE, riid, ppv);
}
//
// Description: This simulates the ComponentCategoryManager
// call which checks to see if a CLSID is a member of a CATID.
//
STDAPI_(BOOL) IsMemberOfCategory(IAssociationArray *paa, REFCATID rcatid)
{
BOOL fRet = FALSE;
CSmartCoTaskMem<WCHAR> spsz;
if (SUCCEEDED(paa->QueryString(ASSOCELEM_MASK_QUERYNORMAL, AQS_CLSID, NULL, &spsz)))
{
TCHAR szKey[GUIDSTR_MAX * 4], szCATID[GUIDSTR_MAX];
// Construct the registry key that detects if
// a CLSID is a member of a CATID.
SHStringFromGUID(rcatid, szCATID, ARRAYSIZE(szCATID));
wnsprintf(szKey, ARRAYSIZE(szKey), TEXT("CLSID\\%s\\Implemented Categories\\%s"), spsz, szCATID);
// See if it's there.
fRet = SHRegQueryValue(HKEY_CLASSES_ROOT, szKey, NULL, NULL) == ERROR_SUCCESS;
}
return fRet;
}
// get flags for a file class.
//
// given a FS PIDL returns a DWORD of flags, or 0 for error
//
// SHCF_ICON_INDEX this is this sys image index for per class
// SHCF_ICON_PERINSTANCE icons are per instance (one per file)
// SHCF_ICON_DOCICON icon is in shell\open\command (simulate doc icon)
//
// SHCF_HAS_ICONHANDLER set if class has a IExtractIcon handler
//
// SHCF_UNKNOWN set if extenstion is not registered
//
// SHCF_IS_LINK set if class is a link
// SHCF_ALWAYS_SHOW_EXT always show the extension
// SHCF_NEVER_SHOW_EXT never show the extension
//
DWORD CFSFolder::_GetClassFlags(LPCIDFOLDER pidf)
{
CFileSysItemString fsi(pidf);
return fsi.ClassFlags(FALSE);
}
void CFileSysItemString::_QueryIconIndex(IAssociationArray *paa)
{
// check for the default icon under HKCU for this file extension.
// null out the icon index
_dwClass &= ~SHCF_ICON_INDEX;
PWSTR pszIcon;
HRESULT hr = E_FAIL;
if (paa)
{
// check for icon in ProgID
// Then, check if the default icon is specified in OLE-style.
hr = paa->QueryString(ASSOCELEM_MASK_QUERYNORMAL, AQS_DEFAULTICON, NULL, &pszIcon);
if (SUCCEEDED(hr))
{
// hijack these icons
// office XP has really ugly icons for images
// and ours are so beautiful...office wont mind
static const struct
{
PCWSTR pszUgly;
PCWSTR pszPretty;
} s_hijack[] =
{
{ L"PEicons.exe,1", L"shimgvw.dll,2" }, // PNG
{ L"PEicons.exe,4", L"shimgvw.dll,2" }, // GIF
{ L"PEicons.exe,5", L"shimgvw.dll,3" }, // JPEG
{ L"MSPVIEW.EXE,1", L"shimgvw.dll,4" }, // TIF
{ L"wordicon.exe,8", L"moricons.dll,-109"},
{ L"xlicons.exe,13", L"moricons.dll,-110"},
{ L"accicons.exe,57", L"moricons.dll,-111"},
{ L"pptico.exe,6", L"moricons.dll,-112"},
{ L"fpicon.exe,2", L"moricons.dll,-113"},
};
PCWSTR pszName = PathFindFileName(pszIcon);
for (int i = 0; i < ARRAYSIZE(s_hijack); i++)
{
if (0 == StrCmpIW(pszName, s_hijack[i].pszUgly))
{
// replace this ugly chicken
CoTaskMemFree(pszIcon);
hr = SHStrDupW(s_hijack[i].pszPretty, &pszIcon);
break;
}
}
}
else if (!CFSFolder::_IsFolder(_pidf))
{
hr = paa->QueryString(ASSOCELEM_MASK_QUERYNORMAL, AQVS_APPLICATION_PATH, NULL, &pszIcon);
if (SUCCEEDED(hr))
_dwClass |= SHCF_ICON_DOCICON;
}
}
// Check if this is a per-instance icon
if (SUCCEEDED(hr) && (lstrcmp(pszIcon, TEXT("%1")) == 0 ||
lstrcmp(pszIcon, TEXT("\"%1\"")) == 0))
{
_dwClass &= ~SHCF_ICON_DOCICON;
_dwClass |= SHCF_ICON_PERINSTANCE;
}
else
{
int iIcon, iImage;
if (SUCCEEDED(hr))
{
iIcon = PathParseIconLocation(pszIcon);
iImage = Shell_GetCachedImageIndex(pszIcon, iIcon, _dwClass & SHCF_ICON_DOCICON ? GIL_SIMULATEDOC : 0);
if (iImage == -1)
{
iIcon = _dwClass & SHCF_ICON_DOCICON ? II_DOCUMENT : II_DOCNOASSOC;
iImage = Shell_GetCachedImageIndex(c_szShell32Dll, iIcon, 0);
}
}
else
{
iIcon = CFSFolder::_IsFolder(_pidf) ? II_FOLDER : II_DOCNOASSOC;
iImage = Shell_GetCachedImageIndex(c_szShell32Dll, iIcon, 0);
_dwClass |= SHCF_ICON_DOCICON; // make _dwClass non-zero
}
// Shell_GetCachedImageIndex can return -1 for failure cases. We
// dont want to or -1 in, so check to make sure the index is valid.
if ((iImage & ~SHCF_ICON_INDEX) == 0)
{
// no higher bits set so its ok to or the index in
_dwClass |= iImage;
}
}
if (SUCCEEDED(hr))
CoTaskMemFree(pszIcon);
}
#define ASSOCELEM_GETBITS (ASSOCELEM_USER | ASSOCELEM_DEFAULT | ASSOCELEM_SYSTEM)
BOOL _IsKnown(IAssociationArray *paa)
{
BOOL fRet = FALSE;
CComPtr<IEnumAssociationElements> spenum;
if (paa && SUCCEEDED(paa->EnumElements(ASSOCELEM_GETBITS, &spenum)))
{
CComPtr<IAssociationElement> spae;
ULONG c;
fRet = S_OK == spenum->Next(1, &spae, &c);
}
return fRet;
}
void CFileSysItemString::_QueryClassFlags(IAssociationArray *paa)
{
// always hide extension for .{guid} junction points:
// unless ShowSuperHidden() is on. since this means the user wants to see system stuff
if (!ShowSuperHidden() && _GetFileCLSID(FSName(), NULL))
_dwClass = SHCF_NEVER_SHOW_EXT;
else if (CFSFolder::_IsFolder(_pidf))
_dwClass = SHCF_ALWAYS_SHOW_EXT;
else
_dwClass = 0;
if (_IsKnown(paa))
{
// see what handlers exist
if (SUCCEEDED(paa->QueryExists(ASSOCELEM_GETBITS, AQNS_SHELLEX_HANDLER, TEXT("IconHandler"))))
_dwClass |= SHCF_HAS_ICONHANDLER;
// check for browsability
if (!(SHGetAppCompatFlags(ACF_DOCOBJECT) & ACF_DOCOBJECT))
{
if (SUCCEEDED(paa->QueryExists(ASSOCELEM_GETBITS, AQN_NAMED_VALUE, TEXT("DocObject")))
|| SUCCEEDED(paa->QueryExists(ASSOCELEM_GETBITS, AQN_NAMED_VALUE, TEXT("BrowseInPlace"))))
_dwClass |= SHCF_IS_DOCOBJECT;
}
if (IsMemberOfCategory(paa, CATID_BrowsableShellExt))
_dwClass |= SHCF_IS_SHELLEXT;
// get attributes
if (SUCCEEDED(paa->QueryExists(ASSOCELEM_GETBITS, AQN_NAMED_VALUE, TEXT("IsShortcut"))))
_dwClass |= SHCF_IS_LINK;
if (SUCCEEDED(paa->QueryExists(ASSOCELEM_GETBITS, AQN_NAMED_VALUE, TEXT("AlwaysShowExt"))))
_dwClass |= SHCF_ALWAYS_SHOW_EXT;
if (SUCCEEDED(paa->QueryExists(ASSOCELEM_GETBITS, AQN_NAMED_VALUE, TEXT("NeverShowExt"))))
_dwClass |= SHCF_NEVER_SHOW_EXT;
// figure out what type of icon this type of file uses.
if (_dwClass & SHCF_HAS_ICONHANDLER)
{
_dwClass |= SHCF_ICON_PERINSTANCE;
}
}
else
{
// unknown type - pick defaults and get out.
_dwClass |= SHCF_UNKNOWN | SHCF_ALWAYS_SHOW_EXT;
}
}
CFSFolder * GetFSFolderFromShellFolder(IShellFolder *psf)
{
CFSFolder *pfs = NULL;
if (psf)
psf->QueryInterface(IID_INeedRealCFSFolder, (void **)&pfs);
return pfs;
}
PERCEIVED GetPerceivedType(IShellFolder *psf, LPCITEMIDLIST pidl)
{
PERCEIVED gen = GEN_UNKNOWN;
CFSFolder *pfsf = GetFSFolderFromShellFolder(psf);
if (pfsf)
{
LPCIDFOLDER pidf = CFSFolder_IsValidID(pidl);
if (pidf)
{
CFileSysItemString fsi(pidf);
gen = fsi.PerceivedType();
}
}
return gen;
}
const struct {
PERCEIVED gen;
LPCWSTR psz;
} c_rgPerceivedTypes[] = {
{GEN_TEXT, L"text"},
{GEN_IMAGE, L"image"},
{GEN_AUDIO, L"audio"},
{GEN_VIDEO, L"video"},
{GEN_COMPRESSED, L"compressed"},
};
PERCEIVED CFileSysItemString::PerceivedType()
{
// look up the file type in the cache.
PERCEIVED gen = LookupFilePerceivedType(Class());
if (gen == GEN_UNKNOWN)
{
WCHAR sz[40];
DWORD cb = sizeof(sz);
if (NOERROR == SHGetValueW(HKEY_CLASSES_ROOT, Class(), L"PerceivedType", NULL, sz, &cb))
{
gen = GEN_CUSTOM;
for (int i = 0; i < ARRAYSIZE(c_rgPerceivedTypes); i++)
{
if (0 == StrCmpC(c_rgPerceivedTypes[i].psz, sz))
{
gen = c_rgPerceivedTypes[i].gen;
break;
}
}
}
else if (CFSFolder::_IsFolder(_pidf))
{
gen = GEN_FOLDER;
}
else
{
gen = GEN_UNSPECIFIED;
}
AddFilePerceivedType(Class(), gen);
}
return gen;
}
BOOL _IsImageExt(PCWSTR psz);
BOOL CFileSysItemString::IsShimgvwImage()
{
return _IsImageExt(Class());
}
DWORD CFileSysItemString::_ClassFlags(IUnknown *punkAssoc, BOOL fNeedsIconBits)
{
// look up the file type in the cache.
if (!_dwClass)
_dwClass = LookupFileClass(Class());
if (_dwClass)
{
if (!fNeedsIconBits || (_dwClass & SHCF_ICON_INDEX) != SHCF_ICON_INDEX)
return _dwClass;
}
IAssociationArray *paa;
HRESULT hr;
if (punkAssoc)
hr = punkAssoc->QueryInterface(IID_PPV_ARG(IAssociationArray, &paa));
else
hr = AssocCreate(NULL, FALSE, IID_PPV_ARG(IAssociationArray, &paa));
if (!_dwClass)
_QueryClassFlags(paa);
if (fNeedsIconBits && !(_dwClass & SHCF_ICON_PERINSTANCE))
_QueryIconIndex(paa);
else
{
// set it to be not init'd
_dwClass |= SHCF_ICON_INDEX;
}
if (SUCCEEDED(hr))
{
paa->Release();
if (0 == _dwClass)
{
// If we hit this, the extension for this file type is incorrectly installed
// and it will cause double clicking on such files to open the "Open With..."
// file associatins dialog.
//
// IF YOU HIT THIS:
// 1. Find the file type by checking szClass.
// 2. Contact the person that installed that file type and have them fix
// the install to have an icon and an associated program.
TraceMsg(TF_WARNING, "_GetClassFlags() has detected an improperly registered class: '%s'", Class());
}
}
AddFileClass(Class(), _dwClass);
return _dwClass;
}
//
// this function checks for flags in desktop.ini
//
#define GFF_DEFAULT_TO_FS 0x0001 // the shell-xtension permits FS as the default where it cannot load
#define GFF_ICON_FOR_ALL_FOLDERS 0x0002 // use the icon specified in the desktop.ini for all sub folders
BOOL CFSFolder::_GetFolderFlags(LPCIDFOLDER pidf, UINT *prgfFlags)
{
TCHAR szPath[MAX_PATH];
*prgfFlags = 0;
if (FAILED(_GetPathForItem(pidf, szPath)))
return FALSE;
if (PathAppend(szPath, c_szDesktopIni))
{
if (GetPrivateProfileInt(STRINI_CLASSINFO, TEXT("DefaultToFS"), 1, szPath))
{
*prgfFlags |= GFF_DEFAULT_TO_FS;
}
#if 0
if (GetPrivateProfileInt(STRINI_CLASSINFO, TEXT("SubFoldersUseIcon"), 1, szPath))
{
*prgfFlags |= GFF_ICON_FOR_ALL_FOLDERS;
}
#endif
}
return TRUE;
}
//
// This funtion retrieves the ICONPATh from desktop.ini file.
// It takes a pidl as an input.
// NOTE: There is code in SHDOCVW--ReadIconLocation that does almost the same thing
// only that code looks in .URL files instead of desktop.ini
BOOL CFSFolder::_GetFolderIconPath(LPCIDFOLDER pidf, LPTSTR pszIcon, int cchMax, UINT *pIndex)
{
TCHAR szPath[MAX_PATH], szIcon[MAX_PATH];
BOOL fSuccess = FALSE;
UINT iIndex;
if (pszIcon == NULL)
{
pszIcon = szIcon;
cchMax = ARRAYSIZE(szPath);
}
if (pIndex == NULL)
pIndex = &iIndex;
*pIndex = _GetDefaultFolderIcon(); // Default the index to II_FOLDER (default folder icon)
if (SUCCEEDED(_GetPathForItem(pidf, szPath)))
{
if (GetFolderString(szPath, _pszNetProvider, pszIcon, cchMax, SZ_CANBEUNICODE TEXT("IconFile")))
{
// Fix the relative path
PathCombine(pszIcon, szPath, pszIcon);
fSuccess = PathFileExistsAndAttributes(pszIcon, NULL);
if (fSuccess)
{
TCHAR szIndex[16];
if (GetFolderString(szPath, _pszNetProvider, szIndex, ARRAYSIZE(szIndex), TEXT("IconIndex")))
{
StrToIntEx(szIndex, 0, (int *)pIndex);
}
}
}
}
return fSuccess;
}
// IDList factory
CFileSysItem::CFileSysItem(LPCIDFOLDER pidf)
: _pidf(pidf), _pidp((PCIDPERSONALIZED)-1)
{
_pidfx = (PCIDFOLDEREX) ILFindHiddenIDOn((LPCITEMIDLIST)pidf, IDLHID_IDFOLDEREX, FALSE);
if (_pidfx && _pidfx->hid.wVersion < IDFX_V1)
_pidfx = NULL;
}
BOOL CFileSysItem::_IsPersonalized()
{
if (_pidp == (PCIDPERSONALIZED) -1)
{
_pidp = (PCIDPERSONALIZED) ILFindHiddenIDOn((LPCITEMIDLIST)_pidf, IDLHID_PERSONALIZED, FALSE);
if (_pidp && 0 >= (signed short) _pidp->hid.wVersion)
_pidp = NULL;
}
return _pidp != NULL;
}
CFileSysItemString::CFileSysItemString(LPCIDFOLDER pidf)
: CFileSysItem(pidf), _pszFSName(NULL), _pszUIName(NULL), _pszClass(NULL), _dwClass(0), _fsin(FSINAME_NONE)
{
*_sz = 0;
}
LPCWSTR CFileSysItemString::FSName()
{
if (!_pszFSName)
{
if (!(_fsin & FSINAME_FS))
{
LPCWSTR psz = MayCopyFSName(FALSE, _sz, ARRAYSIZE(_sz));
if (psz == _sz)
_fsin = FSINAME_FS;
else
_pszFSName = psz;
}
}
return _pszFSName ? _pszFSName : _sz;
}
LPCWSTR CFileSysItem::MayCopyFSName(BOOL fMustCopy, LPWSTR psz, DWORD cch)
{
if (_pidfx)
{
LPNWSTR pnsz = UASTROFFW(_pidfx, _pidfx->offNameW);
// return back a pointer inside the pidfx
// if we can...
if (fMustCopy || ((INT_PTR)pnsz & 1))
{
ualstrcpynW(psz, pnsz, cch);
}
else
psz = (LPWSTR) pnsz;
}
else
{
if ((CFSFolder::_GetType(_pidf) & SHID_FS_UNICODE) == SHID_FS_UNICODE)
{
ualstrcpynW(psz, (LPCWSTR)_pidf->cFileName, cch);
}
else
{
MultiByteToWideChar(CP_ACP, 0, _pidf->cFileName, -1, psz, cch);
}
}
return psz;
}
LPCSTR CFileSysItemString::AltName()
{
UINT cbName;
if (_pidfx)
{
// we put the altname in cFileName
cbName = 0;
}
else if ((CFSFolder::_GetType(_pidf) & SHID_FS_UNICODE) == SHID_FS_UNICODE)
{
cbName = (ualstrlenW((LPCWSTR)_pidf->cFileName) + 1) * sizeof(WCHAR);
}
else
{
cbName = lstrlenA(_pidf->cFileName) + 1;
}
return _pidf->cFileName + cbName;
}
LPCWSTR CFileSysItemString::UIName(CFSFolder *pfs)
{
if (!_pszUIName)
{
if (!(_fsin & FSINAME_UI))
{
if (!_pidfx || !_LoadResource(pfs))
{
if (!ShowExtension(pfs->_DefaultShowExt()))
{
// we need to have a buffer
if (!(_fsin & FSINAME_FS))
MayCopyFSName(TRUE, _sz, ARRAYSIZE(_sz));
PathRemoveExtension(_sz);
// lose the FSINAME_FS bit
_fsin = FSINAME_UI;
}
else
{
// the FSName and the UIName are the same
if (_sz == FSName())
{
// the FSName and the UIName are the same
// pidl is unaligned so the buffer gets double work
_fsin = FSINAME_FSUI;
}
else
{
// and we are aligned so we can use the same name
// directories are often this way.
_pszUIName = _pszFSName;
}
}
}
}
}
return _pszUIName ? _pszUIName : _sz;
}
UINT UnicodeToAscii(LPCWSTR pwsz, LPSTR psz, UINT cch)
{
// always at least copy the NULL
UINT cchRet = 1;
while (cch-- && (*psz++ = (CHAR) *pwsz++))
{
ASSERT(!(*pwsz & 0xff00));
cchRet++;
}
if (0 > (INT)cch)
psz[cchRet - 1] = 0;
return cchRet;
}
UINT AsciiToUnicode(LPCSTR psz, LPWSTR pwsz, UINT cch)
{
UINT cchRet = 1;
while (cch-- && (*pwsz++ = (WCHAR) *psz++))
{
cchRet++;
}
if (0 > (INT)cch)
pwsz[cchRet - 1] = 0;
return cchRet;
}
BOOL CFileSysItemString::_ResourceName(LPWSTR psz, DWORD cch, BOOL fIsMine)
{
BOOL fRet = FALSE;
if (_IsPersonalized())
{
int ids = _GetPersonalizedRes((int)_pidp->hid.wVersion, fIsMine);
if (ids != -1)
{
wnsprintf(psz, cch, L"@shell32.dll,-%d", ids);
fRet = TRUE;
}
}
else if (_pidfx && _pidfx->offResourceA)
{
SHAnsiToUnicode(UASTROFFA(_pidfx, _pidfx->offResourceA), psz, cch);
fRet = TRUE;
}
return fRet;
}
LPCWSTR CFileSysItemString::ResourceName()
{
if (!(_fsin & FSINAME_RESOURCE))
{
if (!_ResourceName(_sz, ARRAYSIZE(_sz), FALSE))
*_sz = 0;
}
_fsin = FSINAME_RESOURCE;
return _sz;
}
HRESULT CFileSysItemString::GetFindDataSimple(WIN32_FIND_DATAW *pfd)
{
ZeroMemory(pfd, sizeof(*pfd));
// Note that COFSFolder doesn't provide any times _but_ COFSFolder
DosDateTimeToFileTime(_pidf->dateModified, _pidf->timeModified, &pfd->ftLastWriteTime);
pfd->dwFileAttributes = _pidf->wAttrs;
pfd->nFileSizeLow = _pidf->dwSize;
StrCpyN(pfd->cFileName, FSName(), ARRAYSIZE(pfd->cFileName));
SHAnsiToUnicode(AltName(), pfd->cAlternateFileName, ARRAYSIZE(pfd->cAlternateFileName));
if (_pidfx)
{
DosDateTimeToFileTime(_pidfx->dsCreate.wDate, _pidfx->dsCreate.wTime, &pfd->ftCreationTime);
DosDateTimeToFileTime(_pidfx->dsAccess.wDate, _pidfx->dsAccess.wTime, &pfd->ftLastAccessTime);
}
return S_OK;
}
HRESULT CFileSysItemString::GetFindData(WIN32_FIND_DATAW *pfd)
{
HRESULT hr;
// if its a simple ID, there's no data in it
if (CFSFolder::_IsReal(_pidf))
{
hr = GetFindDataSimple(pfd);
}
else
{
ZeroMemory(pfd, sizeof(*pfd));
hr = E_INVALIDARG;
}
return hr;
}
typedef struct
{
int csidl;
int idsMine;
int idsTheirs;
} PERSONALIZEDNAME;
int CFileSysItemString::_GetPersonalizedRes(int csidl, BOOL fIsMine)
{
static const PERSONALIZEDNAME s_pnames[] =
{
{ CSIDL_PERSONAL, -1, IDS_LOCALGDN_FLD_THEIRDOCUMENTS},
{ CSIDL_MYPICTURES, IDS_LOCALGDN_FLD_MYPICTURES, IDS_LOCALGDN_FLD_THEIRPICTURES},
{ CSIDL_MYMUSIC, IDS_LOCALGDN_FLD_MYMUSIC, IDS_LOCALGDN_FLD_THEIRMUSIC},
{ CSIDL_MYVIDEO, IDS_LOCALGDN_FLD_MYVIDEOS, IDS_LOCALGDN_FLD_THEIRVIDEOS},
};
for (int i = 0; i < ARRAYSIZE(s_pnames); i++)
{
if (s_pnames[i].csidl == csidl)
{
return fIsMine ? s_pnames[i].idsMine : s_pnames[i].idsTheirs;
}
}
AssertMsg(FALSE, TEXT("Personalized Resource not in table"));
return -1;
}
TRIBIT CFileSysItem::_IsMine(CFSFolder *pfs)
{
TRIBIT tb = TRIBIT_UNDEFINED;
if (_IsPersonalized())
{
WCHAR szPath[MAX_PATH];
if (SUCCEEDED(SHGetFolderPath(NULL, (int)_pidp->hid.wVersion | CSIDL_FLAG_DONT_VERIFY, NULL, SHGFP_TYPE_CURRENT, szPath)))
{
WCHAR szThis[MAX_PATH];
if (SUCCEEDED(pfs->_GetPathForItem(_pidf, szThis)))
{
// if they match then its ours
// if they dont then it still personalized (theirs)
if (0 == StrCmpI(szThis, szPath))
tb = TRIBIT_TRUE;
else
{
tb = TRIBIT_FALSE;
}
}
}
}
return tb;
}
void CFileSysItemString::_FormatTheirs(LPCWSTR pszFormat)
{
WCHAR szOwner[UNLEN];
ualstrcpynW(szOwner, _pidp->szUserName, ARRAYSIZE(szOwner));
if (!IsOS(OS_DOMAINMEMBER))
{
// maybe we should do caching here???
// pfs->GetUserName(szOwner, szOwner, ARRAYSIZE(szOwner));
USER_INFO_10 *pui;
if (NERR_Success == NetUserGetInfo(NULL, szOwner, 10, (LPBYTE*)&pui))
{
LPTSTR pszName = (*pui->usri10_full_name) ? pui->usri10_full_name: pui->usri10_name;
if (*pszName)
{
StrCpyN(szOwner, pszName, ARRAYSIZE(szOwner));
}
NetApiBufferFree(pui);
}
}
wnsprintf(_sz, ARRAYSIZE(_sz), pszFormat, szOwner);
}
BOOL CFileSysItemString::_LoadResource(CFSFolder *pfs)
{
WCHAR szResource[MAX_PATH];
BOOL fRet = FALSE;
TRIBIT tbIsMine = _IsMine(pfs);
if (_ResourceName(szResource, ARRAYSIZE(szResource), tbIsMine == TRIBIT_TRUE))
{
DWORD cb = sizeof(_sz);
// first check the registry for overrides
if (S_OK == SKGetValueW(SHELLKEY_HKCU_SHELL, L"LocalizedResourceName", szResource, NULL, _sz, &cb)
&& *_sz)
{
fRet = TRUE;
}
else if (szResource[0] == TEXT('@'))
{
// it does caching for us
fRet = SUCCEEDED(SHLoadIndirectString(szResource, _sz, ARRAYSIZE(_sz), NULL));
// If the call fails, this means that the
// localized string belongs to a DLL that has been uninstalled.
// Just return the failure code so we act as if the MUI string
// isn't there. (Don't show the user "@DLLNAME.DLL,-5" as the
// name!)
if (fRet && tbIsMine == TRIBIT_FALSE)
{
// reuse szResource as the format string
StrCpyN(szResource, _sz, ARRAYSIZE(szResource));
_FormatTheirs(szResource);
}
}
}
if (fRet)
_fsin = FSINAME_UI;
ASSERT(!_fsin || *_sz);
return fRet;
}
BOOL CFileSysItem::CantRename(CFSFolder *pfs)
{
// BOOL fRest = SHRestricted(REST_NORENAMELOCALIZED);
if (_IsPersonalized())
{
if (!_IsMine(pfs))
return TRUE;
// return fRest;
}
else if (_pidfx && _pidfx->offResourceA)
{
// return fRest;
}
return FALSE;
}
UINT _CopyResource(LPWSTR pszSrc, LPSTR pszRes, UINT cchRes)
{
ASSERT(*pszSrc == L'@');
LPWSTR pszS32 = StrStrIW(pszSrc, L"shell32.dll");
if (pszS32)
{
*(--pszS32) = L'@';
pszSrc = pszS32;
}
return SHUnicodeToAnsi(pszSrc, pszRes, cchRes);
}
UINT CFSFolder::_GetItemExStrings(LPCIDFOLDER pidfSimpleParent, const WIN32_FIND_DATA *pfd, EXSTRINGS *pxs)
{
UINT cbRet = 0;
TCHAR szTemp[MAX_PATH];
if ((pfd->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
&& (pfd->dwFileAttributes & (FILE_ATTRIBUTE_SYSTEM | FILE_ATTRIBUTE_READONLY)))
{
if (SUCCEEDED(_GetPathForItem(pidfSimpleParent, szTemp)))
{
PathAppend(szTemp, pfd->cFileName);
if (GetFolderStringEx(szTemp, _pszNetProvider, L"DeleteOnCopy", SZ_CANBEUNICODE TEXT("Owner"), pxs->idp.szUserName, ARRAYSIZE(pxs->idp.szUserName)))
{
pxs->idp.hid.cb = sizeof(pxs->idp.hid) + CbFromCchW(lstrlenW(pxs->idp.szUserName) + 1);
pxs->idp.hid.id = IDLHID_PERSONALIZED;
WCHAR szFile[MAX_PATH];
if (GetFolderStringEx(szTemp, _pszNetProvider, L"DeleteOnCopy", SZ_CANBEUNICODE TEXT("PersonalizedName"), szFile, ARRAYSIZE(szFile)))
{
if (0 == StrCmpI(pfd->cFileName, szFile))
pxs->idp.hid.wVersion = (WORD) GetFolderInt(szTemp, _pszNetProvider, L"DeleteOnCopy", TEXT("Personalized"), -1);
}
}
else if (GetFolderString(szTemp, _pszNetProvider, szTemp, ARRAYSIZE(szTemp), TEXT("LocalizedResourceName")))
{
pxs->cbResource = _CopyResource(szTemp, pxs->szResource, ARRAYSIZE(pxs->szResource));
cbRet += pxs->cbResource;
}
}
}
else if (!pidfSimpleParent && _IsSelfSystemFolder())
{
if (_HasLocalizedFileNames() && SUCCEEDED(_GetPath(szTemp)))
{
if (GetFolderStringEx(szTemp, _pszNetProvider, TEXT("LocalizedFileNames"), pfd->cFileName, szTemp, ARRAYSIZE(szTemp)))
{
pxs->cbResource = _CopyResource(szTemp, pxs->szResource, ARRAYSIZE(pxs->szResource));
cbRet += pxs->cbResource;
}
}
}
return cbRet;
}
BOOL _PrepIDFName(const WIN32_FIND_DATA *pfd, LPSTR psz, DWORD cch, const void **ppvName, UINT *pcbName)
{
// the normal case:
// the altname should only not be filled in
// in the case of the name being a shortname (ASCII)
LPCWSTR pwsz = *pfd->cAlternateFileName && !(SHGetAppCompatFlags(ACF_FORCELFNIDLIST) & ACF_FORCELFNIDLIST)
? pfd->cAlternateFileName : pfd->cFileName;
if (DoesStringRoundTrip(pwsz, psz, cch))
{
*pcbName = lstrlenA(psz) + 1;
*ppvName = psz;
}
else
{
*pcbName = CbFromCchW(lstrlenW(pwsz) + 1);
*ppvName = pfd->cFileName;
}
return *ppvName != psz;
}
HRESULT CFSFolder::_CreateIDList(const WIN32_FIND_DATA *pfd, LPCIDFOLDER pidfSimpleParent, LPITEMIDLIST *ppidl)
{
// for the idf
CHAR szNameIDF[MAX_PATH];
UINT cbNameIDF;
const void *pvNameIDF;
BOOL fNeedsUnicode = _PrepIDFName(pfd, szNameIDF, ARRAYSIZE(szNameIDF), &pvNameIDF, &cbNameIDF);
UINT cbIDF = FIELD_OFFSET(IDFOLDER, cFileName) + cbNameIDF;
ASSERT(*((char *)pvNameIDF));
// for the idfx
UINT cbNameIDFX = CbFromCchW(lstrlenW(pfd->cFileName) + 1);
EXSTRINGS xs = {0};
UINT cbIDFX = sizeof(IDFOLDEREX) + cbNameIDFX + _GetItemExStrings(pidfSimpleParent, pfd, &xs);
// try to align these babies
cbIDF = ROUNDUP(cbIDF, 2);
cbIDFX = ROUNDUP(cbIDFX, 2);
// ILCreateWithHidden() fills in the cb values
LPIDFOLDER pidf = (LPIDFOLDER)ILCreateWithHidden(cbIDF, cbIDFX);
if (pidf)
{
// initialize the idf
// tag files > 4G so we can do a full find first when we need to know the real size
pidf->dwSize = pfd->nFileSizeHigh ? 0xFFFFFFFF : pfd->nFileSizeLow;
pidf->wAttrs = (WORD)pfd->dwFileAttributes;
// Since the idf entry is not aligned, we cannot just send the address
// of one of its members blindly into FileTimeToDosDateTime.
WORD date, time;
if (FileTimeToDosDateTime(&pfd->ftLastWriteTime, &date, &time))
{
*((UNALIGNED WORD *)&pidf->dateModified) = date;
*((UNALIGNED WORD *)&pidf->timeModified) = time;
}
// copy the short name
memcpy(pidf->cFileName, pvNameIDF, cbNameIDF);
// setup bFlags
pidf->bFlags = pfd->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY ? SHID_FS_DIRECTORY : SHID_FS_FILE;
if (CSIDL_COMMON_DESKTOPDIRECTORY == _csidlTrack)
pidf->bFlags |= SHID_FS_COMMONITEM;
if (fNeedsUnicode)
pidf->bFlags |= SHID_FS_UNICODE;
// now initialize the hidden idfx
PIDFOLDEREX pidfx = (PIDFOLDEREX) _ILSkip((LPITEMIDLIST)pidf, cbIDF);
pidfx->hid.id = IDLHID_IDFOLDEREX;
pidfx->hid.wVersion = IDFX_CV;
if (FileTimeToDosDateTime(&pfd->ftCreationTime, &date, &time))
{
pidfx->dsCreate.wDate = date;
pidfx->dsCreate.wTime = time;
}
if (FileTimeToDosDateTime(&pfd->ftLastAccessTime, &date, &time))
{
pidfx->dsAccess.wDate = date;
pidfx->dsAccess.wTime = time;
}
// append the strings
pidfx->offNameW = (USHORT) sizeof(IDFOLDEREX);
ualstrcpyW(UASTROFFW(pidfx, pidfx->offNameW), pfd->cFileName);
USHORT offNext = (USHORT) sizeof(IDFOLDEREX) + cbNameIDFX;
if (xs.cbResource)
{
pidfx->offResourceA = offNext;
ualstrcpyA(UASTROFFA(pidfx, pidfx->offResourceA), xs.szResource);
// offNext += (USHORT) xs.cbResource; if we have more offsets...
}
pidf = _MarkAsJunction(pidfSimpleParent, pidf, pfd->cFileName);
if (pidf && xs.idp.hid.cb)
pidf = (LPIDFOLDER) ILAppendHiddenID((LPITEMIDLIST)pidf, &xs.idp.hid);
}
*ppidl = (LPITEMIDLIST)pidf;
return *ppidl != NULL ? S_OK : E_OUTOFMEMORY;
}
BOOL _ValidPathSegment(LPCTSTR pszSegment)
{
if (*pszSegment && !PathIsDotOrDotDot(pszSegment))
{
for (LPCTSTR psz = pszSegment; *psz; psz = CharNext(psz))
{
if (!PathIsValidChar(*psz, PIVC_LFN_NAME))
return FALSE;
}
return TRUE;
}
return FALSE;
}
// used to parse up file path like strings:
// "folder\folder\file.txt"
// "file.txt"
//
// in/out:
// *ppszIn in: pointer to start of the buffer,
// output: advanced to next location, NULL on last segment
// out:
// *pszSegment NULL if nothing left
//
// returns:
// S_OK got a segment
// S_FALSE loop done, *pszSegment emtpy
// E_INVALIDARG invalid input "", "\foo", "\\foo", "foo\\bar", "?<>*" chars in seg
HRESULT _NextSegment(LPCWSTR *ppszIn, LPTSTR pszSegment, UINT cchSegment, BOOL bValidate)
{
HRESULT hr;
*pszSegment = 0;
if (*ppszIn)
{
// WARNING! Do not use StrPBrkW(*ppszIn, L"\\/"), because
// Trident passes fully-qualified URLs to
// SHGetFileInfo(USEFILEATTRIBUTES) and relies on the fact that
// we won't choke on the embedded "//" in "http://".
LPWSTR pszSlash = StrChrW(*ppszIn, L'\\');
if (pszSlash)
{
if (pszSlash > *ppszIn) // make sure well formed (no dbl slashes)
{
OleStrToStrN(pszSegment, cchSegment, *ppszIn, (int)(pszSlash - *ppszIn));
// make sure that there is another segment to return
if (!*(++pszSlash))
pszSlash = NULL;
hr = S_OK;
}
else
{
pszSlash = NULL;
hr = E_INVALIDARG; // bad input
}
}
else
{
SHUnicodeToTChar(*ppszIn, pszSegment, cchSegment);
hr = S_OK;
}
*ppszIn = pszSlash;
if (hr == S_OK && bValidate && !_ValidPathSegment(pszSegment))
{
*pszSegment = 0;
hr = E_INVALIDARG;
}
}
else
hr = S_FALSE; // done with loop
return hr;
}
// this makes a fake wfd and then uses the normal
// FillIDFolder as if it were a real found path.
HRESULT CFSFolder::_ParseSimple(LPCWSTR pszPath, const WIN32_FIND_DATA *pfdLast, LPITEMIDLIST *ppidl)
{
WIN32_FIND_DATA wfd = {0};
HRESULT hr = S_OK;
*ppidl = NULL;
ASSERT(*pszPath);
while (SUCCEEDED(hr) && (S_OK == (hr = _NextSegment((LPCWSTR *)&pszPath, wfd.cFileName, ARRAYSIZE(wfd.cFileName), FALSE))))
{
LPITEMIDLIST pidl;
if (pszPath)
{
// internal componets must be folders
wfd.dwFileAttributes = FILE_ATTRIBUTE_DIRECTORY;
}
else
{
// last segment takes the find data from that passed in
// copy everything except the cFileName field
memcpy(&wfd, pfdLast, FIELD_OFFSET(WIN32_FIND_DATA, cFileName));
lstrcpyn(wfd.cAlternateFileName, pfdLast->cAlternateFileName, ARRAYSIZE(wfd.cAlternateFileName));
}
hr = _CreateIDList(&wfd, (LPCIDFOLDER)*ppidl, &pidl);
if (SUCCEEDED(hr))
hr = SHILAppend(pidl, ppidl);
}
if (FAILED(hr))
{
if (*ppidl)
{
ILFree(*ppidl);
*ppidl = NULL;
}
}
else
hr = S_OK; // pin all success to S_OK
return hr;
}
BOOL IsAllWhiteSpace(LPCTSTR pszString)
{
while (*pszString)
{
if ((TEXT(' ') == *pszString) ||
(TEXT('\t') == *pszString))
{
pszString++; // keep walking the string
}
else
{
return FALSE; // something other than a space or tab, done
}
}
return TRUE; // made it through the loop, just spaces or tabs in this string
}
HRESULT _CheckPortName(LPCTSTR pszName)
{
if (PathIsInvalid(pszName))
return HRESULT_FROM_WIN32(ERROR_BAD_DEVICE);
else
return S_OK;
}
class CFindFirstWithTimeout
{
public:
CFindFirstWithTimeout(LPCTSTR pszPath, DWORD dwTicksToAllow);
HRESULT FindFirstWithTimeout(WIN32_FIND_DATA *pfd);
ULONG AddRef();
ULONG Release();
private:
static DWORD WINAPI _FindFistThreadProc(void *pv);
LONG _cRef;
DWORD _dwTicksToAllow;
TCHAR _szPath[MAX_PATH];
WIN32_FIND_DATA _fd;
};
CFindFirstWithTimeout::CFindFirstWithTimeout(LPCTSTR pszPath, DWORD dwTicksToAllow) : _cRef(1), _dwTicksToAllow(dwTicksToAllow)
{
lstrcpyn(_szPath, pszPath, ARRAYSIZE(_szPath));
}
ULONG CFindFirstWithTimeout::AddRef()
{
return InterlockedIncrement(&_cRef);
}
ULONG CFindFirstWithTimeout::Release()
{
if (InterlockedDecrement(&_cRef))
return _cRef;
delete this;
return 0;
}
DWORD CFindFirstWithTimeout::_FindFistThreadProc(void *pv)
{
CFindFirstWithTimeout *pffwt = (CFindFirstWithTimeout *)pv;
HRESULT hr = SHFindFirstFileRetry(NULL, NULL, pffwt->_szPath, &pffwt->_fd, NULL, SHPPFW_NONE);
pffwt->Release();
return hr; // retrieved via GetExitCodeThread()
}
HRESULT CFindFirstWithTimeout::FindFirstWithTimeout(WIN32_FIND_DATA *pfd)
{
HRESULT hr;
AddRef(); // ref for the thread
DWORD dwID;
HANDLE hThread = CreateThread(NULL, 0, _FindFistThreadProc, this, 0, &dwID);
if (hThread)
{
// assume timeout...
hr = HRESULT_FROM_WIN32(ERROR_TIMEOUT); // timeout return value
if (WAIT_OBJECT_0 == WaitForSingleObject(hThread, _dwTicksToAllow))
{
// thread finished with an HRESULT for us
DWORD dw;
if (GetExitCodeThread(hThread, &dw))
{
*pfd = _fd;
hr = dw; // HRESULT returned by _FindFistThreadProc
}
}
CloseHandle(hThread);
}
else
{
hr = E_OUTOFMEMORY;
Release(); // thread create failed, remove that ref
}
return hr;
}
HRESULT SHFindFirstFileWithTimeout(LPCTSTR pszPath, DWORD dwTicksToAllow, WIN32_FIND_DATA *pfd)
{
HRESULT hr;
CFindFirstWithTimeout *pffwt = new CFindFirstWithTimeout(pszPath, dwTicksToAllow);
if (pffwt)
{
hr = pffwt->FindFirstWithTimeout(pfd);
pffwt->Release();
}
else
{
hr = E_OUTOFMEMORY;
}
return hr;
}
HRESULT CFSFolder::_FindDataFromName(LPCTSTR pszName, DWORD dwAttribs, IBindCtx *pbc, WIN32_FIND_DATA **ppfd)
{
*ppfd = NULL;
HRESULT hr = _CheckPortName(pszName);
if (SUCCEEDED(hr))
{
hr = SHLocalAlloc(sizeof(**ppfd), ppfd);
if (SUCCEEDED(hr))
{
if (-1 == dwAttribs)
{
TCHAR szPath[MAX_PATH];
hr = _GetPath(szPath);
if (SUCCEEDED(hr))
{
PathAppend(szPath, pszName);
DWORD dwTicksToAllow;
if (SUCCEEDED(BindCtx_GetTimeoutDelta(pbc, &dwTicksToAllow)) && PathIsNetworkPath(szPath))
{
hr = SHFindFirstFileWithTimeout(szPath, dwTicksToAllow, *ppfd);
}
else
{
hr = SHFindFirstFileRetry(NULL, NULL, szPath, *ppfd, NULL, SHPPFW_NONE);
}
}
}
else
{
// make a simple one up
StrCpyN((*ppfd)->cFileName, pszName, ARRAYSIZE((*ppfd)->cFileName));
(*ppfd)->dwFileAttributes = dwAttribs;
}
if (FAILED(hr))
{
LocalFree(*ppfd);
*ppfd = NULL;
}
}
}
ASSERT(SUCCEEDED(hr) ? NULL != *ppfd : NULL == *ppfd);
return hr;
}
//
// This function returns a relative pidl for the specified file/folder
//
HRESULT CFSFolder::_CreateIDListFromName(LPCTSTR pszName, DWORD dwAttribs, IBindCtx *pbc, LPITEMIDLIST *ppidl)
{
WIN32_FIND_DATA *pfd;
HRESULT hr = _FindDataFromName(pszName, dwAttribs, pbc, &pfd);
if (SUCCEEDED(hr))
{
hr = _CreateIDList(pfd, NULL, ppidl);
LocalFree(pfd);
}
else
*ppidl = NULL;
return hr;
}
// used to detect if a name is a folder. this is used in the case that the
// security for this folders parent is set so you can't enum it's contents
BOOL CFSFolder::_CanSeeInThere(LPCTSTR pszName)
{
TCHAR szPath[MAX_PATH];
if (SUCCEEDED(_GetPath(szPath)))
{
HANDLE hfind;
WIN32_FIND_DATA fd;
PathAppend(szPath, pszName);
PathAppend(szPath, TEXT("*.*"));
hfind = FindFirstFile(szPath, &fd);
if (hfind != INVALID_HANDLE_VALUE)
FindClose(hfind);
return hfind != INVALID_HANDLE_VALUE;
}
return FALSE;
}
HRESULT CFSFolder::v_InternalQueryInterface(REFIID riid, void **ppv)
{
static const QITAB qit[] = {
QITABENTMULTI(CFSFolder, IShellFolder, IShellFolder2),
QITABENT(CFSFolder, IShellFolder2),
QITABENT(CFSFolder, IShellIconOverlay),
QITABENT(CFSFolder, IShellIcon),
QITABENTMULTI(CFSFolder, IPersist, IPersistFolder3),
QITABENTMULTI(CFSFolder, IPersistFolder, IPersistFolder3),
QITABENTMULTI(CFSFolder, IPersistFolder2, IPersistFolder3),
QITABENT(CFSFolder, IPersistFolder3),
QITABENT(CFSFolder, IStorage),
QITABENT(CFSFolder, IPropertySetStorage),
QITABENT(CFSFolder, IItemNameLimits),
QITABENT(CFSFolder, IContextMenuCB),
QITABENT(CFSFolder, ISetFolderEnumRestriction),
QITABENT(CFSFolder, IOleCommandTarget),
{ 0 },
};
HRESULT hr = QISearch(this, qit, riid, ppv);
if (FAILED(hr))
{
if (IsEqualIID(IID_INeedRealCFSFolder, riid))
{
*ppv = this; // not ref counted
hr = S_OK;
}
else if (IsEqualIID(riid, IID_IPersistFreeThreadedObject))
{
if (_GetInner() == _GetOuter()) // not aggregated
{
hr = QueryInterface(IID_IPersist, ppv);
}
else
{
hr = E_NOINTERFACE;
}
}
}
return hr;
}
// briefcase and file system folder call to reset data
HRESULT CFSFolder::_Reset()
{
_DestroyColHandlers();
if (_pidl)
{
ILFree(_pidl);
_pidl = NULL;
}
if (_pidlTarget)
{
ILFree(_pidlTarget);
_pidlTarget = NULL;
}
if (_pszPath)
{
LocalFree(_pszPath);
_pszPath = NULL;
}
if (_pszNetProvider)
{
LocalFree(_pszNetProvider);
_pszNetProvider = NULL;
}
_csidl = -1;
_dwAttributes = -1;
_csidlTrack = -1;
ATOMICRELEASE(_pstg);
return S_OK;
}
#define INVALID_PATHSPEED (-100)
CFSFolder::CFSFolder(IUnknown *punkOuter) : CAggregatedUnknown(punkOuter)
{
_csidl = -1;
_iFolderIcon = -1;
_dwAttributes = -1;
_csidlTrack = -1;
_nFolderType = FVCBFT_DOCUMENTS;
_bSlowPath = INVALID_PATHSPEED; // some non-common value
// Note: BOOL is not bool
_tbOfflineCSC = TRIBIT_UNDEFINED;
DllAddRef();
}
CFSFolder::~CFSFolder()
{
_Reset();
DllRelease();
}
// we need to fail relative type paths since we use PathCombine
// and we don't want that and the Win32 APIs to give us relative path behavior
// ShellExecute() depends on this so it falls back and resolves the relative paths itself
HRESULT CFSFolder::ParseDisplayName(HWND hwnd, IBindCtx *pbc, WCHAR *pszName, ULONG *pchEaten,
LPITEMIDLIST *ppidl, DWORD *pdwAttributes)
{
HRESULT hr;
WIN32_FIND_DATA *pfd;
if (!ppidl)
return E_INVALIDARG;
*ppidl = NULL; // assume error
if (pszName == NULL)
return E_INVALIDARG;
if (S_OK == SHIsFileSysBindCtx(pbc, &pfd))
{
hr = _ParseSimple(pszName, pfd, ppidl);
if (SUCCEEDED(hr) && pdwAttributes && *pdwAttributes)
{
// while strictly not a legit thing to do here, we
// pass the last IDList because 1) this is a simple IDList
// 2) we hope that callers don't ask for bits that
// require a full path to be valid inside the impl of
// ::GetAttributesOf()
LPCITEMIDLIST pidlLast = ILFindLastID(*ppidl);
GetAttributesOf(1, &pidlLast, pdwAttributes);
}
LocalFree(pfd);
}
else
{
DWORD cchNext = lstrlen(pszName) + 1;
WCHAR *pszNext = (WCHAR *)alloca(CbFromCchW(cchNext));
hr = _NextSegment((LPCWSTR *)&pszName, pszNext, cchNext, TRUE);
if (SUCCEEDED(hr))
{
hr = _CreateIDListFromName(pszNext, -1, pbc, ppidl);
if (hr == HRESULT_FROM_WIN32(ERROR_ACCESS_DENIED))
{
// security "List folder contents" may be disabled for
// this items parent. so see if this is really there
if (pszName || _CanSeeInThere(pszNext))
{
hr = _CreateIDListFromName(pszNext, FILE_ATTRIBUTE_DIRECTORY, pbc, ppidl);
}
}
else if (((hr == HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND)) || (hr == HRESULT_FROM_WIN32(ERROR_PATH_NOT_FOUND))) &&
(pszName == NULL) &&
(BindCtx_GetMode(pbc, 0) & STGM_CREATE) &&
!_fDontForceCreate)
{
// create a pidl to something that doesnt exist.
hr = _CreateIDListFromName(pszNext, FILE_ATTRIBUTE_NORMAL, pbc, ppidl);
}
if (SUCCEEDED(hr))
{
if (pszName) // more stuff to parse?
{
IShellFolder *psfFolder;
hr = BindToObject(*ppidl, pbc, IID_PPV_ARG(IShellFolder, &psfFolder));
if (SUCCEEDED(hr))
{
ULONG chEaten;
LPITEMIDLIST pidlNext;
hr = psfFolder->ParseDisplayName(hwnd, pbc,
pszName, &chEaten, &pidlNext, pdwAttributes);
if (SUCCEEDED(hr))
{
hr = SHILAppend(pidlNext, ppidl);
}
psfFolder->Release();
}
}
else
{
if (pdwAttributes && *pdwAttributes)
GetAttributesOf(1, (LPCITEMIDLIST *)ppidl, pdwAttributes);
}
}
}
}
if (FAILED(hr) && *ppidl)
{
// This is needed if psfFolder->ParseDisplayName() or BindToObject()
// fails because the pidl is already allocated.
ILFree(*ppidl);
*ppidl = NULL;
}
ASSERT(SUCCEEDED(hr) ? (*ppidl != NULL) : (*ppidl == NULL));
// display this only as a warning, this can get hit during mergfldr or IStorage::Create probes
if (FAILED(hr))
TraceMsg(TF_WARNING, "CFSFolder::ParseDisplayName(), hr:%x %ls", hr, pszName);
return hr;
}
STDAPI InitFileFolderClassNames(void)
{
if (g_szFileTemplate[0] == 0) // test last one to avoid race
{
LoadString(HINST_THISDLL, IDS_FOLDERTYPENAME, g_szFolderTypeName, ARRAYSIZE(g_szFolderTypeName));
LoadString(HINST_THISDLL, IDS_FILETYPENAME, g_szFileTypeName, ARRAYSIZE(g_szFileTypeName));
LoadString(HINST_THISDLL, IDS_EXTTYPETEMPLATE, g_szFileTemplate, ARRAYSIZE(g_szFileTemplate));
}
return S_OK;
}
HRESULT CFSFolder::EnumObjects(HWND hwnd, DWORD grfFlags, IEnumIDList **ppenum)
{
InitFileFolderClassNames();
grfFlags |= _dwEnumRequired;
grfFlags &= ~_dwEnumForbidden;
return CFSFolder_CreateEnum(this, hwnd, grfFlags, ppenum);
}
HRESULT CFSFolder::BindToObject(LPCITEMIDLIST pidl, LPBC pbc, REFIID riid, void **ppv)
{
// MIL 117282 - Enroute Imaging QuickStitch depends on pre-Jan'97 behavior of us
// *not* nulling ppv out on !_IsValidID(pidl). (They pass in a perfectly valid
// IShellFolder* interfacing asking for IID_IShellFolder on the empty PIDL.)
//
if (!(SHGetAppCompatFlags(ACF_WIN95BINDTOOBJECT) & ACF_WIN95BINDTOOBJECT))
*ppv = NULL;
HRESULT hr;
LPCIDFOLDER pidf = _IsValidID(pidl);
if (pidf)
{
LPCITEMIDLIST pidlRight;
LPIDFOLDER pidfBind;
hr = _GetJunctionForBind(pidf, &pidfBind, &pidlRight);
if (SUCCEEDED(hr))
{
if (hr == S_OK)
{
IShellFolder *psfJunction;
hr = _Bind(pbc, pidfBind, IID_PPV_ARG(IShellFolder, &psfJunction));
if (SUCCEEDED(hr))
{
// now bind to the stuff below the junction point
hr = psfJunction->BindToObject(pidlRight, pbc, riid, ppv);
psfJunction->Release();
}
ILFree((LPITEMIDLIST)pidfBind);
}
else
{
ASSERT(pidfBind == NULL);
hr = _Bind(pbc, pidf, riid, ppv);
}
}
}
else
{
hr = E_INVALIDARG;
TraceMsg(TF_WARNING, "CFSFolder::BindToObject(), hr:%x bad PIDL %s", hr, DumpPidl(pidl));
}
return hr;
}
HRESULT CFSFolder::BindToStorage(LPCITEMIDLIST pidl, LPBC pbc, REFIID riid, void **ppv)
{
return BindToObject(pidl, pbc, riid, ppv);
}
HRESULT CFSFolder::_CheckDriveRestriction(HWND hwnd, REFIID riid)
{
HRESULT hr = S_OK;
DWORD dwRest = SHRestricted(REST_NOVIEWONDRIVE);
if (dwRest)
{
TCHAR szPath[MAX_PATH];
hr = _GetPath(szPath);
if (SUCCEEDED(hr))
{
int iDrive = PathGetDriveNumber(szPath);
if (iDrive != -1)
{
// is the drive restricted
if (dwRest & (1 << iDrive))
{
// don't show the error message on droptarget -- just fail silently
if (hwnd && !IsEqualIID(riid, IID_IDropTarget))
{
ShellMessageBox(HINST_THISDLL, hwnd, MAKEINTRESOURCE(IDS_RESTRICTIONS),
MAKEINTRESOURCE(IDS_RESTRICTIONSTITLE), MB_OK | MB_ICONSTOP);
hr = HRESULT_FROM_WIN32(ERROR_CANCELLED); // user saw the error
}
else
hr = E_ACCESSDENIED;
}
}
}
}
return hr;
}
HRESULT CFSFolder::_CreateUIHandler(REFIID riid, void **ppv)
{
HRESULT hr;
// Cache the view CLSID if not cached.
if (!_fCachedCLSID)
{
if (_IsSelfSystemFolder())
{
TCHAR szPath[MAX_PATH];
if (SUCCEEDED(_GetPath(szPath)))
_fHasCLSID = GetFolderGUID(szPath, _pszNetProvider, &_clsidView, TEXT("UICLSID"));
_fCachedCLSID = TRUE;
}
}
// Use the handler if it exists
if (_fHasCLSID)
{
IPersistFolder *ppf;
hr = SHExtCoCreateInstance(NULL, &_clsidView, NULL, IID_PPV_ARG(IPersistFolder, &ppf));
if (SUCCEEDED(hr))
{
hr = ppf->Initialize(_pidl);
if (FAILED(hr) && _pidlTarget)
{
// It may have failed because the _pidl is an alias (not a file folder). if so try
// again with _pidlTarget (that will be a file system folder)
// this was required for the Fonts FolderShortcut in the ControlPanel (stephstm)
hr = ppf->Initialize(_pidlTarget);
}
if (SUCCEEDED(hr))
hr = ppf->QueryInterface(riid, ppv);
ppf->Release();
}
}
else
hr = E_FAIL; // no handler
return hr;
}
HRESULT CFSFolder::CreateViewObject(HWND hwnd, REFIID riid, void **ppv)
{
HRESULT hr;
*ppv = NULL;
if (IsEqualIID(riid, IID_IShellView) ||
IsEqualIID(riid, IID_IDropTarget))
{
hr = _CheckDriveRestriction(hwnd, riid);
if (SUCCEEDED(hr))
{
hr = _CreateUIHandler(riid, ppv);
if (FAILED(hr))
{
if (IsEqualIID(riid, IID_IDropTarget))
{
hr = CFSDropTarget_CreateInstance(this, hwnd, (IDropTarget **)ppv);
}
else
{
SFV_CREATE csfv = { sizeof(csfv), 0 };
hr = QueryInterface(IID_PPV_ARG(IShellFolder, &csfv.pshf));
if (SUCCEEDED(hr))
{
CFSFolderCallback_Create(this, &csfv.psfvcb);
hr = SHCreateShellFolderView(&csfv, (IShellView **)ppv);
if (csfv.psfvcb)
csfv.psfvcb->Release();
csfv.pshf->Release();
}
}
}
}
}
else if (IsEqualIID(riid, IID_IContextMenu))
{
// do background menu.
IShellFolder *psfToPass; // May be an Aggregate...
hr = QueryInterface(IID_PPV_ARG(IShellFolder, &psfToPass));
if (SUCCEEDED(hr))
{
LPCITEMIDLIST pidlMenuTarget = (_pidlTarget ? _pidlTarget : _pidl);
HKEY hkNoFiles;
RegOpenKey(HKEY_CLASSES_ROOT, TEXT("Directory\\Background"), &hkNoFiles);
IContextMenuCB *pcmcb = new CDefBackgroundMenuCB(pidlMenuTarget);
if (pcmcb)
{
hr = CDefFolderMenu_Create2Ex(pidlMenuTarget, hwnd, 0, NULL, psfToPass, pcmcb,
1, &hkNoFiles, (IContextMenu **)ppv);
pcmcb->Release();
}
psfToPass->Release();
if (hkNoFiles) // CDefFolderMenu_Create can handle NULL ok
RegCloseKey(hkNoFiles);
}
}
else if (IsEqualIID(riid, IID_ICategoryProvider))
{
HKEY hk = NULL;
RegOpenKey(HKEY_CLASSES_ROOT, TEXT("Directory\\shellex\\Category"), &hk);
hr = CCategoryProvider_Create(NULL, NULL, hk, NULL, this, riid, ppv);
if (hk)
RegCloseKey(hk);
}
else
{
ASSERT(*ppv == NULL);
hr = E_NOINTERFACE;
}
return hr;
}
#define LOGICALXOR(a, b) (((a) && !(b)) || (!(a) && (b)))
HRESULT CFSFolder::_CompareNames(LPCIDFOLDER pidf1, LPCIDFOLDER pidf2, BOOL fCaseSensitive, BOOL fCanonical)
{
CFileSysItemString fsi1(pidf1), fsi2(pidf2);
int iRet = StrCmpICW(fsi1.FSName(), fsi2.FSName());
if (iRet)
{
//
// additional check for identity using the 8.3 or AltName()
// if we are then the identity compare is better based off
// the AltName() which should be the same regardless of
// platform or CP.
//
if (LOGICALXOR(fsi1.IsLegacy(), fsi2.IsLegacy()))
{
if (lstrcmpiA(fsi1.AltName(), fsi2.AltName()) == 0)
iRet = 0;
}
if (iRet && !fCanonical)
{
// they are definitely not the same item
// Sort it based on the primary (long) name -- ignore case.
int iUI = StrCmpLogicalRestricted(fsi1.UIName(this), fsi2.UIName(this));
// if they are the same we might want case sensitive instead
if (iUI == 0 && fCaseSensitive)
{
iUI = ustrcmp(fsi1.UIName(this), fsi2.UIName(this));
}
if (iUI)
iRet = iUI;
}
}
return ResultFromShort((short)iRet);
}
HRESULT CFSFolder::_CompareFileTypes(LPCIDFOLDER pidf1, LPCIDFOLDER pidf2)
{
short result;
ENTERCRITICAL;
LPCTSTR psz1 = _GetTypeName(pidf1);
LPCTSTR psz2 = _GetTypeName(pidf2);
if (psz1 != psz2)
result = (short) ustrcmpi(psz1, psz2);
else
result = 0;
LEAVECRITICAL;
return ResultFromShort(result);
}
HRESULT CFSFolder::_CompareModifiedDate(LPCIDFOLDER pidf1, LPCIDFOLDER pidf2)
{
if ((DWORD)MAKELONG(pidf1->timeModified, pidf1->dateModified) <
(DWORD)MAKELONG(pidf2->timeModified, pidf2->dateModified))
{
return ResultFromShort(-1);
}
if ((DWORD)MAKELONG(pidf1->timeModified, pidf1->dateModified) >
(DWORD)MAKELONG(pidf2->timeModified, pidf2->dateModified))
{
return ResultFromShort(1);
}
return ResultFromShort(0);
}
HRESULT CFSFolder::_CompareCreateTime(LPCIDFOLDER pidf1, LPCIDFOLDER pidf2)
{
WIN32_FIND_DATAW wfd1, wfd2;
if (SUCCEEDED(_FindDataFromIDFolder(pidf1, &wfd1, FALSE)) && SUCCEEDED(_FindDataFromIDFolder(pidf2, &wfd2, FALSE)))
{
return ResultFromShort(CompareFileTime(&wfd1.ftCreationTime, &wfd2.ftCreationTime));
}
return ResultFromShort(0);
}
HRESULT CFSFolder::_CompareAccessTime(LPCIDFOLDER pidf1, LPCIDFOLDER pidf2)
{
WIN32_FIND_DATAW wfd1, wfd2;
if (SUCCEEDED(_FindDataFromIDFolder(pidf1, &wfd1, FALSE)) && SUCCEEDED(_FindDataFromIDFolder(pidf2, &wfd2, FALSE)))
{
return ResultFromShort(CompareFileTime(&wfd1.ftLastAccessTime, &wfd2.ftLastAccessTime));
}
return ResultFromShort(0);
}
HRESULT CFSFolder::_CompareAttribs(LPCIDFOLDER pidf1, LPCIDFOLDER pidf2)
{
const DWORD mask = FILE_ATTRIBUTE_READONLY |
FILE_ATTRIBUTE_HIDDEN |
FILE_ATTRIBUTE_SYSTEM |
FILE_ATTRIBUTE_ARCHIVE |
FILE_ATTRIBUTE_COMPRESSED|
FILE_ATTRIBUTE_ENCRYPTED |
FILE_ATTRIBUTE_OFFLINE;
// Calculate value of desired bits in attribute DWORD.
DWORD dwValueA = pidf1->wAttrs & mask;
DWORD dwValueB = pidf2->wAttrs & mask;
if (dwValueA != dwValueB)
{
// If the values are not equal,
// sort alphabetically based on string representation.
TCHAR szTempA[ARRAYSIZE(g_adwAttributeBits) + 1];
TCHAR szTempB[ARRAYSIZE(g_adwAttributeBits) + 1];
// Create attribute string for objects A and B.
BuildAttributeString(pidf1->wAttrs, szTempA, ARRAYSIZE(szTempA));
BuildAttributeString(pidf2->wAttrs, szTempB, ARRAYSIZE(szTempB));
// Compare attribute strings and determine difference.
int diff = ustrcmp(szTempA, szTempB);
if (diff > 0)
return ResultFromShort(1);
if (diff < 0)
return ResultFromShort(-1);
}
return ResultFromShort(0);
}
HRESULT CFSFolder::_CompareFolderness(LPCIDFOLDER pidf1, LPCIDFOLDER pidf2)
{
if (_IsReal(pidf1) && _IsReal(pidf2))
{
// Always put the folders first
if (_IsFolder(pidf1))
{
if (!_IsFolder(pidf2))
return ResultFromShort(-1);
}
else if (_IsFolder(pidf2))
return ResultFromShort(1);
}
return ResultFromShort(0); // same
}
HRESULT CFSFolder::CompareIDs(LPARAM lParam, LPCITEMIDLIST pidl1, LPCITEMIDLIST pidl2)
{
HRESULT hr;
LPCIDFOLDER pidf1 = _IsValidID(pidl1);
LPCIDFOLDER pidf2 = _IsValidID(pidl2);
if (!pidf1 || !pidf2)
{
// ASSERT(0); // we hit this often... who is the bad guy?
return E_INVALIDARG;
}
hr = _CompareFolderness(pidf1, pidf2);
if (hr != ResultFromShort(0))
return hr;
// SHCIDS_ALLFIELDS means to compare absolutely, ie: even if only filetimes
// are different, we rule file pidls to be different
int iColumn = ((DWORD)lParam & SHCIDS_COLUMNMASK);
switch (iColumn)
{
case FS_ICOL_SIZE:
{
ULONGLONG ull1 = _Size(pidf1);
ULONGLONG ull2 = _Size(pidf2);
if (ull1 < ull2)
return ResultFromShort(-1);
if (ull1 > ull2)
return ResultFromShort(1);
}
goto DoDefault;
case FS_ICOL_TYPE:
hr = _CompareFileTypes(pidf1, pidf2);
if (!hr)
goto DoDefault;
break;
case FS_ICOL_WRITETIME:
hr = _CompareModifiedDate(pidf1, pidf2);
if (!hr)
goto DoDefault;
break;
case FS_ICOL_NAME:
hr = _CompareNames(pidf1, pidf2, TRUE, BOOLIFY((SHCIDS_CANONICALONLY & lParam)));
if (hr == ResultFromShort(0))
{
// pidl1 is not simple
hr = ILCompareRelIDs(this, pidl1, pidl2, lParam);
goto DoDefaultModification;
}
break;
case FS_ICOL_CREATETIME:
hr = _CompareCreateTime(pidf1, pidf2);
if (!hr)
goto DoDefault;
break;
case FS_ICOL_ACCESSTIME:
hr = _CompareAccessTime(pidf1, pidf2);
if (!hr)
goto DoDefault;
break;
case FS_ICOL_ATTRIB:
hr = _CompareAttribs(pidf1, pidf2);
if (hr)
return hr;
goto DoDefault;
default:
iColumn -= ARRAYSIZE(c_fs_cols);
// 99/03/24 #295631 vtan: If not one of the standard columns then
// it's probably an extended column. Make a check for dates.
// 99/05/18 #341468 vtan: But also fail if it is an extended column
// because this implementation of IShellFolder::CompareIDs only
// understands basic file system columns and extended date columns.
if (iColumn >= 0)
{
hr = _CompareExtendedProp(iColumn, pidf1, pidf2);
if (hr)
return hr;
}
DoDefault:
hr = _CompareNames(pidf1, pidf2, FALSE, BOOLIFY((SHCIDS_CANONICALONLY & lParam)));
}
DoDefaultModification:
// If they were equal so far, but the caller wants SHCIDS_ALLFIELDS,
// then look closer.
if ((S_OK == hr) && (lParam & SHCIDS_ALLFIELDS))
{
// Must sort by modified date to pick up any file changes!
hr = _CompareModifiedDate(pidf1, pidf2);
if (!hr)
hr = _CompareAttribs(pidf1, pidf2);
}
return hr;
}
// test to see if this folder object is a net folder
BOOL CFSFolder::_IsNetPath()
{
BOOL fRemote = FALSE; // assume no
TCHAR szPath[MAX_PATH];
if (SUCCEEDED(_GetPath(szPath)))
{
fRemote = PathIsRemote(szPath);
}
return fRemote;
}
BOOL _CanRenameFolder(LPCTSTR pszFolder)
{
static const UINT c_aiNoRenameFolders[] = {
CSIDL_WINDOWS,
CSIDL_SYSTEM,
CSIDL_PROGRAM_FILES,
CSIDL_FONTS,
};
return !PathIsOneOf(pszFolder, c_aiNoRenameFolders, ARRAYSIZE(c_aiNoRenameFolders));
}
STDAPI_(LPCIDFOLDER) CFSFolder::_IsValidIDHack(LPCITEMIDLIST pidl)
{
if (!(ACF_NOVALIDATEFSIDS & SHGetAppCompatFlags(ACF_NOVALIDATEFSIDS)))
{
return _IsValidID(pidl);
}
else if (pidl)
{
// old behavior was that we didnt validate, we just
// looked for the last id and casted it
return (LPCIDFOLDER)ILFindLastID(pidl);
}
return NULL;
}
#define SFGAO_NOT_RECENT (SFGAO_CANRENAME | SFGAO_CANLINK)
#define SFGAO_REQ_MASK (SFGAO_HASSUBFOLDER | SFGAO_FILESYSANCESTOR | SFGAO_FOLDER | SFGAO_DROPTARGET | SFGAO_LINK | SFGAO_STREAM | SFGAO_STORAGEANCESTOR | SFGAO_STORAGE | SFGAO_READONLY)
HRESULT CFSFolder::GetAttributesOf(UINT cidl, LPCITEMIDLIST *apidl, ULONG *prgfInOut)
{
LPCIDFOLDER pidf = cidl ? _IsValidIDHack(apidl[0]) : NULL;
ULONG rgfOut = SFGAO_CANDELETE | SFGAO_CANMOVE | SFGAO_CANCOPY | SFGAO_HASPROPSHEET
| SFGAO_FILESYSTEM | SFGAO_DROPTARGET | SFGAO_CANRENAME | SFGAO_CANLINK;
ASSERT(cidl ? apidl[0] == ILFindLastID(apidl[0]) : TRUE); // should be single level IDs only
ASSERT(cidl ? BOOLFROMPTR(pidf) : TRUE); // should always be FS PIDLs
// the RECENT folder doesnt like items in it renamed or linked to.
if ((*prgfInOut & (SFGAO_NOT_RECENT)) && _IsCSIDL(CSIDL_RECENT))
{
rgfOut &= ~SFGAO_NOT_RECENT;
}
if (cidl == 1 && pidf)
{
CFileSysItemString fsi(pidf);
TCHAR szPath[MAX_PATH];
if (*prgfInOut & (SFGAO_VALIDATE | SFGAO_CANRENAME | SFGAO_REMOVABLE | SFGAO_SHARE))
{
HRESULT hr = _GetPathForItem(pidf, szPath);
if (FAILED(hr))
return hr;
}
else
{
// just in case -- if somebody else needs the path they should add to the check above
szPath[0] = 0;
}
if (*prgfInOut & SFGAO_VALIDATE)
{
DWORD dwAttribs;
if (!PathFileExistsAndAttributes(szPath, &dwAttribs))
return E_FAIL;
// Tell the extended columns to update when someone request validation of a pidl
// This allows a client of the shell folder who uses extended columns without a
// view to force an update on stale information (i.e. Start Menu with InfoTips)
// - lamadio 6.11.99
_bUpdateExtendedCols = TRUE;
// hackhack. if they pass in validate, we party into it and update
// the attribs
if (!IsBadWritePtr((void *)&pidf->wAttrs, sizeof(pidf->wAttrs)))
((LPIDFOLDER)pidf)->wAttrs = (WORD)dwAttribs;
}
if (*prgfInOut & SFGAO_COMPRESSED)
{
if (pidf->wAttrs & FILE_ATTRIBUTE_COMPRESSED)
{
rgfOut |= SFGAO_COMPRESSED;
}
}
if (*prgfInOut & SFGAO_ENCRYPTED)
{
if (pidf->wAttrs & FILE_ATTRIBUTE_ENCRYPTED)
{
rgfOut |= SFGAO_ENCRYPTED;
}
}
if (*prgfInOut & SFGAO_READONLY)
{
if ((pidf->wAttrs & FILE_ATTRIBUTE_READONLY) && !(pidf->wAttrs & FILE_ATTRIBUTE_DIRECTORY))
{
rgfOut |= SFGAO_READONLY;
}
}
if (*prgfInOut & SFGAO_HIDDEN)
{
if (pidf->wAttrs & FILE_ATTRIBUTE_HIDDEN)
{
rgfOut |= SFGAO_HIDDEN;
}
}
if (*prgfInOut & SFGAO_NONENUMERATED)
{
if (IsSuperHidden(pidf->wAttrs))
{
// mark superhidden as nonenumerated, IsSuperHidden checks current settings
rgfOut |= SFGAO_NONENUMERATED;
}
else if (pidf->wAttrs & FILE_ATTRIBUTE_HIDDEN)
{
// mark normal hidden as nonenumerated if necessary
SHELLSTATE ss;
SHGetSetSettings(&ss, SSF_SHOWALLOBJECTS, FALSE);
if (!ss.fShowAllObjects)
{
rgfOut |= SFGAO_NONENUMERATED;
}
}
}
if (_IsFolder(pidf))
{
rgfOut |= SFGAO_FOLDER | SFGAO_STORAGE | SFGAO_FILESYSANCESTOR | SFGAO_STORAGEANCESTOR;
if ((*prgfInOut & SFGAO_CANRENAME) && (fsi.CantRename(this) || !_CanRenameFolder(szPath)))
rgfOut &= ~SFGAO_CANRENAME;
if ((*prgfInOut & SFGAO_REMOVABLE) && PathIsRemovable(szPath))
{
rgfOut |= SFGAO_REMOVABLE;
}
if ((*prgfInOut & SFGAO_SHARE) && IsShared(szPath, FALSE))
{
rgfOut |= SFGAO_SHARE;
}
}
else
{
rgfOut |= SFGAO_STREAM;
}
if (*prgfInOut & SFGAO_LINK)
{
DWORD dwFlags = fsi.ClassFlags(FALSE);
if (dwFlags & SHCF_IS_LINK)
{
rgfOut |= SFGAO_LINK;
}
}
CLSID clsid;
if (fsi.GetJunctionClsid(&clsid, TRUE))
{
// NOTE: here we are always including SFGAO_FILESYSTEM. this was not the original
// shell behavior. but since these things will succeeded on SHGetPathFromIDList()
// it is the right thing to do. to filter out SFGAO_FOLDER things that might
// have files in them use SFGAO_FILESYSANCESTOR.
//
// clear out the things we want the extension to be able to optionally have
rgfOut &= ~(SFGAO_DROPTARGET | SFGAO_STORAGE | SFGAO_FILESYSANCESTOR | SFGAO_STORAGEANCESTOR);
// let folder shortcuts yank the folder bit too for bad apps.
if (IsEqualGUID(clsid, CLSID_FolderShortcut) &&
(SHGetAppCompatFlags(ACF_STRIPFOLDERBIT) & ACF_STRIPFOLDERBIT))
{
rgfOut &= ~SFGAO_FOLDER;
}
// and let him add some bits in
rgfOut |= SHGetAttributesFromCLSID2(&clsid, SFGAO_HASSUBFOLDER, SFGAO_REQ_MASK) & SFGAO_REQ_MASK;
// Mill #123708
// prevent zips, cabs and other files with SFGAO_FOLDER set
// from being treated like folders inside bad file open dialogs.
if (!(pidf->wAttrs & FILE_ATTRIBUTE_DIRECTORY) &&
(SHGetAppCompatFlags (ACF_STRIPFOLDERBIT) & ACF_STRIPFOLDERBIT))
{
rgfOut &= ~SFGAO_FOLDER;
}
// Check if this folder needs File System Ancestor bit
if ((rgfOut & SFGAO_FOLDER) && !(rgfOut & SFGAO_FILESYSANCESTOR)
&& SHGetObjectCompatFlags(NULL, &clsid) & OBJCOMPATF_NEEDSFILESYSANCESTOR)
{
rgfOut |= SFGAO_FILESYSANCESTOR;
}
}
// it can only have subfolders if we've first found it's a folder
if ((rgfOut & SFGAO_FOLDER) && (*prgfInOut & SFGAO_HASSUBFOLDER))
{
if (pidf->wAttrs & FILE_ATTRIBUTE_REPARSE_POINT)
{
rgfOut |= SFGAO_HASSUBFOLDER; // DFS junction, local mount point, assume sub folders
}
else if (_IsNetPath())
{
// it would be nice to not assume this. this messes up
// home net cases where we get the "+" wrong
rgfOut |= SFGAO_HASSUBFOLDER; // assume yes because these are slow
}
else if (!(rgfOut & SFGAO_HASSUBFOLDER))
{
IShellFolder *psf;
if (SUCCEEDED(_Bind(NULL, pidf, IID_PPV_ARG(IShellFolder, &psf))))
{
IEnumIDList *peunk;
if (S_OK == psf->EnumObjects(NULL, SHCONTF_FOLDERS, &peunk))
{
LPITEMIDLIST pidlT;
if (peunk->Next(1, &pidlT, NULL) == S_OK)
{
rgfOut |= SFGAO_HASSUBFOLDER;
SHFree(pidlT);
}
peunk->Release();
}
psf->Release();
}
}
}
if (*prgfInOut & SFGAO_GHOSTED)
{
if (pidf->wAttrs & FILE_ATTRIBUTE_HIDDEN)
rgfOut |= SFGAO_GHOSTED;
}
if ((*prgfInOut & SFGAO_BROWSABLE) &&
(_IsFile(pidf)) &&
(fsi.ClassFlags(FALSE) & SHCF_IS_BROWSABLE))
{
rgfOut |= SFGAO_BROWSABLE;
}
}
*prgfInOut = rgfOut;
return S_OK;
}
// load handler for an item based on the handler type:
// DropHandler, IconHandler, etc.
// in:
// pidf type of this object specifies the type of handler - can be multilevel
// pszHandlerType handler type name "DropTarget", may be NULL
// riid interface to talk on
// out:
// ppv output object
//
HRESULT CFSFolder::_LoadHandler(LPCIDFOLDER pidf, DWORD grfMode, LPCTSTR pszHandlerType, REFIID riid, void **ppv)
{
HRESULT hr = E_FAIL;
TCHAR szIID[40];
ASSERT(_FindJunctionNext(pidf) == NULL); // no extra non file sys goo please
*ppv = NULL;
// empty handler type, use the stringized IID as the handler name
if (NULL == pszHandlerType)
{
szIID[0] = 0;
SHStringFromGUID(riid, szIID, ARRAYSIZE(szIID));
pszHandlerType = szIID;
}
CFileSysItemString fsi(_FindLastID(pidf));
IAssociationArray *paa;
hr = fsi.AssocCreate(this, FALSE, IID_PPV_ARG(IAssociationArray, &paa));
if (SUCCEEDED(hr))
{
CSmartCoTaskMem<WCHAR> spszClsid;
hr = paa->QueryString(ASSOCELEM_MASK_QUERYNORMAL, AQNS_SHELLEX_HANDLER, pszHandlerType, &spszClsid);
if (SUCCEEDED(hr))
{
hr = _HandlerCreateInstance(pidf, spszClsid, grfMode, riid, ppv);
}
paa->Release();
}
return hr;
}
HRESULT CFSFolder::_HandlerCreateInstance(LPCIDFOLDER pidf, PCWSTR pszClsid, DWORD grfMode, REFIID riid, void **ppv)
{
IPersistFile *ppf;
HRESULT hr = SHExtCoCreateInstance(pszClsid, NULL, NULL, IID_PPV_ARG(IPersistFile, &ppf));
if (SUCCEEDED(hr))
{
WCHAR wszPath[MAX_PATH];
hr = _GetPathForItem(pidf, wszPath);
if (SUCCEEDED(hr))
{
hr = ppf->Load(wszPath, grfMode);
if (SUCCEEDED(hr))
{
hr = ppf->QueryInterface(riid, ppv);
}
}
ppf->Release();
}
return hr;
}
HRESULT CFSFolder::_CreateShimgvwExtractor(LPCIDFOLDER pidf, REFIID riid, void **ppv)
{
HRESULT hr = E_FAIL;
CFileSysItemString fsi(pidf);
if (fsi.IsShimgvwImage())
{
// cocreate CLSID_GdiThumbnailExtractor implemented in shimgvw.dll
hr = _HandlerCreateInstance(pidf, L"{3F30C968-480A-4C6C-862D-EFC0897BB84B}", STGM_READ, riid, ppv);
}
return hr;
}
int CFSFolder::_GetDefaultFolderIcon()
{
int iIcon = II_FOLDER;
UINT csidlFolder = _GetCSIDL();
// We're removing the icon distinction between per user and common folders.
switch (csidlFolder)
{
case CSIDL_STARTMENU:
case CSIDL_COMMON_STARTMENU:
case CSIDL_PROGRAMS:
case CSIDL_COMMON_PROGRAMS:
iIcon = II_STSPROGS;
break;
}
return iIcon;
}
DWORD CFSFolder::_Attributes()
{
if (_dwAttributes == -1)
{
TCHAR szPath[MAX_PATH];
if (SUCCEEDED(_GetPath(szPath)))
_dwAttributes = GetFileAttributes(szPath);
if (_dwAttributes == -1)
_dwAttributes = FILE_ATTRIBUTE_DIRECTORY; // assume this on failure
}
return _dwAttributes;
}
// non junction, but has the system or readonly bit (regular folder marked special for us)
BOOL CFSFolder::_IsSelfSystemFolder()
{
return (_Attributes() & (FILE_ATTRIBUTE_SYSTEM | FILE_ATTRIBUTE_READONLY));
}
// Determine if there is a LocalizedFileName section in our desktop.ini file
BOOL CFSFolder::_HasLocalizedFileNames()
{
if (_tbHasLocalizedFileNamesSection == TRIBIT_UNDEFINED)
{
TCHAR szPath[MAX_PATH];
TCHAR szName[MAX_PATH];
TCHAR szBuf[4];
_GetPath(szPath);
if (_GetIniPath(FALSE, szPath, _pszNetProvider, szName) &&
GetPrivateProfileSection(TEXT("LocalizedFileNames"), szBuf, sizeof(szBuf)/sizeof(TCHAR), szName) > 0)
_tbHasLocalizedFileNamesSection = TRIBIT_TRUE;
else
_tbHasLocalizedFileNamesSection = TRIBIT_FALSE;
}
return (_tbHasLocalizedFileNamesSection == TRIBIT_TRUE);
}
// This function creates a default IExtractIcon object for either
// a file or a junction point. We should not supposed to call this function
// for a non-junction point directory (we don't want to hit the disk!).
HRESULT CFSFolder::_CreateDefExtIcon(LPCIDFOLDER pidf, REFIID riid, void **ppxicon)
{
HRESULT hr = E_OUTOFMEMORY;
DWORD dwFlags;
// WARNING: don't replace this if-statement with _IsFolder(pidf))!!!
// otherwise all junctions (like briefcase) will get the Folder icon.
//
if (_IsFileFolder(pidf))
{
UINT iIcon = _GetDefaultFolderIcon();
UINT iIconOpen = II_FOLDEROPEN;
TCHAR szPath[MAX_PATH], szModule[MAX_PATH];
szModule[0] = 0;
if (_GetMountingPointInfo(pidf, szPath, ARRAYSIZE(szPath)))
{
// We want same icon for open and close moun point (kind of drive)
iIconOpen = iIcon = GetMountedVolumeIcon(szPath, szModule, ARRAYSIZE(szModule));
}
else if (_IsSystemFolder(pidf))
{
if (_GetFolderIconPath(pidf, szPath, ARRAYSIZE(szPath), &iIcon))
{
return SHCreateDefExtIcon(szPath, iIcon, iIcon, GIL_PERINSTANCE, II_FOLDER, riid, ppxicon);
}
}
return SHCreateDefExtIcon(szModule, iIcon, iIconOpen, GIL_PERCLASS, II_FOLDER, riid, ppxicon);
}
// not a folder, get IExtractIcon and extract it.
// (might be a ds folder)
CFileSysItemString fsi(pidf);
dwFlags = fsi.ClassFlags(TRUE);
if (dwFlags & SHCF_ICON_PERINSTANCE)
{
if (dwFlags & SHCF_HAS_ICONHANDLER)
{
IUnknown *punk;
hr = _LoadHandler(pidf, STGM_READ, TEXT("IconHandler"), IID_PPV_ARG(IUnknown, &punk));
if (SUCCEEDED(hr))
{
hr = punk->QueryInterface(riid, ppxicon);
punk->Release();
}
else
{
*ppxicon = NULL;
}
}
else
{
DWORD uid = _GetUID(pidf);
TCHAR szPath[MAX_PATH];
hr = _GetPathForItem(pidf, szPath);
if (SUCCEEDED(hr))
{
hr = SHCreateDefExtIcon(szPath, uid, uid, GIL_PERINSTANCE | GIL_NOTFILENAME, -1, riid, ppxicon);
}
}
}
else
{
UINT iIcon = (dwFlags & SHCF_ICON_INDEX);
if (II_FOLDER == iIcon)
{
iIcon = _GetDefaultFolderIcon();
}
hr = SHCreateDefExtIcon(c_szStar, iIcon, iIcon, GIL_PERCLASS | GIL_NOTFILENAME, -1, riid, ppxicon);
}
return hr;
}
DWORD CALLBACK CFSFolder::_PropertiesThread(void *pv)
{
PROPSTUFF * pps = (PROPSTUFF *)pv;
STGMEDIUM medium;
ULONG_PTR dwCookie = 0;
ActivateActCtx(NULL, &dwCookie);
LPIDA pida = DataObj_GetHIDA(pps->pdtobj, &medium);
if (pida)
{
LPITEMIDLIST pidl = IDA_ILClone(pida, 0);
if (pidl)
{
TCHAR szPath[MAX_PATH];
LPTSTR pszCaption;
HKEY rgKeys[MAX_ASSOC_KEYS] = {0};
DWORD cKeys = SHGetAssocKeysForIDList(pidl, rgKeys, ARRAYSIZE(rgKeys));
// REVIEW: psb?
pszCaption = SHGetCaption(medium.hGlobal);
SHOpenPropSheet(pszCaption, rgKeys, cKeys,
&CLSID_ShellFileDefExt, pps->pdtobj, NULL, pps->pStartPage);
if (pszCaption)
SHFree(pszCaption);
SHRegCloseKeys(rgKeys, cKeys);
if (SHGetPathFromIDList(pidl, szPath))
{
if (lstrcmpi(PathFindExtension(szPath), TEXT(".pif")) == 0)
{
DebugMsg(TF_FSTREE, TEXT("cSHCNRF_pt: DOS properties done, generating event."));
SHChangeNotify(SHCNE_UPDATEITEM, SHCNF_IDLIST, pidl, NULL);
}
}
ILFree(pidl);
}
HIDA_ReleaseStgMedium(pida, &medium);
}
return 0;
}
//
// Display a property sheet for a set of files.
// The data object supplied must provide the "Shell IDList Array"
// clipboard format.
// The dwFlags argument is provided for future expansion. It is
// currently unused.
//
STDAPI SHMultiFileProperties(IDataObject *pdtobj, DWORD dwFlags)
{
return SHLaunchPropSheet(CFSFolder::_PropertiesThread, pdtobj, 0, NULL, NULL);
}
HMENU FindMenuBySubMenuID(HMENU hmenu, UINT id, LPINT pIndex)
{
HMENU hmenuReturn = NULL;
MENUITEMINFO mii;
mii.cbSize = sizeof(mii);
mii.fMask = MIIM_ID;
mii.cch = 0; // just in case...
for (int cMax = GetMenuItemCount(hmenu) - 1 ; cMax >= 0 ; cMax--)
{
HMENU hmenuSub = GetSubMenu(hmenu, cMax);
if (hmenuSub && GetMenuItemInfo(hmenuSub, 0, TRUE, &mii))
{
if (mii.wID == id)
{
// found it!
hmenuReturn = hmenuSub;
break;
}
}
}
if (hmenuReturn && pIndex)
*pIndex = cMax;
return hmenuReturn;
}
void DeleteMenuBySubMenuID(HMENU hmenu, UINT id)
{
int i;
if (FindMenuBySubMenuID(hmenu, id, &i))
{
DeleteMenu(hmenu, i, MF_BYPOSITION);
}
}
// fMask is from CMIC_MASK_*
STDAPI CFSFolder_CreateLinks(HWND hwnd, IShellFolder *psf, IDataObject *pdtobj, LPCTSTR pszDir, DWORD fMask)
{
LPITEMIDLIST pidl;
HRESULT hr = SHGetIDListFromUnk(psf, &pidl);
if (SUCCEEDED(hr))
{
TCHAR szPath[MAX_PATH];
if (SHGetPathFromIDList(pidl, szPath))
{
UINT fCreateLinkFlags;
int cItems = DataObj_GetHIDACount(pdtobj);
LPITEMIDLIST *ppidl = (LPITEMIDLIST *)LocalAlloc(LPTR, sizeof(LPITEMIDLIST) * cItems);
// passing ppidl == NULL is correct in failure case
if ((pszDir == NULL) || (lstrcmpi(pszDir, szPath) == 0))
{
// create the link in the current folder
fCreateLinkFlags = SHCL_USETEMPLATE;
}
else
{
// this is a sys menu, ask to create on desktop
fCreateLinkFlags = SHCL_USETEMPLATE | SHCL_USEDESKTOP;
if (!(fMask & CMIC_MASK_FLAG_NO_UI))
{
fCreateLinkFlags |= SHCL_CONFIRM;
}
}
hr = SHCreateLinks(hwnd, szPath, pdtobj, fCreateLinkFlags, ppidl);
if (ppidl)
{
// select those objects;
HWND hwndSelect = ShellFolderViewWindow(hwnd);
// select the new links, but on the first one deselect all other selected things
for (int i = 0; i < cItems; i++)
{
if (ppidl[i])
{
SendMessage(hwndSelect, SVM_SELECTITEM,
i == 0 ? SVSI_SELECT | SVSI_ENSUREVISIBLE | SVSI_DESELECTOTHERS | SVSI_FOCUSED :
SVSI_SELECT,
(LPARAM)ILFindLastID(ppidl[i]));
ILFree(ppidl[i]);
}
}
LocalFree((HLOCAL)ppidl);
}
}
else
{
hr = E_FAIL;
}
ILFree(pidl);
}
return hr;
}
// Parameter to the "Delete" thread.
//
typedef struct {
IDataObject *pDataObj; // null on entry to thread proc
IStream *pstmDataObj; // marshalled data object
HWND hwndOwner;
UINT uFlags;
UINT fOptions;
} FSDELTHREADPARAM;
void FreeFSDELThreadParam(FSDELTHREADPARAM * pfsthp)
{
ATOMICRELEASE(pfsthp->pDataObj);
ATOMICRELEASE(pfsthp->pstmDataObj);
LocalFree(pfsthp);
}
DWORD CALLBACK FileDeleteThreadProc(void *pv)
{
FSDELTHREADPARAM *pfsthp = (FSDELTHREADPARAM *)pv;
CoGetInterfaceAndReleaseStream(pfsthp->pstmDataObj, IID_PPV_ARG(IDataObject, &pfsthp->pDataObj));
pfsthp->pstmDataObj = NULL;
if (pfsthp->pDataObj)
DeleteFilesInDataObject(pfsthp->hwndOwner, pfsthp->uFlags, pfsthp->pDataObj, pfsthp->fOptions);
FreeFSDELThreadParam(pfsthp);
return 0;
}
//
// IContextMenuCB
// right click context menu for items handler
//
// Returns:
// S_OK, if successfully processed.
// S_FALSE, if default code should be used.
//
STDMETHODIMP CFSFolder::CallBack(IShellFolder *psf, HWND hwnd, IDataObject *pdtobj, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
HRESULT hr = S_OK;
switch (uMsg)
{
case DFM_MERGECONTEXTMENU:
if (!(wParam & CMF_VERBSONLY))
{
LPQCMINFO pqcm = (LPQCMINFO)lParam;
// corel relies on the hard coded send to menu so we give them one
BOOL bCorelSuite7Hack = (SHGetAppCompatFlags(ACF_CONTEXTMENU) & ACF_CONTEXTMENU);
if (bCorelSuite7Hack)
{
CDefFolderMenu_MergeMenu(HINST_THISDLL, POPUP_FSVIEW_ITEM_COREL7_HACK, 0, pqcm);
}
}
break;
case DFM_GETHELPTEXT:
LoadStringA(HINST_THISDLL, LOWORD(wParam) + IDS_MH_FSIDM_FIRST, (LPSTR)lParam, HIWORD(wParam));;
break;
case DFM_GETHELPTEXTW:
LoadStringW(HINST_THISDLL, LOWORD(wParam) + IDS_MH_FSIDM_FIRST, (LPWSTR)lParam, HIWORD(wParam));;
break;
case DFM_INVOKECOMMANDEX:
{
DFMICS *pdfmics = (DFMICS *)lParam;
switch (wParam)
{
case DFM_CMD_DELETE:
// try not to do delete on the UI thread
// with System Restore it may be slow
//
// NOTE: we need to test to make sure this is acceptable as the data
// object may have come from a data object extension, for example a
// scrap file. but that is a very rare case (DataObj_CanGoAsync() will almost always
// return true).
hr = E_FAIL;
if ((pdfmics->fMask & CMIC_MASK_ASYNCOK) && DataObj_CanGoAsync(pdtobj))
{
FSDELTHREADPARAM *pfsthp;
hr = SHLocalAlloc(sizeof(*pfsthp), &pfsthp);
if (SUCCEEDED(hr))
{
hr = CoMarshalInterThreadInterfaceInStream(IID_IDataObject, pdtobj, &pfsthp->pstmDataObj);
if (SUCCEEDED(hr))
{
pfsthp->hwndOwner = hwnd;
pfsthp->uFlags = pdfmics->fMask;
// dont allow undo in the recent folder.
pfsthp->fOptions = _IsCSIDL(CSIDL_RECENT) ? SD_NOUNDO : 0;
// create another thread to avoid blocking the source thread.
if (!SHCreateThread(FileDeleteThreadProc, pfsthp, CTF_COINIT, NULL))
{
hr = E_FAIL;
}
}
if (FAILED(hr))
{
FreeFSDELThreadParam(pfsthp); // cleanup
}
}
}
if (S_OK != hr)
{
// could not go async, do it sync here
// dont allow undo in the recent folder.
hr = DeleteFilesInDataObject(hwnd, pdfmics->fMask, pdtobj,
_IsCSIDL(CSIDL_RECENT) ? SD_NOUNDO : 0);
}
break;
case DFM_CMD_LINK:
hr = CFSFolder_CreateLinks(hwnd, psf, pdtobj, (LPCTSTR)pdfmics->lParam, pdfmics->fMask);
break;
case DFM_CMD_PROPERTIES:
hr = SHLaunchPropSheet(_PropertiesThread, pdtobj, (LPCTSTR)pdfmics->lParam, NULL, _pidl);
break;
default:
// This is common menu items, use the default code.
hr = S_FALSE;
break;
}
}
break;
default:
hr = E_NOTIMPL;
break;
}
return hr;
}
HRESULT CFSFolder::_CreateContextMenu(HWND hwnd, LPCIDFOLDER pidf, LPCITEMIDLIST *apidl, UINT cidl, IContextMenu **ppcm)
{
// we need a key for each
// 1. UserCustomized
// 2. default Progid
// 3. SFA\.ext
// 4. SFA\PerceivedType
// 5. * or Folder
// 6. AllFileSystemObjects
// (?? 7. maybe pszProvider ??)
IAssociationArray *paa;
CFileSysItemString fsi(pidf);
fsi.AssocCreate(this, TRUE, IID_PPV_ARG(IAssociationArray, &paa));
IShellFolder *psfToPass; // May be an Aggregate...
HRESULT hr = QueryInterface(IID_PPV_ARG(IShellFolder, &psfToPass));
if (SUCCEEDED(hr))
{
DEFCONTEXTMENU dcm = {
hwnd,
SAFECAST(this, IContextMenuCB *),
_pidl,
psfToPass,
cidl,
apidl,
paa,
0,
NULL};
hr = CreateDefaultContextMenu(&dcm, ppcm);
psfToPass->Release();
}
if (paa)
paa->Release();
return hr;
}
HRESULT CFileFolderIconManager_Create(IShellFolder *psf, LPCITEMIDLIST pidl, REFIID riid, void **ppv);
HRESULT CFSFolder::GetUIObjectOf(HWND hwnd, UINT cidl, LPCITEMIDLIST *apidl,
REFIID riid, UINT *prgfInOut, void **ppv)
{
HRESULT hr = E_INVALIDARG;
LPCIDFOLDER pidf = cidl ? _IsValidID(apidl[0]) : NULL;
*ppv = NULL;
if (pidf &&
(IsEqualIID(riid, IID_IExtractIconA) || IsEqualIID(riid, IID_IExtractIconW)))
{
hr = _CreateDefExtIcon(pidf, riid, ppv);
}
else if (IsEqualIID(riid, IID_IContextMenu) && pidf)
{
hr = _CreateContextMenu(hwnd, pidf, apidl, cidl, (IContextMenu **)ppv);
}
else if (IsEqualIID(riid, IID_IDataObject) && cidl)
{
IDataObject *pdtInner = NULL;
if ((cidl == 1) && pidf)
{
_LoadHandler(pidf, STGM_READ, TEXT("DataHandler"), IID_PPV_ARG(IDataObject, &pdtInner));
}
hr = SHCreateFileDataObject(_pidl, cidl, apidl, pdtInner, (IDataObject **)ppv);
if (pdtInner)
pdtInner->Release();
}
else if (IsEqualIID(riid, IID_IDropTarget) && pidf)
{
CLSID clsid;
if (_IsFolder(pidf) || (_GetJunctionClsid(pidf, &clsid) && !SHQueryShellFolderValue(&clsid, L"UseDropHandler")))
{
IShellFolder *psfT;
hr = BindToObject(apidl[0], NULL, IID_PPV_ARG(IShellFolder, &psfT));
if (SUCCEEDED(hr))
{
hr = psfT->CreateViewObject(hwnd, riid, ppv);
psfT->Release();
}
}
else
{
// old code supported absolute PIDLs here. that was bogus...
ASSERT(ILIsEmpty(apidl[0]) || (ILFindLastID(apidl[0]) == apidl[0]));
ASSERT(_IsFile(pidf) || _IsSimpleID(pidf));
hr = _LoadHandler(pidf, STGM_READ, TEXT("DropHandler"), riid, ppv);
}
}
else if (IsEqualIID(riid, IID_ICustomIconManager) && pidf)
{
if (_IsFileFolder(pidf))
{
TCHAR szItemPath[MAX_PATH];
szItemPath[0] = NULL;
hr = _GetPath(szItemPath);
if (SUCCEEDED(hr))
{
// No support in ICustomIconManager for remote shares.
if (PathIsNetworkPath(szItemPath))
hr = E_NOTIMPL;
else
{
hr = CFileFolderIconManager_Create(this, (LPCITEMIDLIST)pidf, riid, ppv);
}
}
}
else
{
hr = E_NOTIMPL;
}
}
else if (pidf)
{
// too many people bogusly register extractors that
// dont work as well as ours for images
// we hard code our list of supported types.
if (IsEqualIID(riid, IID_IExtractImage))
{
hr = _CreateShimgvwExtractor(pidf, riid, ppv);
}
if (FAILED(hr))
hr = _LoadHandler(pidf, STGM_READ, NULL, riid, ppv);
if (FAILED(hr))
{
if (IsEqualIID(riid, IID_IQueryInfo))
{
hr = _GetToolTipForItem(pidf, riid, ppv);
}
else if (IsEqualIID(riid, IID_IQueryAssociations)
|| IsEqualIID(riid, IID_IAssociationArray))
{
hr = _AssocCreate(pidf, riid, ppv);
}
else if ((IsEqualIID(riid, IID_IExtractImage) ||
IsEqualIID(riid, IID_IExtractLogo)) && _IsFileFolder(pidf))
{
// default handler type, use the IID_ as the key to open for the handler
// if it is an image extractor, then check to see if it is a per-folder logo...
hr = CFolderExtractImage_Create(this, (LPCITEMIDLIST)pidf, riid, ppv);
}
}
}
return hr;
}
HRESULT CFSFolder::GetDefaultSearchGUID(GUID *pGuid)
{
return E_NOTIMPL;
}
HRESULT CFSFolder::EnumSearches(IEnumExtraSearch **ppenum)
{
*ppenum = NULL;
return E_NOTIMPL;
}
LPCIDFOLDER CFSFolder::_FindJunction(LPCIDFOLDER pidf)
{
for (; pidf->cb; pidf = _Next(pidf))
{
if (_IsJunction(pidf))
return pidf; // true junction (folder.{guid} folder\desktop.ini)
if (_IsFile(pidf))
{
DWORD dwFlags = _GetClassFlags(pidf);
if (dwFlags & (SHCF_IS_BROWSABLE | SHCF_IS_SHELLEXT))
return pidf; // browsable file (.HTM)
}
}
return NULL;
}
// return IDLIST of item just past the junction point (if there is one)
// if there's no next pointer, return NULL.
LPCITEMIDLIST CFSFolder::_FindJunctionNext(LPCIDFOLDER pidf)
{
pidf = _FindJunction(pidf);
if (pidf)
{
// cast here represents the fact that this data is opaque
LPCITEMIDLIST pidl = (LPCITEMIDLIST)_Next(pidf);
if (!ILIsEmpty(pidl))
return pidl; // first item past junction
}
return NULL;
}
void CFSFolder::_UpdateItem(LPCIDFOLDER pidf)
{
LPITEMIDLIST pidlAbs = ILCombine(_pidl, (LPCITEMIDLIST)pidf);
if (pidlAbs)
{
SHChangeNotify(SHCNE_UPDATEITEM, SHCNF_FLUSH | SHCNF_IDLIST, pidlAbs, NULL);
ILFree(pidlAbs);
}
}
HRESULT CFSFolder::_SetLocalizedDisplayName(LPCIDFOLDER pidf, LPCWSTR pszName)
{
HRESULT hr = E_FAIL;
WCHAR sz[MAX_PATH];
CFileSysItemString fsi(pidf);
if (*pszName == TEXT('@') && SUCCEEDED(SHLoadIndirectString(pszName, sz, ARRAYSIZE(sz), NULL)))
{
TCHAR szPath[MAX_PATH];
//
// this is a localized resource.
// save this off as the items UI name.
//
if (_IsFolder(pidf))
{
if (SUCCEEDED(_GetPathForItem(pidf, szPath))
&& SetFolderString(TRUE, szPath, _pszNetProvider, STRINI_CLASSINFO, TEXT("LocalizedResourceName"), pszName))
{
// we need to insure the bits are set for MUI on upgraded users
// PathMakeSystemFolder(szPath);
hr = S_OK;
}
}
else
{
_GetPath(szPath);
if (SetFolderString(TRUE, szPath, _pszNetProvider, TEXT("LocalizedFileNames"), fsi.FSName(), pszName))
hr = S_OK;
}
}
else
{
if (fsi.HasResourceName())
{
if (*pszName)
{
DWORD cb = CbFromCch(lstrlen(pszName)+1);
// set the registry overrides
if (S_OK == SKSetValueW(SHELLKEY_HKCU_SHELL, L"LocalizedResourceName", fsi.ResourceName(), REG_SZ, pszName, cb))
{
hr = S_OK;
}
}
else
{
SKDeleteValue(SHELLKEY_HKCU_SHELL, L"LocalizedResourceName", fsi.ResourceName());
hr = S_OK;
}
}
}
if (SUCCEEDED(hr))
_UpdateItem(pidf);
return hr;
}
HRESULT CFSFolder::_NormalGetDisplayNameOf(LPCIDFOLDER pidf, STRRET *pStrRet)
{
//
// WARNING - Some apps (e.g., Norton Uninstall Deluxe)
// don't handle STRRET_WSTR properly. NT4's shell32
// returned STRRET_WSTR only if it had no choice, so these apps
// seemed to run just fine on NT as long as you never had any
// UNICODE filenames. We must preserve the NT4 behavior or
// these buggy apps start blowing chunks.
//
// if this is still important, we will apphack these guys
CFileSysItemString fsi(pidf);
if (SHGetAppCompatFlags(ACF_ANSIDISPLAYNAMES) & ACF_ANSIDISPLAYNAMES)
{
pStrRet->uType = STRRET_CSTR;
SHUnicodeToAnsi(fsi.UIName(this), pStrRet->cStr, ARRAYSIZE(pStrRet->cStr));
return S_OK;
}
return StringToStrRet(fsi.UIName(this), pStrRet);
}
HRESULT CFSFolder::_NormalDisplayName(LPCIDFOLDER pidf, LPTSTR psz, UINT cch)
{
CFileSysItemString fsi(pidf);
StrCpyN(psz, fsi.UIName(this), cch);
return S_OK;
}
HRESULT CFSFolder::GetDisplayNameOf(LPCITEMIDLIST pidl, DWORD dwFlags, LPSTRRET pStrRet)
{
HRESULT hr = S_FALSE;
LPCIDFOLDER pidf = _IsValidID(pidl);
if (pidf)
{
TCHAR szPath[MAX_PATH];
LPCITEMIDLIST pidlNext = _ILNext(pidl);
if (dwFlags & SHGDN_FORPARSING)
{
if (dwFlags & SHGDN_INFOLDER)
{
_CopyName(pidf, szPath, ARRAYSIZE(szPath));
if (dwFlags & SHGDN_FORADDRESSBAR)
{
LPTSTR pszExt = PathFindCLSIDExtension(szPath, NULL);
if (pszExt)
*pszExt = 0;
}
if (ILIsEmpty(pidlNext)) // single level idlist
hr = StringToStrRet(szPath, pStrRet);
else
hr = ILGetRelDisplayName(this, pStrRet, pidl, szPath, MAKEINTRESOURCE(IDS_DSPTEMPLATE_WITH_BACKSLASH), dwFlags);
}
else
{
LPIDFOLDER pidfBind;
LPCITEMIDLIST pidlRight;
hr = _GetJunctionForBind(pidf, &pidfBind, &pidlRight);
if (SUCCEEDED(hr))
{
if (hr == S_OK)
{
IShellFolder *psfJctn;
hr = _Bind(NULL, pidfBind, IID_PPV_ARG(IShellFolder, &psfJctn));
if (SUCCEEDED(hr))
{
hr = psfJctn->GetDisplayNameOf(pidlRight, dwFlags, pStrRet);
psfJctn->Release();
}
ILFree((LPITEMIDLIST)pidfBind);
}
else
{
hr = _GetPathForItem(pidf, szPath);
if (SUCCEEDED(hr))
{
if (dwFlags & SHGDN_FORADDRESSBAR)
{
LPTSTR pszExt = PathFindCLSIDExtension(szPath, NULL);
if (pszExt)
*pszExt = 0;
}
hr = StringToStrRet(szPath, pStrRet);
}
}
}
}
}
else if (_IsCSIDL(CSIDL_RECENT) &&
SUCCEEDED(RecentDocs_GetDisplayName((LPCITEMIDLIST)pidf, szPath, SIZECHARS(szPath))))
{
LPITEMIDLIST pidlRecent;
WIN32_FIND_DATA wfd = {0};
StrCpyN(wfd.cFileName, szPath, SIZECHARS(wfd.cFileName));
if (SUCCEEDED(_CreateIDList(&wfd, NULL, &pidlRecent)))
{
hr = _NormalGetDisplayNameOf((LPCIDFOLDER)pidlRecent, pStrRet);
ILFree(pidlRecent);
}
}
else
{
ASSERT(ILIsEmpty(pidlNext)); // this variation should be single level
hr = _NormalGetDisplayNameOf(pidf, pStrRet);
}
}
else
{
if (IsSelf(1, &pidl) &&
((dwFlags & (SHGDN_FORADDRESSBAR | SHGDN_INFOLDER | SHGDN_FORPARSING)) == SHGDN_FORPARSING))
{
TCHAR szPath[MAX_PATH];
hr = _GetPath(szPath);
if (SUCCEEDED(hr))
hr = StringToStrRet(szPath, pStrRet);
}
else
{
hr = E_INVALIDARG;
TraceMsg(TF_WARNING, "CFSFolder::GetDisplayNameOf() failing on PIDL %s", DumpPidl(pidl));
}
}
return hr;
}
void DoSmartQuotes(LPTSTR pszName)
{
LPTSTR pszFirst = StrChr(pszName, TEXT('"'));
if (pszFirst)
{
LPTSTR pszSecond = StrChr(pszFirst + 1, TEXT('"'));
if (pszSecond)
{
if (NULL == StrChr(pszSecond + 1, TEXT('"')))
{
*pszFirst = 0x201C; // left double quotation
*pszSecond = 0x201D; // right double quotation
}
}
}
}
HRESULT _PrepareNameForRename(LPTSTR pszName)
{
if (*pszName)
{
HRESULT hr = _CheckPortName(pszName);
if (SUCCEEDED(hr))
{
DoSmartQuotes(pszName);
}
return hr;
}
// avoid a bogus error msg with blank name (treat as user cancel)
return HRESULT_FROM_WIN32(ERROR_CANCELLED);
}
HRESULT CFSFolder::SetNameOf(HWND hwnd, LPCITEMIDLIST pidl, LPCOLESTR pszName,
DWORD dwFlags, LPITEMIDLIST *ppidl)
{
HRESULT hr = E_INVALIDARG;
if (ppidl)
*ppidl = NULL;
LPCIDFOLDER pidf = _IsValidID(pidl);
if (pidf)
{
CFileSysItemString fsi(pidf);
TCHAR szNewName[MAX_PATH];
SHUnicodeToTChar(pszName, szNewName, ARRAYSIZE(szNewName));
PathRemoveBlanks(szNewName); // leading and trailing blanks
if (dwFlags == SHGDN_NORMAL || dwFlags == SHGDN_INFOLDER)
{
hr = _SetLocalizedDisplayName(pidf, pszName);
if (SUCCEEDED(hr))
{
// Return the new pidl if ppidl is specified.
if (ppidl)
return _CreateIDListFromName(fsi.FSName(), -1, NULL, ppidl);
}
else if (*pszName == TEXT('@') && PathParseIconLocation(szNewName + 1))
{
// this is a localized string (eg "@C:\WINNT\System32\shell32.dll,-3")
// so do not go on and try to call SHRenameFileEx
return hr;
}
}
if (FAILED(hr))
{
hr = _PrepareNameForRename(szNewName);
if (SUCCEEDED(hr))
{
TCHAR szDir[MAX_PATH], szOldName[MAX_PATH];
_CopyName(pidf, szOldName, ARRAYSIZE(szOldName));
// If the extension is hidden
if (!(dwFlags & SHGDN_FORPARSING) && !fsi.ShowExtension(_DefaultShowExt()))
{
// copy it from the old name
StrCatBuff(szNewName, PathFindExtension(szOldName), ARRAYSIZE(szNewName));
}
hr = _GetPath(szDir);
if (SUCCEEDED(hr))
{
UINT cchDirLen = lstrlen(szDir);
// There are cases where the old name exceeded the maximum path, which
// would give a bogus error message. To avoid this we should check for
// this case and see if using the short name for the file might get
// around this...
//
if (cchDirLen + lstrlen(szOldName) + 2 > MAX_PATH)
{
if (cchDirLen + lstrlenA(fsi.AltName()) + 2 <= MAX_PATH)
SHAnsiToTChar(fsi.AltName(), szOldName, ARRAYSIZE(szOldName));
}
// do a binary compare, locale insenstive compare to avoid mappings of
// single chars into multiple and the reverse. specifically german
// sharp-S and "ss"
if (StrCmpC(szOldName, szNewName) == 0)
{
// when the before and after strings are identical we're okay with that.
// SHRenameFileEx would return -1 in that case -- we check here to save
// some stack.
hr = S_OK;
}
else
{
// We need to impl ::SetSite() and pass it to SHRenameFile
// to go modal if we display UI.
int iRes = SHRenameFileEx(hwnd, NULL, szDir, szOldName, szNewName);
hr = HRESULT_FROM_WIN32(iRes);
}
if (SUCCEEDED(hr) && ppidl)
{
// Return the new pidl if ppidl is specified.
hr = _CreateIDListFromName(szNewName, -1, NULL, ppidl);
}
}
}
}
}
return hr;
}
HRESULT CFSFolder::_FindDataFromIDFolder(LPCIDFOLDER pidf, WIN32_FIND_DATAW *pfd, BOOL fAllowSimplePid)
{
HRESULT hr;
CFileSysItemString fsi(pidf);
if (!fAllowSimplePid)
{
hr = fsi.GetFindData(pfd);
}
else
{
hr = fsi.GetFindDataSimple(pfd);
}
return hr;
}
/***
To avoid registry explosion, each pidl is passed to each handler.
HKCR\Folder\ColumnHandlers
<clsid>
"" = "Docfile handler"
<clsid>
"" = "Imagefile handler"
***/
void CFSFolder::_DestroyColHandlers()
{
if (_hdsaColHandlers)
{
for (int i = 0; i < DSA_GetItemCount(_hdsaColHandlers); i++)
{
COLUMNLISTENTRY *pcle = (COLUMNLISTENTRY *)DSA_GetItemPtr(_hdsaColHandlers, i);
if (pcle->pcp)
pcle->pcp->Release();
}
DSA_Destroy(_hdsaColHandlers);
_hdsaColHandlers = NULL;
}
}
// returns the n'th handler for a given column
BOOL CFSFolder::_FindColHandler(UINT iCol, UINT iN, COLUMNLISTENTRY *pcle)
{
for (int i = 0; i < DSA_GetItemCount(_hdsaColHandlers); i++)
{
COLUMNLISTENTRY *pcleWalk = (COLUMNLISTENTRY *)DSA_GetItemPtr(_hdsaColHandlers, i);
if (pcleWalk->iColumnId == iCol)
{
if (iN-- == 0)
{
*pcle = *pcleWalk;
return TRUE;
}
}
}
return FALSE;
}
HRESULT CFSFolder::_LoadColumnHandlers()
{
// Have we been here?
if (NULL != _hdsaColHandlers)
return S_OK; // nothing to do.
ASSERT(0 == _dwColCount);
SHCOLUMNINIT shci = {0};
// retrieve folder path for provider init
HRESULT hr = _GetPathForItem(NULL, shci.wszFolder);
if (SUCCEEDED(hr))
{
_hdsaColHandlers = DSA_Create(sizeof(COLUMNLISTENTRY), 5);
if (_hdsaColHandlers)
{
int iUniqueColumnCount = 0;
HKEY hkCH;
// Enumerate HKCR\Folder\Shellex\ColumnProviders
// note: this really should have been "Directory", not "Folder"
if (ERROR_SUCCESS == RegOpenKey(HKEY_CLASSES_ROOT, TEXT("Folder\\shellex\\ColumnHandlers"), &hkCH))
{
TCHAR szHandlerCLSID[GUIDSTR_MAX];
int iHandler = 0;
while (ERROR_SUCCESS == RegEnumKey(hkCH, iHandler++, szHandlerCLSID, ARRAYSIZE(szHandlerCLSID)))
{
CLSID clsid;
IColumnProvider *pcp;
if (SUCCEEDED(SHCLSIDFromString(szHandlerCLSID, &clsid)) &&
SUCCEEDED(SHExtCoCreateInstance(NULL, &clsid, NULL, IID_PPV_ARG(IColumnProvider, &pcp))))
{
if (SUCCEEDED(pcp->Initialize(&shci)))
{
int iCol = 0;
COLUMNLISTENTRY cle;
cle.pcp = pcp;
while (S_OK == pcp->GetColumnInfo(iCol++, &cle.shci))
{
cle.pcp->AddRef();
cle.iColumnId = iUniqueColumnCount++;
// Check if there's already a handler for this column ID,
for (int i = 0; i < DSA_GetItemCount(_hdsaColHandlers); i++)
{
COLUMNLISTENTRY *pcleLoop = (COLUMNLISTENTRY *)DSA_GetItemPtr(_hdsaColHandlers, i);
if (IsEqualSCID(pcleLoop->shci.scid, cle.shci.scid))
{
cle.iColumnId = pcleLoop->iColumnId; // set the iColumnId to the same as the first one
iUniqueColumnCount--; // so our count stays right
break;
}
}
DSA_AppendItem(_hdsaColHandlers, &cle);
}
}
pcp->Release();
}
}
RegCloseKey(hkCH);
}
// Sanity check
if (!DSA_GetItemCount(_hdsaColHandlers))
{
// DSA_Destroy(*phdsa);
ASSERT(iUniqueColumnCount==0);
iUniqueColumnCount = 0;
}
_dwColCount = (DWORD)iUniqueColumnCount;
}
else
{
hr = E_OUTOFMEMORY;
}
}
return hr;
}
// Initializes a SHCOLUMNDATA block.
HRESULT CFSFolder::_InitColData(LPCIDFOLDER pidf, SHCOLUMNDATA* pscd)
{
ZeroMemory(pscd, sizeof(*pscd));
HRESULT hr = _GetPathForItem(pidf, pscd->wszFile);
if (SUCCEEDED(hr))
{
pscd->pwszExt = PathFindExtensionW(pscd->wszFile);
pscd->dwFileAttributes = pidf->wAttrs;
if (FILE_ATTRIBUTE_OFFLINE & pscd->dwFileAttributes)
hr = E_FAIL;
else if (_bUpdateExtendedCols)
{
// set the dwFlags member to tell the col handler to
// not take data from it's cache
pscd->dwFlags = SHCDF_UPDATEITEM;
_bUpdateExtendedCols = FALSE; // only do this once!
}
}
return hr;
}
// Note:
// Setting _tbOfflineCSC = TRIBIT_UNDEFINED will retest the connection (good for a refresh).
// Setting _tbOfflineCSC = { other } will use a little cache hooey for perf.
//
// Return:
// TRUE pidl is offline
// FALSE otherwise
//
BOOL CFSFolder::_IsOfflineCSC(LPCIDFOLDER pidf)
{
TCHAR szPath[MAX_PATH];
// Update local cached answer for _pidl (folder).
if (_tbOfflineCSC == TRIBIT_UNDEFINED)
{
if (SUCCEEDED(_GetPath(szPath)) && _IsOfflineCSC(szPath))
_tbOfflineCSC = TRIBIT_TRUE;
else
_tbOfflineCSC = TRIBIT_FALSE;
}
ASSERT(_tbOfflineCSC != TRIBIT_UNDEFINED);
// Calculate answer for pidl.
BOOL bIsOffline;
if (_tbOfflineCSC == TRIBIT_TRUE)
bIsOffline = TRUE;
else
{
bIsOffline = _IsFolder(pidf) && SUCCEEDED(_GetPathForItem(pidf, szPath)) && _IsOfflineCSC(szPath);
}
return bIsOffline;
}
// Make sure we have a UNC \\server\share path. Do this before checking
// whether CSC is enabled, to avoid loading CSCDLL.DLL unless absolutely
// necessary.
BOOL CFSFolder::_IsOfflineCSC(LPCTSTR pszPath)
{
BOOL bUNC = FALSE;
TCHAR szUNC[MAX_PATH];
szUNC[0] = 0;
if (PathIsUNC(pszPath))
{
StrCpyN(szUNC, pszPath, ARRAYSIZE(szUNC));
}
else if (pszPath[1] == TEXT(':'))
{
TCHAR szLocalName[3] = { pszPath[0], pszPath[1], TEXT('\0') };
// Call GetDriveType() before WNetGetConnection(), to
// avoid loading MPR.DLL unless absolutely necessary.
if (DRIVE_REMOTE == GetDriveType(szLocalName))
{
// ignore return, szUNC filled in on success
DWORD cch = ARRAYSIZE(szUNC);
WNetGetConnection(szLocalName, szUNC, &cch);
}
}
return szUNC[0] &&
PathStripToRoot(szUNC) &&
(GetOfflineShareStatus(szUNC) == OFS_OFFLINE);
}
HRESULT CFSFolder::_ExtendedColumn(LPCIDFOLDER pidf, UINT iColumn, SHELLDETAILS *pDetails)
{
HRESULT hr = _LoadColumnHandlers();
if (SUCCEEDED(hr))
{
if (iColumn < _dwColCount)
{
if (NULL == pidf)
{
COLUMNLISTENTRY cle;
if (_FindColHandler(iColumn, 0, &cle))
{
pDetails->fmt = cle.shci.fmt;
pDetails->cxChar = cle.shci.cChars;
hr = StringToStrRet(cle.shci.wszTitle, &pDetails->str);
}
else
{
hr = E_NOTIMPL;
}
}
else
{
SHCOLUMNDATA shcd;
hr = _InitColData(pidf, &shcd);
if (SUCCEEDED(hr))
{
hr = E_FAIL; // loop below will try to reset this
// loop through all the column providers, breaking when one succeeds
COLUMNLISTENTRY cle;
for (int iTry = 0; _FindColHandler(iColumn, iTry, &cle); iTry++)
{
VARIANT var = {0};
hr = cle.pcp->GetItemData(&cle.shci.scid, &shcd, &var);
if (SUCCEEDED(hr))
{
if (S_OK == hr)
{
PROPERTYUI_FORMAT_FLAGS puiff = PUIFFDF_DEFAULT;
if (pDetails->fmt == LVCFMT_RIGHT_TO_LEFT)
{
puiff = PUIFFDF_RIGHTTOLEFT;
}
TCHAR szTemp[MAX_PATH];
hr = SHFormatForDisplay(cle.shci.scid.fmtid,
cle.shci.scid.pid,
(PROPVARIANT*)&var,
puiff,
szTemp,
ARRAYSIZE(szTemp));
if (SUCCEEDED(hr))
{
hr = StringToStrRet(szTemp, &pDetails->str);
}
VariantClear(&var);
break;
}
VariantClear(&var);
}
}
// if we failed to find a value here return empty success so we don't
// endlessly pester all column handlers for this column/item.
if (S_OK != hr)
{
pDetails->str.uType = STRRET_CSTR;
pDetails->str.cStr[0] = 0;
hr = S_FALSE;
}
}
}
}
else
hr = E_NOTIMPL; // the bogus return value defview expects...
}
return hr;
}
HRESULT CFSFolder::_CompareExtendedProp(int iColumn, LPCIDFOLDER pidf1, LPCIDFOLDER pidf2)
{
HRESULT hr = _LoadColumnHandlers();
if (SUCCEEDED(hr))
{
if ((DWORD)iColumn < _dwColCount)
{
COLUMNLISTENTRY cle;
if (_FindColHandler(iColumn, 0, &cle))
{
int iRet = CompareBySCID(this, &cle.shci.scid, (LPCITEMIDLIST)pidf1, (LPCITEMIDLIST)pidf2);
hr = ResultFromShort(iRet);
}
}
else
{
hr = E_FAIL;
}
}
return hr;
}
HRESULT CFSFolder::GetDetailsOf(LPCITEMIDLIST pidl, UINT iColumn, SHELLDETAILS *pDetails)
{
LPCIDFOLDER pidf = _IsValidID(pidl);
pDetails->str.uType = STRRET_CSTR;
pDetails->str.cStr[0] = 0;
if (iColumn >= ARRAYSIZE(c_fs_cols))
{
return _ExtendedColumn(pidf, iColumn - ARRAYSIZE(c_fs_cols), pDetails);
}
if (!pidf)
{
return GetDetailsOfInfo(c_fs_cols, ARRAYSIZE(c_fs_cols), iColumn, pDetails);
}
TCHAR szTemp[MAX_PATH];
szTemp[0] = 0;
switch (iColumn)
{
case FS_ICOL_NAME:
_NormalDisplayName(pidf, szTemp, ARRAYSIZE(szTemp));
break;
case FS_ICOL_SIZE:
if (!_IsFolder(pidf))
{
ULONGLONG cbSize = _Size(pidf);
StrFormatKBSize(cbSize, szTemp, ARRAYSIZE(szTemp));
}
break;
case FS_ICOL_TYPE:
_GetTypeNameBuf(pidf, szTemp, ARRAYSIZE(szTemp));
break;
case FS_ICOL_WRITETIME:
DosTimeToDateTimeString(pidf->dateModified, pidf->timeModified, szTemp, ARRAYSIZE(szTemp), pDetails->fmt & LVCFMT_DIRECTION_MASK);
break;
case FS_ICOL_CREATETIME:
case FS_ICOL_ACCESSTIME:
{
WIN32_FIND_DATAW wfd;
if (SUCCEEDED(_FindDataFromIDFolder(pidf, &wfd, FALSE)))
{
DWORD dwFlags = FDTF_DEFAULT;
switch (pDetails->fmt)
{
case LVCFMT_LEFT_TO_RIGHT:
dwFlags |= FDTF_LTRDATE;
break;
case LVCFMT_RIGHT_TO_LEFT:
dwFlags |= FDTF_RTLDATE;
break;
}
FILETIME ft = (iColumn == FS_ICOL_CREATETIME) ? wfd.ftCreationTime : wfd.ftLastAccessTime;
SHFormatDateTime(&ft, &dwFlags, szTemp, ARRAYSIZE(szTemp));
}
}
break;
case FS_ICOL_ATTRIB:
BuildAttributeString(pidf->wAttrs, szTemp, ARRAYSIZE(szTemp));
break;
case FS_ICOL_CSC_STATUS:
LoadString(HINST_THISDLL, _IsOfflineCSC(pidf) ? IDS_CSC_STATUS_OFFLINE : IDS_CSC_STATUS_ONLINE, szTemp, ARRAYSIZE(szTemp));
break;
}
return StringToStrRet(szTemp, &pDetails->str);
}
HRESULT CFSFolder::_GetIntroText(LPCIDFOLDER pidf, WCHAR* pwszIntroText, UINT cchIntroText)
{
HRESULT hr = E_FAIL;
TCHAR szPath[MAX_PATH];
if (SUCCEEDED(_GetPathForItem(pidf, szPath)))
{
// Keep the order in csidlIntroText and IntroTextCSIDLFolders, the same
const int csidlIntroText[] = {
CSIDL_STARTMENU,
CSIDL_COMMON_DOCUMENTS,
CSIDL_COMMON_PICTURES,
CSIDL_COMMON_MUSIC
};
UINT csidl = GetSpecialFolderID(szPath, csidlIntroText, ARRAYSIZE(csidlIntroText));
if (csidl != -1)
{
// Keep the order in csidlIntroText and IntroTextCSIDLFolders, the same
static struct
{
UINT csidl;
UINT resid;
} IntroTextCSIDLFolders[] = { {CSIDL_STARTMENU, IDS_INTRO_STARTMENU},
{CSIDL_COMMON_DOCUMENTS, IDS_INTRO_SHAREDDOCS},
{CSIDL_COMMON_PICTURES, IDS_INTRO_SHAREDPICTURES},
{CSIDL_COMMON_MUSIC, IDS_INTRO_SHAREDMUSIC} };
UINT residIntroText = 0;
for (int i = 0; i < ARRAYSIZE(IntroTextCSIDLFolders); i++)
{
if (IntroTextCSIDLFolders[i].csidl == csidl)
{
residIntroText = IntroTextCSIDLFolders[i].resid;
break;
}
}
if (residIntroText)
{
if (LoadString(HINST_THISDLL, residIntroText, pwszIntroText, cchIntroText))
{
hr = S_OK;
}
}
}
}
return hr;
}
DEFINE_SCID(SCID_HTMLINFOTIPFILE, PSGUID_MISC, PID_HTMLINFOTIPFILE);
BOOL GetShellClassInfoHTMLInfoTipFile(LPCTSTR pszPath, LPTSTR pszBuffer, DWORD cchBuffer)
{
TCHAR szHTMLInfoTipFile[MAX_PATH];
BOOL fRet = GetShellClassInfo(pszPath, TEXT("HTMLInfoTipFile"),
szHTMLInfoTipFile, ARRAYSIZE(szHTMLInfoTipFile));
if (fRet)
{
LPTSTR psz = szHTMLInfoTipFile;
if (StrCmpNI(TEXT("file://"), psz, 7) == 0) // ARRAYSIZE(TEXT("file://"))
{
psz += 7; // ARRAYSIZE(TEXT("file://"))
}
PathCombine(psz, pszPath, psz);
lstrcpyn(pszBuffer, psz, cchBuffer);
}
return fRet;
}
// These next functions are for the shell OM script support
HRESULT CFSFolder::GetDetailsEx(LPCITEMIDLIST pidl, const SHCOLUMNID *pscid, VARIANT *pv)
{
BOOL fFound;
HRESULT hr = AssocGetDetailsOfSCID(this, pidl, pscid, pv, &fFound);
LPCIDFOLDER pidf = _IsValidID(pidl);
if (FAILED(hr) && !fFound && pidf)
{
if (IsEqualSCID(*pscid, SCID_FINDDATA))
{
WIN32_FIND_DATAW wfd;
hr = _FindDataFromIDFolder(pidf, &wfd, TRUE);
if (SUCCEEDED(hr))
{
hr = InitVariantFromBuffer(pv, &wfd, sizeof(wfd));
}
}
else if (IsEqualSCID(*pscid, SCID_DESCRIPTIONID))
{
SHDESCRIPTIONID did = {0};
switch (((SIL_GetType(pidl) & SHID_TYPEMASK) & ~(SHID_FS_UNICODE | SHID_FS_COMMONITEM)) | SHID_FS)
{
case SHID_FS_FILE: did.dwDescriptionId = SHDID_FS_FILE; break;
case SHID_FS_DIRECTORY: did.dwDescriptionId = SHDID_FS_DIRECTORY; break;
default: did.dwDescriptionId = SHDID_FS_OTHER; break;
}
_GetJunctionClsid(pidf, &did.clsid);
hr = InitVariantFromBuffer(pv, &did, sizeof(did));
}
else if (IsEqualSCID(*pscid, SCID_FolderIntroText))
{
WCHAR wszIntroText[INFOTIPSIZE];
hr = _GetIntroText(pidf, wszIntroText, ARRAYSIZE(wszIntroText));
if (SUCCEEDED(hr))
{
hr = InitVariantFromStr(pv, wszIntroText);
}
}
else if (IsEqualSCID(*pscid, SCID_SIZE))
{
TCHAR szMountPoint[MAX_PATH];
// In case we fail
pv->ullVal = 0;
pv->vt = VT_UI8;
if (_GetMountingPointInfo(pidf, szMountPoint, ARRAYSIZE(szMountPoint)))
{
ULARGE_INTEGER uliFreeToCaller, uliTotal, uliTotalFree;
if (SHGetDiskFreeSpaceExW(szMountPoint, &uliFreeToCaller, &uliTotal,
&uliTotalFree))
{
pv->ullVal = uliTotal.QuadPart;
}
}
else
{
pv->ullVal = _Size(pidf); // note, size for folder is 0
pv->vt = VT_UI8;
}
hr = S_OK;
}
else if (IsEqualSCID(*pscid, SCID_FREESPACE))
{
TCHAR szMountPoint[MAX_PATH];
if (_GetMountingPointInfo(pidf, szMountPoint, ARRAYSIZE(szMountPoint)))
{
ULARGE_INTEGER uliFreeToCaller, uliTotal, uliTotalFree;
if (SHGetDiskFreeSpaceExW(szMountPoint, &uliFreeToCaller, &uliTotal, &uliTotalFree))
{
pv->ullVal = uliFreeToCaller.QuadPart;
pv->vt = VT_UI8;
hr = S_OK;
}
}
}
else if (IsEqualSCID(*pscid, SCID_WRITETIME) ||
IsEqualSCID(*pscid, SCID_CREATETIME) ||
IsEqualSCID(*pscid, SCID_ACCESSTIME))
{
WIN32_FIND_DATAW wfd;
hr = _FindDataFromIDFolder(pidf, &wfd, FALSE);
if (SUCCEEDED(hr))
{
FILETIME ft;
if (pscid->pid == PID_STG_WRITETIME)
{
ft = wfd.ftLastWriteTime;
}
else if (pscid->pid == PID_STG_CREATETIME)
{
ft = wfd.ftCreationTime;
}
else
{
ft = wfd.ftLastAccessTime;
}
hr = InitVariantFromFileTime(&ft, pv);
}
}
else if (IsEqualSCID(*pscid, SCID_DIRECTORY))
{
TCHAR szTemp[MAX_PATH];
hr = _GetPath(szTemp);
if (SUCCEEDED(hr))
{
hr = InitVariantFromStr(pv, szTemp);
}
}
else if (IsEqualSCID(*pscid, SCID_ATTRIBUTES_DESCRIPTION))
{
hr = _GetAttributesDescription(pidf, pv);
}
else if (IsEqualSCID(*pscid, SCID_LINKTARGET))
{
hr = _GetLinkTarget(pidl, pv);
}
else if (IsEqualSCID(*pscid, SCID_CSC_STATUS))
{
hr = _GetCSCStatus(pidf, pv);
}
else if ((IsEqualSCID(*pscid, SCID_Comment) || IsEqualSCID(*pscid, SCID_HTMLINFOTIPFILE)) &&
_IsSystemFolder(pidf))
{
TCHAR szPath[MAX_PATH];
hr = _GetPathForItem(pidf, szPath);
if (SUCCEEDED(hr))
{
TCHAR szText[MAX_PATH];
if (IsEqualSCID(*pscid, SCID_Comment))
GetShellClassInfoInfoTip(szPath, szText, ARRAYSIZE(szText));
else
GetShellClassInfoHTMLInfoTipFile(szPath, szText, ARRAYSIZE(szText));
hr = InitVariantFromStr(pv, szText);
}
}
else if (IsEqualSCID(*pscid, SCID_COMPUTERNAME))
{
hr = _GetComputerName(pidf, pv);
}
else if (IsEqualSCID(*pscid, SCID_NETWORKLOCATION))
{
hr = _GetNetworkLocation(pidf, pv);
}
else // if (Column Handler)
{
int iCol = FindSCID(c_fs_cols, ARRAYSIZE(c_fs_cols), pscid);
if (iCol >= 0)
{
SHELLDETAILS sd;
hr = GetDetailsOf(pidl, iCol, &sd);
if (SUCCEEDED(hr))
{
hr = InitVariantFromStrRet(&sd.str, pidl, pv);
}
}
else
{
hr = _LoadColumnHandlers();
if (SUCCEEDED(hr))
{
hr = E_FAIL;
for (int i = 0; i < DSA_GetItemCount(_hdsaColHandlers); i++)
{
COLUMNLISTENTRY *pcle = (COLUMNLISTENTRY *)DSA_GetItemPtr(_hdsaColHandlers, i);
if (IsEqualSCID(*pscid, pcle->shci.scid))
{
SHCOLUMNDATA shcd;
hr = _InitColData(pidf, &shcd);
if (SUCCEEDED(hr))
{
hr = pcle->pcp->GetItemData(pscid, &shcd, pv);
if (S_OK == hr)
break;
else if (SUCCEEDED(hr))
VariantClear(pv);
}
else
break;
}
}
}
}
}
}
return hr;
}
// GetDetailsEx() helper.
HRESULT CFSFolder::_GetAttributesDescription(LPCIDFOLDER pidf, VARIANT *pv)
{
static WCHAR szR[32] = {0}; // read-only
static WCHAR szH[32] = {0}; // hidden
static WCHAR szS[32] = {0}; // system
static WCHAR szC[32] = {0}; // compressed
static WCHAR szE[32] = {0}; // encrypted
static WCHAR szO[32] = {0}; // offline
WCHAR szAttributes[256] = {0};
size_t cchAttributes = ARRAYSIZE(szAttributes);
BOOL bIsFolder = _IsFolder(pidf);
//
// Initialize cached values once 'n only once.
//
if (!szR[0])
{
ASSERT(!szH[0] && !szS[0] && !szC[0] && !szE[0] && !szO[0]);
LoadString(HINST_THISDLL, IDS_ATTRIBUTE_READONLY, szR, ARRAYSIZE(szR));
LoadString(HINST_THISDLL, IDS_ATTRIBUTE_HIDDEN, szH, ARRAYSIZE(szH));
LoadString(HINST_THISDLL, IDS_ATTRIBUTE_SYSTEM, szS, ARRAYSIZE(szS));
LoadString(HINST_THISDLL, IDS_ATTRIBUTE_COMPRESSED, szC, ARRAYSIZE(szC));
LoadString(HINST_THISDLL, IDS_ATTRIBUTE_ENCRYPTED, szE, ARRAYSIZE(szE));
LoadString(HINST_THISDLL, IDS_ATTRIBUTE_OFFLINE, szO, ARRAYSIZE(szO));
}
else
{
ASSERT(szH[0] && szS[0] && szC[0] && szE[0] && szO[0]);
}
//
// Create attribute description string.
//
// read-only
if ((pidf->wAttrs & FILE_ATTRIBUTE_READONLY) && !bIsFolder)
_GetAttributesDescriptionBuilder(szAttributes, cchAttributes, szR);
// hidden
if (pidf->wAttrs & FILE_ATTRIBUTE_HIDDEN)
_GetAttributesDescriptionBuilder(szAttributes, cchAttributes, szH);
// system
if ((pidf->wAttrs & FILE_ATTRIBUTE_SYSTEM) && !bIsFolder)
_GetAttributesDescriptionBuilder(szAttributes, cchAttributes, szS);
// archive
// By design, archive is not exposed as an attribute description. It is
// used by "backup applications" and in general is a loose convention no
// one really cares about (chrisg). The decision to hide archive stems
// from a desire to keep the Details pane free of useless gargabe. Note
// that in Windows 2000, archive was not exposed through the web view.
// compressed
if (pidf->wAttrs & FILE_ATTRIBUTE_COMPRESSED)
_GetAttributesDescriptionBuilder(szAttributes, cchAttributes, szC);
// encrypted
if (pidf->wAttrs & FILE_ATTRIBUTE_ENCRYPTED)
_GetAttributesDescriptionBuilder(szAttributes, cchAttributes, szE);
// offline
if (pidf->wAttrs & FILE_ATTRIBUTE_OFFLINE)
_GetAttributesDescriptionBuilder(szAttributes, cchAttributes, szO);
return InitVariantFromStr(pv, szAttributes);
}
HRESULT CFSFolder::_GetAttributesDescriptionBuilder(LPWSTR szAttributes, size_t cchAttributes, LPWSTR szAttribute)
{
static WCHAR szDelimiter[4] = {0};
// Initialize cached delimiter once 'n only once.
if (!szDelimiter[0])
{
LoadString(HINST_THISDLL, IDS_COMMASPACE, szDelimiter, ARRAYSIZE(szDelimiter));
}
// Build attribute description.
if (!szAttributes[0])
{
StrNCpy(szAttributes, szAttribute, cchAttributes);
}
else
{
StrCatBuff(szAttributes, szDelimiter, cchAttributes);
StrCatBuff(szAttributes, szAttribute, cchAttributes);
}
return S_OK;
}
// GetDetailsEx() helper.
HRESULT CFSFolder::_GetLinkTarget(LPCITEMIDLIST pidl, VARIANT *pv)
{
IShellLink *psl;
HRESULT hr = GetUIObjectOf(NULL, 1, &pidl, IID_PPV_ARG_NULL(IShellLink, &psl));
if (SUCCEEDED(hr))
{
LPITEMIDLIST pidlTarget;
hr = psl->GetIDList(&pidlTarget);
if (SUCCEEDED(hr))
{
WCHAR szPath[MAX_PATH];
hr = SHGetNameAndFlags(pidlTarget, SHGDN_FORADDRESSBAR | SHGDN_FORPARSING, szPath, ARRAYSIZE(szPath), NULL);
if (SUCCEEDED(hr))
hr = InitVariantFromStr(pv, szPath);
ILFree(pidlTarget);
}
psl->Release();
}
return hr;
}
// GetDetailsEx() helper.
HRESULT CFSFolder::_GetNetworkLocation(LPCIDFOLDER pidf, VARIANT *pv)
{
LPCITEMIDLIST pidl = (LPCITEMIDLIST)pidf;
IShellLink *psl;
HRESULT hr = GetUIObjectOf(NULL, 1, &pidl, IID_PPV_ARG_NULL(IShellLink, &psl));
if (SUCCEEDED(hr))
{
LPITEMIDLIST pidlTarget;
hr = psl->GetIDList(&pidlTarget);
if (SUCCEEDED(hr))
{
TCHAR szPath[MAX_PATH];
hr = SHGetNameAndFlags(pidlTarget, SHGDN_FORPARSING, szPath, ARRAYSIZE(szPath), NULL);
if (SUCCEEDED(hr))
{
DWORD dwZone;
hr = GetZoneFromUrl(szPath, NULL, &dwZone);
if (SUCCEEDED(hr))
{
TCHAR szBuffer[MAX_PATH];
switch (dwZone)
{
case URLZONE_LOCAL_MACHINE:
case URLZONE_INTRANET:
LoadString(g_hinst, IDS_NETLOC_LOCALNETWORK, szBuffer, ARRAYSIZE(szBuffer));
hr = InitVariantFromStr(pv, szBuffer);
break;
case URLZONE_INTERNET:
LoadString(g_hinst, IDS_NETLOC_INTERNET, szBuffer, ARRAYSIZE(szBuffer));
hr = InitVariantFromStr(pv, szBuffer);
break;
default:
hr = S_FALSE;
break;
}
}
}
ILFree(pidlTarget);
}
psl->Release();
}
return hr;
}
// GetDetailsEx() helper.
HRESULT CFSFolder::_GetComputerName(LPCIDFOLDER pidf, VARIANT *pv)
{
LPCITEMIDLIST pidl = (LPCITEMIDLIST)pidf;
IShellLink *psl;
HRESULT hr = GetUIObjectOf(NULL, 1, &pidl, IID_PPV_ARG_NULL(IShellLink, &psl));
if (SUCCEEDED(hr))
{
LPITEMIDLIST pidlTarget;
hr = psl->GetIDList(&pidlTarget);
if (SUCCEEDED(hr))
{
WCHAR szPath[MAX_PATH];
if (SUCCEEDED(SHGetNameAndFlags(pidlTarget, SHGDN_FORPARSING, szPath, ARRAYSIZE(szPath), NULL)))
{
if (PathIsURL(szPath))
{
TCHAR szServer[INTERNET_MAX_HOST_NAME_LENGTH + 1];
URL_COMPONENTS urlComps = {0};
urlComps.dwStructSize = sizeof(urlComps);
urlComps.lpszHostName = szServer;
urlComps.dwHostNameLength = ARRAYSIZE(szServer);
BOOL fResult = InternetCrackUrl(szPath, 0, ICU_DECODE, &urlComps);
if (fResult)
{
hr = InitVariantFromStr(pv, szServer);
}
else
{
hr = E_FAIL;
}
}
else if (PathIsUNC(szPath))
{
hr = _GetComputerName_FromPath(szPath, pv);
}
else
{
hr = E_FAIL;
}
}
else
{
hr = E_FAIL;
}
ILFree(pidlTarget);
}
psl->Release();
}
else
{
TCHAR szPath[MAX_PATH];
hr = _GetPath(szPath);
if (SUCCEEDED(hr))
{
hr = _GetComputerName_FromPath(szPath, pv);
}
}
if (FAILED(hr))
{
WCHAR sz[MAX_PATH];
LoadString(HINST_THISDLL, IDS_UNKNOWNGROUP, sz, ARRAYSIZE(sz));
hr = InitVariantFromStr(pv, sz);
}
return hr;
}
HRESULT CFSFolder::_GetComputerName_FromPath(LPCWSTR pszPath, VARIANT *pv)
{
HRESULT hr;
TCHAR szPath[MAX_PATH];
lstrcpyn(szPath, pszPath, ARRAYSIZE(szPath));
PathStripToRoot(szPath);
if (PathIsUNC(szPath))
{
hr = _GetComputerName_FromUNC(szPath, pv);
}
else
{
CMountPoint* pMtPt = CMountPoint::GetMountPoint(szPath, FALSE);
if (pMtPt)
{
if (pMtPt->IsRemote())
{
WCHAR szRemotePath[MAX_PATH];
hr = pMtPt->GetRemotePath(szRemotePath, ARRAYSIZE(szRemotePath));
if (SUCCEEDED(hr))
{
hr = _GetComputerName_FromPath(szRemotePath, pv);
}
}
else
{
WCHAR sz[MAX_PATH];
LoadString(HINST_THISDLL, IDS_THISCOMPUTERGROUP, sz, ARRAYSIZE(sz));
hr = InitVariantFromStr(pv, sz);
}
pMtPt->Release();
}
else
{
hr = E_OUTOFMEMORY;
}
}
return hr;
}
HRESULT CFSFolder::_GetComputerName_FromUNC(LPWSTR pszPath, VARIANT *pv)
{
// strip to "\\server"
LPWSTR psz = pszPath;
while (*psz && *psz==L'\\')
psz++;
while (*psz && *psz!=L'\\')
psz++;
*psz = NULL;
LPITEMIDLIST pidl;
HRESULT hr = SHParseDisplayName(pszPath, NULL, &pidl, 0, NULL);
if (SUCCEEDED(hr))
{
WCHAR szName[MAX_PATH];
hr = SHGetNameAndFlagsW(pidl, SHGDN_INFOLDER, szName, ARRAYSIZE(szName), NULL);
if (SUCCEEDED(hr))
{
hr = InitVariantFromStr(pv, szName);
}
ILFree(pidl);
}
return hr;
}
// GetDetailsEx() helper.
HRESULT CFSFolder::_GetCSCStatus(LPCIDFOLDER pidf, VARIANT *pv)
{
HRESULT hr;
// Note:
// Only display the status in the Details task pane if it is "Offline".
if (_IsOfflineCSC(pidf))
{
WCHAR wszStatus[MAX_PATH];
if (LoadString(HINST_THISDLL, IDS_CSC_STATUS_OFFLINE, wszStatus, ARRAYSIZE(wszStatus)))
hr = InitVariantFromStr(pv, wszStatus);
else
hr = HRESULT_FROM_WIN32(GetLastError());
}
else
{
VariantInit(pv);
pv->vt = VT_NULL;
hr = S_OK;
}
return hr;
}
HRESULT CFSFolder::GetDefaultColumn(DWORD dwRes, ULONG *pSort, ULONG *pDisplay)
{
return E_NOTIMPL;
}
#define FVCBFT_MUSICFOLDER(ft) (FVCBFT_MUSIC == ft || FVCBFT_MYMUSIC == ft || FVCBFT_MUSICARTIST == ft || FVCBFT_MUSICALBUM == ft)
BOOL CFSFolder::_ShouldNotShowColumn(UINT iColumn)
{
BOOL fRet = FALSE; // show by default
if (FVCBFT_MUSICFOLDER(_nFolderType))
{
fRet = (iColumn == FS_ICOL_WRITETIME); // hide this one in the music case
}
return fRet;
}
BOOL CFSFolder::_ShouldShowExtendedColumn(const SHCOLUMNID* pscid)
{
BOOL fRet;
switch(_nFolderType)
{
case FVCBFT_PICTURES:
case FVCBFT_MYPICTURES:
case FVCBFT_PHOTOALBUM:
fRet = (IsEqualSCID(*pscid, SCID_WhenTaken) || IsEqualSCID(*pscid, SCID_ImageDimensions));
break;
case FVCBFT_MUSIC:
case FVCBFT_MYMUSIC:
case FVCBFT_MUSICARTIST:
case FVCBFT_MUSICALBUM:
fRet = (IsEqualSCID(*pscid, SCID_MUSIC_Artist) || IsEqualSCID(*pscid, SCID_MUSIC_Year) ||
IsEqualSCID(*pscid, SCID_MUSIC_Album) || IsEqualSCID(*pscid, SCID_MUSIC_Track) ||
IsEqualSCID(*pscid, SCID_AUDIO_Duration));
break;
case FVCBFT_VIDEOS:
case FVCBFT_MYVIDEOS:
case FVCBFT_VIDEOALBUM:
fRet = (IsEqualSCID(*pscid, SCID_AUDIO_Duration) || IsEqualSCID(*pscid, SCID_ImageDimensions));
break;
default:
fRet = FALSE;
break;
}
return fRet;
}
HRESULT CFSFolder::GetDefaultColumnState(UINT iColumn, DWORD *pdwState)
{
HRESULT hr = S_OK;
*pdwState = 0;
if (iColumn < ARRAYSIZE(c_fs_cols))
{
*pdwState = c_fs_cols[iColumn].csFlags;
if (_ShouldNotShowColumn(iColumn))
{
*pdwState &= ~SHCOLSTATE_ONBYDEFAULT; // flip stuff off
}
}
else
{
iColumn -= ARRAYSIZE(c_fs_cols);
hr = _LoadColumnHandlers();
if (SUCCEEDED(hr))
{
hr = E_INVALIDARG;
if (iColumn < _dwColCount)
{
COLUMNLISTENTRY cle;
if (_FindColHandler(iColumn, 0, &cle))
{
*pdwState |= (cle.shci.csFlags | SHCOLSTATE_EXTENDED | SHCOLSTATE_SLOW);
if (_ShouldShowExtendedColumn(&cle.shci.scid))
{
*pdwState |= SHCOLSTATE_ONBYDEFAULT;
}
else
{
*pdwState &= ~SHCOLSTATE_ONBYDEFAULT; // strip this one
}
hr = S_OK;
}
}
}
}
return hr;
}
HRESULT CFSFolder::MapColumnToSCID(UINT iColumn, SHCOLUMNID *pscid)
{
HRESULT hr = MapColumnToSCIDImpl(c_fs_cols, ARRAYSIZE(c_fs_cols), iColumn, pscid);
if (hr != S_OK)
{
COLUMNLISTENTRY cle;
if (SUCCEEDED(_LoadColumnHandlers()))
{
iColumn -= ARRAYSIZE(c_fs_cols);
if (_FindColHandler(iColumn, 0, &cle))
{
*pscid = cle.shci.scid;
hr = S_OK;
}
}
}
return hr;
}
HRESULT CFSFolder::_MapSCIDToColumn(const SHCOLUMNID* pscid, UINT* puCol)
{
HRESULT hr;
int iCol = FindSCID(c_fs_cols, ARRAYSIZE(c_fs_cols), pscid);
if (iCol >= 0)
{
*puCol = iCol;
hr = S_OK;
}
else
{
hr = _LoadColumnHandlers();
if (SUCCEEDED(hr))
{
hr = E_FAIL;
for (int i = 0; i < DSA_GetItemCount(_hdsaColHandlers); i++)
{
COLUMNLISTENTRY *pcle = (COLUMNLISTENTRY *)DSA_GetItemPtr(_hdsaColHandlers, i);
if (IsEqualSCID(*pscid, pcle->shci.scid))
{
*puCol = pcle->iColumnId;
hr = S_OK;
break;
}
}
}
}
return hr;
}
//
// N ways to get a clasid for an item
//
BOOL CFSFolder::_GetBindCLSID(IBindCtx *pbc, LPCIDFOLDER pidf, CLSID *pclsid)
{
CFileSysItemString fsi(pidf);
DWORD dwClassFlags = fsi.ClassFlags(FALSE);
if (dwClassFlags & SHCF_IS_DOCOBJECT)
{
*pclsid = CLSID_CDocObjectFolder;
}
else if (fsi.GetJunctionClsid(pclsid, TRUE))
{
// *pclsid has the value
// HACK: CLSID_Briefcase is use to identify the briefcase
// but it's InProcServer is syncui.dll. we need to map that CLSID
// to the object implemented in shell32 (CLSID_BriefcaseFolder)
// ZEKELTODO - why isnt this a COM "TreatAs"?
if (IsEqualCLSID(*pclsid, CLSID_Briefcase))
*pclsid = CLSID_BriefcaseFolder;
}
else if (!IsEqualCLSID(CLSID_NULL, _clsidBind))
{
*pclsid = _clsidBind; // briefcase forces all children this way
}
else
{
return FALSE; // do normal binding
}
// TRUE -> special binding, FALSE -> normal file system binding
return !SHSkipJunctionBinding(pbc, pclsid);
}
// initalize shell folder handlers
// in:
// pidf multi level file system pidl
//
// in/out:
// *ppunk
//
// note: on failure this frees *ppunk
HRESULT CFSFolder::_InitFolder(IBindCtx *pbc, LPCIDFOLDER pidf, IUnknown **ppunk)
{
ASSERT(_FindJunctionNext(pidf) == NULL); // no extra goo please
LPITEMIDLIST pidlInit;
HRESULT hr = SHILCombine(_pidl, (LPITEMIDLIST)pidf, &pidlInit);
if (SUCCEEDED(hr))
{
IPersistFolder3 *ppf3;
hr = (*ppunk)->QueryInterface(IID_PPV_ARG(IPersistFolder3, &ppf3));
if (SUCCEEDED(hr))
{
PERSIST_FOLDER_TARGET_INFO pfti = {0};
if (_csidlTrack >= 0)
{
// SHGetSpecialFolderlocation will return error if the target
// doesn't exist (which is good, since that means there's
// nothing to bind to).
LPITEMIDLIST pidl;
hr = SHGetSpecialFolderLocation(NULL, _csidlTrack, &pidl);
if (SUCCEEDED(hr))
{
hr = SHILCombine(pidl, (LPITEMIDLIST)pidf, &pfti.pidlTargetFolder);
ILFree(pidl);
}
}
else if (_pidlTarget)
hr = SHILCombine(_pidlTarget, (LPITEMIDLIST)pidf, &pfti.pidlTargetFolder);
if (SUCCEEDED(hr))
{
hr = _GetPathForItem(pidf, pfti.szTargetParsingName);
if (SUCCEEDED(hr))
{
if (_pszNetProvider)
SHTCharToUnicode(_pszNetProvider, pfti.szNetworkProvider, ARRAYSIZE(pfti.szNetworkProvider));
pfti.dwAttributes = _FindLastID(pidf)->wAttrs;
pfti.csidl = -1;
hr = ppf3->InitializeEx(pbc, pidlInit, &pfti);
}
ILFree(pfti.pidlTargetFolder);
}
ppf3->Release();
}
else
{
IPersistFolder *ppf;
hr = (*ppunk)->QueryInterface(IID_PPV_ARG(IPersistFolder, &ppf));
if (SUCCEEDED(hr))
{
hr = ppf->Initialize(pidlInit);
ppf->Release();
if (hr == E_NOTIMPL) // map E_NOTIMPL into success, the folder does not care
hr = S_OK;
}
}
ILFree(pidlInit);
}
if (FAILED(hr))
{
((IUnknown *)*ppunk)->Release();
*ppunk = NULL;
}
return hr;
}
HRESULT CFSFolder::_InitStgFolder(LPCIDFOLDER pidf, LPCWSTR wszPath, DWORD grfMode, REFIID riid, void **ppv)
{
IStorage *pstg;
// pick up the storage from the pidl.
// TODO: make this work on stuff other than docfiles
HRESULT hr = StgGetStorageFromFile(wszPath, grfMode, &pstg);
if (SUCCEEDED(hr))
{
hr = CStgFolder_CreateInstance(NULL, riid, ppv);
if (SUCCEEDED(hr))
{
// we need to init CStgFolder with an IStorage that it
// wraps, so pass it in through the bind context.
IBindCtx *pbc;
hr = CreateBindCtx(0, &pbc);
if (SUCCEEDED(hr))
{
hr = pbc->RegisterObjectParam(STGSTR_STGTOBIND, pstg);
if (SUCCEEDED(hr))
hr = _InitFolder(pbc, pidf, (IUnknown **)ppv);
pbc->Release();
}
if (FAILED(hr))
ATOMICRELEASE(*((IUnknown **)ppv));
}
pstg->Release();
}
return hr;
}
CFSFolderPropertyBag::CFSFolderPropertyBag(CFSFolder *pFSFolder, DWORD grfMode) :
_cRef(1), _grfMode(grfMode), _pFSFolder(pFSFolder)
{
_pFSFolder->AddRef();
}
CFSFolderPropertyBag::~CFSFolderPropertyBag()
{
_pFSFolder->Release();
// Release all the property bags
for (int i = 0; i < ARRAYSIZE(_pPropertyBags); i++)
{
if (_pPropertyBags[i])
{
_pPropertyBags[i]->Release();
}
}
}
STDMETHODIMP CFSFolderPropertyBag::QueryInterface(REFIID riid, void **ppv)
{
static const QITAB qit[] = {
QITABENT(CFSFolderPropertyBag, IPropertyBag), // IID_IPropertyBag
{ 0 },
};
return QISearch(this, qit, riid, ppv);
}
STDMETHODIMP_(ULONG) CFSFolderPropertyBag::AddRef()
{
return InterlockedIncrement(&_cRef);
}
STDMETHODIMP_(ULONG) CFSFolderPropertyBag::Release()
{
if (InterlockedDecrement(&_cRef))
return _cRef;
delete this;
return 0;
}
HRESULT CFSFolderPropertyBag::_Init(LPCIDFOLDER pidfLast)
{
TCHAR szFolderPath[MAX_PATH];
HRESULT hr = _pFSFolder->_GetPathForItem(pidfLast, szFolderPath);
if (SUCCEEDED(hr))
{
TCHAR szPath[MAX_PATH];
if (_GetIniPath((_grfMode == STGM_WRITE) || (_grfMode == STGM_READWRITE), szFolderPath, NULL, szPath))
{
// This is a customized folder (likely).
// Get an IPropertyBag on it's desktop.ini.
if (SUCCEEDED(SHCreatePropertyBagOnProfileSection(szPath, STRINI_CLASSINFO, _grfMode,
IID_PPV_ARG(IPropertyBag, &_pPropertyBags[INDEX_PROPERTYBAG_DESKTOPINI]))))
{
TCHAR szFolderType[128];
if (SUCCEEDED(SHPropertyBag_ReadStr(_pPropertyBags[INDEX_PROPERTYBAG_DESKTOPINI],
L"FolderType", szFolderType, ARRAYSIZE(szFolderType))))
{
TCHAR szRegPath[256];
StrCpyN(szRegPath, REGSTR_PATH_EXPLORER L"\\FolderClasses\\", ARRAYSIZE(szRegPath));
StrCatN(szRegPath, szFolderType, ARRAYSIZE(szRegPath));
SHCreatePropertyBagOnRegKey(HKEY_CURRENT_USER, szRegPath,
_grfMode, IID_PPV_ARG(IPropertyBag, &_pPropertyBags[INDEX_PROPERTYBAG_HKCU]));
SHCreatePropertyBagOnRegKey(HKEY_LOCAL_MACHINE, szRegPath,
_grfMode, IID_PPV_ARG(IPropertyBag, &_pPropertyBags[INDEX_PROPERTYBAG_HKLM]));
}
}
}
else
{
hr = E_FAIL;
}
}
return hr;
}
HRESULT CFSFolderPropertyBag::Read(LPCOLESTR pszPropName, VARIANT *pvar, IErrorLog *pErrorLog)
{
// We first try reading HKCU\RegKeySpecifiedInDesktopIniForTheFolder,
// then HKLM\RegKeySpecifiedInDesktopIniForTheFolder and finally
// the desktop.ini
HRESULT hr = E_FAIL;
for (int i = 0; FAILED(hr) && (i < ARRAYSIZE(_pPropertyBags)); i++)
{
if (_pPropertyBags[i])
{
hr = _pPropertyBags[i]->Read(pszPropName, pvar, pErrorLog);
}
}
return hr;
}
HRESULT CFSFolderPropertyBag::Write(LPCOLESTR pszPropName, VARIANT *pvar)
{
// We first try writing to HKCU\RegKeySpecifiedInDesktopIniForTheFolder,
// then to HKLM\RegKeySpecifiedInDesktopIniForTheFolder and finally
// to desktop.ini
HRESULT hr = E_FAIL;
for (int i = 0; FAILED(hr) && (i < ARRAYSIZE(_pPropertyBags)); i++)
{
if (_pPropertyBags[i])
{
hr = _pPropertyBags[i]->Write(pszPropName, pvar);
}
}
return hr;
}
// pidfLast can be NULL, if so create the bag on this folder
HRESULT CFSFolder::_CreateFolderPropertyBag(DWORD grfMode, LPCIDFOLDER pidfLast, REFIID riid, void **ppv)
{
*ppv = NULL;
HRESULT hr;
CFSFolderPropertyBag *pbag = new CFSFolderPropertyBag(this, grfMode);
if (pbag)
{
hr = pbag->_Init(pidfLast);
if (SUCCEEDED(hr))
{
hr = pbag->QueryInterface(riid, ppv);
}
pbag->Release();
}
else
{
hr = E_OUTOFMEMORY;
}
return hr;
}
//
// pidfLast and pszIniPath can be NULL.
// If not NULL, pidfLast is an IN param - specifies the relative pidl of a subfolder
// inside the CFSFolder object.
// If not NULL, pszIniPath is an OUT param (pointer to a buffer atleast MAX_PATH long)
// - receives the path to desktop.ini
//
BOOL CFSFolder::_CheckDefaultIni(LPCIDFOLDER pidfLast, LPTSTR pszIniPath)
{
BOOL fForceIni = FALSE;
TCHAR szPath[MAX_PATH];
if (!pszIniPath)
pszIniPath = szPath;
HRESULT hr = _GetPathForItem(pidfLast, pszIniPath);
if (SUCCEEDED(hr) && PathIsRoot(pszIniPath))
{ // Desktop.ini has to be checked for the root folders
// even if the RO or SYSTEM bits are not set on them
fForceIni = TRUE;
}
else
{
UINT csidl;
if (!pidfLast)
{
csidl = _GetCSIDL(); // Get the cached value for the current folder
}
else
{ // For subfolders, we don't have any cached values. So, compute.
_csidl = GetSpecialFolderID(pszIniPath, c_csidlSpecial, ARRAYSIZE(c_csidlSpecial));
}
switch (csidl)
{ // Desktop.ini has to be checked for the following special folders
// even if the RO or SYSTEM bits are not set on them
case CSIDL_SYSTEM:
case CSIDL_WINDOWS:
case CSIDL_PERSONAL:
fForceIni = TRUE;
break;
}
}
if (!fForceIni)
{ // Is the RO or SYSTEM bit set?
fForceIni = (_Attributes() & (FILE_ATTRIBUTE_READONLY | FILE_ATTRIBUTE_SYSTEM));
}
// Append desktop.ini to the path
if (SUCCEEDED(hr))
{
PathAppend(pszIniPath, c_szDesktopIni);
}
return fForceIni;
}
LPCTSTR CFSFolder::_BindHandlerName(REFIID riid)
{
LPCTSTR pszHandler = NULL;
if (IsEqualIID(riid, IID_IPropertySetStorage))
pszHandler = TEXT("PropertyHandler");
else if (IsEqualIID(riid, IID_IStorage))
pszHandler = TEXT("StorageHandler");
return pszHandler;
}
const CLSID CLSID_CTextIFilter = {
0xc1243ca0,
0xbf96,
0x11cd,
{ 0xb5, 0x79, 0x08, 0x00, 0x2b, 0x30, 0xbf, 0xeb }};
HRESULT LoadIFilterWithTextFallback(
WCHAR const *pwcsPath,
IUnknown *pUnkOuter,
void **ppIUnk)
{
HRESULT hr = LoadIFilter(pwcsPath, pUnkOuter, ppIUnk);
if (FAILED(hr))
{
DWORD dwFilterUnknown = 0;
DWORD cb = sizeof(dwFilterUnknown);
SHGetValue(HKEY_LOCAL_MACHINE, TEXT("SYSTEM\\CurrentControlSet\\Control\\ContentIndex"),
TEXT("FilterFilesWithUnknownExtensions"), NULL, &dwFilterUnknown, &cb);
if (dwFilterUnknown != 0)
{
IPersistFile *ppf;
hr = CoCreateInstance(CLSID_CTextIFilter, pUnkOuter, CLSCTX_INPROC_SERVER, IID_PPV_ARG(IPersistFile, &ppf));
if (SUCCEEDED(hr))
{
hr = ppf->Load(pwcsPath, STGM_READ);
if (SUCCEEDED(hr))
{
hr = ppf->QueryInterface(IID_IFilter, ppIUnk);
}
ppf->Release();
}
}
}
return hr;
}
// pidf - multi level file system only item
HRESULT CFSFolder::_Bind(LPBC pbc, LPCIDFOLDER pidf, REFIID riid, void **ppv)
{
ASSERT(_FindJunctionNext(pidf) == NULL); // no extra non file sys goo please
*ppv = NULL;
HRESULT hr;
CLSID clsid;
LPCIDFOLDER pidfLast = _FindLastID(pidf);
if (_GetBindCLSID(pbc, pidfLast, &clsid))
{
hr = SHExtCoCreateInstance(NULL, &clsid, NULL, riid, ppv);
if (SUCCEEDED(hr))
hr = _InitFolder(pbc, pidf, (IUnknown **)ppv);
if (FAILED(hr) && (E_NOINTERFACE != hr) && _IsFolder(pidfLast))
{
// the IShellFolder extension failed to load (might not be installed
// on this machine), so check if we should fall back to default to CFSFolder
UINT dwFlags;
if (_GetFolderFlags(pidf, &dwFlags) && (dwFlags & GFF_DEFAULT_TO_FS))
{
hr = CFSFolder_CreateInstance(NULL, riid, ppv);
if (SUCCEEDED(hr))
hr = _InitFolder(pbc, pidf, (IUnknown **)ppv);
}
}
}
else if (_IsFolder(pidfLast) || _IsSimpleID(pidfLast))
{
hr = CFSFolder_CreateInstance(NULL, riid, ppv);
if (SUCCEEDED(hr))
hr = _InitFolder(pbc, pidf, (IUnknown **)ppv);
}
else
hr = E_FAIL;
if (FAILED(hr))
{
// this handler has a string version
DWORD grfMode = BindCtx_GetMode(pbc, STGM_READ | STGM_SHARE_DENY_WRITE);
LPCTSTR pszHandler = _BindHandlerName(riid);
hr = _LoadHandler(pidf, grfMode, pszHandler, riid, ppv);
if (FAILED(hr))
{
WCHAR wszPath[MAX_PATH];
if (SUCCEEDED(_GetPathForItem(pidf, wszPath)))
{
if (IsEqualIID(riid, IID_IStream) && _IsFile(pidfLast))
{
hr = SHCreateStreamOnFileEx(wszPath, grfMode, FILE_ATTRIBUTE_NORMAL, FALSE, NULL, (IStream **)ppv);
}
else if (IsEqualIID(riid, IID_IPropertyBag) && _IsFolder(pidfLast))
{
hr = _CreateFolderPropertyBag(grfMode, pidf, riid, ppv);
}
else if (IsEqualIID(riid, IID_IPropertySetStorage))
{
// this is questionable at best. the caller
// should be filtering offline files, not this code.
// legacy support, I don't think anyone depends on this
// avoid offline files...
if (FILE_ATTRIBUTE_OFFLINE & pidf->wAttrs)
hr = STG_E_INVALIDFUNCTION;
else
{
hr = StgOpenStorageEx(wszPath, grfMode, STGFMT_ANY, 0, NULL, NULL, riid, ppv);
}
}
else if (IsEqualIID(riid, IID_IStorage))
{
hr = _InitStgFolder(pidf, wszPath, grfMode, riid, ppv);
}
else if (IsEqualIID(riid, IID_IMoniker))
{
hr = CreateFileMoniker(wszPath, (IMoniker **)ppv);
}
else if (IsEqualIID(riid, IID_IFilter))
{
hr = LoadIFilterWithTextFallback(wszPath, NULL, ppv);
}
}
}
}
ASSERT((SUCCEEDED(hr) && *ppv) || (FAILED(hr) && (NULL == *ppv))); // Assert hr is consistent w/out param.
return hr;
}
// returns:
// *ppidfBind - multi level file system pidl part (must free this on S_OK return)
// *ppidlRight - non file system part of pidl, continue bind down to this
//
// S_OK
// *ppidfBind needs to be freed
// S_FALSE
// pidf is a multi level file system only, bind to him
// FAILED() out of meory errors
HRESULT CFSFolder::_GetJunctionForBind(LPCIDFOLDER pidf, LPIDFOLDER *ppidfBind, LPCITEMIDLIST *ppidlRight)
{
*ppidfBind = NULL;
*ppidlRight = _FindJunctionNext(pidf);
if (*ppidlRight)
{
*ppidfBind = (LPIDFOLDER)ILClone((LPITEMIDLIST)pidf);
if (*ppidfBind)
{
// remove the part below the junction point
_ILSkip(*ppidfBind, (ULONG)((ULONG_PTR)*ppidlRight - (ULONG_PTR)pidf))->mkid.cb = 0;
return S_OK;
}
return E_OUTOFMEMORY;
}
return S_FALSE; // nothing interesting
}
HRESULT CFSFolder::GetIconOf(LPCITEMIDLIST pidl, UINT flags, int *piIndex)
{
LPCIDFOLDER pidf = _IsValidID(pidl);
if (pidf)
{
CFileSysItemString fsi(pidf);
DWORD dwFlags;
int iIcon = -1;
// WARNING: don't include junctions (_IsFileFolder(pidf))
// so junctions like briefcase get their own cusotm icon.
//
if (_IsFileFolder(pidf))
{
TCHAR szMountPoint[MAX_PATH];
TCHAR szModule[MAX_PATH];
iIcon = II_FOLDER;
if (_GetMountingPointInfo(pidf, szMountPoint, ARRAYSIZE(szMountPoint)))
{
iIcon = GetMountedVolumeIcon(szMountPoint, szModule, ARRAYSIZE(szModule));
*piIndex = Shell_GetCachedImageIndex(szModule[0] ? szModule : c_szShell32Dll, iIcon, 0);
return S_OK;
}
else
{
if (!_IsSystemFolder(pidf) && (_GetCSIDL() == CSIDL_NORMAL))
{
if (flags & GIL_OPENICON)
iIcon = II_FOLDEROPEN;
else
iIcon = II_FOLDER;
*piIndex = Shell_GetCachedImageIndex(c_szShell32Dll, iIcon, 0);
return S_OK;
}
iIcon = II_FOLDER;
dwFlags = SHCF_ICON_PERINSTANCE;
}
}
else
dwFlags = fsi.ClassFlags(TRUE);
// the icon is per-instance, try to look it up
if (dwFlags & SHCF_ICON_PERINSTANCE)
{
TCHAR szFullPath[MAX_PATH];
DWORD uid = _GetUID(pidf); // get a unique identifier for this file.
if (uid == 0)
return S_FALSE;
if (FAILED(_GetPathForItem(pidf, szFullPath)))
{
// fall back to the relative name if we can't get the full path
lstrcpyn(szFullPath, fsi.FSName(), ARRAYSIZE(szFullPath));
}
*piIndex = LookupIconIndex(szFullPath, uid, flags | GIL_NOTFILENAME);
if (*piIndex != -1)
return S_OK;
// async extract (GIL_ASYNC) support
//
// we cant find the icon in the icon cache, we need to do real work
// to get the icon. if the caller specified GIL_ASYNC
// dont do the work, return E_PENDING forcing the caller to call
// back later to get the real icon.
//
// when returing E_PENDING we must fill in a default icon index
if (flags & GIL_ASYNC)
{
// come up with a default icon and return E_PENDING
if (_IsFolder(pidf))
iIcon = II_FOLDER;
else if (!(dwFlags & SHCF_HAS_ICONHANDLER) && PathIsExe(fsi.FSName()))
iIcon = II_APPLICATION;
else
iIcon = II_DOCNOASSOC;
*piIndex = Shell_GetCachedImageIndex(c_szShell32Dll, iIcon, 0);
TraceMsg(TF_IMAGE, "Shell_GetCachedImageIndex(%d) returned = %d", iIcon, *piIndex);
return E_PENDING; // we will be called back later for the real one
}
// If this is a folder, see if this folder has Per-Instance folder icon
// we do this here because it's too expensive to open a desktop.ini
// file and see what's in there. Most of the cases we will just hit
// the above cases
if (_IsSystemFolder(pidf))
{
if (!_GetFolderIconPath(pidf, NULL, 0, NULL))
{
// Note: the iIcon value has already been computed at the start of this funciton
ASSERT(iIcon != -1);
*piIndex = Shell_GetCachedImageIndex(c_szShell32Dll, iIcon, 0);
return S_OK;
}
}
//
// look up icon using IExtractIcon, this will load handler iff needed
// by calling ::GetUIObjectOf
//
IShellFolder *psf;
HRESULT hr = QueryInterface(IID_PPV_ARG(IShellFolder, &psf));
if (SUCCEEDED(hr))
{
hr = SHGetIconFromPIDL(psf, NULL, (LPCITEMIDLIST)pidf, flags, piIndex);
psf->Release();
}
//
// remember this perinstance icon in the cache so we dont
// need to load the handler again.
//
// SHGetIconFromPIDL will always return a valid image index
// (it may default to a standard one) but it will fail
// if the file cant be accessed or some other sort of error.
// we dont want to cache in this case.
//
if (*piIndex != -1 && SUCCEEDED(hr) && (dwFlags & SHCF_HAS_ICONHANDLER))
{
int iIndexRetry;
ENTERCRITICAL;
//
// Inside the critical section, make sure the icon isn't already
// loaded, and if its not, then add it.
//
iIndexRetry = LookupIconIndex(szFullPath, uid, flags | GIL_NOTFILENAME);
if (iIndexRetry == -1)
{
AddToIconTable(szFullPath, uid, flags | GIL_NOTFILENAME, *piIndex);
}
LEAVECRITICAL;
}
return *piIndex == -1 ? S_FALSE : S_OK;
}
// icon is per-class dwFlags has the image index
*piIndex = (dwFlags & SHCF_ICON_INDEX);
return S_OK;
}
else
{
ASSERT(ILIsEmpty(pidl) || SIL_GetType(pidl) == SHID_ROOT_REGITEM); // regitems gives us these
return S_FALSE;
}
}
HANDLE g_hOverlayMgrCounter = NULL; // Global count of Overlay Manager changes.
int g_lOverlayMgrPerProcessCount = 0; // Per process count of Overlay Manager changes.
//
// Use this function to obtain address of the singleton icon overlay manager.
// If the function succeeds, caller is responsible for calling Release() through
// the returned interface pointer.
// The function ensures that the manager is initialized and up to date.
//
STDAPI GetIconOverlayManager(IShellIconOverlayManager **ppsiom)
{
HRESULT hr = E_FAIL;
if (IconOverlayManagerInit())
{
//
// Is a critsec for g_psiom required here you ask?
//
// No. The first call to IconOverlayInit in any process creates
// the overlay manager object and initializes g_psiom. This creation
// contributes 1 to the object's ref count. Subsequent calls to
// GetIconOverlayManager add to the ref count and the caller is
// responsible for decrementing the count through Release().
// The original ref count of 1 is not removed until
// IconOverlayManagerTerminate is called which happens only
// during PROCESS_DETACH. Therefore, the manager referenced by g_psiom
// in this code block will always be valid and a critsec is not
// required.
//
//
// ID for the global overlay manager counter.
//
static const GUID GUID_Counter = { /* 090851a5-eb96-11d2-8be4-00c04fa31a66 */
0x090851a5,
0xeb96,
0x11d2,
{0x8b, 0xe4, 0x00, 0xc0, 0x4f, 0xa3, 0x1a, 0x66}
};
g_psiom->AddRef();
HANDLE hCounter = SHGetCachedGlobalCounter(&g_hOverlayMgrCounter, &GUID_Counter);
long lGlobalCount = SHGlobalCounterGetValue(hCounter);
if (lGlobalCount != g_lOverlayMgrPerProcessCount)
{
//
// Per-process counter is out of sync with the global counter.
// This means someone called SHLoadNonloadedIconOverlayIdentifiers
// so we must load any non-loaded identifiers from the registry.
//
g_psiom->LoadNonloadedOverlayIdentifiers();
g_lOverlayMgrPerProcessCount = lGlobalCount;
}
*ppsiom = g_psiom;
hr = S_OK;
}
return hr;
}
BOOL IconOverlayManagerInit()
{
if (!g_psiom)
{
IShellIconOverlayManager* psiom;
if (SUCCEEDED(SHCoCreateInstance(NULL, &CLSID_CFSIconOverlayManager, NULL, IID_PPV_ARG(IShellIconOverlayManager, &psiom))))
{
if (SHInterlockedCompareExchange((void **)&g_psiom, psiom, 0))
psiom->Release();
}
}
return BOOLFROMPTR(g_psiom);
}
void IconOverlayManagerTerminate()
{
ASSERTDLLENTRY; // does not require a critical section
IShellIconOverlayManager *psiom = (IShellIconOverlayManager *)InterlockedExchangePointer((void **)&g_psiom, 0);
if (psiom)
psiom->Release();
if (NULL != g_hOverlayMgrCounter)
{
CloseHandle(g_hOverlayMgrCounter);
g_hOverlayMgrCounter = NULL;
}
}
STDAPI SHLoadNonloadedIconOverlayIdentifiers(void)
{
//
// This will cause the next call GetIconOverlayManager() call in each process
// to load any non-loaded icon overlay identifiers.
//
if (g_hOverlayMgrCounter)
SHGlobalCounterIncrement(g_hOverlayMgrCounter);
return S_OK;
}
HRESULT CFSFolder::_GetOverlayInfo(LPCITEMIDLIST pidl, int * pIndex, DWORD dwFlags)
{
HRESULT hr = E_FAIL;
LPCIDFOLDER pidf = _IsValidID(pidl);
*pIndex = 0;
if (!pidf)
{
ASSERT(SIL_GetType(pidl) != SHID_ROOT_REGITEM); // CRegFolder should have handled it
return S_FALSE;
}
ASSERT(pidl == ILFindLastID(pidl));
if (IconOverlayManagerInit())
{
int iReservedID = -1;
WCHAR wszPath[MAX_PATH];
hr = _GetPathForItem(pidf, wszPath);
if (SUCCEEDED(hr))
{
IShellIconOverlayManager *psiom;
// The order of the "if" statements here is significant
if (_IsFile(pidf) && (_GetClassFlags(pidf) & SHCF_IS_LINK))
iReservedID = SIOM_RESERVED_LINK;
else
{
USES_CONVERSION;
LPCTSTR szPath = W2CT(wszPath);
if (_IsFolder(pidf) && (IsShared(szPath, FALSE)))
iReservedID = SIOM_RESERVED_SHARED;
else if (FILE_ATTRIBUTE_OFFLINE & pidf->wAttrs)
iReservedID = SIOM_RESERVED_SLOWFILE;
}
hr = GetIconOverlayManager(&psiom);
if (SUCCEEDED(hr))
{
if (iReservedID != -1)
hr = psiom->GetReservedOverlayInfo(wszPath, pidf->wAttrs, pIndex, dwFlags, iReservedID);
else
hr = psiom->GetFileOverlayInfo(wszPath, pidf->wAttrs, pIndex, dwFlags);
psiom->Release();
}
}
}
return hr;
}
HRESULT CFSFolder::GetOverlayIndex(LPCITEMIDLIST pidl, int * pIndex)
{
HRESULT hr = E_INVALIDARG;
ASSERT(pIndex);
if (pIndex)
hr = (*pIndex == OI_ASYNC) ? E_PENDING :
_GetOverlayInfo(pidl, pIndex, SIOM_OVERLAYINDEX);
return hr;
}
HRESULT CFSFolder::GetOverlayIconIndex(LPCITEMIDLIST pidl, int * pIconIndex)
{
return _GetOverlayInfo(pidl, pIconIndex, SIOM_ICONINDEX);
}
// CFSFolder : IPersist, IPersistFolder, IPersistFolder2, IPersistFolderAlias Members
HRESULT CFSFolder::GetClassID(CLSID *pclsid)
{
if (!IsEqualCLSID(_clsidBind, CLSID_NULL))
{
*pclsid = _clsidBind;
}
else
{
*pclsid = CLSID_ShellFSFolder;
}
return S_OK;
}
HRESULT CFSFolder::Initialize(LPCITEMIDLIST pidl)
{
_Reset();
return SHILClone(pidl, &_pidl);
}
HRESULT CFSFolder::GetCurFolder(LPITEMIDLIST *ppidl)
{
return GetCurFolderImpl(_pidl, ppidl);
}
LPTSTR StrDupUnicode(const WCHAR *pwsz)
{
if (*pwsz)
{
USES_CONVERSION;
return StrDup(W2CT(pwsz));
}
return NULL;
}
HRESULT CFSFolder::_SetStgMode(DWORD grfFlags)
{
HRESULT hr = S_OK;
if (grfFlags & STGM_TRANSACTED)
hr = E_INVALIDARG;
if (SUCCEEDED(hr))
_grfFlags = grfFlags;
return hr;
}
HRESULT CFSFolder::InitializeEx(IBindCtx *pbc, LPCITEMIDLIST pidlRoot,
const PERSIST_FOLDER_TARGET_INFO *pfti)
{
HRESULT hr = Initialize(pidlRoot);
if (SUCCEEDED(hr))
{
if (pfti)
{
_dwAttributes = pfti->dwAttributes;
if (pfti->pidlTargetFolder ||
pfti->szTargetParsingName[0] ||
(pfti->csidl != -1))
{
if ((pfti->csidl != -1) && (pfti->csidl & CSIDL_FLAG_PFTI_TRACKTARGET))
{
// For tracking target, all other fields must be null.
if (!pfti->pidlTargetFolder &&
!pfti->szTargetParsingName[0] &&
!pfti->szNetworkProvider[0])
{
_csidlTrack = pfti->csidl & (~CSIDL_FLAG_MASK | CSIDL_FLAG_CREATE);
}
else
{
hr = E_INVALIDARG;
}
}
else
{
_pidlTarget = ILClone(pfti->pidlTargetFolder); // on NULL returns NULL
_pszPath = StrDupUnicode(pfti->szTargetParsingName);
_pszNetProvider = StrDupUnicode(pfti->szNetworkProvider);
if (pfti->csidl != -1)
_csidl = pfti->csidl & (~CSIDL_FLAG_MASK | CSIDL_FLAG_CREATE);
}
}
}
if (SUCCEEDED(hr))
{
hr = _SetStgMode(BindCtx_GetMode(pbc, STGM_READ | STGM_SHARE_DENY_WRITE));
}
if (SUCCEEDED(hr) && pbc)
{
_fDontForceCreate = BindCtx_ContainsObject(pbc, STR_DONT_FORCE_CREATE);
}
}
return hr;
}
HRESULT CFSFolder::GetFolderTargetInfo(PERSIST_FOLDER_TARGET_INFO *pfti)
{
HRESULT hr = S_OK;
ZeroMemory(pfti, sizeof(*pfti));
_GetPathForItem(NULL, pfti->szTargetParsingName);
if (_pidlTarget)
hr = SHILClone(_pidlTarget, &pfti->pidlTargetFolder);
if (_pszNetProvider)
SHTCharToUnicode(_pszNetProvider, pfti->szNetworkProvider, ARRAYSIZE(pfti->szNetworkProvider));
pfti->dwAttributes = _dwAttributes;
if (_csidlTrack >= 0)
pfti->csidl = _csidlTrack | CSIDL_FLAG_PFTI_TRACKTARGET;
else
pfti->csidl = _GetCSIDL();
return hr;
}
STDAPI CFSFolder_CreateFolder(IUnknown *punkOuter, LPBC pbc, LPCITEMIDLIST pidl,
const PERSIST_FOLDER_TARGET_INFO *pfti, REFIID riid, void **ppv)
{
*ppv = NULL;
HRESULT hr;
CFSFolder *pfolder = new CFSFolder(punkOuter);
if (pfolder)
{
hr = pfolder->InitializeEx(pbc, pidl, pfti);
if (SUCCEEDED(hr))
hr = pfolder->_GetInner()->QueryInterface(riid, ppv);
pfolder->_GetInner()->Release();
}
else
hr = E_OUTOFMEMORY;
return hr;
}
// COM object creation entry point for CLSID_ShellFSFolder
STDAPI CFSFolder_CreateInstance(IUnknown *punkOuter, REFIID riid, void **ppv)
{
return CFSFolder_CreateFolder(punkOuter, NULL, &c_idlDesktop, NULL, riid, ppv);
}
BOOL CFSFolder::_IsSlowPath()
{
if (_bSlowPath == INVALID_PATHSPEED)
{
TCHAR szPath[MAX_PATH];
_GetPath(szPath);
_bSlowPath = PathIsSlow(szPath, _Attributes()) ? TRUE : FALSE;
}
return _bSlowPath;
}
HRESULT CFSFolder::_GetToolTipForItem(LPCIDFOLDER pidf, REFIID riid, void **ppv)
{
IQueryAssociations *pqa;
HRESULT hr = _AssocCreate(pidf, IID_PPV_ARG(IQueryAssociations, &pqa));
if (SUCCEEDED(hr))
{
WCHAR szText[INFOTIPSIZE];
// If we are looking at a folder over a slow connection,
// show only quickly accessible properties
ASSOCSTR assocstr = _IsSlowPath() ? ASSOCSTR_QUICKTIP : ASSOCSTR_INFOTIP;
hr = pqa->GetString(0, assocstr, NULL, szText, (DWORD *)MAKEINTRESOURCE(SIZECHARS(szText)));
if (SUCCEEDED(hr))
{
hr = CreateInfoTipFromItem(SAFECAST(this, IShellFolder2 *), (LPCITEMIDLIST)pidf, szText, riid, ppv);
if (SUCCEEDED(hr) && _IsFileFolder(pidf))
{
IUnknown *punk = (IUnknown *)*ppv;
*ppv = NULL;
WCHAR szPath[MAX_PATH];
hr = _GetPathForItem(pidf, szPath);
if (SUCCEEDED(hr))
hr = CFolderInfoTip_CreateInstance(punk, szPath, riid, ppv);
punk->Release();
}
}
pqa->Release();
}
return hr;
}
//
// Call the shell file operation code to delete recursively the given directory,
// don't show any UI.
//
HRESULT CFSFolder::_Delete(LPCWSTR pszFile)
{
SHFILEOPSTRUCT fos = { 0 };
TCHAR szFile[MAX_PATH + 1];
SHUnicodeToTChar(pszFile, szFile, MAX_PATH);
// szFile is a double-zero terminated list of files.
// we can't just zero-init the szFile string to start with,
// since in debug SHUnicodeToTChar will bonk the uncopied part
// of the string with noise.
szFile[lstrlen(szFile) + 1] = 0;
fos.wFunc = FO_DELETE;
fos.pFrom = szFile;
fos.fFlags = FOF_NOCONFIRMATION | FOF_SILENT;
return SHFileOperation(&fos) ? E_FAIL : S_OK;
}
//
// Do a path combine thunking accordingly
//
HRESULT CFSFolder::_GetFullPath(LPCWSTR pszRelPath, LPWSTR pszFull)
{
WCHAR szPath[MAX_PATH];
_GetPathForItem(NULL, szPath);
PathCombineW(pszFull, szPath, pszRelPath);
return S_OK; // for now
}
HRESULT _FileExists(LPCWSTR pszPath, DWORD *pdwAttribs)
{
return PathFileExistsAndAttributesW(pszPath, pdwAttribs) ? S_OK : STG_E_FILENOTFOUND;
}
// IStorage
STDMETHODIMP CFSFolder::CreateStream(LPCWSTR pwcsName, DWORD grfMode, DWORD res1, DWORD res2, IStream **ppstm)
{
HRESULT hr = _OpenCreateStream(pwcsName, grfMode, ppstm, TRUE);
if (SUCCEEDED(hr))
{
WCHAR szFullPath[MAX_PATH];
_GetFullPath(pwcsName, szFullPath);
SHChangeNotify(SHCNE_CREATE, SHCNF_PATH, szFullPath, NULL);
}
return hr;
}
STDMETHODIMP CFSFolder::OpenStream(LPCWSTR pwcsName, void *res1, DWORD grfMode, DWORD res2, IStream **ppstm)
{
return _OpenCreateStream(pwcsName, grfMode, ppstm, FALSE);
}
HRESULT CFSFolder::_OpenCreateStream(LPCWSTR pwcsName, DWORD grfMode, IStream **ppstm, BOOL fCreate)
{
*ppstm = NULL;
if (!pwcsName)
return STG_E_INVALIDPARAMETER;
WCHAR szFullPath[MAX_PATH];
_GetFullPath(pwcsName, szFullPath);
HRESULT hr = SHCreateStreamOnFileEx(szFullPath, grfMode, FILE_ATTRIBUTE_NORMAL, fCreate, NULL, ppstm);
return MapWin32ErrorToSTG(hr);
}
STDMETHODIMP CFSFolder::CreateStorage(LPCWSTR pwcsName, DWORD grfMode, DWORD res1, DWORD res2, IStorage **ppstg)
{
return _OpenCreateStorage(pwcsName, grfMode, ppstg, TRUE);
}
STDMETHODIMP CFSFolder::OpenStorage(LPCWSTR pwcsName, IStorage *pstgPriority, DWORD grfMode, SNB snbExclude, DWORD res, IStorage **ppstg)
{
return _OpenCreateStorage(pwcsName, grfMode, ppstg, FALSE);
}
HRESULT CFSFolder::_OpenCreateStorage(LPCWSTR pwcsName, DWORD grfMode, IStorage **ppstg, BOOL fCreate)
{
*ppstg = NULL;
if (!pwcsName)
return STG_E_INVALIDPARAMETER;
if (grfMode &
~(STGM_READ |
STGM_WRITE |
STGM_READWRITE |
STGM_SHARE_DENY_NONE |
STGM_SHARE_DENY_READ |
STGM_SHARE_DENY_WRITE |
STGM_SHARE_EXCLUSIVE |
STGM_CREATE ))
{
return STG_E_INVALIDPARAMETER;
}
// if the storage doesn't exist then lets create it, then drop into the
// open storage to do the right thing.
WCHAR szFullPath[MAX_PATH];
_GetFullPath(pwcsName, szFullPath);
DWORD dwAttributes;
HRESULT hr = _FileExists(szFullPath, &dwAttributes);
if (SUCCEEDED(hr))
{
if (dwAttributes & FILE_ATTRIBUTE_DIRECTORY)
{
if (fCreate)
{
// an object exists, we must fail grfMode == STGM_FAILIFTHERE, or
// the object that exists is not a directory.
//
// if the STGM_CREATE flag is set and the object exists we will
// delete the existing storage.
// Check to make sure only one existence flag is specified
// FAILIFTHERE is zero so it can't be checked
if (STGM_FAILIFTHERE == (grfMode & (STGM_CREATE | STGM_CONVERT)))
hr = STG_E_FILEALREADYEXISTS;
else if (grfMode & STGM_CREATE)
{
// If they have not passed STGM_FAILIFTHERE, we'll replace an existing
// folder even if its readonly or system. Its up to the caller to make
// such filesystem-dependant checks first if they want to prevent that,
// as there's no way to pass information about whether we should or not
// down into CreateStorage
if (dwAttributes & (FILE_ATTRIBUTE_SYSTEM | FILE_ATTRIBUTE_READONLY))
SetFileAttributes(szFullPath, dwAttributes & ~(FILE_ATTRIBUTE_SYSTEM | FILE_ATTRIBUTE_READONLY));
hr = _Delete(szFullPath);
//
// I don't trust the result from SHFileOperation, so I consider success
// to be iff the directory is -gone-
//
if (FAILED(_FileExists(szFullPath, &dwAttributes)))
{
DWORD err = SHCreateDirectoryExW(NULL, szFullPath, NULL);
hr = HRESULT_FROM_WIN32(err);
}
else
{
// We couldn't remove the existing directory, so return an error,
// using what _Delete() said or, it if didn't return an error, E_FAIL
return (FAILED(hr) ? hr : E_FAIL);
}
}
else
hr = STG_E_INVALIDPARAMETER;
}
}
else
hr = E_FAIL; // a file, not a folder!
}
else
{
// the object doesn't exist, and they have not set the STGM_CREATE, nor
// is this a ::CreateStorage call.
hr = STG_E_FILENOTFOUND;
if (fCreate)
{
DWORD err = SHCreateDirectoryExW(NULL, szFullPath, NULL);
hr = HRESULT_FROM_WIN32(err);
}
}
// create a directory (we assume this will always succeed)
if (SUCCEEDED(hr))
{
LPITEMIDLIST pidl;
hr = ParseDisplayName(NULL, NULL, (LPWSTR)pwcsName, NULL, &pidl, NULL); // const -> non const
if (SUCCEEDED(hr))
{
hr = BindToObject(pidl, NULL, IID_PPV_ARG(IStorage, ppstg));
ILFree(pidl);
}
}
return hr;
}
STDMETHODIMP CFSFolder::CopyTo(DWORD ciidExclude, const IID *rgiidExclude, SNB snbExclude, IStorage *pstgDest)
{
return E_NOTIMPL;
}
// CFSFolder::MoveElementTo
//
// Copies or moves a source file (stream) to a destination storage. The stream
// itself, in this case our filestream object, does the actual work of moving
// the data around.
STDMETHODIMP CFSFolder::MoveElementTo(LPCWSTR pwcsName, IStorage *pstgDest, LPCWSTR pwcsNewName, DWORD grfFlags)
{
return StgMoveElementTo(SAFECAST(this, IShellFolder *), SAFECAST(this, IStorage *), pwcsName, pstgDest, pwcsNewName, grfFlags);
}
STDMETHODIMP CFSFolder::Commit(DWORD grfCommitFlags)
{
return S_OK; // changes are commited as we go, so return S_OK;
}
STDMETHODIMP CFSFolder::Revert()
{
return E_NOTIMPL; // changes are commited as we go, so cannot implement this.
}
STDMETHODIMP CFSFolder::EnumElements(DWORD res1, void *res2, DWORD res3, IEnumSTATSTG **ppenum)
{
HRESULT hr;
CFSFolderEnumSTATSTG *penum = new CFSFolderEnumSTATSTG(this);
if (penum)
{
*ppenum = (IEnumSTATSTG *) penum;
hr = S_OK;
}
else
{
*ppenum = NULL;
hr = E_OUTOFMEMORY;
}
return hr;
}
STDMETHODIMP CFSFolder::DestroyElement(LPCWSTR pwcsName)
{
if (!pwcsName)
return STG_E_INVALIDPARAMETER;
WCHAR szFullPath[MAX_PATH];
_GetFullPath(pwcsName, szFullPath);
return _Delete(szFullPath);
}
STDMETHODIMP CFSFolder::RenameElement(LPCWSTR pwcsOldName, LPCWSTR pwcsNewName)
{
if (!pwcsOldName || !pwcsNewName)
return STG_E_INVALIDPARAMETER;
WCHAR szOldPath[MAX_PATH];
_GetFullPath(pwcsOldName, szOldPath);
HRESULT hr = _FileExists(szOldPath, NULL);
if (SUCCEEDED(hr))
{
WCHAR szNewPath[MAX_PATH];
_GetFullPath(pwcsNewName, szNewPath);
hr = _FileExists(szNewPath, NULL);
if (FAILED(hr))
{
if (MoveFileW(szOldPath, szNewPath))
hr = S_OK;
else
hr = E_FAIL;
}
else
hr = STG_E_FILEALREADYEXISTS;
}
return hr;
}
STDMETHODIMP CFSFolder::SetElementTimes(LPCWSTR pwcsName, const FILETIME *pctime, const FILETIME *patime, const FILETIME *pmtime)
{
if (!pwcsName)
return STG_E_INVALIDPARAMETER;
WCHAR szFullPath[MAX_PATH];
_GetFullPath(pwcsName, szFullPath);
HRESULT hr = S_OK;
HANDLE hFile = CreateFileW(szFullPath, GENERIC_WRITE, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL);
if (INVALID_HANDLE_VALUE != hFile)
{
if (!SetFileTime(hFile, pctime, patime, pmtime))
hr = HRESULT_FROM_WIN32(GetLastError());
CloseHandle(hFile);
}
else
{
hr = STG_E_FILENOTFOUND;
}
return hr;
}
STDMETHODIMP CFSFolder::SetClass(REFCLSID clsid)
{
return E_NOTIMPL;
}
STDMETHODIMP CFSFolder::SetStateBits(DWORD grfStateBits, DWORD grfMask)
{
return E_NOTIMPL;
}
STDMETHODIMP CFSFolder::Stat(STATSTG *pstatstg, DWORD grfStatFlag)
{
HRESULT hr = E_FAIL;
ZeroMemory(pstatstg, sizeof(STATSTG)); // per COM conventions
TCHAR szPath[MAX_PATH];
_GetPath(szPath);
HANDLE hFile = CreateFile(szPath, FILE_READ_ATTRIBUTES,
FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL | FILE_FLAG_BACKUP_SEMANTICS, NULL);
if (hFile != INVALID_HANDLE_VALUE)
{
BY_HANDLE_FILE_INFORMATION bhfi;
if (GetFileInformationByHandle(hFile, &bhfi))
{
ASSERT(bhfi.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY);
pstatstg->type = STGTY_STORAGE;
pstatstg->mtime = bhfi.ftLastWriteTime;
pstatstg->ctime = bhfi.ftCreationTime;
pstatstg->atime = bhfi.ftLastAccessTime;
pstatstg->cbSize.HighPart = bhfi.nFileSizeHigh;
pstatstg->cbSize.LowPart = bhfi.nFileSizeLow;
pstatstg->grfMode = _grfFlags;
pstatstg->reserved = bhfi.dwFileAttributes;
hr = S_OK;
if (!(grfStatFlag & STATFLAG_NONAME))
{
hr = SHStrDup(PathFindFileName(szPath), &pstatstg->pwcsName);
}
}
CloseHandle(hFile);
}
else
hr = HRESULT_FROM_WIN32(GetLastError());
return hr;
}
// ITransferDest
STDMETHODIMP CFSFolder::Advise(ITransferAdviseSink *pAdvise, DWORD *pdwCookie)
{
if (_pAdvise)
return E_FAIL;
_pAdvise = pAdvise;
_pAdvise->AddRef();
if (pdwCookie)
*pdwCookie = 1;
return S_OK;
}
STDMETHODIMP CFSFolder::Unadvise(DWORD dwCookie)
{
if (1 != dwCookie)
return E_INVALIDARG;
if (_pAdvise)
{
ATOMICRELEASE(_pAdvise);
return S_OK;
}
return S_FALSE;
}
HRESULT CFSFolder::_GetStatStgFromItemName(LPCTSTR szName, STATSTG * pstat)
{
WIN32_FIND_DATA fd;
HRESULT hr = STG_E_FILENOTFOUND;
HANDLE hFile = FindFirstFile(szName, &fd);
if (hFile != INVALID_HANDLE_VALUE)
{
hr = StatStgFromFindData(&fd, STATFLAG_DEFAULT, pstat);
FindClose(hFile);
}
return hr;
}
STDMETHODIMP CFSFolder::OpenElement(const WCHAR *pwcsName, STGXMODE grfMode, DWORD *pdwType, REFIID riid, void **ppunk)
{
return E_NOTIMPL;
}
STDMETHODIMP CFSFolder::CreateElement(const WCHAR *pwcsName, IShellItem *psiTemplate, STGXMODE grfMode, DWORD dwType, REFIID riid, void **ppunk)
{
return E_NOTIMPL;
}
STDMETHODIMP CFSFolder::MoveElement(IShellItem *psiItem, WCHAR *pwcsNewName, STGXMOVE grfOptions)
{
return E_NOTIMPL;
}
STDMETHODIMP CFSFolder::DestroyElement(const WCHAR * pwcsName, STGXDESTROY grfOptions)
{
return E_NOTIMPL;
}
// Create a storage object for the specified path, returning a suitable
// IStorage (or error).
//
// In:
// pwszPath -> directory
// grfMode -> flags passed to IStorage::CreateStorage
// ppstg -> receieves the storage object
//
// Out:
// HRESULT
STDAPI SHCreateStorageOnDirectory(LPCWSTR pszPath, DWORD grfMode, IStorage **ppstg)
{
*ppstg = NULL;
TCHAR szPath[MAX_PATH];
SHUnicodeToTChar(pszPath, szPath, ARRAYSIZE(szPath));
LPITEMIDLIST pidl;
HRESULT hr = SHILCreateFromPath(szPath, &pidl, NULL);
if (SUCCEEDED(hr))
{
hr = SHBindToObject(NULL, IID_X_PPV_ARG(IStorage, pidl, ppstg));
ILFree(pidl);
}
return hr;
}
STDAPI SHCreatePropStgOnFolder(LPCTSTR pszFolder, DWORD grfMode, IPropertySetStorage **ppss);
HRESULT CFSFolder::_LoadPropHandler()
{
HRESULT hr = S_OK;
if (_pstg)
{
hr = S_OK;
}
else
{
TCHAR szPath[MAX_PATH];
_GetPath(szPath);
hr = StgOpenStorageOnFolder(szPath, _grfFlags, IID_PPV_ARG(IPropertySetStorage, &_pstg));
// if (FAILED(hr))
// hr = SHCreatePropStgOnFolder(szPath, _grfFlags, &_pstg);
}
return hr;
}
STDMETHODIMP CFSFolder::Create(REFFMTID fmtid, const CLSID *pclsid, DWORD grfFlags,
DWORD grfMode, IPropertyStorage **pppropstg)
{
HRESULT hr = _LoadPropHandler();
if (SUCCEEDED(hr))
hr = _pstg->Create(fmtid, pclsid, grfFlags, grfMode, pppropstg);
return hr;
}
STDMETHODIMP CFSFolder::Open(REFFMTID fmtid, DWORD grfMode, IPropertyStorage **pppropstg)
{
HRESULT hr = _LoadPropHandler();
if (SUCCEEDED(hr))
hr = _pstg->Open(fmtid, grfMode, pppropstg);
return hr;
}
STDMETHODIMP CFSFolder::Delete(REFFMTID fmtid)
{
HRESULT hr = _LoadPropHandler();
if (SUCCEEDED(hr))
hr = _pstg->Delete(fmtid);
return hr;
}
STDMETHODIMP CFSFolder::Enum(IEnumSTATPROPSETSTG ** ppenum)
{
HRESULT hr = _LoadPropHandler();
if (SUCCEEDED(hr))
hr = _pstg->Enum(ppenum);
return hr;
}
// IItemNameLimits methods
#define INVALID_NAME_CHARS L"\\/:*?\"<>|"
STDMETHODIMP CFSFolder::GetValidCharacters(LPWSTR *ppwszValidChars, LPWSTR *ppwszInvalidChars)
{
*ppwszValidChars = NULL;
return SHStrDup(INVALID_NAME_CHARS, ppwszInvalidChars);
}
STDMETHODIMP CFSFolder::GetMaxLength(LPCWSTR pszName, int *piMaxNameLen)
{
TCHAR szPath[MAX_PATH];
BOOL fShowExtension = _DefaultShowExt();
LPITEMIDLIST pidl;
StrCpyN(szPath, pszName, ARRAYSIZE(szPath));
HRESULT hr = ParseDisplayName(NULL, NULL, szPath, NULL, &pidl, NULL);
if (SUCCEEDED(hr))
{
LPCIDFOLDER pidf = _IsValidID(pidl);
if (pidf)
{
fShowExtension = _ShowExtension(pidf);
}
ILFree(pidl);
}
hr = _GetPath(szPath);
if (SUCCEEDED(hr))
{
if (PathAppend(szPath, pszName))
hr = GetCCHMaxFromPath(szPath, (UINT *)piMaxNameLen, fShowExtension);
else
hr = E_FAIL;
}
return hr;
}
// ISetFolderEnumRestriction methods
STDMETHODIMP CFSFolder::SetEnumRestriction(DWORD dwRequired, DWORD dwForbidden)
{
_dwEnumRequired = dwRequired;
_dwEnumForbidden = dwForbidden;
return S_OK;
}
// IOleCommandTarget stuff
STDMETHODIMP CFSFolder::QueryStatus(const GUID *pguidCmdGroup, ULONG cCmds, OLECMD rgCmds[], OLECMDTEXT *pcmdtext)
{
HRESULT hr = OLECMDERR_E_UNKNOWNGROUP;
if (pguidCmdGroup == NULL)
{
for (UINT i = 0; i < cCmds; i++)
{
// ONLY say that we support the stuff we support in ::OnExec
switch (rgCmds[i].cmdID)
{
case OLECMDID_REFRESH:
rgCmds[i].cmdf = OLECMDF_ENABLED;
break;
default:
rgCmds[i].cmdf = 0;
break;
}
hr = S_OK;
}
}
return hr;
}
STDMETHODIMP CFSFolder::Exec(const GUID *pguidCmdGroup, DWORD nCmdID, DWORD nCmdexecopt, VARIANTARG *pvarargIn, VARIANTARG *pvarargOut)
{
HRESULT hr = OLECMDERR_E_UNKNOWNGROUP;
if (pguidCmdGroup == NULL)
{
switch (nCmdID)
{
case OLECMDID_REFRESH:
_dwAttributes = -1;
_bUpdateExtendedCols = TRUE;
_tbDefShowExt = TRIBIT_UNDEFINED;
_tbOfflineCSC = TRIBIT_UNDEFINED;
hr = S_OK;
break;
}
}
return hr;
}
// global hook in the SHChangeNotify() dispatcher. note we get all change notifies
// here so be careful!
STDAPI CFSFolder_IconEvent(LONG lEvent, LPCITEMIDLIST pidl, LPCITEMIDLIST pidlExtra)
{
switch (lEvent)
{
case SHCNE_ASSOCCHANGED:
{
FlushFileClass(); // flush them all
HWND hwnd = GetDesktopWindow();
if (IsWindow(hwnd))
PostMessage(hwnd, DTM_SETUPAPPRAN, 0, 0);
}
break;
}
return S_OK;
}
//
// 317617 - Hacky update for the icon cache - ZekeL - 19-APR-2001
// this is for defview to invalidate icon indeces that are indirected
// specifically if you have a LNK file and its target changes icons
// (like a CD will), then the LNK is updated by defview processing the
// SHCNE_UPDATEIMAGE and noticing that one of its items also matches
// this image index.
//
// the righteous fix is to make SCN call into the fileicon cache
// and reverse lookup any entries that match the icon index and invalidate
// them. that way we wouldnt miss anything.
//
STDAPI_(void) CFSFolder_UpdateIcon(IShellFolder *psf, LPCITEMIDLIST pidl)
{
LPCIDFOLDER pidf = CFSFolder::_IsValidID(pidl);
if (pidf)
{
TCHAR szName[MAX_PATH];
if (SUCCEEDED(DisplayNameOf(psf, pidl, SHGDN_FORPARSING, szName, ARRAYSIZE(szName))))
{
RemoveFromIconTable(szName);
}
}
}
// ugly wrappers for external clients, remove these as possible
STDAPI CFSFolder_CompareNames(LPCIDFOLDER pidf1, LPCIDFOLDER pidf2)
{
CFileSysItemString fsi1(pidf1), fsi2(pidf2);
return ResultFromShort((short)lstrcmpi(fsi1.FSName(), fsi2.FSName()));
}
STDAPI_(DWORD) CFSFolder_PropertiesThread(void *pv)
{
return CFSFolder::_PropertiesThread(pv);
}
STDAPI_(LPCIDFOLDER) CFSFolder_IsValidID(LPCITEMIDLIST pidl)
{
return CFSFolder::_IsValidID(pidl);
}
STDAPI_(BOOL) CFSFolder_IsCommonItem(LPCITEMIDLIST pidl)
{
return CFSFolder::_IsCommonItem(pidl);
}
CFSIconManager::CFSIconManager()
{
_wszPath[0] = NULL;
_cRef = 1;
}
HRESULT CFSIconManager::_Init(LPCITEMIDLIST pidl, IShellFolder *psf)
{
HRESULT hr = S_OK;
if ((psf == NULL) || (pidl == NULL))
hr = E_INVALIDARG;
if (SUCCEEDED(hr))
hr = DisplayNameOf(psf, pidl, SHGDN_FORPARSING, _wszPath, ARRAYSIZE(_wszPath));
return hr;
}
HRESULT CFSIconManager::QueryInterface(REFIID riid, void **ppv)
{
static const QITAB qit[] = {
QITABENT(CFSIconManager, ICustomIconManager),
{ 0 },
};
return QISearch(this, qit, riid, ppv);
}
ULONG CFSIconManager::AddRef()
{
return InterlockedIncrement(&_cRef);
}
ULONG CFSIconManager::Release()
{
if (InterlockedDecrement(&_cRef))
return _cRef;
delete this;
return 0;
}
STDMETHODIMP CFSIconManager::GetDefaultIconHandle(HICON *phIcon)
{
HRESULT hr = S_OK;
if (phIcon == NULL)
hr = E_INVALIDARG;
if (SUCCEEDED(hr))
{
WCHAR szCustomizedIconPath[MAX_PATH];
int nCustomizedIconIndex;
*phIcon = NULL;
if (SUCCEEDED(hr = GetIcon(szCustomizedIconPath, ARRAYSIZE(szCustomizedIconPath), &nCustomizedIconIndex)))
{
_SetDefaultIconEx(FALSE);
}
SHFILEINFOW sfiw;
if (SHGetFileInfoW(_wszPath, 0, &sfiw, sizeof(sfiw), SHGFI_ICON | SHGFI_LARGEICON))
{
*phIcon = sfiw.hIcon;
hr = S_OK;
}
else
hr = E_FAIL;
if (szCustomizedIconPath[0] != NULL)
_SetIconEx(szCustomizedIconPath, nCustomizedIconIndex, FALSE);
}
return hr;
}
STDMETHODIMP CFSIconManager::SetIcon(LPCWSTR pwszIconPath, int iIcon)
{
return _SetIconEx(pwszIconPath, iIcon, TRUE);
}
STDMETHODIMP CFSIconManager::SetDefaultIcon()
{
return _SetDefaultIconEx(TRUE);
}
HRESULT CFileFolderIconManager_Create(IShellFolder *psf, LPCITEMIDLIST pidl, REFIID riid, void **ppv)
{
HRESULT hr = E_FAIL;
*ppv = NULL;
CFileFolderIconManager *pffim = new CFileFolderIconManager;
if (pffim)
{
hr = pffim->_Init(pidl, psf);
if (SUCCEEDED(hr))
hr = pffim->QueryInterface(riid, ppv);
pffim->Release();
}
else
{
hr = E_OUTOFMEMORY;
}
return hr;
}
STDMETHODIMP CFileFolderIconManager::_SetIconEx(LPCWSTR pwszIconPath, int iIcon, BOOL fChangeNotify)
{
HRESULT hr = S_OK;
WCHAR wszExpandedIconPath[MAX_PATH];
if (SHExpandEnvironmentStrings(pwszIconPath, wszExpandedIconPath, ARRAYSIZE(wszExpandedIconPath)) == 0)
hr = E_FAIL;
if (SUCCEEDED(hr))
{
SHFOLDERCUSTOMSETTINGS fcs;
ZeroMemory(&fcs, sizeof(fcs));
fcs.dwSize = sizeof(fcs);
fcs.dwMask = FCSM_ICONFILE;
fcs.pszIconFile = (LPWSTR) wszExpandedIconPath;
fcs.cchIconFile = ARRAYSIZE(wszExpandedIconPath);
fcs.iIconIndex = iIcon;
hr = SHGetSetFolderCustomSettings(&fcs, _wszPath, FCS_FORCEWRITE);
if (SUCCEEDED(hr) && fChangeNotify)
{
/*
// Work Around - We need to pump a image change message for the folder icon change.
// The right way is the following. But for some reason, the shell views which
// display the folder, don't update there images. So as a work around, we pump a
// SHCNE_RENAMEFOLDER message. This works!.
SHFILEINFO sfi;
if (SHGetFileInfo(pfpsp->szPath, 0, &sfi, sizeof(sfi), SHGFI_ICONLOCATION))
{
int iIconIndex = Shell_GetCachedImageIndex(sfi.szDisplayName, sfi.iIcon, 0);
SHUpdateImage(PathFindFileName(sfi.szDisplayName), sfi.iIcon, 0, iIconIndex);
}
*/
SHChangeNotify(SHCNE_RENAMEFOLDER, SHCNF_PATH, _wszPath, _wszPath);
}
}
return hr;
}
STDMETHODIMP CFileFolderIconManager::_SetDefaultIconEx(BOOL fChangeNotify)
{
HRESULT hr = E_FAIL;
SHFOLDERCUSTOMSETTINGS fcs;
ZeroMemory(&fcs, sizeof(fcs));
fcs.dwSize = sizeof(fcs);
fcs.dwMask = FCSM_ICONFILE;
fcs.pszIconFile = NULL;
fcs.cchIconFile = 0;
fcs.iIconIndex = 0;
hr = SHGetSetFolderCustomSettings(&fcs, _wszPath, FCS_FORCEWRITE);
if (SUCCEEDED(hr) && fChangeNotify)
{
/*
// Work Around - We need to pump a image change message for the folder icon change.
// The right way is the following. But for some reason, the shell views which
// display the folder, don't update there images. So as a work around, we pump a
// SHCNE_RENAMEFOLDER message. This works!.
SHFILEINFO sfi;
if (SHGetFileInfo(pfpsp->szPath, 0, &sfi, sizeof(sfi), SHGFI_ICONLOCATION))
{
int iIconIndex = Shell_GetCachedImageIndex(sfi.szDisplayName, sfi.iIcon, 0);
SHUpdateImage(PathFindFileName(sfi.szDisplayName), sfi.iIcon, 0, iIconIndex);
}
*/
SHChangeNotify(SHCNE_RENAMEFOLDER, SHCNF_PATH, _wszPath, _wszPath);
}
return hr;
}
HRESULT CFileFolderIconManager::GetIcon(LPWSTR pszIconPath, int cchszIconPath, int *piIconIndex)
{
HRESULT hr = S_OK;
if ((pszIconPath == NULL) || (cchszIconPath < MAX_PATH) || (piIconIndex == NULL))
hr = E_INVALIDARG;
if (SUCCEEDED(hr))
{
SHFOLDERCUSTOMSETTINGS fcs;
ZeroMemory(&fcs, sizeof(fcs));
fcs.dwSize = sizeof(fcs);
fcs.dwMask = FCSM_ICONFILE;
fcs.pszIconFile = pszIconPath;
fcs.cchIconFile = cchszIconPath;
hr = SHGetSetFolderCustomSettings(&fcs, _wszPath, FCS_READ);
if (SUCCEEDED(hr))
{
*piIconIndex = fcs.iIconIndex;
}
}
return hr;
}