Fixes: https://bugs.winehq.org/show_bug.cgi?id=41668 (1) https://bugs.winehq.org/show_bug.cgi?id=28216 (2)
(1) Introduces a "WINESYMLINK" env variable that allows a user to control what shell folder links are created to the HOME folder, when a WINEPREFIX is initially created. The Wineprefix Shell Folders, that Wine symlinks to the user's HOME directory, are only (re-)created on each Wine boot if they: do not pre-exist or are broken symbolic links.
(2) The XDG Directory specification automatically falls back to the user HOME directory for any XDG_*_DIR that does not exist (xdg-user-dirs-update). The implemented solution is to block Wine from symlinking directly to HOME directory.
Test on Gentoo GNU/Linux.
Signed-off-by: Rob Walker bob.mt.wya@gmail.com --- dlls/shell32/shellpath.c | 554 ++++++++++++++++++++++++--------------- 1 file changed, 343 insertions(+), 211 deletions(-)
diff --git a/dlls/shell32/shellpath.c b/dlls/shell32/shellpath.c index a551e93aa8..b8c74273c6 100644 --- a/dlls/shell32/shellpath.c +++ b/dlls/shell32/shellpath.c @@ -4326,252 +4326,259 @@ static HRESULT _SHRegisterCommonShellFolders(void) }
/****************************************************************************** - * _SHAppendToUnixPath [Internal] + * append_to_unix_path [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. + * Helper function for create_home_subdir_symbolic_link. + * Appends 'subpath', or the corresponding resource (if it is a + * resource identifier), to the Unix base path 'base_dir'. + * Replace backslashes with forward slashes. * * PARAMS - * szBasePath [IO] The unix base path, which will be appended to (CP_UNXICP). - * pwszSubPath [I] Sub-path or resource id (use MAKEINTRESOURCEW). + * base_dir [IO] The Unix base path, which will be appended to (CP_UNXICP). + * subpath [I] Sub-path or resource id (use MAKEINTRESOURCEW, pre-call). * * RETURNS - * Success: TRUE, + * 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; - default: - ERR("LoadString(%d) failed!\n", LOWORD(pwszSubPath)); - return FALSE; - } +static inline BOOL append_to_unix_path(char *base_dir, LPCWSTR subpath) +{ + WCHAR ws_subpath[MAX_PATH]; + int cLen = strlen(base_dir); + char *back_slash; + + if (!IS_INTRESOURCE(subpath)) + { + lstrcpyW(ws_subpath, subpath); + } + else if (!LoadStringW(shell32_hInstance, LOWORD(subpath), ws_subpath, MAX_PATH)) + { + /* Fall back to hard coded defaults. */ + switch (LOWORD(subpath)) { + case IDS_PERSONAL: + lstrcpyW(ws_subpath, DocumentsW); + break; + case IDS_MYMUSIC: + lstrcpyW(ws_subpath, My_MusicW); + break; + case IDS_MYPICTURES: + lstrcpyW(ws_subpath, My_PicturesW); + break; + case IDS_MYVIDEOS: + lstrcpyW(ws_subpath, My_VideosW); + break; + case IDS_DESKTOP: + lstrcpyW(ws_subpath, DesktopW); + break; + default: + ERR("LoadString(%d) failed!\n", LOWORD(subpath)); + return FALSE; } - } else { - lstrcpyW(wszSubPath, pwszSubPath); } - - if (szBasePath[cLen-1] != '/') szBasePath[cLen++] = '/'; - - if (!WideCharToMultiByte(CP_UNIXCP, 0, wszSubPath, -1, szBasePath + cLen, + + if (!cLen || (base_dir[cLen-1] != '/')) base_dir[cLen++] = '/'; + + if (!WideCharToMultiByte(CP_UNIXCP, 0, ws_subpath, -1, base_dir + cLen, FILENAME_MAX - cLen, NULL, NULL)) { return FALSE; } - - pBackslash = szBasePath + cLen; - while ((pBackslash = strchr(pBackslash, '\'))) *pBackslash = '/'; - + + back_slash = base_dir + cLen; + while ((back_slash = strchr(back_slash, '\'))) *back_slash = '/'; + return TRUE; }
/****************************************************************************** - * _SHCreateSymbolicLinks [Internal] + * compare_pathft_to_bootft [Internal] * - * Sets up symbol links for various shell folders to point into the users 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 imply that the user - * is a Windows lover and has no problem with wine creating 'My Pictures', - * 'My Music' and 'My Videos' subfolders under '$HOME/My Documents', if those - * do not already exits. 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 fo 'My Pictures' and 'My Videos'. - * - 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)) + * Function to compare the FILETIME of ws_path (a Windows path) to the (estimated) + * Wine System boot time. + * + * PARAMS + * ws_path [I] The Windows path, for which provides the FS write time. + * time_difference [O] -1 Windows path last written before time of Wine boot. + * 0 Windows path last written at time of Wine boot. + * +1 Windows path last written after time of Wine boot. + * RETURNS + * Success: S_OK + * Failure: E_FAIL + * + * NOTES + * Pre-allocate storage for time_difference pointer variable externally. */ -static void _SHCreateSymbolicLinks(void) -{ - UINT aidsMyStuff[] = { IDS_MYPICTURES, IDS_MYVIDEOS, IDS_MYMUSIC }, i; - const WCHAR* MyOSXStuffW[] = { PicturesW, MoviesW, MusicW }; - int acsidlMyStuff[] = { CSIDL_MYPICTURES, CSIDL_MYVIDEO, CSIDL_MYMUSIC }; - static const char * const xdg_dirs[] = { "PICTURES", "VIDEOS", "MUSIC", "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; - - /* 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; +static HRESULT compare_pathft_to_bootft(const WCHAR * ws_path, int * time_difference) +{ + static ULONG64 time_system_start = 0; + SYSTEMTIME system_time_st; + FILETIME dummy_ft, system_time_ft, last_write_ft; + ULONG64 tick_count, time_last_write; + HANDLE fhandle; + DWORD ft_errorc = 0;
- hr = XDG_UserDirLookup(xdg_dirs, num, &xdg_results); - if (FAILED(hr)) xdg_results = NULL; + if (!time_difference) return E_FAIL;
- pszHome = getenv("HOME"); - if (pszHome && !stat(pszHome, &statFolder) && S_ISDIR(statFolder.st_mode)) + if (!time_system_start) { - while (1) + tick_count = (ULONG64) GetTickCount64(); + GetSystemTime(&system_time_st); + if (!SystemTimeToFileTime( &system_time_st, &system_time_ft)) { - /* 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 'My Pictures', - * 'My Videos' and 'My Music' subfolders 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; + ERR("SystemTimeToFileTime call failed (%d)\n", GetLastError()); + return E_FAIL; } - - /* Replace 'My Documents' directory with a symlink or fail silently if not empty. */ - remove(pszPersonal); - symlink(szPersonalTarget, pszPersonal); + time_system_start = (((ULONG64)system_time_ft.dwHighDateTime) << 32) + + system_time_ft.dwLowDateTime + - (tick_count * 10000); } - else + fhandle = CreateFileW( ws_path, 0, FILE_SHARE_READ, NULL, + OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL); + if (fhandle == INVALID_HANDLE_VALUE) { - /* '$HOME' doesn't exist. Create 'My Pictures', 'My Videos' and 'My Music' subdirs - * 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); - } + ERR("Open file handle failed for path: %s (%d)\n", + debugstr_w(ws_path), GetLastError()); + return E_FAIL; } - - /* Create symbolic links for 'My Pictures', 'My Videos' and 'My Music'. */ - for (i=0; i < ARRAY_SIZE(aidsMyStuff); i++) + if (!GetFileTime(fhandle, &dummy_ft, &dummy_ft, &last_write_ft)) + ft_errorc = GetLastError(); + if (!CloseHandle(fhandle)) { - /* 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; + ERR("Close file handle failed: %p (%d)\n", + fhandle, GetLastError()); + return E_FAIL; + } + if (ft_errorc) + { + ERR("Get file time failed: %p (%d)\n", + fhandle, ft_errorc); + return E_FAIL; + } + time_last_write = (((ULONG64)last_write_ft.dwHighDateTime) << 32) + + last_write_ft.dwLowDateTime; + *time_difference = ((time_last_write > time_system_start) ? 1 : + ((time_last_write < time_system_start) ? -1 : + 0));
- /* Try the XDG_XXX_DIR folder */ - if (xdg_results && xdg_results[i]) - { - strcpy(szMyStuffTarget, xdg_results[i]); - break; - } + return S_OK; +}
- /* 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; - } +/****************************************************************************** + * create_homedir_symbolic_link [Internal] + * + * Creates a symbolic link from the current Wineprefix to an appropriate + * HOME subdirectory (if one is found). + * + * Creates 'XXXX' directory in Wineprefix. + * Then create a 'My XXXX' symbolic link in Wineprefix: + * 1) If '$HOME/XXXX' (IDS directory) exists then target this. + * 2) If '$HOME/XXXX' (XDG_XXXX_DIR) exists then target this. + * 3) If '$HOME/XXXX' (MacOS XXXX media directory) exists then target this. + * + * PARAMS + * env_enabled [I] + * TRUE The user has enabled this Shell Folder to be symlinked (default). + * FALSE The user has chosen to disable symlinking this Shell Folder. + * The Shell Folder will be created, in the current Wineprefix (if not pre-existing). + * ids_dir [I] Windows Resource Identifier code for current Shell Folder. + * csidl_dir [I] Constant Special Item ID List identifier for current Shell Folder. + * xdg_dir [I] Full path of external Unix XDG directory corresponding to current Shell Folder. + * ws_osx_dir [I] Fallback directory name to use, corresponding to current Shell Folder (OSX specific). + * + */ +void create_homedir_symbolic_link(BOOL env_enabled, + UINT ids_dir, + int csidl_dir, + const char * xdg_dir, + const WCHAR * ws_osx_dir) +{ + static const char * env_homedir = NULL; + WCHAR ws_temp_path[MAX_PATH]; + char home_target[FILENAME_MAX], * prefix_dir; + struct stat stat_folder, stat_home_folder; + HRESULT hr; + BOOL target_ok; + int time_difference;
- /* As a last resort point to the same location as 'My Documents' */ - strcpy(szMyStuffTarget, szPersonalTarget); - break; + hr = SHGetFolderPathW(NULL, csidl_dir, NULL, + SHGFP_TYPE_DEFAULT, ws_temp_path); + if (SUCCEEDED(hr)) + { + if (ids_dir != IDS_DESKTOPDIRECTORY) return; + + if ((compare_pathft_to_bootft(ws_temp_path, &time_difference) == S_OK) + && (time_difference < 0)) + { + TRACE("%s directory created before wineboot\n", + debugstr_w(ws_temp_path)); + return; } - remove(pszMyStuff); - symlink(szMyStuffTarget, pszMyStuff); - heap_free(pszMyStuff); } - - /* Last but not least, the Desktop folder */ - if (pszHome) - strcpy(szDesktopTarget, pszHome); + else if (hr == HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND)) + { + hr = SHGetFolderPathW(NULL, csidl_dir|CSIDL_FLAG_CREATE, NULL, + SHGFP_TYPE_DEFAULT, ws_temp_path); + if (FAILED(hr)) return; + } else - strcpy(szDesktopTarget, pszPersonal); - heap_free(pszPersonal); + { + ERR("Failed to get Wineprefix path corresponding to %d CSIDL\n", + csidl_dir); + return; + } + if (!env_enabled) return; + prefix_dir = wine_get_unix_file_name(ws_temp_path); + if (!prefix_dir) + { + ERR("Failed to get Unix Wineprefix directory for: %s\n", + debugstr_w(ws_temp_path)); + return; + } + if (!env_homedir) env_homedir = getenv("HOME"); + if (!(env_homedir + && (stat(env_homedir, &stat_home_folder) != 1) + && S_ISDIR(stat_home_folder.st_mode))) + { + if (prefix_dir) heap_free(prefix_dir); + return; + }
- 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))) + /* Check for: + * '$HOME/XXXX' (IDS directory) + * or '$HOME/XXXX' (XDG directory) + * or '$HOME/XXXX' (MacOS directory) + */ + target_ok = FALSE; + strcpy(home_target, env_homedir); + if (append_to_unix_path(home_target, MAKEINTRESOURCEW(ids_dir)) + && (stat(home_target, &stat_folder) != -1) + && S_ISDIR(stat_folder.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); - } + target_ok = TRUE; + } + else if (xdg_dir && stat(xdg_dir, &stat_folder)) + { + strcpy(home_target, xdg_dir); + /* Only link to the XDG directory, if it does not point directly + * to the user's HOME directory (XDG specification fallback path). */ + target_ok = (stat_folder.st_ino != stat_home_folder.st_ino); + } + else + { + strcpy(home_target, env_homedir); + target_ok = append_to_unix_path(home_target, ws_osx_dir) + && (stat(home_target, &stat_folder) != -1) + && S_ISDIR(stat_folder.st_mode); }
- /* Free resources allocated by XDG_UserDirLookup() */ - if (xdg_results) + if (target_ok) { - for (i = 0; i < num; i++) - heap_free(xdg_results[i]); - heap_free(xdg_results); + remove(prefix_dir); + symlink(home_target, prefix_dir); } + + + if (prefix_dir) heap_free(prefix_dir); }
/****************************************************************************** @@ -6126,15 +6133,139 @@ static void register_system_knownfolders(void) } }
+/****************************************************************************** + * parse_symlinks_env_variable [Internal] + * + * PARAMS + * xdg_dir [I] Unix XDG directory name (without "XDG_" prefix and "_DIR" suffix). + * + * RETURNS + * TRUE "WINESYMLINK" contains the specified XDG directory string. + * FALSE "WINESYMLINK" does not contain the specified XDG directory string. + * Any error condition. + * + * Note: "WINESYMLINK", defaults to "ALL", when unset. This enables all symlinks. + * "WINESYMLINK" can also be manually set to "ALL", enabling all symlinks. + * + */ +static BOOL parse_symlinks_env_variable(const char * xdg_dir) +{ + static const char * matchall_env_var = "ALL"; + static char env_var[MAX_PATH+1]; + static char * env_winesymlink = NULL; + char * sstr, * sstr_stop, * tstr, * word_start = NULL; + char seperator_ch = '\0'; + BOOL end_string, in_word = FALSE, matched = FALSE; + size_t slength = 0, matchall_length, xdg_dir_slength; + + if (!xdg_dir) return matched; + + if (!env_winesymlink) + { + env_winesymlink = getenv("WINESYMLINK"); + if (!env_winesymlink) env_winesymlink = (char*) matchall_env_var; + tstr = env_var; + sstr_stop = env_winesymlink + MAX_PATH; + for (sstr = env_winesymlink; *sstr && (sstr != sstr_stop); ++sstr) + { + if (!seperator_ch && (ispunct(*sstr) || isspace(*sstr))) + seperator_ch = *sstr; + if (!isalpha(*sstr)) continue; + + if (seperator_ch && in_word) + { + *tstr++ = seperator_ch; + ++slength; + } + in_word = TRUE; + seperator_ch = '\0'; + *tstr++ = toupper(*sstr); + ++slength; + } + env_var[slength] = '\0'; + } + + TRACE("processed env variable: %s\n", debugstr_a(&env_var[0])); + matchall_length = strlen(matchall_env_var); + xdg_dir_slength = strlen(xdg_dir); + end_string = !(env_var[0]); + for (sstr = &env_var[0]; !end_string; ++sstr) + { + end_string = !(*(sstr+1)); + in_word = isalpha(*sstr); + if (in_word && !word_start) word_start = sstr; + in_word = in_word && !end_string; + if (in_word || !word_start) continue; + + slength = ((size_t) (sstr-word_start))+(end_string ? 1 : 0); + matched = ( (slength == xdg_dir_slength) && (strncmp(word_start, xdg_dir, slength) == 0) ) + || ( (slength == matchall_length) && (strncmp(word_start, "ALL", slength) == 0) ); + if (matched) break; + + word_start = NULL; + } + TRACE("%s symlinking for %s\n", matched ? "Enabled" : "Disabled", debugstr_a(xdg_dir)); + + return matched; +} + +/****************************************************************************** + * create_homedir_symbolic_links [Internal] + * + * Parse WINESYMLINK env variable, for each XDG directory argument. To test if symlinking is + * enabled for that XDG directory / Wine Profile Folder. + * Then calls the function create_homedir_symbolic_link to potentially symlink from a Shell Folder, + * in the current Wineprefix, to a subdirectory of the current user's HOME directory. + * + * PARAMS + * xdg_dirnames [I] Pointer to an array of Unix XDG directory names + * (without "XDG_" prefix and "_DIR" suffix). + * xdg_dir_count [I] Item count of array (above). + * + */ +static void create_homedir_symbolic_links(const char * const xdg_dirnames[], const UINT xdg_dir_count) +{ + char ** xdg_dirs_array; + char * xdg_dir; + HRESULT hr; + UINT i; + BOOL env_enabled; + + if (!xdg_dirnames) return; + + hr = XDG_UserDirLookup(xdg_dirnames, xdg_dir_count, &xdg_dirs_array); + if (FAILED(hr)) xdg_dirs_array = NULL; + + for (i = 0; i < xdg_dir_count; ++i) + { + env_enabled = parse_symlinks_env_variable(xdg_dirnames[i]); + xdg_dir = xdg_dirs_array ? xdg_dirs_array[i] : NULL; + if (!strcmp(xdg_dirnames[i],"DOCUMENTS")) + create_homedir_symbolic_link(env_enabled, IDS_PERSONAL, CSIDL_PERSONAL, xdg_dir, DocumentsW); + else if (!strcmp(xdg_dirnames[i],"PICTURES")) + create_homedir_symbolic_link(env_enabled, IDS_MYPICTURES, CSIDL_MYPICTURES, xdg_dir, PicturesW); + else if (!strcmp(xdg_dirnames[i],"VIDEOS")) + create_homedir_symbolic_link(env_enabled, IDS_MYVIDEOS, CSIDL_MYVIDEO, xdg_dir, MoviesW); + else if (!strcmp(xdg_dirnames[i],"MUSIC")) + create_homedir_symbolic_link(env_enabled, IDS_MYMUSIC, CSIDL_MYMUSIC, xdg_dir, MusicW); + else if (!strcmp(xdg_dirnames[i],"DESKTOP")) + create_homedir_symbolic_link(env_enabled, IDS_DESKTOPDIRECTORY, CSIDL_DESKTOPDIRECTORY, xdg_dir, DesktopW); + else + ERR("XDG directory name specifier invalid: %s\n", debugstr_a(xdg_dirnames[i])); + if (xdg_dir) heap_free(xdg_dirs_array[i]); + } + if (xdg_dirs_array) heap_free(xdg_dirs_array); +} + HRESULT SHELL_RegisterShellFolders(void) { + static const char * const xdg_dirnames[] = { "DOCUMENTS", "PICTURES", "VIDEOS", "MUSIC", "DESKTOP" }; + const UINT xdg_dir_count = 5; HRESULT hr;
- /* Set up '$HOME' targeted symlinks for 'My Documents', 'My Pictures', - * 'My Videos', 'My Music' and 'Desktop' in advance, so that the - * _SHRegister*ShellFolders() functions will find everything nice and clean - * and thus will not attempt to create them in the profile directory. */ - _SHCreateSymbolicLinks(); + /* Early setup of symlinks from specific User Shell Folders, in + * current Wineprefix, to subdirecties of the user's HOME directory. */ + create_homedir_symbolic_links(xdg_dirnames, xdg_dir_count);
hr = _SHRegisterUserShellFolders(TRUE); if (SUCCEEDED(hr)) @@ -6147,5 +6278,6 @@ HRESULT SHELL_RegisterShellFolders(void) hr = set_folder_attributes(); if (SUCCEEDED(hr)) register_system_knownfolders(); + return hr; }