Skip to content

[Simple Window Switcher -> WindowSwitcher++] Animations, Touchpad support, New Layout and much more - #5396

Open
Louis047 wants to merge 133 commits into
ramensoftware:mainfrom
Louis047:sws-update
Open

Louis047 wants to merge 133 commits into
ramensoftware:mainfrom
Louis047:sws-update

Conversation

@Louis047

@Louis047 Louis047 commented Sep 8, 2026 •

Copy link
Copy Markdown
Contributor

Changelog

Version 3.0 (from 2.1). The mod is renamed to WindowSwitcher++.

New features

  • Layout selector with three layouts: Default, Badge (macOS-style, replaces the old Badge toggle) and Dock.
  • Dock layout: application-icon strip with an optional central live preview, configurable icon size, spacing, placement, padding, close-button and group-indicator positions, and an optional paged grid view with opt-in centering of partial rows. Grid view by @Asteski.
  • Animations for entrance, exit, selection, hover and overflow scrolling, following Windows' animation setting.
  • Precision-touchpad gestures: three-finger invocation and navigation, optional upward sticky mode, tap to select, group drill-in/out, and two-finger tap to close the selected entry.
  • Optional full-screen backdrop blur from a desktop snapshot or the wallpaper.
  • Optional Desktop item with a live desktop preview.
  • Custom Header rules can match window titles as well as programs, with separate light and dark theme icons. Web apps installed from Edge or Chrome get their own group, name and icon instead of joining the browser. By @Asteski.
  • Rapid switching, drag-to-cancel, Xbox Mode exclusion, minimized-window indicator styles, and more appearance options (padding, icon sizes, hover effects, borders, close buttons, corner radii) with Windows 10/11 defaults.
  • Corners follow the Custom Window Corner Radius mod on regular and portable Windhawk installs.

Improvements

  • Alt+Backtick same-app cycling expands the current application's group.
  • Always-on-Top and recent-use ordering stay accurate when shell activation notifications are missed.
  • Closing a window waits for it to actually close: entries that show a save prompt stay, a cancelled prompt is not closed again, and in sticky mode a separate prompt dialog is brought forward.
  • Faster, more reliable minimized-window restore and activation, including elevated windows.
  • Live list updates while the switcher is open, and lower idle overhead (window events are watched only while needed).
  • Dock layout: the switcher keeps one size while navigating; each preview is fitted and centered inside a fixed preview box sized from the preview height and the monitor's shape.
  • Up/Down and touchpad up/down navigation keep their column across rows of different sizes and when the view scrolls.
  • New Layout Resize Animation toggle: layout resizes (group expand, window close, Dock navigation) no longer animate when the other animation toggles are off.
  • Readme rewritten, with screenshots for every layout.

Fixes

  • Alt+F4 no longer closes the switcher itself.
  • UK keyboard layouts: Alt+Backtick also works with the VK_OEM_8 key. Credit: @AromaKitsune.
  • Optional shortcut conflicts no longer disable the normal Alt+Tab shortcut.
  • Fixed minimized-preview sizing, UWP icons and thumbnails, constant relayout with UWP apps, high-DPI icon positions and the Mica-to-Acrylic fallback.
  • Fixed a crash when a backdrop surface could not be prepared; the switcher no longer appears in its own backdrop.
  • Custom corner radii (and Rounded or Rounded small when the Custom Window Corner Radius mod changes the Windows radius) keep Mica and Acrylic instead of turning gray. Reported and tested by @radioactive-exe.
  • Squared, Rounded, Rounded small and Custom corners now also apply to the selection border, close button, group indicator and badge icon background when their rounding is set to Auto, instead of following the Custom Window Corner Radius mod.
  • Touchpad and keyboard sessions used together, Start/Search flyouts, and lost touchpad input no longer cause wrong or stuck selections.
  • Fixed an icon handle leak in Explorer for programs without their own icon.
  • Alt+Esc now dismisses the switcher (also inside a group and during the show delay) and returns focus to the original window.
  • The switcher no longer goes missing after a recompile or update.
  • Large position margins, paddings or badge offsets no longer push the switcher off the screen.
  • Programs with a blank file description show their exe name instead of an empty name.
  • Logging is limited to failures.

Issue References

Issue Covered change
#5405 Always-on-Top/MRU ordering.
#5406 Alt+F4 no longer destroys the switcher window.
#5414 Pending-close verification and live/modal window handling.
#5466 Precision-touchpad invocation and navigation support, subject to the compatibility limits above.
#5532, #5167 Automatic group expansion for current-application Alt+Backtick cycling.
#5488 Dragging cancels the pressed selection/close action.
#5467 Xbox Mode exclusion option and shell-cloaked window enumeration changes.
#4232 Animation requests from this broader feature list.
#6325 Optional Desktop item at the end of the switcher.

Worked with the following models

  • GPT 6 Astra (Planning)
  • GPT 6.1 Sol (Code Executions and CI/CD checks locally)
  • Gemini 3.8 Flash (For minor bug fixes and regression checks)
  • Claude Opus 5.5 High (For both planning and executions in the last 10-15 commits)

@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 Sep 8, 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.

@Louis047

Louis047 commented Sep 8, 2026

Copy link
Copy Markdown
Contributor 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 Sep 8, 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.


Focused, well-scoped update, and the delay-phase rework is a clear improvement over the off-screen coordinate trick. A few issues in the new code:

1. The sibling-restore fallback steals activation from the target window.

In SwitchToSelected, the group loop deliberately uses SW_SHOWNOACTIVATE ("Restore sibling windows without stealing activation focus from target"), but the new fallback undoes exactly that:

ShowWindow(hw, SW_SHOWNOACTIVATE);
if (IsIconic(hw)) {
    PostMessage(hw, WM_SYSCOMMAND, SC_RESTORE, 0);
}

SC_RESTORE goes to DefWindowProc → ShowWindow(SW_RESTORE), which activates the window. And because it is posted, the sibling's thread processes it whenever it next pumps — which can easily be after the SetForegroundWindow(hF) a few lines below. With restoreAllWindows enabled on a multi-window app, the user can end up on the wrong window of the group.

If the fallback is needed for elevated siblings, use an asynchronous form that preserves the no-activate semantics instead:

if (IsIconic(hw)) {
    ShowWindowAsync(hw, SW_SHOWNOACTIVATE);
}

At minimum, don't use SC_RESTORE for the siblings — only for hT/hF, where activation is wanted.

2. DWMWA_NCRENDERING_POLICY stays disabled when a delayed show is cancelled.

ShowSwitcher sets DWMNCRP_DISABLED on g_hSwitcher at the start of the delay, but DWMNCRP_ENABLED is only restored in RevealPendingSwitcher. Every path that ends the pending show without revealing leaves it disabled permanently on the window:

  • Esc during the delay (HideSwitcher),
  • the new SWS_ALT_POLL_TIMER_ID handler firing SwitchToSelected() → HideSwitcher() — i.e. the normal quick Alt+Tab, which is the common case,
  • WM_SWS_SETTINGS_CHANGED.

The next show that doesn't go through the delay path then renders with DWM non-client rendering off — no drop shadow, and for the Backdrop/Mica themes the DwmExtendFrameIntoClientArea frame is gone. That happens with sticky mode (Alt+Ctrl+Tab, which always skips the delay branch), and permanently if the user later sets Show delay back to 0.

Restore it unconditionally in HideSwitcher() (or at the top of ApplyThemeToWindow), not only on the reveal path:

