use helios_common::protocol::{sanitize_label, WindowInfo}; use std::collections::HashMap; // ── Windows implementation ────────────────────────────────────────────────── #[cfg(windows)] mod win_impl { use super::*; use windows::Win32::Foundation::{BOOL, HWND, LPARAM}; use windows::Win32::Graphics::Dwm::{DwmGetWindowAttribute, DWMWA_CLOAKED}; use windows::Win32::UI::WindowsAndMessaging::{ BringWindowToTop, EnumWindows, GetWindowTextW, IsWindowVisible, SetForegroundWindow, ShowWindow, SW_MAXIMIZE, SW_MINIMIZE, SW_RESTORE, }; use windows::Win32::UI::Input::KeyboardAndMouse::{ keybd_event, KEYEVENTF_KEYUP, VK_MENU, }; use windows::Win32::System::Threading::{ OpenProcess, QueryFullProcessImageNameW, PROCESS_NAME_FORMAT, PROCESS_QUERY_LIMITED_INFORMATION, }; use windows::Win32::System::ProcessStatus::GetModuleBaseNameW; unsafe extern "system" fn enum_callback(hwnd: HWND, lparam: LPARAM) -> BOOL { let list = &mut *(lparam.0 as *mut Vec); list.push(hwnd); BOOL(1) } fn get_all_hwnds() -> Vec { let mut list: Vec = Vec::new(); unsafe { let _ = EnumWindows( Some(enum_callback), LPARAM(&mut list as *mut Vec as isize), ); } list } fn is_cloaked(hwnd: HWND) -> bool { let mut cloaked: u32 = 0; unsafe { DwmGetWindowAttribute( hwnd, DWMWA_CLOAKED, &mut cloaked as *mut u32 as *mut _, std::mem::size_of::() as u32, ).is_err() == false && cloaked != 0 } } fn hwnd_title(hwnd: HWND) -> String { let mut buf = [0u16; 512]; let len = unsafe { GetWindowTextW(hwnd, &mut buf) }; String::from_utf16_lossy(&buf[..len as usize]) } /// Get the process name (exe without extension) for a window handle. /// Tries `GetModuleBaseNameW` first, then `QueryFullProcessImageNameW` /// (which works for elevated processes with `PROCESS_QUERY_LIMITED_INFORMATION`). fn hwnd_process_name(hwnd: HWND) -> Option { unsafe { let mut pid: u32 = 0; windows::Win32::UI::WindowsAndMessaging::GetWindowThreadProcessId(hwnd, Some(&mut pid)); if pid == 0 { return None; } let handle = OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, false, pid).ok()?; // Try GetModuleBaseNameW first let mut buf = [0u16; 260]; let len = GetModuleBaseNameW(handle, None, &mut buf); if len > 0 { let _ = windows::Win32::Foundation::CloseHandle(handle); let name = String::from_utf16_lossy(&buf[..len as usize]); return Some(strip_exe_ext(&name)); } // Fallback: QueryFullProcessImageNameW (works for elevated processes) let mut buf2 = [0u16; 1024]; let mut size = buf2.len() as u32; let ok = QueryFullProcessImageNameW(handle, PROCESS_NAME_FORMAT(0), windows::core::PWSTR(buf2.as_mut_ptr()), &mut size); let _ = windows::Win32::Foundation::CloseHandle(handle); if ok.is_ok() && size > 0 { let full_path = String::from_utf16_lossy(&buf2[..size as usize]); // Extract filename from full path let filename = full_path.rsplit('\\').next().unwrap_or(&full_path); return Some(strip_exe_ext(filename)); } None } } fn strip_exe_ext(name: &str) -> String { name.strip_suffix(".exe") .or(name.strip_suffix(".EXE")) .unwrap_or(name) .to_string() } pub fn list_windows() -> Result, String> { let hwnds = get_all_hwnds(); // Collect visible windows with non-empty titles let mut raw_windows: Vec<(HWND, String, String)> = Vec::new(); for hwnd in &hwnds { let visible = unsafe { IsWindowVisible(*hwnd).as_bool() }; if !visible { continue; } if is_cloaked(*hwnd) { continue; } let title = hwnd_title(*hwnd); if title.is_empty() { continue; } // "Program Manager" is always the Windows desktop shell, never a real window if title.trim().eq_ignore_ascii_case("program manager") { continue; } let process_name = hwnd_process_name(*hwnd).unwrap_or_default(); let proc_lower = process_name.to_lowercase(); // ApplicationFrameHost is a UWP container — always a duplicate of the real app window // MsEdgeWebView2 is an embedded browser component, never a standalone user window if proc_lower == "applicationframehost" || proc_lower == "msedgewebview2" { continue; } raw_windows.push((*hwnd, title, process_name)); } // Generate labels with dedup numbering let mut label_index: HashMap = HashMap::new(); let mut windows = Vec::new(); for (hwnd, title, proc_name) in raw_windows { let base = if proc_name.is_empty() { sanitize_label(&title) } else { sanitize_label(&proc_name) }; if base.is_empty() { continue; } let idx = label_index.entry(base.clone()).or_insert(0); *idx += 1; let label = if *idx == 1 { base.clone() } else { format!("{}{}", base, idx) }; windows.push(WindowInfo { id: hwnd.0 as u64, title, label, visible: true, }); } Ok(windows) } pub fn minimize_all() -> Result<(), String> { let hwnds = get_all_hwnds(); for hwnd in hwnds { let visible = unsafe { IsWindowVisible(hwnd).as_bool() }; let title = hwnd_title(hwnd); if visible && !title.is_empty() { unsafe { let _ = ShowWindow(hwnd, SW_MINIMIZE); } } } Ok(()) } unsafe fn force_foreground(hwnd: HWND) { keybd_event(VK_MENU.0 as u8, 0, Default::default(), 0); keybd_event(VK_MENU.0 as u8, 0, KEYEVENTF_KEYUP, 0); ShowWindow(hwnd, SW_RESTORE); BringWindowToTop(hwnd).ok(); SetForegroundWindow(hwnd); } pub fn focus_window(window_id: u64) -> Result<(), String> { let hwnd = HWND(window_id as isize); unsafe { force_foreground(hwnd); } Ok(()) } pub fn maximize_and_focus(window_id: u64) -> Result<(), String> { let hwnd = HWND(window_id as isize); unsafe { ShowWindow(hwnd, SW_MAXIMIZE); force_foreground(hwnd); } Ok(()) } } // ── Non-Windows stubs ─────────────────────────────────────────────────────── #[cfg(not(windows))] mod win_impl { use super::*; pub fn list_windows() -> Result, String> { Err("Window management is only supported on Windows".to_string()) } pub fn minimize_all() -> Result<(), String> { Err("Window management is only supported on Windows".to_string()) } pub fn focus_window(_window_id: u64) -> Result<(), String> { Err("Window management is only supported on Windows".to_string()) } pub fn maximize_and_focus(_window_id: u64) -> Result<(), String> { Err("Window management is only supported on Windows".to_string()) } } // ── Public API ────────────────────────────────────────────────────────────── pub fn list_windows() -> Result, String> { win_impl::list_windows() } pub fn minimize_all() -> Result<(), String> { win_impl::minimize_all() } pub fn focus_window(window_id: u64) -> Result<(), String> { win_impl::focus_window(window_id) } pub fn maximize_and_focus(window_id: u64) -> Result<(), String> { win_impl::maximize_and_focus(window_id) }