Commit 81b4dba5 authored by Olivier F. R. Dierick's avatar Olivier F. R. Dierick Committed by Alexandre Julliard

shell32: Move _SHCreateSymbolicLinks() above shell folders lookup functions.

Looking up shell folders creates real directories when the folder is inexistant, but the intended behavior for some folders is to create symbolic links by default. Prepare that change by moving the helper function above shell folders lookup functions. Also fix whitespace issues in the moved code. Wine-Bug: https://bugs.winehq.org/show_bug.cgi?id=22974Signed-off-by: 's avatarOlivier F. R. Dierick <o.dierick@piezo-forte.be> Signed-off-by: 's avatarAlexandre Julliard <julliard@winehq.org>
parent 05a8d45e
......@@ -3949,301 +3949,566 @@ end:
}
/*************************************************************************
* SHGetFolderPathW [SHELL32.@]
* _SHAppendToUnixPath [Internal]
*
* Convert nFolder to path.
* Helper function for _SHCreateSymbolicLinks. Appends pwszSubPath (or the
* corresponding resource, if IS_INTRESOURCE) to the unix base path 'szBasePath'
* and replaces backslashes with slashes.
*
* RETURNS
* Success: S_OK
* Failure: standard HRESULT error codes.
* PARAMS
* szBasePath [IO] The unix base path, which will be appended to (CP_UNXICP).
* pwszSubPath [I] Sub-path or resource id (use MAKEINTRESOURCEW).
*
* NOTES
* Most values can be overridden in either
* HKCU\Software\Microsoft\Windows\CurrentVersion\Explorer\User Shell Folders
* or in the same location in HKLM.
* The "Shell Folders" registry key was used in NT4 and earlier systems.
* Beginning with Windows 2000, the "User Shell Folders" key is used, so
* changes made to it are made to the former key too. This synchronization is
* done on-demand: not until someone requests the value of one of these paths
* (by calling one of the SHGet functions) is the value synchronized.
* Furthermore, the HKCU paths take precedence over the HKLM paths.
* RETURNS
* Success: TRUE,
* Failure: FALSE
*/
HRESULT WINAPI SHGetFolderPathW(
HWND hwndOwner, /* [I] owner window */
int nFolder, /* [I] CSIDL identifying the folder */
HANDLE hToken, /* [I] access token */
DWORD dwFlags, /* [I] which path to return */
LPWSTR pszPath) /* [O] converted path */
{
HRESULT hr = SHGetFolderPathAndSubDirW(hwndOwner, nFolder, hToken, dwFlags, NULL, pszPath);
if(HRESULT_FROM_WIN32(ERROR_PATH_NOT_FOUND) == hr)
hr = HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND);
return hr;
}
HRESULT WINAPI SHGetFolderPathAndSubDirA(
HWND hwndOwner, /* [I] owner window */
int nFolder, /* [I] CSIDL identifying the folder */
HANDLE hToken, /* [I] access token */
DWORD dwFlags, /* [I] which path to return */
LPCSTR pszSubPath, /* [I] sub directory of the specified folder */
LPSTR pszPath) /* [O] converted path */
{
int length;
HRESULT hr = S_OK;
LPWSTR pszSubPathW = NULL;
LPWSTR pszPathW = NULL;
TRACE("%p,%#x,%p,%#x,%s,%p\n", hwndOwner, nFolder, hToken, dwFlags, debugstr_a(pszSubPath), pszPath);
static inline BOOL _SHAppendToUnixPath(char *szBasePath, LPCWSTR pwszSubPath) {
WCHAR wszSubPath[MAX_PATH];
int cLen = strlen(szBasePath);
char *pBackslash;
if(pszPath) {
pszPathW = heap_alloc(MAX_PATH * sizeof(WCHAR));
if(!pszPathW) {
hr = HRESULT_FROM_WIN32(ERROR_NOT_ENOUGH_MEMORY);
goto cleanup;
}
if (IS_INTRESOURCE(pwszSubPath)) {
if (!LoadStringW(shell32_hInstance, LOWORD(pwszSubPath), wszSubPath, MAX_PATH)) {
/* Fall back to hard coded defaults. */
switch (LOWORD(pwszSubPath)) {
case IDS_PERSONAL:
lstrcpyW(wszSubPath, DocumentsW);
break;
case IDS_MYMUSIC:
lstrcpyW(wszSubPath, My_MusicW);
break;
case IDS_MYPICTURES:
lstrcpyW(wszSubPath, My_PicturesW);
break;
case IDS_MYVIDEOS:
lstrcpyW(wszSubPath, My_VideosW);
break;
case IDS_DOWNLOADS:
lstrcpyW(wszSubPath, DownloadsW);
break;
case IDS_TEMPLATES:
lstrcpyW(wszSubPath, TemplatesW);
break;
default:
ERR("LoadString(%d) failed!\n", LOWORD(pwszSubPath));
return FALSE;
}
TRACE("%08x,%08x,%s\n",nFolder, dwFlags, debugstr_w(pszSubPathW));
/* SHGetFolderPathAndSubDirW does not distinguish if pszSubPath isn't
* set (null), or an empty string.therefore call it without the parameter set
* if pszSubPath is an empty string
*/
if (pszSubPath && pszSubPath[0]) {
length = MultiByteToWideChar(CP_ACP, 0, pszSubPath, -1, NULL, 0);
pszSubPathW = heap_alloc(length * sizeof(WCHAR));
if(!pszSubPathW) {
hr = HRESULT_FROM_WIN32(ERROR_NOT_ENOUGH_MEMORY);
goto cleanup;
}
MultiByteToWideChar(CP_ACP, 0, pszSubPath, -1, pszSubPathW, length);
} else {
lstrcpyW(wszSubPath, pwszSubPath);
}
hr = SHGetFolderPathAndSubDirW(hwndOwner, nFolder, hToken, dwFlags, pszSubPathW, pszPathW);
if (szBasePath[cLen-1] != '/') szBasePath[cLen++] = '/';
if (SUCCEEDED(hr) && pszPath)
WideCharToMultiByte(CP_ACP, 0, pszPathW, -1, pszPath, MAX_PATH, NULL, NULL);
if (!WideCharToMultiByte(CP_UNIXCP, 0, wszSubPath, -1, szBasePath + cLen,
FILENAME_MAX - cLen, NULL, NULL))
{
return FALSE;
}
cleanup:
heap_free(pszPathW);
heap_free(pszSubPathW);
return hr;
pBackslash = szBasePath + cLen;
while ((pBackslash = strchr(pBackslash, '\\'))) *pBackslash = '/';
return TRUE;
}
/*************************************************************************
* SHGetFolderPathAndSubDirW [SHELL32.@]
/******************************************************************************
* _SHCreateSymbolicLinks [Internal]
*
* Sets up symbol links for various shell folders to point into the user's home
* directory. We do an educated guess about what the user would probably want:
* - If there is a 'My Documents' directory in $HOME, the user probably wants
* wine's 'My Documents' to point there. Furthermore, we infer that the user
* is a Windows lover and has no problem with wine creating subfolders for
* 'My Pictures', 'My Music', 'My Videos' etc. under '$HOME/My Documents', if
* those do not already exist. We put appropriate symbolic links in place for
* those, too.
* - If there is no 'My Documents' directory in $HOME, we let 'My Documents'
* point directly to $HOME. We assume the user to be a unix hacker who does not
* want wine to create anything anywhere besides the .wine directory. So, if
* there already is a 'My Music' directory in $HOME, we symlink the 'My Music'
* shell folder to it. But if not, then we check XDG_MUSIC_DIR - "well known"
* directory, and try to link to that. If that fails, then we symlink to
* $HOME directly. The same holds for 'My Pictures', 'My Videos' etc.
* - The Desktop shell folder is symlinked to XDG_DESKTOP_DIR. If that does not
* exist, then we try '$HOME/Desktop'. If that does not exist, then we leave
* it alone.
* ('My Music',... above in fact means LoadString(IDS_MYMUSIC))
*/
HRESULT WINAPI SHGetFolderPathAndSubDirW(
HWND hwndOwner, /* [I] owner window */
int nFolder, /* [I] CSIDL identifying the folder */
HANDLE hToken, /* [I] access token */
DWORD dwFlags, /* [I] which path to return */
LPCWSTR pszSubPath,/* [I] sub directory of the specified folder */
LPWSTR pszPath) /* [O] converted path */
static void _SHCreateSymbolicLinks(void)
{
static const UINT aidsMyStuff[] = {
IDS_MYPICTURES, IDS_MYVIDEOS, IDS_MYMUSIC, IDS_DOWNLOADS, IDS_TEMPLATES
};
static const WCHAR * const MyOSXStuffW[] = {
PicturesW, MoviesW, MusicW, DownloadsW, TemplatesW
};
static const int acsidlMyStuff[] = {
CSIDL_MYPICTURES, CSIDL_MYVIDEO, CSIDL_MYMUSIC, CSIDL_DOWNLOADS, CSIDL_TEMPLATES
};
static const char * const xdg_dirs[] = {
"PICTURES", "VIDEOS", "MUSIC", "DOWNLOAD", "TEMPLATES", "DOCUMENTS", "DESKTOP"
};
static const unsigned int num = ARRAY_SIZE(xdg_dirs);
WCHAR wszTempPath[MAX_PATH];
char szPersonalTarget[FILENAME_MAX], *pszPersonal;
char szMyStuffTarget[FILENAME_MAX], *pszMyStuff;
char szDesktopTarget[FILENAME_MAX], *pszDesktop;
struct stat statFolder;
const char *pszHome;
HRESULT hr;
WCHAR szBuildPath[MAX_PATH], szTemp[MAX_PATH];
DWORD folder = nFolder & CSIDL_FOLDER_MASK;
CSIDL_Type type;
int ret;
char ** xdg_results;
char * xdg_desktop_dir;
UINT i;
TRACE("%p,%#x,%p,%#x,%s,%p\n", hwndOwner, nFolder, hToken, dwFlags, debugstr_w(pszSubPath), pszPath);
/* Create all necessary profile sub-dirs up to 'My Documents' and get the unix path. */
hr = SHGetFolderPathW(NULL, CSIDL_PERSONAL|CSIDL_FLAG_CREATE, NULL,
SHGFP_TYPE_DEFAULT, wszTempPath);
if (FAILED(hr)) return;
pszPersonal = wine_get_unix_file_name(wszTempPath);
if (!pszPersonal) return;
/* Windows always NULL-terminates the resulting path regardless of success
* or failure, so do so first
*/
if (pszPath)
*pszPath = '\0';
hr = XDG_UserDirLookup(xdg_dirs, num, &xdg_results);
if (FAILED(hr)) xdg_results = NULL;
if (folder >= ARRAY_SIZE(CSIDL_Data))
return E_INVALIDARG;
if ((SHGFP_TYPE_CURRENT != dwFlags) && (SHGFP_TYPE_DEFAULT != dwFlags))
return E_INVALIDARG;
szTemp[0] = 0;
type = CSIDL_Data[folder].type;
switch (type)
pszHome = getenv("HOME");
if (pszHome && !stat(pszHome, &statFolder) && S_ISDIR(statFolder.st_mode))
{
case CSIDL_Type_Disallowed:
hr = E_INVALIDARG;
break;
case CSIDL_Type_NonExistent:
hr = S_FALSE;
break;
case CSIDL_Type_WindowsPath:
GetWindowsDirectoryW(szTemp, MAX_PATH);
if (CSIDL_Data[folder].szDefaultPath &&
!IS_INTRESOURCE(CSIDL_Data[folder].szDefaultPath) &&
*CSIDL_Data[folder].szDefaultPath)
while (1)
{
PathAddBackslashW(szTemp);
strcatW(szTemp, CSIDL_Data[folder].szDefaultPath);
}
hr = S_OK;
break;
case CSIDL_Type_SystemPath:
GetSystemDirectoryW(szTemp, MAX_PATH);
if (CSIDL_Data[folder].szDefaultPath &&
!IS_INTRESOURCE(CSIDL_Data[folder].szDefaultPath) &&
*CSIDL_Data[folder].szDefaultPath)
/* Check if there's already a Wine-specific 'My Documents' folder */
strcpy(szPersonalTarget, pszHome);
if (_SHAppendToUnixPath(szPersonalTarget, MAKEINTRESOURCEW(IDS_PERSONAL)) &&
!stat(szPersonalTarget, &statFolder) && S_ISDIR(statFolder.st_mode))
{
PathAddBackslashW(szTemp);
strcatW(szTemp, CSIDL_Data[folder].szDefaultPath);
/* '$HOME/My Documents' exists. Create subfolders for
* 'My Pictures', 'My Videos', 'My Music' etc. or fail silently
* if they already exist.
*/
for (i = 0; i < ARRAY_SIZE(aidsMyStuff); i++)
{
strcpy(szMyStuffTarget, szPersonalTarget);
if (_SHAppendToUnixPath(szMyStuffTarget, MAKEINTRESOURCEW(aidsMyStuff[i])))
mkdir(szMyStuffTarget, 0777);
}
hr = S_OK;
break;
case CSIDL_Type_SystemX86Path:
if (!GetSystemWow64DirectoryW(szTemp, MAX_PATH)) GetSystemDirectoryW(szTemp, MAX_PATH);
if (CSIDL_Data[folder].szDefaultPath &&
!IS_INTRESOURCE(CSIDL_Data[folder].szDefaultPath) &&
*CSIDL_Data[folder].szDefaultPath)
{
PathAddBackslashW(szTemp);
strcatW(szTemp, CSIDL_Data[folder].szDefaultPath);
}
hr = S_OK;
/* Try to point to the XDG Documents folder */
if (xdg_results && xdg_results[num-2] &&
!stat(xdg_results[num-2], &statFolder) &&
S_ISDIR(statFolder.st_mode))
{
strcpy(szPersonalTarget, xdg_results[num-2]);
break;
case CSIDL_Type_CurrVer:
hr = _SHGetCurrentVersionPath(dwFlags, folder, szTemp);
break;
case CSIDL_Type_User:
hr = _SHGetUserProfilePath(hToken, dwFlags, folder, szTemp);
break;
case CSIDL_Type_AllUsers:
case CSIDL_Type_ProgramData:
hr = _SHGetAllUsersProfilePath(dwFlags, folder, szTemp);
}
/* Or the hardcoded / OS X Documents folder */
strcpy(szPersonalTarget, pszHome);
if (_SHAppendToUnixPath(szPersonalTarget, DocumentsW) &&
!stat(szPersonalTarget, &statFolder) &&
S_ISDIR(statFolder.st_mode))
break;
default:
FIXME("bogus type %d, please fix\n", type);
hr = E_INVALIDARG;
/* As a last resort point to $HOME. */
strcpy(szPersonalTarget, pszHome);
break;
}
/* Expand environment strings if necessary */
if (*szTemp == '%')
hr = _SHExpandEnvironmentStrings(szTemp, szBuildPath);
/* Replace 'My Documents' directory with a symlink or fail silently if not empty. */
remove(pszPersonal);
symlink(szPersonalTarget, pszPersonal);
}
else
strcpyW(szBuildPath, szTemp);
{
/* '$HOME' doesn't exist. Create subdirs for 'My Pictures', 'My Videos',
* 'My Music' etc. in '%USERPROFILE%\My Documents' or fail silently if
* they already exist. */
pszHome = NULL;
strcpy(szPersonalTarget, pszPersonal);
for (i = 0; i < ARRAY_SIZE(aidsMyStuff); i++) {
strcpy(szMyStuffTarget, szPersonalTarget);
if (_SHAppendToUnixPath(szMyStuffTarget, MAKEINTRESOURCEW(aidsMyStuff[i])))
mkdir(szMyStuffTarget, 0777);
}
}
if (FAILED(hr)) goto end;
/* Create symbolic links for 'My Pictures', 'My Videos', 'My Music' etc. */
for (i=0; i < ARRAY_SIZE(aidsMyStuff); i++)
{
/* Create the current 'My Whatever' folder and get its unix path. */
hr = SHGetFolderPathW(NULL, acsidlMyStuff[i]|CSIDL_FLAG_CREATE, NULL,
SHGFP_TYPE_DEFAULT, wszTempPath);
if (FAILED(hr)) continue;
if(pszSubPath) {
/* make sure the new path does not exceed the buffer length
* and remember to backslash and terminate it */
if(MAX_PATH < (lstrlenW(szBuildPath) + lstrlenW(pszSubPath) + 2)) {
hr = HRESULT_FROM_WIN32(ERROR_FILENAME_EXCED_RANGE);
goto end;
pszMyStuff = wine_get_unix_file_name(wszTempPath);
if (!pszMyStuff) continue;
while (1)
{
/* Check for the Wine-specific '$HOME/My Documents' subfolder */
strcpy(szMyStuffTarget, szPersonalTarget);
if (_SHAppendToUnixPath(szMyStuffTarget, MAKEINTRESOURCEW(aidsMyStuff[i])) &&
!stat(szMyStuffTarget, &statFolder) && S_ISDIR(statFolder.st_mode))
break;
/* Try the XDG_XXX_DIR folder */
if (xdg_results && xdg_results[i])
{
strcpy(szMyStuffTarget, xdg_results[i]);
break;
}
PathAppendW(szBuildPath, pszSubPath);
PathRemoveBackslashW(szBuildPath);
/* Or the OS X folder (these are never localized) */
if (pszHome)
{
strcpy(szMyStuffTarget, pszHome);
if (_SHAppendToUnixPath(szMyStuffTarget, MyOSXStuffW[i]) &&
!stat(szMyStuffTarget, &statFolder) &&
S_ISDIR(statFolder.st_mode))
break;
}
/* Copy the path if it's available before we might return */
if (SUCCEEDED(hr) && pszPath)
strcpyW(pszPath, szBuildPath);
/* if we don't care about existing directories we are ready */
if(nFolder & CSIDL_FLAG_DONT_VERIFY) goto end;
/* As a last resort point to the same location as 'My Documents' */
strcpy(szMyStuffTarget, szPersonalTarget);
break;
}
remove(pszMyStuff);
symlink(szMyStuffTarget, pszMyStuff);
heap_free(pszMyStuff);
}
if (PathFileExistsW(szBuildPath)) goto end;
/* Last but not least, the Desktop folder */
if (pszHome)
strcpy(szDesktopTarget, pszHome);
else
strcpy(szDesktopTarget, pszPersonal);
heap_free(pszPersonal);
/* not existing but we are not allowed to create it. The return value
* is verified against shell32 version 6.0.
*/
if (!(nFolder & CSIDL_FLAG_CREATE))
xdg_desktop_dir = xdg_results ? xdg_results[num - 1] : NULL;
if (xdg_desktop_dir ||
(_SHAppendToUnixPath(szDesktopTarget, DesktopW) &&
!stat(szDesktopTarget, &statFolder) && S_ISDIR(statFolder.st_mode)))
{
hr = HRESULT_FROM_WIN32(ERROR_PATH_NOT_FOUND);
goto end;
hr = SHGetFolderPathW(NULL, CSIDL_DESKTOPDIRECTORY|CSIDL_FLAG_CREATE, NULL,
SHGFP_TYPE_DEFAULT, wszTempPath);
if (SUCCEEDED(hr) && (pszDesktop = wine_get_unix_file_name(wszTempPath)))
{
remove(pszDesktop);
if (xdg_desktop_dir)
symlink(xdg_desktop_dir, pszDesktop);
else
symlink(szDesktopTarget, pszDesktop);
heap_free(pszDesktop);
}
}
/* create directory/directories */
ret = SHCreateDirectoryExW(hwndOwner, szBuildPath, NULL);
if (ret && ret != ERROR_ALREADY_EXISTS)
/* Free resources allocated by XDG_UserDirLookup() */
if (xdg_results)
{
ERR("Failed to create directory %s.\n", debugstr_w(szBuildPath));
hr = E_FAIL;
goto end;
for (i = 0; i < num; i++)
heap_free(xdg_results[i]);
heap_free(xdg_results);
}
TRACE("Created missing system directory %s\n", debugstr_w(szBuildPath));
end:
TRACE("returning 0x%08x (final path is %s)\n", hr, debugstr_w(szBuildPath));
return hr;
}
/*************************************************************************
* SHGetFolderPathA [SHELL32.@]
/******************************************************************************
* SHGetFolderPathW [SHELL32.@]
*
* See SHGetFolderPathW.
* Convert nFolder to path.
*
* RETURNS
* Success: S_OK
* Failure: standard HRESULT error codes.
*
* NOTES
* Most values can be overridden in either
* HKCU\Software\Microsoft\Windows\CurrentVersion\Explorer\User Shell Folders
* or in the same location in HKLM.
* The "Shell Folders" registry key was used in NT4 and earlier systems.
* Beginning with Windows 2000, the "User Shell Folders" key is used, so
* changes made to it are made to the former key too. This synchronization is
* done on-demand: not until someone requests the value of one of these paths
* (by calling one of the SHGet functions) is the value synchronized.
* Furthermore, the HKCU paths take precedence over the HKLM paths.
*/
HRESULT WINAPI SHGetFolderPathA(
HWND hwndOwner,
int nFolder,
HANDLE hToken,
DWORD dwFlags,
LPSTR pszPath)
HRESULT WINAPI SHGetFolderPathW(
HWND hwndOwner, /* [I] owner window */
int nFolder, /* [I] CSIDL identifying the folder */
HANDLE hToken, /* [I] access token */
DWORD dwFlags, /* [I] which path to return */
LPWSTR pszPath) /* [O] converted path */
{
WCHAR szTemp[MAX_PATH];
HRESULT hr;
TRACE("%p,%d,%p,%#x,%p\n", hwndOwner, nFolder, hToken, dwFlags, pszPath);
if (pszPath)
*pszPath = '\0';
hr = SHGetFolderPathW(hwndOwner, nFolder, hToken, dwFlags, szTemp);
if (SUCCEEDED(hr) && pszPath)
WideCharToMultiByte(CP_ACP, 0, szTemp, -1, pszPath, MAX_PATH, NULL,
NULL);
HRESULT hr = SHGetFolderPathAndSubDirW(hwndOwner, nFolder, hToken, dwFlags, NULL, pszPath);
if(HRESULT_FROM_WIN32(ERROR_PATH_NOT_FOUND) == hr)
hr = HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND);
return hr;
}
/* For each folder in folders, if its value has not been set in the registry,
* calls _SHGetUserProfilePath or _SHGetAllUsersProfilePath (depending on the
* folder's type) to get the unexpanded value first.
* Writes the unexpanded value to User Shell Folders, and queries it with
* SHGetFolderPathW to force the creation of the directory if it doesn't
* already exist. SHGetFolderPathW also returns the expanded value, which
* this then writes to Shell Folders.
*/
static HRESULT _SHRegisterFolders(HKEY hRootKey, HANDLE hToken,
LPCWSTR szUserShellFolderPath, LPCWSTR szShellFolderPath, const UINT folders[],
UINT foldersLen)
HRESULT WINAPI SHGetFolderPathAndSubDirA(
HWND hwndOwner, /* [I] owner window */
int nFolder, /* [I] CSIDL identifying the folder */
HANDLE hToken, /* [I] access token */
DWORD dwFlags, /* [I] which path to return */
LPCSTR pszSubPath, /* [I] sub directory of the specified folder */
LPSTR pszPath) /* [O] converted path */
{
const WCHAR *szValueName;
WCHAR buffer[40];
UINT i;
WCHAR path[MAX_PATH];
int length;
HRESULT hr = S_OK;
HKEY hUserKey = NULL, hKey = NULL;
DWORD dwType, dwPathLen;
LONG ret;
LPWSTR pszSubPathW = NULL;
LPWSTR pszPathW = NULL;
TRACE("%p,%p,%s,%p,%u\n", hRootKey, hToken,
debugstr_w(szUserShellFolderPath), folders, foldersLen);
TRACE("%p,%#x,%p,%#x,%s,%p\n", hwndOwner, nFolder, hToken, dwFlags, debugstr_a(pszSubPath), pszPath);
ret = RegCreateKeyW(hRootKey, szUserShellFolderPath, &hUserKey);
if (ret)
hr = HRESULT_FROM_WIN32(ret);
else
{
ret = RegCreateKeyW(hRootKey, szShellFolderPath, &hKey);
if (ret)
hr = HRESULT_FROM_WIN32(ret);
if(pszPath) {
pszPathW = heap_alloc(MAX_PATH * sizeof(WCHAR));
if(!pszPathW) {
hr = HRESULT_FROM_WIN32(ERROR_NOT_ENOUGH_MEMORY);
goto cleanup;
}
for (i = 0; SUCCEEDED(hr) && i < foldersLen; i++)
{
dwPathLen = MAX_PATH * sizeof(WCHAR);
}
TRACE("%08x,%08x,%s\n",nFolder, dwFlags, debugstr_w(pszSubPathW));
/* For CSIDL_Type_User we also use the GUID if no szValueName is provided */
szValueName = CSIDL_Data[folders[i]].szValueName;
if (!szValueName && CSIDL_Data[folders[i]].type == CSIDL_Type_User)
{
StringFromGUID2( CSIDL_Data[folders[i]].id, buffer, 39 );
szValueName = &buffer[0];
/* SHGetFolderPathAndSubDirW does not distinguish if pszSubPath isn't
* set (null), or an empty string.therefore call it without the parameter set
* if pszSubPath is an empty string
*/
if (pszSubPath && pszSubPath[0]) {
length = MultiByteToWideChar(CP_ACP, 0, pszSubPath, -1, NULL, 0);
pszSubPathW = heap_alloc(length * sizeof(WCHAR));
if(!pszSubPathW) {
hr = HRESULT_FROM_WIN32(ERROR_NOT_ENOUGH_MEMORY);
goto cleanup;
}
MultiByteToWideChar(CP_ACP, 0, pszSubPath, -1, pszSubPathW, length);
}
if (RegQueryValueExW(hUserKey, szValueName, NULL,
&dwType, (LPBYTE)path, &dwPathLen) || (dwType != REG_SZ &&
dwType != REG_EXPAND_SZ))
hr = SHGetFolderPathAndSubDirW(hwndOwner, nFolder, hToken, dwFlags, pszSubPathW, pszPathW);
if (SUCCEEDED(hr) && pszPath)
WideCharToMultiByte(CP_ACP, 0, pszPathW, -1, pszPath, MAX_PATH, NULL, NULL);
cleanup:
heap_free(pszPathW);
heap_free(pszSubPathW);
return hr;
}
/*************************************************************************
* SHGetFolderPathAndSubDirW [SHELL32.@]
*/
HRESULT WINAPI SHGetFolderPathAndSubDirW(
HWND hwndOwner, /* [I] owner window */
int nFolder, /* [I] CSIDL identifying the folder */
HANDLE hToken, /* [I] access token */
DWORD dwFlags, /* [I] which path to return */
LPCWSTR pszSubPath,/* [I] sub directory of the specified folder */
LPWSTR pszPath) /* [O] converted path */
{
HRESULT hr;
WCHAR szBuildPath[MAX_PATH], szTemp[MAX_PATH];
DWORD folder = nFolder & CSIDL_FOLDER_MASK;
CSIDL_Type type;
int ret;
TRACE("%p,%#x,%p,%#x,%s,%p\n", hwndOwner, nFolder, hToken, dwFlags, debugstr_w(pszSubPath), pszPath);
/* Windows always NULL-terminates the resulting path regardless of success
* or failure, so do so first
*/
if (pszPath)
*pszPath = '\0';
if (folder >= ARRAY_SIZE(CSIDL_Data))
return E_INVALIDARG;
if ((SHGFP_TYPE_CURRENT != dwFlags) && (SHGFP_TYPE_DEFAULT != dwFlags))
return E_INVALIDARG;
szTemp[0] = 0;
type = CSIDL_Data[folder].type;
switch (type)
{
case CSIDL_Type_Disallowed:
hr = E_INVALIDARG;
break;
case CSIDL_Type_NonExistent:
hr = S_FALSE;
break;
case CSIDL_Type_WindowsPath:
GetWindowsDirectoryW(szTemp, MAX_PATH);
if (CSIDL_Data[folder].szDefaultPath &&
!IS_INTRESOURCE(CSIDL_Data[folder].szDefaultPath) &&
*CSIDL_Data[folder].szDefaultPath)
{
PathAddBackslashW(szTemp);
strcatW(szTemp, CSIDL_Data[folder].szDefaultPath);
}
hr = S_OK;
break;
case CSIDL_Type_SystemPath:
GetSystemDirectoryW(szTemp, MAX_PATH);
if (CSIDL_Data[folder].szDefaultPath &&
!IS_INTRESOURCE(CSIDL_Data[folder].szDefaultPath) &&
*CSIDL_Data[folder].szDefaultPath)
{
PathAddBackslashW(szTemp);
strcatW(szTemp, CSIDL_Data[folder].szDefaultPath);
}
hr = S_OK;
break;
case CSIDL_Type_SystemX86Path:
if (!GetSystemWow64DirectoryW(szTemp, MAX_PATH)) GetSystemDirectoryW(szTemp, MAX_PATH);
if (CSIDL_Data[folder].szDefaultPath &&
!IS_INTRESOURCE(CSIDL_Data[folder].szDefaultPath) &&
*CSIDL_Data[folder].szDefaultPath)
{
PathAddBackslashW(szTemp);
strcatW(szTemp, CSIDL_Data[folder].szDefaultPath);
}
hr = S_OK;
break;
case CSIDL_Type_CurrVer:
hr = _SHGetCurrentVersionPath(dwFlags, folder, szTemp);
break;
case CSIDL_Type_User:
hr = _SHGetUserProfilePath(hToken, dwFlags, folder, szTemp);
break;
case CSIDL_Type_AllUsers:
case CSIDL_Type_ProgramData:
hr = _SHGetAllUsersProfilePath(dwFlags, folder, szTemp);
break;
default:
FIXME("bogus type %d, please fix\n", type);
hr = E_INVALIDARG;
break;
}
/* Expand environment strings if necessary */
if (*szTemp == '%')
hr = _SHExpandEnvironmentStrings(szTemp, szBuildPath);
else
strcpyW(szBuildPath, szTemp);
if (FAILED(hr)) goto end;
if(pszSubPath) {
/* make sure the new path does not exceed the buffer length
* and remember to backslash and terminate it */
if(MAX_PATH < (lstrlenW(szBuildPath) + lstrlenW(pszSubPath) + 2)) {
hr = HRESULT_FROM_WIN32(ERROR_FILENAME_EXCED_RANGE);
goto end;
}
PathAppendW(szBuildPath, pszSubPath);
PathRemoveBackslashW(szBuildPath);
}
/* Copy the path if it's available before we might return */
if (SUCCEEDED(hr) && pszPath)
strcpyW(pszPath, szBuildPath);
/* if we don't care about existing directories we are ready */
if(nFolder & CSIDL_FLAG_DONT_VERIFY) goto end;
if (PathFileExistsW(szBuildPath)) goto end;
/* not existing but we are not allowed to create it. The return value
* is verified against shell32 version 6.0.
*/
if (!(nFolder & CSIDL_FLAG_CREATE))
{
hr = HRESULT_FROM_WIN32(ERROR_PATH_NOT_FOUND);
goto end;
}
/* create directory/directories */
ret = SHCreateDirectoryExW(hwndOwner, szBuildPath, NULL);
if (ret && ret != ERROR_ALREADY_EXISTS)
{
ERR("Failed to create directory %s.\n", debugstr_w(szBuildPath));
hr = E_FAIL;
goto end;
}
TRACE("Created missing system directory %s\n", debugstr_w(szBuildPath));
end:
TRACE("returning 0x%08x (final path is %s)\n", hr, debugstr_w(szBuildPath));
return hr;
}
/*************************************************************************
* SHGetFolderPathA [SHELL32.@]
*
* See SHGetFolderPathW.
*/
HRESULT WINAPI SHGetFolderPathA(
HWND hwndOwner,
int nFolder,
HANDLE hToken,
DWORD dwFlags,
LPSTR pszPath)
{
WCHAR szTemp[MAX_PATH];
HRESULT hr;
TRACE("%p,%d,%p,%#x,%p\n", hwndOwner, nFolder, hToken, dwFlags, pszPath);
if (pszPath)
*pszPath = '\0';
hr = SHGetFolderPathW(hwndOwner, nFolder, hToken, dwFlags, szTemp);
if (SUCCEEDED(hr) && pszPath)
WideCharToMultiByte(CP_ACP, 0, szTemp, -1, pszPath, MAX_PATH, NULL,
NULL);
return hr;
}
/* For each folder in folders, if its value has not been set in the registry,
* calls _SHGetUserProfilePath or _SHGetAllUsersProfilePath (depending on the
* folder's type) to get the unexpanded value first.
* Writes the unexpanded value to User Shell Folders, and queries it with
* SHGetFolderPathW to force the creation of the directory if it doesn't
* already exist. SHGetFolderPathW also returns the expanded value, which
* this then writes to Shell Folders.
*/
static HRESULT _SHRegisterFolders(HKEY hRootKey, HANDLE hToken,
LPCWSTR szUserShellFolderPath, LPCWSTR szShellFolderPath, const UINT folders[],
UINT foldersLen)
{
const WCHAR *szValueName;
WCHAR buffer[40];
UINT i;
WCHAR path[MAX_PATH];
HRESULT hr = S_OK;
HKEY hUserKey = NULL, hKey = NULL;
DWORD dwType, dwPathLen;
LONG ret;
TRACE("%p,%p,%s,%p,%u\n", hRootKey, hToken,
debugstr_w(szUserShellFolderPath), folders, foldersLen);
ret = RegCreateKeyW(hRootKey, szUserShellFolderPath, &hUserKey);
if (ret)
hr = HRESULT_FROM_WIN32(ret);
else
{
ret = RegCreateKeyW(hRootKey, szShellFolderPath, &hKey);
if (ret)
hr = HRESULT_FROM_WIN32(ret);
}
for (i = 0; SUCCEEDED(hr) && i < foldersLen; i++)
{
dwPathLen = MAX_PATH * sizeof(WCHAR);
/* For CSIDL_Type_User we also use the GUID if no szValueName is provided */
szValueName = CSIDL_Data[folders[i]].szValueName;
if (!szValueName && CSIDL_Data[folders[i]].type == CSIDL_Type_User)
{
StringFromGUID2( CSIDL_Data[folders[i]].id, buffer, 39 );
szValueName = &buffer[0];
}
if (RegQueryValueExW(hUserKey, szValueName, NULL,
&dwType, (LPBYTE)path, &dwPathLen) || (dwType != REG_SZ &&
dwType != REG_EXPAND_SZ))
{
*path = '\0';
if (CSIDL_Data[folders[i]].type == CSIDL_Type_User)
......@@ -4382,271 +4647,6 @@ static HRESULT _SHRegisterCommonShellFolders(void)
}
/******************************************************************************
* _SHAppendToUnixPath [Internal]
*
* Helper function for _SHCreateSymbolicLinks. Appends pwszSubPath (or the
* corresponding resource, if IS_INTRESOURCE) to the unix base path 'szBasePath'
* and replaces backslashes with slashes.
*
* PARAMS
* szBasePath [IO] The unix base path, which will be appended to (CP_UNXICP).
* pwszSubPath [I] Sub-path or resource id (use MAKEINTRESOURCEW).
*
* RETURNS
* Success: TRUE,
* Failure: FALSE
*/
static inline BOOL _SHAppendToUnixPath(char *szBasePath, LPCWSTR pwszSubPath) {
WCHAR wszSubPath[MAX_PATH];
int cLen = strlen(szBasePath);
char *pBackslash;
if (IS_INTRESOURCE(pwszSubPath)) {
if (!LoadStringW(shell32_hInstance, LOWORD(pwszSubPath), wszSubPath, MAX_PATH)) {
/* Fall back to hard coded defaults. */
switch (LOWORD(pwszSubPath)) {
case IDS_PERSONAL:
lstrcpyW(wszSubPath, DocumentsW);
break;
case IDS_MYMUSIC:
lstrcpyW(wszSubPath, My_MusicW);
break;
case IDS_MYPICTURES:
lstrcpyW(wszSubPath, My_PicturesW);
break;
case IDS_MYVIDEOS:
lstrcpyW(wszSubPath, My_VideosW);
break;
case IDS_DOWNLOADS:
lstrcpyW(wszSubPath, DownloadsW);
break;
case IDS_TEMPLATES:
lstrcpyW(wszSubPath, TemplatesW);
break;
default:
ERR("LoadString(%d) failed!\n", LOWORD(pwszSubPath));
return FALSE;
}
}
} else {
lstrcpyW(wszSubPath, pwszSubPath);
}
if (szBasePath[cLen-1] != '/') szBasePath[cLen++] = '/';
if (!WideCharToMultiByte(CP_UNIXCP, 0, wszSubPath, -1, szBasePath + cLen,
FILENAME_MAX - cLen, NULL, NULL))
{
return FALSE;
}
pBackslash = szBasePath + cLen;
while ((pBackslash = strchr(pBackslash, '\\'))) *pBackslash = '/';
return TRUE;
}
/******************************************************************************
* _SHCreateSymbolicLinks [Internal]
*
* Sets up symbol links for various shell folders to point into the user's home
* directory. We do an educated guess about what the user would probably want:
* - If there is a 'My Documents' directory in $HOME, the user probably wants
* wine's 'My Documents' to point there. Furthermore, we infer that the user
* is a Windows lover and has no problem with wine creating subfolders for
* 'My Pictures', 'My Music', 'My Videos' etc. under '$HOME/My Documents', if
* those do not already exist. We put appropriate symbolic links in place for
* those, too.
* - If there is no 'My Documents' directory in $HOME, we let 'My Documents'
* point directly to $HOME. We assume the user to be a unix hacker who does not
* want wine to create anything anywhere besides the .wine directory. So, if
* there already is a 'My Music' directory in $HOME, we symlink the 'My Music'
* shell folder to it. But if not, then we check XDG_MUSIC_DIR - "well known"
* directory, and try to link to that. If that fails, then we symlink to
* $HOME directly. The same holds for 'My Pictures', 'My Videos' etc.
* - The Desktop shell folder is symlinked to XDG_DESKTOP_DIR. If that does not
* exist, then we try '$HOME/Desktop'. If that does not exist, then we leave
* it alone.
* ('My Music',... above in fact means LoadString(IDS_MYMUSIC))
*/
static void _SHCreateSymbolicLinks(void)
{
static const UINT aidsMyStuff[] = {
IDS_MYPICTURES, IDS_MYVIDEOS, IDS_MYMUSIC, IDS_DOWNLOADS, IDS_TEMPLATES
};
static const WCHAR * const MyOSXStuffW[] = {
PicturesW, MoviesW, MusicW, DownloadsW, TemplatesW
};
static const int acsidlMyStuff[] = {
CSIDL_MYPICTURES, CSIDL_MYVIDEO, CSIDL_MYMUSIC, CSIDL_DOWNLOADS, CSIDL_TEMPLATES
};
static const char * const xdg_dirs[] = {
"PICTURES", "VIDEOS", "MUSIC", "DOWNLOAD", "TEMPLATES", "DOCUMENTS", "DESKTOP"
};
static const unsigned int num = ARRAY_SIZE(xdg_dirs);
WCHAR wszTempPath[MAX_PATH];
char szPersonalTarget[FILENAME_MAX], *pszPersonal;
char szMyStuffTarget[FILENAME_MAX], *pszMyStuff;
char szDesktopTarget[FILENAME_MAX], *pszDesktop;
struct stat statFolder;
const char *pszHome;
HRESULT hr;
char ** xdg_results;
char * xdg_desktop_dir;
UINT i;
/* Create all necessary profile sub-dirs up to 'My Documents' and get the unix path. */
hr = SHGetFolderPathW(NULL, CSIDL_PERSONAL|CSIDL_FLAG_CREATE, NULL,
SHGFP_TYPE_DEFAULT, wszTempPath);
if (FAILED(hr)) return;
pszPersonal = wine_get_unix_file_name(wszTempPath);
if (!pszPersonal) return;
hr = XDG_UserDirLookup(xdg_dirs, num, &xdg_results);
if (FAILED(hr)) xdg_results = NULL;
pszHome = getenv("HOME");
if (pszHome && !stat(pszHome, &statFolder) && S_ISDIR(statFolder.st_mode))
{
while (1)
{
/* Check if there's already a Wine-specific 'My Documents' folder */
strcpy(szPersonalTarget, pszHome);
if (_SHAppendToUnixPath(szPersonalTarget, MAKEINTRESOURCEW(IDS_PERSONAL)) &&
!stat(szPersonalTarget, &statFolder) && S_ISDIR(statFolder.st_mode))
{
/* '$HOME/My Documents' exists. Create subfolders for
* 'My Pictures', 'My Videos', 'My Music' etc. or fail silently
* if they already exist.
*/
for (i = 0; i < ARRAY_SIZE(aidsMyStuff); i++)
{
strcpy(szMyStuffTarget, szPersonalTarget);
if (_SHAppendToUnixPath(szMyStuffTarget, MAKEINTRESOURCEW(aidsMyStuff[i])))
mkdir(szMyStuffTarget, 0777);
}
break;
}
/* Try to point to the XDG Documents folder */
if (xdg_results && xdg_results[num-2] &&
!stat(xdg_results[num-2], &statFolder) &&
S_ISDIR(statFolder.st_mode))
{
strcpy(szPersonalTarget, xdg_results[num-2]);
break;
}
/* Or the hardcoded / OS X Documents folder */
strcpy(szPersonalTarget, pszHome);
if (_SHAppendToUnixPath(szPersonalTarget, DocumentsW) &&
!stat(szPersonalTarget, &statFolder) &&
S_ISDIR(statFolder.st_mode))
break;
/* As a last resort point to $HOME. */
strcpy(szPersonalTarget, pszHome);
break;
}
/* Replace 'My Documents' directory with a symlink or fail silently if not empty. */
remove(pszPersonal);
symlink(szPersonalTarget, pszPersonal);
}
else
{
/* '$HOME' doesn't exist. Create subdirs for 'My Pictures', 'My Videos',
* 'My Music' etc. in '%USERPROFILE%\My Documents' or fail silently if
* they already exist. */
pszHome = NULL;
strcpy(szPersonalTarget, pszPersonal);
for (i = 0; i < ARRAY_SIZE(aidsMyStuff); i++) {
strcpy(szMyStuffTarget, szPersonalTarget);
if (_SHAppendToUnixPath(szMyStuffTarget, MAKEINTRESOURCEW(aidsMyStuff[i])))
mkdir(szMyStuffTarget, 0777);
}
}
/* Create symbolic links for 'My Pictures', 'My Videos', 'My Music' etc. */
for (i=0; i < ARRAY_SIZE(aidsMyStuff); i++)
{
/* Create the current 'My Whatever' folder and get its unix path. */
hr = SHGetFolderPathW(NULL, acsidlMyStuff[i]|CSIDL_FLAG_CREATE, NULL,
SHGFP_TYPE_DEFAULT, wszTempPath);
if (FAILED(hr)) continue;
pszMyStuff = wine_get_unix_file_name(wszTempPath);
if (!pszMyStuff) continue;
while (1)
{
/* Check for the Wine-specific '$HOME/My Documents' subfolder */
strcpy(szMyStuffTarget, szPersonalTarget);
if (_SHAppendToUnixPath(szMyStuffTarget, MAKEINTRESOURCEW(aidsMyStuff[i])) &&
!stat(szMyStuffTarget, &statFolder) && S_ISDIR(statFolder.st_mode))
break;
/* Try the XDG_XXX_DIR folder */
if (xdg_results && xdg_results[i])
{
strcpy(szMyStuffTarget, xdg_results[i]);
break;
}
/* Or the OS X folder (these are never localized) */
if (pszHome)
{
strcpy(szMyStuffTarget, pszHome);
if (_SHAppendToUnixPath(szMyStuffTarget, MyOSXStuffW[i]) &&
!stat(szMyStuffTarget, &statFolder) &&
S_ISDIR(statFolder.st_mode))
break;
}
/* As a last resort point to the same location as 'My Documents' */
strcpy(szMyStuffTarget, szPersonalTarget);
break;
}
remove(pszMyStuff);
symlink(szMyStuffTarget, pszMyStuff);
heap_free(pszMyStuff);
}
/* Last but not least, the Desktop folder */
if (pszHome)
strcpy(szDesktopTarget, pszHome);
else
strcpy(szDesktopTarget, pszPersonal);
heap_free(pszPersonal);
xdg_desktop_dir = xdg_results ? xdg_results[num - 1] : NULL;
if (xdg_desktop_dir ||
(_SHAppendToUnixPath(szDesktopTarget, DesktopW) &&
!stat(szDesktopTarget, &statFolder) && S_ISDIR(statFolder.st_mode)))
{
hr = SHGetFolderPathW(NULL, CSIDL_DESKTOPDIRECTORY|CSIDL_FLAG_CREATE, NULL,
SHGFP_TYPE_DEFAULT, wszTempPath);
if (SUCCEEDED(hr) && (pszDesktop = wine_get_unix_file_name(wszTempPath)))
{
remove(pszDesktop);
if (xdg_desktop_dir)
symlink(xdg_desktop_dir, pszDesktop);
else
symlink(szDesktopTarget, pszDesktop);
heap_free(pszDesktop);
}
}
/* Free resources allocated by XDG_UserDirLookup() */
if (xdg_results)
{
for (i = 0; i < num; i++)
heap_free(xdg_results[i]);
heap_free(xdg_results);
}
}
/******************************************************************************
* create_extra_folders [Internal]
*
* Create some extra folders that don't have a standard CSIDL definition.
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment