Skip to content

Redirect Settings to Control Panel enhancement update#4727

Merged
m417z merged 25 commits into
ramensoftware:mainfrom
babamohammed2022:patch-8
Jul 17, 2026
Merged

Redirect Settings to Control Panel enhancement update#4727
m417z merged 25 commits into
ramensoftware:mainfrom
babamohammed2022:patch-8

Conversation

@babamohammed2022

@babamohammed2022 babamohammed2022 commented Jul 8, 2026

Copy link
Copy Markdown
Contributor

Changelog

If this pull request updates an existing mod, describe the changes below:

  • Made system tray redirect more robust in some edge cases
  • Enhanced Windows 11 compatibility
  • Fixed menu items appearing blank in system tray context menus
  • Enhanced stability
  • Added Legacy Name Mapping Fix

Mod authorship

If this pull request introduces a new mod, please complete the section below.

This mod was created by:

    • The submitter, without AI assistance
    • The submitter, with AI assistance
    • Claude
    • ChatGPT
    • Gemini
    • Another AI (please specify):
    • Other (please specify):

Please select the options that best apply. Your selection does not affect the acceptance criteria, but it helps reviewers understand the context of the code and provide relevant feedback.

@Anixx

Anixx commented Jul 8, 2026

Copy link
Copy Markdown
Contributor

After Explorer's restart, the Network redirect still stops working.

@babamohammed2022

babamohammed2022 commented Jul 8, 2026

Copy link
Copy Markdown
Contributor Author

After Explorer's restart, the Network redirect still stops working.

Does the rest of the mod (redirects, sound context menu redirect) work? So that I can understand what is fixed and what isn't

@Anixx

Anixx commented Jul 8, 2026

Copy link
Copy Markdown
Contributor

The other, you mean sound? Works for now.

@Anixx

Anixx commented Jul 8, 2026

Copy link
Copy Markdown
Contributor

After Explorer's restart the network redirect still does not work.

@Anixx

Anixx commented Jul 8, 2026

Copy link
Copy Markdown
Contributor

You do not need fallback, you need to make the existing method to not depend on whether the mod was activated when explorer was running or explorer was started later.

In the latest version the redirect still does not work after explorer's restart.

@babamohammed2022

babamohammed2022 commented Jul 8, 2026

Copy link
Copy Markdown
Contributor Author

You do not need fallback, you need to make the existing method to not depend on whether the mod was activated when explorer was running or explorer was started later.

In the latest version the redirect still does not work after explorer's restart.

OK, thanks for the suggestion. I will take more time to study a more fitting solution along with fixing the validation issue. Since it works on my setup (Windows 11 24H2 with ExplorerPatcher) it thought that it was fixed.

@babamohammed2022

Copy link
Copy Markdown
Contributor Author

You do not need fallback, you need to make the existing method to not depend on whether the mod was activated when explorer was running or explorer was started later.

In the latest version the redirect still does not work after explorer's restart.

Let me know if it works now. I've tested on Windows 11 24H2 with ExplorerPatcher (as mentioned above) and it seems to work.
LOGS:
DbgViewMini v1.0.4
Listening for OutputDebugString messages...
09:44:03.534 9492 explorer.exe [WH] [local@settings-to-control-panel] [1527:Wh_ModInit]: Redirect Settings to Control Panel v10.0.23
09:44:03.539 9492 explorer.exe [WH] [local@settings-to-control-panel] [1339:TryInstallPniduiHook]: [PNIDUI-HOOK] pnidui.dll loaded at 0x00007FFAA90D0000
09:44:03.539 9492 explorer.exe [WH] [local@settings-to-control-panel] [1357:TryInstallPniduiHook]: [PNIDUI-HOOK] Successfully installed pnidui.dll hook
09:44:03.797 9492 explorer.exe [WH] [local@settings-to-control-panel] [1499:InstallSyscallFallback]: [SYSCALL-HOOK] NtUserTrackPopupMenuEx hooked successfully
09:44:06.745 9492 explorer.exe [WH] [local@settings-to-control-panel] [797:NtUserTrackPopupMenuEx_Hook]: [SYSCALL-HOOK] User selected target item, redirecting
09:44:07.018 7500 explorer.exe [WH] [local@settings-to-control-panel] [1527:Wh_ModInit]: Redirect Settings to Control Panel v10.0.23
09:44:07.020 7500 explorer.exe [WH] [local@settings-to-control-panel] [1334:TryInstallPniduiHook]: [PNIDUI-HOOK] pnidui.dll not loaded yet
09:44:07.020 7500 explorer.exe [WH] [local@settings-to-control-panel] [1446:InstallImmersiveMenuHooks]: [PNIDUI-HOOK] Retry thread created
09:44:07.020 7500 explorer.exe [WH] [local@settings-to-control-panel] [1373:PniduiRetryThread]: [PNIDUI-HOOK] Retry thread started - waiting for pnidui.dll to load
09:44:07.242 7500 explorer.exe [WH] [local@settings-to-control-panel] [1499:InstallSyscallFallback]: [SYSCALL-HOOK] NtUserTrackPopupMenuEx hooked successfully
09:44:07.348 10792 explorer.exe [WH] [local@settings-to-control-panel] [1527:Wh_ModInit]: Redirect Settings to Control Panel v10.0.23
09:44:07.351 10792 explorer.exe [WH] [local@settings-to-control-panel] [1334:TryInstallPniduiHook]: [PNIDUI-HOOK] pnidui.dll not loaded yet
09:44:07.351 10792 explorer.exe [WH] [local@settings-to-control-panel] [1446:InstallImmersiveMenuHooks]: [PNIDUI-HOOK] Retry thread created
09:44:07.351 10792 explorer.exe [WH] [local@settings-to-control-panel] [1373:PniduiRetryThread]: [PNIDUI-HOOK] Retry thread started - waiting for pnidui.dll to load
09:44:07.552 10792 explorer.exe [WH] [local@settings-to-control-panel] [1499:InstallSyscallFallback]: [SYSCALL-HOOK] NtUserTrackPopupMenuEx hooked successfully
09:44:20.681 9664 explorer.exe [WH] [local@settings-to-control-panel] [1527:Wh_ModInit]: Redirect Settings to Control Panel v10.0.23
09:44:20.681 9664 explorer.exe [WH] [local@settings-to-control-panel] [1334:TryInstallPniduiHook]: [PNIDUI-HOOK] pnidui.dll not loaded yet
09:44:20.681 9664 explorer.exe [WH] [local@settings-to-control-panel] [1446:InstallImmersiveMenuHooks]: [PNIDUI-HOOK] Retry thread created
09:44:20.681 9664 explorer.exe [WH] [local@settings-to-control-panel] [1373:PniduiRetryThread]: [PNIDUI-HOOK] Retry thread started - waiting for pnidui.dll to load
09:44:20.928 9664 explorer.exe [WH] [local@settings-to-control-panel] [1499:InstallSyscallFallback]: [SYSCALL-HOOK] NtUserTrackPopupMenuEx hooked successfully
09:44:51.227 9664 explorer.exe [WH] [local@settings-to-control-panel] [1390:PniduiRetryThread]: [PNIDUI-HOOK] pnidui.dll not loaded after timeout, giving up
09:44:57.144 9664 explorer.exe [WH] [local@settings-to-control-panel] [797:NtUserTrackPopupMenuEx_Hook]: [SYSCALL-HOOK] User selected target item, redirecting
09:44:57.461 9644 explorer.exe [WH] [local@settings-to-control-panel] [1527:Wh_ModInit]: Redirect Settings to Control Panel v10.0.23
09:44:57.464 9644 explorer.exe [WH] [local@settings-to-control-panel] [1334:TryInstallPniduiHook]: [PNIDUI-HOOK] pnidui.dll not loaded yet
09:44:57.465 9644 explorer.exe [WH] [local@settings-to-control-panel] [1446:InstallImmersiveMenuHooks]: [PNIDUI-HOOK] Retry thread created
09:44:57.465 9644 explorer.exe [WH] [local@settings-to-control-panel] [1373:PniduiRetryThread]: [PNIDUI-HOOK] Retry thread started - waiting for pnidui.dll to load
09:44:57.678 9644 explorer.exe [WH] [local@settings-to-control-panel] [1499:InstallSyscallFallback]: [SYSCALL-HOOK] NtUserTrackPopupMenuEx hooked successfully
09:44:57.786 6048 explorer.exe [WH] [local@settings-to-control-panel] [1527:Wh_ModInit]: Redirect Settings to Control Panel v10.0.23
09:44:57.789 6048 explorer.exe [WH] [local@settings-to-control-panel] [1334:TryInstallPniduiHook]: [PNIDUI-HOOK] pnidui.dll not loaded yet
09:44:57.789 6048 explorer.exe [WH] [local@settings-to-control-panel] [1446:InstallImmersiveMenuHooks]: [PNIDUI-HOOK] Retry thread created
09:44:57.791 6048 explorer.exe [WH] [local@settings-to-control-panel] [1373:PniduiRetryThread]: [PNIDUI-HOOK] Retry thread started - waiting for pnidui.dll to load
09:44:57.996 6048 explorer.exe [WH] [local@settings-to-control-panel] [1499:InstallSyscallFallback]: [SYSCALL-HOOK] NtUserTrackPopupMenuEx hooked successfully
09:45:28.371 6048 explorer.exe [WH] [local@settings-to-control-panel] [1390:PniduiRetryThread]: [PNIDUI-HOOK] pnidui.dll not loaded after timeout, giving up
09:47:09.175 9664 explorer.exe [WH] [local@settings-to-control-panel] [797:NtUserTrackPopupMenuEx_Hook]: [SYSCALL-HOOK] User selected target item, redirecting
09:47:09.301 9644 explorer.exe [WH] [local@settings-to-control-panel] [1527:Wh_ModInit]: Redirect Settings to Control Panel v10.0.23
09:47:09.306 9644 explorer.exe [WH] [local@settings-to-control-panel] [1334:TryInstallPniduiHook]: [PNIDUI-HOOK] pnidui.dll not loaded yet
09:47:09.306 9644 explorer.exe [WH] [local@settings-to-control-panel] [1446:InstallImmersiveMenuHooks]: [PNIDUI-HOOK] Retry thread created
09:47:09.307 9644 explorer.exe [WH] [local@settings-to-control-panel] [1373:PniduiRetryThread]: [PNIDUI-HOOK] Retry thread started - waiting for pnidui.dll to load
09:47:09.522 9644 explorer.exe [WH] [local@settings-to-control-panel] [1499:InstallSyscallFallback]: [SYSCALL-HOOK] NtUserTrackPopupMenuEx hooked successfully
09:47:09.624 11076 explorer.exe [WH] [local@settings-to-control-panel] [1527:Wh_ModInit]: Redirect Settings to Control Panel v10.0.23
09:47:09.629 11076 explorer.exe [WH] [local@settings-to-control-panel] [1334:TryInstallPniduiHook]: [PNIDUI-HOOK] pnidui.dll not loaded yet
09:47:09.629 11076 explorer.exe [WH] [local@settings-to-control-panel] [1446:InstallImmersiveMenuHooks]: [PNIDUI-HOOK] Retry thread created
09:47:09.629 11076 explorer.exe [WH] [local@settings-to-control-panel] [1373:PniduiRetryThread]: [PNIDUI-HOOK] Retry thread started - waiting for pnidui.dll to load
09:47:09.855 11076 explorer.exe [WH] [local@settings-to-control-panel] [1499:InstallSyscallFallback]: [SYSCALL-HOOK] NtUserTrackPopupMenuEx hooked successfully
09:47:35.118 9664 explorer.exe [WH] [local@settings-to-control-panel] [1574:Wh_ModUninit]: [PNIDUI-HOOK] Stopping retry thread...
09:47:35.195 9664 explorer.exe [WH] [local@settings-to-control-panel] [1527:Wh_ModInit]: Redirect Settings to Control Panel v10.0.24
09:47:35.197 9664 explorer.exe [WH] [local@settings-to-control-panel] [1339:TryInstallPniduiHook]: [PNIDUI-HOOK] pnidui.dll loaded at 0x00007FFAA9DC0000
09:47:35.197 9664 explorer.exe [WH] [local@settings-to-control-panel] [1357:TryInstallPniduiHook]: [PNIDUI-HOOK] Successfully installed pnidui.dll hook
09:47:35.347 11076 explorer.exe [WH] [local@settings-to-control-panel] [1574:Wh_ModUninit]: [PNIDUI-HOOK] Stopping retry thread...
09:47:35.473 9664 explorer.exe [WH] [local@settings-to-control-panel] [1499:InstallSyscallFallback]: [SYSCALL-HOOK] NtUserTrackPopupMenuEx hooked successfully
09:47:35.613 11076 explorer.exe [WH] [local@settings-to-control-panel] [1390:PniduiRetryThread]: [PNIDUI-HOOK] pnidui.dll not loaded after timeout, giving up
09:47:35.746 11076 explorer.exe [WH] [local@settings-to-control-panel] [1527:Wh_ModInit]: Redirect Settings to Control Panel v10.0.24
09:47:35.749 11076 explorer.exe [WH] [local@settings-to-control-panel] [1334:TryInstallPniduiHook]: [PNIDUI-HOOK] pnidui.dll not loaded yet
09:47:35.752 11076 explorer.exe [WH] [local@settings-to-control-panel] [1446:InstallImmersiveMenuHooks]: [PNIDUI-HOOK] Retry thread created
09:47:35.755 11076 explorer.exe [WH] [local@settings-to-control-panel] [1373:PniduiRetryThread]: [PNIDUI-HOOK] Retry thread started - waiting for pnidui.dll to load
09:47:36.674 11076 explorer.exe [WH] [local@settings-to-control-panel] [1499:InstallSyscallFallback]: [SYSCALL-HOOK] NtUserTrackPopupMenuEx hooked successfully
09:48:06.161 11076 explorer.exe [WH] [local@settings-to-control-panel] [1390:PniduiRetryThread]: [PNIDUI-HOOK] pnidui.dll not loaded after timeout, giving up
09:50:03.508 9664 explorer.exe [WH] [local@settings-to-control-panel] [1524:Wh_ModInit]: Redirect Settings to Control Panel v10.0.24
09:50:03.595 9664 explorer.exe [WH] [local@settings-to-control-panel] [1339:TryInstallPniduiHook]: [PNIDUI-HOOK] pnidui.dll loaded at 0x00007FFAA9DC0000
09:50:03.597 9664 explorer.exe [WH] [local@settings-to-control-panel] [1356:TryInstallPniduiHook]: [PNIDUI-HOOK] Successfully installed pnidui.dll hook
09:50:03.908 9664 explorer.exe [WH] [local@settings-to-control-panel] [1496:InstallSyscallFallback]: [SYSCALL-HOOK] NtUserTrackPopupMenuEx hooked successfully

