use async_trait::async_trait;
use tracing::warn;
use windows::Win32::Foundation::CloseHandle;
use windows::Win32::System::Diagnostics::ToolHelp::{
CreateToolhelp32Snapshot, Process32FirstW, Process32NextW, PROCESSENTRY32W, TH32CS_SNAPPROCESS,
};
use windows::Win32::UI::WindowsAndMessaging::SwitchToThisWindow;
use windows::Win32::{
Foundation::{FALSE, HWND, LPARAM, TRUE},
UI::WindowsAndMessaging::{
EnumWindows, GetWindowTextLengthW, GetWindowTextW, GetWindowThreadProcessId,
IsWindowVisible,
},
};
use windows_core::BOOL;
use zerolaunch_plugin_api::host::HostApiError;
use zerolaunch_plugin_api::services::window::WindowManager;
pub struct WindowsWindowManager;
impl Default for WindowsWindowManager {
fn default() -> Self {
Self::new()
}
}
impl WindowsWindowManager {
pub fn new() -> Self {
Self
}
}
#[async_trait]
impl WindowManager for WindowsWindowManager {
async fn activate_window_by_process(&self, process_name: &str) -> Result<bool, HostApiError> {
let hwnd = get_window_by_process_name(process_name);
if let Some(hwnd) = hwnd {
unsafe {
SwitchToThisWindow(hwnd, true);
}
Ok(true)
} else {
Ok(false)
}
}
async fn activate_window_by_pid(&self, pid: u32) -> Result<bool, HostApiError> {
let hwnd = get_window_by_pid(pid);
if let Some(hwnd) = hwnd {
unsafe {
SwitchToThisWindow(hwnd, true);
}
Ok(true)
} else {
Ok(false)
}
}
async fn activate_window_by_title(&self, title: &str) -> Result<bool, HostApiError> {
let hwnd = get_window_by_title(title);
if let Some(hwnd) = hwnd {
unsafe {
SwitchToThisWindow(hwnd, true);
}
Ok(true)
} else {
Ok(false)
}
}
}
fn get_window_title(hwnd: HWND) -> String {
unsafe {
let length = GetWindowTextLengthW(hwnd);
if length == 0 {
return String::new();
}
let mut buffer = vec![0u16; length as usize + 1];
let chars_copied = GetWindowTextW(hwnd, &mut buffer);
if chars_copied == 0 {
return String::new();
}
buffer.truncate(chars_copied as usize);
String::from_utf16_lossy(&buffer)
}
}
struct WindowEnumData {
process_id: u32,
hwnd: Option<HWND>,
}
unsafe extern "system" fn find_process_window(hwnd: HWND, lparam: LPARAM) -> BOOL {
let data = &mut *(lparam.0 as *mut WindowEnumData);
let mut pid: u32 = 0;
GetWindowThreadProcessId(hwnd, Some(&mut pid));
if pid == data.process_id && IsWindowVisible(hwnd).as_bool() {
data.hwnd = Some(hwnd);
return FALSE;
}
TRUE
}
fn get_window_by_process_name(process_name: &str) -> Option<HWND> {
let mut result: Option<HWND> = None;
unsafe {
let snapshot = match CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0) {
Ok(s) => s,
Err(e) => {
warn!("创建进程快照失败: {:?}", e);
return None;
}
};
let mut entry = PROCESSENTRY32W {
dwSize: std::mem::size_of::<PROCESSENTRY32W>() as u32,
..Default::default()
};
if Process32FirstW(snapshot, &mut entry).is_ok() {
loop {
let proc_name_lower = String::from_utf16_lossy(&entry.szExeFile)
.trim_end_matches('\0')
.to_lowercase();
let process_name_lower = process_name.to_lowercase();
if proc_name_lower == process_name_lower {
let process_id = entry.th32ProcessID;
let mut window_data = WindowEnumData {
process_id,
hwnd: None,
};
let _ = EnumWindows(
Some(find_process_window),
LPARAM(&mut window_data as *mut _ as isize),
);
if let Some(hwnd) = window_data.hwnd {
result = Some(hwnd);
break;
}
}
if Process32NextW(snapshot, &mut entry).is_err() {
break;
}
}
}
let _ = CloseHandle(snapshot);
}
result
}
fn get_window_by_pid(pid: u32) -> Option<HWND> {
let mut window_data = WindowEnumData {
process_id: pid,
hwnd: None,
};
unsafe {
let _ = EnumWindows(
Some(find_process_window),
LPARAM(&mut window_data as *mut _ as isize),
);
}
window_data.hwnd
}
fn get_window_by_title(title_part: &str) -> Option<HWND> {
let windows = get_all_windows();
let matching_windows: Vec<_> = windows
.iter()
.filter(|(_, title)| {
let title_part_lower = title_part.to_lowercase();
let title_lower = title.to_lowercase();
title_lower.contains(&title_part_lower)
})
.collect();
if matching_windows.is_empty() {
return None;
}
let (hwnd, _) = matching_windows[0];
Some(*hwnd)
}
struct EnumWindowsCallbackData {
windows: Vec<(HWND, String)>,
}
extern "system" fn get_all_windows_enum_callback(hwnd: HWND, lparam: LPARAM) -> BOOL {
unsafe {
let data = &mut *(lparam.0 as *mut EnumWindowsCallbackData);
if !IsWindowVisible(hwnd).as_bool() {
return TRUE;
}
let title = get_window_title(hwnd);
if !title.is_empty() {
data.windows.push((hwnd, title));
}
TRUE
}
}
fn get_all_windows() -> Vec<(HWND, String)> {
let mut data = EnumWindowsCallbackData {
windows: Vec::new(),
};
unsafe {
let _ = EnumWindows(
Some(get_all_windows_enum_callback),
LPARAM(&mut data as *mut _ as isize),
);
}
data.windows
}