Throttle FG-window polling to fix race with shell hotkeys (fixes #166) - #201
rossveitch wants to merge 1 commit into
Conversation
The _MonitorFGWindowName thread polled the foreground window's name every 20 ms (50 Hz) by calling Util.OS.GetForegroundWindowName(), which sends synchronous WM_GETTEXT to the foreground window's message pump. The thread ran unconditionally, even when feature.restorePreviousWindowFocus (the only feature that needs sub-second freshness) was disabled. During a Win+Ctrl+Left/Right desktop transition the shell briefly has its hotkey and message queues in flux. The helper's 50 Hz blocking SendMessage calls race with the shell's own WM_HOTKEY handling, intermittently delaying or dropping the shell's processing of the user's keystroke. Symptom: the native Win+Ctrl+Arrow desktop switch works most of the time but occasionally no-ops. This matches the report in dankrusi#166. Fix: - _MonitorFGWindowName: poll at 200 ms when restorePreviousWindowFocus is on, 1000 ms when off (was 20 ms unconditionally). The history feeds two consumers: (a) restorePreviousWindowFocus, which needs ~200 ms; and (b) the tray-click "is Task View already open" heuristic in AppForm.cs, which only cares about state at click time -- 1 s is more than enough. - _monitorFocusedWindow: skip work entirely when restorePreviousWindowFocus is disabled (5 s heartbeat). This thread's only purpose is to feed that feature; polling when the feature is off was pure waste. No behavior change for users with restorePreviousWindowFocus=true beyond a 10x reduction in foreground polling rate (200 ms is still plenty for that feature). For users with the feature off (default), the polling load drops 50x and the race condition disappears. Fixes dankrusi#166
Thanks for digging into this @rossveitch. I opened #206 for the same issue and I think the two are complementary rather than competing — they fix different failure modes reported in #166:
They touch different methods ( One honest caveat on the diagnosis: "stopping WVDH makes the shortcut 100% reliable" is consistent with both theories (quitting stops the polling storm and the restore path), so it doesn't by itself pin the no-op on the My suggestion is to land both, since they cover the two distinct symptoms in #166. Happy to fold the throttling into #206 (or rebase #206 on top of this) if a single PR is easier to review — whatever @dankrusi prefers. 🤖 Generated with Claude Code |

Summary
Fixes #166 (Helper fighting native
Win+Ctrl+Left/Rightdesktop switching)._MonitorFGWindowNamepolled the foreground window's name every 20 ms by callingUtil.OS.GetForegroundWindowName(), which issues synchronousWM_GETTEXTto the foreground window's message pump. This thread ran unconditionally, even whenfeature.restorePreviousWindowFocus(the only feature that genuinely needs sub-second foreground-window data) was disabled.During a
Win+Ctrl+Left/Rightdesktop transition the shell briefly has its hotkey and message queues in flux. The helper's 50 Hz blockingSendMessagecalls race with the shell's ownWM_HOTKEYhandling, intermittently delaying or dropping the shell's processing of the user's keystroke. Result: the native desktop-switch shortcut works most of the time but occasionally no-ops. Stopping the helper makes the shortcut 100% reliable — confirmed locally and matches the symptom report in #166.Fix
Two-line conceptual change inside
App.cs:_MonitorFGWindowName— poll at 200 ms whenrestorePreviousWindowFocusis on, 1000 ms when off (was 20 ms unconditionally). TheFGWindowHistorylist feeds two consumers:restorePreviousWindowFocus, which needs ~200 ms.AppForm.cs— only cares about state at click time, 1 s is plenty._monitorFocusedWindow— skip work entirely whenrestorePreviousWindowFocusis off (5 s heartbeat sleep). This thread exists solely to feed that feature.Impact
restorePreviousWindowFocus = true(opt-in): polling load drops 10× (200 ms vs 20 ms). 200 ms is well within human-perceptible latency for the feature's purpose.Win+Ctrl+Arrowdisappears.FGWindowHistoryis still populated; just less aggressively.Why this is the right layer to fix it
Yes, ideally desktop-change monitoring would use
IVirtualDesktopNotificationevent sinks instead of any polling at all, and foreground-window tracking would useEVENT_SYSTEM_FOREGROUNDWinEvent hooks. Those are bigger refactors. This PR is the smallest change that eliminates the user-visible bug without touching architecture.Test plan
useHotKey... = false,restorePreviousWindowFocus = false) — confirmWin+Ctrl+Left/Rightworks reliably across many invocations and across different foreground apps (Edge, terminal, Explorer)feature.restorePreviousWindowFocus = true— confirm focus restoration still works on desktop switchfeature.showDesktopNumberInIconTray.clickToOpenTaskView = true— confirm tray-click open-task-view still suppresses the open when Task View is already foregroundTested locally on Surface Pro 8 / Windows 11 25H2 (build 26200).
🤖 Generated with Claude Code