@Anixx

Anixx commented Jul 9, 2026

Copy link
Copy Markdown
Contributor

Network redirect does not work after Explorer's restart.

@babamohammed2022

babamohammed2022 commented Jul 9, 2026

Copy link
Copy Markdown
Contributor Author

Network redirect does not work after Explorer's restart.

In this case since it works on my setup I need the logs (if present) to see what happens because perhaps something should be logged at least, if possible please send them so that I avoid random fix attempts

@Anixx

Anixx commented Jul 9, 2026

Copy link
Copy Markdown
Contributor

There are no logs when clicking on the network icon after Explorer's restart. Zero. Your program does not know about the click.

@Anixx

Anixx commented Jul 9, 2026

Copy link
Copy Markdown
Contributor

Here is the only log after restart:
01:09:17.340 11980 explorer.exe [WH] [local@settings-to-control-panel] [1338:TryInstallPniduiHook]: [PNIDUI-HOOK] pnidui.dll loaded at 0x00007FF8B1B60000
01:09:17.340 11980 explorer.exe [WH] [local@settings-to-control-panel] [1356:TryInstallPniduiHook]: [PNIDUI-HOOK] Successfully installed pnidui.dll hook
01:09:17.825 11980 explorer.exe [WH] [local@settings-to-control-panel] [1498:InstallSyscallFallback]: [SYSCALL-HOOK] NtUserTrackPopupMenuEx hooked successfully

@babamohammed2022

Copy link
Copy Markdown
Contributor Author

Here is the only log after restart: 01:09:17.340 11980 explorer.exe [WH] [local@settings-to-control-panel] [1338:TryInstallPniduiHook]: [PNIDUI-HOOK] pnidui.dll loaded at 0x00007FF8B1B60000 01:09:17.340 11980 explorer.exe [WH] [local@settings-to-control-panel] [1356:TryInstallPniduiHook]: [PNIDUI-HOOK] Successfully installed pnidui.dll hook 01:09:17.825 11980 explorer.exe [WH] [local@settings-to-control-panel] [1498:InstallSyscallFallback]: [SYSCALL-HOOK] NtUserTrackPopupMenuEx hooked successfully

OK thank you. This confirms that it is partially installed and the problem is specific to the context menu option. I will try to fix this specific part then

@babamohammed2022

Copy link
Copy Markdown
Contributor Author

Here is the only log after restart: 01:09:17.340 11980 explorer.exe [WH] [local@settings-to-control-panel] [1338:TryInstallPniduiHook]: [PNIDUI-HOOK] pnidui.dll loaded at 0x00007FF8B1B60000 01:09:17.340 11980 explorer.exe [WH] [local@settings-to-control-panel] [1356:TryInstallPniduiHook]: [PNIDUI-HOOK] Successfully installed pnidui.dll hook 01:09:17.825 11980 explorer.exe [WH] [local@settings-to-control-panel] [1498:InstallSyscallFallback]: [SYSCALL-HOOK] NtUserTrackPopupMenuEx hooked successfully

I've added a function that detects the system tray after explorer's restart which should make it more robust now. Please let me know if this fixes the issue or if it's the same

@babamohammed2022

Copy link
Copy Markdown
Contributor Author

