Skip to content

[codex] Add Dynamic Taskbar Transparency mod#4530

Open
r1file wants to merge 2 commits into
ramensoftware:mainfrom
r1file:codex/add-dynamic-taskbar-transparency
Open

[codex] Add Dynamic Taskbar Transparency mod#4530
r1file wants to merge 2 commits into
ramensoftware:mainfrom
r1file:codex/add-dynamic-taskbar-transparency

Conversation

@r1file

@r1file r1file commented Jun 23, 2026

Copy link
Copy Markdown

What changed

Adds dynamic-taskbar-transparency, a Windows 11 taskbar mod that changes the taskbar background based on shell state.

The mod supports configurable appearances for desktop/no-maximized-window, maximized windows, Start, search, task view, tray flyouts, and optional unclassified shell interactions. Supported appearances include existing style/other mods, Windows native/default, clear, blur, and acrylic-like blur.

Difference from taskbar-background-helper

taskbar-background-helper is a lower-level background helper for the Windows 11 taskbar. It mainly applies blur/acrylic/color either always or when a maximized window exists, and it is designed to pair with Taskbar Styler.

This mod is intentionally focused on the TranslucentTB-style shell state machine: desktop/no-maximized-window, maximized windows, Start, search, task view, tray/notification/quick-settings flyouts, and an optional catch-all for other shell-host surfaces can each resolve to a separate configured appearance. It also owns the fade animation between those resolved states. The overlap is the XAML taskbar background surface; the additional behavior is the per-shell-state resolution and transition layer.

Notes

The mod targets explorer.exe and edits the Windows 11 taskbar XAML background rectangles. The blur implementation follows the WindhawkBlur-style approach used by Windows 11 Taskbar Styler / TranslucentTaskbar instead of relying on AcrylicBrush directly.

Review follow-up changes:

  • License changed to GPL-3.0 because the blur stack and taskbar symbol hook approach reuse GPL-compatible Windhawk mod code.
  • Worker lifetime now uses a heap std::thread*, joined and deleted in Wh_ModUninit.
  • Captured XAML brush references are released via each element's dispatcher during unload instead of by a global vector destructor/clear path.
  • State detection is decoupled from animation frames; window enumeration runs at the polling interval while frames only interpolate/apply appearance.
  • Steady-state duplicate UI dispatch is skipped when the appearance did not change.
  • English-only localized title matching was removed from shell-state detection.
  • Worker startup now happens after fallible init hook setup succeeds.

Validation

  • .github/pr_validation.py passed locally with the PR changed-file environment.
  • scripts/compile_mod.py passed locally with Windhawk from C:\Program Files\Windhawk and -ldwmapi -lole32 -loleaut32 -lruntimeobject.
  • PR scope is a single file: mods/dynamic-taskbar-transparency.wh.cpp.

@r1file r1file marked this pull request as ready for review June 23, 2026 06:12
@m417z

m417z commented Jun 23, 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 a well-structured mod and the core architecture (hook TaskListButton/ExperienceToggleButton to discover the XAML background rectangles, manipulate them on the UI thread via the dispatcher, restore on unload) is sound and follows established taskbar-mod patterns. A few things need attention before merge.

1. @license MIT is incompatible with the borrowed GPLv3 code. The whole blur stack — IGraphicsEffectD2D1Interop, the guid_v specialization, CompositeEffect / FloodEffect / GaussianBlurEffect, and the XamlCompositionBrushBase blur brush — is taken almost verbatim from Windows 11 Taskbar Styler, which is GPLv3 (your readme even credits "the WindhawkBlur-style approach used by Windows 11 Taskbar Styler"). GetFrameworkElementFromNative and the three symbol hooks are likewise copied from taskbar-content-presenter-injector. GPLv3 code cannot be relicensed under MIT. Change @license to GPL-3.0 (compatible) or replace the borrowed code with your own implementation.

2. The global std::thread g_workerThread will call std::terminate() on process shutdown. It's only joined in Wh_ModUninit, but Wh_ModUninit does not run when the host process terminates (Explorer restart, sign-out, reboot) — there the CRT runs the global's destructor during DLL_PROCESS_DETACH, the thread is still joinable(), so ~thread() calls std::terminate() and aborts explorer. Use a heap pointer with a trivial destructor instead, and join + delete it in Wh_ModUninit:

std::thread* g_workerThread = nullptr;
// init:    g_workerThread = new std::thread(WorkerLoop);
// uninit:  g_stopWorker = true; g_workerThread->join(); delete g_workerThread;

Relatedly, g_backgroundElements is a global std::vector of Media::Brush (XAML) strong refs; its destructor (at shutdown, and clear() in Wh_ModUninit) releases XAML objects from an arbitrary/shutdown thread rather than their UI thread. Prefer releasing the captured brushes on the dispatcher thread during the restore step, and keep globals trivially-destructible.

3. The worker re-runs full window enumeration on every animation frame. WorkerLoop calls FindTaskbars() + DetectShellActivity() + HasMaximizedWindow() — three separate EnumWindows passes, each doing per-window OpenProcess + QueryFullProcessImageNameW — once per loop iteration, and the loop iterates every frameTimeMs (16 ms) while an animation is in progress. That's ~180 EnumWindows calls/second plus a process-image lookup per top-level window, just to drive a fade. The animation only needs to interpolate opacity between frames; state detection only needs to run at the polling cadence. At minimum, decouple them: detect at pollingIntervalMs, and only interpolate/apply on the in-between frames. Better still, drive detection from events rather than polling — taskbar-background-helper (which does the same "maximized window?" detection) uses SetWinEventHook (EVENT_OBJECT_*, EVENT_SYSTEM_PEEK*) instead of a poll loop.

4. Significant overlap with taskbar-background-helper. That mod (by m417z) already sets the Win11 taskbar background to blur / acrylic / color, "always or only when there's a maximized window," and is designed to pair with the Taskbar Styler — which covers a large part of what this mod does. Your addition is the per-shell-state machine (desktop / Start / search / task view / tray flyout) with fades, which is genuinely more than the existing mod, so this isn't a pure duplicate. But the maintainer strongly prefers extending an existing mod over shipping an overlapping one. Please state plainly in the PR how this differs, and consider whether the state-machine behavior could be contributed to taskbar-background-helper (or whether that mod's maximized-detection could just be reused) rather than reimplemented.

5. No screenshot/GIF in the readme. This is a visual mod, so a short GIF showing the taskbar changing across the states (desktop → maximized → Start, etc.) would help users a lot. taskbar-background-helper includes one. Only i.imgur.com and raw.githubusercontent.com are allowed image hosts.

6. Some of the state detection matches on localized window titlestitle.find(L"task view"), L"notification", L"quick settings" in EnumShellActivityProc. These are English-only and won't match on other UI languages. The class-name (MultitaskingViewFrame, NotifyIconOverflowWindow, …) and process-name checks are language-independent and already cover the main cases, so the title checks are mostly redundant — you could drop them or document the limitation.

Optional improvements

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

  • GetStringSetting reinvents WindhawkUtils::StringSetting (RAII), and its value ? value : L"" guard is dead code — Wh_GetStringSetting never returns NULL, only L"". You can drop the helper and read strings via WindhawkUtils::StringSetting directly.

  • In steady state (animation finished, state unchanged) the worker still calls DispatchApplyAppearance(currentAppearance, false) every pollingIntervalMs, dispatching a redundant Opacity set to the UI thread ~12×/second forever. Skip the dispatch when currentAppearance is unchanged since the last applied frame.

  • Wh_ModInit starts the worker thread before the fallible HookTaskbarViewDllSymbols call, then can return FALSE. On init failure Wh_ModUninit is not called, so the worker thread is left running (and g_stopWorker never set). Start the thread only after all fallible setup has succeeded.

Functionality notes

Non-critical observations about the feature behavior itself.

  • The cross-style transition barely fades. In the visibleStyleChange path the opacity floor is min(opacity, 245) while the styles are both opaque at 255, so a blur→acrylic (or similar) change dips only ~4% before swapping the brush at the midpoint — effectively an abrupt brush swap rather than a visible crossfade. If a smoother handoff is intended, the floor needs to drop much lower (or fade the outgoing brush out fully before fading the incoming one in).

  • CaptureNativeAppearance temporarily clears the rectangle's fill/opacity and the parent grid background on the live element to read the "native" defaults, then restores them. This runs on the UI thread during scanning, but it does briefly mutate the visible element, which could show up as a one-frame flicker when a new background rectangle is first discovered.

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