Skip to content

[Auto Custom Titlebar Colors]: Smart ignore UWP Apps and browser windows with settings caching#4797

Open
Louis047 wants to merge 3 commits into
ramensoftware:mainfrom
Louis047:act-update
Open

[Auto Custom Titlebar Colors]: Smart ignore UWP Apps and browser windows with settings caching#4797
Louis047 wants to merge 3 commits into
ramensoftware:mainfrom
Louis047:act-update

Conversation

@Louis047

@Louis047 Louis047 commented Jul 15, 2026

Copy link
Copy Markdown
Contributor

Changelog

  • Added a new method to detect and ignore UWP apps by default
  • Added classes to ignore browser windows (conflict with Zen Browser which is a special case)

Issue References

@Louis047

Copy link
Copy Markdown
Contributor Author

Keeping this as a draft until the user confirm it works.

@Louis047 Louis047 changed the title [Auto Custom Titlebar Colors]: Fix UWPSpy conflict issue and caching settings [Auto Custom Titlebar Colors]: Smart ignore UWP Apps and browser windows with settings caching Jul 15, 2026
@Louis047
Louis047 marked this pull request as ready for review July 19, 2026 06:56
@Louis047

Copy link
Copy Markdown
Contributor Author

The user has confirmed that it works, but with a very minor caveat where they have to wait a second or two to let it work normally

@m417z

m417z commented Jul 20, 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. Wh_ModUninit wipes the caption color of app-controlled windows. The whole point of the new g_appControlledWindows set is to never touch windows whose owning app set DWMWA_CAPTION_COLOR itself. But UninitEnumWindowsProc doesn't consult that set — it resets DWMWA_CAPTION_COLOR to DWMWA_COLOR_DEFAULT (and clears immersive dark mode) on every eligible window:

// UninitEnumWindowsProc — mods/auto-custom-titlebar-colors.wh.cpp:663
if (!IsWindowEligible(hWnd)) return TRUE;
BOOL off = FALSE;
DwmSetWindowAttribute(hWnd, DWMWA_USE_IMMERSIVE_DARK_MODE, &off, sizeof(off));
const COLORREF def = DWMWA_COLOR_DEFAULT;
DwmSetWindowAttribute(hWnd, DWMWA_CAPTION_COLOR, &def, sizeof(def));

So when the mod is disabled/reloaded, any window the app itself colored gets its color blown away and reset to the system default — the opposite of "leave it alone". Since many apps set their caption color only once (at window creation), it won't come back until the app restarts. Skip windows in g_appControlledWindows here (and ideally only reset windows the mod actually colored). This also makes the unload non-reversible for those windows, which is a core Windhawk principle.

2. g_appControlledWindows / seenWindows grow forever and key on recyclable HWND values. Neither set ever removes an entry when a window is destroyed:

static std::unordered_set<HWND> g_appControlledWindows;   // never erased
...
static std::unordered_set<HWND> seenWindows;              // never erased

Two consequences in a long-lived, broadly-injected (@include *) process like explorer.exe:

  • Stale-HWND false positives: Windows recycles HWND values. Once a tracked window is destroyed, a brand-new, unrelated window can be created with the same handle value — it will be found in g_appControlledWindows and silently never colored (item stays until the process dies).
  • Unbounded growth: both sets accumulate one entry per window ever seen, for the life of the process.

You already hook DefWindowProcW/A, so HandleWindowMessage can handle WM_NCDESTROY and erase(hWnd) from both sets to bound the lifetime and fix the reuse hazard.

Optional improvements

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

  • The two branches in ApplyTitleBar are near-identical. After Wh_ModInit succeeds, DwmSetWindowAttribute_orig is always non-null, so the else branch (lines 380–399) that calls the hooked DwmSetWindowAttribute directly is effectively dead and duplicates the whole color-selection block. Since g_inMod already guards the hook from re-tracking the mod's own calls, you can drop the branch and just call DwmSetWindowAttribute (or _orig) once — the g_inMod flag makes either safe.

  • Stale README note. The ## Notes section still says "systemsettings.exe and applicationframehost.exe are excluded to avoid conflicts" (line 40), but this PR removed those @exclude lines in favor of runtime detection. Update or drop that line.

  • g_settings is written on the settings-change thread and read on window threads without synchronization. It's a POD struct, so the worst case is one repaint using a half-updated struct (e.g. useCustom toggled but a color not yet stored) right after a settings save — no crash, self-corrects on the next paint. Only worth a mention since the pre-cache code read each value atomically per call.

Functionality notes

Non-critical observations about the feature behavior itself.

  • The DwmGetWindowAttribute "already-colored" fallback almost certainly never fires. DWMWA_CAPTION_COLOR is a set-only attribute — DwmGetWindowAttribute returns E_INVALIDARG for it on current Windows, so SUCCEEDED(...) is false and the inner block (and its Wh_Log) never runs:

    // ApplyTitleBar — mods/auto-custom-titlebar-colors.wh.cpp:349
    if (SUCCEEDED(DwmGetWindowAttribute(hWnd, DWMWA_CAPTION_COLOR, &currentColor, sizeof(currentColor)))) {
        if (currentColor != DWMWA_COLOR_DEFAULT) { ... }   // unreachable
    }

    That means an app which set its own caption color before the mod loaded (mid-session enable) isn't detected via this path — only the DwmSetWindowAttribute hook (which catches sets after the hook is installed) works. Easy to confirm: enable logging and check whether the "already has custom … ignoring" line ever appears. If it doesn't, the seenWindows set + this block are pure overhead and can be removed.

  • EnumChildWindows runs on every ApplyTitleBar call, in every process. Each eligibility check that doesn't short-circuit on MozillaWindowClass walks the full child-window tree calling GetClassNameW per descendant, and ApplyTitleBar fires on WM_NCACTIVATE, WM_ACTIVATE, CreateWindowEx, etc. across @include *. For most windows the tree is small so it's fine, but for complex apps this repeats a full subtree walk on every activation. If it shows up in practice, consider caching a per-HWND eligibility verdict (invalidated on WM_NCDESTROY, alongside item 2 above).

  • The DesktopWindowXamlSource check is broad. A growing number of otherwise-normal Win32 apps host XAML islands (DesktopWindowXamlSource) for parts of their UI while still using a standard caption. Those will now be skipped entirely. That's likely the intended trade-off for the crash fix, but worth being aware that the excluded set is wider than "UWP apps" — it's "anything hosting a XAML island / CoreWindow".

@Louis047

Copy link
Copy Markdown
Contributor Author

Noted, will work on this when I have the time.

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