Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
64 changes: 63 additions & 1 deletion src/commands/open_windows.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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},
Expand Down Expand Up @@ -99,7 +100,28 @@ 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();
if !dismiss_shell_flyout(foreground) {
return Err("Windows did not switch to the window.".into());
}
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())
Expand All @@ -124,6 +146,46 @@ 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. Returns whether one was
/// in front.
fn dismiss_shell_flyout(foreground: HWND) -> bool {
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 false;
}
let exe = process_path(pid).to_ascii_lowercase();
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
}
}

/// 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()) }
Expand Down
Loading