2734 lines
94 KiB
C++
2734 lines
94 KiB
C++
|
|
#include "shellprv.h"
|
|
|
|
#include "apithk.h"
|
|
#include "folder.h"
|
|
#include "ids.h"
|
|
#include "deskfldr.h"
|
|
#include <winnls.h>
|
|
#include "shitemid.h"
|
|
#include "sddl.h"
|
|
#ifdef _WIN64
|
|
#include <wow64t.h>
|
|
#endif
|
|
#include "filefldr.h"
|
|
#include "lmcons.h"
|
|
#include "netview.h"
|
|
|
|
//---------------------------------------------------------------------------
|
|
// Get the path for the CSIDL_ folders and optionally create it if it
|
|
// doesn't exist.
|
|
//
|
|
// Returns FALSE if the special folder given isn't one of those above or the
|
|
// directory couldn't be created.
|
|
// By default all the special folders are in the windows directory.
|
|
// This can be overidden by a [.Shell Folders] section in win.ini with
|
|
// entries like Desktop = c:\stuff\desktop
|
|
// This in turn can be overidden by a "per user" section in win.ini eg
|
|
// [Shell Folder Ianel] - the user name for this section is the current
|
|
// network user name, if this fails the default network user name is used
|
|
// and if this fails the name given at setup time is used.
|
|
//
|
|
// "Shell Folders" is the key that records all the absolute paths to the
|
|
// shell folders. The values there are always supposed to be present.
|
|
//
|
|
// "User Shell Folders" is the key where the user's modifications from
|
|
// the defaults are stored.
|
|
//
|
|
// When we need to find the location of a path, we look in "User Shell Folders"
|
|
// first, and if that's not there, generate the default path. In either
|
|
// case we then write the absolute path under "Shell Folders" for other
|
|
// apps to look at. This is so that HKEY_CURRENT_USER can be propagated
|
|
// to a machine with Windows installed in a different directory, and as
|
|
// long as the user hasn't changed the setting, they won't have the other
|
|
// Windows directory hard-coded in the registry.
|
|
// -- gregj, 11/10/94
|
|
|
|
typedef enum {
|
|
SDIF_NONE = 0,
|
|
SDIF_CREATE_IN_ROOT = 0x00000001, // create in root (not in profiles dir)
|
|
SDIF_CREATE_IN_WINDIR = 0x00000002, // create in the windows dir (not in profiles dir)
|
|
SDIF_CREATE_IN_ALLUSERS = 0x00000003, // create in "All Users" folder (not in profiles dir)
|
|
SDIF_CREATE_IN_MYDOCUMENTS = 0x00000004, // create in CSIDL_PERSONAL folder
|
|
SDIF_CREATE_IN_LOCALSET = 0x00000005, // create in <user>\Local Settings folder
|
|
|
|
SDIF_CREATE_IN_MASK = 0x0000000F, // mask for above values
|
|
|
|
SDIF_CAN_DELETE = 0x00000010,
|
|
SDIF_SHORTCUT_RELATIVE = 0x00000020, // make shortcuts relative to this folder
|
|
SDIF_HIDE = 0x00000040, // hide these when we create them
|
|
SDIF_EMPTY_IF_NOT_IN_REG = 0x00000080, // does not exist if nothing in the registry
|
|
SDIF_NOT_FILESYS = 0x00000100, // not a file system folder
|
|
SDIF_NOT_TRACKED = 0x00000200, // don't track this, it can't change
|
|
SDIF_CONST_IDLIST = 0x00000400, // don't alloc or free this
|
|
SDIF_REMOVABLE = 0x00000800, // Can exist on removable media
|
|
SDIF_CANT_MOVE_RENAME = 0x00001000, // can't move or rename this
|
|
SDIF_WX86 = 0x00002000, // do Wx86 thunking
|
|
SDIF_NETWORKABLE = 0x00004000, // Can be moved to the net
|
|
SDIF_MAYBE_ALIASED = 0x00008000, // could have an alias representation
|
|
SDIF_PERSONALIZED = 0x00010000, // resource name is to be personalized
|
|
SDIF_POLICY_NO_MOVE = 0x00020000, // policy blocks move
|
|
} ;
|
|
typedef DWORD FOLDER_FLAGS;
|
|
|
|
typedef void (*FOLDER_CREATE_PROC)(int id, LPCTSTR pszPath);
|
|
|
|
void _InitMyPictures(int id, LPCTSTR pszPath);
|
|
void _InitMyMusic(int id, LPCTSTR pszPath);
|
|
void _InitMyVideos(int id, LPCTSTR pszPath);
|
|
void _InitPerUserMyMusic(int id, LPCTSTR pszPath);
|
|
void _InitPerUserMyPictures(int id, LPCTSTR pszPath);
|
|
void _InitRecentDocs(int id, LPCTSTR pszPath);
|
|
void _InitFavorites(int id, LPCTSTR pszPath);
|
|
|
|
typedef struct {
|
|
int id; // CSIDL_ value
|
|
int idsDefault; // string id of default folder name name
|
|
LPCTSTR pszValueName; // reg key (not localized)
|
|
HKEY hKey; // HKCU or HKLM (Current User or Local Machine)
|
|
FOLDER_FLAGS dwFlags;
|
|
FOLDER_CREATE_PROC pfnInit;
|
|
INT idsLocalizedName;
|
|
} FOLDER_INFO;
|
|
|
|
// typical entry
|
|
#define FOLDER(csidl, ids, value, key, ff) \
|
|
{ csidl, ids, value, key, ff, NULL, 0}
|
|
|
|
// FIXEDFOLDER entries must have be marked SDIF_CONST_IDLIST
|
|
// or have code in _GetFolderDefaultPath() to create their path
|
|
// if they have a filesys path
|
|
#define FIXEDFOLDER(csidl, value, ff) \
|
|
{ csidl, 0, value, NULL, ff, NULL, 0}
|
|
|
|
// PROCFOLDER's have a FOLDER_CREATE_PROC pfn that gets
|
|
// run in _PostCreateStuff()
|
|
#define PROCFOLDER(csidl, ids, value, key, ff, proc, idsLocal) \
|
|
{csidl, ids, value, key, ff, proc, idsLocal}
|
|
|
|
// folder that needs SHSetLocalizedName() in _PostCreateStuff()
|
|
#define LOCALFOLDER(csidl, ids, value, key, ff, idsLocal) \
|
|
{csidl, ids, value, key, ff, NULL, idsLocal}
|
|
|
|
const FOLDER_INFO c_rgFolderInfo[] =
|
|
{
|
|
FOLDER( CSIDL_DESKTOP,
|
|
IDS_CSIDL_DESKTOPDIRECTORY,
|
|
TEXT("DesktopFolder"),
|
|
NULL,
|
|
SDIF_NOT_TRACKED | SDIF_CONST_IDLIST),
|
|
|
|
FIXEDFOLDER( CSIDL_NETWORK,
|
|
TEXT("NetworkFolder"),
|
|
SDIF_NOT_TRACKED | SDIF_NOT_FILESYS | SDIF_CONST_IDLIST),
|
|
|
|
FIXEDFOLDER( CSIDL_DRIVES,
|
|
TEXT("DriveFolder"),
|
|
SDIF_NOT_TRACKED | SDIF_NOT_FILESYS | SDIF_CONST_IDLIST),
|
|
|
|
FIXEDFOLDER( CSIDL_INTERNET,
|
|
TEXT("InternetFolder"),
|
|
SDIF_NOT_TRACKED | SDIF_NOT_FILESYS | SDIF_CONST_IDLIST),
|
|
|
|
FIXEDFOLDER( CSIDL_CONTROLS,
|
|
TEXT("ControlPanelFolder"),
|
|
SDIF_NOT_TRACKED | SDIF_NOT_FILESYS | SDIF_CONST_IDLIST),
|
|
|
|
FIXEDFOLDER( CSIDL_PRINTERS,
|
|
TEXT("PrintersFolder"),
|
|
SDIF_NOT_TRACKED | SDIF_NOT_FILESYS | SDIF_CONST_IDLIST),
|
|
|
|
FIXEDFOLDER( CSIDL_BITBUCKET,
|
|
TEXT("RecycleBinFolder"),
|
|
SDIF_NOT_TRACKED | SDIF_NOT_FILESYS | SDIF_CONST_IDLIST),
|
|
|
|
FIXEDFOLDER( CSIDL_CONNECTIONS,
|
|
TEXT("ConnectionsFolder"),
|
|
SDIF_NOT_TRACKED | SDIF_NOT_FILESYS | SDIF_CONST_IDLIST),
|
|
|
|
FOLDER( CSIDL_FONTS,
|
|
0,
|
|
TEXT("Fonts"),
|
|
HKEY_CURRENT_USER,
|
|
SDIF_NOT_TRACKED | SDIF_CREATE_IN_WINDIR | SDIF_CANT_MOVE_RENAME),
|
|
|
|
FOLDER( CSIDL_DESKTOPDIRECTORY,
|
|
IDS_CSIDL_DESKTOPDIRECTORY,
|
|
TEXT("Desktop"),
|
|
HKEY_CURRENT_USER, SDIF_SHORTCUT_RELATIVE),
|
|
|
|
// _STARTUP is a subfolder of _PROGRAMS is a subfolder of _STARTMENU -- keep that order
|
|
FOLDER( CSIDL_STARTUP,
|
|
IDS_CSIDL_STARTUP,
|
|
TEXT("Startup"),
|
|
HKEY_CURRENT_USER, SDIF_NONE),
|
|
|
|
FOLDER( CSIDL_PROGRAMS,
|
|
IDS_CSIDL_PROGRAMS,
|
|
TEXT("Programs"),
|
|
HKEY_CURRENT_USER,
|
|
SDIF_NONE),
|
|
|
|
FOLDER( CSIDL_STARTMENU,
|
|
IDS_CSIDL_STARTMENU,
|
|
TEXT("Start Menu"),
|
|
HKEY_CURRENT_USER,
|
|
SDIF_SHORTCUT_RELATIVE),
|
|
|
|
PROCFOLDER( CSIDL_RECENT,
|
|
IDS_CSIDL_RECENT,
|
|
TEXT("Recent"),
|
|
HKEY_CURRENT_USER,
|
|
SDIF_HIDE | SDIF_CANT_MOVE_RENAME | SDIF_CAN_DELETE,
|
|
_InitRecentDocs,
|
|
IDS_FOLDER_RECENTDOCS),
|
|
|
|
FOLDER( CSIDL_SENDTO,
|
|
IDS_CSIDL_SENDTO,
|
|
TEXT("SendTo"),
|
|
HKEY_CURRENT_USER,
|
|
SDIF_HIDE),
|
|
|
|
FOLDER( CSIDL_PERSONAL,
|
|
IDS_CSIDL_PERSONAL,
|
|
TEXT("Personal"),
|
|
HKEY_CURRENT_USER,
|
|
SDIF_SHORTCUT_RELATIVE | SDIF_NETWORKABLE | SDIF_REMOVABLE | SDIF_CONST_IDLIST | SDIF_MAYBE_ALIASED | SDIF_PERSONALIZED | SDIF_POLICY_NO_MOVE),
|
|
|
|
PROCFOLDER( CSIDL_FAVORITES,
|
|
IDS_CSIDL_FAVORITES,
|
|
TEXT("Favorites"),
|
|
HKEY_CURRENT_USER,
|
|
SDIF_POLICY_NO_MOVE,
|
|
_InitFavorites,
|
|
IDS_FOLDER_FAVORITES),
|
|
|
|
FOLDER( CSIDL_NETHOOD,
|
|
IDS_CSIDL_NETHOOD,
|
|
TEXT("NetHood"),
|
|
HKEY_CURRENT_USER,
|
|
SDIF_HIDE),
|
|
|
|
FOLDER( CSIDL_PRINTHOOD,
|
|
IDS_CSIDL_PRINTHOOD,
|
|
TEXT("PrintHood"),
|
|
HKEY_CURRENT_USER,
|
|
SDIF_HIDE),
|
|
|
|
FOLDER( CSIDL_TEMPLATES,
|
|
IDS_CSIDL_TEMPLATES,
|
|
TEXT("Templates"),
|
|
HKEY_CURRENT_USER,
|
|
SDIF_HIDE),
|
|
|
|
// Common special folders
|
|
|
|
// _STARTUP is a subfolder of _PROGRAMS is a subfolder of _STARTMENU -- keep that order
|
|
|
|
FOLDER( CSIDL_COMMON_STARTUP,
|
|
IDS_CSIDL_STARTUP,
|
|
TEXT("Common Startup"),
|
|
HKEY_LOCAL_MACHINE,
|
|
SDIF_CREATE_IN_ALLUSERS | SDIF_CANT_MOVE_RENAME | SDIF_EMPTY_IF_NOT_IN_REG),
|
|
|
|
FOLDER( CSIDL_COMMON_PROGRAMS,
|
|
IDS_CSIDL_PROGRAMS,
|
|
TEXT("Common Programs"),
|
|
HKEY_LOCAL_MACHINE,
|
|
SDIF_CREATE_IN_ALLUSERS | SDIF_EMPTY_IF_NOT_IN_REG),
|
|
|
|
FOLDER( CSIDL_COMMON_STARTMENU,
|
|
IDS_CSIDL_STARTMENU,
|
|
TEXT("Common Start Menu"),
|
|
HKEY_LOCAL_MACHINE,
|
|
SDIF_SHORTCUT_RELATIVE | SDIF_CREATE_IN_ALLUSERS | SDIF_EMPTY_IF_NOT_IN_REG),
|
|
|
|
FOLDER( CSIDL_COMMON_DESKTOPDIRECTORY,
|
|
IDS_CSIDL_DESKTOPDIRECTORY,
|
|
TEXT("Common Desktop"),
|
|
HKEY_LOCAL_MACHINE,
|
|
SDIF_SHORTCUT_RELATIVE | SDIF_CREATE_IN_ALLUSERS),
|
|
|
|
FOLDER( CSIDL_COMMON_FAVORITES,
|
|
IDS_CSIDL_FAVORITES,
|
|
TEXT("Common Favorites"),
|
|
HKEY_LOCAL_MACHINE,
|
|
SDIF_CREATE_IN_ALLUSERS),
|
|
|
|
FOLDER( CSIDL_COMMON_APPDATA,
|
|
IDS_CSIDL_APPDATA,
|
|
TEXT("Common AppData"),
|
|
HKEY_LOCAL_MACHINE,
|
|
SDIF_SHORTCUT_RELATIVE | SDIF_CREATE_IN_ALLUSERS),
|
|
|
|
FOLDER( CSIDL_COMMON_TEMPLATES,
|
|
IDS_CSIDL_TEMPLATES,
|
|
TEXT("Common Templates"),
|
|
HKEY_LOCAL_MACHINE,
|
|
SDIF_NOT_TRACKED | SDIF_CAN_DELETE | SDIF_CREATE_IN_ALLUSERS),
|
|
|
|
LOCALFOLDER( CSIDL_COMMON_DOCUMENTS,
|
|
IDS_CSIDL_ALLUSERS_DOCUMENTS,
|
|
TEXT("Common Documents"),
|
|
HKEY_LOCAL_MACHINE,
|
|
SDIF_NOT_TRACKED | SDIF_CANT_MOVE_RENAME | SDIF_MAYBE_ALIASED | SDIF_CREATE_IN_ALLUSERS,
|
|
IDS_LOCALGDN_FLD_SHARED_DOC),
|
|
|
|
// Application Data special folder
|
|
FOLDER( CSIDL_APPDATA,
|
|
IDS_CSIDL_APPDATA,
|
|
TEXT("AppData"),
|
|
HKEY_CURRENT_USER, SDIF_SHORTCUT_RELATIVE),
|
|
|
|
FOLDER( CSIDL_LOCAL_APPDATA,
|
|
IDS_CSIDL_APPDATA,
|
|
TEXT("Local AppData"),
|
|
HKEY_CURRENT_USER, SDIF_CREATE_IN_LOCALSET),
|
|
|
|
// Non-localized startup folder (do not localize this folder name)
|
|
FOLDER( CSIDL_ALTSTARTUP,
|
|
IDS_CSIDL_ALTSTARTUP,
|
|
TEXT("AltStartup"),
|
|
HKEY_CURRENT_USER,
|
|
SDIF_EMPTY_IF_NOT_IN_REG),
|
|
|
|
// Non-localized Common StartUp group (do not localize this folde name)
|
|
FOLDER( CSIDL_COMMON_ALTSTARTUP,
|
|
IDS_CSIDL_ALTSTARTUP,
|
|
TEXT("Common AltStartup"),
|
|
HKEY_LOCAL_MACHINE,
|
|
SDIF_EMPTY_IF_NOT_IN_REG | SDIF_CREATE_IN_ALLUSERS),
|
|
|
|
// Per-user Internet-related folders
|
|
|
|
FOLDER( CSIDL_INTERNET_CACHE,
|
|
IDS_CSIDL_CACHE,
|
|
TEXT("Cache"),
|
|
HKEY_CURRENT_USER,
|
|
SDIF_CREATE_IN_LOCALSET),
|
|
|
|
FOLDER( CSIDL_COOKIES,
|
|
IDS_CSIDL_COOKIES,
|
|
TEXT("Cookies"),
|
|
HKEY_CURRENT_USER,
|
|
SDIF_NONE),
|
|
|
|
FOLDER( CSIDL_HISTORY,
|
|
IDS_CSIDL_HISTORY,
|
|
TEXT("History"),
|
|
HKEY_CURRENT_USER,
|
|
SDIF_CREATE_IN_LOCALSET),
|
|
|
|
FIXEDFOLDER( CSIDL_SYSTEM,
|
|
TEXT("System"),
|
|
SDIF_NOT_TRACKED | SDIF_CANT_MOVE_RENAME | SDIF_SHORTCUT_RELATIVE),
|
|
|
|
FIXEDFOLDER( CSIDL_SYSTEMX86,
|
|
TEXT("SystemX86"),
|
|
SDIF_NOT_TRACKED | SDIF_CANT_MOVE_RENAME | SDIF_WX86 | SDIF_SHORTCUT_RELATIVE),
|
|
|
|
FIXEDFOLDER( CSIDL_WINDOWS,
|
|
TEXT("Windows"),
|
|
SDIF_NOT_TRACKED | SDIF_SHORTCUT_RELATIVE | SDIF_CANT_MOVE_RENAME),
|
|
|
|
FIXEDFOLDER( CSIDL_PROFILE,
|
|
TEXT("Profile"),
|
|
SDIF_NOT_TRACKED | SDIF_CANT_MOVE_RENAME),
|
|
|
|
PROCFOLDER( CSIDL_MYPICTURES,
|
|
IDS_CSIDL_MYPICTURES,
|
|
TEXT("My Pictures"),
|
|
HKEY_CURRENT_USER,
|
|
SDIF_CAN_DELETE | SDIF_NETWORKABLE | SDIF_REMOVABLE | SDIF_CREATE_IN_MYDOCUMENTS | SDIF_SHORTCUT_RELATIVE | SDIF_MAYBE_ALIASED | SDIF_PERSONALIZED | SDIF_POLICY_NO_MOVE,
|
|
_InitPerUserMyPictures,
|
|
0),
|
|
|
|
//
|
|
// CSIDL_PROGRAM_FILES must come after CSIDL_PROGRAM_FILESX86 so that shell links for x86 apps
|
|
// work correctly on non-x86 platforms.
|
|
// Example: On IA64 a 32-bit app creates a shortcut via IShellLink to the Program
|
|
// Files directory. A WOW64 registry hive maps "Program Files" to "Program Files (x86)". The shell
|
|
// link code then tries to abstract the special folder part of the path by mapping to one of the
|
|
// entries in this table. Since CSIDL_PROGRAM_FILES and CSIDL_PROGRAM_FILESX86 are the same it
|
|
// will map to the one that appears first in this table. When the shortcut is accessed in
|
|
// 64-bit mode the cidls are no longer the same. If CSIDL_PROGRAM_FILES was used instead of
|
|
// CSIDL_PROGRAM_FILESX86 the shortcut will be broken.
|
|
#ifdef WX86
|
|
FIXEDFOLDER( CSIDL_PROGRAM_FILESX86,
|
|
TEXT("ProgramFilesX86"),
|
|
SDIF_NOT_TRACKED | SDIF_CAN_DELETE | SDIF_SHORTCUT_RELATIVE|SDIF_WX86),
|
|
|
|
FIXEDFOLDER( CSIDL_PROGRAM_FILES_COMMONX86,
|
|
TEXT("CommonProgramFilesX86"),
|
|
SDIF_NOT_TRACKED | SDIF_CAN_DELETE | SDIF_WX86),
|
|
#else
|
|
FIXEDFOLDER( CSIDL_PROGRAM_FILESX86,
|
|
TEXT("ProgramFilesX86"),
|
|
SDIF_NOT_TRACKED | SDIF_CAN_DELETE | SDIF_SHORTCUT_RELATIVE),
|
|
|
|
FIXEDFOLDER( CSIDL_PROGRAM_FILES_COMMONX86,
|
|
TEXT("CommonProgramFilesX86"),
|
|
SDIF_NOT_TRACKED | SDIF_CAN_DELETE),
|
|
#endif
|
|
|
|
// CSIDL_PROGRAM_FILES must come after CSIDL_PROGRAM_FILESX86. See comment above.
|
|
FIXEDFOLDER( CSIDL_PROGRAM_FILES,
|
|
TEXT("ProgramFiles"),
|
|
SDIF_NOT_TRACKED | SDIF_CAN_DELETE | SDIF_SHORTCUT_RELATIVE),
|
|
|
|
FIXEDFOLDER( CSIDL_PROGRAM_FILES_COMMON,
|
|
TEXT("CommonProgramFiles"),
|
|
SDIF_NOT_TRACKED | SDIF_CAN_DELETE),
|
|
|
|
FOLDER( CSIDL_ADMINTOOLS,
|
|
IDS_CSIDL_ADMINTOOLS,
|
|
TEXT("Administrative Tools"),
|
|
HKEY_CURRENT_USER,
|
|
SDIF_NONE),
|
|
|
|
FOLDER( CSIDL_COMMON_ADMINTOOLS,
|
|
IDS_CSIDL_ADMINTOOLS,
|
|
TEXT("Common Administrative Tools"),
|
|
HKEY_LOCAL_MACHINE,
|
|
SDIF_CREATE_IN_ALLUSERS),
|
|
|
|
PROCFOLDER( CSIDL_MYMUSIC,
|
|
IDS_CSIDL_MYMUSIC,
|
|
TEXT("My Music"),
|
|
HKEY_CURRENT_USER,
|
|
SDIF_CAN_DELETE | SDIF_NETWORKABLE | SDIF_REMOVABLE | SDIF_CREATE_IN_MYDOCUMENTS | SDIF_MAYBE_ALIASED | SDIF_PERSONALIZED | SDIF_POLICY_NO_MOVE,
|
|
_InitPerUserMyMusic,
|
|
0),
|
|
|
|
PROCFOLDER( CSIDL_MYVIDEO,
|
|
IDS_CSIDL_MYVIDEO,
|
|
TEXT("My Video"),
|
|
HKEY_CURRENT_USER,
|
|
SDIF_CAN_DELETE | SDIF_NETWORKABLE | SDIF_REMOVABLE | SDIF_CREATE_IN_MYDOCUMENTS | SDIF_MAYBE_ALIASED | SDIF_PERSONALIZED | SDIF_POLICY_NO_MOVE,
|
|
_InitMyVideos,
|
|
0),
|
|
|
|
PROCFOLDER( CSIDL_COMMON_PICTURES,
|
|
IDS_CSIDL_ALLUSERS_PICTURES,
|
|
TEXT("CommonPictures"),
|
|
HKEY_LOCAL_MACHINE,
|
|
SDIF_SHORTCUT_RELATIVE | SDIF_CANT_MOVE_RENAME | SDIF_CAN_DELETE | SDIF_MAYBE_ALIASED | SDIF_CREATE_IN_ALLUSERS,
|
|
_InitMyPictures,
|
|
IDS_SHAREDPICTURES),
|
|
|
|
PROCFOLDER( CSIDL_COMMON_MUSIC,
|
|
IDS_CSIDL_ALLUSERS_MUSIC,
|
|
TEXT("CommonMusic"),
|
|
HKEY_LOCAL_MACHINE,
|
|
SDIF_SHORTCUT_RELATIVE | SDIF_CANT_MOVE_RENAME | SDIF_CAN_DELETE | SDIF_MAYBE_ALIASED | SDIF_CREATE_IN_ALLUSERS,
|
|
_InitMyMusic,
|
|
IDS_SHAREDMUSIC),
|
|
|
|
PROCFOLDER( CSIDL_COMMON_VIDEO,
|
|
|
|
IDS_CSIDL_ALLUSERS_VIDEO,
|
|
TEXT("CommonVideo"),
|
|
HKEY_LOCAL_MACHINE,
|
|
SDIF_SHORTCUT_RELATIVE | SDIF_CANT_MOVE_RENAME | SDIF_CAN_DELETE | SDIF_MAYBE_ALIASED | SDIF_CREATE_IN_ALLUSERS,
|
|
_InitMyVideos,
|
|
IDS_SHAREDVIDEO),
|
|
|
|
FIXEDFOLDER( CSIDL_RESOURCES,
|
|
TEXT("ResourceDir"),
|
|
SDIF_NOT_TRACKED),
|
|
|
|
FIXEDFOLDER( CSIDL_RESOURCES_LOCALIZED,
|
|
TEXT("LocalizedResourcesDir"),
|
|
SDIF_NOT_TRACKED),
|
|
|
|
FOLDER( CSIDL_COMMON_OEM_LINKS,
|
|
IDS_CSIDL_ALLUSERS_OEM_LINKS,
|
|
TEXT("OEM Links"),
|
|
HKEY_LOCAL_MACHINE,
|
|
SDIF_CAN_DELETE | SDIF_CREATE_IN_ALLUSERS | SDIF_EMPTY_IF_NOT_IN_REG),
|
|
|
|
FOLDER( CSIDL_CDBURN_AREA,
|
|
IDS_CSIDL_CDBURN_AREA,
|
|
TEXT("CD Burning"),
|
|
HKEY_CURRENT_USER,
|
|
SDIF_CAN_DELETE | SDIF_CREATE_IN_LOCALSET),
|
|
|
|
FIXEDFOLDER( CSIDL_COMPUTERSNEARME,
|
|
TEXT("ComputersNearMe"),
|
|
SDIF_NONE),
|
|
|
|
FIXEDFOLDER(-1, NULL, SDIF_NONE)
|
|
};
|
|
|
|
|
|
EXTERN_C const IDLREGITEM c_idlMyDocs =
|
|
{
|
|
{sizeof(IDREGITEM), SHID_ROOT_REGITEM, SORT_ORDER_MYDOCS,
|
|
{ 0x450d8fba, 0xad25, 0x11d0, 0x98,0xa8,0x08,0x00,0x36,0x1b,0x11,0x03, },}, // CLSID_MyDocuments
|
|
0,
|
|
} ;
|
|
|
|
EXTERN_C const IDREGITEM c_idlPrinters[] =
|
|
{
|
|
{sizeof(IDREGITEM), SHID_ROOT_REGITEM, SORT_ORDER_DRIVES,
|
|
{ 0x20D04FE0, 0x3AEA, 0x1069, 0xA2,0xD8,0x08,0x00,0x2B,0x30,0x30,0x9D, },}, // CLSID_MyComputer
|
|
{sizeof(IDREGITEM), SHID_COMPUTER_REGITEM, 0,
|
|
{ 0x21EC2020, 0x3AEA, 0x1069, 0xA2,0xDD,0x08,0x00,0x2B,0x30,0x30,0x9D, },}, // CLSID_ControlPanel
|
|
{sizeof(IDREGITEM), SHID_CONTROLPANEL_REGITEM, 0,
|
|
{ 0x2227A280, 0x3AEA, 0x1069, 0xA2, 0xDE, 0x08, 0x00, 0x2B, 0x30, 0x30, 0x9D, },}, // CLSID_Printers
|
|
0,
|
|
} ;
|
|
|
|
EXTERN_C const IDREGITEM c_idlControls[] =
|
|
{
|
|
{sizeof(IDREGITEM), SHID_ROOT_REGITEM, SORT_ORDER_DRIVES,
|
|
{ 0x20D04FE0, 0x3AEA, 0x1069, 0xA2,0xD8,0x08,0x00,0x2B,0x30,0x30,0x9D, },}, // CLSID_MyComputer
|
|
{sizeof(IDREGITEM), SHID_COMPUTER_REGITEM, 0,
|
|
{ 0x21EC2020, 0x3AEA, 0x1069, 0xA2,0xDD,0x08,0x00,0x2B,0x30,0x30,0x9D, },}, // CLSID_ControlPanel
|
|
0,
|
|
} ;
|
|
|
|
EXTERN_C const IDLREGITEM c_idlBitBucket =
|
|
{
|
|
{sizeof(IDREGITEM), SHID_ROOT_REGITEM, SORT_ORDER_RECYCLEBIN,
|
|
{ 0x645FF040, 0x5081, 0x101B, 0x9F, 0x08, 0x00, 0xAA, 0x00, 0x2F, 0x95, 0x4E, },}, // CLSID_RecycleBin
|
|
0,
|
|
} ;
|
|
|
|
// this array holds a cache of the values of these folders. this cache can only
|
|
// be used in the hToken == NULL case otherwise we would need a per user version
|
|
// of this cache.
|
|
|
|
#define SFENTRY(x) { (LPTSTR)-1, (LPITEMIDLIST)x , (LPITEMIDLIST)-1}
|
|
|
|
EXTERN_C const IDREGITEM c_aidlConnections[];
|
|
|
|
struct {
|
|
LPTSTR psz;
|
|
LPITEMIDLIST pidl;
|
|
LPITEMIDLIST pidlNonAlias;
|
|
} g_aFolderCache[] = {
|
|
SFENTRY(&c_idlDesktop), // CSIDL_DESKTOP (0x0000)
|
|
SFENTRY(&c_idlInetRoot), // CSIDL_INTERNET (0x0001)
|
|
SFENTRY(-1), // CSIDL_PROGRAMS (0x0002)
|
|
SFENTRY(&c_idlControls), // CSIDL_CONTROLS (0x0003)
|
|
SFENTRY(&c_idlPrinters), // CSIDL_PRINTERS (0x0004)
|
|
SFENTRY(&c_idlMyDocs), // CSIDL_PERSONAL (0x0005)
|
|
SFENTRY(-1), // CSIDL_FAVORITES (0x0006)
|
|
SFENTRY(-1), // CSIDL_STARTUP (0x0007)
|
|
SFENTRY(-1), // CSIDL_RECENT (0x0008)
|
|
SFENTRY(-1), // CSIDL_SENDTO (0x0009)
|
|
SFENTRY(&c_idlBitBucket), // CSIDL_BITBUCKET (0x000a)
|
|
SFENTRY(-1), // CSIDL_STARTMENU (0x000b)
|
|
SFENTRY(-1), // CSIDL_MYDOCUMENTS (0x000c)
|
|
SFENTRY(-1), // CSIDL_MYMUSIC (0x000d)
|
|
SFENTRY(-1), // CSIDL_MYVIDEO (0x000e)
|
|
SFENTRY(-1), // <unused> (0x000f)
|
|
SFENTRY(-1), // CSIDL_DESKTOPDIRECTORY (0x0010)
|
|
SFENTRY(&c_idlDrives), // CSIDL_DRIVES (0x0011)
|
|
SFENTRY(&c_idlNet), // CSIDL_NETWORK (0x0012)
|
|
SFENTRY(-1), // CSIDL_NETHOOD (0x0013)
|
|
SFENTRY(-1), // CSIDL_FONTS (0x0014)
|
|
SFENTRY(-1), // CSIDL_TEMPLATES (0x0015)
|
|
SFENTRY(-1), // CSIDL_COMMON_STARTMENU (0x0016)
|
|
SFENTRY(-1), // CSIDL_COMMON_PROGRAMS (0X0017)
|
|
SFENTRY(-1), // CSIDL_COMMON_STARTUP (0x0018)
|
|
SFENTRY(-1), // CSIDL_COMMON_DESKTOPDIRECTORY (0x0019)
|
|
SFENTRY(-1), // CSIDL_APPDATA (0x001a)
|
|
SFENTRY(-1), // CSIDL_PRINTHOOD (0x001b)
|
|
SFENTRY(-1), // CSIDL_LOCAL_APPDATA (0x001c)
|
|
SFENTRY(-1), // CSIDL_ALTSTARTUP (0x001d)
|
|
SFENTRY(-1), // CSIDL_COMMON_ALTSTARTUP (0x001e)
|
|
SFENTRY(-1), // CSIDL_COMMON_FAVORITES (0x001f)
|
|
SFENTRY(-1), // CSIDL_INTERNET_CACHE (0x0020)
|
|
SFENTRY(-1), // CSIDL_COOKIES (0x0021)
|
|
SFENTRY(-1), // CSIDL_HISTORY (0x0022)
|
|
SFENTRY(-1), // CSIDL_COMMON_APPDATA (0x0023)
|
|
SFENTRY(-1), // CSIDL_WINDOWS (0x0024)
|
|
SFENTRY(-1), // CSIDL_SYSTEM (0x0025)
|
|
SFENTRY(-1), // CSIDL_PROGRAM_FILES (0x0026)
|
|
SFENTRY(-1), // CSIDL_MYPICTURES (0x0027)
|
|
SFENTRY(-1), // CSIDL_PROFILE (0x0028)
|
|
SFENTRY(-1), // CSIDL_SYSTEMX86 (0x0029)
|
|
SFENTRY(-1), // CSIDL_PROGRAM_FILESX86 (0x002a)
|
|
SFENTRY(-1), // CSIDL_PROGRAM_FILES_COMMON (0x002b)
|
|
SFENTRY(-1), // CSIDL_PROGRAM_FILES_COMMONX86 (0x002c)
|
|
SFENTRY(-1), // CSIDL_COMMON_TEMPLATES (0x002d)
|
|
SFENTRY(-1), // CSIDL_COMMON_DOCUMENTS (0x002e)
|
|
SFENTRY(-1), // CSIDL_COMMON_ADMINTOOLS (0x002f)
|
|
SFENTRY(-1), // CSIDL_ADMINTOOLS (0x0030)
|
|
SFENTRY(c_aidlConnections), // CSIDL_CONNECTIONS (0x0031)
|
|
SFENTRY(-1), // (0x0032)
|
|
SFENTRY(-1), // (0x0033)
|
|
SFENTRY(-1), // (0x0034)
|
|
SFENTRY(-1), // CSIDL_COMMON_MUSIC (0x0035)
|
|
SFENTRY(-1), // CSIDL_COMMON_PICTURES (0x0036)
|
|
SFENTRY(-1), // CSIDL_COMMON_VIDEO (0x0037)
|
|
SFENTRY(-1), // CSIDL_RESOURCES (0x0038)
|
|
SFENTRY(-1), // CSIDL_RESOURCES_LOCALIZED (0x0039)
|
|
SFENTRY(-1), // CSIDL_COMMON_OEM_LINKS (0x003a)
|
|
SFENTRY(-1), // CSIDL_CDBURN_AREA (0x003b)
|
|
SFENTRY(-1), // <unused> (0x003c)
|
|
SFENTRY(-1), // CSIDL_COMPUTERSNEARME (0x003d)
|
|
};
|
|
|
|
HRESULT _OpenKeyForFolder(const FOLDER_INFO *pfi, HANDLE hToken, LPCTSTR pszSubKey, HKEY *phkey);
|
|
void _UpdateShellFolderCache(void);
|
|
BOOL GetUserProfileDir(HANDLE hToken, TCHAR *pszPath);
|
|
HRESULT VerifyAndCreateFolder(HWND hwnd, const FOLDER_INFO *pfi, UINT uFlags, LPTSTR pszPath) ;
|
|
|
|
|
|
#define _IsDefaultUserToken(hToken) ((HANDLE)-1 == hToken)
|
|
|
|
|
|
const FOLDER_INFO *_GetFolderInfo(int csidl)
|
|
{
|
|
const FOLDER_INFO *pfi;
|
|
|
|
// make sure g_aFolderCache can be indexed by the CSIDL values
|
|
|
|
COMPILETIME_ASSERT((ARRAYSIZE(g_aFolderCache) - 1) == CSIDL_COMPUTERSNEARME);
|
|
|
|
for (pfi = c_rgFolderInfo; pfi->id != -1; pfi++)
|
|
{
|
|
if (pfi->id == csidl)
|
|
return pfi;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
|
|
// expand an individual enviornment variable
|
|
// in:
|
|
// pszVar "%USERPROFILE%
|
|
// pszValue "c:\winnt\profiles\user"
|
|
//
|
|
// in/out:
|
|
// pszToExpand in: %USERPROFILE%\My Docs", out: c:\winnt\profiles\user\My Docs"
|
|
|
|
BOOL ExpandEnvVar(LPCTSTR pszVar, LPCTSTR pszValue, LPTSTR pszToExpand)
|
|
{
|
|
TCHAR *pszStart = StrStrI(pszToExpand, pszVar);
|
|
if (pszStart)
|
|
{
|
|
TCHAR szAfter[MAX_PATH];
|
|
|
|
lstrcpy(szAfter, pszStart + lstrlen(pszVar)); // save the tail
|
|
lstrcpyn(pszStart, pszValue, (int) (MAX_PATH - (pszStart - pszToExpand)));
|
|
StrCatBuff(pszToExpand, szAfter, MAX_PATH); // put the tail back on
|
|
return TRUE;
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
HANDLE GetCurrentUserToken()
|
|
{
|
|
HANDLE hToken;
|
|
if (OpenThreadToken(GetCurrentThread(), TOKEN_QUERY | TOKEN_IMPERSONATE, TRUE, &hToken) ||
|
|
OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY | TOKEN_IMPERSONATE, &hToken))
|
|
return hToken;
|
|
return NULL;
|
|
}
|
|
|
|
|
|
// like ExpandEnvironmentStrings but is robust to the enviornment variables
|
|
// not being set. this works on...
|
|
// %SYSTEMROOT%
|
|
// %SYSTEMDRIVE%
|
|
// %USERPROFILE%
|
|
// %ALLUSERSPROFILE%
|
|
//
|
|
// in the rare case (Winstone!) that there is a NULL enviornment block
|
|
|
|
DWORD ExpandEnvironmentStringsNoEnv(HANDLE hToken, LPCTSTR pszExpand, LPTSTR pszOut, UINT cchOut)
|
|
{
|
|
TCHAR szPath[MAX_PATH];
|
|
if (hToken && !_IsDefaultUserToken(hToken))
|
|
{
|
|
if (!ExpandEnvironmentStringsForUser(hToken, pszExpand, pszOut, cchOut))
|
|
lstrcpyn(pszOut, pszExpand, cchOut);
|
|
}
|
|
else if (hToken == NULL)
|
|
{
|
|
// to debug env expansion failure...
|
|
// lstrcpyn(pszOut, pszExpand, cchOut);
|
|
SHExpandEnvironmentStrings(pszExpand, pszOut, cchOut);
|
|
}
|
|
|
|
// manually expand in this order since
|
|
// %USERPROFILE% -> %SYSTEMDRIVE%\Docs & Settings
|
|
|
|
if (StrChr(pszOut, TEXT('%')) && (hToken == NULL))
|
|
{
|
|
hToken = GetCurrentUserToken();
|
|
if (hToken)
|
|
{
|
|
// this does %USERPROFILE% and other per user stuff
|
|
ExpandEnvironmentStringsForUser(hToken, pszExpand, pszOut, cchOut);
|
|
CloseHandle(hToken);
|
|
}
|
|
}
|
|
else if (_IsDefaultUserToken(hToken) && StrChr(pszOut, TEXT('%')))
|
|
{
|
|
GetUserProfileDir(hToken, szPath);
|
|
ExpandEnvVar(TEXT("%USERPROFILE%"), szPath, pszOut);
|
|
}
|
|
|
|
if (*pszOut == TEXT('%'))
|
|
{
|
|
GetAllUsersDirectory(szPath);
|
|
ExpandEnvVar(TEXT("%ALLUSERSPROFILE%"), szPath, pszOut);
|
|
}
|
|
|
|
if (*pszOut == TEXT('%'))
|
|
{
|
|
GetSystemWindowsDirectory(szPath, ARRAYSIZE(szPath));
|
|
ExpandEnvVar(TEXT("%SYSTEMROOT%"), szPath, pszOut);
|
|
}
|
|
|
|
if (*pszOut == TEXT('%'))
|
|
{
|
|
GetSystemWindowsDirectory(szPath, ARRAYSIZE(szPath));
|
|
ASSERT(szPath[1] == TEXT(':')); // this better not be a UNC!
|
|
szPath[2] = 0; // SYSTEMDRIVE = 'c:', not 'c:\'
|
|
ExpandEnvVar(TEXT("%SYSTEMDRIVE%"), szPath, pszOut);
|
|
}
|
|
|
|
if (*pszOut == TEXT('%'))
|
|
*pszOut = 0;
|
|
|
|
return lstrlen(pszOut) + 1; // +1 to cover the NULL
|
|
}
|
|
|
|
// get the user profile directory:
|
|
// uses the hToken as needed to determine the proper user profile
|
|
|
|
BOOL GetUserProfileDir(HANDLE hToken, TCHAR *pszPath)
|
|
{
|
|
DWORD dwcch = MAX_PATH;
|
|
HANDLE hClose = NULL;
|
|
BOOL fRet;
|
|
|
|
*pszPath = 0; // in case of error
|
|
|
|
if (!hToken)
|
|
{
|
|
hClose = hToken = GetCurrentUserToken();
|
|
}
|
|
if (_IsDefaultUserToken(hToken))
|
|
{
|
|
fRet = GetDefaultUserProfileDirectory(pszPath, &dwcch);
|
|
}
|
|
else
|
|
{
|
|
fRet = GetUserProfileDirectory(hToken, pszPath, &dwcch);
|
|
}
|
|
if (hClose)
|
|
{
|
|
CloseHandle(hClose);
|
|
}
|
|
return fRet;
|
|
}
|
|
|
|
#ifdef WX86
|
|
void SetUseKnownWx86Dll(const FOLDER_INFO *pfi, BOOL bValue)
|
|
{
|
|
if (pfi->dwFlags & SDIF_WX86)
|
|
{
|
|
// GetSystemDirectory() knows we're looking for the Wx86 system
|
|
// directory when this flag is set.
|
|
NtCurrentTeb()->Wx86Thread.UseKnownWx86Dll = bValue ? TRUE : FALSE;
|
|
}
|
|
}
|
|
#else
|
|
#define SetUseKnownWx86Dll(pfi, bValue)
|
|
#endif
|
|
|
|
// read from registry
|
|
BOOL GetProgramFiles(LPCTSTR pszValue, LPTSTR pszPath)
|
|
{
|
|
DWORD cbPath = MAX_PATH * sizeof(*pszPath);
|
|
|
|
*pszPath = 0;
|
|
|
|
SHGetValue(HKEY_LOCAL_MACHINE, TEXT("Software\\Microsoft\\Windows\\CurrentVersion"),
|
|
pszValue, NULL, pszPath, &cbPath);
|
|
return (BOOL)*pszPath;
|
|
}
|
|
|
|
LPTSTR GetFontsDirectory(LPTSTR pszPath)
|
|
{
|
|
if (GetWindowsDirectory(pszPath, MAX_PATH))
|
|
{
|
|
PathAppend(pszPath, TEXT("Fonts"));
|
|
}
|
|
|
|
return pszPath;
|
|
}
|
|
|
|
void LoadDefaultString(int idString, LPTSTR lpBuffer, int cchBufferMax)
|
|
{
|
|
BOOL fSucceeded = FALSE;
|
|
HRSRC hResInfo;
|
|
HANDLE hStringSeg;
|
|
LPWSTR lpsz;
|
|
int cch;
|
|
HMODULE hmod = GetModuleHandle(TEXT("SHELL32"));
|
|
|
|
// Make sure the parms are valid.
|
|
if (lpBuffer == NULL || cchBufferMax == 0)
|
|
{
|
|
return;
|
|
}
|
|
|
|
cch = 0;
|
|
|
|
// String Tables are broken up into 16 string segments. Find the segment
|
|
// containing the string we are interested in.
|
|
if (hResInfo = FindResourceExW(hmod, (LPCWSTR)RT_STRING,
|
|
(LPWSTR)((LONG_PTR)(((USHORT)idString >> 4) + 1)), GetSystemDefaultUILanguage()))
|
|
{
|
|
// Load that segment.
|
|
hStringSeg = LoadResource(hmod, hResInfo);
|
|
|
|
// Lock the resource.
|
|
if (lpsz = (LPWSTR)LockResource(hStringSeg))
|
|
{
|
|
// Move past the other strings in this segment.
|
|
// (16 strings in a segment -> & 0x0F)
|
|
idString &= 0x0F;
|
|
while (TRUE)
|
|
{
|
|
cch = *((WORD *)lpsz++); // PASCAL like string count
|
|
// first UTCHAR is count if TCHARs
|
|
if (idString-- == 0) break;
|
|
lpsz += cch; // Step to start if next string
|
|
}
|
|
|
|
|
|
// Account for the NULL
|
|
cchBufferMax--;
|
|
|
|
// Don't copy more than the max allowed.
|
|
if (cch > cchBufferMax)
|
|
cch = cchBufferMax;
|
|
|
|
// Copy the string into the buffer.
|
|
CopyMemory(lpBuffer, lpsz, cch*sizeof(WCHAR));
|
|
|
|
// Attach Null terminator.
|
|
lpBuffer[cch] = 0;
|
|
|
|
fSucceeded = TRUE;
|
|
|
|
}
|
|
}
|
|
|
|
if (!fSucceeded)
|
|
{
|
|
LoadString(HINST_THISDLL, idString, lpBuffer, cchBufferMax);
|
|
}
|
|
}
|
|
|
|
BOOL GetLocalSettingsDir(HANDLE hToken, LPTSTR pszPath)
|
|
{
|
|
*pszPath = 0;
|
|
|
|
GetUserProfileDir(hToken, pszPath);
|
|
|
|
if (*pszPath)
|
|
{
|
|
TCHAR szEntry[MAX_PATH];
|
|
LoadDefaultString(IDS_LOCALSETTINGS, szEntry, ARRAYSIZE(szEntry));
|
|
PathAppend(pszPath, szEntry);
|
|
}
|
|
return *pszPath ? TRUE : FALSE;
|
|
}
|
|
|
|
|
|
HRESULT GetResourcesDir(IN BOOL fLocalized, IN LPTSTR pszPath, IN DWORD cchSize)
|
|
{
|
|
HRESULT hr = E_FAIL;
|
|
TCHAR szTemp[MAX_PATH];
|
|
|
|
RIP(IS_VALID_WRITE_BUFFER(pszPath, TCHAR, cchSize));
|
|
pszPath[0] = 0; // Terminate in case we fail.
|
|
|
|
if (SHGetSystemWindowsDirectory(szTemp, ARRAYSIZE(szTemp)))
|
|
{
|
|
// It's now "%windir%\resources\".
|
|
PathAppend(szTemp, TEXT("resources"));
|
|
|
|
if (fLocalized)
|
|
{
|
|
LANGID lidUI = GetUserDefaultUILanguage();
|
|
TCHAR szSubDir[10];
|
|
|
|
// Now make it "%windir%\resources\<LangID>\"
|
|
wnsprintfW(szSubDir, ARRAYSIZE(szSubDir), TEXT("%04x"), lidUI);
|
|
PathAppend(szTemp, szSubDir);
|
|
}
|
|
|
|
StrCpyN(pszPath, szTemp, cchSize);
|
|
hr = S_OK;
|
|
}
|
|
|
|
return hr;
|
|
}
|
|
|
|
|
|
// out:
|
|
// pszPath fills in with the full path with no env gunk (MAX_PATH)
|
|
|
|
HRESULT _GetFolderDefaultPath(const FOLDER_INFO *pfi, HANDLE hToken, LPTSTR pszPath)
|
|
{
|
|
ASSERT(!(pfi->dwFlags & SDIF_NOT_FILESYS)); // speical folders should not come here
|
|
|
|
*pszPath = 0;
|
|
|
|
TCHAR szEntry[MAX_PATH];
|
|
|
|
switch (pfi->id)
|
|
{
|
|
case CSIDL_PROFILE:
|
|
GetUserProfileDir(hToken, pszPath);
|
|
break;
|
|
|
|
case CSIDL_PROGRAM_FILES:
|
|
GetProgramFiles(TEXT("ProgramFilesDir"), pszPath);
|
|
break;
|
|
|
|
case CSIDL_PROGRAM_FILES_COMMON:
|
|
GetProgramFiles(TEXT("CommonFilesDir"), pszPath);
|
|
break;
|
|
|
|
case CSIDL_PROGRAM_FILESX86:
|
|
GetProgramFiles(TEXT("ProgramFilesDir (x86)"), pszPath);
|
|
break;
|
|
|
|
case CSIDL_PROGRAM_FILES_COMMONX86:
|
|
GetProgramFiles(TEXT("CommonFilesDir (x86)"), pszPath);
|
|
break;
|
|
#ifdef _WIN64
|
|
case CSIDL_SYSTEMX86:
|
|
//
|
|
// downlevel systems do not have GetSystemWindowsDirectory export,
|
|
// but shell thunking layer handles this gracefully
|
|
GetSystemWindowsDirectory(pszPath, MAX_PATH);
|
|
//
|
|
// tack on subdirectory
|
|
//
|
|
PathCombine(pszPath, pszPath, TEXT(WOW64_SYSTEM_DIRECTORY));
|
|
break;
|
|
#else
|
|
case CSIDL_SYSTEMX86:
|
|
#endif
|
|
case CSIDL_SYSTEM:
|
|
GetSystemDirectory(pszPath, MAX_PATH);
|
|
break;
|
|
|
|
case CSIDL_WINDOWS:
|
|
GetWindowsDirectory(pszPath, MAX_PATH);
|
|
break;
|
|
|
|
case CSIDL_RESOURCES:
|
|
GetResourcesDir(FALSE, pszPath, MAX_PATH);
|
|
break;
|
|
|
|
case CSIDL_RESOURCES_LOCALIZED:
|
|
GetResourcesDir(TRUE, pszPath, MAX_PATH);
|
|
break;
|
|
|
|
case CSIDL_COMPUTERSNEARME:
|
|
// no path for this
|
|
break;
|
|
|
|
case CSIDL_FONTS:
|
|
GetFontsDirectory(pszPath);
|
|
break;
|
|
|
|
default:
|
|
switch (pfi->dwFlags & SDIF_CREATE_IN_MASK)
|
|
{
|
|
case SDIF_CREATE_IN_ROOT:
|
|
GetWindowsDirectory(pszPath, MAX_PATH);
|
|
PathStripToRoot(pszPath);
|
|
break;
|
|
|
|
case SDIF_CREATE_IN_ALLUSERS:
|
|
GetAllUsersDirectory(pszPath);
|
|
break;
|
|
|
|
case SDIF_CREATE_IN_WINDIR:
|
|
GetWindowsDirectory(pszPath, MAX_PATH);
|
|
break;
|
|
|
|
case SDIF_CREATE_IN_MYDOCUMENTS:
|
|
// 99/10/21 Mil#104600: When asking for folders in "My Documents" don't
|
|
// verify their existance. Just return the path. The caller will make
|
|
// the decision to create the folder or not.
|
|
|
|
// on failure *pszPath will be empty
|
|
|
|
SHGetFolderPath(NULL, CSIDL_PERSONAL | CSIDL_FLAG_DONT_VERIFY, hToken, SHGFP_TYPE_CURRENT, pszPath);
|
|
break;
|
|
|
|
case SDIF_CREATE_IN_LOCALSET:
|
|
GetLocalSettingsDir(hToken, pszPath);
|
|
break;
|
|
|
|
default:
|
|
GetUserProfileDir(hToken, pszPath);
|
|
break;
|
|
}
|
|
|
|
if (*pszPath)
|
|
{
|
|
LoadDefaultString(pfi->idsDefault, szEntry, ARRAYSIZE(szEntry));
|
|
PathAppend(pszPath, szEntry);
|
|
}
|
|
break;
|
|
}
|
|
return *pszPath ? S_OK : E_FAIL;
|
|
}
|
|
|
|
|
|
void RegSetFolderPath(const FOLDER_INFO *pfi, LPCTSTR pszSubKey, LPCTSTR pszPath)
|
|
{
|
|
HKEY hk;
|
|
if (SUCCEEDED(_OpenKeyForFolder(pfi, NULL, pszSubKey, &hk)))
|
|
{
|
|
if (pszPath)
|
|
RegSetValueEx(hk, pfi->pszValueName, 0, REG_SZ, (LPBYTE)pszPath, (1 + lstrlen(pszPath)) * sizeof(TCHAR));
|
|
else
|
|
RegDeleteValue(hk, pfi->pszValueName);
|
|
RegCloseKey(hk);
|
|
}
|
|
}
|
|
|
|
BOOL RegQueryPath(HKEY hk, LPCTSTR pszValue, LPTSTR pszPath)
|
|
{
|
|
DWORD cbPath = MAX_PATH * sizeof(TCHAR);
|
|
|
|
*pszPath = 0;
|
|
SHQueryValueEx(hk, pszValue, 0, NULL, pszPath, &cbPath);
|
|
return (BOOL)*pszPath;
|
|
}
|
|
|
|
|
|
// More than 50 is silly
|
|
#define MAX_TEMP_FILE_TRIES 50
|
|
|
|
// returns:
|
|
// S_OK the path exists and it is a folder
|
|
// FAILED() result
|
|
HRESULT _IsFolderNotFile(LPCTSTR pszFolder)
|
|
{
|
|
HRESULT hr;
|
|
DWORD dwAttribs = GetFileAttributes(pszFolder);
|
|
if (dwAttribs == -1)
|
|
{
|
|
DWORD err = GetLastError();
|
|
hr = HRESULT_FROM_WIN32(err);
|
|
}
|
|
else
|
|
{
|
|
// see if it is a file, if so we need to rename that file
|
|
if (dwAttribs & FILE_ATTRIBUTE_DIRECTORY)
|
|
{
|
|
hr = S_OK;
|
|
}
|
|
else
|
|
{
|
|
int iExt = 0;
|
|
do
|
|
{
|
|
TCHAR szExt[32], szDst[MAX_PATH];
|
|
|
|
wsprintf(szExt, TEXT(".%03d"), iExt);
|
|
lstrcpy(szDst, pszFolder);
|
|
lstrcat(szDst, szExt);
|
|
if (MoveFile(pszFolder, szDst))
|
|
iExt = 0;
|
|
else
|
|
{
|
|
// Normally we fail because .00x already exists but that may not be true.
|
|
DWORD dwError = GetLastError();
|
|
if (ERROR_ALREADY_EXISTS == dwError)
|
|
iExt++; // Try the next one...
|
|
else
|
|
iExt = 0; // We have problems and need to give up. (No write access?)
|
|
}
|
|
|
|
} while (iExt && (iExt < MAX_TEMP_FILE_TRIES));
|
|
|
|
hr = HRESULT_FROM_WIN32(ERROR_PATH_NOT_FOUND);
|
|
}
|
|
}
|
|
return hr;
|
|
}
|
|
|
|
HRESULT _OpenKeyForFolder(const FOLDER_INFO *pfi, HANDLE hToken, LPCTSTR pszSubKey, HKEY *phkey)
|
|
{
|
|
TCHAR szRegPath[255];
|
|
LONG err;
|
|
HKEY hkRoot, hkeyToFree = NULL;
|
|
|
|
*phkey = NULL;
|
|
|
|
lstrcpy(szRegPath, TEXT("Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\"));
|
|
lstrcat(szRegPath, pszSubKey);
|
|
|
|
if (_IsDefaultUserToken(hToken) && (pfi->hKey == HKEY_CURRENT_USER))
|
|
{
|
|
if (ERROR_SUCCESS == RegOpenKeyEx(HKEY_USERS, TEXT(".Default"), 0, KEY_READ, &hkRoot))
|
|
hkeyToFree = hkRoot;
|
|
else
|
|
return E_FAIL;
|
|
}
|
|
else if (hToken && (pfi->hKey == HKEY_CURRENT_USER))
|
|
{
|
|
if (GetUserProfileKey(hToken, &hkRoot))
|
|
hkeyToFree = hkRoot;
|
|
else
|
|
return E_FAIL;
|
|
}
|
|
else
|
|
hkRoot = pfi->hKey;
|
|
|
|
err = RegCreateKeyEx(hkRoot, szRegPath, 0, NULL, REG_OPTION_NON_VOLATILE,
|
|
MAXIMUM_ALLOWED, NULL, phkey, NULL);
|
|
|
|
if (hkeyToFree)
|
|
RegCloseKey(hkeyToFree);
|
|
|
|
return HRESULT_FROM_WIN32(err);
|
|
}
|
|
|
|
//
|
|
// Roaming Profiles can set up the environment variables and registry
|
|
// keys like so:
|
|
//
|
|
// HOMESHARE=\\server\share\user
|
|
// HOMEPATH=\
|
|
// My Music=%HOMESHARE%%HOMEPATH%\My Music
|
|
//
|
|
// so you end up with "\\server\share\user\\My Music", which is an
|
|
// invalid path. Clean them up; otherwise SHGetSpecialFolderLocation will
|
|
// fail.
|
|
//
|
|
void _CleanExpandedEnvironmentPath(LPTSTR szExpand)
|
|
{
|
|
// Search for "\\" at a location other than the start of the string.
|
|
// If found, collapse it.
|
|
LPTSTR pszWhackWhack;
|
|
while (lstrlen(szExpand) > 2 &&
|
|
(pszWhackWhack = StrStr(szExpand+1, TEXT("\\\\"))))
|
|
{
|
|
StrCpy(pszWhackWhack+1, pszWhackWhack+2);
|
|
}
|
|
}
|
|
|
|
// returns:
|
|
// S_OK found in registry, path well formed
|
|
// S_FALSE empty registry
|
|
// FAILED() failure result
|
|
|
|
HRESULT _GetFolderFromReg(const FOLDER_INFO *pfi, HANDLE hToken, LPTSTR pszPath)
|
|
{
|
|
HKEY hkUSF;
|
|
HRESULT hr;
|
|
|
|
*pszPath = 0;
|
|
|
|
hr = _OpenKeyForFolder(pfi, hToken, TEXT("User Shell Folders"), &hkUSF);
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
TCHAR szExpand[MAX_PATH];
|
|
DWORD dwType, cbPath = sizeof(szExpand);
|
|
|
|
if (RegQueryValueEx(hkUSF, pfi->pszValueName, 0, &dwType, (BYTE *)szExpand, &cbPath) == ERROR_SUCCESS)
|
|
{
|
|
if (REG_SZ == dwType)
|
|
{
|
|
lstrcpyn(pszPath, szExpand, MAX_PATH);
|
|
}
|
|
else if (REG_EXPAND_SZ == dwType)
|
|
{
|
|
ExpandEnvironmentStringsNoEnv(hToken, szExpand, pszPath, MAX_PATH);
|
|
_CleanExpandedEnvironmentPath(pszPath);
|
|
}
|
|
TraceMsg(TF_PATH, "_CreateFolderPath 'User Shell Folders' %s = %s", pfi->pszValueName, pszPath);
|
|
}
|
|
|
|
if (*pszPath == 0)
|
|
{
|
|
hr = S_FALSE; // empty registry, success but empty
|
|
}
|
|
else if ((PathGetDriveNumber(pszPath) != -1) || PathIsUNC(pszPath))
|
|
{
|
|
hr = S_OK; // good reg path, fully qualified
|
|
}
|
|
else
|
|
{
|
|
*pszPath = 0; // bad reg data
|
|
hr = E_INVALIDARG;
|
|
}
|
|
|
|
RegCloseKey(hkUSF);
|
|
}
|
|
return hr;
|
|
}
|
|
|
|
HRESULT _GetFolderPath(HWND hwnd, const FOLDER_INFO *pfi, HANDLE hToken, UINT uFlags, LPTSTR pszPath)
|
|
{
|
|
HRESULT hr;
|
|
|
|
*pszPath = 0; // assume failure
|
|
|
|
if (pfi->hKey)
|
|
{
|
|
hr = _GetFolderFromReg(pfi, hToken, pszPath);
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
if (hr == S_FALSE)
|
|
{
|
|
// empty registry, SDIF_EMPTY_IF_NOT_IN_REG means they don't exist
|
|
// if the registry is not populated with a value. this lets us disable
|
|
// the common items on platforms that don't want them
|
|
|
|
if (pfi->dwFlags & SDIF_EMPTY_IF_NOT_IN_REG)
|
|
return S_FALSE; // success, but empty
|
|
|
|
hr = _GetFolderDefaultPath(pfi, hToken, pszPath);
|
|
}
|
|
|
|
if (!(uFlags & CSIDL_FLAG_DONT_VERIFY))
|
|
{
|
|
hr = VerifyAndCreateFolder(hwnd, pfi, uFlags, pszPath) ;
|
|
}
|
|
|
|
if (hr != S_OK)
|
|
{
|
|
*pszPath = 0;
|
|
}
|
|
|
|
if (!(uFlags & CSIDL_FLAG_DONT_VERIFY))
|
|
{
|
|
HKEY hkey;
|
|
// record value in "Shell Folders", even in the failure case
|
|
|
|
// NOTE: we only do this for historical reasons. there may be some
|
|
// apps that depend on these values being in the registry, but in general
|
|
// the contetens here are unreliable as they are only written after someone
|
|
// asks for the folder through this API.
|
|
|
|
if (SUCCEEDED(_OpenKeyForFolder(pfi, hToken, TEXT("Shell Folders"), &hkey)))
|
|
{
|
|
RegSetValueEx(hkey, pfi->pszValueName, 0, REG_SZ, (LPBYTE)pszPath, (1 + lstrlen(pszPath)) * sizeof(TCHAR));
|
|
RegCloseKey(hkey);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
hr = _GetFolderDefaultPath(pfi, hToken, pszPath);
|
|
|
|
if ((S_OK == hr) && !(uFlags & CSIDL_FLAG_DONT_VERIFY))
|
|
{
|
|
hr = VerifyAndCreateFolder(hwnd, pfi, uFlags, pszPath);
|
|
}
|
|
|
|
if (hr != S_OK)
|
|
{
|
|
*pszPath = 0;
|
|
}
|
|
}
|
|
|
|
ASSERT(hr == S_OK ? *pszPath != 0 : *pszPath == 0);
|
|
return hr;
|
|
}
|
|
|
|
void _PostCreateStuff(const FOLDER_INFO *pfi, LPTSTR pszPath, BOOL fUpgrade)
|
|
{
|
|
if (pfi->pfnInit || pfi->idsLocalizedName || (pfi->dwFlags & SDIF_PERSONALIZED))
|
|
{
|
|
if (fUpgrade)
|
|
{
|
|
// if we are upgrading, torch all our previous meta data
|
|
TCHAR sz[MAX_PATH];
|
|
PathCombine(sz, pszPath, TEXT("desktop.ini"));
|
|
|
|
if (PathFileExistsAndAttributes(sz, NULL))
|
|
{
|
|
WritePrivateProfileSection(TEXT(".ShellClassInfo"), NULL, sz);
|
|
// in the upgrade case, sometimes the desktop.ini
|
|
// file was there but the folder wasnt marked.
|
|
// insure that it is marked.
|
|
PathMakeSystemFolder(pszPath);
|
|
}
|
|
}
|
|
|
|
// now call the create proc if we have one
|
|
if (pfi->pfnInit)
|
|
pfi->pfnInit(pfi->id, pszPath);
|
|
|
|
// does the table specify a localized resource name that we should be
|
|
// using for this object?
|
|
if (pfi->idsLocalizedName)
|
|
SHSetLocalizedName(pszPath, TEXT("shell32.dll"), pfi->idsLocalizedName);
|
|
|
|
// do we need to store the user name for this folder?
|
|
|
|
if (pfi->dwFlags & SDIF_PERSONALIZED)
|
|
{
|
|
TCHAR szName[UNLEN+1];
|
|
DWORD dwName = ARRAYSIZE(szName);
|
|
if (GetUserName(szName, &dwName))
|
|
{
|
|
// CSharedDocuments depends on a per system list of MyDocs folders
|
|
// this is where we make sure that list is setup
|
|
|
|
if (!IsOS(OS_DOMAINMEMBER) && (pfi->id == CSIDL_PERSONAL))
|
|
{
|
|
SKSetValue(SHELLKEY_HKLM_EXPLORER, L"DocFolderPaths",
|
|
szName, REG_SZ, pszPath, (lstrlen(pszPath) + 1) * sizeof(TCHAR));
|
|
}
|
|
|
|
SetFolderString(TRUE, pszPath, NULL, L"DeleteOnCopy", SZ_CANBEUNICODE TEXT("Owner"), szName);
|
|
wsprintf(szName, L"%d", pfi->id);
|
|
SetFolderString(TRUE, pszPath, NULL, L"DeleteOnCopy", TEXT("Personalized"), szName);
|
|
LoadDefaultString(pfi->idsDefault, szName, ARRAYSIZE(szName));
|
|
SetFolderString(TRUE, pszPath, NULL, L"DeleteOnCopy", SZ_CANBEUNICODE TEXT("PersonalizedName"), szName);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
HRESULT VerifyAndCreateFolder(HWND hwnd, const FOLDER_INFO *pfi, UINT uFlags, LPTSTR pszPath)
|
|
{
|
|
HRESULT hr = _IsFolderNotFile(pszPath);
|
|
|
|
// this code supports a UI mode of this API. but generally this is not used
|
|
// this code should be removed
|
|
if ((hr != S_OK) && hwnd)
|
|
{
|
|
// we might be able to reconnect if this is a net path
|
|
if (PathIsUNC(pszPath))
|
|
{
|
|
if (SHValidateUNC(hwnd, pszPath, 0))
|
|
hr = _IsFolderNotFile(pszPath);
|
|
}
|
|
else if (IsDisconnectedNetDrive(DRIVEID(pszPath)))
|
|
{
|
|
TCHAR szDrive[3];
|
|
PathBuildSimpleRoot(DRIVEID(pszPath), szDrive);
|
|
|
|
if (WNetRestoreConnection(hwnd, szDrive) == WN_SUCCESS)
|
|
hr = _IsFolderNotFile(pszPath);
|
|
}
|
|
}
|
|
|
|
// to avoid a sequence of long net timeouts or calls we know won't
|
|
// succeed test for these specific errors and don't try to create
|
|
// the folder
|
|
|
|
if (hr == HRESULT_FROM_WIN32(ERROR_ACCESS_DENIED) ||
|
|
hr == HRESULT_FROM_WIN32(ERROR_BAD_NETPATH))
|
|
{
|
|
return hr;
|
|
}
|
|
|
|
if ((hr != S_OK) && (uFlags & CSIDL_FLAG_CREATE))
|
|
{
|
|
DWORD err = SHCreateDirectory(NULL, pszPath);
|
|
hr = HRESULT_FROM_WIN32(err);
|
|
if (hr == S_OK)
|
|
{
|
|
ASSERT(NULL == StrChr(pszPath, TEXT('%')));
|
|
|
|
if (pfi->dwFlags & SDIF_HIDE)
|
|
SetFileAttributes(pszPath, GetFileAttributes(pszPath) | FILE_ATTRIBUTE_HIDDEN);
|
|
|
|
_PostCreateStuff(pfi, pszPath, FALSE);
|
|
}
|
|
}
|
|
else if (hr == S_OK)
|
|
{
|
|
if (uFlags & CSIDL_FLAG_PER_USER_INIT)
|
|
_PostCreateStuff(pfi, pszPath, TRUE);
|
|
}
|
|
|
|
return hr;
|
|
}
|
|
|
|
void _SetPathCache(const FOLDER_INFO *pfi, LPCTSTR psz)
|
|
{
|
|
LPTSTR pszOld = (LPTSTR)InterlockedExchangePointer((void **)&g_aFolderCache[pfi->id].psz, (void *)psz);
|
|
if (pszOld && pszOld != (LPTSTR)-1)
|
|
{
|
|
// check for the concurent use... very rare case
|
|
LocalFree(pszOld);
|
|
}
|
|
}
|
|
|
|
|
|
HRESULT _GetFolderPathCached(HWND hwnd, const FOLDER_INFO *pfi, HANDLE hToken, UINT uFlags, LPTSTR pszPath)
|
|
{
|
|
HRESULT hr;
|
|
|
|
*pszPath = 0;
|
|
|
|
// can only cache for the current user, hToken == NULL or per machine folders
|
|
if (!hToken || (pfi->hKey != HKEY_CURRENT_USER))
|
|
{
|
|
_UpdateShellFolderCache();
|
|
|
|
LPTSTR pszCache = (LPTSTR)InterlockedExchangePointer((void **)&g_aFolderCache[pfi->id].psz, (void *)-1);
|
|
if ((pszCache == (LPTSTR)-1) || (pszCache == NULL))
|
|
{
|
|
// either not cached or cached failed state
|
|
if ((pszCache == (LPTSTR)-1) || (uFlags & (CSIDL_FLAG_CREATE | CSIDL_FLAG_DONT_VERIFY)))
|
|
{
|
|
hr = _GetFolderPath(hwnd, pfi, hToken, uFlags, pszPath);
|
|
|
|
// only set the cache value if CSIDL_FLAG_DONT_VERIFY was NOT passed
|
|
if (!(uFlags & CSIDL_FLAG_DONT_VERIFY))
|
|
{
|
|
if (hr == S_OK)
|
|
{
|
|
// dupe the string so we can add it to the cache
|
|
pszCache = StrDup(pszPath);
|
|
}
|
|
else
|
|
{
|
|
// we failed to get the folder path, null out the cache
|
|
ASSERT(*pszPath == 0);
|
|
pszCache = NULL;
|
|
}
|
|
_SetPathCache(pfi, pszCache);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// cache was null and user didnt pass create flag so we just fail
|
|
ASSERT(pszCache == NULL);
|
|
ASSERT(*pszPath == 0);
|
|
hr = E_FAIL;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// cache hit case: copy the cached string and then restore the cached value back
|
|
lstrcpyn(pszPath, pszCache, MAX_PATH);
|
|
_SetPathCache(pfi, pszCache);
|
|
hr = S_OK;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
hr = _GetFolderPath(hwnd, pfi, hToken, uFlags, pszPath);
|
|
}
|
|
|
|
return hr;
|
|
}
|
|
|
|
// NOTE: possibly we need a csidlSkip param to avoid recursion?
|
|
BOOL _ReparentAliases(HWND hwnd, HANDLE hToken, LPCITEMIDLIST pidl, LPITEMIDLIST *ppidlAlias, DWORD dwXlateAliases)
|
|
{
|
|
static const struct {DWORD dwXlate; int idPath; int idAlias; BOOL fCommon;} s_rgidAliases[]=
|
|
{
|
|
{ XLATEALIAS_MYDOCS, CSIDL_PERSONAL | CSIDL_FLAG_NO_ALIAS, CSIDL_PERSONAL, FALSE},
|
|
{ XLATEALIAS_COMMONDOCS, CSIDL_COMMON_DOCUMENTS | CSIDL_FLAG_NO_ALIAS, CSIDL_COMMON_DOCUMENTS, FALSE},
|
|
{ XLATEALIAS_DESKTOP, CSIDL_DESKTOPDIRECTORY, CSIDL_DESKTOP, FALSE},
|
|
{ XLATEALIAS_DESKTOP, CSIDL_COMMON_DESKTOPDIRECTORY, CSIDL_DESKTOP, TRUE},
|
|
};
|
|
BOOL fContinue = TRUE;
|
|
*ppidlAlias = NULL;
|
|
|
|
for (int i = 0; fContinue && i < ARRAYSIZE(s_rgidAliases); i++)
|
|
{
|
|
LPITEMIDLIST pidlPath;
|
|
if ((dwXlateAliases & s_rgidAliases[i].dwXlate) &&
|
|
(S_OK == SHGetFolderLocation(hwnd, s_rgidAliases[i].idPath, hToken, 0, &pidlPath)))
|
|
{
|
|
LPCITEMIDLIST pidlChild = ILFindChild(pidlPath, pidl);
|
|
if (pidlChild)
|
|
{
|
|
// ok we need to use the alias instead of the path
|
|
LPITEMIDLIST pidlAlias;
|
|
if (S_OK == SHGetFolderLocation(hwnd, s_rgidAliases[i].idAlias, hToken, 0, &pidlAlias))
|
|
{
|
|
if (SUCCEEDED(SHILCombine(pidlAlias, pidlChild, ppidlAlias)))
|
|
{
|
|
if (s_rgidAliases[i].fCommon && !ILIsEmpty(*ppidlAlias))
|
|
{
|
|
// find the size of the special part (subtacting for null pidl terminator)
|
|
UINT cbSize = ILGetSize(pidlAlias) - sizeof(pidlAlias->mkid.cb);
|
|
LPITEMIDLIST pidlChildFirst = _ILSkip(*ppidlAlias, cbSize);
|
|
|
|
// We set the first ID under the common path to have the SHID_FS_COMMONITEM so that when we bind we
|
|
// can hand this to the proper merged psf
|
|
pidlChildFirst->mkid.abID[0] |= SHID_FS_COMMONITEM;
|
|
}
|
|
ILFree(pidlAlias);
|
|
}
|
|
fContinue = FALSE;
|
|
}
|
|
}
|
|
ILFree(pidlPath);
|
|
}
|
|
}
|
|
|
|
return (*ppidlAlias != NULL);
|
|
}
|
|
|
|
STDAPI SHILAliasTranslate(LPCITEMIDLIST pidl, LPITEMIDLIST *ppidlAlias, DWORD dwXlateAliases)
|
|
{
|
|
return _ReparentAliases(NULL, NULL, pidl, ppidlAlias, dwXlateAliases) ? S_OK : E_FAIL;
|
|
}
|
|
|
|
HRESULT _CreateFolderIDList(HWND hwnd, const FOLDER_INFO *pfi, HANDLE hToken, UINT uFlags, LPITEMIDLIST *ppidl)
|
|
{
|
|
HRESULT hr = S_OK;
|
|
|
|
*ppidl = NULL; // assume failure or empty
|
|
|
|
if (pfi->id == CSIDL_PRINTERS && (ACF_STAROFFICE5PRINTER & SHGetAppCompatFlags(ACF_STAROFFICE5PRINTER)))
|
|
{
|
|
// Star Office 5.0 relies on the fact that the printer pidl used to be like below. They skip the
|
|
// first simple pidl (My Computer) and do not check if there is anything else, they assume that the
|
|
// second simple pidl is the Printer folder one. (stephstm, 07/30/99)
|
|
|
|
// CLSID_MyComputer, CLSID_Printers
|
|
hr = ILCreateFromPathEx(TEXT("::{20D04FE0-3AEA-1069-A2D8-08002B30309D}\\::{2227A280-3AEA-1069-A2DE-08002B30309D}"), NULL, ILCFP_FLAG_NO_MAP_ALIAS, ppidl, NULL);
|
|
}
|
|
else if (pfi->id == CSIDL_COMPUTERSNEARME)
|
|
{
|
|
if (IsOS(OS_DOMAINMEMBER))
|
|
{
|
|
// only if you are in a workgroup - fail otherwise
|
|
hr = E_FAIL;
|
|
}
|
|
else
|
|
{
|
|
// we computer this IDLIST from the domain/workgroup you are a member of
|
|
hr = SHGetDomainWorkgroupIDList(ppidl);
|
|
}
|
|
}
|
|
else if ((pfi->id == CSIDL_COMMON_DOCUMENTS)
|
|
&& !(uFlags & CSIDL_FLAG_NO_ALIAS))
|
|
{
|
|
// CLSID_MyComputer \ SharedDocumnets (canonical name)
|
|
hr = ILCreateFromPathEx(TEXT("::{20D04FE0-3AEA-1069-A2D8-08002B30309D}\\::{59031a47-3f72-44a7-89c5-5595fe6b30ee},SharedDocuments"), NULL, ILCFP_FLAG_NO_MAP_ALIAS, ppidl, NULL);
|
|
}
|
|
else if ((pfi->dwFlags & SDIF_CONST_IDLIST)
|
|
&& (!(uFlags & CSIDL_FLAG_NO_ALIAS) || !(pfi->dwFlags & SDIF_MAYBE_ALIASED)))
|
|
{
|
|
// these are CONST, never change
|
|
hr = SHILClone(g_aFolderCache[pfi->id].pidl, ppidl);
|
|
}
|
|
else
|
|
{
|
|
TCHAR szPath[MAX_PATH];
|
|
hr = _GetFolderPathCached(hwnd, pfi, hToken, uFlags, szPath);
|
|
if (hr == S_OK)
|
|
{
|
|
HRESULT hrInit = SHCoInitialize();
|
|
hr = ILCreateFromPathEx(szPath, NULL, ILCFP_FLAG_SKIPJUNCTIONS, ppidl, NULL);
|
|
|
|
// attempt to reparent aliased pidls.
|
|
if (SUCCEEDED(hr)
|
|
&& (pfi->dwFlags & SDIF_MAYBE_ALIASED)
|
|
&& !(uFlags & CSIDL_FLAG_NO_ALIAS))
|
|
{
|
|
LPITEMIDLIST pidlAlias;
|
|
if (_ReparentAliases(hwnd, hToken, *ppidl, &pidlAlias, XLATEALIAS_ALL))
|
|
{
|
|
ILFree(*ppidl);
|
|
*ppidl = pidlAlias;
|
|
}
|
|
}
|
|
|
|
SHCoUninitialize(hrInit);
|
|
}
|
|
}
|
|
|
|
return hr;
|
|
}
|
|
|
|
void _SetIDListCache(const FOLDER_INFO *pfi, LPCITEMIDLIST pidl, BOOL fNonAlias)
|
|
{
|
|
if (fNonAlias || !(pfi->dwFlags & SDIF_CONST_IDLIST))
|
|
{
|
|
void **ppv = (void **) (fNonAlias ? &g_aFolderCache[pfi->id].pidlNonAlias : &g_aFolderCache[pfi->id].pidl);
|
|
LPITEMIDLIST pidlOld = (LPITEMIDLIST)InterlockedExchangePointer(ppv, (void *)pidl);
|
|
if (pidlOld && pidlOld != (LPITEMIDLIST)-1)
|
|
{
|
|
// check for the concurent use... very rare case
|
|
// ASSERT(pidl == (LPCITEMIDLIST)-1); // should not really be ASSERT
|
|
ILFree(pidlOld);
|
|
}
|
|
}
|
|
}
|
|
|
|
LPITEMIDLIST _GetIDListCache(const FOLDER_INFO *pfi, BOOL fNonAlias)
|
|
{
|
|
void **ppv = (void **) (fNonAlias ? &g_aFolderCache[pfi->id].pidlNonAlias : &g_aFolderCache[pfi->id].pidl);
|
|
ASSERT(fNonAlias || !(pfi->dwFlags & SDIF_CONST_IDLIST));
|
|
return (LPITEMIDLIST)InterlockedExchangePointer(ppv, (void *)-1);
|
|
}
|
|
|
|
// hold this lock for the minimal amout of time possible to avoid other users
|
|
// of this resource requring them to re-create the pidl
|
|
|
|
HRESULT _GetFolderIDListCached(HWND hwnd, const FOLDER_INFO *pfi, UINT uFlags, LPITEMIDLIST *ppidl)
|
|
{
|
|
HRESULT hr;
|
|
BOOL fNonAlias = uFlags & CSIDL_FLAG_NO_ALIAS;
|
|
|
|
ASSERT(pfi->id < ARRAYSIZE(g_aFolderCache));
|
|
|
|
if ((pfi->dwFlags & SDIF_CONST_IDLIST) &&
|
|
(!fNonAlias || !(pfi->dwFlags & SDIF_MAYBE_ALIASED)))
|
|
{
|
|
// these are CONST, never change
|
|
hr = SHILClone(g_aFolderCache[pfi->id].pidl, ppidl);
|
|
}
|
|
else
|
|
{
|
|
LPITEMIDLIST pidlCache;
|
|
|
|
_UpdateShellFolderCache();
|
|
pidlCache = _GetIDListCache(pfi, fNonAlias);
|
|
|
|
if ((pidlCache == (LPCITEMIDLIST)-1) || (pidlCache == NULL))
|
|
{
|
|
// either uninitalized cache state OR cached failure (NULL)
|
|
if ((pidlCache == (LPCITEMIDLIST)-1) || (uFlags & CSIDL_FLAG_CREATE))
|
|
{
|
|
// not initialized (or concurent use) try creating it for this use
|
|
hr = _CreateFolderIDList(hwnd, pfi, NULL, uFlags, ppidl);
|
|
if (S_OK == hr)
|
|
hr = SHILClone(*ppidl, &pidlCache); // create cache copy
|
|
else
|
|
pidlCache = NULL;
|
|
}
|
|
else
|
|
hr = E_FAIL; // return cached failure
|
|
}
|
|
else
|
|
{
|
|
hr = SHILClone(pidlCache, ppidl); // cache hit
|
|
}
|
|
|
|
// store back the PIDL if it is non NULL or they specified CREATE
|
|
// and we failed to create it (cache the not existant state). this is needed
|
|
// so we don't cache a NULL if the first callers don't ask for create and
|
|
// subsequent callers do
|
|
if (pidlCache || (uFlags & CSIDL_FLAG_CREATE))
|
|
_SetIDListCache(pfi, pidlCache, fNonAlias);
|
|
}
|
|
|
|
return hr;
|
|
}
|
|
|
|
void _ClearCacheEntry(const FOLDER_INFO *pfi)
|
|
{
|
|
if (!(pfi->dwFlags & SDIF_CONST_IDLIST))
|
|
_SetIDListCache(pfi, (LPCITEMIDLIST)-1, FALSE);
|
|
|
|
if (pfi->dwFlags & SDIF_MAYBE_ALIASED)
|
|
_SetIDListCache(pfi, (LPCITEMIDLIST)-1, TRUE);
|
|
|
|
_SetPathCache(pfi, (LPCTSTR)-1);
|
|
}
|
|
|
|
void _ClearAllCacheEntrys()
|
|
{
|
|
for (const FOLDER_INFO *pfi = c_rgFolderInfo; pfi->id != -1; pfi++)
|
|
{
|
|
_ClearCacheEntry(pfi);
|
|
}
|
|
}
|
|
|
|
void _ClearAllAliasCacheEntrys()
|
|
{
|
|
for (const FOLDER_INFO *pfi = c_rgFolderInfo; pfi->id != -1; pfi++)
|
|
{
|
|
if (pfi->dwFlags & SDIF_MAYBE_ALIASED)
|
|
{
|
|
_SetIDListCache(pfi, (LPCITEMIDLIST)-1, FALSE); // nuke the aliased pidl
|
|
}
|
|
}
|
|
}
|
|
|
|
// Per instance count of mods to Special Folder cache.
|
|
EXTERN_C HANDLE g_hCounter;
|
|
HANDLE g_hCounter = NULL; // Global count of mods to Special Folder cache.
|
|
int g_lPerProcessCount = 0;
|
|
|
|
// Make sure the special folder cache is up to date.
|
|
void _UpdateShellFolderCache(void)
|
|
{
|
|
HANDLE hCounter = SHGetCachedGlobalCounter(&g_hCounter, &GUID_SystemPidlChange);
|
|
|
|
// Is the cache up to date?
|
|
long lGlobalCount = SHGlobalCounterGetValue(hCounter);
|
|
if (lGlobalCount != g_lPerProcessCount)
|
|
{
|
|
_ClearAllCacheEntrys();
|
|
g_lPerProcessCount = lGlobalCount;
|
|
}
|
|
}
|
|
|
|
STDAPI_(void) SHFlushSFCache(void)
|
|
{
|
|
// Increment the shared variable; the per-process versions will no
|
|
// longer match, causing this and/or other processes to refresh their
|
|
// pidl caches when they next need to access a folder.
|
|
if (g_hCounter)
|
|
SHGlobalCounterIncrement(g_hCounter);
|
|
}
|
|
|
|
// use SHGetFolderLocation() instead using CSIDL_FLAG_CREATE
|
|
|
|
STDAPI_(LPITEMIDLIST) SHCloneSpecialIDList(HWND hwnd, int csidl, BOOL fCreate)
|
|
{
|
|
LPITEMIDLIST pidlReturn;
|
|
|
|
if (fCreate)
|
|
csidl |= CSIDL_FLAG_CREATE;
|
|
|
|
SHGetSpecialFolderLocation(hwnd, csidl, &pidlReturn);
|
|
return pidlReturn;
|
|
}
|
|
|
|
STDAPI SHGetSpecialFolderLocation(HWND hwnd, int csidl, LPITEMIDLIST *ppidl)
|
|
{
|
|
HRESULT hr = SHGetFolderLocation(hwnd, csidl, NULL, 0, ppidl);
|
|
if (hr == S_FALSE)
|
|
hr = E_FAIL; // mail empty case into failure for compat with this API
|
|
return hr;
|
|
}
|
|
|
|
// return IDLIST for special folder
|
|
// fCreate encoded in csidl with CSIDL_FLAG_CREATE (new for NT5)
|
|
//
|
|
// in:
|
|
// hwnd should be NULL
|
|
// csidl CSIDL_ value with CSIDL_FLAG_ values ORed in as well
|
|
// dwType must be SHGFP_TYPE_CURRENT
|
|
//
|
|
// out:
|
|
// *ppild NULL on failure or empty, PIDL to be freed by caller on success
|
|
//
|
|
// returns:
|
|
// S_OK *ppidl is non NULL
|
|
// S_FALISE *ppidl is NULL, but valid csidl was passed (folder does not exist)
|
|
// FAILED(hr)
|
|
|
|
STDAPI SHGetFolderLocation(HWND hwnd, int csidl, HANDLE hToken, DWORD dwType, LPITEMIDLIST *ppidl)
|
|
{
|
|
const FOLDER_INFO *pfi;
|
|
HRESULT hr;
|
|
|
|
*ppidl = NULL; // in case of error or empty
|
|
|
|
// -1 is an invalid csidl
|
|
if ((dwType != SHGFP_TYPE_CURRENT) || (-1 == csidl))
|
|
return E_INVALIDARG; // no flags used yet, validate this param
|
|
|
|
pfi = _GetFolderInfo(csidl & ~CSIDL_FLAG_MASK);
|
|
if (pfi)
|
|
{
|
|
HANDLE hTokenToFree = NULL;
|
|
|
|
if ((hToken == NULL) && (pfi->hKey == HKEY_CURRENT_USER))
|
|
{
|
|
if (OpenThreadToken(GetCurrentThread(), TOKEN_QUERY | TOKEN_IMPERSONATE, TRUE, &hToken))
|
|
hTokenToFree = hToken;
|
|
}
|
|
if (hToken && (pfi->hKey == HKEY_CURRENT_USER))
|
|
{
|
|
// we don't cache PIDLs for other users, do all of the work
|
|
hr = _CreateFolderIDList(hwnd, pfi, hToken, csidl & CSIDL_FLAG_MASK, (LPITEMIDLIST *)ppidl);
|
|
}
|
|
else
|
|
{
|
|
hr = _GetFolderIDListCached(hwnd, pfi, csidl & CSIDL_FLAG_MASK, ppidl);
|
|
}
|
|
|
|
if (hTokenToFree)
|
|
CloseHandle(hTokenToFree);
|
|
}
|
|
else
|
|
hr = E_INVALIDARG; // bad CSIDL (apps can check to veryify our support)
|
|
return hr;
|
|
}
|
|
|
|
STDAPI_(BOOL) SHGetSpecialFolderPath(HWND hwnd, LPWSTR pszPath, int csidl, BOOL fCreate)
|
|
{
|
|
if (fCreate)
|
|
csidl |= CSIDL_FLAG_CREATE;
|
|
return SHGetFolderPath(hwnd, csidl, NULL, 0, pszPath) == S_OK;
|
|
}
|
|
|
|
// in:
|
|
// hwnd should be NULL
|
|
// csidl CSIDL_ value with CSIDL_FLAG_ values ORed in as well
|
|
// dwType must be SHGFP_TYPE_CURRENT
|
|
//
|
|
// out:
|
|
// *pszPath MAX_PATH buffer to get path name, zeroed on failure or empty case
|
|
//
|
|
// returns:
|
|
// S_OK filled in pszPath with path value
|
|
// S_FALSE pszPath is NULL, valid CSIDL value, but this folder does not exist
|
|
// E_FAIL
|
|
|
|
STDAPI SHGetFolderPath(HWND hwnd, int csidl, HANDLE hToken, DWORD dwType, LPWSTR pszPath)
|
|
{
|
|
HRESULT hr = E_INVALIDARG;
|
|
const FOLDER_INFO *pfi;
|
|
|
|
ASSERT(IS_VALID_WRITE_BUFFER(pszPath, TCHAR, MAX_PATH));
|
|
*pszPath = 0;
|
|
|
|
pfi = _GetFolderInfo(csidl & ~CSIDL_FLAG_MASK);
|
|
if (pfi && !(pfi->dwFlags & SDIF_NOT_FILESYS))
|
|
{
|
|
switch (dwType)
|
|
{
|
|
case SHGFP_TYPE_DEFAULT:
|
|
ASSERT((csidl & CSIDL_FLAG_MASK) == 0); // meaningless for default
|
|
hr = _GetFolderDefaultPath(pfi, hToken, pszPath);
|
|
break;
|
|
|
|
case SHGFP_TYPE_CURRENT:
|
|
{
|
|
HANDLE hTokenToFree = NULL;
|
|
if ((hToken == NULL) && (pfi->hKey == HKEY_CURRENT_USER))
|
|
{
|
|
if (OpenThreadToken(GetCurrentThread(), TOKEN_QUERY | TOKEN_IMPERSONATE, TRUE, &hToken))
|
|
hTokenToFree = hToken;
|
|
}
|
|
hr = _GetFolderPathCached(hwnd, pfi, hToken, csidl & CSIDL_FLAG_MASK, pszPath);
|
|
|
|
if (hTokenToFree)
|
|
CloseHandle(hTokenToFree);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
return hr;
|
|
}
|
|
|
|
STDAPI SHGetFolderPathA(HWND hwnd, int csidl, HANDLE hToken, DWORD dwType, LPSTR pszPath)
|
|
{
|
|
WCHAR wsz[MAX_PATH];
|
|
HRESULT hr = SHGetFolderPath(hwnd, csidl, hToken, dwType, wsz);
|
|
|
|
ASSERT(IS_VALID_WRITE_BUFFER(pszPath, CHAR, MAX_PATH));
|
|
|
|
SHUnicodeToAnsi(wsz, pszPath, MAX_PATH);
|
|
return hr;
|
|
}
|
|
|
|
STDAPI_(BOOL) SHGetSpecialFolderPathA(HWND hwnd, LPSTR pszPath, int csidl, BOOL fCreate)
|
|
{
|
|
if (fCreate)
|
|
csidl |= CSIDL_FLAG_CREATE;
|
|
return SHGetFolderPathA(hwnd, csidl, NULL, 0, pszPath) == S_OK;
|
|
}
|
|
|
|
// Similar to SHGetFolderPath, but appends an optional subdirectory path after
|
|
// the csidl folder path. Handles creating the subdirectories.
|
|
|
|
STDAPI SHGetFolderPathAndSubDir(HWND hwnd, int csidl, HANDLE hToken, DWORD dwFlags, LPCWSTR pszSubDir, LPWSTR pszPath)
|
|
{
|
|
HRESULT hr = SHGetFolderPath(hwnd, csidl, hToken, dwFlags, pszPath);
|
|
|
|
if (S_OK == hr && pszSubDir && *pszSubDir)
|
|
{
|
|
if (!PathAppend(pszPath, pszSubDir))
|
|
{
|
|
hr = HRESULT_FROM_WIN32(ERROR_FILENAME_EXCED_RANGE);
|
|
}
|
|
else if (csidl & CSIDL_FLAG_CREATE)
|
|
{
|
|
int err = SHCreateDirectoryEx(NULL, pszPath, NULL);
|
|
|
|
if (ERROR_ALREADY_EXISTS == err)
|
|
{
|
|
err = ERROR_SUCCESS;
|
|
}
|
|
hr = HRESULT_FROM_WIN32(err);
|
|
}
|
|
else if (!(csidl & CSIDL_FLAG_DONT_VERIFY))
|
|
{
|
|
DWORD dwAttributes;
|
|
|
|
if (PathFileExistsAndAttributes(pszPath, &dwAttributes))
|
|
{
|
|
if ((dwAttributes & FILE_ATTRIBUTE_DIRECTORY) == 0)
|
|
{
|
|
hr = HRESULT_FROM_WIN32(ERROR_FILE_EXISTS);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
hr = HRESULT_FROM_WIN32(ERROR_PATH_NOT_FOUND);
|
|
}
|
|
}
|
|
|
|
if (S_OK != hr)
|
|
{
|
|
*pszPath = 0;
|
|
}
|
|
}
|
|
|
|
return hr;
|
|
}
|
|
|
|
STDAPI SHGetFolderPathAndSubDirA(HWND hwnd, int csidl, HANDLE hToken, DWORD dwFlags, LPCSTR pszSubDir, LPSTR pszPath)
|
|
{
|
|
WCHAR wsz[MAX_PATH];
|
|
WCHAR wszSubDir[MAX_PATH];
|
|
|
|
SHAnsiToUnicode(pszSubDir, wszSubDir, MAX_PATH);
|
|
|
|
HRESULT hr = SHGetFolderPathAndSubDir(hwnd, csidl, hToken, dwFlags, wszSubDir, wsz);
|
|
|
|
ASSERT(IS_VALID_WRITE_BUFFER(pszPath, CHAR, MAX_PATH));
|
|
|
|
SHUnicodeToAnsi(wsz, pszPath, MAX_PATH);
|
|
return hr;
|
|
}
|
|
|
|
// HRESULT SHSetFolderPath (int csidl, HANDLE hToken, DWORD dwFlags, LPTSTR pszPath)
|
|
//
|
|
// in:
|
|
// csidl CSIDL_ value with CSIDL_FLAG_ values ORed in as well
|
|
// dwFlags reserved: should be 0x00000000
|
|
// pszPath path to change shell folder to (will optionally be unexpanded)
|
|
//
|
|
// returns:
|
|
// S_OK function succeeded and flushed cache
|
|
|
|
STDAPI SHSetFolderPath(int csidl, HANDLE hToken, DWORD dwFlags, LPCTSTR pszPath)
|
|
{
|
|
HRESULT hr = E_INVALIDARG;
|
|
|
|
// Validate csidl and dwFlags. Add extra valid flags as needed.
|
|
|
|
RIPMSG(((csidl & CSIDL_FLAG_MASK) & ~(CSIDL_FLAG_DONT_UNEXPAND | 0x00000000)) == 0, "SHSetFolderPath: CSIDL flag(s) invalid");
|
|
RIPMSG(dwFlags == 0, "SHSetFolderPath: dwFlags parameter must be 0x00000000");
|
|
|
|
// Exit with E_INVALIDARG if bad parameters.
|
|
|
|
if ((((csidl & CSIDL_FLAG_MASK) & ~(CSIDL_FLAG_DONT_UNEXPAND | 0x00000000)) != 0) ||
|
|
(dwFlags != 0) ||
|
|
(pszPath == NULL) ||
|
|
(pszPath[0] == 0))
|
|
{
|
|
return hr;
|
|
}
|
|
|
|
const FOLDER_INFO *pfi = _GetFolderInfo(csidl & ~CSIDL_FLAG_MASK);
|
|
|
|
// Only allow setting for SDIF_NOT_FILESYS is clear
|
|
// SDIF_NOT_TRACKED is clear
|
|
// SDIF_CANT_MOVE_RENAME is clear
|
|
// and for non-NULL value
|
|
|
|
// If HKLM is used then rely on security or registry restrictions
|
|
// to enforce whether the change can be made.
|
|
|
|
if ((pfi != NULL) &&
|
|
((pfi->dwFlags & (SDIF_NOT_FILESYS | SDIF_NOT_TRACKED | SDIF_CANT_MOVE_RENAME)) == 0))
|
|
{
|
|
BOOL fSuccessfulUnexpand, fSuccessfulExpand, fEmptyOrNullPath;
|
|
LONG lError;
|
|
HANDLE hTokenToFree;
|
|
TCHAR szPath[MAX_PATH];
|
|
TCHAR szExpandedPath[MAX_PATH]; // holds expanded path for "Shell Folder" compat key
|
|
LPCTSTR pszWritePath;
|
|
|
|
hTokenToFree = NULL;
|
|
if ((hToken == NULL) && (pfi->hKey == HKEY_CURRENT_USER))
|
|
{
|
|
if (OpenThreadToken(GetCurrentThread(), TOKEN_QUERY | TOKEN_IMPERSONATE, TRUE, &hToken))
|
|
{
|
|
hTokenToFree = hToken;
|
|
}
|
|
}
|
|
|
|
fEmptyOrNullPath = ((pszPath == NULL) || (pszPath[0] == 0));
|
|
if (fEmptyOrNullPath)
|
|
{
|
|
HKEY hKeyDefaultUser;
|
|
|
|
pszWritePath = NULL;
|
|
if (SUCCEEDED(_OpenKeyForFolder(pfi, (HANDLE)-1, TEXT("User Shell Folders"), &hKeyDefaultUser)))
|
|
{
|
|
DWORD dwPathSize = sizeof(szPath);
|
|
if (ERROR_SUCCESS == RegQueryValueEx(hKeyDefaultUser, pfi->pszValueName,
|
|
NULL, NULL, (LPBYTE)szPath, &dwPathSize))
|
|
{
|
|
pszWritePath = szPath;
|
|
}
|
|
RegCloseKey(hKeyDefaultUser);
|
|
}
|
|
fSuccessfulUnexpand = TRUE;
|
|
}
|
|
else if (csidl & CSIDL_FLAG_DONT_UNEXPAND)
|
|
{
|
|
// Does the caller want to write the string as is? Leave
|
|
// it alone if so.
|
|
|
|
pszWritePath = pszPath;
|
|
fSuccessfulUnexpand = TRUE;
|
|
}
|
|
else
|
|
{
|
|
if (pfi->hKey == HKEY_CURRENT_USER)
|
|
{
|
|
fSuccessfulUnexpand = (PathUnExpandEnvStringsForUser(hToken, pszPath, szPath, ARRAYSIZE(szPath)) != FALSE);
|
|
}
|
|
else
|
|
{
|
|
fSuccessfulUnexpand = FALSE;
|
|
}
|
|
|
|
// Choose the appropriate source if the unexpansion was successful or not.
|
|
// Either way the unexpansion failure should be ignored.
|
|
|
|
if (fSuccessfulUnexpand)
|
|
{
|
|
pszWritePath = szPath;
|
|
}
|
|
else
|
|
{
|
|
fSuccessfulUnexpand = TRUE;
|
|
pszWritePath = pszPath;
|
|
}
|
|
}
|
|
|
|
if (fSuccessfulUnexpand)
|
|
{
|
|
HKEY hKeyUser, hKeyUSF, hKeyToFree;
|
|
|
|
// we also get the fully expanded path so that we can write it out to the "Shell Folders" key for apps that depend on
|
|
// the old registry values
|
|
fSuccessfulExpand = (SHExpandEnvironmentStringsForUser(hToken, pszPath, szExpandedPath, ARRAYSIZE(szExpandedPath)) != 0);
|
|
|
|
// Get either the current users HKCU or HKU\SID if a token
|
|
// was specified and running in NT.
|
|
|
|
if (hToken && GetUserProfileKey(hToken, &hKeyUser))
|
|
{
|
|
hKeyToFree = hKeyUser;
|
|
}
|
|
else
|
|
{
|
|
hKeyUser = pfi->hKey;
|
|
hKeyToFree = NULL;
|
|
}
|
|
|
|
// Open the key to the User Shell Folders and write the string
|
|
// there. Clear the shell folder cache.
|
|
|
|
// NOTE: This functionality is duplicated in SetFolderPath but
|
|
// that function deals with the USF key only. This function
|
|
// requires HKU\SID so while there is identical functionality
|
|
// from the point of view of settings the USF value that is
|
|
// where it ends. To make this function simple it just writes
|
|
// the value to registry itself.
|
|
|
|
// Additional note: there is a threading issue here with
|
|
// clearing the cache entry incrementing the counter. This
|
|
// should be locked access.
|
|
|
|
lError = RegOpenKeyEx(hKeyUser, TEXT("Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\User Shell Folders"),
|
|
0, KEY_READ | KEY_WRITE, &hKeyUSF);
|
|
if (lError == ERROR_SUCCESS)
|
|
{
|
|
if (pszWritePath)
|
|
{
|
|
lError = RegSetValueEx(hKeyUSF, pfi->pszValueName, 0, REG_EXPAND_SZ,
|
|
(LPBYTE)pszWritePath, (lstrlen(pszWritePath) + sizeof('\0')) * sizeof(TCHAR));
|
|
}
|
|
else
|
|
{
|
|
lError = RegDeleteValue(hKeyUSF, pfi->pszValueName);
|
|
}
|
|
RegCloseKey(hKeyUSF);
|
|
|
|
// nuke the cache state for this folder
|
|
_ClearCacheEntry(pfi);
|
|
|
|
// and all folders that might be aliased as those
|
|
// could be related to this folder (under MyDocs for example)
|
|
// and now their aliases forms my no longer be valid
|
|
_ClearAllAliasCacheEntrys();
|
|
|
|
g_lPerProcessCount = SHGlobalCounterIncrement(g_hCounter);
|
|
}
|
|
|
|
// update the old "Shell Folders" value for compat
|
|
if ((lError == ERROR_SUCCESS) && fSuccessfulExpand)
|
|
{
|
|
HKEY hkeySF;
|
|
|
|
if (RegOpenKeyEx(hKeyUser, TEXT("Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\Shell Folders"),
|
|
0, KEY_READ | KEY_WRITE, &hkeySF) == ERROR_SUCCESS)
|
|
{
|
|
if (pszWritePath)
|
|
{
|
|
RegSetValueEx(hkeySF, pfi->pszValueName, 0, REG_SZ,
|
|
(LPBYTE)szExpandedPath, (lstrlen(szExpandedPath) + sizeof('\0')) * sizeof(TCHAR));
|
|
}
|
|
else
|
|
{
|
|
RegDeleteValue(hkeySF, pfi->pszValueName);
|
|
}
|
|
|
|
RegCloseKey(hkeySF);
|
|
}
|
|
}
|
|
|
|
if ((lError == ERROR_SUCCESS) && (pfi->hKey == HKEY_CURRENT_USER))
|
|
{
|
|
switch (csidl & ~CSIDL_FLAG_MASK)
|
|
{
|
|
case CSIDL_APPDATA:
|
|
{
|
|
HKEY hKeyVolatileEnvironment;
|
|
|
|
// In the case of AppData there is a matching environment variable
|
|
// for this shell folder. Make sure the place in the registry where
|
|
// userenv.dll places this value is updated and correct so that when
|
|
// the user context is created by winlogon it will have the updated
|
|
// value.
|
|
|
|
// It's probably also a good thing to check for a %APPDATA% variable
|
|
// in the calling process' context but this would only be good for
|
|
// the life of the process. What is really required is a mechanism
|
|
// to change the environment variable for the entire logon session.
|
|
|
|
lError = RegOpenKeyEx(hKeyUser, TEXT("Volatile Environment"), 0,
|
|
KEY_READ | KEY_WRITE, &hKeyVolatileEnvironment);
|
|
if (lError == ERROR_SUCCESS)
|
|
{
|
|
if (SUCCEEDED(SHGetFolderPath(NULL, csidl | CSIDL_FLAG_DONT_VERIFY,
|
|
hToken, SHGFP_TYPE_CURRENT, szPath)))
|
|
{
|
|
lError = RegSetValueEx(hKeyVolatileEnvironment, TEXT("APPDATA"),
|
|
0, REG_SZ, (LPBYTE)szPath, (lstrlen(szPath) + sizeof('\0')) * sizeof(TCHAR));
|
|
}
|
|
RegCloseKey(hKeyVolatileEnvironment);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (hKeyToFree)
|
|
{
|
|
RegCloseKey(hKeyToFree);
|
|
}
|
|
|
|
if (lError == ERROR_SUCCESS)
|
|
{
|
|
hr = S_OK;
|
|
}
|
|
else
|
|
{
|
|
hr = HRESULT_FROM_WIN32(lError);
|
|
}
|
|
}
|
|
if (hTokenToFree)
|
|
{
|
|
CloseHandle(hTokenToFree);
|
|
}
|
|
|
|
SHChangeDWORDAsIDList dwidl = { sizeof(dwidl) - sizeof(dwidl.cbZero), SHCNEE_UPDATEFOLDERLOCATION, csidl & ~CSIDL_FLAG_MASK, 0};
|
|
SHChangeNotify(SHCNE_EXTENDED_EVENT, SHCNF_ONLYNOTIFYINTERNALS | SHCNF_IDLIST, (LPCITEMIDLIST)&dwidl, NULL);
|
|
}
|
|
|
|
return hr;
|
|
}
|
|
|
|
STDAPI SHSetFolderPathA(int csidl, HANDLE hToken, DWORD dwType, LPCSTR pszPath)
|
|
{
|
|
WCHAR wsz[MAX_PATH];
|
|
|
|
SHAnsiToUnicode(pszPath, wsz, ARRAYSIZE(wsz));
|
|
return SHSetFolderPath(csidl, hToken, dwType, wsz);
|
|
}
|
|
|
|
// NOTE: called from DllEntry
|
|
|
|
void SpecialFolderIDTerminate()
|
|
{
|
|
ASSERTDLLENTRY // does not require a critical section
|
|
|
|
_ClearAllCacheEntrys();
|
|
|
|
if (g_hCounter)
|
|
{
|
|
CloseHandle(g_hCounter);
|
|
g_hCounter = NULL;
|
|
}
|
|
}
|
|
|
|
// update our cache and the registry for pfi with pszPath. this also invalidates the
|
|
// cache in other processes so they stay in sync
|
|
|
|
void SetFolderPath(const FOLDER_INFO *pfi, LPCTSTR pszPath)
|
|
{
|
|
_ClearCacheEntry(pfi);
|
|
|
|
if (pszPath)
|
|
{
|
|
HKEY hk;
|
|
if (SUCCEEDED(_OpenKeyForFolder(pfi, NULL, TEXT("User Shell Folders"), &hk)))
|
|
{
|
|
LONG err;
|
|
TCHAR szDefaultPath[MAX_PATH];
|
|
|
|
// Check for an existing path, and if the unexpanded version
|
|
// of the existing path does not match the new path, then
|
|
// write the new path to the registry.
|
|
//
|
|
// RegQueryPath expands the environment variables for us
|
|
// so we can't just blindly set the new value to the registry.
|
|
//
|
|
|
|
RegQueryPath(hk, pfi->pszValueName, szDefaultPath);
|
|
|
|
if (lstrcmpi(szDefaultPath, pszPath) != 0)
|
|
{
|
|
// The paths are different. Write to the registry as file
|
|
// system path.
|
|
|
|
err = SHRegSetPath(hk, NULL, pfi->pszValueName, pszPath, 0);
|
|
}
|
|
else
|
|
{
|
|
err = ERROR_SUCCESS;
|
|
}
|
|
|
|
// clear out any temp paths
|
|
RegSetFolderPath(pfi, TEXT("User Shell Folders\\New"), NULL);
|
|
|
|
if (err == ERROR_SUCCESS)
|
|
{
|
|
// this will force a new creation (see TRUE as fCreate).
|
|
// This will also copy the path from "User Shell Folders"
|
|
// to "Shell Folders".
|
|
LPITEMIDLIST pidl;
|
|
if (S_OK == _GetFolderIDListCached(NULL, pfi, CSIDL_FLAG_CREATE, &pidl))
|
|
{
|
|
ILFree(pidl);
|
|
}
|
|
else
|
|
{
|
|
// failed! null out the entry. this will go back to our default
|
|
RegDeleteValue(hk, pfi->pszValueName);
|
|
_ClearCacheEntry(pfi);
|
|
}
|
|
}
|
|
RegCloseKey(hk);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
RegSetFolderPath(pfi, TEXT("User Shell Folders"), NULL);
|
|
// clear out any temp paths
|
|
RegSetFolderPath(pfi, TEXT("User Shell Folders\\New"), NULL);
|
|
}
|
|
|
|
// set the global different from the per process variable
|
|
// to signal an update needs to happen other processes
|
|
g_lPerProcessCount = SHGlobalCounterIncrement(g_hCounter);
|
|
}
|
|
|
|
|
|
// file system change notifies come in here AFTER the folders have been moved/deleted
|
|
// we fix up the registry to match what occured in the file system
|
|
EXTERN_C void SFP_FSEvent(LONG lEvent, LPITEMIDLIST pidl, LPITEMIDLIST pidlExtra)
|
|
{
|
|
const FOLDER_INFO *pfi;
|
|
TCHAR szSrc[MAX_PATH];
|
|
|
|
if (!(lEvent & (SHCNE_RENAMEFOLDER | SHCNE_RMDIR | SHCNE_MKDIR)) ||
|
|
!SHGetPathFromIDList(pidl, szSrc) ||
|
|
(pidlExtra && ILIsEqual(pidl, pidlExtra))) // when volume label changes, pidl==pidlExtra so we detect this case and skip it for perf
|
|
{
|
|
return;
|
|
}
|
|
|
|
for (pfi = c_rgFolderInfo; pfi->id != -1; pfi++)
|
|
{
|
|
if (0 == (pfi->dwFlags & (SDIF_NOT_TRACKED | SDIF_NOT_FILESYS)))
|
|
{
|
|
TCHAR szCurrent[MAX_PATH];
|
|
if (S_OK == _GetFolderPathCached(NULL, pfi, NULL, CSIDL_FLAG_DONT_VERIFY, szCurrent) &&
|
|
PathIsEqualOrSubFolder(szSrc, szCurrent))
|
|
{
|
|
TCHAR szDest[MAX_PATH];
|
|
|
|
szDest[0] = 0;
|
|
|
|
if (lEvent & SHCNE_RMDIR)
|
|
{
|
|
// complete the "move accross volume" case
|
|
HKEY hk;
|
|
if (SUCCEEDED(_OpenKeyForFolder(pfi, NULL, TEXT("User Shell Folders\\New"), &hk)))
|
|
{
|
|
RegQueryPath(hk, pfi->pszValueName, szDest);
|
|
RegCloseKey(hk);
|
|
}
|
|
}
|
|
else if (pidlExtra)
|
|
{
|
|
SHGetPathFromIDList(pidlExtra, szDest);
|
|
}
|
|
|
|
if (szDest[0])
|
|
{
|
|
// rename the specal folder
|
|
UINT cch = PathCommonPrefix(szCurrent, szSrc, NULL);
|
|
ASSERT(cch != 0);
|
|
|
|
if (szCurrent[cch])
|
|
{
|
|
PathAppend(szDest, szCurrent + cch);
|
|
}
|
|
|
|
SetFolderPath(pfi, szDest);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
ULONG _ILGetChildOffset(LPCITEMIDLIST pidlParent, LPCITEMIDLIST pidlChild)
|
|
{
|
|
DWORD cbOff = 0;
|
|
LPCITEMIDLIST pidlChildT = ILFindChild(pidlParent, pidlChild);
|
|
if (pidlChildT)
|
|
{
|
|
cbOff = (ULONG)((LPBYTE)pidlChildT - (LPBYTE)pidlChild);
|
|
}
|
|
return cbOff;
|
|
}
|
|
|
|
// returns the first special folder CSIDL_ id that is a parent
|
|
// of the passed in pidl or 0 if not found. only CSIDL_ entries marked as
|
|
// SDIF_SHORTCUT_RELATIVE are considered for this.
|
|
//
|
|
// returns:
|
|
// CSIDL_ values
|
|
// *pcbOffset offset into pidl
|
|
|
|
STDAPI_(int) GetSpecialFolderParentIDAndOffset(LPCITEMIDLIST pidl, ULONG *pcbOffset)
|
|
{
|
|
int iRet = 0; // everything is desktop relative
|
|
const FOLDER_INFO *pfi;
|
|
|
|
*pcbOffset = 0;
|
|
|
|
for (pfi = c_rgFolderInfo; pfi->id != -1; pfi++)
|
|
{
|
|
if (pfi->dwFlags & SDIF_SHORTCUT_RELATIVE)
|
|
{
|
|
LPITEMIDLIST pidlFolder;
|
|
if (S_OK == _GetFolderIDListCached(NULL, pfi, 0, &pidlFolder))
|
|
{
|
|
ULONG cbOff = _ILGetChildOffset(pidlFolder, pidl);
|
|
if (cbOff > *pcbOffset)
|
|
{
|
|
*pcbOffset = cbOff;
|
|
iRet = pfi->id;
|
|
}
|
|
ILFree(pidlFolder);
|
|
}
|
|
}
|
|
}
|
|
return iRet;
|
|
}
|
|
|
|
// note, only works for file system path (bummer, we would like others supported too)
|
|
|
|
STDAPI_(BOOL) MakeShellURLFromPath(LPCTSTR pszPathIn, LPTSTR pszUrl, DWORD dwCch)
|
|
{
|
|
const FOLDER_INFO *pfi;
|
|
|
|
for (pfi = c_rgFolderInfo; pfi->id != -1; pfi++)
|
|
{
|
|
if ((pfi->dwFlags & SDIF_SHORTCUT_RELATIVE) &&
|
|
!(pfi->dwFlags & SDIF_NOT_FILESYS))
|
|
{
|
|
TCHAR szCurrent[MAX_PATH];
|
|
if (S_OK == _GetFolderPathCached(NULL, pfi, 0, CSIDL_FLAG_DONT_VERIFY, szCurrent))
|
|
{
|
|
if (PathIsPrefix(szCurrent, pszPathIn))
|
|
{
|
|
StrCpy(pszUrl, TEXT("shell:"));
|
|
StrCat(pszUrl, pfi->pszValueName);
|
|
PathAppend(pszUrl, &pszPathIn[lstrlen(szCurrent)]);
|
|
|
|
return TRUE;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
STDAPI_(BOOL) MakeShellURLFromPathA(LPCSTR pszPathIn, LPSTR pszUrl, DWORD dwCch)
|
|
{
|
|
WCHAR szTmp1[MAX_PATH], szTmp2[MAX_PATH];
|
|
SHAnsiToUnicode(pszPathIn, szTmp1, ARRAYSIZE(szTmp1));
|
|
|
|
BOOL bRet = MakeShellURLFromPathW(szTmp1, szTmp2, ARRAYSIZE(szTmp2));
|
|
|
|
SHUnicodeToAnsi(szTmp2, pszUrl, dwCch);
|
|
return bRet;
|
|
}
|
|
|
|
BOOL MoveBlockedByPolicy(const FOLDER_INFO *pfi)
|
|
{
|
|
BOOL bRet = FALSE;
|
|
if (pfi->dwFlags & SDIF_POLICY_NO_MOVE)
|
|
{
|
|
// similar to code in mydocs.dll
|
|
TCHAR szValue[128];
|
|
wnsprintf(szValue, ARRAYSIZE(szValue), TEXT("Disable%sDirChange"), pfi->pszValueName);
|
|
if (ERROR_SUCCESS == SHGetValue(HKEY_CURRENT_USER,
|
|
TEXT("Software\\Microsoft\\Windows\\CurrentVersion\\Policies\\Explorer"),
|
|
szValue, NULL, NULL, NULL))
|
|
{
|
|
bRet = TRUE;
|
|
}
|
|
}
|
|
return bRet;
|
|
}
|
|
|
|
// this is called from the copy engine (like all other copy hooks)
|
|
// this is where we put up UI blocking the delete/move of some special folders
|
|
EXTERN_C int PathCopyHookCallback(HWND hwnd, UINT wFunc, LPCTSTR pszSrc, LPCTSTR pszDest)
|
|
{
|
|
int ret = IDYES;
|
|
|
|
if ((wFunc == FO_DELETE) || (wFunc == FO_MOVE) || (wFunc == FO_RENAME))
|
|
{
|
|
const FOLDER_INFO *pfi;
|
|
|
|
// is one of our system directories being affected?
|
|
|
|
for (pfi = c_rgFolderInfo; ret == IDYES && pfi->id != -1; pfi++)
|
|
{
|
|
// even non tracked folders (windows, system) come through here
|
|
if (0 == (pfi->dwFlags & SDIF_NOT_FILESYS))
|
|
{
|
|
TCHAR szCurrent[MAX_PATH];
|
|
if (S_OK == _GetFolderPathCached(NULL, pfi, NULL, CSIDL_FLAG_DONT_VERIFY, szCurrent) &&
|
|
PathIsEqualOrSubFolder(pszSrc, szCurrent))
|
|
{
|
|
// Yes
|
|
if (wFunc == FO_DELETE)
|
|
{
|
|
if (pfi->dwFlags & SDIF_CAN_DELETE)
|
|
{
|
|
SetFolderPath(pfi, NULL); // Let them delete some folders
|
|
}
|
|
else
|
|
{
|
|
ShellMessageBox(HINST_THISDLL, hwnd, MAKEINTRESOURCE(IDS_CANTDELETESPECIALDIR),
|
|
MAKEINTRESOURCE(IDS_DELETE), MB_OK | MB_ICONINFORMATION, PathFindFileName(pszSrc));
|
|
ret = IDNO;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
int idSrc = PathGetDriveNumber(pszSrc);
|
|
int idDest = PathGetDriveNumber(pszDest);
|
|
|
|
ASSERT((wFunc == FO_MOVE) || (wFunc == FO_RENAME));
|
|
|
|
if ((pfi->dwFlags & SDIF_CANT_MOVE_RENAME) ||
|
|
((idSrc != -1) && (idDest == -1) && !(pfi->dwFlags & SDIF_NETWORKABLE)) ||
|
|
((idSrc != idDest) && PathIsRemovable(pszDest) && !(pfi->dwFlags & SDIF_REMOVABLE)) ||
|
|
MoveBlockedByPolicy(pfi))
|
|
{
|
|
ShellMessageBox(HINST_THISDLL, hwnd, MAKEINTRESOURCE(IDS_CANTMOVESPECIALDIRHERE),
|
|
wFunc == FO_MOVE ? MAKEINTRESOURCE(IDS_MOVE) : MAKEINTRESOURCE(IDS_RENAME),
|
|
MB_ICONERROR, PathFindFileName(pszSrc));
|
|
ret = IDNO;
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// store this info here
|
|
// if we need it we will use it.
|
|
//
|
|
// we used to try to optimise in the case of same
|
|
// volume renames. we assumed that if it was the same
|
|
// volume we would later get a SHCNE_RENAME. but sometimes
|
|
// we have to do a copy even on the same volume. so
|
|
// we need to always set this value.
|
|
//
|
|
RegSetFolderPath(pfi, TEXT("User Shell Folders\\New"), pszDest);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
// Given a key name ("programs", "desktop", "start menu"), convert it to
|
|
// the corresponding CSIDL.
|
|
|
|
STDAPI_(int) SHGetSpecialFolderID(LPCWSTR pszName)
|
|
{
|
|
// make sure g_aFolderCache can be indexed by the CSIDL values
|
|
|
|
COMPILETIME_ASSERT((ARRAYSIZE(g_aFolderCache) - 1) == CSIDL_COMPUTERSNEARME);
|
|
|
|
for (int i = 0; c_rgFolderInfo[i].id != -1; i++)
|
|
{
|
|
if (c_rgFolderInfo[i].pszValueName &&
|
|
(0 == StrCmpI(pszName, c_rgFolderInfo[i].pszValueName)))
|
|
{
|
|
return c_rgFolderInfo[i].id;
|
|
}
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
// Given a CSIDL, returns the key name -- the opposite of
|
|
// SHGetSpecialFolderID
|
|
|
|
STDAPI_(LPCTSTR) SHGetSpecialFolderKey(int csidl)
|
|
{
|
|
const FOLDER_INFO *pfi = _GetFolderInfo(csidl);
|
|
return pfi ? pfi->pszValueName : NULL;
|
|
}
|
|
|
|
|
|
// Return the special folder ID, if this folder is one of them.
|
|
// At this point, we handle PROGRAMS folder only.
|
|
|
|
//
|
|
// GetSpecialFolderID()
|
|
// this allows a list of CSIDLs to be passed in.
|
|
// they will be searched in order for the specified csidl
|
|
// and the path will be checked against it.
|
|
// if -1 is specified as the csidl, then all of array entries should
|
|
// be checked for a match with the folder.
|
|
//
|
|
int GetSpecialFolderID(LPCTSTR pszFolder, const int *rgcsidl, UINT count)
|
|
{
|
|
for (UINT i = 0; i < count; i++)
|
|
{
|
|
int csidlSpecial = rgcsidl[i] & ~TEST_SUBFOLDER;
|
|
TCHAR szPath[MAX_PATH];
|
|
if (S_OK == SHGetFolderPath(NULL, csidlSpecial | CSIDL_FLAG_DONT_VERIFY, NULL, SHGFP_TYPE_CURRENT, szPath))
|
|
{
|
|
if (((rgcsidl[i] & TEST_SUBFOLDER) && PathIsEqualOrSubFolder(szPath, pszFolder)) ||
|
|
(lstrcmpi(szPath, pszFolder) == 0))
|
|
{
|
|
return csidlSpecial;
|
|
}
|
|
}
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
* Tacks a name onto a CSIDL, e.g. gets a pidl for
|
|
* CSIDL_COMMON_PICTURES\Sample Pictures
|
|
* if it exists.
|
|
* Called must free ppidlSampleMedia
|
|
* Note: The folder is *not* created if it does not exist.
|
|
*/
|
|
HRESULT _AppendPathToPIDL(int nAllUsersMediaFolder, LPCWSTR pszName, LPITEMIDLIST *ppidlSampleMedia)
|
|
{
|
|
LPITEMIDLIST pidlAllUsersMedia;
|
|
HRESULT hr = SHGetFolderLocation(NULL, nAllUsersMediaFolder, NULL, 0, &pidlAllUsersMedia);
|
|
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
// Get the shellfolder for this guy.
|
|
IShellFolder *psf;
|
|
hr = SHBindToObject(NULL, IID_X_PPV_ARG(IShellFolder, pidlAllUsersMedia, &psf));
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
// And now the pidl for the sample folder
|
|
LPITEMIDLIST pidlSampleMediaRel;
|
|
ULONG dwAttributes = 0;
|
|
hr = psf->ParseDisplayName(NULL, NULL, (LPOLESTR)pszName, NULL, &pidlSampleMediaRel, &dwAttributes);
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
// It exists!
|
|
hr = SHILCombine(pidlAllUsersMedia, pidlSampleMediaRel, ppidlSampleMedia);
|
|
ILFree(pidlSampleMediaRel);
|
|
}
|
|
psf->Release();
|
|
}
|
|
ILFree(pidlAllUsersMedia);
|
|
}
|
|
|
|
return hr;
|
|
}
|
|
|
|
|
|
/**
|
|
* Returns a pidl to the samples folder under a particular CSIDL
|
|
* Caller must free ppidlSampleMedia
|
|
*/
|
|
HRESULT _ParseSubfolderResource(int csidl, UINT ids, LPITEMIDLIST *ppidl)
|
|
{
|
|
WCHAR szSub[MAX_PATH];
|
|
LoadDefaultString(ids, szSub, ARRAYSIZE(szSub));
|
|
|
|
return _AppendPathToPIDL(csidl, szSub, ppidl);
|
|
}
|
|
|
|
HRESULT SHGetSampleMediaFolder(int nAllUsersMediaFolder, LPITEMIDLIST *ppidlSampleMedia)
|
|
{
|
|
UINT uID = -1;
|
|
switch (nAllUsersMediaFolder)
|
|
{
|
|
case CSIDL_COMMON_PICTURES:
|
|
uID = IDS_SAMPLEPICTURES;
|
|
break;
|
|
case CSIDL_COMMON_MUSIC:
|
|
uID = IDS_SAMPLEMUSIC;
|
|
break;
|
|
default:
|
|
ASSERT(FALSE);
|
|
return E_INVALIDARG;
|
|
break;
|
|
}
|
|
return _ParseSubfolderResource(nAllUsersMediaFolder, uID, ppidlSampleMedia);
|
|
}
|
|
|
|
void _CreateLinkToSampleMedia(LPCWSTR pszNewFolderPath, int nAllUsersMediaFolder, UINT uIDSampleFolderName)
|
|
{
|
|
LPITEMIDLIST pidl;
|
|
if (SUCCEEDED(SHGetSampleMediaFolder(nAllUsersMediaFolder, &pidl)))
|
|
{
|
|
// Check to make sure the link doesn't already exist.
|
|
WCHAR szSampleFolderName[MAX_PATH];
|
|
WCHAR szFullLnkPath[MAX_PATH];
|
|
LoadString(HINST_THISDLL, uIDSampleFolderName, szSampleFolderName, ARRAYSIZE(szSampleFolderName));
|
|
StrCatBuff(szSampleFolderName, L".lnk", ARRAYSIZE(szSampleFolderName));
|
|
if (PathCombine(szFullLnkPath, pszNewFolderPath, szSampleFolderName))
|
|
{
|
|
if (!PathFileExists(szFullLnkPath))
|
|
{
|
|
// MUI-WARNING - we are not doing a SHSetLocalizedName for this link - ZekeL - 15-MAY-2001
|
|
// this means that this link is always created in the default system UI language
|
|
// we should probably call SHSetLocalizedName() here but i am scared right now of perf implications.
|
|
CreateLinkToPidl(pidl, pszNewFolderPath, NULL, 0);
|
|
}
|
|
}
|
|
|
|
ILFree(pidl);
|
|
}
|
|
}
|
|
|
|
|
|
void _InitFolder(LPCTSTR pszPath, UINT idsInfoTip, HINSTANCE hinstIcon, UINT idiIcon)
|
|
{
|
|
// Set the default custom settings for the folder.
|
|
SHFOLDERCUSTOMSETTINGS fcs = {sizeof(fcs), 0};
|
|
TCHAR szInfoTip[128];
|
|
TCHAR szPath[MAX_PATH];
|
|
|
|
// Get the infotip for this folder
|
|
if (idsInfoTip)
|
|
{
|
|
wnsprintf(szInfoTip,ARRAYSIZE(szInfoTip),TEXT("@Shell32.dll,-%u"),idsInfoTip);
|
|
fcs.pszInfoTip = szInfoTip;
|
|
fcs.cchInfoTip = ARRAYSIZE(szInfoTip);
|
|
|
|
fcs.dwMask |= FCSM_INFOTIP;
|
|
}
|
|
|
|
// this will be encoded to the %SystemRoot% style path when setting the folder information.
|
|
if (idiIcon)
|
|
{
|
|
GetModuleFileName(hinstIcon, szPath, ARRAYSIZE(szPath));
|
|
|
|
fcs.pszIconFile = szPath;
|
|
fcs.cchIconFile = ARRAYSIZE(szPath);
|
|
fcs.iIconIndex = idiIcon;
|
|
|
|
fcs.dwMask |= FCSM_ICONFILE;
|
|
}
|
|
|
|
// NOTE: we need FCS_FORCEWRITE because we didn't used to specify iIconIndex
|
|
// and so "0" was written to the ini file. When we upgrade, this API won't
|
|
// fix the ini file unless we pass FCS_FORCEWRITE
|
|
SHGetSetFolderCustomSettings(&fcs, pszPath, FCS_FORCEWRITE);
|
|
}
|
|
|
|
void _InitMyPictures(int id, LPCTSTR pszPath)
|
|
{
|
|
// Get the path to the icon. We reference MyDocs.dll for backwards compat.
|
|
HINSTANCE hinstMyDocs = LoadLibrary(TEXT("mydocs.dll"));
|
|
if (hinstMyDocs)
|
|
{
|
|
_InitFolder(pszPath, IDS_FOLDER_MYPICS_TT, hinstMyDocs, -101); // known index for IDI_MYPICS in mydocs.dll
|
|
FreeLibrary(hinstMyDocs);
|
|
}
|
|
}
|
|
|
|
void _InitMyMusic(int id, LPCTSTR pszPath)
|
|
{
|
|
_InitFolder(pszPath, IDS_FOLDER_MYMUSIC_TT, HINST_THISDLL, -IDI_MYMUSIC);
|
|
}
|
|
|
|
void _InitPerUserMyPictures(int id, LPCTSTR pszPath)
|
|
{
|
|
_InitMyPictures(id, pszPath);
|
|
|
|
_CreateLinkToSampleMedia(pszPath, CSIDL_COMMON_PICTURES, IDS_SAMPLEPICTURES);
|
|
}
|
|
|
|
void _InitPerUserMyMusic(int id, LPCTSTR pszPath)
|
|
{
|
|
_InitMyMusic(id, pszPath);
|
|
|
|
_CreateLinkToSampleMedia(pszPath, CSIDL_COMMON_MUSIC, IDS_SAMPLEMUSIC);
|
|
}
|
|
|
|
|
|
void _InitMyVideos(int id, LPCTSTR pszPath)
|
|
{
|
|
_InitFolder(pszPath, IDS_FOLDER_MYVIDEOS_TT, HINST_THISDLL, -IDI_MYVIDEOS);
|
|
}
|
|
|
|
void _InitRecentDocs(int id, LPCTSTR pszPath)
|
|
{
|
|
_InitFolder(pszPath, IDS_FOLDER_RECENTDOCS_TT, HINST_THISDLL, -IDI_STDOCS);
|
|
}
|
|
|
|
void _InitFavorites(int id, LPCTSTR pszPath)
|
|
{
|
|
_InitFolder(pszPath, 0, HINST_THISDLL, -IDI_FAVORITES);
|
|
}
|