@Anixx I have 2 questions:

  • If you can, please tell me if it works now as I've added a specific check for explorer restart
  • Am I allowed to integrate parts of your code from this mod that you created (https://windhawk.net/mods/restore-classic-cpls) to make my mod complete (by adding more parts too) with credits?

@Anixx

Anixx commented Jul 10, 2026

Copy link
Copy Markdown
Contributor

Nothing changed. After Explorer's restart the network redirect does not work.

You can include whatever parts you want, it is under free license.

@babamohammed2022

babamohammed2022 commented Jul 10, 2026

Copy link
Copy Markdown
Contributor Author

Nothing changed. After Explorer's restart the network redirect does not work.

You can include whatever parts you want, it is under free license.

Thanks for the permission. However, since I've tried multiple approaches to make it more robust but without result on your setup (Windows 10 20H1 taskbar on Windows 11 23H2, according to WinClassic forums if I am not wrong), I've noted it as a limitation for now until I find a solution to the problem. In the meantime, if you have any other reports feel free to tell them

@Anixx

Anixx commented Jul 10, 2026

Copy link
Copy Markdown
Contributor

Windows 10 20H1 taskbar on Windows 11 23H2

Where did u get it? I am using the taskbar from 23H2. If you mod can do something when it gets enabled, it should be able to do the same after it injects Explorer.

@Anixx

Anixx commented Jul 10, 2026

Copy link
Copy Markdown
Contributor

I don't think it is a good idea to just double functionality of the mods and include the functions of another unrelated mod. Otherwise you just can combine any mods you like to get a huge giant mod of your own.
The control panels elements functionality is maintained by me and can be fixed and expanded in the future. It is generally a bad idea to include into a mod something about which you do not understand how it works.
Plus, your mod would conflict with my mod if people would like to use them together.
Plus, your mod is already so huge that the AIs struggle to analyze it as a whole. You overload the context.

Why you don't want just install my mod? If you have issues, I can fix them.

@babamohammed2022

babamohammed2022 commented Jul 14, 2026

Copy link
Copy Markdown
Contributor Author

Yes, version // @Version 10.0.30

The sound redirect seems to work reliably, sound redirect stops working after Explorer's restart.

I'm sorry, but it seems like the sound redirect phrasing has been repeated multiple times. Could you clarify? While I acknowledge that you're using the latest version, I can't understand what is working and what isn't.

@Anixx

Anixx commented Jul 14, 2026

Copy link
Copy Markdown
Contributor

sound redirect works, network redirect stops working.

@babamohammed2022

Copy link
Copy Markdown
Contributor Author

sound redirect works, network redirect stops working.

I understand, in this case I think that this is the best that could be done for now until I find a better solution for another future update. Thanks for testing and if you find any other issues let me know

@babamohammed2022

Copy link
Copy Markdown
Contributor Author

@m417z Hi, if possible could you let me know if the code requires other fixes? So that I can improve it if necessary

@m417z

m417z commented Jul 14, 2026

Copy link
Copy Markdown
Member

Submission review

Note: This review was done by Claude, and then refined manually. Due to the amount of submissions, doing a fully manual review for each pull request is no longer feasible. Thank you for understanding.

Please address the following issues. The items in the collapsed sections are optional, so it's your call whether to address them.


1. The two new features are gated behind settings that don't exist, so they never run.
LoadSettings() reads two new keys:

g_settings.comActivationRedirect = Wh_GetIntSetting(L"ComActivationRedirect") != 0;   // line 229
g_settings.legacyNameMappingFix  = Wh_GetIntSetting(L"LegacyNameMappingFix")  != 0;   // line 230

but neither ComActivationRedirect nor LegacyNameMappingFix is declared in the ==WindhawkModSettings== block. Wh_GetIntSetting returns 0 for an undeclared key, so both flags are always false, and InstallAAMHook() / InstallLegacyNameHook() are never called (guards at lines 1847, 1851, 1924, 1928). That means both new additions in this PR — the COM-activation redirect and the _MapLegacyName fix ("menu items appearing blank") — are dead code as shipped. CI validates the block's structure but not that read keys are declared, so this passes validation silently. Fix: add the two settings to the block (default true), or drop the gating.

2. The COM vtable patch is never undone → crash after the mod is disabled/updated.
InstallAAMHook() overwrites the ActivateApplication slot of the process-wide IApplicationActivationManager vtable with VirtualProtect (line 1319). This is a manual memory patch, not a Windhawk hook, so Windhawk does not remove it on unload — and Wh_ModUninit never restores it. Once this feature is actually enabled (item 1), disabling or updating the mod leaves explorer's AAM vtable pointing at AAM_ActivateApplication_hook inside the now-unloaded mod image; the next ActivateApplication call (Start-menu search, app launches, etc.) jumps into freed memory → crash. It also violates Windhawk's reversibility principle — the patch persists after the mod is gone. Additional problems with this block: it patches a shared vtable (affects every AAM caller in explorer), and CoCreateInstance runs on the Windhawk callback thread, which likely isn't COM-initialized (CO_E_NOTINITIALIZED). Recommendation: drop the experimental COM interception. If you keep it, restore the original slot in Wh_ModUninit (save it under g_aamHookMutex, and re-VirtualProtect).

If you must keep it, prefer using SetFunctionHook instead of using vtable patching, which might conflict with other mods.

Also IsBadReadPtr (line 1301) is unsafe/unreliable (why); drop it.

3. TerminateThread on unload.
Wh_ModUninit calls TerminateThread on the watchdog and pnidui-retry threads when they don't stop within 3s (lines 1883, 1894). TerminateThread is unsafe — it can leave a lock held (g_pniduiHookMutex) or abort mid-SendMessage (the subclass calls go through SetWindowSubclassFromAnyThread), corrupting state. The threads only Sleep + poll a volatile bool, and the watchdog's slow phase sleeps for 3000ms, so hitting the 3s wait timeout is entirely plausible. Fix: have each thread wait on a manual-reset stop event (WaitForSingleObject(hStop, interval)) instead of Sleep, signal it in uninit, and remove the TerminateThread fallback.

4. Re-hooking the same modules repeatedly.
InstallImmersiveMenuHooks() (line 1620) calls HookSymbols for SndVolSSO.dll (and shell32.dll on Win11) with no "already installed" guard, and it's re-invoked from Wh_ModSettingsChanged (line 1920) and from ReinitializeTrayRedirect on every tray recreation (line 1757). Per Windhawk guidance, calling HookSymbols more than once for the same module invalidates its symbol cache and forces a slow re-resolution each time. Resolve each module's hooks once and guard against re-hooking, the way you already do for pnidui via g_pniduiHookInstalled.

Optional improvements

Minor polish — none of this affects users, so it's your call.

  • LoadLibraryW(L"win32u.dll") (line 1692) loads a non-KnownDLL by bare name; prefer LoadLibraryExW(L"win32u.dll", nullptr, LOAD_LIBRARY_SEARCH_SYSTEM32). Low risk here since the scope is explorer.exe and win32u is effectively always already loaded (the GetModuleHandleW above it succeeds), so this is just hardening.
  • Italian comments are mixed into otherwise-English code — e.g. "// Hook a livello syscall per catturare menu che bypassano TrackPopupMenuEx" (line 692), "// Se non è un menu della system tray" (694), "// NUOVO: Rilevamento shell32.dll per Win11 23H2" (586). Translate them for maintainability.
  • NtUserTrackPopupMenuEx_Hook is installed with raw Wh_SetFunctionHook (line 1705); WindhawkUtils::SetFunctionHook is the type-safe form used elsewhere.

Functionality notes

Non-critical observations about the feature behavior itself.

  • NtUserTrackPopupMenuEx_Hook (lines 693–823) is a ~130-line near-verbatim copy of TrackPopupMenuEx_Hook. For any menu that goes through user32 TrackPopupMenuEx, the syscall hook is redundant — the HookGuard reentrancy check makes it a straight pass-through. It only adds coverage for callers that reach NtUserTrackPopupMenuEx without going through user32 TrackPopupMenuEx (e.g. TrackPopupMenu non-Ex). Given it's a broad hook firing on every popup menu in explorer, and the duplicated maintenance surface, it's worth confirming it's actually needed; if so, factor the shared detection/redirect logic into one helper instead of duplicating it. (Flagging in part because this reads like AI over-engineering — the PR discloses AI assistance.)
  • COpenControlPanel__MapLegacyName_hook (line 1329) unconditionally reports "not changed" for every legacy-name lookup process-wide. Verify this doesn't affect unrelated Control Panel name resolution, since it suppresses the mapping globally rather than only for the URIs you care about.
  • The tray watchdog is a permanent polling thread (500ms for the first 30s, then every 3s, forever). Within a single explorer process there isn't a clean event for "Shell_TrayWnd was recreated", so polling may be unavoidable here — noting as FYI. (A full explorer restart spawns a new process and re-runs Wh_ModInit fresh, so that path doesn't rely on the watchdog.)

@babamohammed2022

babamohammed2022 commented Jul 14, 2026

Copy link
Copy Markdown
Contributor Author

Submission review

Note: This review was done by Claude, and then refined manually. Due to the amount of submissions, doing a fully manual review for each pull request is no longer feasible. Thank you for understanding.

Please address the following issues. The items in the collapsed sections are optional, so it's your call whether to address them.

1. The two new features are gated behind settings that don't exist, so they never run. LoadSettings() reads two new keys:

g_settings.comActivationRedirect = Wh_GetIntSetting(L"ComActivationRedirect") != 0;   // line 229
g_settings.legacyNameMappingFix  = Wh_GetIntSetting(L"LegacyNameMappingFix")  != 0;   // line 230

but neither ComActivationRedirect nor LegacyNameMappingFix is declared in the ==WindhawkModSettings== block. Wh_GetIntSetting returns 0 for an undeclared key, so both flags are always false, and InstallAAMHook() / InstallLegacyNameHook() are never called (guards at lines 1847, 1851, 1924, 1928). That means both new additions in this PR — the COM-activation redirect and the _MapLegacyName fix ("menu items appearing blank") — are dead code as shipped. CI validates the block's structure but not that read keys are declared, so this passes validation silently. Fix: add the two settings to the block (default true), or drop the gating.

2. The COM vtable patch is never undone → crash after the mod is disabled/updated. InstallAAMHook() overwrites the ActivateApplication slot of the process-wide IApplicationActivationManager vtable with VirtualProtect (line 1319). This is a manual memory patch, not a Windhawk hook, so Windhawk does not remove it on unload — and Wh_ModUninit never restores it. Once this feature is actually enabled (item 1), disabling or updating the mod leaves explorer's AAM vtable pointing at AAM_ActivateApplication_hook inside the now-unloaded mod image; the next ActivateApplication call (Start-menu search, app launches, etc.) jumps into freed memory → crash. It also violates Windhawk's reversibility principle — the patch persists after the mod is gone. Additional problems with this block: it patches a shared vtable (affects every AAM caller in explorer), and CoCreateInstance runs on the Windhawk callback thread, which likely isn't COM-initialized (CO_E_NOTINITIALIZED). Recommendation: drop the experimental COM interception. If you keep it, restore the original slot in Wh_ModUninit (save it under g_aamHookMutex, and re-VirtualProtect).

If you must keep it, prefer using SetFunctionHook instead of using vtable patching, which might conflict with other mods.

Also IsBadReadPtr (line 1301) is unsafe/unreliable (why); drop it.

3. TerminateThread on unload. Wh_ModUninit calls TerminateThread on the watchdog and pnidui-retry threads when they don't stop within 3s (lines 1883, 1894). TerminateThread is unsafe — it can leave a lock held (g_pniduiHookMutex) or abort mid-SendMessage (the subclass calls go through SetWindowSubclassFromAnyThread), corrupting state. The threads only Sleep + poll a volatile bool, and the watchdog's slow phase sleeps for 3000ms, so hitting the 3s wait timeout is entirely plausible. Fix: have each thread wait on a manual-reset stop event (WaitForSingleObject(hStop, interval)) instead of Sleep, signal it in uninit, and remove the TerminateThread fallback.

4. Re-hooking the same modules repeatedly. InstallImmersiveMenuHooks() (line 1620) calls HookSymbols for SndVolSSO.dll (and shell32.dll on Win11) with no "already installed" guard, and it's re-invoked from Wh_ModSettingsChanged (line 1920) and from ReinitializeTrayRedirect on every tray recreation (line 1757). Per Windhawk guidance, calling HookSymbols more than once for the same module invalidates its symbol cache and forces a slow re-resolution each time. Resolve each module's hooks once and guard against re-hooking, the way you already do for pnidui via g_pniduiHookInstalled.
Optional improvements

