Skip to content

Restore foreground application after desktop switching - #113

Open
timburman wants to merge 4 commits into
Ciantic:rustfrom
timburman:rust
Open

Restore foreground application after desktop switching#113
timburman wants to merge 4 commits into
Ciantic:rustfrom
timburman:rust

Conversation

@timburman

Copy link
Copy Markdown

Summary

This PR improves virtual desktop switching behavior on newer Windows 11 builds by restoring foreground application focus after a desktop switch. It also introduces new raw and move-foreground APIs while preserving backward compatibility with the existing API surface.

While using VirtualDesktopAccessor on Windows 11 25H2, I noticed that desktop switching through IVirtualDesktopManagerInternal::switch_desktop() could leave keyboard focus with the shell instead of an application on the destination desktop. After investigating the behavior and testing possible approaches, this implementation restores focus in a way that closely matches native Explorer desktop switching.

Restore foreground application after desktop switching

Problem

On Windows 11 25H2, switching desktops through IVirtualDesktopManagerInternal::switch_desktop() can leave no application focused on the destination desktop. As a result, keyboard input may remain with the shell until the user manually activates a window.

Native Explorer desktop switching restores focus automatically.

Solution

After a successful desktop switch:

  • Enumerate IApplicationViewCollection in Z-order.
  • Locate the highest-priority eligible application on the destination desktop.
  • Skip minimized, hidden, tool, and known overlay windows using documented and isolated heuristics.
  • Restore focus using IApplicationView::set_focus(), while retaining SetForegroundWindow() as a compatibility fallback.

Additionally, when switching to the current desktop, the implementation detects whether focus has been left on the shell (Shell_TrayWnd, WorkerW, or Progman) and restores the foreground application only when necessary.

The window-selection heuristics have been isolated into a dedicated helper and documented as experimental so they can be refined independently if future Windows versions expose better APIs.

New raw desktop switch API

Rust

switch_desktop_raw(desktop)

DLL

GoToDesktopNumberRaw(desktop_number)

This exposes the original COM desktop switching behavior without automatic focus restoration for callers that require the underlying semantics.

New move-foreground API

Rust

move_foreground_window_to_desktop(desktop)

DLL

GoToDesktopNumberAndMoveForegroundWindow(desktop_number)

This wraps Windows' switch_desktop_and_move_foreground_view() API, allowing the current foreground window to be moved to another desktop while switching to it.

During development I initially investigated replacing switch_desktop() with this API directly, but testing showed that it has different semantics: it moves the currently focused window between desktops rather than restoring focus on the destination desktop. Exposing it as a separate API preserves both behaviors cleanly.

Additional Improvements

  • Added switch_desktop_raw() to preserve the original COM behavior.
  • Refactored focus selection into a dedicated helper for readability and maintainability.
  • Replaced magic numbers with named constants.
  • Added inline documentation explaining the focus restoration heuristics.
  • Updated the README with the new exported APIs.
  • Added tests covering the raw desktop switching path.

Testing

Tested on:

  • Windows 11 25H2

Validation performed:

  • Extensive daily usage on two Windows 11 25H2 systems
  • Repeated virtual desktop switching
  • Same-desktop switching
  • Multiple virtual desktops
  • Picture-in-Picture windows
  • Rust API
  • DLL exports

The project builds successfully and the existing test suite passes.


I'd appreciate any feedback if there are historical or compatibility considerations that would make a different implementation preferable.

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.

1 participant