Skip to content

Add mod to hide taskbar only on desktop - #5234

Open
Sahil-Dashoni wants to merge 17 commits into
ramensoftware:mainfrom
Sahil-Dashoni:main
Open

Add mod to hide taskbar only on desktop#5234
Sahil-Dashoni wants to merge 17 commits into
ramensoftware:mainfrom
Sahil-Dashoni:main

Conversation

@Sahil-Dashoni

@Sahil-Dashoni Sahil-Dashoni commented Aug 27, 2026

Copy link
Copy Markdown

This mod hides the taskbar when the desktop is active and shows it for other windows or when hovering near the bottom edge. It includes configurable settings for hover margin and auto-hide delay.

Difference from Existing Taskbar Auto-Hide Mods

This mod is related to the existing taskbar auto-hide mods, but its trigger and behavior are intentionally different.

taskbar-auto-hide-when-maximized focuses on the state of application windows, such as whether a window is maximized or intersects the taskbar. This mod instead uses the desktop/application state of each monitor: the taskbar is hidden when that monitor is showing only the desktop, and it becomes visible again when an application or Windows shell UI needs it.

taskbar-auto-hide-per-monitor provides per-monitor control over taskbar auto-hide behavior. This mod also supports independent per-monitor behavior, but its purpose is different: it automatically decides whether each taskbar should be visible based on whether an application is present on that monitor. For example, with two monitors, an application can remain open on monitor 2 while the taskbar on monitor 1 hides because monitor 1 is showing only the desktop.

taskbar-auto-hide-custom-activation-area primarily changes the area used to trigger the taskbar's native auto-hide reveal. This mod uses a different model: it hides the taskbar window when the desktop is active and provides a configurable taskbar-sized hover area plus an additional margin for revealing it.

A key difference is also the work-area behavior. This mod intentionally hides the taskbar window without changing the Windows desktop work area. This leaves the existing desktop/work-area layout unchanged, which is part of the reason this mod does not simply use Windows' native auto-hide mechanism.

The goal is therefore not to provide another general-purpose auto-hide implementation, but a specific "hide taskbar only when the desktop is active" behavior with independent per-monitor application state, configurable hover margin, and a configurable reveal/hide delay.

Changelog

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

  • Changelog item 1...
  • Changelog item 2...

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.

This mod hides the taskbar when the desktop is active and shows it for other windows or when hovering near the bottom edge. It includes configurable settings for hover margin and auto-hide delay.
Copilot AI lite review requested due to automatic review settings August 27, 2026 13:53
@windhawk-reviewer windhawk-reviewer Bot added the waiting-for-author The author's turn: request an AI review, or respond to one that was posted. label Aug 27, 2026
@windhawk-reviewer

Copy link
Copy Markdown

Thanks for the pull request! This repository uses a two-stage review: an AI review that you run yourself, followed by a human review.

To get started, comment /ai-review. Once you're happy with the result, comment /ready-for-reviewer to hand it over to a human reviewer.

See the pull request review process for the full details.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

This PR introduces a new Windhawk mod that hides the Windows taskbar when the desktop is active, and reveals it when an app/shell UI becomes active or when the mouse hovers near the reveal zone. It fits into the mods/ collection as a standalone Explorer-targeted behavior mod with configurable hover margin, hide delay, and optional secondary-taskbar handling.

Changes:

  • Adds a new mod implementation with desktop/foreground detection and taskbar show/hide logic.
  • Implements a bottom-edge hover reveal zone with configurable margin and hover hide delay.
  • Adds support for hiding/showing secondary taskbars based on a setting.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment on lines +176 to +180
L"Shell_TrayWnd",
L"Shell_SecondaryTrayWnd",

L"Windows.UI.Core.CoreWindow",
L"Xaml_WindowedPopupClass",
Comment on lines +1316 to +1321
settings.autoHideDelayMs =
static_cast<DWORD>(
Wh_GetIntSetting(
L"autoHideDelayMs"
)
);
Comment on lines +812 to +816
return pt.y >=
monitorInfo.rcMonitor.bottom -
hotZonePx &&

pt.y <
@Sahil-Dashoni

Copy link
Copy Markdown
Author

/ai-review

@windhawk-reviewer windhawk-reviewer Bot added waiting-for-ai-review An AI review was requested and is being prepared. and removed waiting-for-author The author's turn: request an AI review, or respond to one that was posted. labels Aug 27, 2026
@windhawk-reviewer

Copy link
Copy Markdown

Submission review

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

Remember: The AI reviewer can be wrong - it may misread code, flag correct code as broken, or suggest changes that make things worse. Treat its findings as suggestions to verify, not instructions to follow blindly. You're responsible for the code you submit, so if a finding doesn't hold up, say so instead of changing working code to satisfy it.

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


The tool-mod structure and the teardown (message queue created before the ready event, WM_QUIT + WaitForSingleObject(INFINITE) + CloseHandle in WhTool_ModUninit, taskbar restored on exit) are done correctly — that part is solid. The findings below are about the hide mechanism, some stale-state bugs, and the polling loop.

1. Consider extending the existing taskbar auto-hide mod instead of reimplementing auto-hide.
There is already a family of mods that drive the taskbar's native auto-hide dynamically, most directly taskbar-auto-hide-when-maximized ("auto-hide only when a window is maximized or intersects the taskbar"). What this mod does — "auto-hide only when the desktop is active" — is the same idea with a different trigger condition, and would fit naturally as another mode value there. The maintainer's stated preference is to add an option to an existing mod rather than merge a closely related new one, so it's worth opening a feature request on that mod before pursuing this as a separate submission.

Beyond catalog tidiness, the mechanism matters. That mod flips native auto-hide with the taskbar's private TrayUI::_HandleTrayPrivateSettingMessage setting (line 867), i.e. without persisting anything. Doing that gives you, for free, everything this mod has to hand-roll or simply loses:

  • reveal on hover, with the correct activation area and the standard animation (no 100 ms cursor polling, no hand-computed hover rect, no DPI math),
  • keyboard reveal — with ShowWindow(SW_HIDE) the taskbar is genuinely gone, so Win+T / Ctrl+Esc taskbar focus and the tray keyboard access don't reveal it,
  • flashing taskbar buttons and tray notifications remain visible instead of being silently hidden,
  • per-monitor and multi-taskbar behavior handled by the shell.

The mod already stands down when native auto-hide is on, which is itself a sign the two mechanisms are solving the same problem.

2. Stale g_taskbarHidden cache leaves the taskbar in the wrong state.
The state machine trusts a cached bool instead of the taskbar's actual visibility, so any time something else changes it, the mod stops correcting it:

  • Turning off "Hide secondary taskbars" never restores them. WhTool_ModSettingsChanged only stores the new value and relies on the next poll — but with the taskbar hidden on the desktop, UpdateTaskbarState hits the Desktop + taskbar already hidden early-return and never calls SetTaskbarVisibility(true). The secondary taskbars stay hidden until the user activates an app, contradicting the setting description ("When disabled, secondary taskbars are always restored"). Note that WM_APP_REFRESH_STATE exists and is handled in the message loop for exactly this, but it is never posted anywhere — the constant and its handler are currently dead code. PostThreadMessageW(g_threadId, WM_APP_REFRESH_STATE, 0, 0) from WhTool_ModSettingsChanged is the missing call.
  • After an Explorer restart the taskbar stays visible on the desktop. The new Shell_TrayWnd is visible while g_taskbarHidden is still true, so the same early-return skips the hide until the user activates an app and comes back. The same applies whenever Explorer re-shows the taskbar on its own (display change, taskbar settings change). Testing across an Explorer restart is expected before merge.

Deriving the current state from IsWindowVisible(FindPrimaryTaskbar()) instead of the cached flag fixes both cases at once.

3. The mm→px conversion is off by a factor of 10, so the hover margin setting is effectively inert.

int px = MulDiv(mm, static_cast<int>(dpi), 254);

px = mm * dpi / 25.4, so the divisor for whole millimeters is 25.4, not 254. The default 5 mm at 96 DPI yields 2 px instead of ~19 px. Fix:

// px = mm * dpi / 25.4
int px = MulDiv(mm * 10, static_cast<int>(dpi), 254);

4. The 100 ms poll runs forever, including a synchronous cross-process call into Explorer.
Constant polling is a recurring objection in reviews — 500 ms timers have been flagged, and this is a 10 Hz timer that never stops. Every tick does GetForegroundWindow + GetClassNameW (cross-process), FindWindowW, and IsNativeTaskbarAutoHideEnabled()SHAppBarMessage(ABM_GETSTATE), which is a synchronous call into Explorer's tray window. Two problems:

  • Most of it is pure waste: while an application is focused nothing the mod cares about can change without an event firing.
  • If Explorer is hung, the worker can block inside SHAppBarMessage. WhTool_ModUninit then waits on that thread with INFINITE, so the unload blocks and the taskbar stays hidden.

Suggested shape: keep the timer only while it's actually needed (i.e. while g_onDesktopState is true / the taskbar is hidden, which is the only state where cursor hover matters) and KillTimer otherwise; drive the rest from WinEvents — you already have EVENT_SYSTEM_FOREGROUND, and EVENT_SYSTEM_MINIMIZESTART/EVENT_SYSTEM_MINIMIZEEND/EVENT_OBJECT_DESTROY/EVENT_OBJECT_HIDE cover the "last window minimized/closed" transitions. Cache the native auto-hide state and refresh it on state changes rather than ten times a second.

5. Make the worker thread per-monitor DPI aware.
The mod mixes coordinate spaces: GetCursorPos, GetWindowRect and GetMonitorInfoW return values in the calling process's DPI context, while GetDpiForWindow(taskbar) returns the taskbar's real per-monitor DPI. Unless the thread is made per-monitor-v2 aware, on a scaled monitor the margin is computed in physical pixels and applied to virtualized coordinates, and geometry gets unreliable on mixed-DPI setups — which the README explicitly claims to support. Existing tool mods set this explicitly at the top of the worker thread, e.g. proportional-monitor-cursor.wh.cpp#L587:

// DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2, so that all coordinates
// we deal with are real physical pixels.
SetThreadDpiAwarenessContext(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2);

6. Add a screenshot or GIF to the README. This is a visually obvious mod, and a short GIF of the taskbar hiding on the desktop and revealing on hover makes it much easier to understand. Images must be hosted on i.imgur.com or raw.githubusercontent.com.

Optional improvements

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

  • The taskbar-edge detection in IsCursorInTaskbarHoverZone is ~120 lines of no-op. InflateRect(&hoverRect, marginPx, marginPx) already expands all four sides by marginPx, and every one of the four docking branches then assigns exactly the values InflateRect produced, before all of them (and the fallback) return the identical PtInRect(&hoverRect, pt). distanceTop/distanceBottom/distanceLeft/distanceRight, edgeDistance, monitorWidth/monitorHeight/taskbarWidth/taskbarHeight and the four if blocks can all be deleted with zero behavior change — the whole function body after InflateRect collapses to return PtInRect(&hoverRect, pt) != FALSE;. Worth checking whether an edge-specific hover depth was actually intended here (inflating all four sides also means the zone extends sideways past a bottom-docked taskbar, and vertically past a side-docked one), or whether this is just leftover generated code.
  • Keep the tool-mod launcher snippet byte-for-byte identical to the wiki version. The section is reformatted and rewritten (reinterpret_cast instead of C casts, GetModuleHandleW/CreateMutexW/GetModuleFileNameW, PROCESS_INFORMATION pi{}), which is functionally equivalent but makes it harder to diff against the wiki snippet — the file's own comment asks to keep it unchanged. explorer-folder-hover-menu.wh.cpp has a verbatim copy for reference.
  • Formatting. The one-argument-per-line style with blank lines between nearly every statement roughly triples the file length (1563 lines for what is ~500 lines of code). Running clang-format with the repo's usual style would make it much easier to review.
  • Drop the mod name from log messagesWh_Log(L"Hide Taskbar Only on Desktop: Init"). Windhawk already prefixes log output with the mod name; Wh_Log(L">") or a short message is the convention.
  • Negative autoHideDelayMs becomes the maximum delay. Wh_GetIntSetting is cast to DWORD before the range check, so -1 underflows and is then clamped to 60000, i.e. a negative value gives a 60-second delay. Clamp the int to >= 0 first, then cast.
  • Duplicate clamp. UpdateTaskbarState re-clamps delay > 60000 although LoadSettings already clamped it on store.
  • abs is used without including <cstdlib> (it currently compiles only via a transitive include from <algorithm>); std::abs with the explicit include is safer. Moot if the edge-detection block is removed.
  • Millimeters are an unusual unit for a screen-space margin. Pixels are more predictable for users and consistent with other taskbar mods; if you keep mm, the conversion in item 3 above needs fixing either way.

Functionality notes

Non-critical observations and ideas about the feature behavior itself.

  • Clicking the desktop hides the taskbar even when application windows are visible. RefreshDesktopState returns early on IsDesktopWindow(foreground) without consulting AnyOtherVisibleWindowExists(), so clicking the wallpaper with several windows open hides the taskbar immediately. That matches the mod's name, but it's a different behavior from "hides after minimizing/closing the last application" and is worth spelling out in the README.
  • The taskbar can be hidden while it still has keyboard focus. If the user clicks the taskbar and then moves the cursor off it, IsTaskbarForegroundAndUnderCursor fails, the ambiguous-class path runs, and the taskbar can hide out from under an in-progress Win+T navigation. Treating "taskbar is foreground" as non-desktop regardless of cursor position would avoid that.
  • Windows.UI.Core.CoreWindow in IsShellChromeClass (also raised by the Copilot bot): most UWP apps present a top-level ApplicationFrameWindow, so the practical impact is limited, but some app windows are top-level CoreWindows and would be treated as shell chrome by AnyOtherVisibleWindowExists(). Worth verifying with a few UWP apps.
  • Desktop widgets count as applications. EnumWindowsProc accepts any visible, non-cloaked, non-tool top-level window, so Rainmeter skins, wallpaper utilities and always-on-top HUDs will make AnyOtherVisibleWindowExists() return true permanently. Only the ambiguous paths use it, so the effect is limited, but users of such tools may see inconsistent behavior.
  • If the tool process is killed while the taskbar is hidden, the taskbar stays hidden until Explorer restarts or the mod runs again. Inherent to the ShowWindow(SW_HIDE) approach — another point in favor of native auto-hide (item 1), where a dead helper simply leaves the taskbar in a normal state.


Next steps:

  • /ai-review - after pushing fixes, to get a review of the updated code. You can repeat this as many times as you need, but each review is thorough and usually there's no need for more than 2-3 iterations.
  • /ready-for-reviewer - once you're satisfied with the state of the pull request, to hand it over to a human reviewer. If some findings above are left unaddressed, add a short note explaining why.

See the review process for details.

@windhawk-reviewer windhawk-reviewer Bot added waiting-for-author The author's turn: request an AI review, or respond to one that was posted. and removed waiting-for-ai-review An AI review was requested and is being prepared. labels Aug 27, 2026
@Sahil-Dashoni

Copy link
Copy Markdown
Author

/ai-review

@windhawk-reviewer windhawk-reviewer Bot added waiting-for-ai-review An AI review was requested and is being prepared. and removed waiting-for-author The author's turn: request an AI review, or respond to one that was posted. labels Aug 27, 2026
@windhawk-reviewer

Copy link
Copy Markdown

Submission review

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

Remember: The AI reviewer can be wrong - it may misread code, flag correct code as broken, or suggest changes that make things worse. Treat its findings as suggestions to verify, not instructions to follow blindly. You're responsible for the code you submit, so if a finding doesn't hold up, say so instead of changing working code to satisfy it.

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


The tool-mod structure and the teardown path (stop message → WaitForSingleObject(INFINITE)UnhookWinEvent/KillTimer on the worker thread) are done correctly. The issues below are mostly about the hiding mechanism itself and one state-machine hole.

1. The taskbar can be left permanently hidden when Windows' own auto-hide is turned on.

In UpdateTaskbarState, the "don't fight native auto-hide" branch returns without restoring visibility:

if (g_nativeAutoHideEnabled.load(std::memory_order_relaxed)) {
    g_hideDeadline = 0;
    g_shownDueToHover = false;
    StopHoverTimer();
    return;   // <-- taskbar is still SW_HIDE'd if we hid it a moment ago
}

Repro: sit on the desktop until the mod hides the taskbar, then enable "Automatically hide the taskbar" in Settings. The next refresh sees the native flag, stands down, and the taskbar window stays hidden. Native auto-hide can't rescue it either, because SW_HIDE beats the slide-in — the only way back is disabling the mod or restarting Explorer. Hand the taskbar back before returning:

    StopHoverTimer();
    SetTaskbarVisibility(true);
    return;

2. Substantial overlap with existing mods, and the hiding mechanism is the blunt one.

There are already several mods in this space:

  • taskbar-auto-hide-when-maximized — conditional auto-hide driven by window state, with a mode setting (intersected / maximized / never), per-monitor and exclusion options.
  • taskbar-fade — also a windhawk.exe tool mod that hides the taskbar and wakes it on hover, and already has a SmartIdle option described as "Only hide on empty desktop".
  • taskbar-auto-hide-keyboard-only — "prevent the taskbar from showing at all", plus hotkey/mouse show-or-toggle.

Your trigger condition (desktop focused) is genuinely different from "a window is maximized" and from "idle for N seconds", so this isn't a straight duplicate — but "auto-hide the taskbar when condition X holds" is exactly what taskbar-auto-hide-when-maximized already models with its mode setting, and the maintainer's consistent preference is an extra option on the existing mod over a fourth mod in the same area. Please say explicitly in the PR how this differs from those three, or consider proposing a new mode value there instead.

Related, and the reason the existing mod is worth studying: it doesn't hide the window at all — it toggles the taskbar's native auto-hide state via TrayUI::_HandleTrayPrivateSettingMessage (here):

SendMessage(hTaskbarWnd, kHandleTrayPrivateSettingMessage,
            kTrayPrivateSettingAutoHideSet, FALSE);

With that approach Windows keeps ownership of reveal-on-hover, the slide animation, keyboard access (Win+T, Win+number, tray navigation), flashing taskbar buttons, and the docking edge — so the whole 200 ms cursor-polling loop, the hover-rect math and the hideSecondaryTaskbars special-casing disappear, and item 3 below stops being a concern. Your README says the work area is deliberately left unchanged, which is a real difference from native auto-hide, so this isn't a mechanical swap — but it's worth weighing that one difference against everything SW_HIDE costs.

3. The hidden state isn't crash-safe.

Restoration only happens in WhTool_ModUninit / at the end of HookThread. If the dedicated windhawk.exe tool process is killed, crashes, or is terminated during an ungraceful shutdown while the taskbar is hidden, the taskbar stays SW_HIDE'd and the user has no in-Windows way to get it back short of restarting Explorer — and unlike a normal mod bug, disabling the mod after the fact won't help, since the process that would restore it is already gone. This is inherent to hiding the window from another process; the native auto-hide toggle in item 2 doesn't have this failure mode. If you keep the current mechanism, please at least call it out in the README.

4. Every window show/hide anywhere on the system triggers a synchronous cross-process call into Explorer.

WM_APP_REFRESH_STATE handling starts with RefreshNativeTaskbarAutoHideState()SHAppBarMessage(ABM_GETSTATE), which is a synchronous call into the shell. Those refreshes are driven by six system-wide WINEVENT_OUTOFCONTEXT hooks including EVENT_OBJECT_SHOW / EVENT_OBJECT_HIDE / EVENT_OBJECT_DESTROY, which fire for every window in every process — menus (#32768), tooltips, combo-box dropdowns, child controls being shown, every window creation and destruction. The g_refreshPosted debounce collapses a burst into one refresh per loop iteration, but on a busy desktop that's still many appbar round-trips per second, plus a possible full AnyOtherVisibleWindowExists() sweep (a system-wide EnumWindows with a DwmGetWindowAttribute per window) on each one. It also means the worker thread can be parked inside Explorer at any moment — and WhTool_ModUninit waits on that thread with INFINITE.

Two fixes:

  • Don't re-query the native auto-hide state per event. It changes only when the user changes a setting — query it in WhTool_ModInit, in WhTool_ModSettingsChanged, and on WM_SETTINGCHANGE / display changes.
  • Narrow the hook set. EVENT_SYSTEM_FOREGROUND fires when the last window closes and focus falls back to Progman/WorkerW, so EVENT_OBJECT_SHOW/HIDE may be droppable entirely; if you keep them, at least merge the registrations into ranges (EVENT_SYSTEM_MINIMIZESTARTMINIMIZEEND, EVENT_OBJECT_DESTROYEVENT_OBJECT_HIDE) so it's 3 hooks instead of 6.

5. The README has no screenshot or GIF.

This is a visual mod, and users browse the catalog by its README. Please add a short GIF showing the taskbar hiding on the desktop and revealing on hover — see the demo in taskbar-auto-hide-when-maximized for the format. Only i.imgur.com and raw.githubusercontent.com are allowed image hosts.

Optional improvements

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

  • Keep the tool-mod launcher snippet verbatim. The block at the bottom is functionally equivalent but has been reflowed throughout, and the comment above it asks to keep it unchanged while the code below has in fact been changed. Keeping it byte-identical to the wiki snippet makes it trivial to diff during review and to re-sync when the snippet is updated.

    In particular, the added ARRAYSIZE(commandLine) argument and its explanatory comment ("MinGW's swprintf_s requires the destination buffer size … caused the PR build to fail") rest on a wrong diagnosis — the array-deducing overload works fine on the Windhawk toolchain, and 20+ merged tool mods use the 2-argument form, e.g. always-on-top.wh.cpp:588. Whatever broke that build was something else.

  • Drop the mod-name prefix from log strings. Wh_Log(L"Hide Taskbar Only on Desktop: Init") — Windhawk already prefixes log lines with the mod name, so Wh_Log(L">") (or just the message) is enough.

  • Clamp autoHideDelayMs before the DWORD cast. LoadSettings casts the int from Wh_GetIntSetting to DWORD and only then clamps to 60000, so a negative value wraps to a huge number and lands on the maximum 60-second delay — surprising. Clamp the signed value first. (Also, the same 60000 clamp is repeated in UpdateTaskbarState; one of the two is redundant.)

  • WinEventProc's if (!hwnd) branch is identical to the code below it — both just call RequestStateRefresh(), so the branch and the comment block that explains a distinction the code doesn't make can go. Similarly, IsRelevantWinEvent re-checks exactly the events the six hooks were registered for.

  • Trim IsShellChromeClass. tooltips_class32, SysShadow, MSCTFIME UI and IME are already filtered out by the WS_EX_TOOLWINDOW check or the owner-window check further down in EnumWindowsProc. Worth verifying and removing the ones that never actually match.

  • Consider pixels instead of millimetres for the hover margin. DPI-independence is nice, but mm is an unusual unit for a Windows UI setting and users can't easily reason about it; the comparable setting in taskbar-auto-hide-custom-activation-area uses pixels/percentages.

Functionality notes

Non-critical observations and ideas about the feature behavior itself.

  • The hover timer isn't as narrow as the README suggests. The README says the mod "only uses a short timer while cursor hover handling is actually needed", but every branch of the desktop path ends in EnsureHoverTimer(true) — so a 200 ms timer runs continuously for the entire time the desktop is focused, which for this mod's use case is most of the time it's doing anything. Each tick calls RefreshDesktopState() + IsCursorInTaskbarHoverZone(), i.e. GetForegroundWindow, FindWindowW, a FindWindowExW loop over secondary taskbars, GetDpiForWindow, GetMonitorInfoW — and when the foreground window is Shell_TrayWnd (ambiguous path), a full system-wide EnumWindows with a DwmGetWindowAttribute per window, every 200 ms. Cheap wins: do the GetCursorPos test against a cached hover rect first and only re-resolve the taskbar/monitor when the point falls near it, and invalidate that cache on display-change / taskbar-recreated events.

  • The taskbar can stay visible longer than intended after you click it. Clicking empty taskbar space makes Shell_TrayWnd the foreground window; with the cursor still inside it, IsTaskbarForegroundAndUnderCursor sets g_onDesktopState = false, which stops the hover timer. Move the cursor away and nothing re-evaluates — EVENT_SYSTEM_FOREGROUND won't fire because the foreground window hasn't changed — so the taskbar lingers until some unrelated WinEvent happens to arrive. On a quiet desktop that can be a while. Keeping the timer alive while the taskbar is the foreground window would close the gap.

  • Windows.UI.Core.CoreWindow in IsShellChromeClass (also raised by the Copilot bot). The impact is narrower than that comment suggests, since the list is only consulted by EnumWindowsProc and modern UWP apps are hosted in ApplicationFrameWindow — but it's worth double-checking on your setup that no app you care about ends up ignored.

  • Re: the Copilot comment about non-bottom taskbars — the current code derives the hover zone from GetWindowRect(taskbar) + InflateRect, which already handles top/left/right docking without assuming an edge, so I don't think that one needs a change.

  • Hovering a secondary taskbar reveals every taskbar. SetTaskbarVisibility(true) shows the primary and all secondaries together, so a hover on monitor 2 also pops the primary taskbar on monitor 1. Reasonable as a default, but revealing only the hovered monitor's taskbar may be what people expect from a per-monitor reveal.

  • Nothing gains screen space. Because the work area is intentionally left untouched, the hidden taskbar leaves a permanent wallpaper strip rather than giving windows or desktop icons the room back. That's clearly deliberate, but it's the main behavioural difference from native auto-hide and worth stating up front in the README so users pick the right mod.


Next steps:

  • /ai-review - after pushing fixes, to get a review of the updated code. You can repeat this as many times as you need, but each review is thorough and usually there's no need for more than 2-3 iterations.
  • /ready-for-reviewer - once you're satisfied with the state of the pull request, to hand it over to a human reviewer. If some findings above are left unaddressed, add a short note explaining why.

See the review process for details.

@windhawk-reviewer windhawk-reviewer Bot added waiting-for-author The author's turn: request an AI review, or respond to one that was posted. and removed waiting-for-ai-review An AI review was requested and is being prepared. labels Aug 27, 2026
…nt hooks

Updated mod metadata and improved event handling for taskbar visibility.
@Sahil-Dashoni

Copy link
Copy Markdown
Author

/ai-review

@windhawk-reviewer windhawk-reviewer Bot added waiting-for-ai-review An AI review was requested and is being prepared. and removed waiting-for-author The author's turn: request an AI review, or respond to one that was posted. labels Aug 27, 2026
@windhawk-reviewer

Copy link
Copy Markdown

Submission review

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

Remember: The AI reviewer can be wrong - it may misread code, flag correct code as broken, or suggest changes that make things worse. Treat its findings as suggestions to verify, not instructions to follow blindly. You're responsible for the code you submit, so if a finding doesn't hold up, say so instead of changing working code to satisfy it.

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


Nice work on the structure — the tool-mod choice is right for this (no function hooks, only SetWinEventHook/EnumWindows/ShowWindow), the worker thread is properly signalled and joined in WhTool_ModUninit, the settings block matches the code, and there are no problematic global destructors. A few things to look at:

1. Overlap with existing mods — please explain how this differs, or consider extending one of them instead.

The catalog already has a very close mod: taskbar-fade. It is also a tool mod (@include windhawk.exe), and with EnableIdleFade + SmartIdle ("Only hide on empty desktop") it hides the taskbar when the desktop is focused and restores it when the cursor comes back to the taskbar area — the same user-visible behavior this mod provides. The differences I can see are that taskbar-fade gates on an idle timeout rather than hiding immediately, and hides via layered-window alpha rather than SW_HIDE.

There is also taskbar-auto-hide-when-maximized, which is the established "conditional taskbar auto-hide" mod and already exposes a mode dropdown; it drives Explorer's own auto-hide (hooking TrayUI::_Hide / CSecondaryTray::_AutoHide) so the native animation, reveal and work-area handling are preserved.

The maintainer's strong preference is to extend an existing mod (add an option, or PR the original author's repo) rather than merge a near-duplicate, since duplicates fragment the catalog. A "hide immediately when the desktop is focused (no idle timeout)" option in taskbar-fade would cover this. If you believe the behavior is distinct enough to warrant a separate mod, please spell out in the PR description and README exactly what this does that those two can't.

2. The taskbar gets stuck visible after you click it, until you click something else.

When the taskbar is the foreground window and the cursor is over it, RefreshDesktopState sets g_onDesktopState = false (line 315-318), and UpdateTaskbarState then takes the "application state" branch which shows the taskbar and stops the hover timer (lines 709-717). Moving the cursor away does not generate EVENT_SYSTEM_FOREGROUND/MINIMIZESTART/MINIMIZEEND, and the timer is gone — so nothing re-evaluates the state and the taskbar stays visible indefinitely.

Repro: with no application windows open, hover the bottom edge to reveal the taskbar, click an empty spot on it, then move the mouse to the middle of the screen. The taskbar stays up until you click the desktop or activate a window.

The fix is to keep the timer alive whenever the "not desktop" verdict was caused by the taskbar itself, e.g.:

// RefreshDesktopState()
if (IsTaskbarForegroundAndUnderCursor(foreground)) {
    g_onDesktopState = false;
    g_taskbarIsForeground = true;   // new flag
    return;
}
...
g_taskbarIsForeground = false;
// UpdateTaskbarState(), "application state" branch
if (!g_onDesktopState) {
    g_hideDeadline = 0;
    g_shownDueToHover = false;
    SetTaskbarVisibility(true);
    EnsureHoverTimer(g_taskbarIsForeground);  // keep polling while the taskbar has focus
    return;
}

3. The README has no screenshot or GIF.

This mod has an obvious visual effect, so a short GIF showing the hide-on-desktop and hover-reveal behavior would help a lot — see taskbar-auto-hide-when-maximized for an example. Only i.imgur.com and raw.githubusercontent.com are allowed image hosts.

Optional improvements

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

  • Keep the tool-mod launcher boilerplate verbatim. The block at the bottom has been reformatted and modified relative to the wiki snippet (explicit ARRAYSIZE argument to swprintf_s, CreateMutexW/GetModuleHandleW/GetModuleFileNameW, an extra GetLastError() in the CreateProcess failed log, added comment blocks, different wrapping). Keeping it byte-identical makes it much easier to diff against the canonical version when it changes. Note that the comment at line 1477-1481 ("MinGW's swprintf_s requires the destination buffer size... caused the PR build to fail") isn't accurate — the template form swprintf_s(commandLine, L"\"%s\" -tool-mod \"%s\"", ...) compiles fine on the Windhawk toolchain; it's used verbatim in dozens of merged mods, e.g. explorer-folder-hover-menu. Whatever broke the build was something else.

  • autoHideDelayMs is cast to DWORD before clamping (lines 1065-1087). A negative value in the settings underflows to a huge DWORD and is then clamped to 60000, so "-1" silently becomes a 60-second delay. Clamp the signed value first:

    int delayMs = Wh_GetIntSetting(L"autoHideDelayMs");
    if (delayMs < 0) delayMs = 0;
    if (delayMs > 60000) delayMs = 60000;

    (The same clamp is then duplicated in UpdateTaskbarState at lines 785-787 — it can go once LoadSettings guarantees the range.)

  • RequestStateRefresh can drop the refreshNativeAutoHide request. If a plain refresh is already pending, the coalescing flag makes the second call return early (lines 812-822) and the kRefreshNativeAutoHide bit is lost, so WhTool_ModSettingsChanged's re-query of the native auto-hide state is skipped. Consider a separate std::atomic<bool> g_refreshNativeAutoHidePending that the worker consumes, instead of encoding it in wParam.

  • SHAppBarMessage(ABM_GETSTATE) runs on every system-wide foreground change (lines 879, 1008-1010). That's a synchronous call into Explorer on every window activation in the session. The native auto-hide setting changes very rarely; querying it on the hover timer, or registering an appbar and listening for ABN_STATECHANGE, would be cheaper.

  • Taskbar handles are re-looked-up on every 200 ms tick. UpdateTaskbarState calls FindWindowW/FindWindowExW several times per tick (FindPrimaryTaskbar in SetTaskbarVisibility, again in IsPrimaryTaskbarHidden, plus the Shell_SecondaryTrayWnd enumeration in SetSecondaryTaskbarsVisibility, plus FindTaskbarForMonitor). Caching the handles and refreshing them when IsWindow() fails (or on RegisterWindowMessage(L"TaskbarCreated")) would cut most of that.

  • kMaxWinEventHooks is 3 but only two hooks are installed (lines 110, 969-979), so slot 2 is permanently null. Size the array to what's used.

  • Drop the mod name from Wh_Log strings (lines 1111-1113, 1216-1218, 1232-1234). Windhawk already prefixes log lines with the mod name, so Wh_Log(L"Init") / Wh_Log(L">") is enough.

  • IsShellChromeClass is partly redundant. Shell_TrayWnd/Shell_SecondaryTrayWnd are already filtered by the IsTaskbarWindow check three lines earlier (line 202), and SysShadow, tooltips_class32, MSCTFIME UI and IME are all WS_EX_TOOLWINDOW and/or owned windows, which the checks at lines 218 and 249 already reject. Trimming the list makes it clearer which entries are actually load-bearing.

  • IsAmbiguousForegroundClass duplicates IsTaskbarWindow's class comparison (lines 186-187 vs. 281-282). IsAmbiguousForegroundClass could just take the HWND and call IsTaskbarWindow.

Functionality notes

Non-critical observations and ideas about the feature behavior itself.

  • Windows.UI.Core.CoreWindow in the shell-chrome list (line 156) — a previous automated comment flagged this as hiding real UWP app windows. In practice most UWP apps are enumerated as ApplicationFrameWindow (owned by ApplicationFrameHost.exe), so the common cases still work, and excluding CoreWindow is what keeps the Start menu / Search from counting as "an app". Worth double-checking against the couple of app types that do expose a top-level CoreWindow though, since the DWMWA_CLOAKED check at lines 222-234 already rejects the shell surfaces when they're dismissed — you may not need the class exclusion at all.

  • SW_HIDE leaves the work area reserved, which the README documents as intentional. Two consequences worth being aware of: the desktop keeps a blank strip where the taskbar was (icons won't reflow into it), and you lose the native slide-in/out animation that Explorer's own auto-hide gives. That's a reasonable trade-off for avoiding icon reflow, just make sure it's what you want.

  • If the tool process dies abnormally (crash, taskkill, etc.) the taskbar stays hidden, and with no taskbar and no Start button the user has to restart Explorer to get it back. WhTool_ModUninit handles the normal disable/update path correctly, so this is only about the abnormal case — but it's worth a line in the README so users know the recovery path (Ctrl+Shift+Esc → restart Windows Explorer).

  • Explorer sometimes re-shows the taskbar on its own (taskbar button flash for an inactive window, exiting a full-screen app, TaskbarCreated after an Explorer restart). The mod recovers on the next 200 ms tick, so the user sees a brief flicker rather than a stuck taskbar — fine, just noting it.

  • The native auto-hide state is only refreshed on foreground changes. If a user turns Windows' own taskbar auto-hide on while sitting on the desktop, the mod keeps fighting it until the next window activation. Refreshing the cached state on the hover timer (see the ABN_STATECHANGE note above) would close that window.

  • Millimeters as the unit for the hover margin is DPI-independent, which is a nice property, but it's an unusual unit for this kind of setting and users are more likely to think in pixels. Worth considering whether pixels-at-96-DPI (scaled internally) reads better.


Next steps:

  • /ai-review - after pushing fixes, to get a review of the updated code. You can repeat this as many times as you need, but each review is thorough and usually there's no need for more than 2-3 iterations.
  • /ready-for-reviewer - once you're satisfied with the state of the pull request, to hand it over to a human reviewer. If some findings above are left unaddressed, add a short note explaining why.

See the review process for details.

@windhawk-reviewer windhawk-reviewer Bot added waiting-for-author The author's turn: request an AI review, or respond to one that was posted. and removed waiting-for-ai-review An AI review was requested and is being prepared. labels Aug 27, 2026
Added logic to manage taskbar focus state and desktop visibility.
@windhawk-reviewer

Copy link
Copy Markdown

This pull request has already had 3 AI reviews in the last 24 hours, which is the limit, so no review was posted this time.

Comment /ai-review again after 2026-08-29 14:24 UTC (in 22 hours) to get another one.

@windhawk-reviewer windhawk-reviewer Bot added waiting-for-author The author's turn: request an AI review, or respond to one that was posted. and removed waiting-for-ai-review An AI review was requested and is being prepared. labels Aug 28, 2026
@Sahil-Dashoni

Copy link
Copy Markdown
Author

/ai-review

@windhawk-reviewer windhawk-reviewer Bot added waiting-for-ai-review An AI review was requested and is being prepared. and removed waiting-for-author The author's turn: request an AI review, or respond to one that was posted. labels Aug 29, 2026
@windhawk-reviewer

Copy link
Copy Markdown

Submission review

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

Remember: The AI reviewer can be wrong - it may misread code, flag correct code as broken, or suggest changes that make things worse. Treat its findings as suggestions to verify, not instructions to follow blindly. You're responsible for the code you submit, so if a finding doesn't hold up, say so instead of changing working code to satisfy it.

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


The overall structure is sound — the tool-mod split, the per-monitor state array, the join-in-uninit teardown and the WinEvent + timer mix are all done correctly, and there are no lifetime/unloadability problems. The findings below are mostly about how much work the 200 ms tick does and about a couple of over-broad window classifications.

1. Every timer tick runs 3+ full EnumWindows sweeps, and the timer effectively never stops

Per 200 ms tick the mod currently does:

  • ScanApplicationWindows()EnumWindows I LOVE IT #1, with a DwmGetWindowAttribute(DWMWA_CLOAKED) for every candidate window (an out-of-process query).
  • RefreshTaskbarForegroundState()IsShellUiForegroundOnMonitorIsShellUiWindowOpenProcess + QueryFullProcessImageNameW on the foreground window, once per taskbar.
  • UpdateTaskbarState()IsAltTabActive()EnumWindows added brave to chrome-wheel-scroll-tabs #2, plus HasVisibleShellFlyout(taskbar.monitor)EnumWindows [Slick Window Arrangement] Hotkey to disable snapping #3…N (once per taskbar), again with DwmGetWindowAttribute per matching window.

And the timer gating never lets it stop in the mod's own steady state — needTimer is set when taskbar.hasApplication == false or IsTaskbarActuallyHidden(taskbar.hwnd), both of which are true exactly when the mod is doing its job (desktop showing, taskbar hidden). So this is a permanent 5 Hz system-wide window enumeration in a background process.

Fix: do one EnumWindows pass per tick and collect everything in a single callback — per-monitor hasApplication, per-monitor shell-flyout presence, and the Alt+Tab window — instead of one pass per concern per monitor. Cache the foreground window's process name keyed by (HWND, PID) so OpenProcess only runs when the foreground actually changes (you already get EVENT_SYSTEM_FOREGROUND, so this can be computed there rather than on the timer). tray-hover-expand.wh.cpp is a good reference for the shape: it polls too, but the per-tick path is local Win32 calls only and every expensive query is throttled behind its own interval.

2. HasVisibleShellFlyout matches on class alone, and Xaml_WindowedPopupClass is not shell-specific

IsShellUiClass (line 445) matches Xaml_WindowedPopupClass, and IsVisibleShellFlyoutForMonitor deliberately uses only the class, not the owning process (see the comment at lines 492–496). But Xaml_WindowedPopupClass is the popup host class used by every XAML/WinUI app, not just shell flyouts — your own IsShellChromeClass lists it for the same reason. A single lingering visible XAML popup from any app on that monitor makes HasVisibleShellFlyout return true forever, and the taskbar then never hides — i.e. the mod silently stops working with no way for the user to tell why.

The comment is right that the process check alone is also insufficient (those hosts own other windows). The fix is to require both: the class must be a flyout class and the owner must be one of the known shell processes. You already have IsShellUiWindow for the second half:

if (!IsShellUiClass(className) || !IsShellUiWindow(hwnd)) {
    return false;
}

3. Killing the tool process leaves the taskbar hidden until Explorer is restarted

The README documents this, but it is worth reconsidering rather than documenting: Shell_TrayWnd belongs to explorer.exe, and the only thing that will ever un-hide it is this separate windhawk.exe process running its cleanup path. Any TerminateProcess (Task Manager, a crash, a hung shutdown) leaves the user with no taskbar and no obvious recovery.

Hosting the mod in explorer.exe (@include explorer.exe) removes the failure mode entirely: the taskbar is the host process's own window, so an Explorer crash/restart recreates it visible, and Wh_ModUninit restores it on every normal unload. The mod installs no function hooks, so the tool-mod pattern fits mechanically — but the thing being manipulated lives in Explorer, which is what makes the out-of-process split costly here. (An alternative worth considering is driving Windows' own auto-hide instead of ShowWindow, which also fixes the work-area mismatch noted in your README — see taskbar-auto-hide-when-maximized.wh.cpp for how the tray's private auto-hide setting is toggled and restored.)

4. Overlap with taskbar-fade

taskbar-fade is also a @include windhawk.exe tool mod that hides the taskbar, has a "Smart Idle (Only hide on empty desktop)" option, reveals on hover at the bottom edge, and has a configurable hover/fade delay. The catalog already has several taskbar auto-hide mods, and the maintainer's strong preference is to extend an existing mod rather than merge a near-neighbour.

Please state explicitly in the PR how this differs — as far as I can tell the real difference is immediate, focus-driven hiding versus idle-timeout hiding, plus independent per-monitor evaluation. If that's the whole delta, adding a "hide as soon as only the desktop is visible" option (idle timeout of 0) to taskbar-fade is likely the better outcome for users.

5. README has no screenshot or GIF

The mod has a very visible effect. Please add a short GIF showing the taskbar hiding on the desktop and revealing on hover — only i.imgur.com and raw.githubusercontent.com are allowed as image hosts.

Optional improvements

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

  • Window class is never unregistered, and ERROR_CLASS_ALREADY_EXISTS is swallowed. CreateSystemMessageWindow (line 1375) continues when RegisterClassW fails with ERROR_CLASS_ALREADY_EXISTS, and DestroySystemMessageWindow destroys the window but never calls UnregisterClass. Today this is masked because Wh_ModUninit ends in ExitProcess(0), so the registration never outlives the mod — but it's a fragile thing to depend on. Treat ERROR_CLASS_ALREADY_EXISTS as a failure and UnregisterClass(kSystemMessageWindowClass, hInstance) in the teardown path, so the class is always registered fresh against the current WndProc. Also note CreateSystemMessageWindow()'s return value is discarded at line 1559.

  • The tool-mod launcher boilerplate has been reformatted. The block from line 1930 down is functionally equivalent to the wiki snippet (CreateMutexW/GetModuleHandleW/GetModuleFileNameW are the same symbols under UNICODE), but it has been rewrapped, the STARTUPINFO designated initializer was converted to assignments, LPSTARTUPINFOW became LPSTARTUPINFO, and a %lu was added to a log line. The convention is to paste the wiki snippet verbatim so it stays diffable when it's updated — the file's own comment at line 1927 already asks for that. See explorer-folder-hover-menu.wh.cpp for a verbatim copy.

  • Drop the mod-name prefix from log messages. Wh_Log(L"Hide Taskbar Only on Desktop: Init") (lines 1740, 1845, 1866) — Windhawk already prefixes the mod name and function, so Wh_Log(L">") or just the message is enough.

  • Leftover empty comment block. Lines 1851–1855 in WhTool_ModSettingsChanged describe refreshing "native taskbar auto-hide … here" but nothing follows it — looks like a remnant of the earlier SHAppBarMessage approach.

  • Stray indentation at line 1590 (RefreshPerMonitorState(); is indented one extra level relative to the following line).

  • kMaxTaskbars = 16 silently drops extra taskbars. addTaskbar returns early past 16; a taskbar beyond the limit would never be managed and never restored. Worth at least a Wh_Log when the cap is hit.

  • Millimeter units for the hover margin are unusual for a Windhawk setting — pixels (converted with the monitor's DPI, as you already do) would match the rest of the catalog and be easier for users to reason about.

Functionality notes

Non-critical observations and ideas about the feature behavior itself.

  • Alt+Tab handling doesn't work on Windows 11. IsAltTabActive() (line 631) looks for class #32771, which is the Windows 10-era switcher; Windows 11 hosts Alt+Tab in XamlExplorerHostIslandWindow. The GetAsyncKeyState(VK_MENU) && GetAsyncKeyState(VK_TAB) fallback is only sampled on the 200 ms timer, and Tab is typically down for a few milliseconds, so it will almost never coincide with a tick. In practice shownDueToAltTab and the README's "Keeps the taskbar visible while Alt+Tab is open, then uses the same configurable hide delay after Alt+Tab closes" are effectively dead on Windows 11. taskbar-auto-hide-when-maximized.wh.cpp shows a reliable detection (class + window band + thread description), and the same mod tracks the switcher via WinEvents rather than polling.

  • The reveal zone is quite large. IsCursorInTaskbarHoverZone inflates the full (hidden) taskbar rect by the margin, so with a ~48 px taskbar and the default 5 mm the taskbar reappears whenever the cursor enters roughly the bottom 65–70 px of the screen. Native auto-hide triggers only at the very edge. Consider using a thin strip at the docked edge as the reveal trigger, and the full taskbar rect + margin only as the keep-visible region once it's shown — that would also make the "extra margin" setting do something more predictable.

  • Windows.UI.Core.CoreWindow is treated as shell chrome in IsShellChromeClass, so any app whose top-level window uses that class (fullscreen UWP games, Game Bar, and similar) isn't counted as an application and won't keep the taskbar visible. Most UWP apps are hosted in ApplicationFrameWindow so this is usually fine, but the same class+process pairing suggested in item 2 above would make it exact.

  • Some state transitions are only caught because the timer never stops. The mod hooks EVENT_SYSTEM_FOREGROUND and EVENT_SYSTEM_MINIMIZESTART/END only, so closing a background window on a second monitor, or dragging the last window off a monitor, produces no event. Today the permanent 200 ms tick papers over this — but if you fix item 1 by making the timer conditional, these cases will need EVENT_OBJECT_DESTROY/EVENT_OBJECT_LOCATIONCHANGE (filtered) or a coarse fallback tick.

  • hasApplication uses IntersectRect against the whole monitor rect, so a window that overlaps a monitor by one pixel keeps that monitor's taskbar visible. Requiring a minimum intersection area (or that the window's center falls on the monitor) would behave better with windows straddling two displays.


Next steps:

  • /ai-review - after pushing fixes, to get a review of the updated code. You can repeat this as many times as you need, but each review is thorough and usually there's no need for more than 2-3 iterations.
  • /ready-for-reviewer - once you're satisfied with the state of the pull request, to hand it over to a human reviewer. If some findings above are left unaddressed, add a short note explaining why.

See the review process for details.

@windhawk-reviewer windhawk-reviewer Bot added waiting-for-author The author's turn: request an AI review, or respond to one that was posted. and removed waiting-for-ai-review An AI review was requested and is being prepared. labels Aug 29, 2026
@Sahil-Dashoni

Copy link
Copy Markdown
Author

/ai-review

@windhawk-reviewer windhawk-reviewer Bot added waiting-for-ai-review An AI review was requested and is being prepared. and removed waiting-for-author The author's turn: request an AI review, or respond to one that was posted. labels Aug 29, 2026
@windhawk-reviewer

Copy link
Copy Markdown

Submission review

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

Remember: The AI reviewer can be wrong - it may misread code, flag correct code as broken, or suggest changes that make things worse. Treat its findings as suggestions to verify, not instructions to follow blindly. You're responsible for the code you submit, so if a finding doesn't hold up, say so instead of changing working code to satisfy it.

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


Nice work adopting the tool-mod pattern and per-monitor state — the structure is in good shape. A few things need attention before merge:

1. The 200 ms tick re-scans the whole desktop several times per second, forever, while the desktop is showing.

needTimer is true whenever any monitor has no application (taskbar.hasApplication == false), which is exactly the mod's steady state. Each tick then runs:

  • RefreshPerMonitorState() → one EnumWindows with a cross-process DwmGetWindowAttribute(DWMWA_CLOAKED) (an RPC to dwm.exe) per visible top-level window;
  • per taskbar, HasVisibleShellFlyout(taskbar.monitor) → another full EnumWindows, plus OpenProcess + QueryFullProcessImageNameW for every matching window;
  • per taskbar, IsAltTabActive() → yet another full EnumWindows.

On a two-monitor setup that's 5 full window enumerations plus dozens of DWM RPCs and process-handle opens, 5×/second, indefinitely on an idle desktop. Two concrete fixes:

  • IsAltTabActive() does not depend on the taskbar — it is called g_taskbarCount times per tick with an identical result. Hoist it above the loop. Likewise, HasVisibleShellFlyout can be a single EnumWindows that fills a per-monitor found[] array, the same way ScanApplicationWindows already does for hasApplication.
  • The timer exists only to track the cursor (per the comment at line ~1754). Let the timer tick do just that — GetCursorPos against the cached taskbar rects plus the hideDeadline checks — and leave the full RefreshPerMonitorState() to the WinEvent path, which is already installed for exactly those transitions.

2. WM_SETTINGCHANGE runs the full scan synchronously inside a broadcast.

SystemMessageWindowProc calls RefreshPerMonitorState() + UpdateTaskbarState() directly. WM_SETTINGCHANGE is broadcast to every top-level window via SendMessageTimeout and fires often (theme, policy, environment, locale changes), so every one of those broadcasts now blocks on the multi-EnumWindows scan described above. Post the work to your own queue instead — you already have the mechanism:

case WM_SETTINGCHANGE:
case WM_DISPLAYCHANGE:
    RequestStateRefresh();
    return 0;

3. The application scan skips Windows.UI.Core.CoreWindow / Xaml_WindowedPopupClass without the process check you use everywhere else.

EnumWindowsProc (line ~267) bails on IsShellChromeClass(className) || IsShellUiClass(className), and both lists contain Windows.UI.Core.CoreWindow and Xaml_WindowedPopupClass. Neither class is exclusive to the shell. IsVisibleShellFlyoutForMonitor already guards against this correctly — it requires explorer.exe or a known shell-UI process before treating a window as a flyout (lines ~522-545), with the comment "XAML popup classes are used by ordinary applications too." The app scan needs the same guard; otherwise a third-party window with one of those classes makes the monitor look empty and the taskbar hides underneath a visible app. Extract that process check into a helper and use it in both places.

4. If the tool process dies while the taskbar is hidden, the taskbar stays hidden until Explorer restarts.

The README documents this, which is good, but it's a fairly harsh failure mode for a crash/kill: ShowWindow(SW_HIDE) on Shell_TrayWnd leaves no way to reveal it, and Windhawk's model is that a mod's effects go away when it stops. There is an established alternative in the repo — taskbar-auto-hide-when-maximized drives Windows' native auto-hide state instead (see its kTrayPrivateSettingAutoHideGet/Set usage), which keeps the taskbar hover-reachable and self-recovers if the mod goes away. If you're deliberately choosing SW_HIDE because you want the work area left alone, please say so explicitly in the PR description so the tradeoff is a documented decision rather than an oversight.

5. Keep the tool-mod launcher boilerplate byte-for-byte identical to the wiki snippet.

Everything from bool g_isToolModProcessLauncher; down has been reflowed (one argument per line, reinterpret_cast<> instead of C casts, STARTUPINFO si = {} instead of the designated initializer, an extra GetLastError() in the CreateProcess failed log). It's semantically equivalent, but the maintainer asks for this block to stay verbatim so it can be diffed at a glance and updated in bulk. Please paste it unchanged from Mods as tools: Running mods in a dedicated processexplorer-folder-hover-menu has a verbatim copy at the bottom for reference. (The // IMPORTANT: Keep the official Windhawk launcher section below unchanged. comment you added suggests this was the intent already.)

6. The README has no screenshot or GIF.

This mod has an obvious visible effect, so a short GIF of the desktop→app→hover transitions would help a lot on windhawk.net. Only i.imgur.com and raw.githubusercontent.com are allowed as image hosts.

7. Please state in the PR description how this differs from the existing taskbar auto-hide mods.

The catalog already has taskbar-auto-hide-when-maximized, taskbar-auto-hide-per-monitor and taskbar-auto-hide-custom-activation-area. As far as I can tell yours is genuinely distinct — it's the inverse trigger of "auto-hide when maximized" (hide only when nothing is on the monitor) and it hides the window rather than using native auto-hide — but the maintainer's strong preference is to extend an existing mod over adding a near-neighbour, so it's worth making the case up front, including why an inverted mode on taskbar-auto-hide-when-maximized wouldn't cover it.

Optional improvements

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

  • Dead code. IsDesktopWindow() (line ~155) and IsPrimaryTaskbarHidden() (line ~1018) are defined but never called. SetTaskbarVisibility() is only ever called with show == true (lines ~1776 and ~2028), which makes its else { SetSecondaryTaskbarsVisibility(true); } branch unreachable — the whole show == false path can go.
  • Leftovers from an earlier revision. WhTool_ModSettingsChanged has an orphaned comment block about native auto-hide followed by nothing (lines ~1960-1964, including a trailing-whitespace line), and RefreshPerMonitorState(); at line ~1699 is indented by 8 spaces instead of 4.
  • Drop the mod name from log strings. Wh_Log(L"Hide Taskbar Only on Desktop: Init") — Windhawk already prefixes log output with the mod name, so Wh_Log(L"Init") (or just Wh_Log(L">")) is enough.
  • Window class cleanup. CreateSystemMessageWindow swallows ERROR_CLASS_ALREADY_EXISTS and the class is never UnregisterClassd, and it registers with GetModuleHandleW(nullptr) (i.e. windhawk.exe) rather than the mod's own module even though SystemMessageWindowProc lives in the mod image. This is harmless here only because Wh_ModUninit ends in ExitProcess(0), so the class never outlives a load — but it's a pattern that breaks the moment the code is reused in a non-tool mod. UnregisterClass(kSystemMessageWindowClass, hInstance) next to DestroySystemMessageWindow(), and treating ERROR_CLASS_ALREADY_EXISTS as a failure, costs nothing.
  • Redundant class lists. Windows.UI.Core.CoreWindow and Xaml_WindowedPopupClass appear in both IsShellChromeClass and IsShellUiClass, and Shell_TrayWnd/Shell_SecondaryTrayWnd are in IsShellChromeClass even though IsTaskbarWindow already filtered them out one check earlier. Worth consolidating into one classifier.
  • kMaxTaskbars = 16 silently ignores anything past the 16th taskbar. A Wh_Log when addTaskbar hits the cap would make that debuggable.
  • Millimetres are an unusual unit for a Windhawk setting — every other mod in the repo expresses hover/margin distances in pixels, which is also what users can measure. Consider extraHoverMarginPx (the mod already handles per-monitor DPI for the taskbar rect itself).
  • Include what you use. _wcsicmp, wcsrchr, wcslen and memmove come in transitively via <windows.h> today; #include <string.h> makes that explicit.

Functionality notes

Non-critical observations and ideas about the feature behavior itself.

  • The taskbar can get stuck visible on the desktop. Lines ~1305-1315 keep a taskbar shown, with no deadline, whenever taskbar.taskbarIsForeground && !taskbar.shownDueToHover. Explorer routinely leaves Shell_TrayWnd as the foreground window after a flyout closes (e.g. dismissing Start with Esc), so on an empty desktop the taskbar will then stay visible until the user clicks the desktop. Applying the same autoHideDelayMs deadline here as in the hover/Alt+Tab branches would be more consistent — the comment right above that branch already argues against using foreground as a permanent keep-visible condition.
  • The hover zone inflates sideways as well as upward. InflateRect(&hoverRect, marginPx, marginPx) grows the taskbar rect on all four sides, so with a multi-monitor setup the cursor sitting on the adjacent monitor near the shared edge, at taskbar height, falls inside this taskbar's zone and reveals it. If that's not intended, inflate only along the taskbar's docked edge (you can derive it by comparing taskbarRect to taskbar.monitorRect).
  • Alt+Tab detection is broader than Alt+Tab. XamlExplorerHostIslandWindow also hosts Task View, snap assist and the Widgets board on Windows 11, and the GetAsyncKeyState(VK_MENU) && GetAsyncKeyState(VK_TAB) fallback fires for any Alt+Tab anywhere. Probably fine (all of those arguably should keep the taskbar up), just be aware the name understates the scope.
  • Leaving the work area unchanged is a real tradeoff, not just an implementation detail: maximized windows still stop above the hidden taskbar, so the desktop keeps a permanent empty strip along the taskbar edge. The README mentions it; a line in @description might save some confused users.
  • The mod can't tell whether Windows' own auto-hide is on. The README asks users to turn it off, but if they don't, Explorer's auto-hide animations and your ShowWindowAsync calls will fight. Detecting ABS_AUTOHIDE via SHAppBarMessage(ABM_GETSTATE, ...) once (e.g. on the WM_SETTINGCHANGE refresh, not per tick) and logging or backing off would make the failure mode less mysterious.


Next steps:

  • /ai-review - after pushing fixes, to get a review of the updated code. You can repeat this as many times as you need, but each review is thorough and usually there's no need for more than 2-3 iterations.
  • /ready-for-reviewer - once you're satisfied with the state of the pull request, to hand it over to a human reviewer. If some findings above are left unaddressed, add a short note explaining why.

See the review process for details.

@windhawk-reviewer windhawk-reviewer Bot added waiting-for-author The author's turn: request an AI review, or respond to one that was posted. and removed waiting-for-ai-review An AI review was requested and is being prepared. labels Aug 29, 2026
@Sahil-Dashoni

Copy link
Copy Markdown
Author

/ai-review

@windhawk-reviewer windhawk-reviewer Bot added waiting-for-ai-review An AI review was requested and is being prepared. and removed waiting-for-author The author's turn: request an AI review, or respond to one that was posted. labels Aug 29, 2026
@windhawk-reviewer

Copy link
Copy Markdown

Submission review

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

Remember: The AI reviewer can be wrong - it may misread code, flag correct code as broken, or suggest changes that make things worse. Treat its findings as suggestions to verify, not instructions to follow blindly. You're responsible for the code you submit, so if a finding doesn't hold up, say so instead of changing working code to satisfy it.

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


The tool-mod choice is right for this mod (no hooks, only SetWinEventHook/EnumWindows), the worker thread is properly joined in WhTool_ModUninit, ShowWindowAsync is correctly used for the cross-process show/hide, and the PR description does a good job of differentiating it from the existing auto-hide mods. The window-classification logic is where the problems are.

1. UWP / Store apps are not counted as applications, so the taskbar hides while one is in use

IsShellChromeClass lists ApplicationFrameWindow and Windows.UI.Core.CoreWindow, and EnumWindowsProc skips those classes unconditionally:

if (
    IsShellChromeClass(className) ||
    IsShellUiClass(className)
) {
    return TRUE;
}

ApplicationFrameWindow is the top-level frame of every UWP/Store app (Calculator, Settings, Photos, Clock, Store, Mail…), hosted by ApplicationFrameHost.exe — not by the shell. See the same distinction in center-new-windows.wh.cpp.

So with Calculator open and focused on an otherwise empty desktop: hasApplication stays false, IsShellUiForegroundOnMonitor returns false (ApplicationFrameWindow isn't in IsShellUiClass), HasVisibleShellFlyout finds nothing (the app's CoreWindow is a child of the frame, so EnumWindows never sees it) — and UpdateTaskbarState falls through to the "no application and no reveal" branch and hides the taskbar under the user.

Drop ApplicationFrameWindow entirely, and gate the remaining class exclusions on the window actually belonging to a shell process — IsVisibleShellFlyoutForMonitor already does exactly this, so reuse it:

if (IsShellUiClass(className) && IsShellUiWindow(hwnd)) {
    return TRUE;
}

2. The desktop window itself is counted as an application

EnumWindowsProc no longer excludes Progman / WorkerW — version 1.1.0 did (they were in kShellClasses), and the leftover IsDesktopWindow helper is now dead code. Progman and the wallpaper WorkerW are visible, non-iconic, unowned, non-cloaked, monitor-sized top-level windows without WS_EX_TOOLWINDOW, so they pass every filter in EnumWindowsProc and set hasApplication = true for the monitor they cover — meaning the taskbar would never hide there, i.e. the mod's core feature wouldn't work at all.

Please verify this with Wh_Log in EnumWindowsProc (log the class name of whatever sets hasApplication) — if it reproduces, re-add both classes to the exclusion list. Existing mods class-exclude them explicitly rather than relying on style bits, which is the pattern to follow: gather-windows-to-monitor.wh.cpp, center-new-windows.wh.cpp, always-on-top.wh.cpp.

3. Several full EnumWindows sweeps per timer tick, continuously

UpdateTaskbarState calls both HasVisibleShellFlyout(taskbar.monitor) and IsAltTabActive() inside the per-taskbar loop, on top of the EnumWindows that RefreshPerMonitorState already did:

bool shellFlyoutOpen =
    taskbar.shellUiForeground ||
    HasVisibleShellFlyout(taskbar.monitor);

bool altTabActive =
    IsAltTabActive();

That's 1 + 2 × taskbarCount full desktop enumerations every 200 ms — 7 sweeps per tick on a 3-monitor setup, 35 per second. HasVisibleShellFlyout additionally does OpenProcess + QueryFullProcessImageNameW + DwmGetWindowAttribute per candidate window, and EnumWindowsProc does a DwmGetWindowAttribute per visible window. And this is the steady state, not an edge case: needTimer is true whenever !hasApplication or IsTaskbarActuallyHidden(...), so the timer runs the entire time the desktop is showing.

Two fixes, both straightforward:

  • IsAltTabActive() is monitor-independent — compute it once before the loop.
  • Collect everything (per-monitor app presence, per-monitor shell flyouts, alt-tab window presence) in the single EnumWindows pass that ScanApplicationWindows already performs, instead of three separate ones.

4. The tool-mod launcher block has been reformatted

The file comment says "Keep the official Windhawk launcher section below unchanged", but the block has been rewritten: every call re-wrapped across multiple lines, C-style casts changed to reinterpret_cast, STARTUPINFO designated initializer replaced with field assignment, LPSTARTUPINFOW changed to LPSTARTUPINFO and the parameter names dropped from the CreateProcessInternalW_t typedef, Wh_Log(L"CreateProcess failed") changed. It's functionally equivalent, but the maintainer asks for this snippet to be a byte-for-byte copy of the wiki version so it can be diffed at a glance. Please paste it verbatim — see explorer-folder-hover-menu.wh.cpp (bottom of file) for a mod that does.

5. No screenshot/GIF in the README

The mod has a very visible effect, so a short GIF showing the taskbar hiding on the desktop and revealing on hover would help a lot on windhawk.net. Only i.imgur.com and raw.githubusercontent.com are allowed as image hosts.

Optional improvements

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

  • Dead code. IsDesktopWindow and IsPrimaryTaskbarHidden are defined but never called. IsShellChromeClass and IsShellUiClass are near-duplicates (four of six entries are identical) — after fix 1 above, IsShellChromeClass can go away entirely.

  • RefreshTaskbarForegroundState duplicates the hover logic. It sets shownDueToHover = true via IsTaskbarForegroundAndUnderCursor, which IsCursorInTaskbarHoverZone in UpdateTaskbarState already covers (and more accurately, since it accounts for the extra margin and doesn't require the taskbar to be foreground). Consider dropping the duplicate path.

  • Helper window class registration. Three small issues in CreateSystemMessageWindow: wc.hInstance = GetModuleHandleW(nullptr) is windhawk.exe's handle even though lpfnWndProc lives in the mod image; ERROR_CLASS_ALREADY_EXISTS is swallowed and registration continues (which would reuse a class whose WndProc points into a previous, unmapped mod image); and the class is never UnregisterClassed. The impact is nil here because Wh_ModUninit ends with ExitProcess(0), so the process never sees a second load — but the correct shape is to register with the mod's own HINSTANCE, treat ERROR_CLASS_ALREADY_EXISTS as a failure, and UnregisterClass in WhTool_ModUninit.

  • Drop the mod name from log strings. Wh_Log(L"Hide Taskbar Only on Desktop: Init") — Windhawk already prefixes log lines with the mod name, so Wh_Log(L"Init") (or just Wh_Log(L">")) is enough. Same at the settings-changed and uninit callbacks.

  • Leftover comments and formatting. WhTool_ModSettingsChanged has an empty comment block about re-querying native auto-hide (with trailing whitespace) that no longer refers to any code, and the WM_SETTINGCHANGE case comment says the same thing though the mod never queries native auto-hide. There's also a stray extra indent on the RefreshPerMonitorState(); call in HookThread. More generally, the repo has a .clang-format (Chromium, 4-space indent) — running it would cut the file down substantially and make it much easier to review; right now most calls are split across 5–10 lines each.

Functionality notes

Non-critical observations and ideas about the feature behavior itself.

  • Taskbar left hidden if the tool process dies. The README documents this, and there's no clean fix inside the tool-mod design, so this is just an FYI: if windhawk.exe -tool-mod ... is killed while the taskbar is hidden, the user is left with no taskbar and no way to reveal it (the hover loop is gone too) until Explorer is restarted or the mod is toggled. Worth keeping in mind if you ever see reports of a "missing taskbar".

  • Alt+Tab detection. The GetAsyncKeyState(VK_MENU) && GetAsyncKeyState(VK_TAB) fallback is sampled at 200 ms, so it will usually miss the actual Tab keypress (Tab is held for far less than one tick) — in practice the XamlExplorerHostIslandWindow check is doing all the work. That class in explorer.exe is also used by Task View and the snap layouts flyout, so those get treated as "Alt+Tab is open" too. That's probably the behavior you want, but the code comment says something narrower.

  • 200 ms reveal latency. Everything hover- and flyout-related is timer-driven, so the taskbar appears up to 200 ms after the cursor enters the hover zone and up to 200 ms after Start/Search opens. Native auto-hide is immediate. If that feels sluggish, WH_MOUSE_LL isn't a good option (blocking hook), but SetWinEventHook(EVENT_OBJECT_LOCATIONCHANGE) filtered to OBJID_CURSOR gives event-driven cursor tracking.

  • Explorer restart. Recovery relies on a EVENT_SYSTEM_FOREGROUND arriving after the new taskbars are created — and if g_taskbarCount hits 0, UpdateTaskbarState stops the timer, so nothing polls in the meantime. Since your helper window is a real top-level window (not HWND_MESSAGE), it will receive the RegisterWindowMessage(L"TaskbarCreated") broadcast; handling that in SystemMessageWindowProc would make re-discovery deterministic.

  • Fighting Explorer over visibility. Explorer calls ShowWindow on the taskbar itself in some situations (ABM_SETPOS, tray balloons, display changes). Since the mod re-hides on the next tick, that can show up as a brief flicker rather than a stuck state — just something to watch for.


Next steps:

  • /ai-review - after pushing fixes, to get a review of the updated code. You can repeat this as many times as you need, but each review is thorough and usually there's no need for more than 2-3 iterations.
  • /ready-for-reviewer - once you're satisfied with the state of the pull request, to hand it over to a human reviewer. If some findings above are left unaddressed, add a short note explaining why.

See the review process for details.

@windhawk-reviewer windhawk-reviewer Bot added waiting-for-author The author's turn: request an AI review, or respond to one that was posted. and removed waiting-for-ai-review An AI review was requested and is being prepared. labels Aug 29, 2026
@Sahil-Dashoni

Copy link
Copy Markdown
Author

/ai-review

@windhawk-reviewer windhawk-reviewer Bot added waiting-for-ai-review An AI review was requested and is being prepared. and removed waiting-for-author The author's turn: request an AI review, or respond to one that was posted. labels Aug 29, 2026
@windhawk-reviewer

Copy link
Copy Markdown

This pull request has already had 3 AI reviews in the last 24 hours, which is the limit, so no review was posted this time.

Comment /ai-review again after 2026-08-30 14:28 UTC (in 21 hours) to get another one.

@windhawk-reviewer windhawk-reviewer Bot added waiting-for-author The author's turn: request an AI review, or respond to one that was posted. and removed waiting-for-ai-review An AI review was requested and is being prepared. labels Aug 29, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

waiting-for-author The author's turn: request an AI review, or respond to one that was posted.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants