use std::sync::OnceLock;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Timing {
pub poll_ms: u64,
pub wake_bound_ms: u64,
pub probe_ms: u32,
pub focus_retries: u32,
pub press_focus_ms: u64,
pub type_focus_ms: u64,
pub segment_chars: usize,
pub modifier_wait_ms: u64,
pub lock_wait_ms: u32,
pub settle_ms: u64,
pub fast_poll_ms: u64,
pub ready_poll_ms: u64,
pub vis_probe_ms: u64,
pub handoff_poll_ms: u64,
}
pub const EXPECT_MS: u64 = 2000;
pub const WAIT_MS: u64 = 5000;
pub const WAIT_WINDOW_MS: u64 = 5000;
pub const DISPLAY_READY_MS: u64 = 4000;
pub const DISPLAY_ACK_MS: u64 = 3000;
impl Default for Timing {
fn default() -> Self {
Self {
poll_ms: 100,
wake_bound_ms: 1600,
probe_ms: 60,
focus_retries: 3,
press_focus_ms: 120,
type_focus_ms: 150,
segment_chars: 32,
modifier_wait_ms: 1500,
lock_wait_ms: 3000,
settle_ms: 250,
fast_poll_ms: 25,
ready_poll_ms: 50,
vis_probe_ms: 1000,
handoff_poll_ms: 100,
}
}
}
fn env_num<T: std::str::FromStr>(name: &str, default: T) -> T {
std::env::var(name)
.ok()
.and_then(|v| v.trim().parse().ok())
.unwrap_or(default)
}
impl Timing {
pub fn load() -> &'static Timing {
static T: OnceLock<Timing> = OnceLock::new();
T.get_or_init(|| {
let d = Timing::default();
Timing {
poll_ms: env_num("ACTL_POLL_MS", d.poll_ms),
wake_bound_ms: env_num("ACTL_WAKE_BOUND_MS", d.wake_bound_ms),
probe_ms: env_num("ACTL_PROBE_MS", d.probe_ms),
focus_retries: env_num("ACTL_FOCUS_RETRIES", d.focus_retries),
press_focus_ms: env_num("ACTL_PRESS_FOCUS_MS", d.press_focus_ms),
type_focus_ms: env_num("ACTL_TYPE_FOCUS_MS", d.type_focus_ms),
segment_chars: env_num("ACTL_SEGMENT_CHARS", d.segment_chars),
modifier_wait_ms: env_num("ACTL_MODIFIER_WAIT_MS", d.modifier_wait_ms),
lock_wait_ms: env_num("ACTL_LOCK_WAIT_MS", d.lock_wait_ms),
settle_ms: env_num("ACTL_SETTLE_MS", d.settle_ms),
fast_poll_ms: env_num("ACTL_FAST_POLL_MS", d.fast_poll_ms),
ready_poll_ms: env_num("ACTL_READY_POLL_MS", d.ready_poll_ms),
vis_probe_ms: env_num("ACTL_VIS_PROBE_MS", d.vis_probe_ms),
handoff_poll_ms: env_num("ACTL_HANDOFF_POLL_MS", d.handoff_poll_ms),
}
})
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn defaults_match_production_values() {
let d = Timing::default();
assert_eq!(d.poll_ms, 100); assert_eq!(d.wake_bound_ms, 1600); assert_eq!(d.segment_chars, 32);
assert_eq!(d.lock_wait_ms, 3000);
assert_eq!(d.modifier_wait_ms, 1500);
assert_eq!(d.focus_retries, 3);
assert_eq!(d.settle_ms, 250); assert_eq!(d.fast_poll_ms, 25);
assert_eq!(d.ready_poll_ms, 50);
assert_eq!(d.vis_probe_ms, 1000);
assert_eq!(d.handoff_poll_ms, 100);
}
#[test]
fn protocol_defaults_are_pinned() {
assert_eq!(EXPECT_MS, 2000);
assert_eq!(WAIT_MS, 5000);
assert_eq!(WAIT_WINDOW_MS, 5000);
}
#[test]
fn env_num_parses_and_falls_back() {
assert_eq!(env_num::<u64>("_ACTL_TEST_ABSENT_", 42), 42);
assert_eq!(env_num::<u64>("PATH", 42), 42); }
}