From a83e40f6d1ca16b8a7edceebe64580eb223a51d0 Mon Sep 17 00:00:00 2001 From: Whindmar Saksit Date: Sun, 24 Mar 2024 21:37:59 +0100 Subject: [PATCH] [SHELL32] Fix FS folder assoc array class order (#6047) Fixes the reg class key order for FS items. The existing code was close, but for some reason used `//` as the path separator for SystemFileAssociations! - Fixed SystemFileAssociations. - Swapped the order of `*` and `AllFilesystemObjects`. This is the documented order and can also be observed in Process Monitor. https://learn.microsoft.com/en-us/windows/win32/shell/fa-associationarray#about-association-arrays - Removed `(..., L"%s//%s", extension, wszClass)`, this does not seem to be a valid thing (`.TestAAExtWeird` in my tests). - Adds the `Unknown` class when appropriate. Not adding the `openas` verb to `Unknown` rgs registration now to mimic Windows, because ROS `CDefaultContextMenu` lacks verb de-duplication and the menu would end up with two "Open With" entries. This just uses `(cidl == 1)` to simulate Windows, while Windows on NT6 uses `MultiSelectModel=Single`, a NT6 feature not implemented in ROS. - The class order for folders was wrong and is still "wrong" in this PR, but I chose to use the Windows menu display order until the exact mechanics required in `CDefaultContextMenu` can be understood. - Extracts the extension from ANSI PIDLs. --- dll/win32/shell32/folders/CDesktopFolder.cpp | 2 +- dll/win32/shell32/folders/CFSFolder.cpp | 2 +- dll/win32/shell32/shfldr.h | 4 +- dll/win32/shell32/shlfolder.cpp | 68 +++++++++++++------- 4 files changed, 50 insertions(+), 26 deletions(-) diff --git a/dll/win32/shell32/folders/CDesktopFolder.cpp b/dll/win32/shell32/folders/CDesktopFolder.cpp index d1fafa88b5c..37dc4cb6118 100644 --- a/dll/win32/shell32/folders/CDesktopFolder.cpp +++ b/dll/win32/shell32/folders/CDesktopFolder.cpp @@ -624,7 +624,7 @@ HRESULT WINAPI CDesktopFolder::GetUIObjectOf( UINT cKeys = 0; if (cidl > 0) { - AddFSClassKeysToArray(apidl[0], hKeys, &cKeys); + AddFSClassKeysToArray(cidl, apidl, hKeys, &cKeys); } DEFCONTEXTMENU dcm; diff --git a/dll/win32/shell32/folders/CFSFolder.cpp b/dll/win32/shell32/folders/CFSFolder.cpp index 4f26b4eb864..978b0d440fd 100644 --- a/dll/win32/shell32/folders/CFSFolder.cpp +++ b/dll/win32/shell32/folders/CFSFolder.cpp @@ -1207,7 +1207,7 @@ HRESULT WINAPI CFSFolder::GetUIObjectOf(HWND hwndOwner, { HKEY hKeys[16]; UINT cKeys = 0; - AddFSClassKeysToArray(apidl[0], hKeys, &cKeys); + AddFSClassKeysToArray(cidl, apidl, hKeys, &cKeys); DEFCONTEXTMENU dcm; dcm.hwnd = hwndOwner; diff --git a/dll/win32/shell32/shfldr.h b/dll/win32/shell32/shfldr.h index 7fd635f7385..bb67f1f2bee 100644 --- a/dll/win32/shell32/shfldr.h +++ b/dll/win32/shell32/shfldr.h @@ -64,7 +64,7 @@ HRESULT SHELL32_BindToSF (LPCITEMIDLIST pidlRoot, PERSIST_FOLDER_TARGET_INFO* pp extern "C" BOOL HCR_RegOpenClassIDKey(REFIID riid, HKEY *hkey); -void AddFSClassKeysToArray(PCUITEMID_CHILD pidl, HKEY* array, UINT* cKeys); +void AddFSClassKeysToArray(UINT cidl, PCUITEMID_CHILD_ARRAY apidl, HKEY* array, UINT* cKeys); HRESULT CDefViewBckgrndMenu_CreateInstance(IShellFolder* psf, REFIID riid, void **ppv); @@ -94,7 +94,7 @@ static __inline int SHELL32_GUIDToStringW (REFGUID guid, LPWSTR str) void SHELL_FS_ProcessDisplayFilename(LPWSTR szPath, DWORD dwFlags); BOOL SHELL_FS_HideExtension(LPCWSTR pwszPath); -void AddClassKeyToArray(const WCHAR * szClass, HKEY* array, UINT* cKeys); +LSTATUS AddClassKeyToArray(const WCHAR* szClass, HKEY* array, UINT* cKeys); #ifdef __cplusplus diff --git a/dll/win32/shell32/shlfolder.cpp b/dll/win32/shell32/shlfolder.cpp index 8f176e8f9e2..283cf76ce8b 100644 --- a/dll/win32/shell32/shlfolder.cpp +++ b/dll/win32/shell32/shlfolder.cpp @@ -265,59 +265,83 @@ HRESULT SHELL32_CompareDetails(IShellFolder2* isf, LPARAM lParam, LPCITEMIDLIST return MAKE_COMPARE_HRESULT(ret); } -void AddClassKeyToArray(const WCHAR * szClass, HKEY* array, UINT* cKeys) +LSTATUS AddClassKeyToArray(const WCHAR* szClass, HKEY* array, UINT* cKeys) { if (*cKeys >= 16) - return; + return ERROR_MORE_DATA; HKEY hkey; LSTATUS result = RegOpenKeyExW(HKEY_CLASSES_ROOT, szClass, 0, KEY_READ | KEY_QUERY_VALUE, &hkey); - if (result != ERROR_SUCCESS) - return; - - array[*cKeys] = hkey; - *cKeys += 1; + if (result == ERROR_SUCCESS) + { + array[*cKeys] = hkey; + *cKeys += 1; + } + return result; } -void AddFSClassKeysToArray(PCUITEMID_CHILD pidl, HKEY* array, UINT* cKeys) +void AddFSClassKeysToArray(UINT cidl, PCUITEMID_CHILD_ARRAY apidl, HKEY* array, UINT* cKeys) { + // This function opens the association array keys in canonical order for filesystem items. + // The order is documented: learn.microsoft.com/en-us/windows/win32/shell/fa-associationarray + + ASSERT(cidl >= 1 && apidl); + PCUITEMID_CHILD pidl = apidl[0]; if (_ILIsValue(pidl)) { + WCHAR buf[MAX_PATH]; + PWSTR name; FileStructW* pFileData = _ILGetFileStructW(pidl); - LPWSTR extension = PathFindExtension(pFileData->wszName); + if (pFileData) + { + name = pFileData->wszName; + } + else + { + _ILSimpleGetTextW(pidl, buf, _countof(buf)); + name = buf; + } + LPCWSTR extension = PathFindExtension(name); if (extension) { - AddClassKeyToArray(extension, array, cKeys); - - WCHAR wszClass[MAX_PATH], wszClass2[MAX_PATH]; + WCHAR wszClass[MAX_PATH], wszSFA[23 + _countof(wszClass)]; DWORD dwSize = sizeof(wszClass); - if (RegGetValueW(HKEY_CLASSES_ROOT, extension, NULL, RRF_RT_REG_SZ, NULL, wszClass, &dwSize) == ERROR_SUCCESS) + if (RegGetValueW(HKEY_CLASSES_ROOT, extension, NULL, RRF_RT_REG_SZ, NULL, wszClass, &dwSize) != ERROR_SUCCESS || + !*wszClass || AddClassKeyToArray(wszClass, array, cKeys) != ERROR_SUCCESS) { - swprintf(wszClass2, L"%s//%s", extension, wszClass); + // Only add the extension key if the ProgId is not valid + AddClassKeyToArray(extension, array, cKeys); - AddClassKeyToArray(wszClass, array, cKeys); - AddClassKeyToArray(wszClass2, array, cKeys); + // "Open With" becomes the default when there are no verbs in the above keys + if (cidl == 1) + AddClassKeyToArray(L"Unknown", array, cKeys); } - swprintf(wszClass2, L"SystemFileAssociations//%s", extension); - AddClassKeyToArray(wszClass2, array, cKeys); + swprintf(wszSFA, L"SystemFileAssociations\\%s", extension); + AddClassKeyToArray(wszSFA, array, cKeys); + dwSize = sizeof(wszClass); if (RegGetValueW(HKEY_CLASSES_ROOT, extension, L"PerceivedType ", RRF_RT_REG_SZ, NULL, wszClass, &dwSize) == ERROR_SUCCESS) { - swprintf(wszClass2, L"SystemFileAssociations//%s", wszClass); - AddClassKeyToArray(wszClass2, array, cKeys); + swprintf(wszSFA, L"SystemFileAssociations\\%s", wszClass); + AddClassKeyToArray(wszSFA, array, cKeys); } } - AddClassKeyToArray(L"AllFilesystemObjects", array, cKeys); AddClassKeyToArray(L"*", array, cKeys); + AddClassKeyToArray(L"AllFilesystemObjects", array, cKeys); } else if (_ILIsFolder(pidl)) { + // FIXME: Directory > Folder > AFO is the correct order and it's + // the order Windows reports in its undocumented association array + // but it is somehow not the order Windows adds the items to its menu! + // Until the correct algorithm in CDefaultContextMenu can be determined, + // we add the folder keys in "menu order". + AddClassKeyToArray(L"Folder", array, cKeys); AddClassKeyToArray(L"AllFilesystemObjects", array, cKeys); AddClassKeyToArray(L"Directory", array, cKeys); - AddClassKeyToArray(L"Folder", array, cKeys); } else {