use actl_core::{CtlError, handoff};
use windows::Win32::UI::WindowsAndMessaging::{
IsIconic, SW_RESTORE, SetForegroundWindow, ShowWindow,
};
use crate::window::WindowEntry;
fn anchor_matches(pattern: &str, w: &WindowEntry) -> bool {
if let Some(raw) = pattern.strip_prefix("hwnd:") {
return w
.hwnd
.is_some_and(|h| raw.parse::<isize>().is_ok_and(|n| n == h));
}
w.visible && w.title.contains(pattern)
}
fn settled(windows: &[WindowEntry], pattern: &str) -> bool {
windows
.iter()
.any(|w| w.foreground && anchor_matches(pattern, w))
}
fn foreground_title(windows: &[WindowEntry]) -> String {
windows
.iter()
.find(|w| w.foreground)
.map(|w| w.title.clone())
.unwrap_or_default()
}
pub fn ensure_input_foreground(app: Option<&str>) -> Result<(), CtlError> {
let Some(pattern) = app else {
return Ok(());
};
let windows = crate::native_windows::native_windows()?;
let matched: Vec<&WindowEntry> = windows
.iter()
.filter(|w| anchor_matches(pattern, w))
.collect();
if matched.is_empty() {
return Ok(()); }
if matched.iter().any(|w| w.foreground) {
return Ok(());
}
let target = matched[0];
let Some(handle) = target.hwnd else {
return Ok(()); };
unsafe {
let hwnd = windows::Win32::Foundation::HWND(handle as *mut _);
if IsIconic(hwnd).as_bool() {
let _ = ShowWindow(hwnd, SW_RESTORE);
}
let _ = SetForegroundWindow(hwnd);
}
if settle_foreground(pattern) {
return Ok(());
}
if let Ok(auto) = uiautomation::UIAutomation::new() {
let hwnd = windows::Win32::Foundation::HWND(handle as *mut _);
if let Ok(element) = auto.element_from_handle(uiautomation::types::Handle::from(hwnd)) {
let _ = element.set_focus();
}
}
if settle_foreground(pattern) {
return Ok(());
}
let now = crate::native_windows::native_windows()?;
Err(handoff::foreground_error(
&target.title,
&foreground_title(&now),
))
}
fn settle_foreground(pattern: &str) -> bool {
for _ in 0..12 {
std::thread::sleep(std::time::Duration::from_millis(100));
if let Ok(windows) = crate::native_windows::native_windows()
&& settled(&windows, pattern)
{
return true;
}
}
false
}
#[cfg(test)]
mod tests {
use super::*;
fn entry(title: &str, visible: bool, foreground: bool, hwnd: Option<isize>) -> WindowEntry {
WindowEntry {
title: title.into(),
class: "TestClass".into(),
pid: 1,
foreground,
topmost: false,
minimized: false,
visible,
z_order: None,
rect: None,
hwnd,
owner_title: None,
}
}
#[test]
fn anchor_title_substring_requires_visible() {
let w = entry("文档1 - Word", true, false, Some(42));
assert!(anchor_matches("Word", &w));
let hidden = entry("文档1 - Word", false, false, Some(42));
assert!(!anchor_matches("Word", &hidden));
assert!(!anchor_matches("Excel", &w));
}
#[test]
fn anchor_hwnd_exact_match() {
let w = entry("任意标题", true, false, Some(4242));
assert!(anchor_matches("hwnd:4242", &w));
assert!(!anchor_matches("hwnd:4243", &w));
assert!(!anchor_matches("hwnd:bad", &w));
}
#[test]
fn settled_requires_foreground_anchor() {
let bg = || entry("a - Word", true, false, Some(1));
let fg = || entry("b - Word", true, true, Some(2));
assert!(!settled(&[bg()], "Word"));
assert!(settled(&[bg(), fg()], "Word"));
}
#[test]
fn foreground_title_reports_front_window() {
let bg = || entry("a", true, false, Some(1));
let fg = || entry("ChatGPT", true, true, Some(2));
assert_eq!(foreground_title(&[bg(), fg()]), "ChatGPT");
assert_eq!(foreground_title(&[bg()]), "");
}
}