diff --git a/mods/settings-to-control-panel.wh.cpp b/mods/settings-to-control-panel.wh.cpp index ecb17b7109..81bb2bbb45 100644 --- a/mods/settings-to-control-panel.wh.cpp +++ b/mods/settings-to-control-panel.wh.cpp @@ -2,70 +2,47 @@ // @id settings-to-control-panel // @name Redirect Settings to Control Panel // @description Forces classic Control Panel to open instead of Windows 10/11 Settings app using native components. Primarily designed for Windows 10; Windows 11 support is limited due to Microsoft's shell architecture changes. -// @version 10.0.1 +// @version 10.0.20 // @author babamohammed // @github https://github.com/babamohammed2022 // @include explorer.exe +// @compilerOptions -lcomctl32 -lpsapi // ==/WindhawkMod== // ==WindhawkModReadme== /* # Redirect Settings → Control Panel -This mod intercepts modern `ms-settings:` URIs and redirects them to their -corresponding classic Control Panel applets using only native Windows components. +This mod intercepts modern `ms-settings:` links (the ones that open the +Settings app) and redirects them to their corresponding classic Control +Panel pages, using only native Windows components. --- ## Compatibility - **Windows 10** – Mostly complete support -- **Windows 11** – Partial support (some redirects may vary by build) +- **Windows 11** – Partial support --- ## Features -- Redirects numerous `ms-settings:` URIs to classic Control Panel -- Anti-loop protection -- Configurable fallback modes - ---- - -## Configuration - -- **EnableRedirects** – Turn the mod on or off -- **UIOnlyRedirects** – Only redirect clicks (safer, may miss some) -- **FallbackMode** – What to do when no classic page exists: - - `0` = ignore (do nothing) - - `1` = open Control Panel - - `2` = open modern Settings (default) -- **Win11CompatibilityMode** – Extra safety for Windows 11 -- **MaxLaunchesPerUri** – Prevents infinite loops (default: 3 launches per 5 seconds) - +- Redirects many `ms-settings:` links to the classic Control Panel +- Anti-loop protection (stops windows from reopening endlessly) +- Configurable fallback behavior for unmapped links +- Tray menu detection (experimental) --- ## Limitations -- System tray icons (audio/network) use DCOM activation and cannot be intercepted (This could change in future) -- Windows 11 support is limited due to Microsoft's architectural changes and some redirects might change based on versions. - ---- - -## How It Works - -The mod hooks: -- `ShellExecuteExW` / `ShellExecuteW` -- `CreateProcessW` -- `IShellDispatch2::ShellExecute` - -When a `ms-settings:` URI is detected, it launches the corresponding classic -Control Panel target instead of the modern Settings app. +- The system tray context menu redirect only supports the Win32 taskbar (the one from Windows 10 and previous versions). If using Windows 11, it might function decently but it is still an experimental feature. +- The device & printers system tray redirect may not work on some Windows 11 configurations, as Microsoft hardcoded the redirect to the Settings app in certain shell code paths. This could change in future if correct documentation is found. --- ## Credits - m417z – Code reviews and feedback -- Anixx – Testing on Windows 11 23H2 +- Anixx – Testing on Windows 11 23H2 and the original toolbar subclassing approach - dbilanoski – CLSID documentation */ // ==/WindhawkModReadme== @@ -73,12 +50,15 @@ Control Panel target instead of the modern Settings app. /* - EnableRedirects: true $name: Enable Redirects - $description: "Turns the mod on or off. When off, all Settings calls open normally." + $description: "Turns the mod on or off. When disabled, Settings opens normally as usual." +- RedirectSystemTray: false + $name: Redirect System Tray Audio/Network/Device & Printers (EXPERIMENTAL) + $description: "If enabled, right-clicking the Audio or Network or Device & Printers icon near the clock and choosing 'Open Sound settings' or 'Open Network settings' or 'Open devices and printers' will open the classic panel instead of the Settings app." - UIOnlyRedirects: false - $name: Non-Invasive UI Mode - $description: "Only intercepts ShellExecute* calls. Leaves CreateProcessW alone." + $name: Non-Invasive Mode + $description: "Only redirects clicks made in the UI. Doesn't touch programs that open Settings in other ways." - FallbackMode: "2" - $name: Fallback Mode (unmapped URIs) + $name: Behavior for Unmapped Links $description: "What to do when a Settings page has no classic equivalent." $options: - "0": Ignore (silent fail) @@ -86,46 +66,59 @@ Control Panel target instead of the modern Settings app. - "2": Pass through to the modern Settings application (ms-settings.exe)" - Win11CompatibilityMode: false $name: Windows 11 Compatibility Mode - $description: "On Windows 11, also replaces CLSIDs not confirmed safe (beyond the always-blocked known-loop CLSIDs)." + $description: "Safer mode for Windows 11. When enabled, only uses proven redirects while everything else opens the standard Control Panel page as a fallback to avoid loops or other issues." - MaxLaunchesPerUri: 3 - $name: Loop Guard — max launches per URI (per 5 s) - $description: "Safety valve: if the same redirect target fires more than this many times in 5 seconds the mod stops launching it. Set to 0 to disable." + $name: Anti-Loop Limit (per window, every 5 seconds) + $description: "Safety measure: if the same window gets opened too many times within a few seconds, the mod stops reopening it. Set to 0 to disable this limit." */ // ==/WindhawkModSettings== -#include +#include #include -#include +#include +#include +#include #include #include #include #include #include -#include -#include +#include + +// Custom IDs for tray menu redirection (TrackPopupMenuEx method) +#define TRAY_CUSTOM_ID_AUDIO 65001 +#define TRAY_CUSTOM_ID_NETWORK 65002 +#define TRAY_CUSTOM_ID_DEVICES 65003 + +// TrackPopupMenuEx hook (DLL-based fallback method) +using TrackPopupMenuEx_t = BOOL(WINAPI*)(HMENU, UINT, int, int, HWND, const TPMPARAMS*); +static TrackPopupMenuEx_t g_origTrackPopupMenuEx = nullptr; + +// Set on WM_RBUTTONUP by the subclass proc so TrackPopupMenuEx knows the icon type. +static int g_trayContextType = 0; +static DWORD g_trayContextTick = 0; +static std::mutex g_trayContextMutex; +static constexpr DWORD TRAY_CONTEXT_MAX_AGE_MS = 1500; -// Dynamic COM handling - no linking required -typedef HRESULT (WINAPI *CoCreateInstance_t)(const GUID* rclsid, IUnknown* pUnkOuter, DWORD dwClsContext, const GUID* riid, void** ppv); -static CoCreateInstance_t dyn_CoCreateInstance = nullptr; -static HMODULE g_hOle32 = nullptr; +using ICMH_CAODTM_t = bool(__fastcall*)(HMENU, HWND); +static ICMH_CAODTM_t g_icmhOrig_SndVolSSO = nullptr; +static ICMH_CAODTM_t g_icmhOrig_pnidui = nullptr; +static ICMH_CAODTM_t g_icmhOrig_Shell32Devices = nullptr; -// IShellDispatch2 vtable hook -using IShellDispatch2_ShellExecute_t = HRESULT(WINAPI*)(void* pThis, BSTR File, void* vArgs, void* vDir, void* vOperation, void* vShow); -static IShellDispatch2_ShellExecute_t IShellDispatch2_ShellExecute_orig = nullptr; + +static bool __fastcall ICMH_CAODTM_hook(HMENU, HWND); // Constants static const HINSTANCE SHELL_EXECUTE_SUCCESS = (HINSTANCE)33; -#define PERS_ED_CLSID L"shell:::{ED834ED6-4B5A-4bfe-8F11-A626DCB6A921}" #define PERS_ROOT L"explorer shell:::{ED834ED6-4B5A-4bfe-8F11-A626DCB6A921}" #define PERS_WALLPAPER L"explorer shell:::{ED834ED6-4B5A-4bfe-8F11-A626DCB6A921} -Microsoft.Personalization\\pageWallpaper" #define PERS_COLORS L"explorer shell:::{ED834ED6-4B5A-4bfe-8F11-A626DCB6A921} -Microsoft.Personalization\\pageColorization" #define SYSTEM_PROPS_CLSID L"shell:::{BB06C0E4-D293-4f75-8A90-CB05B6477EEE}" #define NOTIF_AREA_CLSID L"shell:::{05d7b0f4-2121-4eff-bf6b-ed3f69b894d9}" -#define WIN11_PASSTHROUGH L"__PASSTHROUGH__" -#define EASE_OF_ACCESS L"explorer shell:::{D555645E-D4F8-4c29-A827-D93C859C4F2A}" +#define WIN11_PASSTHROUGH L"__PASSTHROUGH__" +#define EASE_OF_ACCESS L"explorer shell:::{D555645E-D4F8-4c29-A827-D93C859C4F2A}" -// Forward declarations using CreateProcessW_t = BOOL(WINAPI*)(LPCWSTR, LPWSTR, LPSECURITY_ATTRIBUTES, LPSECURITY_ATTRIBUTES, BOOL, DWORD, LPVOID, LPCWSTR, LPSTARTUPINFOW, LPPROCESS_INFORMATION); static CreateProcessW_t CreateProcessW_orig = nullptr; @@ -134,13 +127,11 @@ using ShellExecuteExW_t = BOOL(WINAPI*)(SHELLEXECUTEINFOW*); static ShellExecuteExW_t ShellExecuteExW_orig = nullptr; static ShellExecuteW_t ShellExecuteW_orig = nullptr; -// Core resolve logic struct - MUST be defined before use struct ResolveResult { std::wstring target; bool intercept; }; -// Reentry guards static thread_local int g_hookDepth = 0; struct HookGuard { @@ -149,7 +140,6 @@ struct HookGuard { bool IsReentrant() const { return g_hookDepth > 1; } }; -// Cross-process reentry guard static std::wstring g_childEnvBlock; static void BuildChildEnvironment() { @@ -172,9 +162,9 @@ static bool IsChildProcess() { return GetEnvironmentVariableW(L"WH_STC_NOREDIRECT", nullptr, 0) > 0; } -// Settings struct ModSettings { bool enableRedirects = true; + bool redirectSystemTray = false; bool uiOnlyRedirects = false; int fallbackMode = 2; bool win11CompatibilityMode = false; @@ -183,31 +173,31 @@ struct ModSettings { static ModSettings g_settings; +static bool __fastcall ICMH_CAODTM_hook(HMENU, HWND) { + if (!g_settings.redirectSystemTray) return true; + return false; +} + static void LoadSettings() { g_settings.enableRedirects = Wh_GetIntSetting(L"EnableRedirects") != 0; + g_settings.redirectSystemTray = Wh_GetIntSetting(L"RedirectSystemTray") != 0; g_settings.uiOnlyRedirects = Wh_GetIntSetting(L"UIOnlyRedirects") != 0; - PCWSTR fallbackStr = Wh_GetStringSetting(L"FallbackMode"); + WindhawkUtils::StringSetting fallbackSetting(Wh_GetStringSetting(L"FallbackMode")); + PCWSTR fallbackStr = fallbackSetting; if (fallbackStr[0] != L'\0') { int mode = _wtoi(fallbackStr); g_settings.fallbackMode = (mode >= 0 && mode <= 2) ? mode : 2; } else { g_settings.fallbackMode = 2; } - Wh_FreeStringSetting(fallbackStr); g_settings.win11CompatibilityMode = Wh_GetIntSetting(L"Win11CompatibilityMode") != 0; int ml = Wh_GetIntSetting(L"MaxLaunchesPerUri"); g_settings.maxLaunchesPerUri = (ml >= 0 && ml <= 20) ? ml : 3; - - Wh_Log(L"EnableRedirects=%d UIOnly=%d SmartPers=always_on Fallback=%d Win11Compat=%d MaxLaunches=%d", - (int)g_settings.enableRedirects, (int)g_settings.uiOnlyRedirects, - g_settings.fallbackMode, - (int)g_settings.win11CompatibilityMode, g_settings.maxLaunchesPerUri); } -// Win11 detection static bool g_isWin11 = false; static void DetectWindowsVersion() { @@ -220,10 +210,8 @@ static void DetectWindowsVersion() { if (fn) fn(&osvi); } g_isWin11 = (osvi.dwMajorVersion == 10 && osvi.dwMinorVersion == 0 && osvi.dwBuildNumber >= 22000); - Wh_Log(L"Build %lu IsWin11=%d", osvi.dwBuildNumber, (int)g_isWin11); } -// Bounce-back guard struct BounceRecord { DWORD lastRedirectTick = 0; }; @@ -244,15 +232,12 @@ static bool BounceGuardIsBounce(const std::wstring& uri) { if (it == g_bounceGuard.end()) return false; DWORD elapsed = GetTickCount() - it->second.lastRedirectTick; if (elapsed < BOUNCE_WINDOW_MS) { - Wh_Log(L"BOUNCE-BACK: '%s' returned %lu ms after redirect — target is dead, routing to fallback", - uri.c_str(), elapsed); it->second.lastRedirectTick = 0; return true; } return false; } -// Loop guard struct LaunchRecord { int count = 0; DWORD firstTick = 0; @@ -281,55 +266,45 @@ static bool LoopGuardAllow(const std::wstring& target) { return true; } - Wh_Log(L"LOOP GUARD: suppressing launch of '%s' (%d times in %lu ms)", target.c_str(), rec.count, (now - rec.firstTick)); return false; } -// CLSID classification static const std::unordered_set g_win11SafeClsids = { - L"shell:::{8e908fc9-becc-40f6-915b-f4ca0e70d03d}", - L"shell:::{7007acc7-3202-11d1-aad2-00805fc1270e}", - L"shell:::{a8a91a66-3a7d-4424-8d24-04e180695c7a}", - L"shell:::{4026492f-2f69-46b8-b9bf-5654fc07e423}", - L"shell:::{20d04fe0-3aea-1069-a2d8-08002b30309d}", - L"shell:::{60632754-c523-4b62-b45c-4172da012619}", + L"shell:::{025a5937-a6be-4686-a844-36fe4bec8b6d}", L"shell:::{05d7b0f4-2121-4eff-bf6b-ed3f69b894d9}", + L"shell:::{15eae92e-f17a-4431-9f28-805e482dafd4}", + L"shell:::{20d04fe0-3aea-1069-a2d8-08002b30309d}", L"shell:::{2227a280-3aea-1069-a2de-08002b30309d}", + L"shell:::{26ee0668-a00a-44d7-9371-beb064c98683}", + L"shell:::{4026492f-2f69-46b8-b9bf-5654fc07e423}", L"shell:::{59031a47-3f72-44a7-89c5-5595fe6b30ee}", + L"shell:::{60632754-c523-4b62-b45c-4172da012619}", + L"shell:::{6dfd7c5c-2451-11d3-a299-00c04f8ef6af}", + L"shell:::{7007acc7-3202-11d1-aad2-00805fc1270e}", + L"shell:::{725be8f7-668e-4c7b-8f90-46bdb0936430}", + L"shell:::{7a9d77bd-5403-11d2-8785-2e0420524153}", + L"shell:::{8e908fc9-becc-40f6-915b-f4ca0e70d03d}", L"shell:::{9c60de1e-e5fc-40f4-a487-460851a8d915}", + L"shell:::{a8a91a66-3a7d-4424-8d24-04e180695c7a}", L"shell:::{b98a2bea-7d42-4558-8bd1-832f41bac6fd}", + L"shell:::{bb64f8a7-bee7-4e1a-ab8d-7d8273f7fdb6}", L"shell:::{bd84b380-8ca2-1069-ab1d-08000948f534}", - L"shell:::{d9ef8727-cac2-4e60-809e-86f80a666c91}", L"shell:::{c58c4893-3be0-4b45-abb5-a63e4b8c8651}", - L"shell:::{6dfd7c5c-2451-11d3-a299-00c04f8ef6af}", - L"shell:::{15eae92e-f17a-4431-9f28-805e482dafd4}", - L"shell:::{d450a8a1-9568-45c7-9c0e-b4f9fb4537bd}", - L"shell:::{26ee0668-a00a-44d7-9371-beb064c98683}", - L"shell:::{725be8f7-668e-4c7b-8f90-46bdb0936430}", - L"shell:::{f02c1a0d-be21-4350-88b0-7367fc96ef3c}", - L"shell:::{bb64f8a7-bee7-4e1a-ab8d-7d8273f7fdb6}", - L"shell:::{025a5937-a6be-4686-a844-36fe4bec8b6d}", L"shell:::{d17d1d6d-cc3f-4815-8fe3-607e7d5d10b3}", - L"shell:::{7a9d77bd-5403-11d2-8785-2e0420524153}", + L"shell:::{d450a8a1-9568-45c7-9c0e-b4f9fb4537bd}", + L"shell:::{d555645e-d4f8-4c29-a827-d93c859c4f2a}", + L"shell:::{d9ef8727-cac2-4e60-809e-86f80a666c91}", L"shell:::{ecd0924-4208-451e-8ee0-373c0956de16}", L"shell:::{ed7ba470-8e54-465e-825c-99712043e01c}", - L"shell:::{d555645e-d4f8-4c29-a827-d93c859c4f2a}", + L"shell:::{f02c1a0d-be21-4350-88b0-7367fc96ef3c}", }; static const std::unordered_set g_win11LoopClsids = { - L"shell:::{bb06c0e4-d293-4f75-8a90-cb05b6477eee}", - L"shell:::{ed834ed6-4b5a-4bfe-8f11-a626dcb6a921}", L"shell:::{17cd9488-1228-4b2f-88ce-4298e93e0966}", L"shell:::{80f3f1d5-feca-45f3-bc32-752c152e456e}", L"shell:::{9fe63afd-59cf-4419-9775-abcc3849f861}", -}; - -static const std::unordered_set g_win11NoForceRedirect = { - L"ms-settings:defaultapps", - L"ms-settings:appsfeatures", - L"ms-settings:network", - L"ms-settings:bluetooth", - L"ms-settings:printers", + L"shell:::{bb06c0e4-d293-4f75-8a90-cb05b6477eee}", + L"shell:::{ed834ed6-4b5a-4bfe-8f11-a626dcb6a921}", }; static bool IsClsidSafeOnWin11(const std::wstring& lowerTarget) { @@ -344,20 +319,359 @@ static bool IsClsidLoopOnWin11(const std::wstring& lowerTarget) { return g_win11LoopClsids.count(base) > 0; } -// String utilities static std::wstring ToLower(std::wstring s) { std::transform(s.begin(), s.end(), s.begin(), ::towlower); return s; } -// Mappings +static HWND g_hTrayToolbar = nullptr; +static BYTE* g_sndVolSSOBase = nullptr; +static BYTE* g_sndVolSSOEnd = nullptr; +static BYTE* g_pniduiBase = nullptr; +static BYTE* g_pniduiEnd = nullptr; + +static bool InitTrayDllInfo() { + if (g_sndVolSSOBase && g_pniduiBase) return true; + + HMODULE hSndVol = GetModuleHandleW(L"SndVolSSO.dll"); + if (hSndVol) { + MODULEINFO mi{}; + if (GetModuleInformation(GetCurrentProcess(), hSndVol, &mi, sizeof(mi))) { + g_sndVolSSOBase = (BYTE*)mi.lpBaseOfDll; + g_sndVolSSOEnd = g_sndVolSSOBase + mi.SizeOfImage; + } + } + + HMODULE hPniDui = GetModuleHandleW(L"pnidui.dll"); + if (hPniDui) { + MODULEINFO mi{}; + if (GetModuleInformation(GetCurrentProcess(), hPniDui, &mi, sizeof(mi))) { + g_pniduiBase = (BYTE*)mi.lpBaseOfDll; + g_pniduiEnd = g_pniduiBase + mi.SizeOfImage; + } + } + + return (g_sndVolSSOBase != nullptr || g_pniduiBase != nullptr); +} + +static int GetTrayButtonType(HWND hToolbar, int buttonIndex) { + if (buttonIndex < 0) return 0; + + TBBUTTON tb{}; + if (!SendMessageW(hToolbar, TB_GETBUTTON, buttonIndex, (LPARAM)&tb)) return 0; + if (!tb.dwData) return 0; + + HWND hIconWnd = *(HWND*)tb.dwData; + if (!hIconWnd || !IsWindow(hIconWnd)) return 0; + + wchar_t className[256]{}; + if (!GetClassNameW(hIconWnd, className, 256)) return 0; + + // Language-independent check: Safely Remove Hardware is a plain Shell_NotifyIcon + // and doesn't use an ATL class wrapper from a specific DLL. We ignore it here + // and let TrackPopupMenuEx identify it via hotplug.dll return address. + if (wcsncmp(className, L"ATL:", 4) != 0) { + return 0; + } + + const wchar_t* hexPart = className + 4; + ULONG_PTR addr = 0; + + while (*hexPart) { + wchar_t c = *hexPart; + int digit = 0; + if (c >= L'0' && c <= L'9') digit = c - L'0'; + else if (c >= L'A' && c <= L'F') digit = 10 + (c - L'A'); + else if (c >= L'a' && c <= L'f') digit = 10 + (c - L'a'); + else break; + addr = (addr << 4) | digit; + hexPart++; + } + + if (g_sndVolSSOBase && addr >= (ULONG_PTR)g_sndVolSSOBase && addr < (ULONG_PTR)g_sndVolSSOEnd) + return 1; // Audio + if (g_pniduiBase && addr >= (ULONG_PTR)g_pniduiBase && addr < (ULONG_PTR)g_pniduiEnd) + return 2; // Network + + return 0; +} + +static void OpenClassicSoundPanel() { + SHELLEXECUTEINFOW sei = {}; + sei.cbSize = sizeof(sei); + sei.fMask = SEE_MASK_FLAG_NO_UI; + sei.lpVerb = L"open"; + sei.lpFile = L"control.exe"; + sei.lpParameters = L"mmsys.cpl,,0"; + sei.nShow = SW_SHOWNORMAL; + ShellExecuteExW_orig(&sei); +} + +static void OpenClassicNetworkConnections() { + SHELLEXECUTEINFOW sei = {}; + sei.cbSize = sizeof(sei); + sei.fMask = SEE_MASK_FLAG_NO_UI | SEE_MASK_INVOKEIDLIST; + sei.lpVerb = L"open"; + sei.lpFile = L"explorer.exe"; + sei.lpParameters = L"shell:::{8E908FC9-BECC-40f6-915B-F4CA0E70D03D}"; + sei.nShow = SW_SHOWNORMAL; + ShellExecuteExW_orig(&sei); +} + +static void OpenClassicDevicesAndPrinters() { + SHELLEXECUTEINFOW sei = {}; + sei.cbSize = sizeof(sei); + sei.fMask = SEE_MASK_FLAG_NO_UI | SEE_MASK_INVOKEIDLIST; + sei.lpVerb = L"open"; + sei.lpFile = L"explorer.exe"; + sei.lpParameters = L"shell:::{A8A91A66-3A7D-4424-8D24-04E180695C7A}"; + sei.nShow = SW_SHOWNORMAL; + ShellExecuteExW_orig(&sei); +} + +static LRESULT CALLBACK TrayToolbarSubclassProc( + HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam, DWORD_PTR dwRefData) +{ + if (msg == WM_RBUTTONUP) { + POINT pt; + pt.x = (int)(short)LOWORD(lParam); + pt.y = (int)(short)HIWORD(lParam); + int hitIndex = (int)SendMessageW(hwnd, TB_HITTEST, 0, (LPARAM)&pt); + + if (hitIndex >= 0) { + int buttonType = GetTrayButtonType(hwnd, hitIndex); + if (buttonType == 1) { + std::lock_guard lk(g_trayContextMutex); + g_trayContextType = 1; + g_trayContextTick = GetTickCount(); + } + else if (buttonType == 2) { + std::lock_guard lk(g_trayContextMutex); + g_trayContextType = 2; + g_trayContextTick = GetTickCount(); + } + } + } + return DefSubclassProc(hwnd, msg, wParam, lParam); +} + +static HWND FindTrayToolbar() { + HWND hTray = FindWindowW(L"Shell_TrayWnd", nullptr); + if (!hTray) return nullptr; + DWORD pid = 0; + GetWindowThreadProcessId(hTray, &pid); + if (pid != GetCurrentProcessId()) return nullptr; + HWND hNotify = FindWindowExW(hTray, nullptr, L"TrayNotifyWnd", nullptr); + if (!hNotify) return nullptr; + HWND hSysPager = FindWindowExW(hNotify, nullptr, L"SysPager", nullptr); + if (hSysPager) { + HWND hToolbar = FindWindowExW(hSysPager, nullptr, L"ToolbarWindow32", nullptr); + if (hToolbar) return hToolbar; + } + return FindWindowExW(hNotify, nullptr, L"ToolbarWindow32", nullptr); +} + +static void SetupTraySubclass() { + if (g_hTrayToolbar) return; + if (!InitTrayDllInfo()) return; + HWND hToolbar = FindTrayToolbar(); + if (!hToolbar) return; + if (WindhawkUtils::SetWindowSubclassFromAnyThread(hToolbar, TrayToolbarSubclassProc, 0)) { + g_hTrayToolbar = hToolbar; + } +} + +static void RemoveTraySubclass() { + if (g_hTrayToolbar) { + WindhawkUtils::RemoveWindowSubclassFromAnyThread(g_hTrayToolbar, TrayToolbarSubclassProc); + g_hTrayToolbar = nullptr; + } +} + +static void* GetReturnAddress() { + void* stackTrace[3]; + WORD frames = CaptureStackBackTrace(0, 3, stackTrace, NULL); + if (frames >= 3) return stackTrace[2]; + return nullptr; +} + +static bool IsAddressInModule(void* address, const wchar_t* moduleName) { + HMODULE hModule = nullptr; + if (GetModuleHandleExW(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS, (LPCWSTR)address, &hModule)) { + wchar_t path[MAX_PATH]; + if (GetModuleFileNameW(hModule, path, MAX_PATH)) + return (wcsstr(path, moduleName) != nullptr); + } + return false; +} +BOOL WINAPI TrackPopupMenuEx_Hook(HMENU hMenu, UINT uFlags, int x, int y, HWND hWnd, const TPMPARAMS* lptpm) { + if (!g_settings.redirectSystemTray || !g_settings.enableRedirects) + return g_origTrackPopupMenuEx(hMenu, uFlags, x, y, hWnd, lptpm); + + HookGuard guard; + if (guard.IsReentrant()) + return g_origTrackPopupMenuEx(hMenu, uFlags, x, y, hWnd, lptpm); + + // --- Primary: subclass flag set on WM_RBUTTONUP (language-independent) --- + int contextType; + { + std::lock_guard lk(g_trayContextMutex); + contextType = g_trayContextType; + DWORD tick = g_trayContextTick; + g_trayContextType = 0; + g_trayContextTick = 0; + if (contextType != 0 && GetTickCount() - tick > TRAY_CONTEXT_MAX_AGE_MS) { + contextType = 0; + } + } + + bool isAudioMenu = (contextType == 1); + bool isNetworkMenu = (contextType == 2); + bool isDeviceMenu = false; + + // --- Fallback: DLL return-address detection (language-independent) --- + if (!isAudioMenu && !isNetworkMenu) { + void* retAddr = GetReturnAddress(); + int itemCount = GetMenuItemCount(hMenu); + if (itemCount > 0) { + if (IsAddressInModule(retAddr, L"SndVolSSO.dll")) { + if (itemCount <= 6) isAudioMenu = true; + } + else if (IsAddressInModule(retAddr, L"pnidui.dll")) { + if (itemCount <= 6) isNetworkMenu = (itemCount >= 2 && itemCount <= 5); + } + else if (IsAddressInModule(retAddr, L"dxgi.dll")) { + // dxgi.dll handles both Network flyout (IDs 3107/3109) and + // Device/Safely Remove Hardware flyout (ID 215 = "Open Devices and Printers") + if (itemCount == 2 && GetMenuItemID(hMenu, 0) == 3107 && GetMenuItemID(hMenu, 1) == 3109) { + isNetworkMenu = true; + } + // Check for device menu: item ID 215 is the language-independent + // identifier for "Open Devices and Printers" + else if (GetMenuItemID(hMenu, 0) == 215) { + isDeviceMenu = true; + Wh_Log(L"[TRAY-HOOK] Device menu detected via dxgi.dll + ID 215"); + } + } + else if (IsAddressInModule(retAddr, L"shell32.dll")) { + // NUOVO: Rilevamento shell32.dll per Win11 23H2 + // Cerca l'ID 215 in qualsiasi posizione del menu + for (int i = 0; i < itemCount; i++) { + UINT itemId = GetMenuItemID(hMenu, i); + if (itemId == 215) { + isDeviceMenu = true; + Wh_Log(L"[TRAY-HOOK] Device menu detected via shell32.dll + ID 215 at index %d", i); + break; + } + } + } + else if (IsAddressInModule(retAddr, L"hotplug.dll")) { + // Fallback for older Windows versions where hotplug.dll handles the menu + isDeviceMenu = true; + Wh_Log(L"[TRAY-HOOK] Device menu detected via hotplug.dll"); + } + } + } + + if (!isAudioMenu && !isNetworkMenu && !isDeviceMenu) + return g_origTrackPopupMenuEx(hMenu, uFlags, x, y, hWnd, lptpm); + + int itemCount = GetMenuItemCount(hMenu); + const wchar_t* menuKind = isAudioMenu ? L"AUDIO" : (isNetworkMenu ? L"NETWORK" : L"DEVICE"); + Wh_Log(L"[TRAY-HOOK] %s menu, %d items", menuKind, itemCount); + + // Find target item without any text matching. + // Audio: first item. + // Network: last non-separator item. + // Device: item with ID 215 ("Open Devices and Printers") — language-independent. + int targetIndex = -1; + + if (isAudioMenu) { + targetIndex = 0; + } + else if (isNetworkMenu) { + for (int i = itemCount - 1; i >= 0; i--) { + MENUITEMINFOW miiCheck = { sizeof(MENUITEMINFOW) }; + miiCheck.fMask = MIIM_FTYPE; + if (GetMenuItemInfoW(hMenu, i, TRUE, &miiCheck)) { + if (!(miiCheck.fType & MFT_SEPARATOR)) { + targetIndex = i; + break; + } + } + } + } + else if (isDeviceMenu) { + // Find item with ID 215 — the language-independent ID for "Open Devices and Printers" + for (int i = 0; i < itemCount; i++) { + if (GetMenuItemID(hMenu, i) == 215) { + targetIndex = i; + break; + } + } + // Fallback: if ID 215 not found (unlikely), use first non-separator item + if (targetIndex == -1) { + for (int i = 0; i < itemCount; i++) { + MENUITEMINFOW miiCheck = { sizeof(MENUITEMINFOW) }; + miiCheck.fMask = MIIM_FTYPE; + if (GetMenuItemInfoW(hMenu, i, TRUE, &miiCheck)) { + if (!(miiCheck.fType & MFT_SEPARATOR)) { + targetIndex = i; + break; + } + } + } + } + } + + if (targetIndex == -1) { + Wh_Log(L"[TRAY-HOOK] No valid menu item found for %s menu", menuKind); + return g_origTrackPopupMenuEx(hMenu, uFlags, x, y, hWnd, lptpm); + } + + Wh_Log(L"[TRAY-HOOK] %s menu target index=%d, ID=%u", menuKind, targetIndex, GetMenuItemID(hMenu, targetIndex)); + + UINT customId = isAudioMenu ? TRAY_CUSTOM_ID_AUDIO + : isNetworkMenu ? TRAY_CUSTOM_ID_NETWORK + : TRAY_CUSTOM_ID_DEVICES; + UINT originalId = GetMenuItemID(hMenu, targetIndex); + + MENUITEMINFOW mii = { sizeof(MENUITEMINFOW) }; + mii.fMask = MIIM_ID; + mii.wID = customId; + SetMenuItemInfoW(hMenu, targetIndex, TRUE, &mii); + + bool callerWantedReturnCmd = (uFlags & TPM_RETURNCMD) != 0; + uFlags |= TPM_RETURNCMD; + BOOL result = g_origTrackPopupMenuEx(hMenu, uFlags, x, y, hWnd, lptpm); + int selectedId = (int)result; + + // Restore original ID + mii.wID = originalId; + SetMenuItemInfoW(hMenu, targetIndex, TRUE, &mii); + + if (selectedId == (int)customId) { + Wh_Log(L"[TRAY-HOOK] User selected target item, redirecting"); + if (isAudioMenu) OpenClassicSoundPanel(); + else if (isNetworkMenu) OpenClassicNetworkConnections(); + else OpenClassicDevicesAndPrinters(); + return 0; + } + + if (selectedId != 0 && !callerWantedReturnCmd) { + PostMessageW(hWnd, WM_COMMAND, MAKEWPARAM((WORD)selectedId, 0), 0); + return TRUE; + } + + return result; +} + static std::unordered_map g_mappings; static void InitMappings() { const bool w11 = g_isWin11; g_mappings = { - // Personalization {L"ms-settings:personalization", PERS_ROOT}, {L"ms-settings:personalization-colors", PERS_COLORS}, {L"ms-settings:colors", PERS_COLORS}, @@ -368,22 +682,15 @@ static void InitMappings() { {L"ms-settings:background", PERS_WALLPAPER}, {L"ms-settings:personalization-background-wallpaper", PERS_WALLPAPER}, {L"ms-settings:personalization-background-slideshow", PERS_WALLPAPER}, - - // Fonts & Color {L"ms-settings:fonts", L"shell:::{BD84B380-8CA2-1069-AB1D-08000948F534}"}, {L"ms-settings:display-advanced-color", L"colorcpl.exe"}, {L"ms-settings:colorcpl", L"colorcpl.exe"}, - - // Display - reindirizzamento a Impostazioni schermo classiche (rundll32) {L"ms-settings:display", L"rundll32.exe display.dll,ShowAdapterSettings 0"}, {L"ms-settings:display-advanced", L"rundll32.exe display.dll,ShowAdapterSettings 0"}, {L"ms-settings:display-advanced-graphics", L"rundll32.exe display.dll,ShowAdapterSettings 0"}, - {L"ms-settings:graphics-settings", L"rundll32.exe display.dll,ShowAdapterSettings 0"}, {L"ms-settings:display-adapter-properties", L"rundll32.exe display.dll,ShowAdapterSettings 0"}, {L"ms-settings:display-resolution", L"rundll32.exe display.dll,ShowAdapterSettings 0"}, {L"ms-settings:screenrotation", L"rundll32.exe display.dll,ShowAdapterSettings 0"}, - - // System {L"ms-settings:about", w11 ? L"sysdm.cpl" : SYSTEM_PROPS_CLSID}, {L"ms-settings:system", w11 ? L"sysdm.cpl" : SYSTEM_PROPS_CLSID}, {L"ms-settings:sysinfo", w11 ? L"sysdm.cpl" : SYSTEM_PROPS_CLSID}, @@ -399,15 +706,11 @@ static void InitMappings() { {L"ms-settings:appsforwebsites", L"appwiz.cpl"}, {L"ms-settings:optionalfeatures", L"OptionalFeatures.exe"}, {L"ms-settings:system-settings", L"shell:::{025A5937-A6BE-4686-A844-36FE4BEC8B6D}\\pageGlobalSettings"}, - - // Power {L"ms-settings:powersleep", L"powercfg.cpl"}, {L"ms-settings:battery", L"powercfg.cpl"}, {L"ms-settings:batterysaver", L"powercfg.cpl"}, {L"ms-settings:batterysaver-settings", L"powercfg.cpl"}, {L"ms-settings:batterysaver-usagedetails", L"powercfg.cpl"}, - - // Sound {L"ms-settings:audio", L"mmsys.cpl"}, {L"ms-settings:sound-control-panel", L"control.exe /name Microsoft.Sound"}, {L"ms-settings:sound-playback", L"control.exe mmsys.cpl,,0"}, @@ -419,16 +722,12 @@ static void InitMappings() { {L"ms-settings:sound-input", L"control.exe mmsys.cpl,,1"}, {L"ms-settings:apps-volume", L"control.exe mmsys.cpl,,0"}, {L"ms-settings:sound", L"control.exe mmsys.cpl,,0"}, - - // Notifications / Taskbar {L"ms-settings:notifications", NOTIF_AREA_CLSID}, {L"ms-settings:taskbar-notifications", NOTIF_AREA_CLSID}, {L"ms-settings:taskbar-systemtray", NOTIF_AREA_CLSID}, {L"ms-settings:notifications-systemtray", NOTIF_AREA_CLSID}, {L"ms-settings:systemtray", NOTIF_AREA_CLSID}, {L"ms-settings:notificationiconpreferences", NOTIF_AREA_CLSID}, - - // Input devices {L"ms-settings:mousetouchpad", L"main.cpl"}, {L"ms-settings:devices-touchpad", L"main.cpl"}, {L"ms-settings:keyboard", L"main.cpl,,1"}, @@ -438,8 +737,6 @@ static void InitMappings() { {L"ms-settings:pen-windowsinksettings", w11 ? L"control.exe" : L"shell:::{80F3F1D5-FECA-45F3-BC32-752C152E456E}"}, {L"ms-settings:devices-touch", w11 ? L"control.exe" : L"shell:::{80F3F1D5-FECA-45F3-BC32-752C152E456E}"}, {L"ms-settings:autoplay", L"shell:::{9C60DE1E-E5FC-40f4-A487-460851A8D915}"}, - - // Devices / Printers {L"ms-settings:printers", L"shell:::{A8A91A66-3A7D-4424-8D24-04E180695C7A}"}, {L"ms-settings:printers-scanners", L"shell:::{2227A280-3AEA-1069-A2DE-08002B30309D}"}, {L"ms-settings:bluetooth", L"shell:::{A8A91A66-3A7D-4424-8D24-04E180695C7A}"}, @@ -448,8 +745,6 @@ static void InitMappings() { {L"ms-settings:mobile-devices", L"shell:::{A8A91A66-3A7D-4424-8D24-04E180695C7A}"}, {L"ms-settings:camera", L"shell:::{A8A91A66-3A7D-4424-8D24-04E180695C7A}"}, {L"ms-settings:privacy-customdevices", L"shell:::{A8A91A66-3A7D-4424-8D24-04E180695C7A}"}, - - // Network {L"ms-settings:network", L"shell:::{8E908FC9-BECC-40f6-915B-F4CA0E70D03D}"}, {L"ms-settings:network-wifi", L"shell:::{8E908FC9-BECC-40f6-915B-F4CA0E70D03D}"}, {L"ms-settings:network-ethernet", L"shell:::{8E908FC9-BECC-40f6-915B-F4CA0E70D03D}"}, @@ -461,13 +756,10 @@ static void InitMappings() { {L"ms-settings:network-proxy", L"inetcpl.cpl,,4"}, {L"ms-settings:network-status", L"shell:::{7007ACC7-3202-11D1-AAD2-00805FC1270E}"}, {L"ms-settings:network-dialup", L"shell:::{7007ACC7-3202-11D1-AAD2-00805FC1270E}"}, - {L"ms-settings:network-advancedsettings", L"shell:::{8E908FC9-BECC-40f6-915B-F4CA0E70D03D}"}, {L"ms-settings:firewall", L"shell:::{4026492F-2F69-46B8-B9BF-5654FC07E423}"}, {L"ms-settings:network-firewall", L"shell:::{4026492F-2F69-46B8-B9BF-5654FC07E423}"}, {L"ms-settings:windowsdefender", L"shell:::{4026492F-2F69-46B8-B9BF-5654FC07E423}"}, {L"ms-settings:network-places", L"shell:::{F02C1A0D-BE21-4350-88B0-7367FC96EF3C}"}, - - // Accounts {L"ms-settings:yourinfo", L"shell:::{60632754-c523-4b62-b45c-4172da012619}"}, {L"ms-settings:yourinfo-profile", L"shell:::{59031a47-3f72-44a7-89c5-5595fe6b30ee}"}, {L"ms-settings:emailandaccounts", L"shell:::{60632754-c523-4b62-b45c-4172da012619}"}, @@ -475,19 +767,13 @@ static void InitMappings() { {L"ms-settings:startupapps", L"msconfig.exe"}, {L"ms-settings:netplwiz", L"shell:::{7A9D77BD-5403-11d2-8785-2E0420524153}"}, {L"ms-settings:workplace", L"shell:::{26EE0668-A00A-44D7-9371-BEB064C98683}\\0\\::{ECDB0924-4208-451E-8EE0-373C0956DE16}"}, - - // Default Apps {L"ms-settings:defaultapps", w11 ? WIN11_PASSTHROUGH : L"shell:::{17cd9488-1228-4b2f-88ce-4298e93e0966}"}, - - // Time & Language {L"ms-settings:dateandtime", L"timedate.cpl"}, {L"ms-settings:dateandtime-region", L"timedate.cpl"}, {L"ms-settings:dateandtime-addclocks", L"timedate.cpl,,1"}, {L"ms-settings:regionlanguage", L"intl.cpl"}, {L"ms-settings:regionformatting", L"intl.cpl"}, {L"ms-settings:language", L"intl.cpl"}, - - // Ease of Access {L"ms-settings:easeofaccess", EASE_OF_ACCESS}, {L"ms-settings:easeofaccess-narrator", EASE_OF_ACCESS}, {L"ms-settings:easeofaccess-magnifier", EASE_OF_ACCESS}, @@ -500,72 +786,43 @@ static void InitMappings() { {L"ms-settings:easeofaccess-audio", EASE_OF_ACCESS}, {L"ms-settings:easeofaccess-mouse", EASE_OF_ACCESS}, {L"ms-settings:easeofaccess-keyboard", EASE_OF_ACCESS}, - - // Recovery / Backup / Troubleshooting - {L"ms-settings:backup", L"shell:::{B98A2BEA-7D42-4558-8BD1-832F41BAC6FD}"}, {L"ms-settings:recovery", w11 ? L"control.exe" : L"shell:::{9FE63AFD-59CF-4419-9775-ABCC3849F861}"}, - {L"ms-settings:troubleshoot", w11 ? L"msdt.exe -id DeviceDiagnostic" : L"shell:::{C58C4893-3BE0-4B45-ABB5-A63E4B8C8651}"}, + {L"ms-settings:troubleshoot", w11 ? L"msdt.exe -id DeviceDiagnostic" : L"shell:::{C58C4893-3BE0-4B45-ABB5-A63E4B8C8651}"}, {L"ms-settings:deviceencryption", L"shell:::{D9EF8727-CAC2-4e60-809E-86F80A666C91}"}, - - // Gaming {L"ms-settings:gaming-gamebar", L"joy.cpl"}, - - // File Explorer Options {L"ms-settings:folders", L"shell:::{6DFD7C5C-2451-11d3-A299-00C04F8EF6AF}"}, - - // Get Programs (modern: Apps & Features) {L"ms-settings:appsfeatures-app", L"shell:::{15eae92e-f17a-4431-9f28-805e482dafd4}"}, - - // Installed Updates {L"ms-settings:windowsupdate-history", L"shell:::{d450a8a1-9568-45c7-9c0e-b4f9fb4537bd}"}, - - // History - doubled like as how it was in 10.0.0 - {L"ms-settings:troubleshoot", L"shell:::{C58C4893-3BE0-4B45-ABB5-A63E4B8C8651}\\historyPage"}, - - // Keyboard + {L"ms-settings:troubleshoot-history", L"shell:::{C58C4893-3BE0-4B45-ABB5-A63E4B8C8651}\\historyPage"}, {L"ms-settings:keyboard-advanced", L"shell:::{26EE0668-A00A-44D7-9371-BEB064C98683}\\0\\::{725BE8F7-668E-4C7B-8F90-46BDB0936430}"}, - - // Keyboard Properties {L"ms-settings:keyboard-properties", L"shell:::{725BE8F7-668E-4C7B-8F90-46BDB0936430}"}, - - // Problem Details / Reports {L"ms-settings:privacy-feedback", L"shell:::{BB64F8A7-BEE7-4E1A-AB8D-7D8273F7FDB6}\\pageReportDetails"}, - - // Problem Reporting Settings {L"ms-settings:problem-reporting-settings", L"shell:::{BB64F8A7-BEE7-4E1A-AB8D-7D8273F7FDB6}\\pageSettings"}, - - // Problem Reports (list) {L"ms-settings:problem-reports", L"shell:::{BB64F8A7-BEE7-4E1A-AB8D-7D8273F7FDB6}\\pageProblems"}, - - // Reliability Monitor {L"ms-settings:reliability", L"shell:::{BB64F8A7-BEE7-4E1A-AB8D-7D8273F7FDB6}\\pageReliabilityView"}, - - // Speech Properties {L"ms-settings:speech", L"shell:::{D17D1D6D-CC3F-4815-8FE3-607E7D5D10B3}"}, - - // Search Troubleshooting {L"ms-settings:search-diagnostics", L"shell:::{C58C4893-3BE0-4B45-ABB5-A63E4B8C8651}\\searchPage"}, - - // Control Panel All Tasks {L"ms-settings:controlpanel", L"shell:::{ED7BA470-8E54-465E-825C-99712043E01C}"}, + {L"ms-settings:signinoptions", L"netplwiz"}, + {L"ms-settings:accounts-signinoptions", L"netplwiz"}, + {L"ms-settings:accounts-users", L"netplwiz"}, + {L"ms-settings:family-users", L"netplwiz"}, + {L"ms-settings:power", L"powercfg.cpl"}, + {L"ms-settings:display-hdr", L"colorcpl.exe"}, + {L"ms-settings:personalization-taskbar", NOTIF_AREA_CLSID}, + {L"ms-settings:multitasking", L"control.exe"}, + {L"ms-settings:storage", L"control.exe"}, + {L"ms-settings:storagesense", L"control.exe"}, }; - - // Additional Windows 11 24H2 specific mappings + + g_mappings[L"ms-settings:backup"] = L"control.exe /name Microsoft.BackupAndRestore"; + g_mappings[L"ms-settings:network-advancedsettings"] = L"control.exe /name Microsoft.NetworkAndSharingCenter"; + if (g_isWin11) { - g_mappings[L"ms-settings:power"] = L"powercfg.cpl"; - g_mappings[L"ms-settings:display-hdr"] = L"colorcpl.exe"; - g_mappings[L"ms-settings:taskbar"] = NOTIF_AREA_CLSID; - g_mappings[L"ms-settings:personalization-taskbar"] = NOTIF_AREA_CLSID; - g_mappings[L"ms-settings:multitasking"] = L"control.exe"; - g_mappings[L"ms-settings:storage"] = L"control.exe"; - g_mappings[L"ms-settings:storagesense"] = L"control.exe"; - g_mappings[L"ms-settings:backup"] = L"control.exe /name Microsoft.BackupAndRestore"; - g_mappings[L"ms-settings:network-advancedsettings"] = L"control.exe /name Microsoft.NetworkAndSharingCenter"; g_mappings[L"ms-settings:recovery"] = L"shell:::{26EE0668-A00A-44D7-9371-BEB064C98683}\\0\\::{9FE63AFD-59CF-4419-9775-ABCC3849F861}"; } } -// URI normalization static std::wstring NormalizeUri(const std::wstring& uri) { std::wstring result = ToLower(uri); const std::wstring PROTOCOL = L"ms-settings://"; @@ -593,46 +850,33 @@ static bool IsShellClsid(const wchar_t* s) { return ToLower(s).find(L"shell:::") != std::wstring::npos; } -// Win11 CLSID filter static std::wstring ApplyWin11Filter(const std::wstring& target) { if (!g_isWin11) return target; - std::wstring lower = ToLower(target); - if (lower.find(L"shell:::") != 0) return target; - - if (IsClsidLoopOnWin11(lower)) { + if (lower.find(L"shell:::") != 0 && lower.find(L"explorer shell:::") != 0) return target; + + std::wstring clsPart = lower; + if (lower.find(L"explorer ") == 0) clsPart = lower.substr(9); + + if (IsClsidLoopOnWin11(clsPart)) { if (lower.find(L"ed834ed6") != std::wstring::npos) { - if (lower.find(L"pagewallpaper") != std::wstring::npos) { - Wh_Log(L"Win11 loop-guard: {ED834ED6}\\pageWallpaper -> PERS_WALLPAPER"); - return PERS_WALLPAPER; - } - Wh_Log(L"Win11 loop-guard: {ED834ED6} -> PERS_ROOT"); + if (lower.find(L"pagewallpaper") != std::wstring::npos) return PERS_WALLPAPER; + if (lower.find(L"pagecolorization") != std::wstring::npos) return PERS_COLORS; return PERS_ROOT; } - if (lower.find(L"bb06c0e4") != std::wstring::npos) { - Wh_Log(L"Win11 loop-guard: {BB06C0E4} -> sysdm.cpl"); - return L"sysdm.cpl"; - } - Wh_Log(L"Win11 loop-guard: replacing loop CLSID '%s' with control.exe", target.c_str()); + if (lower.find(L"bb06c0e4") != std::wstring::npos) return L"sysdm.cpl"; return L"control.exe"; } - - if (g_settings.win11CompatibilityMode && !IsClsidSafeOnWin11(lower)) { - Wh_Log(L"Win11 compat: replacing unconfirmed CLSID '%s' with control.exe", target.c_str()); + if (g_settings.win11CompatibilityMode && !IsClsidSafeOnWin11(clsPart)) { return L"control.exe"; } - return target; } -// Fallback handling static bool HandleFallback(const std::wstring& uri) { switch (g_settings.fallbackMode) { - case 0: - Wh_Log(L"Fallback: ignoring unmapped URI: %s", uri.c_str()); - return true; + case 0: return true; case 1: { - Wh_Log(L"Fallback: opening control.exe for unmapped URI: %s", uri.c_str()); std::wstring cmd = L"control.exe"; STARTUPINFOW si = {}; si.cb = sizeof(si); @@ -645,76 +889,47 @@ static bool HandleFallback(const std::wstring& uri) { } return true; } - default: - Wh_Log(L"Fallback: passing through unmapped URI: %s", uri.c_str()); - return false; + default: return false; } } - static void LaunchTarget(const std::wstring& command) { - Wh_Log(L"Launching: %s", command.c_str()); - - if (!LoopGuardAllow(command)) { - Wh_Log(L"Launch suppressed by loop guard: %s", command.c_str()); - return; - } + if (!LoopGuardAllow(command)) return; std::wstring lower = ToLower(command); - // PER I COMANDI "explorer shell:::" USA ShellExecuteExW if (lower.find(L"explorer shell:::") != std::wstring::npos) { SHELLEXECUTEINFOW sei = {}; sei.cbSize = sizeof(sei); sei.fMask = SEE_MASK_FLAG_NO_UI; sei.lpVerb = L"open"; sei.lpFile = L"explorer.exe"; - sei.lpParameters = command.c_str() + 9; // Salta "explorer " (9 caratteri) + sei.lpParameters = command.c_str() + 9; sei.nShow = SW_SHOWNORMAL; - - Wh_Log(L"Using ShellExecuteExW: explorer.exe params='%s'", sei.lpParameters); ShellExecuteExW_orig(&sei); return; } - // Gestione speciale per rundll32.exe e comandi con parametri - if (lower.find(L"rundll32.exe ") == 0 || - (lower.find(L"rundll32.exe") == 0 && lower.length() > 12)) { - Wh_Log(L"Detected rundll32 command: %s", command.c_str()); - - std::wstring rundll32Path; - wchar_t sysDir[MAX_PATH]; - if (GetSystemDirectoryW(sysDir, MAX_PATH)) { - rundll32Path = std::wstring(sysDir) + L"\\rundll32.exe"; - } else { - rundll32Path = L"rundll32.exe"; - } - - std::wstring params; - if (lower.find(L"rundll32.exe ") != std::wstring::npos) { - params = command.substr(13); + if (lower.find(L"rundll32.exe ") == 0) { + wchar_t rundll32Path[MAX_PATH]; + if (GetSystemDirectoryW(rundll32Path, MAX_PATH)) { + wcscat_s(rundll32Path, MAX_PATH, L"\\rundll32.exe"); } else { - params = command.substr(12); + wcscpy_s(rundll32Path, MAX_PATH, L"rundll32.exe"); } - SHELLEXECUTEINFOW sei = {}; sei.cbSize = sizeof(sei); sei.fMask = SEE_MASK_FLAG_NO_UI; sei.lpVerb = L"open"; - sei.lpFile = rundll32Path.c_str(); - sei.lpParameters = params.c_str(); + sei.lpFile = rundll32Path; + sei.lpParameters = command.c_str() + 13; sei.nShow = SW_SHOWNORMAL; - - Wh_Log(L"Using ShellExecuteExW: %s params='%s'", rundll32Path.c_str(), params.c_str()); ShellExecuteExW_orig(&sei); return; } - // Comandi completi con percorso eseguibile - bool isFullCmdLine = - (lower.find(L"explorer.exe ") != std::wstring::npos) || - (lower.find(L"control.exe /") != std::wstring::npos); - + bool isFullCmdLine = (lower.find(L"explorer.exe ") != std::wstring::npos) || + (lower.find(L"control.exe /") != std::wstring::npos); if (isFullCmdLine) { STARTUPINFOW si = {}; si.cb = sizeof(si); @@ -724,10 +939,7 @@ static void LaunchTarget(const std::wstring& command) { std::wstring mutable_cmd = command; if (!CreateProcessW_orig(nullptr, mutable_cmd.data(), nullptr, nullptr, FALSE, CREATE_UNICODE_ENVIRONMENT, - (LPVOID)g_childEnvBlock.c_str(), - nullptr, &si, &pi)) { - Wh_Log(L"CreateProcess failed for '%s' (%lu)", - command.c_str(), GetLastError()); + (LPVOID)g_childEnvBlock.c_str(), nullptr, &si, &pi)) { } else { CloseHandle(pi.hProcess); CloseHandle(pi.hThread); @@ -735,15 +947,12 @@ static void LaunchTarget(const std::wstring& command) { return; } - // UAC targets if (command == L"devmgmt.msc" || command == L"compmgmt.msc" || command == L"slui.exe" || command == L"OptionalFeatures.exe") { - ShellExecuteW_orig(nullptr, L"open", command.c_str(), - nullptr, nullptr, SW_SHOWNORMAL); + ShellExecuteW_orig(nullptr, L"open", command.c_str(), nullptr, nullptr, SW_SHOWNORMAL); return; } - // Generic router STARTUPINFOW si = {}; si.cb = sizeof(si); si.dwFlags = STARTF_USESHOWWINDOW; @@ -754,8 +963,6 @@ static void LaunchTarget(const std::wstring& command) { if (command.find(L".msc") != std::wstring::npos) { cmdLine = L"mmc.exe \"" + command + L"\""; } else if (command.find(L".cpl") != std::wstring::npos) { - // Usa ShellExecuteW per i .cpl - molto più affidabile - Wh_Log(L"Using ShellExecuteW for .cpl: control.exe %s", command.c_str()); ShellExecuteW_orig(nullptr, L"open", L"control.exe", command.c_str(), nullptr, SW_SHOWNORMAL); return; } else if (command.find(L".exe") != std::wstring::npos) { @@ -763,7 +970,7 @@ static void LaunchTarget(const std::wstring& command) { } else if (command.find(L"shell:::") == 0) { SHELLEXECUTEINFOW sei = {}; sei.cbSize = sizeof(sei); - sei.fMask = SEE_MASK_FLAG_NO_UI; + sei.fMask = SEE_MASK_FLAG_NO_UI | SEE_MASK_INVOKEIDLIST; sei.lpVerb = L"open"; sei.lpFile = L"explorer.exe"; sei.lpParameters = command.c_str(); @@ -780,10 +987,7 @@ static void LaunchTarget(const std::wstring& command) { std::wstring mutableCmd = cmdLine; if (!CreateProcessW_orig(nullptr, mutableCmd.data(), nullptr, nullptr, FALSE, CREATE_UNICODE_ENVIRONMENT, - (LPVOID)g_childEnvBlock.c_str(), - nullptr, &si, &pi)) { - Wh_Log(L"CreateProcess failed for '%s' (error %lu)", - cmdLine.c_str(), GetLastError()); + (LPVOID)g_childEnvBlock.c_str(), nullptr, &si, &pi)) { return; } CloseHandle(pi.hProcess); @@ -791,103 +995,61 @@ static void LaunchTarget(const std::wstring& command) { } } -// Personalization detection static bool IsPersonalizationWindow(HWND hwnd) { - if (!hwnd) { - Wh_Log(L"HWND null -> desktop context menu path"); - return false; - } - + if (!hwnd) return false; HWND h = hwnd; while (h) { - wchar_t cls[256] = {}; - wchar_t title[512] = {}; + wchar_t cls[256] = {}, title[512] = {}; GetClassNameW(h, cls, 256); GetWindowTextW(h, title, 512); - - std::wstring c = ToLower(cls); - std::wstring t = ToLower(title); - - if (c == L"progman" || c == L"workerw" || c == L"shelldll_defview") { - Wh_Log(L"HWND: desktop class '%s' -> context menu", cls); - return false; - } - if (c == L"cabinetwclass") { - return true; - } - if (t.find(L"personaliz") != std::wstring::npos) { - Wh_Log(L"HWND: title match 'personaliz' -> Personalization window"); - return true; - } - + std::wstring c = ToLower(cls), t = ToLower(title); + if (c == L"progman" || c == L"workerw" || c == L"shelldll_defview") return false; + if (c == L"cabinetwclass") return true; + if (t.find(L"personaliz") != std::wstring::npos) return true; HWND parent = GetParent(h); if (!parent || parent == h) break; h = parent; } - - Wh_Log(L"HWND: no personalization window found -> context menu"); return false; } static std::wstring ResolvePersonalizationBackground(HWND hwnd) { - if (IsPersonalizationWindow(hwnd)) { - Wh_Log(L"personalization-background -> wallpaper page"); - return PERS_WALLPAPER; - } - Wh_Log(L"personalization-background -> Personalization root"); - return PERS_ROOT; + return IsPersonalizationWindow(hwnd) ? PERS_WALLPAPER : PERS_ROOT; +} + +static bool ShouldApplyBounceGuard(const std::wstring& uri) { + return uri.find(L"personalization") != std::wstring::npos; } static ResolveResult ResolveUri(const std::wstring& uri, HWND hwnd) { if (uri == L"ms-settings:personalization-background") { - if (BounceGuardIsBounce(uri)) { - Wh_Log(L"Bounce-back on personalization-background - suppressing duplicate, target is opening"); - return {L"", true}; - } + if (BounceGuardIsBounce(uri)) return {L"", true}; std::wstring t = ApplyWin11Filter(ResolvePersonalizationBackground(hwnd)); BounceGuardRecord(uri); return {t, true}; } - auto it = g_mappings.find(uri); if (it != g_mappings.end()) { - if (BounceGuardIsBounce(uri)) { - Wh_Log(L"Bounce-back on '%s', routing to fallback", uri.c_str()); + bool useBounceGuard = ShouldApplyBounceGuard(uri); + if (useBounceGuard && BounceGuardIsBounce(uri)) { bool handled = HandleFallback(uri); return {L"", handled}; } - std::wstring t = ApplyWin11Filter(it->second); - if (t == WIN11_PASSTHROUGH) { - Wh_Log(L"Passthrough sentinel for '%s', routing to fallback", uri.c_str()); bool handled = HandleFallback(uri); return {L"", handled}; } - - Wh_Log(L"Mapped: %s -> %s", uri.c_str(), t.c_str()); - BounceGuardRecord(uri); + if (useBounceGuard) BounceGuardRecord(uri); return {t, true}; } - if (uri.find(L"ms-settings:") == 0) { bool handled = HandleFallback(uri); return {L"", handled}; } - - if (uri.find(L"shell:::") == 0) { - if (g_isWin11 && IsClsidLoopOnWin11(uri)) { - std::wstring t = ApplyWin11Filter(uri); - Wh_Log(L"Win11 loop-CLSID intercepted: %s -> %s", uri.c_str(), t.c_str()); - return {t, true}; - } - } - - Wh_Log(L"Unmapped, passing through: %s", uri.c_str()); return {L"", false}; } -// Control system detection helpers static std::wstring BaseNameLower(const std::wstring& path) { size_t pos = path.rfind(L'\\'); return ToLower((pos != std::wstring::npos) ? path.substr(pos + 1) : path); @@ -905,89 +1067,40 @@ static bool IsControlSystemCommand(const std::wstring& cmdLine) { std::vector tokens; std::wstring current; bool inQuotes = false; - for (wchar_t c : cmdLine) { - if (c == L'"') { - inQuotes = !inQuotes; - } else if (c == L' ' && !inQuotes) { - if (!current.empty()) { - tokens.push_back(current); - current.clear(); - } - } else { - current += c; - } + if (c == L'"') { inQuotes = !inQuotes; } + else if (c == L' ' && !inQuotes) { + if (!current.empty()) { tokens.push_back(current); current.clear(); } + } else { current += c; } } if (!current.empty()) tokens.push_back(current); - if (tokens.size() != 2) return false; - std::wstring exe = BaseNameLower(tokens[0]); if (exe != L"control.exe" && exe != L"control") return false; - std::wstring arg = ToLower(tokens[1]); return (arg == L"system" || arg == L"microsoft.system"); } -// Helper function to open sound panel -static bool OpenSoundPanel() { - Wh_Log(L"Opening sound panel via LaunchTarget"); - LaunchTarget(L"control.exe mmsys.cpl,,0"); - return true; -} - -// Hook: ShellExecuteExW BOOL WINAPI ShellExecuteExW_hook(SHELLEXECUTEINFOW* pei) { if (IsChildProcess()) return ShellExecuteExW_orig(pei); - HookGuard guard; - if (guard.IsReentrant()) { - Wh_Log(L"ShellExecuteExW: reentrant call, passing through"); - return ShellExecuteExW_orig(pei); - } - + if (guard.IsReentrant()) return ShellExecuteExW_orig(pei); if (!g_settings.enableRedirects || !pei) return ShellExecuteExW_orig(pei); if (IsControlSystemParams(pei->lpFile, pei->lpParameters)) { - Wh_Log(L"ShellExecuteExW: intercepted control system"); LaunchTarget(g_isWin11 ? L"sysdm.cpl" : SYSTEM_PROPS_CLSID); if (pei->fMask & SEE_MASK_NOCLOSEPROCESS) pei->hProcess = nullptr; return TRUE; } - + std::wstring uri; - if (IsMsSettings(pei->lpFile)) - uri = NormalizeUri(pei->lpFile); - else if (IsMsSettings(pei->lpParameters)) - uri = NormalizeUri(pei->lpParameters); - else if (IsShellClsid(pei->lpFile)) - uri = ToLower(pei->lpFile); - else if (IsShellClsid(pei->lpParameters)) - uri = ToLower(pei->lpParameters); - - // Audio URI handling - if (!uri.empty() && (uri.find(L"ms-settings:sound") == 0 || - uri.find(L"ms-settings:audio") == 0 || - uri.find(L"ms-settings:apps-volume") == 0 || - uri.find(L"ms-settings:sound-devices") == 0 || - uri.find(L"ms-settings:sound-output") == 0 || - uri.find(L"ms-settings:sound-input") == 0)) { - - if (OpenSoundPanel()) { - if (pei->fMask & SEE_MASK_NOCLOSEPROCESS) pei->hProcess = nullptr; - return TRUE; - } - } + if (IsMsSettings(pei->lpFile)) uri = NormalizeUri(pei->lpFile); + else if (IsMsSettings(pei->lpParameters)) uri = NormalizeUri(pei->lpParameters); + else if (IsShellClsid(pei->lpFile)) uri = ToLower(pei->lpFile); + else if (IsShellClsid(pei->lpParameters)) uri = ToLower(pei->lpParameters); - // Display URI handling - questo intercetta anche i clic dal menu contestuale del desktop - if (!uri.empty() && (uri.find(L"ms-settings:display") == 0 || - uri.find(L"ms-settings:screenrotation") == 0 || - uri.find(L"ms-settings:graphics-settings") == 0)) { - Wh_Log(L"ShellExecuteExW: intercepted display settings, launching classic Display Properties"); - LaunchTarget(L"rundll32.exe display.dll,ShowAdapterSettings 0"); - if (pei->fMask & SEE_MASK_NOCLOSEPROCESS) pei->hProcess = nullptr; - return TRUE; - } + if (uri == L"ms-settings:taskbar") + return ShellExecuteExW_orig(pei); if (!uri.empty()) { auto result = ResolveUri(uri, pei->hwnd); @@ -1000,55 +1113,25 @@ BOOL WINAPI ShellExecuteExW_hook(SHELLEXECUTEINFOW* pei) { return ShellExecuteExW_orig(pei); } -// Hook: ShellExecuteW HINSTANCE WINAPI ShellExecuteW_hook(HWND hwnd, LPCWSTR op, LPCWSTR file, LPCWSTR params, LPCWSTR dir, INT show) { if (IsChildProcess()) return ShellExecuteW_orig(hwnd, op, file, params, dir, show); - HookGuard guard; - if (guard.IsReentrant()) { - Wh_Log(L"ShellExecuteW: reentrant call, passing through"); - return ShellExecuteW_orig(hwnd, op, file, params, dir, show); - } - + if (guard.IsReentrant()) return ShellExecuteW_orig(hwnd, op, file, params, dir, show); if (!g_settings.enableRedirects) return ShellExecuteW_orig(hwnd, op, file, params, dir, show); if (IsControlSystemParams(file, params)) { - Wh_Log(L"ShellExecuteW: intercepted control system"); LaunchTarget(g_isWin11 ? L"sysdm.cpl" : SYSTEM_PROPS_CLSID); return SHELL_EXECUTE_SUCCESS; } - + std::wstring uri; - if (IsMsSettings(file)) - uri = NormalizeUri(file); - else if (IsMsSettings(params)) - uri = NormalizeUri(params); - else if (IsShellClsid(file)) - uri = ToLower(file); - else if (IsShellClsid(params)) - uri = ToLower(params); - - // Audio URI handling - if (!uri.empty() && (uri.find(L"ms-settings:sound") == 0 || - uri.find(L"ms-settings:audio") == 0 || - uri.find(L"ms-settings:apps-volume") == 0 || - uri.find(L"ms-settings:sound-devices") == 0 || - uri.find(L"ms-settings:sound-output") == 0 || - uri.find(L"ms-settings:sound-input") == 0)) { - - if (OpenSoundPanel()) { - return SHELL_EXECUTE_SUCCESS; - } - } + if (IsMsSettings(file)) uri = NormalizeUri(file); + else if (IsMsSettings(params)) uri = NormalizeUri(params); + else if (IsShellClsid(file)) uri = ToLower(file); + else if (IsShellClsid(params)) uri = ToLower(params); - // Display URI handling - questo intercetta anche i clic dal menu contestuale del desktop - if (!uri.empty() && (uri.find(L"ms-settings:display") == 0 || - uri.find(L"ms-settings:screenrotation") == 0 || - uri.find(L"ms-settings:graphics-settings") == 0)) { - Wh_Log(L"ShellExecuteW: intercepted display settings, launching classic Display Properties"); - LaunchTarget(L"rundll32.exe display.dll,ShowAdapterSettings 0"); - return SHELL_EXECUTE_SUCCESS; - } + if (uri == L"ms-settings:taskbar") + return ShellExecuteW_orig(hwnd, op, file, params, dir, show); if (!uri.empty()) { auto result = ResolveUri(uri, hwnd); @@ -1060,153 +1143,169 @@ HINSTANCE WINAPI ShellExecuteW_hook(HWND hwnd, LPCWSTR op, LPCWSTR file, LPCWSTR return ShellExecuteW_orig(hwnd, op, file, params, dir, show); } -// Hook: CreateProcessW BOOL WINAPI CreateProcessW_hook(LPCWSTR lpApplicationName, LPWSTR lpCommandLine, LPSECURITY_ATTRIBUTES lpProcessAttributes, LPSECURITY_ATTRIBUTES lpThreadAttributes, BOOL bInheritHandles, DWORD dwCreationFlags, LPVOID lpEnvironment, LPCWSTR lpCurrentDirectory, LPSTARTUPINFOW lpStartupInfo, LPPROCESS_INFORMATION lpProcessInformation) { - if (IsChildProcess()) { - return CreateProcessW_orig(lpApplicationName, lpCommandLine, lpProcessAttributes, lpThreadAttributes, - bInheritHandles, dwCreationFlags, lpEnvironment, lpCurrentDirectory, - lpStartupInfo, lpProcessInformation); - } - + if (IsChildProcess()) return CreateProcessW_orig(lpApplicationName, lpCommandLine, lpProcessAttributes, + lpThreadAttributes, bInheritHandles, dwCreationFlags, lpEnvironment, lpCurrentDirectory, + lpStartupInfo, lpProcessInformation); HookGuard guard; - if (guard.IsReentrant()) { - return CreateProcessW_orig(lpApplicationName, lpCommandLine, lpProcessAttributes, lpThreadAttributes, - bInheritHandles, dwCreationFlags, lpEnvironment, lpCurrentDirectory, - lpStartupInfo, lpProcessInformation); - } - - if (!g_settings.enableRedirects || g_settings.uiOnlyRedirects) { - return CreateProcessW_orig(lpApplicationName, lpCommandLine, lpProcessAttributes, lpThreadAttributes, - bInheritHandles, dwCreationFlags, lpEnvironment, lpCurrentDirectory, - lpStartupInfo, lpProcessInformation); - } - - std::wstring cmdLine = lpCommandLine ? std::wstring(lpCommandLine) : L""; - - if (IsControlSystemCommand(cmdLine)) { - Wh_Log(L"CreateProcessW: intercepted 'control system': %s", lpCommandLine); - LaunchTarget(g_isWin11 ? L"sysdm.cpl" : SYSTEM_PROPS_CLSID); - if (lpProcessInformation) ZeroMemory(lpProcessInformation, sizeof(PROCESS_INFORMATION)); - SetLastError(ERROR_SUCCESS); - return TRUE; + if (guard.IsReentrant()) return CreateProcessW_orig(lpApplicationName, lpCommandLine, lpProcessAttributes, + lpThreadAttributes, bInheritHandles, dwCreationFlags, lpEnvironment, lpCurrentDirectory, + lpStartupInfo, lpProcessInformation); + if (!g_settings.enableRedirects || g_settings.uiOnlyRedirects) return CreateProcessW_orig(lpApplicationName, + lpCommandLine, lpProcessAttributes, lpThreadAttributes, bInheritHandles, dwCreationFlags, lpEnvironment, + lpCurrentDirectory, lpStartupInfo, lpProcessInformation); + + if (lpCommandLine) { + std::wstring cmdLine(lpCommandLine); + if (IsControlSystemCommand(cmdLine)) { + LaunchTarget(g_isWin11 ? L"sysdm.cpl" : SYSTEM_PROPS_CLSID); + if (lpProcessInformation) ZeroMemory(lpProcessInformation, sizeof(PROCESS_INFORMATION)); + SetLastError(ERROR_SUCCESS); + return TRUE; + } } - - return CreateProcessW_orig(lpApplicationName, lpCommandLine, lpProcessAttributes, lpThreadAttributes, - bInheritHandles, dwCreationFlags, lpEnvironment, lpCurrentDirectory, - lpStartupInfo, lpProcessInformation); + return CreateProcessW_orig(lpApplicationName, lpCommandLine, lpProcessAttributes, lpThreadAttributes, + bInheritHandles, dwCreationFlags, lpEnvironment, lpCurrentDirectory, lpStartupInfo, lpProcessInformation); } -// Hook: IShellDispatch2::ShellExecute -HRESULT WINAPI IShellDispatch2_ShellExecute_hook( - void* pThis, BSTR File, void* vArgs, void* vDir, void* vOperation, void* vShow) -{ - if (IsChildProcess()) { - return IShellDispatch2_ShellExecute_orig(pThis, File, vArgs, vDir, vOperation, vShow); - } - - HookGuard guard; - if (guard.IsReentrant()) { - return IShellDispatch2_ShellExecute_orig(pThis, File, vArgs, vDir, vOperation, vShow); - } +static void InstallImmersiveMenuHooks() { + struct DllHook { + const wchar_t* dll; + ICMH_CAODTM_t* orig; + } targets[] = { + { L"SndVolSSO.dll", &g_icmhOrig_SndVolSSO }, + { L"pnidui.dll", &g_icmhOrig_pnidui }, + }; - if (!g_settings.enableRedirects || !File) { - return IShellDispatch2_ShellExecute_orig(pThis, File, vArgs, vDir, vOperation, vShow); + for (auto& t : targets) { + HMODULE hMod = LoadLibraryExW(t.dll, nullptr, LOAD_LIBRARY_SEARCH_SYSTEM32); + if (!hMod) continue; + + // SndVolSSO.dll, pnidui.dll + WindhawkUtils::SYMBOL_HOOK sndVolSSO_pnidui_hooks[] = { + {{ + L"bool " +#ifdef _WIN64 + L"__cdecl" +#else + L"__stdcall" +#endif + L" ImmersiveContextMenuHelper::CanApplyOwnerDrawToMenu" + L"(struct HMENU__ *,struct HWND__ *)" + }, + (void**)t.orig, + (void*)(ICMH_CAODTM_t)ICMH_CAODTM_hook} + }; + + WindhawkUtils::HookSymbols(hMod, sndVolSSO_pnidui_hooks, 1); } - std::wstring fileStr(File); - std::wstring uri; - - if (IsMsSettings(fileStr.c_str())) - uri = NormalizeUri(fileStr); - else if (IsShellClsid(fileStr.c_str())) - uri = ToLower(fileStr); - - if (!uri.empty()) { - auto result = ResolveUri(uri, nullptr); - if (result.intercept) { - if (!result.target.empty()) LaunchTarget(result.target); - return S_OK; + if (g_isWin11) { + HMODULE hShell32 = GetModuleHandleW(L"shell32.dll"); + if (hShell32) { + // shell32.dll + WindhawkUtils::SYMBOL_HOOK shell32_hooks[] = { + {{ + L"bool " +#ifdef _WIN64 + L"__cdecl" +#else + L"__stdcall" +#endif + L" CDevicesAndPrintersFolder::_HandleContextMenu" + L"(struct HMENU__ *,unsigned int)" + }, + (void**)&g_icmhOrig_Shell32Devices, + (void*)(ICMH_CAODTM_t)ICMH_CAODTM_hook} + }; + + WindhawkUtils::HookSymbols(hShell32, shell32_hooks, 1); } } - - return IShellDispatch2_ShellExecute_orig(pThis, File, vArgs, vDir, vOperation, vShow); } +using CreateWindowExW_t = decltype(&CreateWindowExW); +CreateWindowExW_t CreateWindowExW_Original; -// Windhawk entry points -BOOL Wh_ModInit() { - Wh_Log(L"Redirect Settings to Control Panel v10.0.1"); +HWND WINAPI CreateWindowExW_Hook( + DWORD dwExStyle, LPCWSTR lpClassName, LPCWSTR lpWindowName, + DWORD dwStyle, int X, int Y, int nWidth, int nHeight, + HWND hWndParent, HMENU hMenu, HINSTANCE hInstance, LPVOID lpParam) +{ + HWND hwnd = CreateWindowExW_Original( + dwExStyle, lpClassName, lpWindowName, dwStyle, + X, Y, nWidth, nHeight, hWndParent, hMenu, hInstance, lpParam); - // Load COM dynamically for IShellDispatch2 hook - g_hOle32 = LoadLibraryW(L"ole32.dll"); - if (g_hOle32) { - dyn_CoCreateInstance = (CoCreateInstance_t)GetProcAddress(g_hOle32, "CoCreateInstance"); - if (!dyn_CoCreateInstance) { - Wh_Log(L"Warning: CoCreateInstance not found"); + if (g_settings.redirectSystemTray && hwnd && !g_hTrayToolbar && lpClassName && !IS_INTRESOURCE(lpClassName)) { + if (wcscmp(lpClassName, L"ToolbarWindow32") == 0) { + SetupTraySubclass(); } } + + return hwnd; +} + +BOOL Wh_ModInit() { + Wh_Log(L"Redirect Settings to Control Panel v10.0.20"); DetectWindowsVersion(); LoadSettings(); BuildChildEnvironment(); InitMappings(); - Wh_Log(L"%zu URI mappings loaded", g_mappings.size()); - // Install ShellExecute hooks HMODULE hShell32 = GetModuleHandleW(L"shell32.dll"); if (!hShell32) hShell32 = LoadLibraryW(L"shell32.dll"); - if (!hShell32) { - Wh_Log(L"ERROR: could not load shell32.dll"); - return FALSE; - } + if (!hShell32) return FALSE; FARPROC pExW = GetProcAddress(hShell32, "ShellExecuteExW"); FARPROC pW = GetProcAddress(hShell32, "ShellExecuteW"); - if (!pExW || !pW) { - Wh_Log(L"ERROR: required exports not found in shell32.dll"); - return FALSE; - } + if (!pExW || !pW) return FALSE; - bool ok1 = Wh_SetFunctionHook((void*)pExW, (void*)ShellExecuteExW_hook, (void**)&ShellExecuteExW_orig); - bool ok2 = Wh_SetFunctionHook((void*)pW, (void*)ShellExecuteW_hook, (void**)&ShellExecuteW_orig); - Wh_Log(L"ShellExecuteExW hook=%d ShellExecuteW hook=%d", ok1, ok2); - - if (!ok1 && !ok2) { - Wh_Log(L"ERROR: failed to install any hooks"); - return FALSE; - } + Wh_SetFunctionHook((void*)pExW, (void*)ShellExecuteExW_hook, (void**)&ShellExecuteExW_orig); + Wh_SetFunctionHook((void*)pW, (void*)ShellExecuteW_hook, (void**)&ShellExecuteW_orig); - // Install CreateProcessW hook HMODULE hKernel32 = GetModuleHandleW(L"kernel32.dll"); if (!hKernel32) hKernel32 = LoadLibraryW(L"kernel32.dll"); if (hKernel32) { FARPROC pCPW = GetProcAddress(hKernel32, "CreateProcessW"); - if (pCPW) { - bool ok3 = Wh_SetFunctionHook((void*)pCPW, (void*)CreateProcessW_hook, (void**)&CreateProcessW_orig); - Wh_Log(L"CreateProcessW hook=%d", ok3); - } + if (pCPW) Wh_SetFunctionHook((void*)pCPW, (void*)CreateProcessW_hook, (void**)&CreateProcessW_orig); + } + + Wh_SetFunctionHook((void*)CreateWindowExW, (void*)CreateWindowExW_Hook, (void**)&CreateWindowExW_Original); + if (g_settings.redirectSystemTray) { + SetupTraySubclass(); } - Wh_Log(L"Ready — EnableRedirects=%d UIOnly=%d SmartPers=always_active Fallback=%d Win11Compat=%d MaxLaunches=%d", - (int)g_settings.enableRedirects, (int)g_settings.uiOnlyRedirects, - g_settings.fallbackMode, (int)g_settings.win11CompatibilityMode, g_settings.maxLaunchesPerUri); + InstallImmersiveMenuHooks(); + + HMODULE hUser32 = GetModuleHandleW(L"user32.dll"); + if (!hUser32) hUser32 = LoadLibraryW(L"user32.dll"); + if (hUser32) { + void* pTrackPopupMenuEx = (void*)GetProcAddress(hUser32, "TrackPopupMenuEx"); + if (pTrackPopupMenuEx) { + Wh_SetFunctionHook(pTrackPopupMenuEx, (void*)TrackPopupMenuEx_Hook, (void**)&g_origTrackPopupMenuEx); + } + } return TRUE; } void Wh_ModUninit() { - if (g_hOle32) { - FreeLibrary(g_hOle32); - g_hOle32 = nullptr; - } - Wh_Log(L"Redirect Settings to Control Panel v10.0.1 unloaded."); + RemoveTraySubclass(); } void Wh_ModSettingsChanged() { - Wh_Log(L"Settings changed, reloading"); + RemoveTraySubclass(); + g_sndVolSSOBase = nullptr; + g_sndVolSSOEnd = nullptr; + g_pniduiBase = nullptr; + g_pniduiEnd = nullptr; LoadSettings(); InitMappings(); + if (g_settings.redirectSystemTray) { + SetupTraySubclass(); + } }