intercept_bounce/filter.rs
1// This module defines the core BounceFilter state and logic.
2// It has been refactored to be stateless regarding historical statistics,
3// focusing only on the information needed for the immediate bounce decision.
4
5pub mod keynames;
6pub mod stats;
7
8use crate::event::{self, is_key_event};
9use crate::logger::EventInfo;
10use input_linux_sys::{input_event, KEY_MAX};
11use std::time::Duration;
12
13// Constants for filter state size
14/// Number of key codes to track (0 to KEY_MAX inclusive).
15pub const FILTER_MAP_SIZE: usize = KEY_MAX as usize + 1;
16/// Number of key states (0=release, 1=press, 2=repeat).
17pub const NUM_KEY_STATES: usize = 3;
18
19/// Holds the minimal state required for bounce filtering decisions.
20///
21/// This struct only stores the timestamp (in microseconds) of the last *passed* event
22/// for each key code and value combination.
23pub struct BounceFilter {
24 // Stores the timestamp (in microseconds) of the last event that *passed* the filter
25 // for a given key code (index 0..KEY_MAX) and key value (index 0=release, 1=press, 2=repeat).
26 // Initialized with u64::MAX to indicate no event has passed yet.
27 last_event_us: [[u64; NUM_KEY_STATES]; FILTER_MAP_SIZE],
28 // Ring buffer to store the last N *passed* events for debugging purposes.
29 // Only allocated if ring_buffer_size > 0.
30 recent_passed_events: Vec<Option<input_event>>,
31 recent_event_idx: usize,
32 ring_buffer_size: usize,
33 // Timestamp of the very first event processed, used for calculating total runtime.
34 overall_first_event_us: Option<u64>,
35 // Timestamp of the very last event processed, used for calculating total runtime.
36 overall_last_event_us: Option<u64>,
37}
38
39impl Default for BounceFilter {
40 fn default() -> Self {
41 // Default to a filter with no ring buffer
42 Self::new(0)
43 }
44}
45
46impl BounceFilter {
47 /// Creates a new `BounceFilter` with a specified ring buffer size.
48 ///
49 /// The ring buffer stores the last `ring_buffer_size` passed events for debugging.
50 /// If `ring_buffer_size` is 0, the buffer is not allocated and has no overhead.
51 #[must_use]
52 pub fn new(ring_buffer_size: usize) -> Self {
53 let recent_passed_events = if ring_buffer_size > 0 {
54 vec![None; ring_buffer_size]
55 } else {
56 Vec::new() // Don't allocate if size is 0
57 };
58
59 BounceFilter {
60 last_event_us: [[u64::MAX; NUM_KEY_STATES]; FILTER_MAP_SIZE],
61 recent_passed_events,
62 recent_event_idx: 0,
63 ring_buffer_size,
64 overall_first_event_us: None,
65 overall_last_event_us: None,
66 }
67 }
68
69 /// Checks an incoming event against the debounce filter state.
70 ///
71 /// Determines if the event is a bounce based on the `debounce_time_us`
72 /// and the timestamp of the last passed event of the same type.
73 /// Updates the internal state (`last_event_us`) *only* if the event passes.
74 /// Also tracks the overall first and last event timestamps.
75 ///
76 /// # Arguments
77 /// * `event`: The input event to check.
78 /// * `debounce_time`: The debounce threshold as a `Duration`.
79 ///
80 /// # Returns
81 /// An `EventInfo` struct containing the result of the check and relevant timestamps.
82 pub fn check_event(
83 &mut self,
84 event: &input_event,
85 debounce_time: Duration,
86 skip_debounce: bool,
87 ) -> EventInfo {
88 let event_us = event::event_microseconds(event);
89
90 // Update overall timestamps
91 if self.overall_first_event_us.is_none() {
92 self.overall_first_event_us = Some(event_us);
93 }
94 self.overall_last_event_us = Some(event_us);
95
96 if skip_debounce {
97 if self.ring_buffer_size > 0 {
98 self.recent_passed_events[self.recent_event_idx] = Some(*event);
99 self.recent_event_idx = (self.recent_event_idx + 1) % self.ring_buffer_size;
100 }
101 return EventInfo {
102 event: *event,
103 event_us,
104 is_bounce: false,
105 diff_us: None,
106 last_passed_us: None,
107 };
108 }
109
110 // --- Early returns for non-debounced events ---
111 // Pass non-key events or key repeats immediately
112 if !is_key_event(event) || event.value == 2 {
113 // Record passed event in ring buffer if enabled
114 if self.ring_buffer_size > 0 {
115 self.recent_passed_events[self.recent_event_idx] = Some(*event);
116 self.recent_event_idx = (self.recent_event_idx + 1) % self.ring_buffer_size;
117 }
118 return EventInfo {
119 event: *event,
120 event_us,
121 is_bounce: false,
122 diff_us: None,
123 last_passed_us: None, // No relevant last_passed_us for non-debounced events
124 };
125 }
126
127 // Check bounds for key code/value indices
128 let key_code_idx = event.code as usize;
129 let key_value_idx = event.value as usize;
130 if !(key_code_idx < FILTER_MAP_SIZE && key_value_idx < NUM_KEY_STATES) {
131 // Out of bounds - treat as passed, no relevant history
132 // Record passed event in ring buffer if enabled
133 if self.ring_buffer_size > 0 {
134 self.recent_passed_events[self.recent_event_idx] = Some(*event);
135 self.recent_event_idx = (self.recent_event_idx + 1) % self.ring_buffer_size;
136 }
137 return EventInfo {
138 event: *event,
139 event_us,
140 is_bounce: false,
141 diff_us: None,
142 last_passed_us: None,
143 };
144 }
145
146 // --- Debounce logic ---
147 let last_passed_us = self.last_event_us[key_code_idx][key_value_idx];
148
149 // If no previous event passed for this key/value, it cannot be a bounce. Record and pass.
150 if last_passed_us == u64::MAX {
151 self.last_event_us[key_code_idx][key_value_idx] = event_us;
152 // Record passed event in ring buffer if enabled
153 if self.ring_buffer_size > 0 {
154 self.recent_passed_events[self.recent_event_idx] = Some(*event);
155 self.recent_event_idx = (self.recent_event_idx + 1) % self.ring_buffer_size;
156 }
157 return EventInfo {
158 event: *event, // Copy event
159 event_us,
160 is_bounce: false,
161 diff_us: None,
162 last_passed_us: None, // No previous passed event for this key/value
163 };
164 }
165
166 // Calculate time difference if possible (handles time going backwards)
167 let diff_us_opt = event_us.checked_sub(last_passed_us);
168
169 if let Some(diff_us) = diff_us_opt {
170 // Check if the difference is within the debounce window.
171 if debounce_time > Duration::ZERO && Duration::from_micros(diff_us) < debounce_time {
172 // It's a bounce! Return bounce info. Do NOT update last_event_us or ring buffer.
173 return EventInfo {
174 event: *event,
175 event_us,
176 is_bounce: true,
177 diff_us: Some(diff_us),
178 last_passed_us: Some(last_passed_us),
179 };
180 }
181 }
182 // If time went backwards (checked_sub returned None), or diff_us >= debounce_time, it's not a bounce.
183
184 // --- Event Passed ---
185 // If we reach here, the event is NOT a bounce. Record as passed.
186 self.last_event_us[key_code_idx][key_value_idx] = event_us;
187 // Record passed event in ring buffer if enabled
188 if self.ring_buffer_size > 0 {
189 self.recent_passed_events[self.recent_event_idx] = Some(*event);
190 self.recent_event_idx = (self.recent_event_idx + 1) % self.ring_buffer_size;
191 }
192
193 // Return non-bounce info, providing the timestamp of the previously passed event.
194 EventInfo {
195 event: *event,
196 event_us,
197 is_bounce: false,
198 diff_us: None, // Not a bounce, so no bounce diff_us
199 last_passed_us: Some(last_passed_us),
200 }
201 }
202
203 /// Returns the total duration based on the first and last event timestamps seen.
204 /// Returns `None` if no events were processed.
205 pub fn get_runtime_us(&self) -> Option<u64> {
206 self.overall_last_event_us.and_then(|last| {
207 self.overall_first_event_us
208 .map(|first| last.saturating_sub(first))
209 })
210 }
211}