[SHELL32] Fix FS folder assoc array class order (#6047)
authorWhindmar Saksit <whindsaks@proton.me>
Sun, 24 Mar 2024 20:37:59 +0000 (21:37 +0100)
committerGitHub <noreply@github.com>
Sun, 24 Mar 2024 20:37:59 +0000 (21:37 +0100)
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
dll/win32/shell32/folders/CFSFolder.cpp
dll/win32/shell32/shfldr.h
dll/win32/shell32/shlfolder.cpp

index d1fafa8..37dc4cb 100644 (file)
@@ -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;
index 4f26b4e..978b0d4 100644 (file)
@@ -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;
index 7fd635f..bb67f1f 100644 (file)
@@ -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
 
index 8f176e8..283cf76 100644 (file)
@@ -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
     {