1use std::sync::OnceLock;
7
8#[derive(Debug, Clone, PartialEq, Eq)]
9pub struct Timing {
10 pub poll_ms: u64,
12 pub wake_bound_ms: u64,
14 pub probe_ms: u32,
16 pub focus_retries: u32,
18 pub press_focus_ms: u64,
20 pub type_focus_ms: u64,
22 pub segment_chars: usize,
24 pub modifier_wait_ms: u64,
26 pub lock_wait_ms: u32,
28 pub settle_ms: u64,
31 pub fast_poll_ms: u64,
33 pub ready_poll_ms: u64,
35 pub vis_probe_ms: u64,
37 pub handoff_poll_ms: u64,
39}
40
41pub const EXPECT_MS: u64 = 2000;
46pub const WAIT_MS: u64 = 5000;
47pub const WAIT_WINDOW_MS: u64 = 5000;
48pub const DISPLAY_READY_MS: u64 = 4000;
50pub const DISPLAY_ACK_MS: u64 = 3000;
51
52impl Default for Timing {
53 fn default() -> Self {
54 Self {
55 poll_ms: 100,
56 wake_bound_ms: 1600,
57 probe_ms: 60,
58 focus_retries: 3,
59 press_focus_ms: 120,
60 type_focus_ms: 150,
61 segment_chars: 32,
62 modifier_wait_ms: 1500,
63 lock_wait_ms: 3000,
64 settle_ms: 250,
65 fast_poll_ms: 25,
66 ready_poll_ms: 50,
67 vis_probe_ms: 1000,
68 handoff_poll_ms: 100,
69 }
70 }
71}
72
73fn env_num<T: std::str::FromStr>(name: &str, default: T) -> T {
74 std::env::var(name)
75 .ok()
76 .and_then(|v| v.trim().parse().ok())
77 .unwrap_or(default)
78}
79
80impl Timing {
81 pub fn load() -> &'static Timing {
83 static T: OnceLock<Timing> = OnceLock::new();
84 T.get_or_init(|| {
85 let d = Timing::default();
86 Timing {
87 poll_ms: env_num("ACTL_POLL_MS", d.poll_ms),
88 wake_bound_ms: env_num("ACTL_WAKE_BOUND_MS", d.wake_bound_ms),
89 probe_ms: env_num("ACTL_PROBE_MS", d.probe_ms),
90 focus_retries: env_num("ACTL_FOCUS_RETRIES", d.focus_retries),
91 press_focus_ms: env_num("ACTL_PRESS_FOCUS_MS", d.press_focus_ms),
92 type_focus_ms: env_num("ACTL_TYPE_FOCUS_MS", d.type_focus_ms),
93 segment_chars: env_num("ACTL_SEGMENT_CHARS", d.segment_chars),
94 modifier_wait_ms: env_num("ACTL_MODIFIER_WAIT_MS", d.modifier_wait_ms),
95 lock_wait_ms: env_num("ACTL_LOCK_WAIT_MS", d.lock_wait_ms),
96 settle_ms: env_num("ACTL_SETTLE_MS", d.settle_ms),
97 fast_poll_ms: env_num("ACTL_FAST_POLL_MS", d.fast_poll_ms),
98 ready_poll_ms: env_num("ACTL_READY_POLL_MS", d.ready_poll_ms),
99 vis_probe_ms: env_num("ACTL_VIS_PROBE_MS", d.vis_probe_ms),
100 handoff_poll_ms: env_num("ACTL_HANDOFF_POLL_MS", d.handoff_poll_ms),
101 }
102 })
103 }
104}
105
106#[cfg(test)]
107mod tests {
108 use super::*;
109
110 #[test]
111 fn defaults_match_production_values() {
112 let d = Timing::default();
113 assert_eq!(d.poll_ms, 100); assert_eq!(d.wake_bound_ms, 1600); assert_eq!(d.segment_chars, 32);
116 assert_eq!(d.lock_wait_ms, 3000);
117 assert_eq!(d.modifier_wait_ms, 1500);
118 assert_eq!(d.focus_retries, 3);
119 assert_eq!(d.settle_ms, 250); assert_eq!(d.fast_poll_ms, 25);
121 assert_eq!(d.ready_poll_ms, 50);
122 assert_eq!(d.vis_probe_ms, 1000);
123 assert_eq!(d.handoff_poll_ms, 100);
124 }
125
126 #[test]
127 fn protocol_defaults_are_pinned() {
128 assert_eq!(EXPECT_MS, 2000);
130 assert_eq!(WAIT_MS, 5000);
131 assert_eq!(WAIT_WINDOW_MS, 5000);
132 }
133
134 #[test]
135 fn env_num_parses_and_falls_back() {
136 assert_eq!(env_num::<u64>("_ACTL_TEST_ABSENT_", 42), 42);
137 assert_eq!(env_num::<u64>("PATH", 42), 42); }
140}