helios-remote/crates/client/src/windows_mgmt.rs
Helios 0b4a6de8ae
refactor: enforce device labels, unify screenshot, remove deprecated commands, session-id-less design
- Device labels: lowercase, no whitespace, only a-z 0-9 - _ (enforced at config time)
- Session IDs removed: device label is the sole identifier
- Routes changed: /sessions/:id → /devices/:label
- Removed commands: click, type, find-window, wait-for-window, label, old version, server-version
- Renamed: status → version (compares relay/remote.py/client commits)
- Unified screenshot: takes 'screen' or a window label as argument
- Windows listed with human-readable labels (same format as device labels)
- Single instance enforcement via PID lock file
- Removed input.rs (click/type functionality)
- All docs and code in English
- Protocol: Hello.label is now required (String, not Option<String>)
- Client auto-migrates invalid labels on startup
2026-03-06 01:55:28 +01:00

146 lines
4.7 KiB
Rust

use helios_common::protocol::{sanitize_label, WindowInfo};
// ── Windows implementation ──────────────────────────────────────────────────
#[cfg(windows)]
mod win_impl {
use super::*;
use windows::Win32::Foundation::{BOOL, HWND, LPARAM};
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,
};
unsafe extern "system" fn enum_callback(hwnd: HWND, lparam: LPARAM) -> BOOL {
let list = &mut *(lparam.0 as *mut Vec<HWND>);
list.push(hwnd);
BOOL(1)
}
fn get_all_hwnds() -> Vec<HWND> {
let mut list: Vec<HWND> = Vec::new();
unsafe {
let _ = EnumWindows(
Some(enum_callback),
LPARAM(&mut list as *mut Vec<HWND> as isize),
);
}
list
}
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])
}
/// Generate a human-readable label from a window title.
/// E.g. "Google Chrome" -> "google_chrome", "Discord" -> "discord"
fn window_label(title: &str) -> String {
sanitize_label(title)
}
pub fn list_windows() -> Result<Vec<WindowInfo>, String> {
let hwnds = get_all_hwnds();
let mut windows = Vec::new();
for hwnd in hwnds {
let visible = unsafe { IsWindowVisible(hwnd).as_bool() };
let title = hwnd_title(hwnd);
if !visible || title.is_empty() {
continue;
}
let label = window_label(&title);
if label.is_empty() {
continue;
}
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<Vec<WindowInfo>, 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<Vec<WindowInfo>, 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)
}