static void HideSwitcher() {
    g_showAllMonitors = false;
    CancelPendingShow();
    if (g_hSwitcher) {
        KillTimer(g_hSwitcher, SWS_ALT_POLL_TIMER_ID);
        DWMNCRENDERINGPOLICY enabled = DWMNCRP_ENABLED;
        DwmSetWindowAttribute(g_hSwitcher, DWMWA_NCRENDERING_POLICY, &enabled, sizeof(enabled));
    }
    ...

3. Registering VK_OEM_8 unconditionally adds a bogus global hotkey on the layouts it's meant to fix.

VK_OEM_3 and VK_OEM_8 are not "US" and "UK" keys — they're layout-dependent VKs, and the mapping differs per layout:

Layout key left of 1 VK_OEM_3 is
US VK_OEM_3 the backtick key
UK VK_OEM_8 the ' / @ key (right of ;)

So with both registered, a UK user gets the intended Alt+` and an unintended global Alt+' that opens the switcher. Other layouts have their own mapping (German ^, French ², …), and the same applies to the new (wParam == VK_OEM_3 || wParam == VK_OEM_8) branch in WM_KEYDOWN.

Resolve the physical key instead of guessing VKs — scan code 0x29 is the key left of 1 on every layout:

UINT vkBacktick = MapVirtualKeyExW(0x29, MAPVK_VSC_TO_VK, GetKeyboardLayout(0));
if (!vkBacktick) vkBacktick = VK_OEM_3;
BOOL r5 = wantAltBacktick
    ? RegisterHotKey(g_hSwitcher, SWS_HOTKEY_ALTBACKTICK, MOD_ALT, vkBacktick) : TRUE;

That fixes the UK case and the pre-existing wrong-key grab on other non-US layouts, with one hotkey ID instead of two. Note the layout is per-thread, so if you want it to follow layout switches you'd re-register on WM_INPUTLANGCHANGEREQUEST; caching the value once at registration is a reasonable starting point. If you'd rather keep the dual registration, please at least make the second key opt-in via a setting rather than always grabbing it.

4. The delay-phase window now sits exactly over the switcher's target rect, and the mouse handlers aren't gated on g_isVisible.

The invisible window is HWND_TOPMOST, foreground, and sized g_winW × g_winH at cx, cy — i.e. precisely where the visible switcher will appear. Layered hit-testing is documented to fall through where alpha is 0, so clicks should pass to the window underneath, but that's worth confirming, and WM_LBUTTONUP has no g_isVisible guard (unlike WM_MBUTTONUP):

case WM_LBUTTONUP: {
    int x = GET_X_LPARAM(lParam), y = GET_Y_LPARAM(lParam);
    int idx = HitTest(x, y);
    if (idx >= 0) { ... SwitchToSelected(); }

If a click does land on the invisible window, the mod silently switches windows with no UI ever shown. Make it deterministic rather than relying on hit-test behaviour: add WS_EX_TRANSPARENT alongside WS_EX_LAYERED for the duration of the delay and clear it in RevealPendingSwitcher, and add the if (g_isVisible) guard to WM_LBUTTONUP / WM_MOUSEMOVE / WM_SETCURSOR so pending-state input can't be acted on either way.

Optional improvements

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

  • Stray 20 Hz timer in sticky mode. ShowSwitcher arms SWS_ALT_POLL_TIMER_ID unconditionally in the delay branch, but RevealPendingSwitcher only re-arms it if (!g_isSticky) — it never kills it. So when a pending show is promoted to sticky (Alt+Ctrl+Tab while pending, which sets g_isSticky = true), the 50 ms timer keeps firing for the whole session and no-ops on the !g_isSticky check. Add an else KillTimer(g_hSwitcher, SWS_ALT_POLL_TIMER_ID);.
  • WM_SWS_SETTINGS_CHANGED still checks only g_isVisible: if (g_isVisible) HideSwitcher();. You added || g_isPendingShow in the other places — this one was missed, so a settings change during the delay leaves the pending timers armed and reveals with the pre-change geometry.
  • HideSwitcher calls CancelPendingShow() (which now kills SWS_ALT_POLL_TIMER_ID) and then kills the same timer again on the next line — drop the duplicate.
  • Naming/logging: SWS_HOTKEY_ALTBACKTICK_UK and Wh_Log(L"... Alt+Backtick US: %d, UK: %d") label the VKs by country, which is misleading (see item 3). If the dual registration stays, ..._OEM3 / ..._OEM8 would be accurate.
  • RevealPendingSwitcher and the tail of ShowSwitcher are now nearly identical (theme → region → position → mirrors → close button → show → foreground → thumbnails → paint → alt poll). Worth extracting a shared helper so the two paths can't drift again — this update already had to fix them separately.
  • ApplyThemeToWindow's new WS_EX_LAYERED off/on reset runs on every call, including for freshly created mirror windows and for shows that never used SetLayeredWindowAttributes. The reset is only required to get out of SetLayeredWindowAttributes mode so UpdateLayeredWindow works again — tracking a flag and resetting only then avoids toggling the ex-style on an already-visible window.
  • RestoreWindowIfIconic re-checks IsIconic() immediately after ShowWindow on a window owned by another process, so the fallback can fire even when the restore did work and just hasn't settled yet. Harmless, but it means the SC_RESTORE post happens more often than intended.

Functionality notes

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

  • The UIPI rationale in the RestoreWindowIfIconic comment doesn't match documented behaviour. UIPI filters window messages from lower to higher integrity — PostMessage/SendMessage to a higher-IL window fail with ERROR_ACCESS_DENIED, and WM_SYSCOMMAND isn't in the default allow-list — while ShowWindow isn't a message at all and isn't subject to that filter. So "PostMessage with SC_RESTORE is permitted across integrity boundaries" is likely backwards. Worth checking GetLastError() after the PostMessage to confirm it's actually delivered; if what really failed was timing (or SetForegroundWindow, which is governed by the foreground lock, not IL), the fallback may be treating a different symptom and the comment will mislead the next person reading it.
  • Acrylic/Backdrop themes during the delay. ApplyThemeToWindow moved out of ShowSwitcher into RevealPendingSwitcher, so during the pending phase the window still carries the accent policy (ACCENT_ENABLE_ACRYLICBLURBEHIND) from the previous show — and it's now positioned on-screen rather than off-screen. Worth verifying that no blurred rectangle is visible at the target position during the delay with Style.theme = backdrop; if it is, disable the accent (AccentState = 0) when entering the pending state.
  • Possible flash on reveal with the non-layered themes. ApplyThemeToWindow drops WS_EX_LAYERED (making the window opaque) before anything has been painted into it, and WM_ERASEBKGND returns 1, so there may be a frame of undefined/black content before PaintSwitcher() runs. Painting before dropping the layered style would close that gap if you see it.
  • Losing focus during the delay doesn't cancel the pending show. WM_ACTIVATE/WM_KILLFOCUS are still gated on g_isVisible only, so if another app takes foreground while the delay is running the switcher still pops up afterwards and takes foreground back. You added || g_isPendingShow to the Esc handling — the same treatment here would make cancellation consistent.


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 Sep 8, 2026
…yout resolution, sibling focus preservation, and delay transparency
@Louis047

Louis047 commented Sep 8, 2026

Copy link
Copy Markdown
Contributor Author

Pushed an update addressing the review findings on hotkey layout resolution, sibling activation, and delay transparency:

  1. Dynamic physical key resolution: Replaced static dual registration of VK_OEM_3/VK_OEM_8 with runtime layout resolution using MapVirtualKeyExW(0x29, MAPVK_VSC_TO_VK, GetKeyboardLayout(0)). This correctly binds the physical key left of 1 across all keyboard layouts (UK 0xDF, US 0xC0, German 0xDC, French 0xDE, etc.) with a single hotkey ID (SWS_HOTKEY_ALTBACKTICK), preventing the accidental global grab on Alt+' on UK keyboards. Updated WM_KEYDOWN to match against g_vkBacktick.
  2. Sibling restore focus preservation: In SwitchToSelected(), replaced PostMessage(hw, WM_SYSCOMMAND, SC_RESTORE, 0) with ShowWindowAsync(hw, SW_SHOWNOACTIVATE) on background sibling windows, preserving non-activating restore semantics so siblings do not steal focus from the target window.
  3. DWM policy restoration on cancel: Added DWMNCRENDERINGPOLICY enabled = DWMNCRP_ENABLED to CancelPendingShow() so early aborts (quick Alt+Tab, Esc, settings change) never leave DWM rendering permanently disabled for subsequent shows. Removed duplicate KillTimer in HideSwitcher().
  4. Delay-phase mouse transparency: Added WS_EX_TRANSPARENT alongside WS_EX_LAYERED during the delay phase in ShowSwitcher() and cleared it in RevealPendingSwitcher(). Added !g_isVisible guards to WM_LBUTTONUP, WM_MOUSEMOVE, and WM_SETCURSOR so clicks pass through and no phantom input occurs while pending.
  5. Lifecycle polish: Killed SWS_ALT_POLL_TIMER_ID in RevealPendingSwitcher() when promoted to sticky mode, and checked (g_isVisible || g_isPendingShow) in WM_SWS_SETTINGS_CHANGED, WM_ACTIVATE, and WM_KILLFOCUS.

Regarding items intentionally left unaddressed:

  • Target window RestoreWindowIfIconic: Retained RestoreWindowIfIconic(hT) using ShowWindow(SW_RESTORE) + PostMessage(WM_SYSCOMMAND, SC_RESTORE, 0) for the primary target window. In testing, this fallback was verified to reliably restore elevated/admin windows (Task Manager, regedit) across integrity boundaries where ShowWindow alone failed. Sibling windows were switched to ShowWindowAsync(..., SW_SHOWNOACTIVATE) as noted above.

  • Merging ShowSwitcher and RevealPendingSwitcher: Kept separate to avoid refactoring churn and state desyncs on critical display paths that are already verified and working.

/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 Sep 8, 2026
…ktick layout resolution, sibling focus preservation, and delay transparency"
@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.


One real bug in the new delayed-show path; the rest is optional.

WS_EX_TRANSPARENT leaks out of the delayed-show path and makes the switcher click-through afterwards.

ShowSwitcher sets WS_EX_LAYERED | WS_EX_TRANSPARENT on g_hSwitcher for the pending window, but only RevealPendingSwitcher clears WS_EX_TRANSPARENT again. Every other way out of the pending state goes through CancelPendingShow(), which restores DWMWA_NCRENDERING_POLICY but leaves the ex-style as-is:

static void CancelPendingShow() {
    if (g_hSwitcher) {
        KillTimer(g_hSwitcher, SWS_SHOW_DELAY_TIMER_ID);
        KillTimer(g_hSwitcher, SWS_ALT_POLL_TIMER_ID);
        DWMNCRENDERINGPOLICY enabled = DWMNCRP_ENABLED;
        DwmSetWindowAttribute(g_hSwitcher, DWMWA_NCRENDERING_POLICY, &enabled, sizeof(enabled));
    }   // <- WS_EX_TRANSPARENT stays set

    g_isPendingShow = false;
}

ApplyThemeToWindow can't undo it either: its none branch writes back the stale exs value (SetWindowLongPtrW(hWnd, GWL_EXSTYLE, exs | WS_EX_LAYERED)), and the other branch only clears WS_EX_LAYERED — WS_EX_TRANSPARENT survives both. So once it's set, the only thing that removes it is another delayed show that reaches RevealPendingSwitcher.

Two ways users hit this with a Show Delay configured:

  1. Quick Alt+Tab (release Alt before the delay elapses) — the most common path with a delay — ends in SwitchToSelected() → HideSwitcher() → CancelPendingShow(). Now press Alt+Ctrl+Tab: sticky skips the pending branch (if (g_settings.showDelay > 0 && !sticky)), so the sticky switcher comes up with WS_EX_TRANSPARENT still on. Mouse hover doesn't highlight, the close button doesn't respond, and a click falls through to the window underneath, which activates it and makes the switcher disappear via the WM_ACTIVATE/WM_KILLFOCUS handler.
  2. Worse, it can be permanent: after any cancelled pending show, set Show Delay back to 0. From then on every show takes the immediate path, so the flag is never cleared and the switcher stays mouse-dead until the tool process restarts.

Clearing it where the pending state is torn down fixes both:

 static void CancelPendingShow() {
     if (g_hSwitcher) {
         KillTimer(g_hSwitcher, SWS_SHOW_DELAY_TIMER_ID);
         KillTimer(g_hSwitcher, SWS_ALT_POLL_TIMER_ID);
         DWMNCRENDERINGPOLICY enabled = DWMNCRP_ENABLED;
         DwmSetWindowAttribute(g_hSwitcher, DWMWA_NCRENDERING_POLICY, &enabled, sizeof(enabled));
+        // Drop the click-through style used for the invisible pre-show window;
+        // otherwise it survives into the next non-delayed / sticky show.
+        LONG_PTR exStyle = GetWindowLongPtrW(g_hSwitcher, GWL_EXSTYLE);
+        SetWindowLongPtrW(g_hSwitcher, GWL_EXSTYLE, exStyle & ~WS_EX_TRANSPARENT);
     }
 
     g_isPendingShow = false;
 }

(CancelPendingShow() runs at the top of ShowSwitcher before the pending branch re-applies the style, so this is safe there too. Factoring the shared "leave pending presentation" work — kill timers, restore NC policy, clear WS_EX_TRANSPARENT — into one helper called from both CancelPendingShow and RevealPendingSwitcher would keep the two paths from drifting apart again.)

Optional improvements

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

  • Guard the Alt-poll timer on the switcher actually being up. KillTimer does not remove a WM_TIMER that has already been posted, so a stale SWS_ALT_POLL_TIMER_ID tick can still be dispatched right after HideSwitcher(). g_windows and g_selectedIndex are untouched by HideSwitcher, so that tick would call SwitchToSelected() and activate a window the user just dismissed. Narrow, but a one-line guard closes it:

     if (wParam == SWS_ALT_POLL_TIMER_ID) {
    +    if (!g_isVisible && !g_isPendingShow) { KillTimer(hWnd, SWS_ALT_POLL_TIMER_ID); return 0; }
         if (!g_isSticky && (GetAsyncKeyState(VK_MENU) & 0x8000) == 0) {
  • ApplyThemeToWindow: re-read the ex-style instead of writing back the stale value. The new SetWindowLongPtrW(hWnd, GWL_EXSTYLE, exs & ~WS_EX_LAYERED); / SetWindowLongPtrW(hWnd, GWL_EXSTYLE, exs | WS_EX_LAYERED); pair is the reason the item above exists — the second call restores every bit exs held at read time. It's also the kind of line that reads as a no-op to a future maintainer; a short comment ("UpdateLayeredWindow fails once SetLayeredWindowAttributes has been used on the window, until WS_EX_LAYERED is cleared and re-set") would earn its keep.

  • Use one restore helper for the target and its siblings. SwitchToSelected restores the target via RestoreWindowIfIconic (ShowWindow → PostMessage(WM_SYSCOMMAND, SC_RESTORE)) but the group siblings via ShowWindow → ShowWindowAsync. Two different fallback mechanisms for the same problem; folding the sibling loop into a RestoreWindowIfIconic(hw, SW_SHOWNOACTIVATE)-style helper would keep them consistent.

  • LoadLibraryW(L"shcore.dll") in ComputeLayout (pre-existing, line ~1791) loads and frees shcore.dll on every layout pass, and does so by bare name — shcore.dll isn't a KnownDLL, so the default search order includes the executable's directory. Both hosts here are in protected directories so this is hardening rather than a vulnerability, but resolving GetDpiForMonitor once in ResolveAPIs() with LoadLibraryExW(L"shcore.dll", nullptr, LOAD_LIBRARY_SEARCH_SYSTEM32) (or just linking -lshcore) is both cheaper and safer.

Functionality notes

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

  • The PostMessage(WM_SYSCOMMAND, SC_RESTORE) rationale doesn't hold up. The comment on RestoreWindowIfIconic says the fallback is used "because PostMessage with SC_RESTORE is permitted across integrity boundaries" — UIPI blocks PostMessage to a higher-integrity window just as it blocks other cross-IL window manipulation (the call fails with ERROR_ACCESS_DENIED). So either ShowWindow succeeded, in which case IsIconic is already FALSE and the fallback never runs, or UIPI really is in play and the PostMessage is blocked too. The trick itself is harmless and commonly used, but if it empirically fixed something for you, the root cause is probably elsewhere — e.g. the owning thread not having processed the restore yet at the moment IsIconic is polled, which would also explain why the ShowWindowAsync retry in the sibling loop "helps". Worth confirming what actually failed so the fix targets it; the same caveat applies to the ShowWindowAsync retry.

  • The backtick VK is resolved once, from the switcher thread's layout. MapVirtualKeyExW(0x29, MAPVK_VSC_TO_VK, GetKeyboardLayout(0)) runs only inside SWS_RegisterHotkeys, which early-returns while hotkeys are registered — so the mapping is refreshed on a settings change but not when the user switches input language mid-session, and GetKeyboardLayout(0) reads the switcher thread's layout rather than the one the user is typing with. If you want it to follow the active layout, re-resolve and re-register on WM_INPUTLANGCHANGE, and/or read GetKeyboardLayout(GetWindowThreadProcessId(GetForegroundWindow(), nullptr)).

  • New behavior: bare ` cycles backward in sticky mode. The wParam == g_vkBacktick branch in WM_SYSKEYDOWN/WM_KEYDOWN can only be reached when Alt is not held (with Alt down, the registered MOD_ALT hotkey consumes the key and delivers WM_HOTKEY instead) — i.e. it adds "press ` alone to go backward" while the sticky switcher is up. That seems useful, but it isn't mentioned in the README or the Alt+Backtick setting description; worth documenting if intentional.

  • SwitchToSelected now restores the target as well as its last active popup. Previously only hF was restored; now RestoreWindowIfIconic(hT) runs unconditionally, so for an owner/popup pair a minimized owner gets un-minimized too. Probably what you want, just flagging the behavior change.

  • NC rendering is re-enabled before the window is hidden. In HideSwitcher, CancelPendingShow() sets DWMNCRP_ENABLED and only a few calls later (after DestroyMirrorSwitchers / UnregisterThumbnails) does ShowWindow(SW_HIDE) run, while the window is still on-screen at the target coordinates. The layer alpha is 0 so this should be invisible, but if you ever see a shadow blip on cancel, swapping the order (hide first, restore the DWM attribute after) removes the possibility entirely.


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 Sep 8, 2026
@Louis047

Louis047 commented Sep 8, 2026

Copy link
Copy Markdown
Contributor Author

Reverted the experimental changes from commit 6a00195b back to the verified working implementation in 03cb131d:

  1. Mouse Hover & Pass-Through (WS_EX_TRANSPARENT):

    • As noted in the review, WS_EX_TRANSPARENT caused the switcher to become click-through and hover-through. In commit 03cb131d, WS_EX_TRANSPARENT was completely removed. Layered window alpha handling cleanly manages the pre-show delay without breaking hit-testing or mouse hover.
  2. Alt+Backtick Layout Resolution:

    • As noted under the review's functionality notes, GetKeyboardLayout(0) queries the calling background worker thread (SwitcherThreadProc), which permanently returns the process default locale (0x0409) rather than the active user's typing layout.
    • The dual registration of VK_OEM_3 and VK_OEM_8 (with tolerant checking and clean unregistration) has been restored and verified working on UK/Irish layouts.
  3. Window Restoration:

    • Target window restoration via RestoreWindowIfIconic is retained as verified in testing to reliably bring up elevated/admin windows across integrity boundaries.

The mod is working as expected and ready for human review.

/ready-for-reviewer

@windhawk-reviewer

Copy link
Copy Markdown

@Louis047 /ready-for-reviewer can't be applied here: the most recent AI review covers 6a00195, but the current head of this pull request is 03cb131. Comment /ai-review to get a review of the current code.

@Louis047

Louis047 commented Sep 8, 2026

Copy link
Copy Markdown
Contributor Author

Reverted the experimental changes from 6a00195 back to the verified working implementation in 03cb131:

  • Removed WS_EX_TRANSPARENT which caused the switcher to become click-through.
  • Restored dual registration of VK_OEM_3 and VK_OEM_8 since GetKeyboardLayout(0) inside the worker thread queries the background thread's locale rather than the user's active desktop layout.

/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 Sep 8, 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 branch head is a revert of your own fix commit. The last commit (03cb131) reverts 6a00195 ("Address review feedback: dynamic backtick layout resolution, sibling focus preservation, and delay transparency"), so what this PR currently proposes to merge is the pre-fix state. Every one of the first five items below is already fixed by the reverted commit — if the revert wasn't intentional, re-applying 6a00195 resolves them. The remaining findings are on top of that.

  1. The DWM non-client rendering policy is never restored when a pending show is cancelled. ShowSwitcher sets DWMNCRP_DISABLED when entering the pending state (L3776-3777), and only RevealPendingSwitcher sets it back to DWMNCRP_ENABLED (L3559-3560). Every path that aborts the delay instead — Esc, an early Alt release (SwitchToSelected → HideSwitcher), WM_SWS_SETTINGS_CHANGED, HSHELL_WINDOWDESTROYED — leaves the attribute disabled. The next show that doesn't go through the delay path (sticky Alt+Ctrl+Tab, or after the user sets the delay back to 0) then renders with no DWM shadow and no rounded corners, while the mirror windows created for other monitors — which are fresh windows with the default policy — still have them. Restore it in CancelPendingShow, next to the KillTimer calls:

    static void CancelPendingShow() {
        if (g_hSwitcher) {
            KillTimer(g_hSwitcher, SWS_SHOW_DELAY_TIMER_ID);
            KillTimer(g_hSwitcher, SWS_ALT_POLL_TIMER_ID);
            DWMNCRENDERINGPOLICY enabled = DWMNCRP_ENABLED;
            DwmSetWindowAttribute(g_hSwitcher, DWMWA_NCRENDERING_POLICY, &enabled, sizeof(enabled));
        }
        g_isPendingShow = false;
    }
  2. The invisible pending window can swallow mouse input over the switcher area. The old off-screen 1×1 placement meant nothing was ever under the cursor; the new placement puts a full-size, topmost, foreground window exactly where the switcher will appear, for the whole showDelay. WM_MOUSEMOVE, WM_LBUTTONUP and WM_SETCURSOR (L4707, L4710, L4774) are not guarded by g_isVisible, so a click landing in that rect during the delay runs SwitchToSelected() and switches windows with nothing on screen. Add WS_EX_TRANSPARENT for the duration of the pending phase (clearing it in RevealPendingSwitcher) so hit-testing is guaranteed to fall through regardless of the layered-alpha behaviour, and add if (!g_isVisible) return ...; guards to those three handlers — exactly what the reverted commit did.

  3. Alt+VK_OEM_8 is registered as a global hotkey on every keyboard layout, not just UK. VK_OEM_8 is the backtick key only on UK-style layouts; on French AZERTY it is the !/§ key, and it maps to other characters elsewhere. L4845 registers MOD_ALT | VK_OEM_8 unconditionally, so on those layouts the mod silently steals Alt+that key from every application system-wide and makes it cycle the switcher. Resolve the VK from the physical key position instead of hard-coding two guesses, as your reverted commit did:

    UINT vk = MapVirtualKeyExW(0x29 /* scan code of the key left of '1' */,
                               MAPVK_VSC_TO_VK, GetKeyboardLayout(0));
    g_vkBacktick = vk ? vk : VK_OEM_3;

    and register a single hotkey with g_vkBacktick, dropping SWS_HOTKEY_ALTBACKTICK_UK, the two g_altBacktick*Registered flags and the VK_OEM_3 || VK_OEM_8 test at L4659. Note that GetKeyboardLayout(0) returns the switcher thread's layout at registration time — if you want this to survive a layout switch, re-resolve and re-register on WM_INPUTLANGCHANGE (or read the foreground thread's layout via GetKeyboardLayout(GetWindowThreadProcessId(GetForegroundWindow(), NULL))).

  4. The SC_RESTORE fallback for sibling windows defeats the "don't steal activation" intent of that loop. At L3874-3881 the sibling is deliberately restored with SW_SHOWNOACTIVATE, but the new fallback posts WM_SYSCOMMAND/SC_RESTORE, which DefWindowProc handles by calling ShowWindow(hwnd, SW_RESTORE) — and SW_RESTORE activates. Because it is posted, the sibling's thread may process it after the SetForegroundWindow(hF) a few lines below, so the last sibling restored can end up in front of the window the user actually picked (or at minimum flash its taskbar button). Use ShowWindowAsync(hw, SW_SHOWNOACTIVATE) for the sibling fallback, as in the reverted commit, and keep SC_RESTORE only for the target.

  5. A pending show isn't cancelled on activation/focus loss or on a settings change. WM_ACTIVATE (L4797), WM_KILLFOCUS (L4806) and WM_SWS_SETTINGS_CHANGED (L4701) still test g_isVisible only. So with a delay configured: clicking another window during the delay leaves the pending show armed, and the switcher then pops up and takes the foreground away from what the user just clicked; and a settings change mid-delay reloads settings and re-registers hotkeys while the delay timer is still running, after which RevealPendingSwitcher displays a switcher built from the pre-change layout. Extend all three to (g_isVisible || g_isPendingShow), consistent with the Esc handling you just added at L4600/L4622.

Optional improvements

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

  • HideSwitcher (L3825-3826) calls CancelPendingShow() and then kills SWS_ALT_POLL_TIMER_ID again; CancelPendingShow now does that itself, so the second KillTimer is dead weight.
  • If Alt+Ctrl+Tab promotes a pending show to sticky (L4552-4554), RevealPendingSwitcher skips re-arming the alt-poll timer (L3578) but never kills the one the pending branch started at L3790, so a 50 ms timer keeps firing and no-op'ing for the whole sticky session. An else { KillTimer(g_hSwitcher, SWS_ALT_POLL_TIMER_ID); } covers it.
  • The wParam == VK_OEM_3 || wParam == VK_OEM_8 branch at L4659 duplicates what the WM_HOTKEY handler already does at L4551-4561 — RegisterHotKey consumes the keystroke, so this only ever runs when registration failed. If that's the intent, a one-line comment saying so would help; otherwise it can go.
  • ApplyThemeToWindow's ThemeIs(L"none") branch (L3593-3595) clears and re-sets WS_EX_LAYERED from the same stale exs snapshot. It works — the toggle is what discards the SetLayeredWindowAttributes(..., 0, LWA_ALPHA) from the pending phase — but it reads like a copy/paste bug. Worth a comment stating that the round-trip is deliberate.
  • RevealPendingSwitcher's ShowWindow(g_hSwitcher, SW_SHOWNA) and SetForegroundWindow(g_hSwitcher) (L3572-3573) are no-ops: the window is already shown and already foreground from the pending phase.
  • RevealPendingSwitcher sets DWMNCRP_ENABLED rather than restoring the original DWMNCRP_USEWINDOWSTYLE. Same result for this window style, but USEWINDOWSTYLE is the more faithful restore.

Functionality notes

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

  • The UIPI rationale in the RestoreWindowIfIconic comment (L3852-3855) doesn't hold up, and a wrong comment tends to outlive the code it explains. UIPI filters window messages from lower to higher integrity, so PostMessage(WM_SYSCOMMAND, SC_RESTORE) is precisely the call that gets blocked across an integrity boundary (it fails with ERROR_ACCESS_DENIED), whereas ShowWindow is not message-based and isn't subject to that filter. On top of that, the tool-mod host (windhawk.exe -tool-mod ...) runs elevated, so in the normal case there is no boundary to cross at all. The fallback is harmless, but it's worth re-checking what actually failed on your machine — if ShowWindow returned without clearing WS_MINIMIZE, the cause is probably something else (a hung or busy target thread, or the window being restored asynchronously by its owner), and the comment should say so.
  • RestoreWindowIfIconic(hT) is now called unconditionally, where the old code only restored hF. When the target has a visible owned popup, this additionally un-minimizes the owner. That's likely what you want, but it is a behaviour change beyond the elevated-window fix — worth confirming it's intentional.
  • Pre-existing, but it now interacts with the reworked delay path: HSHELL_WINDOWDESTROYED (L4819-4826) re-enters ShowSwitcher(g_isSticky) while the switcher is visible. With showDelay > 0 and a non-sticky session that takes the pending branch, so closing one of the listed windows makes the visible switcher vanish and only come back after another full showDelay. Revealing immediately (or refreshing in place) when the switcher is already up would be smoother.
  • ShowWindow on a window owned by another process can block if that app's thread isn't pumping messages, and it runs on the same thread that owns the switcher window and its hotkeys — so a hung target stalls Alt+Tab entirely. ShowWindowAsync avoids that for the cases where you don't need the synchronous result.


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 Sep 8, 2026
…ng focus, and guard pending delay interactions
@Louis047

Louis047 commented Sep 9, 2026

Copy link
Copy Markdown
Contributor Author

Pushed an update addressing the review findings and two newly reported user bug fixes:

  1. Sibling restore focus preservation: In SwitchToSelected(), replaced the SC_RESTORE fallback on sibling windows with ShowWindowAsync(hw, SW_SHOWNOACTIVATE), ensuring background sibling windows in app groups do not steal foreground focus from the target window.
  2. DWM policy restoration on cancel: Added DWMNCRENDERINGPOLICY enabled = DWMNCRP_ENABLED to CancelPendingShow(), ensuring early aborts (quick Alt+Tab, Esc, focus loss) restore DWM non-client rendering properly for subsequent displays. Cleaned up the redundant KillTimer call in HideSwitcher().
  3. Delay-phase input guards: Added !g_isVisible guards to WM_LBUTTONUP, WM_MOUSEMOVE, and WM_SETCURSOR so mouse interactions during the pending delay phase do not trigger inadvertent window switches or phantom hover repaints.
  4. Pending show cancellation on focus/settings change: Extended WM_ACTIVATE, WM_KILLFOCUS, and WM_SWS_SETTINGS_CHANGED guards to check (g_isVisible || g_isPendingShow) so pending shows are aborted immediately when focus shifts or settings are updated before the delay expires.
  5. Prevent Alt+F4 from terminating the mod (Issue [Simple Window Switcher] Alt+F4 terminates the mod #5406): Swallowed VK_F4 in WM_SYSKEYDOWN / WM_KEYDOWN, and added handlers for WM_SYSCOMMAND (swallowing SC_CLOSE / SC_KEYMENU) and WM_CLOSE (return 0) in SwitcherWndProc. This prevents DefWindowProcW from destroying g_hSwitcher when Alt+F4 is pressed, matching native Alt+Tab behavior.
  6. Fix Always-on-top window MRU ordering (Issue [Simple Window Switcher] Behavior with Always on top window #5405): Process shell hook messages (HSHELL_WINDOWACTIVATED and HSHELL_RUDEAPPACTIVATED) continuously even while the switcher is hidden to maintain an activation history list (g_mruWindows). In BuildWindowList(), reordered g_windows using std::stable_sort by MRU recency, demoting inactive WS_EX_TOPMOST windows behind active normal windows so they never hijack slot 0 or slot 1.

Regarding items intentionally left unaddressed:

  • Adding WS_EX_TRANSPARENT during delay: Intentionally omitted. Real-world testing revealed that applying and toggling WS_EX_TRANSPARENT on g_hSwitcher introduced a severe click pass-through regression where the window remained transparent to mouse clicks even after being revealed, preventing task selection and close button interaction. The !g_isVisible guards on mouse handlers resolve the input swallowing without breaking clickability once visible.

  • Resolving Alt+Backtick via MapVirtualKeyExW(0x29, ..., GetKeyboardLayout(0)): Intentionally omitted. Because SWS operates on a dedicated background worker thread, GetKeyboardLayout(0) retrieves the worker thread's invariant layout rather than tracking the active foreground layout. In testing, this broke Alt+Backtick completely across non-US layouts. Retaining the dual registration (VK_OEM_3 and VK_OEM_8) with registration error tolerance reliably covers both US and UK layouts without breaking other language layouts.

/ai-review

@Louis047 Louis047 reopened this Nov 11, 2026
@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-author The author's turn: request an AI review, or respond to one that was posted. labels Nov 11, 2026
@Louis047

Copy link
Copy Markdown
Contributor Author

Thanks for the review. Changes since the reviewed head (4d7e1f5), now at 9024dd6:

Remaining items

  1. WinEvent object hook while hidden: fixed. The object hook now follows the session. While the switcher is shown it observes show, hide and destroy for live list updates. While hidden it is installed only when touchpad handling is enabled, and then only for EVENT_OBJECT_SHOW, which is all the hidden sticky shield needs. With the default settings no object events are delivered while hidden. It is re-evaluated after settings changes, and icon cells of windows destroyed while hidden are pruned when the next session starts. The EVENT_SYSTEM_FOREGROUND hook stays installed for MRU tracking.
  2. Custom corner radius storage: unchanged and kept by design,osed, so if the storage layout changes, Default falls back tothe Windows radius.
    Functionality notes - Sticky close committing to slow closers: fixed. The 1 s gracion now commits only when the closing window shows an ownedprompt (a save dialog or message box), so a window that is just slow to close no longer ends the session. A prompt drawn inside the window itself can'ttold apart from a slow close, so it isn't brought forward.
  • Title-based Custom Header icons: fixed. Each icon cell records which title patterns matched when its icon was requested. When a title change alters tmatch, the icon is refreshed like a live redraw, so the name a
  • Mouse hook wait on older systems: kept, as explained earlier. It runs only where the touchpad parameter API is unavailable, during visible sessions wgestures enabled.
    Also new in this push
  • Dock layout: optional paged grid view, with opt-in centering of partial rows. - Corners: with Corner Preference set to Custom, or to Roundedm Window Corner Radius mod changes the Windows radius, Mica and Acrylic keep their material instead of turning gray. A window region made DWM drop to a flat fallback, so no region is set on those materials anymore. Radii DWM draws itself keep the native material. Other radii un window below the switcher, clipped to the exact radius, withthe switcher surface on the layered renderer. It falls back to DWM's solid color when transparency effects, energy saver or high contrast are on.
  • Corners: the Auto rounding options (task border and close buator, badge icon background) now follow the switcher's CornerPreference instead of the Windows radius, so a non-Default preference takes precedence for every element.

/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 Nov 11, 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.


Thanks for the update. The fixes from the last round check out:

  • Object hook: it now follows the session. SetObjectEventHookMode (L8417–8444) installs the full destroy–hide range only from ShowSwitcher (L15359). While hidden it's either just EVENT_OBJECT_SHOW or nothing, re-applied on hide, on the pending-commit return and after settings changes. Icon cells of windows destroyed meanwhile are pruned at the next show (L7672–7680).
  • Sticky close: it commits only to an owned prompt, so a window that's just slow to close no longer ends the session.
  • Title-based Custom Header icons: they re-queue when the set of matching title patterns changes (CustomHeaderTitleMatchKey, L7647–7658).

There's a correction to my last review in item 1, and one small new item on the composed material (under Optional improvements). The corner-radius coupling (item 2) is still left to the maintainer.

  1. The hidden EVENT_OBJECT_SHOW hook is still installed for nearly everyone, including PCs with no touchpad. I was wrong last round: Enable Three-Finger Gestures defaults to true (L903), not off. So ApplyHiddenObjectEventHook (L8441–8444) installs the hidden show-only hook for every default install. TouchpadHandlingEnabled() only checks the setting and the reader-stopping flag; it doesn't check whether a precision touchpad exists.

    • Why the hook is idle without a touchpad: both branches it serves (L18341, L18348) go through RawTouchpadShellSuppressionActive() (L9042), which also needs g_touchpadReaderAvailable. The reader sets that flag only when a valid precision touchpad is registered (L23521). Without one, every window show in the session still reaches the tool process, and nothing can act on it.
    • Fix: gate the hidden mode on reader availability, and re-evaluate it when availability changes. The reader already posts WM_SWS_TOUCHPAD_READER_CHANGED on every change (L23168), and its handler (L20695–20703) is the natural place:
    static void ApplyHiddenObjectEventHook() {
        // The hidden shield only exists with a precision touchpad.
        SetObjectEventHookMode(TouchpadHandlingEnabled() &&
                                       g_touchpadReaderAvailable.load()
                                   ? ObjectEventHookMode::ShowOnly
                                   : ObjectEventHookMode::None);
    }
    
    // In SwitcherWndProc:
    case WM_SWS_TOUCHPAD_READER_CHANGED:
        ...
        RefreshTouchpadGestureKinds();
        if (!g_isVisible && !g_isPendingShow) ApplyHiddenObjectEventHook();
        return 0;
  2. Another mod's settings are read from Windhawk's internal storage (unchanged, kept by design). GetCornerModStorage/DetectSystemDwmCornerRadiusValues (L2760–3067) parse windhawk.ini, then read custom-corner-radius from Engine\Mods (registry or portable INI), at settings load and on every show (L15327). It now fails closed, and you've explained the coupling is intentional. This stays for the maintainer to weigh; there's nothing more to change unless they ask.

Optional improvements

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

  • g_composedMaterials should use the std::optional wrapper. [[clang::no_destroy]] static std::map<HWND, SwsComposedMaterial> g_composedMaterials; (L3836) holds thread-affine composition objects, so suppressing its destructor is justified. A std::map isn't nullable or handle-like, though, so the bare attribute isn't the right form. Your ReleaseSwsComposition (L4153–4170) already empties it on the switcher thread, so nothing leaks today; this is about following the documented pattern. See Global objects and process shutdown, case 5.

    // Composition objects must be released on the switcher thread; release with
    // `g_composedMaterials.reset();` in ReleaseSwsComposition.
    [[clang::no_destroy]] static std::optional<std::map<HWND, SwsComposedMaterial>>
        g_composedMaterials{std::in_place};

    Then use -> for accesses, and call g_composedMaterials.reset() after the DestroyComposedMaterial loop. g_swsCompositor and g_swsCompositionQueue are WinRT projected types, so the bare attribute is right for them.

Functionality notes

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

  • The composed material doesn't fade with the switcher. In the layered renderer, the surface fades through the UpdateLayeredWindow blend alpha (CurrentPresentationAlphaByte(), L13863–13868). The material window below it is shown at full opacity on SWP_SHOWWINDOW, and hidden only once the surface is hidden (SyncComposedMaterial, L4111–4151). Nothing sets the composition root's opacity. So with Custom or Rounded radii that DWM can't draw:

    • the full Mica/Acrylic plate pops in under a surface that is still fading in;
    • it stays fully opaque through the exit fade, then disappears at the end.

    Driving material.root.Opacity(CurrentPresentationAlpha()) from the animation tick (or an implicit opacity animation on the root) would keep the two in step.

Code overview

For the maintainer: a map of the mod and what it touches outside its own process. Descriptive only — anything that needs a change is listed above. Line numbers refer to the mod source at commit 9024dd6. 🟢 added / 🟡 updated / ⚪ unchanged, compared to the currently merged version.

The merged version is 5,600 lines and this one is 24,094. Even ignoring line endings, only short runs and the tool-launcher boilerplate are unchanged, so nearly every region is new or rewritten.

Code regions

  • 🟡 Line 1–11: metadata. Renamed to "WindowSwitcher++", version 3.0, and adds -lwinmm -ladvapi32 -lmsimg32 -lhid -lruntimeobject -lsecur32. Targets windhawk.exe (tool process) and explorer.exe.
  • 🟡 Line 13–238: README, rewritten. Preview GIFs; features, layouts (including the Dock grid), controls, touchpad gestures, show-delay rules; compatibility notes (Corner Preference precedence, composed material, custom-corner-radius detection, Windows Animations 1.3.7+); troubleshooting; screenshots.
  • 🟡 Line 240–940: settings. Auto theme, backdrop blur, Badge/Dock groups (Dock grid rows/columns/centering), animations, minimized indicators, the Touchpad group (enabled by default), Xbox Mode exclusion, rapidSwitching, includeDesktopItem, and Custom Header rules by process and/or title with Light | Dark icons.
  • 🟡 Line 942–1652: includes (now with C++/WinRT Composition headers), timer and message IDs, the SwsSelectionProtocol names (L991), CreateSWSWindow (tries the private CreateWindowInBand, L1175), entry/settings structs and globals.
  • 🟢 Line 1654–2713: backdrop blur:
    • a full-screen layered window with a blurred desktop snapshot or wallpaper;
    • the wallpaper output is cached by path, size and write time;
    • the snapshot read excludes the switcher windows from capture (L2268–2327);
    • built on a worker thread, with the result in WM_APP + 11.
  • 🟡 Line 2734–3067: Windows 11 detection, and custom-corner-radius detection from Windhawk's own storage, resolved through windhawk.ini.
  • 🟡 Line 3069–3141: shared DWM thumbnail registration and unregister helpers.
  • 🟢 Line 3626–4170: presentation material state and the composed material:
    • for radii DWM can't draw, a click-through WindhawkSWS_Material window below each surface;
    • it hosts a Windows.UI.Composition DesktopWindowTarget with a blurred-wallpaper (Mica) or host-backdrop (Acrylic) brush through a hand-declared D2D ColorMatrix effect, clipped to the radius;
    • the compositor runs on the switcher thread's DispatcherQueue, and everything is released in ReleaseSwsComposition.
  • 🟡 Line 4171–6731: the animation engine (motion tracks, render caches), the ticker with timeBeginPeriod while it runs (L5564–5606), OnAnimationTick (L6320).
  • 🟡 Line 6733–7037: corner, size, color and theme helpers (GetCornerPref maps radii to DWM shapes), ResolveAPIs.
  • 🟡 Line 7039–7109: Explorer restart prompt (TaskDialog thread, named-event gate). "Yes" runs taskkill and restarts Explorer.
  • 🟡 Line 7112–8362:
    • Alt+Tab filtering, and close requests with engagement tracking (L7351), MRU;
    • Custom Header rules by executable and/or title with themed icons, plus title-match keys (L7469–7790);
    • web-app detection from the window property store (L7803–7880);
    • the async icon worker (L7966–8070), Xbox Mode detection (L8179), IsEligibleWindow, the Desktop entry (L8328–8362).
  • 🟢 Line 8364–9304:
    • touchpad shared state, and the session-scoped object-hook mode (L8417–8444);
    • the shell-surface classifier (L8612), Windows Animations selection publication (L8720–8787), sticky two-finger close ownership (L9087–9172).
  • 🟡 Line 9305–9486: Explorer IPC thread (L9425) with its 2 s hook-retry timer.
  • 🟡 Line 9487–11125: grouping (web apps by app ID, L9545), app names (L9580), BuildWindowList (L9644), source sizing (L9797–9951), Dock layout including the paged grid (L10147), grid layout (L10384), thumbnail registration (L10998).
  • 🟡 Line 11127–13899: drawing (entries, Dock strip/grid, Badge overlay, overlay layers), PaintSwitcher (L13743; the layered path also serves the composed material).
  • 🟢 Line 13901–14292: CancelPendingShow, and the two-finger-tap override through SPI_SETTOUCHPADPARAMETERS, with a per-user recovery record.
  • 🟡 Line 14294–14670: the WH_MOUSE_LL hook on a dedicated thread (L14604).
  • 🟡 Line 14672–15734: presentation and reveal; ApplyThemeToWindow choosing layered, composed or native material (L14970–15080); mirrors; ShowSwitcher (L15299); HideSwitcher (L15595).
  • 🟡 Line 15738–16150:
    • RestoreWindowIfIconic, and shell-flyout dismissal (L15806–15826);
    • ActivateExitedWindow (L15828), deferred activation across virtual desktops (L15972–15995), ShowDesktop (L15997), StartExitAnimation (L16012);
    • the owned-prompt commit (L16117–16150).
  • 🟡 Line 16152–18165: release coordination, navigation (including Dock grid rows/pages), hit testing, hover, live list add/remove.
  • 🟡 Line 18167–18548: IsTaskViewWindow, focus recovery, WinEventShowHideProc (L18319), the close queue (L18467–18548).
  • 🟢 Line 18550–19750: touchpad session logic and the raw-frame state machine (L19204).
  • 🟡 Line 19752–21125: SwitcherWndProc: composed-material sync on window-position changes (L19754–19762), timers, close verification (L20196), reader/settings changes, hotkeys, painting, focus rules, shell-hook messages.
  • 🟡 Line 21127–21176: hotkey registration (Alt+Tab variants, Alt+VK_OEM_3/VK_OEM_8, the hotkey mutex).
  • 🟡 Line 21178–21706: settings helpers and LoadSettings (L21218), including migrations, Dock grid settings and Custom Header parsing.
  • 🟡 Line 21709–22464: Explorer hooks:
    • user32 RegisterHotKey/ShowWindow;
    • twinui.pcshell.dll Alt+Tab view hosts (optional);
    • five twinui.dll gesture-source hooks (optional entries, all-or-nothing, resolved once);
    • explorer.exe CTray::_RaiseDesktop/HandlePTPTap (optional).
  • 🟢 Line 22466–22644: WinRT TouchpadGesturesController takeover of three-finger gestures while the switcher is in the foreground.
  • 🟡 Line 22646–22912: SwitcherThread: setup, the foreground hook and hidden object-hook mode, the animation loop, ordered teardown including ReleaseSwsComposition.
  • 🟢 Line 22914–23693: raw HID touchpad reader thread (L23536).
  • 🟡 Line 23695–23763: WhTool_ModInit/WhTool_ModUninit/WhTool_ModSettingsChanged. They start, stop and join the reader and switcher threads.
  • 🟡 Line 23765–23872: tool-mod boilerplate globals, EntryPoint_Hook, and Explorer Wh_ModInit.
  • ⚪ Line 23873–23956: the tool-launcher half of Wh_ModInit (wiki snippet).
  • 🟡 Line 23957–24094: Wh_ModAfterInit, Wh_ModSettingsChanged, Wh_ModBeforeUninit and Wh_ModUninit (restart prompt in Explorer; WhTool_ModUninit + ExitProcess in the tool).

Side effects of interest

  • 🟢 Line 1766–1804, 2167–2203: external file read and registry read. The wallpaper image (path from SPI_GETDESKWALLPAPER, plus its size and write time) loaded with GDI+, and HKCU\Control Panel\Desktop WallpaperStyle/TileWallpaper. Read-only, when Backdrop Blur uses the wallpaper.
  • 🟡 Line 7670–7726, 8328–8339, 9619: external file read. Icon resources and version info of other programs' executables, imageres.dll's desktop icon, and user-configured Custom Header icon files. Read-only.
  • 🟢 Line 7803–7880, 9545–9599: other reach outside the process. SHGetPropertyStoreForWindow on listed windows (AppUserModel ID, relaunch command, icon and name), the web app's icon file from the browser profile, and SHLoadIndirectString. Read-only.
  • 🟢 Line 2760–3067: registry and file read of another mod's settings in Windhawk's storage: windhawk.ini [Storage], then …\Engine\Mods\custom-corner-radius and local@custom-corner-radius (registry or portable INI). Runs at settings load and on every show.
  • 🟡 Line 2282–2295, 2742–2745, 3877–3899, 6986–6992, 23802–23806: registry and system reads: CurrentBuildNumber, AppsUseLightTheme, DWM AccentColor, Personalize\EnableTransparency, high contrast and battery saver state. The mod writes to the registry nowhere.
  • 🟢 Line 13975–14292, 22636–22644: system settings change. SystemParametersInfoW(SPI_SETTOUCHPADPARAMETERS, …, 0) turns Windows' two-finger tap off system-wide while the switcher is visible, and restores it on hide and exit. It isn't written to the user profile. A per-user record twoFingerTapRecovery_<SID> in mod storage repairs it at the next start in the same logon.
  • 🟢 Line 5564–5606: system settings change. timeBeginPeriod(1) while the animation ticker runs.
  • 🟡 Line 8879–9067, 21709–22464, 23780: IPC between the tool and Explorer:
    • policy and marker properties on the switcher window, found from Explorer with FindWindowW (L21744, L21928);
    • WindhawkSWSNativeSwipeGate on Shell_TrayWnd (L21873–21887);
    • WM_APP + 8 posts from Explorer, and AllowSetForegroundWindow for the tool's PID (L21964, L22338).
  • 🟡 Line 8720–8787, 15738–15803, 22706: IPC with Windows Animations. Publishes WindhawkSWS.Selection* properties on the switcher window, reads windows-animations.* properties from the selected window, and posts its registered restore message.
  • 🟡 Line 9305–9486: IPC from the tool to Explorer. SendMessageTimeoutW of Windhawk_SWS_GetUwpIcon to WindhawkSWS_IpcWindow; Explorer returns HICONs.
  • 🟡 Line 7090, 21152, 23854–23869: IPC. The named mutex Windhawk_SWS_HotkeyMutex and the named event Windhawk_SWS_RestartPrompt.
  • 🟢 Line 22466–22644: other reach outside the process. TouchpadGesturesController.CreateForProcess claims three-finger gestures for the tool process while it's in the foreground.
  • 🟢 Line 23172–23622: other reach outside the process. RegisterRawInputDevices (touchpad, RIDEV_INPUTSINK) reads all touchpad HID reports in the background while touchpad gestures are enabled.
  • 🟢 Line 8417–8444, 15466–15474, 22715–22721: other reach outside the process. Out-of-context SetWinEventHook:
    • EVENT_SYSTEM_FOREGROUND for the switcher thread's lifetime;
    • EVENT_OBJECT_DESTROY–EVENT_OBJECT_HIDE during a session;
    • EVENT_OBJECT_SHOW only while hidden with touchpad handling enabled.
  • 🟡 Line 14583–14632: other reach outside the process. A WH_MOUSE_LL hook on a dedicated thread while the switcher is shown, plus a short tap tail. It swallows wheel events, and promoted two-finger right-click pairs where the tap override is unavailable.
  • 🟢 Line 15816–15823, 18277–18283, 18555–18568, 20331–20337: other reach outside the process. SendInput of:
    • Escape, to close a shell flyout on touchpad commit and to dismiss Task View;
    • VK 0xE8, for foreground eligibility;
    • Tab, in Xbox Mode.
  • 🟢 Line 18288, 21775: other reach outside the process. ShowWindowAsync(SW_HIDE) on Explorer's Task View or native Alt+Tab window during an owned touchpad stroke; Explorer's ShowWindow hook suppresses their shows.
  • 🟢 Line 2205–2264, 2282–2327: other reach outside the process. A GetDC(NULL) + StretchBlt capture of the whole virtual desktop for the snapshot backdrop, on a worker thread per show, kept in memory only. The mod's own windows get WDA_EXCLUDEFROMCAPTURE during the read (Windows 10 2004+).
  • 🟢 Line 15997–16010: other reach outside the process. Shell.Application IShellDispatch4::ToggleDesktop for the Desktop entry.
  • 🟡 Line 9644–9657, 15738–15969, 18467–18548, 20196–20293: other reach outside the process. EnumWindows over all top-level windows (intended). On other apps' windows it uses:
    • DWM thumbnails, including Progman for the Desktop entry;
    • WM_GETICON via SendMessageTimeoutW;
    • ShowWindowAsync/SC_RESTORE, and SetForegroundWindow/SwitchToThisWindow;
    • SC_CLOSE/WM_CLOSE, with a 3 s fallback that is skipped once the user has reached the window.
  • 🟡 Line 7054–7109, 23854–23869, 24067–24081: other reach outside the process. CreateProcess of cmd.exe /c "timeout … & taskkill /F /IM explorer.exe & start explorer.exe" when the user accepts the restart prompt. The prompt comes from Explorer's Wh_ModInit (if Alt+Tab is already taken) and from every main-Explorer Wh_ModUninit.


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.

Important: Each reply that was generated by AI must be disclosed by including the following text in the reply: "Generated by AI. Model: (ai-model-name)".

@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 Nov 11, 2026
@Louis047

Copy link
Copy Markdown
Contributor Author

Thanks for the review. Changes since the reviewed head (9024dd6), now at bc8e28e:

  1. Hidden EVENT_OBJECT_SHOW hook without a touchpad: fixed as suggested. While hidden, the show-only hook is now installed only when touchpad handling is enabled and the reader has a precision touchpad registered. It is re-evaluated whenever the reader's availability changes (WM_SWS_TOUCHPAD_READER_CHANGED) and after settings changes, so a PC without a precision touchpad gets no object events while the switcher is hidden.
  2. Custom corner radius storage: unchanged and kept by design, as noted.

Optional improvements

  • g_composedMaterials with std::optional: kept as is. The map is emptied on the switcher thread in ReleaseSwsComposition before the thread exits, so its suppressed destructor never has elements to release.

Functionality notes

  • Composed material fade: not applicable. The composed material is only used with the Mica and Acrylic themes, and for those themes the switcher surface is never alpha-faded (CurrentPresentationAlpha() returns 1.0 for every theme except None; native material surfaces use motion only). The material window and the surface show and hide together, the same as the native Mica/Acrylic path.

Also new in this push

  • Dock layout: the switcher keeps one size while navigating. Before, the window width followed each selected window's aspect, so moving between windows of different shapes resized it every time. The preview area is now a fixed box (the preview height setting at the work area's aspect, DPI-scaled and kept within the width limit), and each preview is fitted and centered inside it. Windows wider than the screen are letterboxed instead of widening the switcher.

/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 Nov 12, 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.


Thanks for the update. Since the last round:

  • Object hook: fixed. ApplyHiddenObjectEventHook (L8442–8448) now also needs g_touchpadReaderAvailable, and the reader-changed handler applies it again while the switcher is hidden (L20714). There's no startup race either. The reader posts only once g_hSwitcher exists (L23179–23181), and the switcher thread reads the hidden mode after creating its window (L22733), so a change on either side of that point is picked up.
  • Composed-material fade: you're right, and I withdraw that note. CurrentPresentationAlpha() returns 1.0 for every theme except None (L4215–4224), so the Mica/Acrylic surface is never alpha-faded.
  • g_composedMaterials: your call. ReleaseSwsComposition empties the map on the switcher thread (L4153–4156), so nothing leaks either way.
  • Dock preview box (L10255–10275): looks right. The slot height and content width now come from the fixed box, and each preview is fitted and centered inside it, so the window size no longer depends on the selection.

Re-reading the full source turned up one new item (1). Item 2 is unchanged and stays with the maintainer.

  1. Explorer leaks an icon handle each time the executable-icon fallback runs.

    • What happens: in ExplorerIpcWndProc (L9385–9397), when a window has no AppUserModelID and no window or class icon, Explorer calls SHGetFileInfoW(..., SHGFI_ICON | SHGFI_USEFILEATTRIBUTES ...). It stores the result in g_uwpIconCache under <exe path>_<size>. Unlike the AUMID branch (L9409–9412), it never looks the key up first. So every request creates a new HICON and overwrites the previous one. The old handle is never destroyed, because Wh_ModUninit only frees what is still in the map (L24074–24077).
    • When: the tool asks Explorer only after its own exe-icon extraction, WM_GETICON and class icons all come up empty (ResolveWindowIcon, L7871–7889). That's typically an executable with no icon resources whose window sets no icon.
    • How fast it grows: such a window's icon is requested again once its 30 s retry has passed, and on live refreshes (L7949). That's about one USER object leaked inside explorer.exe per switcher session per such window, until Explorer restarts.
    • The code is unchanged from the merged version, but it ships with this update too.
    • Fix: check the cache first, as the AUMID branch does:
    std::wstring cacheKey = std::wstring(exePath) + L"_" + std::to_wstring(desiredSizePx);
    auto cached = g_uwpIconCache.find(cacheKey);
    if (cached != g_uwpIconCache.end()) {
        CloseHandle(hProc2);
        return (LRESULT)cached->second;
    }
    SHFILEINFOW sfi = {};
    UINT flags = SHGFI_ICON | SHGFI_USEFILEATTRIBUTES | (desiredSizePx > 24 ? SHGFI_LARGEICON : SHGFI_SMALLICON);
    if (SHGetFileInfoW(exePath, FILE_ATTRIBUTE_NORMAL, &sfi, sizeof(sfi), flags) && sfi.hIcon) {
        g_uwpIconCache[cacheKey] = sfi.hIcon;
        CloseHandle(hProc2);
        return (LRESULT)sfi.hIcon;
    }
  2. Another mod's settings are read from Windhawk's internal storage (unchanged, kept by design).

    • GetCornerModStorage/DetectSystemDwmCornerRadiusValues (L2760–3067) locate the storage through windhawk.ini. They then read custom-corner-radius from Engine\Mods, in the registry or the portable INI.
    • This runs at settings load and on every show. It fails closed if the layout changes.
    • You've explained the coupling is intended, so there's nothing more to change unless the maintainer asks.
Code overview

For the maintainer: a map of the mod and what it touches outside its own process. Descriptive only — anything that needs a change is listed above. Line numbers refer to the mod source at commit bc8e28e. 🟢 added / 🟡 updated / ⚪ unchanged, compared to the currently merged version.

The merged version is 5,600 lines and this one is 24,106. Apart from short runs and the tool-launcher boilerplate, nearly every region is new or rewritten.

Code regions

  • 🟡 Line 1–11: metadata. Renamed to "WindowSwitcher++", version 3.0, and adds -lwinmm -ladvapi32 -lmsimg32 -lhid -lruntimeobject -lsecur32. Targets windhawk.exe (tool process) and explorer.exe.
  • 🟡 Line 13–238: README, rewritten. Preview GIFs; features, layouts (including the Dock grid), controls, touchpad gestures, show-delay rules; compatibility notes (Corner Preference precedence, composed material, custom-corner-radius detection, Windows Animations 1.3.7+); troubleshooting; screenshots.
  • 🟡 Line 240–940: settings:
    • Auto theme, backdrop blur, and the Badge and Dock groups (Dock grid rows, columns and centering);
    • animations, minimized indicators, and the Touchpad group (enabled by default);
    • Xbox Mode exclusion, rapidSwitching, includeDesktopItem;
    • Custom Header rules by process and/or title, with Light | Dark icons.
  • 🟡 Line 942–1652: includes (now with C++/WinRT Composition headers), timer and message IDs, the SwsSelectionProtocol names (L991), CreateSWSWindow (tries the private CreateWindowInBand, L1175), entry/settings structs and globals.
  • 🟢 Line 1654–2713: backdrop blur:
    • a full-screen layered window with a blurred desktop snapshot or wallpaper;
    • the wallpaper output is cached by path, size and write time;
    • the snapshot read excludes the switcher windows from capture (L2268–2327);
    • built on a worker thread, with the result in WM_APP + 11.
  • 🟡 Line 2734–3067: Windows 11 detection, and custom-corner-radius detection from Windhawk's own storage, resolved through windhawk.ini.
  • 🟡 Line 3069–3141: shared DWM thumbnail registration and unregister helpers.
  • 🟢 Line 3626–4170: presentation material state and the composed material:
    • for radii DWM can't draw, a click-through WindhawkSWS_Material window below each surface;
    • it hosts a Windows.UI.Composition DesktopWindowTarget with a blurred-wallpaper (Mica) or host-backdrop (Acrylic) brush through a hand-declared D2D ColorMatrix effect, clipped to the radius;
    • the compositor runs on the switcher thread's DispatcherQueue, and everything is released in ReleaseSwsComposition (L4153).
  • 🟡 Line 4171–6731: the animation engine (motion tracks, render caches), the ticker with timeBeginPeriod while it runs (L5564–5606), OnAnimationTick (L6320).
  • 🟡 Line 6733–7037: corner, size, color and theme helpers (GetCornerPref maps radii to DWM shapes), ResolveAPIs (L7028).
  • 🟡 Line 7039–7109: Explorer restart prompt (TaskDialog thread, named-event gate). "Yes" runs taskkill and restarts Explorer.
  • 🟡 Line 7112–8362:
    • Alt+Tab filtering, and close requests with engagement tracking (L7351), MRU;
    • Custom Header rules by executable and/or title with themed icons, plus title-match keys (L7469–7790);
    • web-app detection from the window property store (L7803–7880), ResolveWindowIcon (L7855);
    • the async icon worker (L7966–8070), Xbox Mode detection (L8179), IsEligibleWindow (L8196), the Desktop entry (L8328–8362).
  • 🟢 Line 8364–9308:
    • touchpad shared state, and the session-scoped object-hook mode, which while hidden is gated on reader availability (L8412–8448);
    • the shell-surface classifier (L8616), Windows Animations selection publication (L8724–8791), sticky two-finger close ownership (L9091–9176).
  • 🟡 Line 9309–9490: Explorer IPC window procedure (icon lookup by AUMID or executable, L9311–9427), the IPC thread with its 2 s hook-retry timer (L9429), and the tool-side client (L9460).
  • 🟡 Line 9491–11136:
    • grouping (web apps by app ID, L9549), app names (L9584), BuildWindowList (L9648), source sizing (L9801–9955);
    • Dock layout, with the paged grid and the fixed preview box (L10151–10393);
    • grid layout (ComputeLayout, L10395), thumbnail registration (L11009).
  • 🟡 Line 11138–13910: drawing (entries, Dock strip/grid, Badge overlay, overlay layers), PaintSwitcher (L13754; the layered path also serves the composed material).
  • 🟢 Line 13912–14303: CancelPendingShow (L13914), and the two-finger-tap override through SPI_SETTOUCHPADPARAMETERS, with a per-user recovery record.
  • 🟡 Line 14305–14681: the WH_MOUSE_LL hook: two-finger tap suppression (L14431), LowLevelMouseProc (L14487), the dedicated hook thread (L14615).
  • 🟡 Line 14683–15745: presentation and reveal; ApplyThemeToWindow choosing layered, composed or native material (L14981–15091); mirrors; ShowSwitcher (L15310); HideSwitcher (L15606).
  • 🟡 Line 15749–16161:
    • RestoreWindowIfIconic, and shell-flyout dismissal (L15817–15837);
    • ActivateExitedWindow (L15839), deferred activation across virtual desktops (L15983–16006), ShowDesktop (L16008), StartExitAnimation (L16023);
    • the owned-prompt commit (L16128–16161).
  • 🟡 Line 16163–18176: release coordination, navigation (including Dock grid rows/pages), hit testing, hover, live list add/remove.
  • 🟡 Line 18178–18559: IsTaskViewWindow, focus recovery, WinEventShowHideProc (L18330), the close queue (L18478–18559).
  • 🟢 Line 18561–19761: touchpad session logic and the raw-frame state machine (HandleRawTouchpadFrame, L19215).
  • 🟡 Line 19763–21137: SwitcherWndProc:
    • composed-material sync on window-position changes (L19765–19773), timers, close verification (L20207);
    • reader availability changes (L20706–20715) and settings changes;
    • hotkeys, painting, focus rules, shell-hook messages.
  • 🟡 Line 21139–21188: hotkey registration (Alt+Tab variants, Alt+VK_OEM_3/VK_OEM_8, the hotkey mutex).
  • 🟡 Line 21190–21718: settings helpers and LoadSettings (L21230), including migrations, Dock grid settings and Custom Header parsing.
  • 🟡 Line 21721–22476: Explorer hooks:
    • user32 RegisterHotKey/ShowWindow (L21732, L21787);
    • twinui.pcshell.dll Alt+Tab view hosts (optional);
    • five twinui.dll gesture-source hooks (optional entries, all-or-nothing, resolved once);
    • explorer.exe CTray::_RaiseDesktop/HandlePTPTap (optional).
  • 🟢 Line 22478–22656: WinRT TouchpadGesturesController takeover of three-finger gestures while the switcher is in the foreground.
  • 🟡 Line 22658–22924: SwitcherThread: setup, the foreground hook and hidden object-hook mode, the animation loop, ordered teardown including ReleaseSwsComposition.
  • 🟢 Line 22926–23705: raw HID touchpad reader thread (L23548).
  • 🟡 Line 23707–23775: WhTool_ModInit/WhTool_ModUninit/WhTool_ModSettingsChanged. They start, stop and join the reader and switcher threads.
  • 🟡 Line 23777–23888: tool-mod boilerplate globals, EntryPoint_Hook, IsMainExplorer, and the Explorer half of Wh_ModInit.
  • ⚪ Line 23890–23967: the tool-launcher half of Wh_ModInit (wiki snippet).
  • 🟡 Line 23969–24106: Wh_ModAfterInit, Wh_ModSettingsChanged, Wh_ModBeforeUninit and Wh_ModUninit (restart prompt in Explorer; WhTool_ModUninit + ExitProcess in the tool).

Side effects of interest

  • 🟢 Line 1766–1804, 2167–2203: external file read and registry read. The wallpaper image (path from SPI_GETDESKWALLPAPER, plus its size and write time) loaded with GDI+, and HKCU\Control Panel\Desktop WallpaperStyle/TileWallpaper. Read-only, when Backdrop Blur uses the wallpaper.
  • 🟡 Line 7670–7726, 8328–8339, 9385–9397, 9623: external file read. Icon resources and version info of other programs' executables, imageres.dll's desktop icon, and user-configured Custom Header icon files. Read-only.
  • 🟢 Line 7803–7880, 9549–9603: other reach outside the process. SHGetPropertyStoreForWindow on listed windows (AppUserModel ID, relaunch command, icon and name), the web app's icon file from the browser profile, and SHLoadIndirectString. Read-only.
  • 🟢 Line 2760–3067: registry and file read of another mod's settings in Windhawk's storage. It reads windhawk.ini [Storage], then …\Engine\Mods\custom-corner-radius and local@custom-corner-radius (registry or portable INI). Runs at settings load and on every show.
  • 🟡 Line 2282–2295, 2742–2745, 3877–3899, 6986–6992, 23814–23818: registry and system reads: CurrentBuildNumber, AppsUseLightTheme, DWM AccentColor, Personalize\EnableTransparency, high contrast and battery saver state. The mod writes to the registry nowhere.
  • 🟢 Line 13986–14303, 22648–22656: system settings change.
    • SystemParametersInfoW(SPI_SETTOUCHPADPARAMETERS, …, 0) turns Windows' two-finger tap off system-wide while the switcher is visible, and restores it on hide and exit.
    • The change isn't written to the user profile.
    • A per-user record twoFingerTapRecovery_<SID> in mod storage repairs it at the next start in the same logon.
  • 🟢 Line 5564–5606: system settings change. timeBeginPeriod(1) while the animation ticker runs.
  • 🟡 Line 8883–9071, 21721–22476, 23792: IPC between the tool and Explorer:
    • policy and marker properties on the switcher window, found from Explorer with FindWindowW (L21756, L21940);
    • WindhawkSWSNativeSwipeGate on Shell_TrayWnd (L21882–21910);
    • WM_APP + 8 posts from Explorer, and AllowSetForegroundWindow for the tool's PID (L21976, L22350).
  • 🟡 Line 8724–8791, 15749–15814, 22718: IPC with Windows Animations. Publishes WindhawkSWS.Selection* properties on the switcher window, reads windows-animations.* properties from the selected window, and posts its registered restore message.
  • 🟡 Line 9309–9490: IPC from the tool to Explorer. SendMessageTimeoutW of Windhawk_SWS_GetUwpIcon to WindhawkSWS_IpcWindow; Explorer returns HICONs.
  • 🟡 Line 7090, 21164, 23866–23881: IPC. The named mutex Windhawk_SWS_HotkeyMutex and the named event Windhawk_SWS_RestartPrompt.
  • 🟢 Line 22478–22656: other reach outside the process. TouchpadGesturesController.CreateForProcess claims three-finger gestures for the tool process while it's in the foreground.
  • 🟢 Line 23184–23634: other reach outside the process. RegisterRawInputDevices (touchpad, RIDEV_INPUTSINK) reads all touchpad HID reports in the background while touchpad gestures are enabled.
  • 🟢 Line 8412–8448, 15477–15485, 22727–22733: other reach outside the process. Out-of-context SetWinEventHook:
    • EVENT_SYSTEM_FOREGROUND for the switcher thread's lifetime;
    • EVENT_OBJECT_DESTROY–EVENT_OBJECT_HIDE during a session;
    • EVENT_OBJECT_SHOW only while hidden, with touchpad handling enabled and a precision touchpad registered.
  • 🟡 Line 14431–14643: other reach outside the process. A WH_MOUSE_LL hook on a dedicated thread, installed while the switcher is shown and for a short tap tail after it. It swallows wheel events. Where the tap override is unavailable, it also swallows promoted two-finger right-click pairs, waiting up to 40 ms for the reader.
  • 🟢 Line 15827–15834, 18288–18294, 18566–18579, 20342–20348: other reach outside the process. SendInput of:
    • Escape, to close a shell flyout on touchpad commit and to dismiss Task View;
    • VK 0xE8, for foreground eligibility;
    • Tab, in Xbox Mode.
  • 🟢 Line 18299, 21787: other reach outside the process. ShowWindowAsync(SW_HIDE) on Explorer's Task View or native Alt+Tab window during an owned touchpad stroke; Explorer's ShowWindow hook suppresses their shows.
  • 🟢 Line 2205–2264, 2282–2327: other reach outside the process.
    • A GetDC(NULL) + StretchBlt capture of the whole virtual desktop for the snapshot backdrop, on a worker thread per show, kept in memory only.
    • The mod's own windows get WDA_EXCLUDEFROMCAPTURE during the read (Windows 10 2004+).
  • 🟢 Line 16008–16021: other reach outside the process. Shell.Application IShellDispatch4::ToggleDesktop for the Desktop entry.
  • 🟡 Line 9648–9661, 15749–15980, 18478–18559, 20207–20304: other reach outside the process. EnumWindows over all top-level windows (intended). On other apps' windows it uses:
    • DWM thumbnails, including Progman for the Desktop entry;
    • WM_GETICON via SendMessageTimeoutW;
    • ShowWindowAsync/SC_RESTORE, and SetForegroundWindow/SwitchToThisWindow;
    • SC_CLOSE/WM_CLOSE, with a 3 s fallback that is skipped once the user has reached the window.
  • 🟡 Line 7054–7109, 23866–23881, 24079–24093: other reach outside the process.
    • CreateProcess of cmd.exe /c "timeout … & taskkill /F /IM explorer.exe & start explorer.exe" when the user accepts the restart prompt.
    • The prompt comes from Explorer's Wh_ModInit (if Alt+Tab is already taken) and from every main-Explorer Wh_ModUninit, which waits up to 30 s for an answer.


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.

Important: Each reply that was generated by AI must be disclosed by including the following text in the reply: "Generated by AI. Model: (ai-model-name)".

@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 Nov 12, 2026
@Louis047

Copy link
Copy Markdown
Contributor Author

Thanks for the review. Changes since the reviewed head (bc8e28e), now at 7adc487:

  1. Explorer icon leak in the executable-icon fallback: fixed as suggested. The branch now looks up <exe path>_<size> in the cache first and only calls SHGetFileInfoW on a miss, like the AUMID branch. Returning the cached handle is safe because the tool treats the result as borrowed and makes its own CopyIcon. Checked with a test program that has no icon resources and sets no window or class icon: before the change, Explorer's GDI count rose by 3 on every switcher session; after it, the count stays flat.
  2. Custom corner radius storage: unchanged and kept by design, as noted.

Also new in this push

  • Up/Down and touchpad up/down navigation keep a goal column for a chain of cross-row moves, like a text editor. Before, each move aimed at the current tile's center, so rows with different counts or widths made the selection drift, and Down then Up could land on a different tile. The goal is measured from the switcher's center (rows and columns are centered, and the switcher size changes when the view scrolls). The Dock grid uses the visual column, which accounts for a centered partial row. Any other selection change starts a new goal.

/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 Nov 12, 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.


Thanks for the update. Both changes since the last round check out:

  • Explorer icon leak: fixed. The executable fallback in ExplorerIpcWndProc now looks up <exe path>_<size> before calling SHGetFileInfoW (L9390–9406), as the AUMID branch does. Returning the cached handle is safe, because the tool copies whatever it receives.
  • Up/Down goal column: checked. The state is at L15319–15349 and CycleDirectional at L17097–17348.
    • Dock grid: the slot mapping matches the centered-row shift in ComputeDockLayout, and the clamp keeps the target inside its row (L17111–17124).
    • Other layouts: they measure from the switcher's center, so the goal survives a scroll that resizes the window.
    • One small gap is under Functionality notes.

The remaining item is unchanged from the last round and stays with the maintainer.

  1. Another mod's settings are read from Windhawk's internal storage (unchanged, kept by design).
    • GetCornerModStorage/DetectSystemDwmCornerRadiusValues (L2760–3067) locate the storage through windhawk.ini. They then read custom-corner-radius from Engine\Mods, in the registry or the portable INI.
    • This runs at settings load and on every show. It fails closed if the layout changes.
    • You've explained the coupling is intended, so there's nothing more to change unless the maintainer asks.
Optional improvements

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

  • Include the headers for the math and limits you use. The new navigation code adds another floor (L17120), on top of the existing lround/roundf calls, and there's INT_MAX (L17330 and others) and std::is_trivially_destructible_v (L22112). None of <cmath>, <climits> and <type_traits> is included directly, so the file builds only because other headers happen to pull them in. Adding the three includes next to <algorithm> (L969) makes it independent of that.

Functionality notes

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

  • The goal column survives a Left/Right round trip.
    • Why: CrossLineGoalCurrent() only checks that the selection is still the entry the last cross-row move picked. Left/Right (and Tab, the wheel and touchpad steps) go through CycleLinear without clearing the goal.
    • Example: in a horizontal layout, Down from the last tile of a 5-tile row lands on the last tile of a 3-tile row. Then Left followed by Right returns to that same tile. Up now aims at the old 5-tile column instead of the current tile, unlike the text-editor behavior the PR describes ("any other selection change starts a new goal").
    • Fix: add s_crossLineGoal.valid = false; at the top of CycleLinear (L16771). Both cross-row paths still keep their goal: the Dock grid calls RememberCrossLineGoal after its own CycleLinear (L17123–17124), and the other layouts assign g_selectedIndex directly (L17344–17345).

Code overview

For the maintainer: a map of the mod and what it touches outside its own process. Descriptive only — anything that needs a change is listed above. Line numbers refer to the mod source at commit 7adc487. 🟢 added / 🟡 updated / ⚪ unchanged, compared to the currently merged version.

The merged version is 5,600 lines and this one is 24,175. Apart from short runs and the tool-launcher boilerplate, nearly every region is new or rewritten.

Code regions

  • 🟡 Line 1–11: metadata. Renamed to "WindowSwitcher++", version 3.0, and adds -lwinmm -ladvapi32 -lmsimg32 -lhid -lruntimeobject -lsecur32. Targets windhawk.exe (tool process) and explorer.exe.
  • 🟡 Line 13–238: README, rewritten. Preview GIFs; features, layouts (including the Dock grid), controls, touchpad gestures, show-delay rules; compatibility notes (Corner Preference precedence, composed material, custom-corner-radius detection, Windows Animations 1.3.7+); troubleshooting; screenshots.
  • 🟡 Line 240–940: settings:
    • Auto theme, backdrop blur, and the Badge and Dock groups (Dock grid rows, columns and centering);
    • animations, minimized indicators, and the Touchpad group (enabled by default);
    • Xbox Mode exclusion, rapidSwitching, includeDesktopItem;
    • Custom Header rules by process and/or title, with Light | Dark icons.
  • 🟡 Line 942–1652: includes (now with C++/WinRT Composition headers), timer and message IDs, the SwsSelectionProtocol names (L991), CreateSWSWindow (tries the private CreateWindowInBand, L1175), entry/settings structs and globals.
  • 🟢 Line 1654–2713: backdrop blur:
    • a full-screen layered window with a blurred desktop snapshot or wallpaper;
    • the wallpaper output is cached by path, size and write time;
    • the snapshot read excludes the switcher windows from capture (L2268–2327);
    • built on a worker thread, with the result in WM_APP + 11.
  • 🟡 Line 2734–3067: Windows 11 detection, and custom-corner-radius detection from Windhawk's own storage, resolved through windhawk.ini.
  • 🟡 Line 3069–3141: shared DWM thumbnail registration and unregister helpers.
  • 🟢 Line 3626–4170: presentation material state and the composed material:
    • for radii DWM can't draw, a click-through WindhawkSWS_Material window below each surface;
    • it hosts a Windows.UI.Composition DesktopWindowTarget with a blurred-wallpaper (Mica) or host-backdrop (Acrylic) brush through a hand-declared D2D ColorMatrix effect, clipped to the radius;
    • the compositor runs on the switcher thread's DispatcherQueue, and everything is released in ReleaseSwsComposition (L4153).
  • 🟡 Line 4171–6731: the animation engine (motion tracks, render caches), the ticker with timeBeginPeriod while it runs (L5564–5606), OnAnimationTick (L6320).
  • 🟡 Line 6733–7037: corner, size, color and theme helpers (GetCornerPref maps radii to DWM shapes), ResolveAPIs (L7028).
  • 🟡 Line 7039–7109: Explorer restart prompt (TaskDialog thread, named-event gate). "Yes" runs taskkill and restarts Explorer.
  • 🟡 Line 7112–8362:
    • Alt+Tab filtering, and close requests with engagement tracking (L7351), MRU;
    • Custom Header rules by executable and/or title with themed icons, plus title-match keys (L7469–7790);
    • web-app detection from the window property store (L7803–7880), ResolveWindowIcon (L7855);
    • the async icon worker (L7966–8070), Xbox Mode detection (L8179), IsEligibleWindow (L8196), the Desktop entry (L8328–8362).
  • 🟢 Line 8364–9308:
    • touchpad shared state, and the session-scoped object-hook mode, which while hidden is gated on reader availability (L8412–8448);
    • the shell-surface classifier (L8616), Windows Animations selection publication (L8724–8791), sticky two-finger close ownership (L9091–9176).
  • 🟡 Line 9309–9499: Explorer IPC window procedure (icon lookup by AUMID or executable, both cached per key, L9311–9436), the IPC thread with its 2 s hook-retry timer (L9438), and the tool-side client (L9469).
  • 🟡 Line 9500–11145:
    • grouping (web apps by app ID, L9558), app names (L9593), BuildWindowList (L9657), source sizing (L9810–9964);
    • Dock layout, with the paged grid and the fixed preview box (L10160–10402);
    • grid layout (ComputeLayout, L10404), thumbnail registration (L11018).
  • 🟡 Line 11147–13919: drawing (entries, Dock strip/grid, Badge overlay, overlay layers), PaintSwitcher (L13763; the layered path also serves the composed material).
  • 🟢 Line 13921–14312: CancelPendingShow (L13923), and the two-finger-tap override through SPI_SETTOUCHPADPARAMETERS, with a per-user recovery record.
  • 🟡 Line 14314–14690: the WH_MOUSE_LL hook: two-finger tap suppression (L14440), LowLevelMouseProc (L14496), the dedicated hook thread (L14624).
  • 🟡 Line 14692–15787: presentation and reveal; ApplyThemeToWindow choosing layered, composed or native material (L14990–15100); mirrors; the cross-row navigation goal (L15319–15349); ShowSwitcher (L15351); HideSwitcher (L15648).
  • 🟡 Line 15791–16203:
    • RestoreWindowIfIconic, and shell-flyout dismissal (L15859–15879);
    • ActivateExitedWindow (L15881), deferred activation across virtual desktops (L16025–16048), ShowDesktop (L16050), StartExitAnimation (L16065);
    • the owned-prompt commit (L16170–16203).
  • 🟡 Line 16205–18245: release coordination, navigation (including Dock grid rows/pages, and CycleDirectional with its goal column, L17097), hit testing, hover, live list add/remove.
  • 🟡 Line 18247–18628: IsTaskViewWindow, focus recovery, WinEventShowHideProc (L18399), the close queue (L18547–18628).
  • 🟢 Line 18630–19830: touchpad session logic and the raw-frame state machine (HandleRawTouchpadFrame, L19284).
  • 🟡 Line 19832–21206: SwitcherWndProc:
    • composed-material sync on window-position changes (L19834–19842), timers, close verification (L20276);
    • reader availability changes (L20775–20784) and settings changes;
    • hotkeys, painting, focus rules, shell-hook messages.
  • 🟡 Line 21208–21257: hotkey registration (Alt+Tab variants, Alt+VK_OEM_3/VK_OEM_8, the hotkey mutex).
  • 🟡 Line 21259–21787: settings helpers and LoadSettings (L21299), including migrations, Dock grid settings and Custom Header parsing.
  • 🟡 Line 21790–22545: Explorer hooks:
    • user32 RegisterHotKey/ShowWindow (L21801, L21856);
    • twinui.pcshell.dll Alt+Tab view hosts (optional);
    • five twinui.dll gesture-source hooks (optional entries, all-or-nothing, resolved once);
    • explorer.exe CTray::_RaiseDesktop/HandlePTPTap (optional).
  • 🟢 Line 22547–22725: WinRT TouchpadGesturesController takeover of three-finger gestures while the switcher is in the foreground.
  • 🟡 Line 22727–22993: SwitcherThread: setup, the foreground hook and hidden object-hook mode, the animation loop, ordered teardown including ReleaseSwsComposition.
  • 🟢 Line 22995–23774: raw HID touchpad reader thread (L23617).
  • 🟡 Line 23776–23844: WhTool_ModInit/WhTool_ModUninit/WhTool_ModSettingsChanged. They start, stop and join the reader and switcher threads.
  • 🟡 Line 23846–23957: tool-mod boilerplate globals, EntryPoint_Hook, IsMainExplorer, and the Explorer half of Wh_ModInit.
  • ⚪ Line 23959–24036: the tool-launcher half of Wh_ModInit (wiki snippet).
  • 🟡 Line 24038–24175: Wh_ModAfterInit, Wh_ModSettingsChanged, Wh_ModBeforeUninit and Wh_ModUninit (restart prompt in Explorer; WhTool_ModUninit + ExitProcess in the tool).

Side effects of interest

  • 🟢 Line 1766–1804, 2167–2203: external file read and registry read. The wallpaper image (path from SPI_GETDESKWALLPAPER, plus its size and write time) loaded with GDI+, and HKCU\Control Panel\Desktop WallpaperStyle/TileWallpaper. Read-only, when Backdrop Blur uses the wallpaper.
  • 🟡 Line 7670–7726, 8328–8339, 9385–9406, 9632: external file read. Icon resources and version info of other programs' executables, imageres.dll's desktop icon, and user-configured Custom Header icon files. Read-only.
  • 🟢 Line 7803–7880, 9558–9612: other reach outside the process. SHGetPropertyStoreForWindow on listed windows (AppUserModel ID, relaunch command, icon and name), the web app's icon file from the browser profile, and SHLoadIndirectString. Read-only.
  • 🟢 Line 2760–3067: registry and file read of another mod's settings in Windhawk's storage. It reads windhawk.ini [Storage], then …\Engine\Mods\custom-corner-radius and local@custom-corner-radius (registry or portable INI). Runs at settings load and on every show.
  • 🟡 Line 2282–2295, 2742–2745, 3877–3899, 6986–6992, 23883–23887: registry and system reads: CurrentBuildNumber, AppsUseLightTheme, DWM AccentColor, Personalize\EnableTransparency, high contrast and battery saver state. The mod writes to the registry nowhere.
  • 🟢 Line 13995–14312, 22717–22725: system settings change.
    • SystemParametersInfoW(SPI_SETTOUCHPADPARAMETERS, …, 0) turns Windows' two-finger tap off system-wide while the switcher is visible, and restores it on hide and exit.
    • The change isn't written to the user profile.
    • A per-user record twoFingerTapRecovery_<SID> in mod storage repairs it at the next start in the same logon.
  • 🟢 Line 5564–5606: system settings change. timeBeginPeriod(1) while the animation ticker runs.
  • 🟡 Line 8883–9071, 21790–22545, 23861: IPC between the tool and Explorer:
    • policy and marker properties on the switcher window, found from Explorer with FindWindowW (L21825, L22009);
    • WindhawkSWSNativeSwipeGate on Shell_TrayWnd (L21951–21979);
    • WM_APP + 8 posts from Explorer, and AllowSetForegroundWindow for the tool's PID (L22045, L22419).
  • 🟡 Line 8724–8791, 15791–15856, 22787: IPC with Windows Animations. Publishes WindhawkSWS.Selection* properties on the switcher window, reads windows-animations.* properties from the selected window, and posts its registered restore message.
  • 🟡 Line 9309–9499: IPC from the tool to Explorer. SendMessageTimeoutW of Windhawk_SWS_GetUwpIcon to WindhawkSWS_IpcWindow; Explorer returns cached HICONs, which the tool copies.
  • 🟡 Line 7090, 21233, 23935–23950: IPC. The named mutex Windhawk_SWS_HotkeyMutex and the named event Windhawk_SWS_RestartPrompt.
  • 🟢 Line 22547–22725: other reach outside the process. TouchpadGesturesController.CreateForProcess claims three-finger gestures for the tool process while it's in the foreground.
  • 🟢 Line 23253–23703: other reach outside the process. RegisterRawInputDevices (touchpad, RIDEV_INPUTSINK) reads all touchpad HID reports in the background while touchpad gestures are enabled.
  • 🟢 Line 8412–8448, 15519–15527, 22796–22802: other reach outside the process. Out-of-context SetWinEventHook:
    • EVENT_SYSTEM_FOREGROUND for the switcher thread's lifetime;
    • EVENT_OBJECT_DESTROY–EVENT_OBJECT_HIDE during a session;
    • EVENT_OBJECT_SHOW only while hidden, with touchpad handling enabled and a precision touchpad registered.
  • 🟡 Line 14440–14652: other reach outside the process. A WH_MOUSE_LL hook on a dedicated thread, installed while the switcher is shown and for a short tap tail after it. It swallows wheel events. Where the tap override is unavailable, it also swallows promoted two-finger right-click pairs, waiting up to 40 ms for the reader.
  • 🟢 Line 15869–15876, 18357–18363, 18635–18648, 20411–20417: other reach outside the process. SendInput of:
    • Escape, to close a shell flyout on touchpad commit and to dismiss Task View;
    • VK 0xE8, for foreground eligibility;
    • Tab, in Xbox Mode.
  • 🟢 Line 18368, 21856: other reach outside the process. ShowWindowAsync(SW_HIDE) on Explorer's Task View or native Alt+Tab window during an owned touchpad stroke; Explorer's ShowWindow hook suppresses their shows.
  • 🟢 Line 2205–2264, 2282–2327: other reach outside the process.
    • A GetDC(NULL) + StretchBlt capture of the whole virtual desktop for the snapshot backdrop, on a worker thread per show, kept in memory only.
    • The mod's own windows get WDA_EXCLUDEFROMCAPTURE during the read (Windows 10 2004+).
  • 🟢 Line 16050–16063: other reach outside the process. Shell.Application IShellDispatch4::ToggleDesktop for the Desktop entry.
  • 🟡 Line 9657–9670, 15791–16022, 18547–18628, 20276–20373: other reach outside the process. EnumWindows over all top-level windows (intended). On other apps' windows it uses:
    • DWM thumbnails, including Progman for the Desktop entry;
    • WM_GETICON via SendMessageTimeoutW;
    • ShowWindowAsync/SC_RESTORE, and SetForegroundWindow/SwitchToThisWindow;
    • SC_CLOSE/WM_CLOSE, with a 3 s fallback that is skipped once the user has reached the window.
  • 🟡 Line 7054–7109, 23935–23950, 24148–24162: other reach outside the process.
    • CreateProcess of cmd.exe /c "timeout … & taskkill /F /IM explorer.exe & start explorer.exe" when the user accepts the restart prompt.
    • The prompt comes from Explorer's Wh_ModInit (if Alt+Tab is already taken) and from every main-Explorer Wh_ModUninit, which waits up to 30 s for an answer.


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.

Important: Each reply that was generated by AI must be disclosed by including the following text in the reply: "Generated by AI. Model: (ai-model-name)".

@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 Nov 12, 2026

This branch has not been deployed

No deployments
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.

1 participant