Skip to content

Add new mod: Faster Notifications Settings Page v1.0#4826

Open
mario0318 wants to merge 1 commit into
ramensoftware:mainfrom
mario0318:mod-notifications-settings-page-speedup
Open

Add new mod: Faster Notifications Settings Page v1.0#4826
mario0318 wants to merge 1 commit into
ramensoftware:mainfrom
mario0318:mod-notifications-settings-page-speedup

Conversation

@mario0318

@mario0318 mario0318 commented Jul 18, 2026

Copy link
Copy Markdown

Faster Notifications Settings Page

Adds a new mod (notifications-settings-page-speedup).

The Windows 11 Settings, System, Notifications page rebuilds its full per-app list on every visit, including when you edit one app and press Back. With many notification senders this takes minutes each time. This mod keeps the first open as-is (it fills a cache) and makes later visits load in a couple of seconds, by skipping a per-app summary line and caching each app's settings object across visits. The cached objects read live data, so edits still show correctly. Both behaviours default on and revert instantly when turned off.

Trade-off: skipping the per-app summary removes the small subtitle that shows banners/sounds status at a glance. Every app still shows its name, icon, and main on/off toggle, and all per-app options remain editable by opening the app. This can be turned off to keep the subtitle.

Approach: hooks a couple of internal functions in SettingsHandlers_Notifications.dll by symbol and caches the per-app object at the controller call that produces it. The offsets are build-specific, so it may need a refresh after a major Windows release.

Testing: verified on Windows 11. First open runs the normal full scan; repeat visits, including after editing an app, load in a few seconds with edited toggles shown correctly.

Changelog

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

  • Initial release (v1.0) — new mod.

Mod authorship

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

This mod was created by:

    • The submitter, with AI assistance
    • The submitter, without 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.

@mario0318 mario0318 closed this Jul 20, 2026
@mario0318 mario0318 reopened this Jul 20, 2026
@mario0318

mario0318 commented Jul 20, 2026

Copy link
Copy Markdown
Author

Restored the ## Mod authorship section in the description. That was the validation failure.

@mario0318

mario0318 commented Jul 20, 2026

Copy link
Copy Markdown
Author

Replaced the raw Wh_FindFirstSymbol/NextSymbol/CloseSymbol loop with WindhawkUtils::HookSymbols, using the full decorated names for _AddEntry and PopulateSummaryValues. No behavior change, and it clears the manual-inspection warnings. Passes pr_validation.py locally.

@mario0318

mario0318 commented Jul 21, 2026

Copy link
Copy Markdown
Author

Tested on Windows 11 (26100). Both hooks resolve and apply, the settings cache kicks in, and repeat visits to the Notifications page load in a couple seconds instead of minutes. Green and mergeable, so ready for review whenever.

Speeds up Settings > System > Notifications on Win11 so repeat visits load
in a couple seconds instead of minutes. Hooks _AddEntry and
PopulateSummaryValues in SettingsHandlers_Notifications.dll with
WindhawkUtils::HookSymbols, caches the per-app settings object across
visits, and skips the per-app summary lookup. Both are toggleable and
Ctrl+Alt+R clears the cache to force a full rescan.
@mario0318
mario0318 force-pushed the mod-notifications-settings-page-speedup branch from e489699 to 6443a45 Compare July 21, 2026 01:42
@m417z

m417z commented Jul 21, 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.

Additional manual notes:

  • WatchThread shouldn't be needed if the LoadLibraryExW hook works correctly, remove it.
  • For HotkeyThread, instead of polling, consider using RegisterHotKey which is more efficient and more responsive.
  • Replace VirtualProtect vtable hooks with WindhawkUtils::SetFunctionHook. If not possible, please explain why.
  • Remove all Sleep calls which shouldn't be needed once all feedback is addressed.

1. Worker threads are not joined before unload → crash window. Wh_ModUninit does g_running = false; Sleep(120); and then lets the DLL unmap, but neither WatchThread nor HotkeyThread is actually joined:

  • WatchThread sleeps in 200 ms increments (Sleep(200) > Sleep(120)), so it is very likely still inside its Sleep(200) when uninit returns; when it wakes, its return address is in the just-unmapped mod image → crash.
  • HotkeyThread sleeps Sleep(500) right after a FlushCache(), so if Ctrl+Alt+R was pressed shortly before unload, it wakes inside unmapped code → crash.

Sleep(120) is a guess, not a guarantee. Capture the thread handles and join them. The idiomatic form is a manual-reset stop event so the wait is immediate rather than polling:

static HANDLE g_stopEvent;   // CreateEventW(nullptr, TRUE, FALSE, nullptr)
static HANDLE g_watchThread, g_hotkeyThread;
// threads wait on g_stopEvent instead of Sleep(...) so they exit at once
void Wh_ModUninit() {
    SetEvent(g_stopEvent);
    HANDLE hs[] = { g_watchThread, g_hotkeyThread };
    WaitForMultipleObjects(2, hs, TRUE, INFINITE);
    CloseHandle(g_watchThread); CloseHandle(g_hotkeyThread); CloseHandle(g_stopEvent);
    ...
}

See taskbar-vd-switcher.wh.cpp for a stop-event + WaitForSingleObject join (and note it also drains in-flight worker threads before freeing state they touch). As a side effect this also fixes the handle leak — the CreateThread return values are currently discarded, so the handles are never closed.

2. LoadLibraryExW is hooked on the kernel32 import, so the handler-DLL hook can silently fail to install. WindhawkUtils::SetFunctionHook((void*)LoadLibraryExW, ...) binds kernel32's LoadLibraryExW export, but the real implementation is in kernelbase.dll, and internal/delay-load paths often call kernelbase's copy directly — those loads won't fire your hook. Today the only backstop is WatchThread, which polls for just 120 s (600 * 200 ms). So if SettingsHandlers_Notifications.dll is loaded via a kernelbase-direct path and the user opens the Notifications page more than ~2 minutes after the mod is enabled, the mod never installs its hooks and the whole speedup quietly does nothing until the mod is toggled. Resolve the kernelbase address explicitly:

HMODULE kernelBase = GetModuleHandleW(L"kernelbase.dll");
auto pLoadLibraryExW = (decltype(&LoadLibraryExW))GetProcAddress(kernelBase, "LoadLibraryExW");
WindhawkUtils::SetFunctionHook(pLoadLibraryExW, LoadLibraryExW_Hook, &LoadLibraryExW_Orig);

With a reliable loader hook, the arbitrary 120 s cap on the watcher stops mattering (you can shorten or drop it).

3. Cached COM objects are released off their creating thread. The IAumidNotificationSettings objects in g_cache come out of an out-of-process controller (per your comment), i.e. they're apartment-bound proxies. But FlushCache() (called from HotkeyThread) and the cleanup loop in Wh_ModUninit (arbitrary thread) call ->Release() on them from a thread that is almost certainly not the one that created them. Cross-apartment release marshals the call into the owning STA — at best it works but adds a round-trip, and if that apartment is busy/blocked it can hang, or the ref just leaks. Please verify the apartment these objects live in; if they're STA proxies, post the releases to the owning thread rather than releasing directly from the hotkey/uninit thread. taskbar-vd-switcher does exactly this for WinRT objects — it runs the releases on the owning UI thread.

Optional improvements

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

  • volatile bool g_runningstd::atomic<bool>. You already use std::atomic for the other flags; volatile doesn't provide the cross-thread visibility guarantees you want here.
  • Ref leak on a concurrent same-AUMID miss. In GetSettings_Hook, two threads that both miss the cache for the same aumid will both call the original and both AddRef, but only the first g_cache.emplace(...) inserts — the second returns false and its AddRef'd pointer is leaked. Check the emplace result and Release() on failure. Rare (enumeration is normally single-threaded), so low priority.
  • Consider WindhawkUtils::HookSymbols with a SYMBOL_HOOK array instead of the manual Wh_FindFirstSymbol/Wh_FindNextSymbol enumeration + wcsstr substring match. HookSymbols has built-in caching and is the idiomatic path; it accepts multiple candidate strings per hook if you need tolerance for signature variations. (If the substring match is a deliberate choice to survive parameter-list changes across builds, feel free to keep it.)

Functionality notes

Non-critical observations about the approach itself.

  • Inherent fragility of the offset/vtable approach. Reading the controller at helper + 0x108, the vtable slot 14, and the guessed _AddEntry signature (unsigned long long opt) are all build-specific — which you already call out in the README. Two caveats worth noting: (a) if the layout shifts on a future build, *(helper + 0x108) can be a non-null garbage pointer that you then dereference as a vtable → a crash in SystemSettings rather than a graceful no-op, so a sanity check on controller/its vtable before patching would be safer; (b) the manual vtable patch isn't managed by Windhawk, so unlike your symbol hooks it isn't quiesced on unload — an in-flight GetSettings_Hook on the UI thread at the moment of unload can crash when the DLL unmaps. There's no clean fix for the manual-patch case, but it's a real (if low-probability) window; the thread-join fix above at least removes the more likely crash sources.
  • The Ctrl+Alt+R hotkey is global and may be unnecessary. GetAsyncKeyState reads global key state, so the flush fires even when SystemSettings isn't focused (and could collide with another app's Ctrl+Alt+R). Since the cached objects read live data, the only case that actually needs a manual flush is apps being added/removed — consider whether the hotkey earns its keep, or scope it to when the window is focused.

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.

2 participants