Skip to content

Fix #166: stop focus restore from fighting native desktop switching - #206

Open
koppor wants to merge 2 commits into
dankrusi:mainfrom
koppor:fix/166-restore-focus-fighting
Open

koppor wants to merge 2 commits into
dankrusi:mainfrom
koppor:fix/166-restore-focus-fighting

Conversation

@koppor

@koppor koppor commented Jul 15, 2026

Copy link
Copy Markdown
Contributor

Fixes the focus "fighting" from #166, specifically the mechanism identified in this comment: switching desktops natively snaps back to the previous desktop and refocuses the wrong window.

Root cause

With feature.restorePreviousWindowFocus enabled, every detected switch runs App._restorePrevWinFocus(), which SetForegroundWindow()s the window recorded for the desktop just landed on. That record comes from _storeLastWinFocused(), polled every 200 ms:

IntPtr hWnd = GetForegroundWindow();           // (A) reads window
...
var displayNumber = GetVDDisplayNumber(false); // (B) reads desktop number
VDDToLastFocusedWin[displayNumber] = hWnd;      // files A under B

(A) and (B) are read non-atomically, so during a native switch animation a window that lives on desktop 1 gets filed under desktop 2. Restoring it later force-focuses a window on another desktop, and Windows jumps to that desktop to show it — the snap-back. Rapid switches poison the map heavily, producing the erratic multi-hop behavior also reported.

Fix

Guard both sides with the public IVirtualDesktopManager::IsWindowOnCurrentVirtualDesktop:

  • Store: only record a window under the desktop it actually lives on (no more poisoning).
  • Restore: only SetForegroundWindow if the recorded window is still on the desktop just landed on (no snap-back even if a stale record slips through).

The public IVirtualDesktopManager (CLSID AA509086-…, IID A5CD92FF-…) is stable across all Win10 1607+/Win11 builds — important because affected users fall back to VirtualDesktopWin11_23H2_2921, which has no per-version manager. It's added once in Util.OS (fail-closed on any COM error), so the 9 version-specific implementations, the app's IVirtualDesktopManager interface, and the default settings are all untouched.

Scope

This addresses the focus-restoration fighting. The feature is off by default, so the separate "fighting on stock config" reports (feature disabled ⇒ no SetForegroundWindow on the switch path at all) are a different, Windows-level cause (wallpaper/accent-color repaint, stale post-sleep COM) and are out of scope here.

Testing

  • Builds clean (VS 2026 MSBuild, .NET Framework 4.7.2).
  • Reproduced the comment's scenario with the feature enabled (two desktops, two apps each, different focus per desktop): before → snaps back to the origin desktop; after → stays on the target desktop with its own last-focused window. Rapid switching no longer multi-hops.
  • Ran ~1 hour of normal daily use with restorePreviousWindowFocus on — no snap-back observed.

No automated test: the path is COM- and live-desktop-bound and the repo has no test project.

🤖 Generated with Claude Code

…tching

With feature.restorePreviousWindowFocus enabled, switching virtual desktops
natively (Ctrl+Win+Left/Right) could immediately snap back to the previous
desktop.

_storeLastWinFocused() reads the foreground window and the current desktop
number non-atomically (200 ms poll), so during a switch animation it can file
a window under the wrong desktop number. _restorePrevWinFocus() then calls
SetForegroundWindow() on that window when landing on the desktop, and because
the window actually lives on another desktop, Windows jumps back to it -- the
"fighting" from the issue. Rapid switches poison the map heavily, producing the
erratic multi-hop behavior also reported.

Guard both sides with the public IVirtualDesktopManager COM interface's
IsWindowOnCurrentVirtualDesktop(): only record a window under the desktop it
actually lives on, and only restore focus to a window that is still on the
desktop just landed on. The public interface is stable across all Win10 1607+
and Win11 builds, so it works even when the app falls back to the
VirtualDesktopWin11_23H2_2921 implementation (which has no per-version manager).
Added once in Util.OS -- no changes to the version-specific implementations or
to default settings (the feature stays off by default).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@dankrusi

Copy link
Copy Markdown
Owner

hi @koppor , many thanks.

Am testing this now as well... Anyone on windows 10 tested this?

@koppor

koppor commented Jul 20, 2026

Copy link
Copy Markdown
Contributor Author

Am testing this now as well... Anyone on windows 10 tested this?

Yes. The PR was created on Windows 10 (although development took place on Windows 11).

@dankrusi

Copy link
Copy Markdown
Owner

@koppor What do you mean exactly the PR was created on Windows 10? Does that mean it was also thoroughly tested on Windows 10? I am not just talking about it building, but the actual app tested properly for any regressions (including cases like sleep/resume etc)

@koppor

koppor commented Jul 21, 2026

Copy link
Copy Markdown
Contributor Author

@koppor What do you mean exactly the PR was created on Windows 10? Does that mean it was also thoroughly tested on Windows 10? I am not just talking about it building, but the actual app tested properly for any regressions (including cases like sleep/resume etc)

My desktop machine is Windows 10, but has serious issues with suspend/resume, so I could not test there.

I see two options to move forward:

  1. Build a test binary and ask other users to test / work with it and if there is no related issue then release in 1 or 2 months
  2. Setup Sandbox Windows 10 VM and do a structured test with a checklist covering all the edge cases

@koppor

koppor commented Jul 29, 2026

Copy link
Copy Markdown
Contributor Author

Does that mean it was also thoroughly tested on Windows 10?

I am using it continuously on my Windows 10 desktop and Windows 11 laptop. Please consider it for the next release.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants