use actl_core::{CtlError, ErrorCode};
use windows::Win32::System::SystemInformation::GetTickCount;
use windows::Win32::UI::Input::KeyboardAndMouse::{GetLastInputInfo, LASTINPUTINFO};
fn idle_from_ticks(now: u32, last: u32) -> u32 {
now.wrapping_sub(last)
}
pub fn idle_ms() -> u32 {
let mut info = LASTINPUTINFO {
cbSize: std::mem::size_of::<LASTINPUTINFO>() as u32,
dwTime: 0,
};
let _ = unsafe { GetLastInputInfo(&mut info) };
let now = unsafe { GetTickCount() };
idle_from_ticks(now, info.dwTime)
}
pub fn wait_idle(threshold_ms: u32, timeout_ms: u64) -> Result<u32, CtlError> {
let deadline = std::time::Instant::now() + std::time::Duration::from_millis(timeout_ms);
loop {
let idle = idle_ms();
if idle >= threshold_ms {
return Ok(idle);
}
if std::time::Instant::now() >= deadline {
return Err(CtlError::new(
ErrorCode::Timeout,
format!(
"system input activity within the last {idle}ms (idle threshold {threshold_ms}ms) \
— user is present; retry with a larger --timeout or when the console is idle"
),
));
}
std::thread::sleep(std::time::Duration::from_millis(crate::timing().poll_ms));
}
}
#[cfg(test)]
mod tests {
use super::idle_from_ticks;
#[test]
fn idle_is_zero_right_after_input() {
assert_eq!(idle_from_ticks(1000, 1000), 0);
}
#[test]
fn idle_accumulates_normally() {
assert_eq!(idle_from_ticks(10_000, 2_000), 8_000);
}
#[test]
fn tick_wraparound_stays_correct() {
let last = u32::MAX - 99; let now = 101; assert_eq!(idle_from_ticks(now, last), 201);
}
#[test]
fn never_input_reports_uptime_as_idle() {
assert_eq!(idle_from_ticks(3_600_000, 0), 3_600_000);
}
}