From 8f03d836b664b3dc6c2152ae0fde7158f4fc1297 Mon Sep 17 00:00:00 2001 From: Julien Maille Date: Sat, 7 Nov 2026 11:16:50 +0100 Subject: [PATCH 1/2] Close the shell flyout before switching apps from the dock --- src/commands/open_windows.rs | 59 +++++++++++++++++++++++++++++++++++- 1 file changed, 58 insertions(+), 1 deletion(-) diff --git a/src/commands/open_windows.rs b/src/commands/open_windows.rs index ffb2dfe..840971f 100644 --- a/src/commands/open_windows.rs +++ b/src/commands/open_windows.rs @@ -7,6 +7,7 @@ use windows::{ System::Threading::{ OpenProcess, PROCESS_NAME_WIN32, PROCESS_QUERY_LIMITED_INFORMATION, QueryFullProcessImageNameW, }, + UI::Input::KeyboardAndMouse::{KEYBD_EVENT_FLAGS, KEYEVENTF_KEYUP, VK_ESCAPE, keybd_event}, UI::WindowsAndMessaging::*, }, core::{BOOL, PWSTR}, @@ -99,7 +100,26 @@ pub fn focus(hwnd: isize) -> Result<(), String> { if IsIconic(hwnd).as_bool() { let _ = ShowWindow(hwnd, SW_RESTORE); } - if SetForegroundWindow(hwnd).as_bool() { + if GetForegroundWindow() == hwnd { + return Ok(()); + } + if SetForegroundWindow(hwnd).as_bool() && wait_for_foreground(hwnd) { + return Ok(()); + } + // A shell flyout such as the Action Center holds the foreground from + // an AppContainer, where Windows refuses both a plain activation and + // thread-input attachment. Escape closes it, as the taskbar's own + // click does, and then the app can come forward. + let foreground = GetForegroundWindow(); + dismiss_shell_flyout(foreground); + for _ in 0..20 { + if GetForegroundWindow() != foreground { + break; + } + std::thread::sleep(std::time::Duration::from_millis(10)); + } + let _ = SetForegroundWindow(hwnd); + if wait_for_foreground(hwnd) { Ok(()) } else { Err("Windows did not switch to the window.".into()) @@ -124,6 +144,43 @@ pub fn minimize(hwnd: isize) -> Result<(), String> { } } +/// Waits briefly for a requested activation to land; Windows reports it a +/// moment after the call while the flyout closes. +fn wait_for_foreground(hwnd: HWND) -> bool { + unsafe { + for _ in 0..20 { + if GetForegroundWindow() == hwnd { + return true; + } + std::thread::sleep(std::time::Duration::from_millis(10)); + } + GetForegroundWindow() == hwnd + } +} + +/// Closes the Action Center, Start menu or search, which otherwise keep the +/// foreground and stop the dock from switching apps. +fn dismiss_shell_flyout(foreground: HWND) { + const SHELL: [&str; 4] = [ + r"\shellexperiencehost.exe", + r"\startmenuexperiencehost.exe", + r"\searchhost.exe", + r"\shellhost.exe", + ]; + unsafe { + let mut pid = 0; + GetWindowThreadProcessId(foreground, Some(&mut pid)); + if pid == 0 { + return; + } + let exe = process_path(pid).to_ascii_lowercase(); + if SHELL.iter().any(|name| exe.ends_with(name)) { + keybd_event(VK_ESCAPE.0 as u8, 0, KEYBD_EVENT_FLAGS(0), 0); + keybd_event(VK_ESCAPE.0 as u8, 0, KEYEVENTF_KEYUP, 0); + } + } +} + /// Asks the window to close, as its close button does. pub fn close(hwnd: isize) -> Result<(), String> { unsafe { PostMessageW(Some(HWND(hwnd as _)), WM_CLOSE, WPARAM(0), LPARAM(0)).map_err(|error| error.message()) } From f512af8f63a3d1c46b8878ee0a9b4aea96aa6aff Mon Sep 17 00:00:00 2001 From: Sumir Seth Date: Sat, 7 Nov 2026 21:41:28 +0530 Subject: [PATCH 2/2] Give up at once when no shell flyout holds the foreground The retry only helps when a flyout was dismissed; otherwise it kept the dock's UI thread waiting another 400 ms before reporting the failure. Co-Authored-By: Claude Opus 5.5 --- src/commands/open_windows.rs | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/src/commands/open_windows.rs b/src/commands/open_windows.rs index 840971f..c990808 100644 --- a/src/commands/open_windows.rs +++ b/src/commands/open_windows.rs @@ -111,7 +111,9 @@ pub fn focus(hwnd: isize) -> Result<(), String> { // thread-input attachment. Escape closes it, as the taskbar's own // click does, and then the app can come forward. let foreground = GetForegroundWindow(); - dismiss_shell_flyout(foreground); + if !dismiss_shell_flyout(foreground) { + return Err("Windows did not switch to the window.".into()); + } for _ in 0..20 { if GetForegroundWindow() != foreground { break; @@ -159,8 +161,9 @@ fn wait_for_foreground(hwnd: HWND) -> bool { } /// Closes the Action Center, Start menu or search, which otherwise keep the -/// foreground and stop the dock from switching apps. -fn dismiss_shell_flyout(foreground: HWND) { +/// foreground and stop the dock from switching apps. Returns whether one was +/// in front. +fn dismiss_shell_flyout(foreground: HWND) -> bool { const SHELL: [&str; 4] = [ r"\shellexperiencehost.exe", r"\startmenuexperiencehost.exe", @@ -171,13 +174,15 @@ fn dismiss_shell_flyout(foreground: HWND) { let mut pid = 0; GetWindowThreadProcessId(foreground, Some(&mut pid)); if pid == 0 { - return; + return false; } let exe = process_path(pid).to_ascii_lowercase(); - if SHELL.iter().any(|name| exe.ends_with(name)) { - keybd_event(VK_ESCAPE.0 as u8, 0, KEYBD_EVENT_FLAGS(0), 0); - keybd_event(VK_ESCAPE.0 as u8, 0, KEYEVENTF_KEYUP, 0); + if !SHELL.iter().any(|name| exe.ends_with(name)) { + return false; } + keybd_event(VK_ESCAPE.0 as u8, 0, KEYBD_EVENT_FLAGS(0), 0); + keybd_event(VK_ESCAPE.0 as u8, 0, KEYEVENTF_KEYUP, 0); + true } }