1
0
mirror of https://github.com/valinet/ExplorerPatcher.git synced 2024-11-15 03:27:35 +01:00

File Explorer: Add support for classic drive groupings in This PC

This commit is contained in:
lordmilko 2022-03-26 20:49:11 +11:00 committed by Valentin-Gabriel Radu
parent 21f3377a27
commit 3f96325ad6
5 changed files with 311 additions and 1 deletions

View File

@ -111,6 +111,12 @@ BEGIN
IDS_INSTALL_SUCCESS_TEXT "Installation succeeded."
IDS_INSTALL_ERROR_TEXT "Installation failed."
IDS_UNINSTALL_SUCCESS_TEXT "Uninstallation succeeded."
IDS_DRIVECATEGORY_HARDDISKDRIVES "Hard Disk Drives"
IDS_DRIVECATEGORY_REMOVABLESTORAGE "Devices with Removable Storage"
IDS_DRIVECATEGORY_OTHER "Other"
IDS_DRIVECATEGORY_IMAGING "Scanners and Cameras"
IDS_DRIVECATEGORY_PORTABLEMEDIADEVICE "Portable Media Devices"
IDS_DRIVECATEGORY_PORTABLEDEVICE "Portable Devices"
END
STRINGTABLE

View File

@ -13,6 +13,8 @@
#include <Uxtheme.h>
#pragma comment(lib, "UxTheme.lib")
#include <Shlobj_core.h>
#include <propvarutil.h>
#pragma comment(lib, "Propsys.lib")
#include <commctrl.h>
#pragma comment(lib, "Comctl32.lib")
#include <dwmapi.h>
@ -78,6 +80,7 @@ DWORD bEnableArchivePlugin = FALSE;
DWORD bMonitorOverride = TRUE;
DWORD bOpenAtLogon = FALSE;
DWORD bClockFlyoutOnWinC = FALSE;
DWORD bUseClassicDriveGrouping = FALSE;
DWORD bDisableImmersiveContextMenu = FALSE;
DWORD bClassicThemeMitigations = FALSE;
DWORD bWasClassicThemeMitigationsSet = FALSE;
@ -106,6 +109,7 @@ DWORD bNoPropertiesInContextMenu = FALSE;
DWORD dwTaskbarGlomLevel = TASKBARGLOMLEVEL_DEFAULT;
DWORD dwMMTaskbarGlomLevel = MMTASKBARGLOMLEVEL_DEFAULT;
HMODULE hModule = NULL;
HANDLE hShell32 = NULL;
HANDLE hDelayedInjectionThread = NULL;
HANDLE hIsWinXShown = NULL;
HANDLE hWinXThread = NULL;
@ -5859,6 +5863,15 @@ void WINAPI LoadSettings(LPARAM lParam)
&dwSize
);
dwSize = sizeof(DWORD);
RegQueryValueExW(
hKey,
TEXT("UseClassicDriveGrouping"),
0,
NULL,
&bUseClassicDriveGrouping,
&dwSize
);
dwSize = sizeof(DWORD);
RegQueryValueExW(
hKey,
TEXT("DisableImmersiveContextMenu"),
@ -7905,6 +7918,252 @@ HINSTANCE explorer_ShellExecuteW(
#pragma endregion
#pragma region "Classic Drive Grouping"
const struct { DWORD dwDescriptionId; UINT uResourceId; } driveCategoryMap[] = {
{ SHDID_FS_DIRECTORY, 9338 }, //shell32
{ SHDID_COMPUTER_SHAREDDOCS, 9338 }, //shell32
{ SHDID_COMPUTER_FIXED, IDS_DRIVECATEGORY_HARDDISKDRIVES },
{ SHDID_COMPUTER_DRIVE35, IDS_DRIVECATEGORY_REMOVABLESTORAGE },
{ SHDID_COMPUTER_REMOVABLE, IDS_DRIVECATEGORY_REMOVABLESTORAGE },
{ SHDID_COMPUTER_CDROM, IDS_DRIVECATEGORY_REMOVABLESTORAGE },
{ SHDID_COMPUTER_DRIVE525, IDS_DRIVECATEGORY_REMOVABLESTORAGE },
{ SHDID_COMPUTER_NETDRIVE, 9340 }, //shell32
{ SHDID_COMPUTER_OTHER, IDS_DRIVECATEGORY_OTHER },
{ SHDID_COMPUTER_RAMDISK, IDS_DRIVECATEGORY_OTHER },
{ SHDID_COMPUTER_IMAGING, IDS_DRIVECATEGORY_IMAGING },
{ SHDID_COMPUTER_AUDIO, IDS_DRIVECATEGORY_PORTABLEMEDIADEVICE },
{ SHDID_MOBILE_DEVICE, IDS_DRIVECATEGORY_PORTABLEDEVICE }
};
//Represents the true data structure that is returned from shell32!DllGetClassObject
typedef struct {
const IClassFactoryVtbl* lpVtbl;
ULONG flags;
REFCLSID rclsid;
HRESULT(*pfnCreateInstance)(IUnknown*, REFIID, void**);
} Shell32ClassFactoryEntry;
//Represents a custom ICategorizer/IShellExtInit
typedef struct _EPCategorizer
{
ICategorizerVtbl* categorizer;
IShellExtInitVtbl* shellExtInit;
ULONG ulRefCount;
IShellFolder2* pShellFolder;
} EPCategorizer;
#pragma region "EPCategorizer: ICategorizer"
HRESULT STDMETHODCALLTYPE EPCategorizer_ICategorizer_QueryInterface(ICategorizer* _this, REFIID riid, void** ppvObject)
{
if (IsEqualIID(riid, &IID_IUnknown) || IsEqualIID(riid, &IID_ICategorizer))
{
*ppvObject = _this;
}
else if (IsEqualIID(riid, &IID_IShellExtInit))
{
*ppvObject = &((EPCategorizer*) _this)->shellExtInit;
}
else
{
ppvObject = NULL;
return E_NOINTERFACE;
}
_this->lpVtbl->AddRef(_this);
return S_OK;
}
ULONG STDMETHODCALLTYPE EPCategorizer_ICategorizer_AddRef(ICategorizer* _this)
{
return InterlockedIncrement(&((EPCategorizer*)_this)->ulRefCount);
}
ULONG STDMETHODCALLTYPE EPCategorizer_ICategorizer_Release(ICategorizer* _this)
{
ULONG ulNewCount = InterlockedDecrement(&((EPCategorizer*)_this)->ulRefCount);
//When the window is closed or refreshed the object is finally freed
if (ulNewCount == 0)
{
EPCategorizer* epCategorizer = (EPCategorizer*)_this;
if (epCategorizer->pShellFolder != NULL)
{
epCategorizer->pShellFolder->lpVtbl->Release(epCategorizer->pShellFolder);
epCategorizer->pShellFolder = NULL;
}
free(epCategorizer);
}
return ulNewCount;
}
HRESULT STDMETHODCALLTYPE EPCategorizer_ICategorizer_GetDescription(ICategorizer* _this, LPWSTR pszDesc, UINT cch)
{
//As of writing returns the string "Type". Same implementation as shell32!CStorageSystemTypeCategorizer::GetDescription
LoadStringW(hShell32, 0x3105, pszDesc, cch);
return S_OK;
}
HRESULT STDMETHODCALLTYPE EPCategorizer_ICategorizer_GetCategory(ICategorizer* _this, UINT cidl, PCUITEMID_CHILD_ARRAY apidl, DWORD* rgCategoryIds)
{
EPCategorizer* epCategorizer = (EPCategorizer*)_this;
HRESULT hr = S_OK;
for (UINT i = 0; i < cidl; i++)
{
rgCategoryIds[i] = IDS_DRIVECATEGORY_OTHER;
PROPERTYKEY key = { FMTID_ShellDetails, PID_DESCRIPTIONID };
VARIANT variant;
VariantInit(&variant);
hr = epCategorizer->pShellFolder->lpVtbl->GetDetailsEx(epCategorizer->pShellFolder, apidl[i], &key, &variant);
if (SUCCEEDED(hr))
{
SHDESCRIPTIONID did;
if (SUCCEEDED(VariantToBuffer(&variant, &did, sizeof(did))))
{
for (int j = 0; j < ARRAYSIZE(driveCategoryMap); j++)
{
if (did.dwDescriptionId == driveCategoryMap[j].dwDescriptionId)
{
rgCategoryIds[i] = driveCategoryMap[j].uResourceId;
break;
}
}
}
VariantClear(&variant);
}
}
return hr;
}
HRESULT STDMETHODCALLTYPE EPCategorizer_ICategorizer_GetCategoryInfo(ICategorizer* _this, DWORD dwCategoryId, CATEGORY_INFO* pci)
{
//Now retrieve the display name to use for the resource ID dwCategoryId.
//pci is already populated with most of the information it needs, we just need to fill in the wszName
if (!LoadStringW(hModule, dwCategoryId, pci->wszName, ARRAYSIZE(pci->wszName)))
LoadStringW(hShell32, dwCategoryId, pci->wszName, ARRAYSIZE(pci->wszName));
return S_OK;
}
HRESULT STDMETHODCALLTYPE EPCategorizer_ICategorizer_CompareCategory(ICategorizer* _this, CATSORT_FLAGS csfFlags, DWORD dwCategoryId1, DWORD dwCategoryId2)
{
//Typically a categorizer would use the resource IDs containing the names of each category as the "category ID" as well. In our case however, we're using
//a combination of resource/category IDs provided by shell32 and resource/category IDs we're overriding ourselves. As a result, we are forced to compare
//not by the value of the category/resource IDs themselves, but by their _position_ within our category ID map
int categoryArraySize = ARRAYSIZE(driveCategoryMap);
int firstPos = -1;
int secondPos = -1;
for (int i = 0; i < categoryArraySize; i++)
{
if (driveCategoryMap[i].uResourceId == dwCategoryId1)
{
firstPos = i;
break;
}
}
for (int i = 0; i < categoryArraySize; i++)
{
if (driveCategoryMap[i].uResourceId == dwCategoryId2)
{
secondPos = i;
break;
}
}
int diff = firstPos - secondPos;
if (diff < 0)
return 0xFFFF;
return diff > 0;
}
#pragma endregion
#pragma region "EPCategorizer: IShellExtInit"
//Adjustor Thunks: https://devblogs.microsoft.com/oldnewthing/20040206-00/?p=40723
HRESULT STDMETHODCALLTYPE EPCategorizer_IShellExtInit_QueryInterface(IShellExtInit* _this, REFIID riid, void** ppvObject)
{
return EPCategorizer_ICategorizer_QueryInterface((ICategorizer*)((char*)_this - sizeof(IShellExtInitVtbl*)), riid, ppvObject);
}
ULONG STDMETHODCALLTYPE EPCategorizer_IShellExtInit_AddRef(IShellExtInit* _this)
{
return EPCategorizer_ICategorizer_AddRef((ICategorizer*)((char*)_this - sizeof(IShellExtInitVtbl*)));
}
ULONG STDMETHODCALLTYPE EPCategorizer_IShellExtInit_Release(IShellExtInit* _this)
{
return EPCategorizer_ICategorizer_Release((ICategorizer*)((char*)_this - sizeof(IShellExtInitVtbl*)));
}
HRESULT STDMETHODCALLTYPE EPCategorizer_IShellExtInit_Initialize(IShellExtInit* _this, PCIDLIST_ABSOLUTE pidlFolder, IDataObject* pdtobj, HKEY hkeyProgID)
{
EPCategorizer* epCategorizer = (EPCategorizer*)((char*)_this - sizeof(IShellExtInitVtbl*));
return SHBindToObject(NULL, pidlFolder, NULL, &IID_IShellFolder2, (void**)&epCategorizer->pShellFolder);
}
#pragma endregion
const ICategorizerVtbl EPCategorizer_categorizerVtbl = {
EPCategorizer_ICategorizer_QueryInterface,
EPCategorizer_ICategorizer_AddRef,
EPCategorizer_ICategorizer_Release,
EPCategorizer_ICategorizer_GetDescription,
EPCategorizer_ICategorizer_GetCategory,
EPCategorizer_ICategorizer_GetCategoryInfo,
EPCategorizer_ICategorizer_CompareCategory
};
const IShellExtInitVtbl EPCategorizer_shellExtInitVtbl = {
EPCategorizer_IShellExtInit_QueryInterface,
EPCategorizer_IShellExtInit_AddRef,
EPCategorizer_IShellExtInit_Release,
EPCategorizer_IShellExtInit_Initialize
};
HRESULT(STDMETHODCALLTYPE *shell32_DriveTypeCategorizer_CreateInstanceFunc)(IUnknown* pUnkOuter, REFIID riid, void** ppvObject);
HRESULT shell32_DriveTypeCategorizer_CreateInstanceHook(IUnknown* pUnkOuter, REFIID riid, void** ppvObject)
{
if (IsEqualIID(riid, &IID_ICategorizer))
{
EPCategorizer* epCategorizer = (EPCategorizer*) malloc(sizeof(EPCategorizer));
epCategorizer->categorizer = &EPCategorizer_categorizerVtbl;
epCategorizer->shellExtInit = &EPCategorizer_shellExtInitVtbl;
epCategorizer->ulRefCount = 1;
epCategorizer->pShellFolder = NULL;
*ppvObject = epCategorizer;
return S_OK;
}
return shell32_DriveTypeCategorizer_CreateInstanceFunc(pUnkOuter, riid, ppvObject);
}
#pragma endregion
#pragma region "Change language UI style"
#ifdef _WIN64
DEFINE_GUID(CLSID_InputSwitchControl,
@ -9529,7 +9788,7 @@ DWORD Inject(BOOL bIsExplorer)
#endif
HANDLE hShell32 = GetModuleHandleW(L"shell32.dll");
hShell32 = GetModuleHandleW(L"shell32.dll");
if (hShell32)
{
HRESULT(*SHELL32_Create_IEnumUICommand)(IUnknown*, int*, int, IUnknown**) = GetProcAddress(hShell32, (LPCSTR)0x2E8);
@ -9563,6 +9822,38 @@ DWORD Inject(BOOL bIsExplorer)
}
}
}
if (bUseClassicDriveGrouping)
{
HRESULT(*SHELL32_DllGetClassObject)(REFCLSID rclsid, REFIID riid, LPVOID* ppv) = GetProcAddress(hShell32, "DllGetClassObject");
if (SHELL32_DllGetClassObject)
{
IClassFactory* pClassFactory;
SHELL32_DllGetClassObject(&CLSID_DriveTypeCategorizer, &IID_IClassFactory, &pClassFactory);
if (pClassFactory)
{
//DllGetClassObject hands out a unique "factory entry" data structure for each type of CLSID, containing a pointer to an IClassFactoryVtbl as well as some other members including
//the _true_ create instance function that should be called (in this instance, shell32!CDriveTypeCategorizer_CreateInstance). When the IClassFactory::CreateInstance method is called,
//shell32!ECFCreateInstance will cast the IClassFactory* passed to it back into a factory entry, and then invoke the pfnCreateInstance function defined in that entry directly.
//Thus, rather than hooking the shared shell32!ECFCreateInstance function found on the IClassFactoryVtbl* shared by all class objects returned by shell32!DllGetClassObject, we get the real
//CreateInstance function that will be called and hook that instead
Shell32ClassFactoryEntry* pClassFactoryEntry = (Shell32ClassFactoryEntry*)pClassFactory;
DWORD flOldProtect = 0;
if (VirtualProtect(pClassFactoryEntry, sizeof(Shell32ClassFactoryEntry), PAGE_EXECUTE_READWRITE, &flOldProtect))
{
shell32_DriveTypeCategorizer_CreateInstanceFunc = pClassFactoryEntry->pfnCreateInstance;
pClassFactoryEntry->pfnCreateInstance = shell32_DriveTypeCategorizer_CreateInstanceHook;
VirtualProtect(pClassFactoryEntry, sizeof(Shell32ClassFactoryEntry), flOldProtect, &flOldProtect);
}
pClassFactory->lpVtbl->Release(pClassFactory);
}
}
}
}
printf("Setup shell32 functions done\n");

View File

@ -16,6 +16,13 @@
#define IDS_UNINSTALL_ERROR_TEXT 112
#define IDS_OPERATION_NONE 113
#define IDR_REGISTRY2 114
#define IDS_DRIVECATEGORY_HARDDISKDRIVES 40000
#define IDS_DRIVECATEGORY_REMOVABLESTORAGE 40001
#define IDS_DRIVECATEGORY_OTHER 40002
#define IDS_DRIVECATEGORY_IMAGING 40003
#define IDS_DRIVECATEGORY_PORTABLEMEDIA 40004
#define IDS_DRIVECATEGORY_PORTABLEMEDIADEVICE 40004
#define IDS_DRIVECATEGORY_PORTABLEDEVICE 40005
// Next default values for new objects
//

View File

@ -164,6 +164,9 @@
[-HKEY_CURRENT_USER\Software\Classes\CLSID\{86ca1aa0-34aa-4e8b-a509-50c905bae2a2}\InprocServer32]
;d Disable the Windows 11 context menu *
@=""
[HKEY_CURRENT_USER\Software\ExplorerPatcher]
;b Use classic drive groupings in This PC *
"UseClassicDriveGrouping"=dword:00000000
;t The following settings take effect on newly created File Explorer windows:
[HKEY_CURRENT_USER\Software\ExplorerPatcher]
;i Use immersive menus when displaying Windows 10 context menus **

View File

@ -142,6 +142,9 @@
;https://github.com/valinet/ExplorerPatcher/wiki/Using-ExplorerPatcher-as-shell-extension
;q
[HKEY_CURRENT_USER\Software\ExplorerPatcher]
;b Use classic drive groupings in This PC *
"UseClassicDriveGrouping"=dword:00000000
[HKEY_CURRENT_USER\Software\ExplorerPatcher]
;i Use immersive menus when displaying Windows 10 context menus **
"DisableImmersiveContextMenu"=dword:00000000
[-HKEY_CURRENT_USER\Software\Classes\CLSID\{056440FD-8568-48e7-A632-72157243B55B}\InprocServer32]