#![cfg(windows)]
use std::collections::HashMap;
use std::ffi::OsString;
use std::io;
use std::os::windows::process::CommandExt;
use std::process::{Command, ExitStatus};
use std::sync::atomic::{AtomicU32, Ordering};
use std::time::{Duration, Instant, SystemTime, UNIX_EPOCH};
use windows_sys::Win32::Foundation::{BOOL, FALSE, TRUE};
use windows_sys::Win32::System::Console::{
GenerateConsoleCtrlEvent, SetConsoleCtrlHandler, CTRL_BREAK_EVENT, CTRL_C_EVENT,
};
use super::launcher::{ChildHandle, Launcher};
const CREATE_NEW_PROCESS_GROUP: u32 = 0x0000_0200;
static CHILD_PGID: AtomicU32 = AtomicU32::new(0);
fn claude_bin() -> OsString {
std::env::var_os("CLAUDE_SMART_CLAUDE_BIN").unwrap_or_else(|| OsString::from("claude"))
}
unsafe extern "system" fn console_ctrl_handler(ctrl_type: u32) -> BOOL {
let pgid = CHILD_PGID.load(Ordering::SeqCst);
match ctrl_type {
CTRL_C_EVENT => {
if pgid != 0 {
let _ = GenerateConsoleCtrlEvent(CTRL_C_EVENT, pgid);
}
TRUE }
CTRL_BREAK_EVENT => {
if pgid != 0 {
let _ = GenerateConsoleCtrlEvent(CTRL_BREAK_EVENT, pgid);
}
TRUE
}
_ => FALSE,
}
}
#[derive(Default)]
pub struct WindowsLauncher;
impl Launcher for WindowsLauncher {
fn run_foreground(
&self,
sid: &str,
cli: &[OsString],
env: &HashMap<OsString, OsString>,
) -> io::Result<(ExitStatus, ChildHandle)> {
let born = SystemTime::now()
.duration_since(UNIX_EPOCH)
.map(|d| d.as_secs() as i64)
.unwrap_or(0);
let mut cmd = Command::new(claude_bin());
cmd.args(cli);
for (k, v) in env {
cmd.env(k, v);
}
cmd.creation_flags(CREATE_NEW_PROCESS_GROUP);
let child = cmd.spawn()?;
let pid = child.id();
CHILD_PGID.store(pid, Ordering::SeqCst);
unsafe {
SetConsoleCtrlHandler(Some(console_ctrl_handler), TRUE);
}
let _ = crate::platform::pid::write_pid_file(&crate::paths::pid_file(sid), pid, born);
let stop_flag = crate::paths::stop_flag(sid);
let grace = Duration::from_millis(
std::env::var("CLAUDE_SWITCH_GRACE_MS")
.ok()
.and_then(|s| s.parse().ok())
.unwrap_or(5_000),
);
let status = supervise(child, &stop_flag, pid, grace)?;
unsafe {
SetConsoleCtrlHandler(Some(console_ctrl_handler), FALSE);
}
CHILD_PGID.store(0, Ordering::SeqCst);
Ok((status, ChildHandle { pid, born }))
}
}
fn supervise(
mut child: std::process::Child,
stop_flag: &std::path::Path,
pgid: u32,
grace: Duration,
) -> io::Result<ExitStatus> {
const POLL: Duration = Duration::from_millis(200);
loop {
if let Some(status) = child.try_wait()? {
return Ok(status);
}
if stop_flag.exists() {
let _ = std::fs::remove_file(stop_flag);
unsafe {
let _ = GenerateConsoleCtrlEvent(CTRL_BREAK_EVENT, pgid);
}
let deadline = Instant::now() + grace;
while Instant::now() < deadline {
if let Some(status) = child.try_wait()? {
return Ok(status);
}
std::thread::sleep(POLL);
}
let _ = child.kill();
return child.wait();
}
std::thread::sleep(POLL);
}
}