Functionality notes

Thanks for the review. I've tried to address the reported issues and I've updated the README of the mod. Let me know if other fixes are required.

@m417z

m417z commented Jul 15, 2026

Copy link
Copy Markdown
Member

Submission review

Note: This review was done by Claude, and then refined manually. Due to the amount of submissions, doing a fully manual review for each pull request is no longer feasible. Thank you for understanding.

Please address the following issues. The items in the collapsed sections are optional, so it's your call whether to address them.


The main problem is that the central fixes in this update never actually take effect, because the hooks are registered from a background thread with no follow-up apply.

1. The new hooks are registered after Wh_ModInit returns but Wh_ApplyHookOperations() is never called — so they are never applied. Windhawk applies hook operations registered by Wh_SetFunctionHook/HookSymbols automatically once, right after Wh_ModInit returns. Any hook registered later must be applied explicitly with Wh_ApplyHookOperations() (see the wiki / windhawk_api.h).

In this PR, all three of the new/relocated hook installers are called only from PerformBackgroundInit() (which runs on the TraySubclassWatchdogThread after a Sleep(200)) and from Wh_ModSettingsChanged() — both after init has returned:

  • InstallImmersiveMenuHooks() → the ImmersiveContextMenuHelper::CanApplyOwnerDrawToMenu hooks in SndVolSSO.dll / pnidui.dll / shell32.dll (this is what suppresses owner-draw and fixes the blank menu items),
  • InstallAAMHook() → the IApplicationActivationManager::ActivateApplication COM hook,
  • InstallLegacyNameHook() → the COpenControlPanel::_MapLegacyName hook,
  • TryInstallPniduiHook() (also from PniduiRetryThread).

None of these are ever applied, so the "Fixed menu items appearing blank" fix, the COM-activation redirect, and the legacy-name-mapping fix (i.e. essentially the entire changelog) silently do nothing. Note that in the previous version InstallImmersiveMenuHooks() was called directly inside Wh_ModInit, so it was applied — moving it to the background thread is the regression.

Fix: call Wh_ApplyHookOperations() after the batch of registrations on the post-init paths (once per batch — it's documented as slow, so don't call it per-hook). For example at the end of PerformBackgroundInit(), after TryInstallPniduiHook() succeeds in PniduiRetryThread, and in ReinitializeTrayRedirect() after InstallImmersiveMenuHooks(). A cleaner alternative for the late-loading DLLs (pnidui.dll, SndVolSSO.dll) is to hook LoadLibraryExW in kernelbase.dll and apply the module's hooks when it loads, instead of polling with a retry thread. See taskbar-clock-customization.wh.cpp for the "register then Wh_ApplyHookOperations()" pattern.

2. InstallLegacyNameHook() re-resolves shell32.dll symbols on every settings change. Unlike the other installers it has no "already installed" guard, so every Wh_ModSettingsChanged()PerformBackgroundInit() calls HookSymbols(hShell32, …) again. Repeated HookSymbols calls against the same module invalidate the symbol cache and force a slow re-resolution each time. Add a g_legacyNameHookInstalled flag like the others. Related: shell32.dll is currently hooked by two separate HookSymbols calls (_MapLegacyName in InstallLegacyNameHook and CDevicesAndPrintersFolder::_HandleContextMenu in InstallImmersiveMenuHooks) — collect both into a single SYMBOL_HOOK array and resolve them in one HookSymbols call for the module.

Optional improvements

Minor polish — none of this affects users, so it's your call.

  • Dead custom-ID constants. TRAY_CUSTOM_ID_AUDIO / TRAY_CUSTOM_ID_NETWORK / TRAY_CUSTOM_ID_DEVICES are no longer referenced now that CommonTrackPopupMenuEx_Hook compares against originalId instead of swapping in a custom ID. They can be removed.
  • Dead stop flags. g_pniduiRetryStop and g_traySubclassWatchdogStop are written but never read — the threads stop via g_stopEvent. Remove them.
  • Mixed-case CLSID that can never match. The diff changes the g_win11LoopClsids entry …cb05b6477eee} to …cb05B6477EEE}. That set is only ever queried with lowercased keys (IsClsidLoopOnWin11 receives the already-ToLower'd target), so the uppercase entry can never match. Restore it to all-lowercase.
  • Sleep(200) on the settings-changed path. PerformBackgroundInit() starts with Sleep(200). That's fine on the dedicated watchdog thread, but Wh_ModSettingsChanged() also calls PerformBackgroundInit(), so it blocks the settings-changed callback for 200 ms (plus the symbol resolution). Consider skipping the sleep when called from the settings path.
  • Dead branch in ExtractExplorerLaunchUri. It returns a lowercased shell:::… string, but ResolveUri only matches ms-settings: keys, so a shell::: result is never intercepted. The shell::: handling there is effectively unreachable.

Functionality notes

Non-critical observations about the feature behavior itself (assuming issue #1 is fixed so the hooks actually run).

  • selectedId == originalId can misfire when the target item's ID is 0. CommonTrackPopupMenuEx_Hook now forces TPM_RETURNCMD and treats a return value equal to originalId as "the redirect item was chosen". TrackPopupMenuEx with TPM_RETURNCMD returns 0 when the user cancels the menu, so if GetMenuItemID() for the target index ever returns 0 (e.g. a popup/submenu item), a plain cancel would be misread as a selection and trigger a spurious redirect. The previous custom-ID approach (guaranteed non-zero, unique) avoided this. Guarding with originalId != 0 restores that robustness.
  • Return-address / item-count heuristics are fragile. GetReturnAddress() (frame 2 of CaptureStackBackTrace) plus the loosened item-count ranges (<= 10 for audio, 1..20 for network) are inherently build-dependent. It's acceptable as a fallback since the subclass-flag path is primary, but expect it to need maintenance across Windows builds.
  • _MapLegacyName is suppressed process-wide. The legacy-name hook forces every COpenControlPanel::_MapLegacyName call in Explorer to report "no change", not just the ones involved in ms-settings: redirects. Worth confirming it doesn't have side effects on other classic-applet name resolution.
  • The AAM COM hook is default-on and broad. ComActivationRedirect defaults to true, and (once applied) it patches ActivateApplication for all app activations in Explorer on Windows 11 via a hand-rolled vtable layout. Given it's a reverse-engineering-based best-effort path, defaulting it off (opt-in) may be safer until it's proven across builds.

@babamohammed2022

Copy link
Copy Markdown
Contributor Author

Submission review

Note: This review was done by Claude, and then refined manually. Due to the amount of submissions, doing a fully manual review for each pull request is no longer feasible. Thank you for understanding.

Please address the following issues. The items in the collapsed sections are optional, so it's your call whether to address them.

The main problem is that the central fixes in this update never actually take effect, because the hooks are registered from a background thread with no follow-up apply.

1. The new hooks are registered after Wh_ModInit returns but Wh_ApplyHookOperations() is never called — so they are never applied. Windhawk applies hook operations registered by Wh_SetFunctionHook/HookSymbols automatically once, right after Wh_ModInit returns. Any hook registered later must be applied explicitly with Wh_ApplyHookOperations() (see the wiki / windhawk_api.h).

In this PR, all three of the new/relocated hook installers are called only from PerformBackgroundInit() (which runs on the TraySubclassWatchdogThread after a Sleep(200)) and from Wh_ModSettingsChanged() — both after init has returned:

* `InstallImmersiveMenuHooks()` → the `ImmersiveContextMenuHelper::CanApplyOwnerDrawToMenu` hooks in `SndVolSSO.dll` / `pnidui.dll` / `shell32.dll` (this is what suppresses owner-draw and fixes the _blank menu items_),

* `InstallAAMHook()` → the `IApplicationActivationManager::ActivateApplication` COM hook,

* `InstallLegacyNameHook()` → the `COpenControlPanel::_MapLegacyName` hook,

* `TryInstallPniduiHook()` (also from `PniduiRetryThread`).

None of these are ever applied, so the "Fixed menu items appearing blank" fix, the COM-activation redirect, and the legacy-name-mapping fix (i.e. essentially the entire changelog) silently do nothing. Note that in the previous version InstallImmersiveMenuHooks() was called directly inside Wh_ModInit, so it was applied — moving it to the background thread is the regression.

Fix: call Wh_ApplyHookOperations() after the batch of registrations on the post-init paths (once per batch — it's documented as slow, so don't call it per-hook). For example at the end of PerformBackgroundInit(), after TryInstallPniduiHook() succeeds in PniduiRetryThread, and in ReinitializeTrayRedirect() after InstallImmersiveMenuHooks(). A cleaner alternative for the late-loading DLLs (pnidui.dll, SndVolSSO.dll) is to hook LoadLibraryExW in kernelbase.dll and apply the module's hooks when it loads, instead of polling with a retry thread. See taskbar-clock-customization.wh.cpp for the "register then Wh_ApplyHookOperations()" pattern.

2. InstallLegacyNameHook() re-resolves shell32.dll symbols on every settings change. Unlike the other installers it has no "already installed" guard, so every Wh_ModSettingsChanged()PerformBackgroundInit() calls HookSymbols(hShell32, …) again. Repeated HookSymbols calls against the same module invalidate the symbol cache and force a slow re-resolution each time. Add a g_legacyNameHookInstalled flag like the others. Related: shell32.dll is currently hooked by two separate HookSymbols calls (_MapLegacyName in InstallLegacyNameHook and CDevicesAndPrintersFolder::_HandleContextMenu in InstallImmersiveMenuHooks) — collect both into a single SYMBOL_HOOK array and resolve them in one HookSymbols call for the module.
Optional improvements

Functionality notes

Thanks for the review, now all issues should be solved. Let me know if other fixes are required

Comment thread mods/settings-to-control-panel.wh.cpp Outdated
}

if (hookCount > 0) {
if (WindhawkUtils::HookSymbols(hShell32, shell32_dll_hooks, hookCount)) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The conditional hooking in this function will break symbol caching. Avoid this, and even better, avoid calling Wh_ApplyHookOperations. Just set all hook that might be needed, and if nothing should be done in the hook, just call the original function.

@m417z

m417z commented Jul 15, 2026

Copy link
Copy Markdown
Member

Submission review

Note: This review was done by Claude, and then refined manually. Due to the amount of submissions, doing a fully manual review for each pull request is no longer feasible. Thank you for understanding.

Please address the following issues. The items in the collapsed sections are optional, so it's your call whether to address them.


1. The TrackPopupMenuEx refactor likely broke the DLL return-address fallback. Previously the logic lived directly in TrackPopupMenuEx_Hook, so GetReturnAddress() (which returns stackTrace[2]) landed on the real caller (SndVolSSO/pnidui/dxgi/shell32). The refactor inserted a thin wrapper:

BOOL WINAPI TrackPopupMenuEx_Hook(...) {
    return CommonTrackPopupMenuEx_Hook(..., g_origTrackPopupMenuEx, L"TRAY-HOOK"); // extra frame
}

GetReturnAddress() (line 549) still hard-codes stackTrace[2], but there is now one extra mod frame in between. Whether [2] lands on the calling DLL or on TrackPopupMenuEx_Hook (mod code) now depends entirely on whether the optimizer inlines GetReturnAddress / tail-calls the wrapper — and on x86 (__stdcall, callee-cleanup, and CommonTrackPopupMenuEx_Hook takes more stack args than the wrapper received) a tail call isn't possible, so it's a regular frame. If [2] resolves into the mod module, every IsAddressInModule(retAddr, L"...") check returns false and the entire fallback path (network/device detection on menus not caught by the subclass flag) silently stops working. This is a plausible cause of the "network system tray redirect might not work after explorer restart" note you added. Fix by getting the return address inside the hook and passing it along.

2. Disabling LegacyNameMappingFix or ComActivationRedirect has no effect until the mod is fully reloaded. Both hooks are installed once (guarded by g_legacyNameHookInstalled / g_aamHookInstalled) and never removed, and — unlike ICMH_CAODTM_hook, which correctly re-checks g_settings.redirectSystemTray on every call — their hook bodies don't re-read the setting:

  • COpenControlPanel__MapLegacyName_hook (line 1173) unconditionally suppresses the mapping.
  • AAM_ActivateApplication_hook (line 1107) unconditionally intercepts.

So a user who toggles either setting off (e.g. to troubleshoot) sees no change. Gate the bodies on the current setting and fall through to the original when disabled, e.g.:

bool COpenControlPanel__MapLegacyName_hook(...) {
    if (!g_settings.legacyNameMappingFix)
        return COpenControlPanel__MapLegacyName_orig(pThis, pszLegacyName, pszNewName, uLen, nameChanged);
    ...
}

3. COpenControlPanel::_MapLegacyName is suppressed for every input, process-wide. Beyond the setting-gating in #2, the hook returns "no mapping" for all legacy names, not just the ones this mod cares about — it's a blanket override of a shell internal used by all Control-Panel-by-name resolution in explorer, and it's on by default. Please confirm that suppressing all mappings is intended rather than scoping it to the specific names that misbehave; a blanket override risks side effects on unrelated Control Panel navigation. Also guard the out-buffer write — *pszNewName = L'\0'; (line 1183) assumes pszNewName != nullptr && uLen > 0:

if (pszNewName && uLen > 0) *pszNewName = L'\0';
Optional improvements

Minor polish — none of this affects users in the common case, so it's your call.

  • Unsynchronized shared state across threads. g_hTrayToolbar, g_lastShellTrayWnd, and g_sndVolSSOBase/End / g_pniduiBase/End are read and written from the watchdog thread, the CreateWindowExW hook (on the window's thread), and Wh_ModInit/Wh_ModSettingsChanged without a lock. Tearing is unlikely for pointer-sized values, but two threads can both pass the if (g_hTrayToolbar) return; check in SetupTraySubclass and subclass twice. A small mutex (or std::atomic) around this state would make it clean.
  • ICMH_CAODTM_t calling convention (pre-existing, 32-bit only). The typedef is bool(__fastcall*)(HMENU, HWND), but the resolved symbols are __stdcall on x86. On x64 all conventions coincide so this is harmless, but on a 32-bit build the callee-cleanup mismatch (ret 0 vs ret 8) imbalances the stack on every call. Since the mod has no @architecture it also builds for x86; consider matching the convention conditionally.
  • COM path ignores fallback mode. In AAM_ActivateApplication_hook, when ResolveUri returns {intercept=true, target=""} (a fallback/pass-through decision), the code falls through and calls the original ActivateApplication, launching Settings regardless of FallbackMode. The ShellExecute/CreateProcess paths honor the fallback; the COM path doesn't.
  • IsShellProcess() defaults to "shell" before the shell window exists. When GetShellWindow() returns null and no /factory//separate//nodeuse flag is present, it assumes isShell = 1 and caches it. A non-shell explorer instance that starts before the shell window is up (and lacks those flags) would get the tray subclass + watchdog thread it doesn't need.

Functionality notes

Non-critical observations about the feature behavior itself.

  • Menu-detection heuristics are loose. Audio is accepted at itemCount <= 10 and network at 1 <= itemCount <= 20 (lines 599-603), and the network target is chosen as "last non-separator item." These will misidentify unrelated popups that happen to originate from those DLLs, and the last-item assumption is build-fragile. It's inherent to the return-address approach and probably has no clean alternative, but worth being aware of when redirects fire on the wrong menu.
  • ICMH_CAODTM_hook is reused for CDevicesAndPrintersFolder::_HandleContextMenu (line 1487), whose semantics differ from CanApplyOwnerDrawToMenu — one governs owner-draw, the other handles the context menu. Returning the same !redirectSystemTray value for both is worth double-checking; a wrong return here is likely why the device & printers redirect is unreliable. (Pre-existing.)
  • Tray recreation is handled by polling. The watchdog re-detects the toolbar every 500 ms→3 s. That's an acceptable rate, but listening for the RegisterWindowMessage(L"TaskbarCreated") broadcast (via a message-only window) would be the event-driven equivalent if you want to reduce idle polling.

@babamohammed2022

babamohammed2022 commented Jul 15, 2026

Copy link
Copy Markdown
Contributor Author

Submission review

Note: This review was done by Claude, and then refined manually. Due to the amount of submissions, doing a fully manual review for each pull request is no longer feasible. Thank you for understanding.

Please address the following issues. The items in the collapsed sections are optional, so it's your call whether to address them.

1. The TrackPopupMenuEx refactor likely broke the DLL return-address fallback. Previously the logic lived directly in TrackPopupMenuEx_Hook, so GetReturnAddress() (which returns stackTrace[2]) landed on the real caller (SndVolSSO/pnidui/dxgi/shell32). The refactor inserted a thin wrapper:

BOOL WINAPI TrackPopupMenuEx_Hook(...) {
    return CommonTrackPopupMenuEx_Hook(..., g_origTrackPopupMenuEx, L"TRAY-HOOK"); // extra frame
}

GetReturnAddress() (line 549) still hard-codes stackTrace[2], but there is now one extra mod frame in between. Whether [2] lands on the calling DLL or on TrackPopupMenuEx_Hook (mod code) now depends entirely on whether the optimizer inlines GetReturnAddress / tail-calls the wrapper — and on x86 (__stdcall, callee-cleanup, and CommonTrackPopupMenuEx_Hook takes more stack args than the wrapper received) a tail call isn't possible, so it's a regular frame. If [2] resolves into the mod module, every IsAddressInModule(retAddr, L"...") check returns false and the entire fallback path (network/device detection on menus not caught by the subclass flag) silently stops working. This is a plausible cause of the "network system tray redirect might not work after explorer restart" note you added. Fix by getting the return address inside the hook and passing it along.

2. Disabling LegacyNameMappingFix or ComActivationRedirect has no effect until the mod is fully reloaded. Both hooks are installed once (guarded by g_legacyNameHookInstalled / g_aamHookInstalled) and never removed, and — unlike ICMH_CAODTM_hook, which correctly re-checks g_settings.redirectSystemTray on every call — their hook bodies don't re-read the setting:

* `COpenControlPanel__MapLegacyName_hook` (line 1173) unconditionally suppresses the mapping.

* `AAM_ActivateApplication_hook` (line 1107) unconditionally intercepts.

So a user who toggles either setting off (e.g. to troubleshoot) sees no change. Gate the bodies on the current setting and fall through to the original when disabled, e.g.:

bool COpenControlPanel__MapLegacyName_hook(...) {
    if (!g_settings.legacyNameMappingFix)
        return COpenControlPanel__MapLegacyName_orig(pThis, pszLegacyName, pszNewName, uLen, nameChanged);
    ...
}

3. COpenControlPanel::_MapLegacyName is suppressed for every input, process-wide. Beyond the setting-gating in #2, the hook returns "no mapping" for all legacy names, not just the ones this mod cares about — it's a blanket override of a shell internal used by all Control-Panel-by-name resolution in explorer, and it's on by default. Please confirm that suppressing all mappings is intended rather than scoping it to the specific names that misbehave; a blanket override risks side effects on unrelated Control Panel navigation. Also guard the out-buffer write — *pszNewName = L'\0'; (line 1183) assumes pszNewName != nullptr && uLen > 0:

if (pszNewName && uLen > 0) *pszNewName = L'\0';

Optional improvements

Functionality notes

Thanks for the additional review. Is the code correct now?

@m417z

m417z commented Jul 15, 2026

Copy link
Copy Markdown
Member

Submission review

Note: This review was done by Claude, and then refined manually. Due to the amount of submissions, doing a fully manual review for each pull request is no longer feasible. Thank you for understanding.

Please address the following issues. The items in the collapsed sections are optional, so it's your call whether to address them.


This is an update to your own mod, so there's no overlap/duplication concern. The main thing to fix is a deadlock that this PR newly introduces via the tray-subclass mutex, plus a follow-up to the maintainer's earlier hook comment.

1. g_traySubclassMutex is held across the SendMessage-based subclass calls → can freeze/hang Explorer.

SetWindowSubclassFromAnyThread and RemoveWindowSubclassFromAnyThread are implemented via SendMessage to the window's owning thread (see windhawk_utils.h, lines 325/334). In this PR you now call them while holding g_traySubclassMutex:

static void SetupTraySubclass() {
    std::lock_guard<std::mutex> lk(g_traySubclassMutex);      // lock held...
    ...
    if (WindhawkUtils::SetWindowSubclassFromAnyThread(hToolbar, ...)) {  // ...across a cross-thread SendMessage

At the same time, CreateWindowExW_Hook — which fires on the tray UI thread every time Explorer creates a window — acquires the same mutex:

HWND WINAPI CreateWindowExW_Hook(...) {
    HWND hwnd = CreateWindowExW_Original(...);
    {
        std::lock_guard<std::mutex> lk(g_traySubclassMutex);   // victim
        trayToolbarMissing = (g_hTrayToolbar == nullptr);
    }

Classic lock-and-SendMessage deadlock: the watchdog thread (or Wh_ModSettingsChanged) holds g_traySubclassMutex and SendMessages the tray thread, while the tray thread is blocked in CreateWindowExW_Hook waiting for that mutex. The SendMessage never gets dispatched → both threads hang, which freezes the taskbar/shell. This is routinely exercised: on the very first watchdog tick, HasTrayBeenRecreated() returns true (because Wh_ModInit resets g_lastShellTrayWnd = nullptr), so ReinitializeTrayRedirect() does a RemoveTraySubclass() + SetupTraySubclass() (two cross-thread SendMessages under the lock) right while Explorer is busy creating windows at startup. When it deadlocks on unload, the WaitForSingleObject(..., 3000) join in Wh_ModUninit then times out and CloseHandles a still-running thread → crash on DLL unload.

Note the base version (10.0.20) did not hold a lock here at all — this is a regression introduced by the new watchdog/mutex code. Fix: copy the handle out under the lock, release it, then make the cross-thread call:

static void SetupTraySubclass() {
    HWND hToolbar;
    {
        std::lock_guard<std::mutex> lk(g_traySubclassMutex);
        if (g_hTrayToolbar && IsWindow(g_hTrayToolbar)) return;
        g_hTrayToolbar = nullptr;
        hToolbar = FindTrayToolbar();
    }
    if (!hToolbar || !InitTrayDllInfo()) return;
    BOOL ok = WindhawkUtils::SetWindowSubclassFromAnyThread(hToolbar, TrayToolbarSubclassProc, 0);
    if (ok) {
        std::lock_guard<std::mutex> lk(g_traySubclassMutex);
        g_hTrayToolbar = hToolbar;
    }
}

static void RemoveTraySubclass() {
    HWND h;
    {
        std::lock_guard<std::mutex> lk(g_traySubclassMutex);
        h = g_hTrayToolbar;
        g_hTrayToolbar = nullptr;
    }
    if (h) WindhawkUtils::RemoveWindowSubclassFromAnyThread(h, TrayToolbarSubclassProc);
}

2. ICMH_CAODTM_hook returns a hardcoded constant instead of calling the original when the feature is off.

This is the still-unaddressed half of the reviewer's earlier comment ("if nothing should be done in the hook, just call the original function"). The hook is installed unconditionally (good, for cache stability), but when RedirectSystemTray/EnableRedirects are off — which is the default — it returns true unconditionally instead of forwarding to the real CanApplyOwnerDrawToMenu:

static bool ICMH_CALL ICMH_CAODTM_hook(HMENU, HWND) {
    if (!g_settings.enableRedirects || !g_settings.redirectSystemTray) return true;  // not a true no-op
    return false;
}

So by default the mod still forces CanApplyOwnerDrawToMenu → true on the audio/network/devices context menus, which is not equivalent to the shell's own decision (it can legitimately return false, e.g. high-contrast). Because one shared hook serves three modules (SndVolSSO, pnidui, shell32), it can't know which original to call — split it into three tiny thunks that each forward to the correct g_icmhOrig_* when disabled:

static bool ICMH_CALL ICMH_hook_pnidui(HMENU m, HWND w) {
    if (!g_settings.enableRedirects || !g_settings.redirectSystemTray)
        return g_icmhOrig_pnidui(m, w);
    return false;
}
// ...same for SndVolSSO and Shell32Devices, each calling its own orig.

This also makes the currently-unused g_icmhOrig_* pointers meaningful.

Optional improvements

Minor polish — none of this affects users, so it's your call.

  • Dead late-hook paths. PniduiRetryThread (and the watchdog re-invoking InstallImmersiveMenuHooks/InstallShell32Hooks/InstallAAMHook) call HookSymbols/SetFunctionHook after Wh_ModInit returns, but nothing calls Wh_ApplyHookOperations anymore, so any hook they'd install late can never actually apply. In practice pnidui.dll/SndVolSSO.dll are force-loaded and hooked during Wh_ModInit, so TryInstallPniduiHook() almost always succeeds there and the retry thread never runs. The whole PniduiRetryThread / g_pniduiRetryRunning machinery looks like it can be removed.

  • Wh_ModSettingsChanged never removes the subclass when the feature is toggled off. It only calls SetupTraySubclass() when redirectSystemTray is on; turning it off at runtime leaves the toolbar subclassed until unload. Harmless (the redirect gating lives in the hooks), but consider a RemoveTraySubclass() in the else branch for symmetry.

  • FallbackMode $options label has a stray trailing quote: - "2": Pass through to the modern Settings application (ms-settings.exe)" — the dropdown label will render with a trailing ".

Functionality notes

Non-critical observations about the feature behavior itself.

  • The experimental COM ActivateApplication hook has process-wide blast radius. InstallAAMHook patches the resolved ActivateApplication function itself (not just this instance's vtable slot), so it intercepts every app activation in Explorer, and on failure returns S_OK with *processId = GetCurrentProcessId() (a fabricated PID). It's gated behind ComActivationRedirect and marked experimental, so this is fine as-is — just be aware that a misbehaving redirect here affects all immersive app launches, not only Settings.

  • One ICMH_CAODTM_hook is applied to two semantically different functions. CanApplyOwnerDrawToMenu(HMENU, HWND) and CDevicesAndPrintersFolder::_HandleContextMenu(HMENU, unsigned int) are hooked with the same bool(HMENU, HWND) function. It works only because the body ignores its arguments and both happen to be bool __cdecl with two register-sized params — but it's fragile, and _HandleContextMenu's second arg is a UINT, not an HWND. If you split the hook per module (item 2 above), consider giving the devices hook its own correctly-typed prototype.

  • Tray-recreation detection is polling-based (500 ms fast phase, then 3 s). That's a reasonable rate and fine here; the conventional signal would be the RegisterWindowMessage(L"TaskbarCreated") broadcast, but since the mod has no window of its own to receive it, polling is an acceptable choice.

@babamohammed2022

babamohammed2022 commented Jul 16, 2026

Copy link
Copy Markdown
Contributor Author

Submission review

Note: This review was done by Claude, and then refined manually. Due to the amount of submissions, doing a fully manual review for each pull request is no longer feasible. Thank you for understanding.

Please address the following issues. The items in the collapsed sections are optional, so it's your call whether to address them.

This is an update to your own mod, so there's no overlap/duplication concern. The main thing to fix is a deadlock that this PR newly introduces via the tray-subclass mutex, plus a follow-up to the maintainer's earlier hook comment.

1. g_traySubclassMutex is held across the SendMessage-based subclass calls → can freeze/hang Explorer.

SetWindowSubclassFromAnyThread and RemoveWindowSubclassFromAnyThread are implemented via SendMessage to the window's owning thread (see windhawk_utils.h, lines 325/334). In this PR you now call them while holding g_traySubclassMutex:

static void SetupTraySubclass() {
    std::lock_guard<std::mutex> lk(g_traySubclassMutex);      // lock held...
    ...
    if (WindhawkUtils::SetWindowSubclassFromAnyThread(hToolbar, ...)) {  // ...across a cross-thread SendMessage

At the same time, CreateWindowExW_Hook — which fires on the tray UI thread every time Explorer creates a window — acquires the same mutex:

HWND WINAPI CreateWindowExW_Hook(...) {
    HWND hwnd = CreateWindowExW_Original(...);
    {
        std::lock_guard<std::mutex> lk(g_traySubclassMutex);   // victim
        trayToolbarMissing = (g_hTrayToolbar == nullptr);
    }

Classic lock-and-SendMessage deadlock: the watchdog thread (or Wh_ModSettingsChanged) holds g_traySubclassMutex and SendMessages the tray thread, while the tray thread is blocked in CreateWindowExW_Hook waiting for that mutex. The SendMessage never gets dispatched → both threads hang, which freezes the taskbar/shell. This is routinely exercised: on the very first watchdog tick, HasTrayBeenRecreated() returns true (because Wh_ModInit resets g_lastShellTrayWnd = nullptr), so ReinitializeTrayRedirect() does a RemoveTraySubclass() + SetupTraySubclass() (two cross-thread SendMessages under the lock) right while Explorer is busy creating windows at startup. When it deadlocks on unload, the WaitForSingleObject(..., 3000) join in Wh_ModUninit then times out and CloseHandles a still-running thread → crash on DLL unload.

Note the base version (10.0.20) did not hold a lock here at all — this is a regression introduced by the new watchdog/mutex code. Fix: copy the handle out under the lock, release it, then make the cross-thread call:

static void SetupTraySubclass() {
    HWND hToolbar;
    {
        std::lock_guard<std::mutex> lk(g_traySubclassMutex);
        if (g_hTrayToolbar && IsWindow(g_hTrayToolbar)) return;
        g_hTrayToolbar = nullptr;
        hToolbar = FindTrayToolbar();
    }
    if (!hToolbar || !InitTrayDllInfo()) return;
    BOOL ok = WindhawkUtils::SetWindowSubclassFromAnyThread(hToolbar, TrayToolbarSubclassProc, 0);
    if (ok) {
        std::lock_guard<std::mutex> lk(g_traySubclassMutex);
        g_hTrayToolbar = hToolbar;
    }
}

static void RemoveTraySubclass() {
    HWND h;
    {
        std::lock_guard<std::mutex> lk(g_traySubclassMutex);
        h = g_hTrayToolbar;
        g_hTrayToolbar = nullptr;
    }
    if (h) WindhawkUtils::RemoveWindowSubclassFromAnyThread(h, TrayToolbarSubclassProc);
}

2. ICMH_CAODTM_hook returns a hardcoded constant instead of calling the original when the feature is off.

This is the still-unaddressed half of the reviewer's earlier comment ("if nothing should be done in the hook, just call the original function"). The hook is installed unconditionally (good, for cache stability), but when RedirectSystemTray/EnableRedirects are off — which is the default — it returns true unconditionally instead of forwarding to the real CanApplyOwnerDrawToMenu:

static bool ICMH_CALL ICMH_CAODTM_hook(HMENU, HWND) {
    if (!g_settings.enableRedirects || !g_settings.redirectSystemTray) return true;  // not a true no-op
    return false;
}

So by default the mod still forces CanApplyOwnerDrawToMenu → true on the audio/network/devices context menus, which is not equivalent to the shell's own decision (it can legitimately return false, e.g. high-contrast). Because one shared hook serves three modules (SndVolSSO, pnidui, shell32), it can't know which original to call — split it into three tiny thunks that each forward to the correct g_icmhOrig_* when disabled:

static bool ICMH_CALL ICMH_hook_pnidui(HMENU m, HWND w) {
    if (!g_settings.enableRedirects || !g_settings.redirectSystemTray)
        return g_icmhOrig_pnidui(m, w);
    return false;
}
// ...same for SndVolSSO and Shell32Devices, each calling its own orig.

This also makes the currently-unused g_icmhOrig_* pointers meaningful.
Optional improvements

Functionality notes

Thanks for reporting the issues. If possible, let me know if this new version is more stable and without regression

@m417z

m417z commented Jul 16, 2026

Copy link
Copy Markdown
Member

Submission review

Note: This review was done by Claude, and then refined manually. Due to the amount of submissions, doing a fully manual review for each pull request is no longer feasible. Thank you for understanding.

Please address the following issues. The items in the collapsed sections are optional, so it's your call whether to address them.


1. The CDevicesAndPrintersFolder::_HandleContextMenu hook is missing the implicit this parameter.

_HandleContextMenu is a non-static member function, so its real runtime signature is bool(this, HMENU, UINT) — the demangled name only shows the two explicit parameters. The hook declares just two:

using ICMH_HCM_t = bool(ICMH_CALL*)(HMENU, UINT);
static bool ICMH_CALL ICMH_hook_Shell32Devices(HMENU m, UINT u) {
    if (!g_settings.enableRedirects || !g_settings.redirectSystemTray)
        return g_icmhOrig_Shell32Devices ? g_icmhOrig_Shell32Devices(m, u) : true;
    return false;
}

So the prototype is wrong:

  • On x64, this and the menu handle happen to land in the right registers by coincidence, but the third argument (UINT) is never set up, so the original is called reading an undefined value from r8. Relying on the compiler to leave the value there is UB and can break with any codegen change.
  • On x86 it's worse — the member is __thiscall (this in ecx), so the hook loses this entirely and the stack is unbalanced → crash.

Note this fires with the tray feature off (the default RedirectSystemTray: false takes the return g_icmhOrig_Shell32Devices(m, u) branch), so any invocation of the Devices and Printers folder's context-menu handler hits the corrupt forward regardless of settings.

You already handled this correctly for _MapLegacyName (it takes void *pThis first). Do the same here:

using ICMH_HCM_t = bool(ICMH_CALL*)(void* /*pThis*/, HMENU, UINT);
static bool ICMH_CALL ICMH_hook_Shell32Devices(void* pThis, HMENU m, UINT u) {
    if (!g_settings.enableRedirects || !g_settings.redirectSystemTray)
        return g_icmhOrig_Shell32Devices ? g_icmhOrig_Shell32Devices(pThis, m, u) : true;
    return false;
}

The symbol string stays as-is (this is not part of the demangled name). See taskbar-classic-menu.wh.cpp#L93 and taskbar-button-scroll.wh.cpp#L930, which both hook a _HandleContextMenu member and include pThis. Also worth confirming this hook is actually pulling its weight at all — since the readme already says the Devices & Printers redirect is unreliable, if it isn't doing anything measurable it may be an AI artifact that can just be dropped (which also removes the risk above).

Human note: I wanted to verify this manually, but the symbol is missing on my Win11 shell32.dll. Which Windows version is it for?

2. The 32-bit builds have several calling-convention problems.

The mod has no @architecture, so it also builds for x86, but the x86 code paths look untested (the readme only lists 64-bit test machines) and they aren't correct:

  • The ImmersiveContextMenuHelper::CanApplyOwnerDrawToMenu hooks use ICMH_CALL = __stdcall on x86 for the function-pointer type. The established mods that hook this exact function use __fastcall for the pointer type (keeping the demangled name as __stdcall/__cdecl) and explicitly note it's required — see eradicate-immersive-menus.wh.cpp#L84 ("__fastcall is needed, or else stack corruption occurs") and custom-menu-height.wh.cpp#L599. With __stdcall you'll get stack corruption on 32-bit.
  • COpenControlPanel::_MapLegacyName's symbol string hard-codes __cdecl for all architectures, but on x86 a non-static member is __thiscall, so the symbol won't resolve there and the fix silently no-ops (and the void*-first prototype wouldn't match the __thiscall convention anyway).

The simplest fix, given the mod is only tested on 64-bit, is to add @architecture x86-64 (which also covers ARM64 for shell processes) and stop shipping the broken 32-bit build. If you do want to keep 32-bit support, match the reference mods' __fastcall typing and correct the _MapLegacyName convention.

Human note: I haven't verified it. Have you tested it on 32-bit?

@babamohammed2022

babamohammed2022 commented Jul 16, 2026

Copy link
Copy Markdown
Contributor Author

Submission review

Note: This review was done by Claude, and then refined manually. Due to the amount of submissions, doing a fully manual review for each pull request is no longer feasible. Thank you for understanding.

Please address the following issues. The items in the collapsed sections are optional, so it's your call whether to address them.

1. The CDevicesAndPrintersFolder::_HandleContextMenu hook is missing the implicit this parameter.

_HandleContextMenu is a non-static member function, so its real runtime signature is bool(this, HMENU, UINT) — the demangled name only shows the two explicit parameters. The hook declares just two:

using ICMH_HCM_t = bool(ICMH_CALL*)(HMENU, UINT);
static bool ICMH_CALL ICMH_hook_Shell32Devices(HMENU m, UINT u) {
    if (!g_settings.enableRedirects || !g_settings.redirectSystemTray)
        return g_icmhOrig_Shell32Devices ? g_icmhOrig_Shell32Devices(m, u) : true;
    return false;
}

So the prototype is wrong:

* **On x64**, `this` and the menu handle happen to land in the right registers by coincidence, but the third argument (`UINT`) is never set up, so the original is called reading an undefined value from `r8`. Relying on the compiler to leave the value there is UB and can break with any codegen change.

* **On x86** it's worse — the member is `__thiscall` (`this` in `ecx`), so the hook loses `this` entirely and the stack is unbalanced → crash.

Note this fires with the tray feature off (the default RedirectSystemTray: false takes the return g_icmhOrig_Shell32Devices(m, u) branch), so any invocation of the Devices and Printers folder's context-menu handler hits the corrupt forward regardless of settings.

You already handled this correctly for _MapLegacyName (it takes void *pThis first). Do the same here:

using ICMH_HCM_t = bool(ICMH_CALL*)(void* /*pThis*/, HMENU, UINT);
static bool ICMH_CALL ICMH_hook_Shell32Devices(void* pThis, HMENU m, UINT u) {
    if (!g_settings.enableRedirects || !g_settings.redirectSystemTray)
        return g_icmhOrig_Shell32Devices ? g_icmhOrig_Shell32Devices(pThis, m, u) : true;
    return false;
}

The symbol string stays as-is (this is not part of the demangled name). See taskbar-classic-menu.wh.cpp#L93 and taskbar-button-scroll.wh.cpp#L930, which both hook a _HandleContextMenu member and include pThis. Also worth confirming this hook is actually pulling its weight at all — since the readme already says the Devices & Printers redirect is unreliable, if it isn't doing anything measurable it may be an AI artifact that can just be dropped (which also removes the risk above).

Human note: I wanted to verify this manually, but the symbol is missing on my Win11 shell32.dll. Which Windows version is it for?

2. The 32-bit builds have several calling-convention problems.

The mod has no @architecture, so it also builds for x86, but the x86 code paths look untested (the readme only lists 64-bit test machines) and they aren't correct:

* The `ImmersiveContextMenuHelper::CanApplyOwnerDrawToMenu` hooks use `ICMH_CALL` = `__stdcall` on x86 for the _function-pointer type_. The established mods that hook this exact function use `__fastcall` for the pointer type (keeping the demangled name as `__stdcall`/`__cdecl`) and explicitly note it's required — see [eradicate-immersive-menus.wh.cpp#L84](https://github.com/ramensoftware/windhawk-mods/blob/main/mods/eradicate-immersive-menus.wh.cpp#L84) (_"`__fastcall` is needed, or else stack corruption occurs"_) and [custom-menu-height.wh.cpp#L599](https://github.com/ramensoftware/windhawk-mods/blob/main/mods/custom-menu-height.wh.cpp#L599). With `__stdcall` you'll get stack corruption on 32-bit.

* `COpenControlPanel::_MapLegacyName`'s symbol string hard-codes `__cdecl` for all architectures, but on x86 a non-static member is `__thiscall`, so the symbol won't resolve there and the fix silently no-ops (and the `void*`-first prototype wouldn't match the `__thiscall` convention anyway).

The simplest fix, given the mod is only tested on 64-bit, is to add @architecture x86-64 (which also covers ARM64 for shell processes) and stop shipping the broken 32-bit build. If you do want to keep 32-bit support, match the reference mods' __fastcall typing and correct the _MapLegacyName convention.

Human note: I haven't verified it. Have you tested it on 32-bit?

I'm sorry, but I currently do not have a 32 bit system to test on so I can't test it directly. If I can, I'll try on 32 bit systems too but I'm not sure that I can due to my PC not being completely working. If required, I will drop 32 bit support because I do not have enough storage to test anyways thanks for the review. Please tell me what's the best option this case so that the mod can work in a more reliable way.
Note: I've personally tested on Windows 10 21H2 and Windows 11 24H2 as they're the only operating systems that I currently have access to and I asked a friend to test on Windows 10 1809 but not on 32 bit versions. If it is a problem, I could try to test on a 32 bit but I'm not sure if I can.

@m417z

m417z commented Jul 16, 2026

Copy link
Copy Markdown
Member

If it's untested and is likely broken, it's better to just limit it to 64-bit. 32-bit OSes aren't common nowadays, and Windhawk will be dropping support for them soon anyway. Many mods are already limited to 64-bit.

@babamohammed2022

Copy link
Copy Markdown
Contributor Author

If it's untested and is likely broken, it's better to just limit it to 64-bit. 32-bit OSes aren't common nowadays, and Windhawk will be dropping support for them soon anyway. Many mods are already limited to 64-bit.

Thanks for telling me, in this case I will just make it compatible with 64 bit architecture and I'll try to fix the reported issues above. Thanks

@babamohammed2022

Copy link
Copy Markdown
Contributor Author

Submission review

Note: This review was done by Claude, and then refined manually. Due to the amount of submissions, doing a fully manual review for each pull request is no longer feasible. Thank you for understanding.

Please address the following issues. The items in the collapsed sections are optional, so it's your call whether to address them.

1. The CDevicesAndPrintersFolder::_HandleContextMenu hook is missing the implicit this parameter.

_HandleContextMenu is a non-static member function, so its real runtime signature is bool(this, HMENU, UINT) — the demangled name only shows the two explicit parameters. The hook declares just two:

using ICMH_HCM_t = bool(ICMH_CALL*)(HMENU, UINT);
static bool ICMH_CALL ICMH_hook_Shell32Devices(HMENU m, UINT u) {
    if (!g_settings.enableRedirects || !g_settings.redirectSystemTray)
        return g_icmhOrig_Shell32Devices ? g_icmhOrig_Shell32Devices(m, u) : true;
    return false;
}

So the prototype is wrong:

* **On x64**, `this` and the menu handle happen to land in the right registers by coincidence, but the third argument (`UINT`) is never set up, so the original is called reading an undefined value from `r8`. Relying on the compiler to leave the value there is UB and can break with any codegen change.

* **On x86** it's worse — the member is `__thiscall` (`this` in `ecx`), so the hook loses `this` entirely and the stack is unbalanced → crash.

Note this fires with the tray feature off (the default RedirectSystemTray: false takes the return g_icmhOrig_Shell32Devices(m, u) branch), so any invocation of the Devices and Printers folder's context-menu handler hits the corrupt forward regardless of settings.

You already handled this correctly for _MapLegacyName (it takes void *pThis first). Do the same here:

using ICMH_HCM_t = bool(ICMH_CALL*)(void* /*pThis*/, HMENU, UINT);
static bool ICMH_CALL ICMH_hook_Shell32Devices(void* pThis, HMENU m, UINT u) {
    if (!g_settings.enableRedirects || !g_settings.redirectSystemTray)
        return g_icmhOrig_Shell32Devices ? g_icmhOrig_Shell32Devices(pThis, m, u) : true;
    return false;
}

The symbol string stays as-is (this is not part of the demangled name). See taskbar-classic-menu.wh.cpp#L93 and taskbar-button-scroll.wh.cpp#L930, which both hook a _HandleContextMenu member and include pThis. Also worth confirming this hook is actually pulling its weight at all — since the readme already says the Devices & Printers redirect is unreliable, if it isn't doing anything measurable it may be an AI artifact that can just be dropped (which also removes the risk above).

Human note: I wanted to verify this manually, but the symbol is missing on my Win11 shell32.dll. Which Windows version is it for?

2. The 32-bit builds have several calling-convention problems.

The mod has no @architecture, so it also builds for x86, but the x86 code paths look untested (the readme only lists 64-bit test machines) and they aren't correct:

* The `ImmersiveContextMenuHelper::CanApplyOwnerDrawToMenu` hooks use `ICMH_CALL` = `__stdcall` on x86 for the _function-pointer type_. The established mods that hook this exact function use `__fastcall` for the pointer type (keeping the demangled name as `__stdcall`/`__cdecl`) and explicitly note it's required — see [eradicate-immersive-menus.wh.cpp#L84](https://github.com/ramensoftware/windhawk-mods/blob/main/mods/eradicate-immersive-menus.wh.cpp#L84) (_"`__fastcall` is needed, or else stack corruption occurs"_) and [custom-menu-height.wh.cpp#L599](https://github.com/ramensoftware/windhawk-mods/blob/main/mods/custom-menu-height.wh.cpp#L599). With `__stdcall` you'll get stack corruption on 32-bit.

* `COpenControlPanel::_MapLegacyName`'s symbol string hard-codes `__cdecl` for all architectures, but on x86 a non-static member is `__thiscall`, so the symbol won't resolve there and the fix silently no-ops (and the `void*`-first prototype wouldn't match the `__thiscall` convention anyway).

The simplest fix, given the mod is only tested on 64-bit, is to add @architecture x86-64 (which also covers ARM64 for shell processes) and stop shipping the broken 32-bit build. If you do want to keep 32-bit support, match the reference mods' __fastcall typing and correct the _MapLegacyName convention.

Human note: I haven't verified it. Have you tested it on 32-bit?

I've removed the support for 32 bit based operating systems and I've noted it as a limitation. Is the mod correct now?

@m417z

m417z commented Jul 17, 2026

Copy link
Copy Markdown
Member

Submission review

The mod is large and the findings below were missed in a previous runs, but they seem like real issues worth addressing.


ComActivationRedirect — default and documentation don't match the behavior.

The setting defaults to true (both in the settings block and ModSettings::comActivationRedirect), but its own description says:

On Windows 11, this affects all app launches process-wide, so only enable it if you have a specific issue it fixes.

On Windows 11 the hook (InstallAAMHook) patches IApplicationActivationManager::ActivateApplication in-place, so with the default settings every app activation that goes through that COM path in explorer.exe is routed through the mod by default — exactly what the description tells users to avoid unless they have a specific problem. A process-wide interception of app activation shouldn't be on by default; please default it to false.

There's also a second inconsistency: InstallAAMHook() is only ever called under if (g_isWin11) (settings-to-control-panel.wh.cpp:1637), so on Windows 10 the setting does nothing at all — yet the description says "Recommended on Windows 10." Please reconcile these: either install the hook on Win10 too if it's meant to work there, or drop the Win10 wording. As written the setting is a no-op on Win10 and an aggressive default on Win11, which is the opposite of what the text implies.

  • Dead _MSC_VER branch. #ifdef _MSC_VER #include <intrin.h> and the _ReturnAddress() path (settings-to-control-panel.wh.cpp:116, :737) never compile — Windhawk builds with Clang/mingw and doesn't define _MSC_VER, so it's always the __builtin_return_address(0) branch. You can drop the MSVC half.
  • Dead 32-bit branches. Now that the mod is @architecture x86-64 only, the #else L"__stdcall" halves of the symbol strings and the ICMH_CALL macro are unreachable (_WIN64 is always defined). Harmless, but removable if you want to simplify.
  • Pre-existing typo in g_win11SafeClsids: L"shell:::{ecd0924-4208-451e-8ee0-373c0956de16}" (settings-to-control-panel.wh.cpp:396) is missing a hex digit — it should be ecdb0924 to match the ECDB0924-... CLSID used at line 845. The entry can never match as written (though that CLSID is only ever produced in its nested form anyway, so impact is nil).
  • README typos: "Contro Panel" and "primarly".

@babamohammed2022

Copy link
Copy Markdown
Contributor Author

Submission review

The mod is large and the findings below were missed in a previous runs, but they seem like real issues worth addressing.

ComActivationRedirect — default and documentation don't match the behavior.

The setting defaults to true (both in the settings block and ModSettings::comActivationRedirect), but its own description says:

On Windows 11, this affects all app launches process-wide, so only enable it if you have a specific issue it fixes.

On Windows 11 the hook (InstallAAMHook) patches IApplicationActivationManager::ActivateApplication in-place, so with the default settings every app activation that goes through that COM path in explorer.exe is routed through the mod by default — exactly what the description tells users to avoid unless they have a specific problem. A process-wide interception of app activation shouldn't be on by default; please default it to false.

There's also a second inconsistency: InstallAAMHook() is only ever called under if (g_isWin11) (settings-to-control-panel.wh.cpp:1637), so on Windows 10 the setting does nothing at all — yet the description says "Recommended on Windows 10." Please reconcile these: either install the hook on Win10 too if it's meant to work there, or drop the Win10 wording. As written the setting is a no-op on Win10 and an aggressive default on Win11, which is the opposite of what the text implies.

* **Dead `_MSC_VER` branch.** `#ifdef _MSC_VER #include <intrin.h>` and the `_ReturnAddress()` path (settings-to-control-panel.wh.cpp:116, :737) never compile — Windhawk builds with Clang/mingw and doesn't define `_MSC_VER`, so it's always the `__builtin_return_address(0)` branch. You can drop the MSVC half.

* **Dead 32-bit branches.** Now that the mod is `@architecture x86-64` only, the `#else L"__stdcall"` halves of the symbol strings and the `ICMH_CALL` macro are unreachable (`_WIN64` is always defined). Harmless, but removable if you want to simplify.

* **Pre-existing typo in `g_win11SafeClsids`:** `L"shell:::{ecd0924-4208-451e-8ee0-373c0956de16}"` (settings-to-control-panel.wh.cpp:396) is missing a hex digit — it should be `ecdb0924` to match the `ECDB0924-...` CLSID used at line 845. The entry can never match as written (though that CLSID is only ever produced in its nested form anyway, so impact is nil).

* **README typos:** "Contro Panel" and "primarly".

I've put ComActivationRedirect false by default and I've also removed the unnecessary parts of the code and fixed the typos. Let me know if it is correct now.

@m417z

m417z commented Jul 17, 2026

Copy link
Copy Markdown
Member

@Anixx would you like to test it before we merge the update?

@Anixx

Anixx commented Jul 17, 2026

Copy link
Copy Markdown
Contributor

All the same for me.

@m417z
m417z merged commit 639e8f6 into ramensoftware:main Jul 17, 2026
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants