Skip to main content

smart_keymap_core/
keymap.rs

1#[cfg(feature = "std")]
2mod distinct_reports;
3mod event_scheduler;
4/// The HID keyboard reporter.
5pub mod hid_keyboard_reporter;
6mod input_event_queue;
7#[cfg(feature = "std")]
8mod observed_eb_keymap;
9#[cfg(feature = "std")]
10mod observed_keymap;
11mod pending;
12
13use core::cmp::PartialEq;
14use core::fmt::Debug;
15use core::marker::Copy;
16use core::ops::Index;
17
18use serde::Deserialize;
19
20use crate::input;
21use crate::key;
22
23use key::Event;
24
25#[cfg(feature = "std")]
26pub use distinct_reports::DistinctReports;
27use event_scheduler::EventScheduler;
28use hid_keyboard_reporter::HIDKeyboardReporter;
29use input_event_queue::InputEventQueue;
30#[cfg(feature = "std")]
31pub use observed_eb_keymap::ObservedKeymap as ObservedEventBasedKeymap;
32#[cfg(feature = "std")]
33pub use observed_keymap::ObservedKeymap;
34
35/// Maximum number of pressed keys supported.
36pub const MAX_PRESSED_KEYS: usize = 16;
37
38pub(crate) const MAX_QUEUED_INPUT_EVENTS: usize = 32;
39
40/// Constructs an HID report or a sequence of key codes from the given sequence of [key::KeyOutput].
41#[derive(Debug, Default, PartialEq)]
42pub struct KeymapOutput {
43    pressed_key_codes: heapless::Vec<key::KeyOutput, { MAX_PRESSED_KEYS }>,
44}
45
46impl KeymapOutput {
47    /// Constructs a new keymap output.
48    pub fn new(pressed_key_codes: heapless::Vec<key::KeyOutput, { MAX_PRESSED_KEYS }>) -> Self {
49        Self { pressed_key_codes }
50    }
51
52    /// Returns the pressed key codes.
53    pub fn pressed_key_codes(&self) -> heapless::Vec<u8, 24> {
54        let mut result = heapless::Vec::new();
55
56        let modifiers = self
57            .pressed_key_codes
58            .iter()
59            .fold(key::KeyboardModifiers::new(), |acc, &ko| {
60                acc.union(&ko.key_modifiers())
61            });
62
63        result.extend(modifiers.as_key_codes());
64
65        result.extend(
66            self.pressed_key_codes
67                .iter()
68                .flat_map(|ko| match ko.key_code() {
69                    key::KeyUsage::Keyboard(kc) => Some(kc),
70                    _ => None,
71                }),
72        );
73
74        result
75    }
76
77    /// Returns the current HID keyboard report.
78    pub fn as_hid_boot_keyboard_report(&self) -> [u8; 8] {
79        let mut report = [0u8; 8];
80
81        let modifiers = self
82            .pressed_key_codes
83            .iter()
84            .fold(key::KeyboardModifiers::new(), |acc, &ko| {
85                acc.union(&ko.key_modifiers())
86            });
87
88        report[0] = modifiers.as_byte();
89
90        let key_codes = self
91            .pressed_key_codes
92            .iter()
93            .flat_map(|ko| match ko.key_code() {
94                key::KeyUsage::Keyboard(kc) => Some(kc),
95                _ => None,
96            })
97            .filter(|&kc| kc != 0);
98
99        for (i, key_code) in key_codes.take(6).enumerate() {
100            report[i + 2] = key_code;
101        }
102
103        report
104    }
105
106    /// Returns the pressed consumer codes.
107    pub fn pressed_consumer_codes(&self) -> heapless::Vec<u8, 24> {
108        self.pressed_key_codes
109            .iter()
110            .flat_map(|ko| match ko.key_code() {
111                key::KeyUsage::Consumer(uc) => Some(uc),
112                _ => None,
113            })
114            .collect()
115    }
116
117    /// Returns the pressed custom codes.
118    pub fn pressed_custom_codes(&self) -> heapless::Vec<u8, 24> {
119        self.pressed_key_codes
120            .iter()
121            .flat_map(|ko| match ko.key_code() {
122                key::KeyUsage::Custom(kc) => Some(kc),
123                _ => None,
124            })
125            .collect()
126    }
127
128    /// Returns the combined pressed mouse output.
129    pub fn pressed_mouse_output(&self) -> key::MouseOutput {
130        self.pressed_key_codes
131            .iter()
132            .filter_map(|ko| match ko.key_code() {
133                key::KeyUsage::Mouse(mo) => Some(mo),
134                _ => None,
135            })
136            .fold(key::MouseOutput::NO_OUTPUT, |acc, mo| acc.combine(&mo))
137    }
138}
139
140/// Commands for managing Bluetooth profiles. (BLE pairing and bonding).
141#[derive(Deserialize, Debug, Clone, Copy, Eq, PartialEq)]
142pub enum BluetoothProfileCommand {
143    /// Disconnect the current profile.
144    Disconnect,
145    /// Clear the current profile. (Start pairing mode).
146    Clear,
147    /// Clear all profiles. (Start pairing mode).
148    ClearAll,
149    /// Switch to the previous profile.
150    Previous,
151    /// Switch to the next profile.
152    Next,
153    /// Switch to the given profile index.
154    Select(u8),
155}
156
157/// Callbacks for effect keys in the keymap.
158#[derive(Deserialize, Debug, Clone, Copy, Eq, PartialEq)]
159pub enum KeymapCallback {
160    /// Reset the keyboard
161    Reset,
162    /// Reset the keyboard to bootloader
163    ResetToBootloader,
164    /// Reset the keyboard to bootloader
165    Bluetooth(BluetoothProfileCommand),
166    /// A custom callback. Its behaviour is specific to the firmware implementation.
167    Custom(u8, u8),
168}
169
170/// Max recent physical presses tracked in [KeymapContext] (for quick-tap, etc.).
171pub const MAX_RECENT_PRESSES: usize = 8;
172
173/// Context provided from the keymap to the smart keys.
174#[derive(Debug, Clone, Copy, Default)]
175pub struct KeymapContext {
176    /// Number of milliseconds since keymap has been initialized.
177    pub time_ms: u32,
178
179    /// Number of milliseconds since keymap received an input event.
180    pub idle_time_ms: u32,
181
182    /// Aggregate keyboard modifiers from already-pressed inputs (physical + virtual).
183    ///
184    /// Updated by the keymap before press handling and on tick
185    ///  so families can branch without maintaining their own mod-tracking.
186    pub pressed_modifiers: key::KeyboardModifiers,
187
188    /// Recent physical presses as `(keymap_index, time_ms)`, oldest first.
189    ///
190    /// Used by features such as tap-hold `quick_tap_ms` (ZMK-style re-tap).
191    pub recent_presses: [(u16, u32); MAX_RECENT_PRESSES],
192
193    /// Number of valid entries in [Self::recent_presses].
194    pub recent_press_count: u8,
195}
196
197impl KeymapContext {
198    /// Constructs a new default keymap context.
199    pub const fn new() -> Self {
200        KeymapContext {
201            time_ms: 0,
202            idle_time_ms: 0,
203            pressed_modifiers: key::KeyboardModifiers::NONE,
204            recent_presses: [(0, 0); MAX_RECENT_PRESSES],
205            recent_press_count: 0,
206        }
207    }
208
209    /// Most recent press time for `keymap_index`, if still in the ring.
210    pub fn last_press_time_ms(&self, keymap_index: u16) -> Option<u32> {
211        self.recent_presses[..self.recent_press_count as usize]
212            .iter()
213            .rev()
214            .find(|(ki, _)| *ki == keymap_index)
215            .map(|(_, t)| *t)
216    }
217}
218
219/// Context for `new_pressed_key` when replacing a pending key at `keymap_index`.
220///
221/// Drops the newest ring entry for `keymap_index` and uses that entry's time
222///  as `time_ms`.
223/// The omitted entry is this still-held press, already recorded
224///  when the pending key was created.
225fn keymap_context_without_current_press(
226    recent_presses: [(u16, u32); MAX_RECENT_PRESSES],
227    recent_press_count: u8,
228    idle_time_ms: u32,
229    fallback_time_ms: u32,
230    pressed_modifiers: key::KeyboardModifiers,
231    keymap_index: u16,
232) -> KeymapContext {
233    let count = recent_press_count as usize;
234    let occupied = &recent_presses[..count];
235
236    let time_ms = occupied
237        .iter()
238        .rfind(|(ki, _)| *ki == keymap_index)
239        .map(|&(_, t)| t)
240        .unwrap_or(fallback_time_ms);
241
242    let (recent_presses, recent_press_count) =
243        if let Some(idx) = occupied.iter().rposition(|(ki, _)| *ki == keymap_index) {
244            let mut shifted = [(0, 0); MAX_RECENT_PRESSES];
245            shifted[..idx].copy_from_slice(&recent_presses[..idx]);
246            shifted[idx..count - 1].copy_from_slice(&recent_presses[idx + 1..count]);
247            (shifted, recent_press_count - 1)
248        } else {
249            (recent_presses, recent_press_count)
250        };
251
252    KeymapContext {
253        time_ms,
254        idle_time_ms,
255        pressed_modifiers,
256        recent_presses,
257        recent_press_count,
258    }
259}
260
261/// Append a physical press to the recent-press ring.
262///
263/// Same-index entries are appended, not replaced.
264/// The ring evicts the oldest entry when full.
265fn push_recent_press(
266    recent_presses: [(u16, u32); MAX_RECENT_PRESSES],
267    recent_press_count: u8,
268    keymap_index: u16,
269    time_ms: u32,
270) -> ([(u16, u32); MAX_RECENT_PRESSES], u8) {
271    let count = recent_press_count as usize;
272    if count == MAX_RECENT_PRESSES {
273        let mut shifted = [(0, 0); MAX_RECENT_PRESSES];
274        shifted[..MAX_RECENT_PRESSES - 1].copy_from_slice(&recent_presses[1..]);
275        shifted[MAX_RECENT_PRESSES - 1] = (keymap_index, time_ms);
276        (shifted, recent_press_count)
277    } else {
278        let mut recent_presses = recent_presses;
279        recent_presses[count] = (keymap_index, time_ms);
280        (recent_presses, recent_press_count + 1)
281    }
282}
283
284/// Trait for setting the keymap context.
285pub trait SetKeymapContext {
286    /// Sets the keymap context.
287    fn set_keymap_context(&mut self, context: KeymapContext);
288}
289
290/// Report-level policy hints from aggregate context (feature-agnostic).
291///
292/// Families that need to hide held modifiers from the host
293///  (e.g. mod-conditioned morphs) expose a suppress mask;
294///  the keymap applies it when folding [`Keymap::pressed_keys`].
295pub trait ReportHints {
296    /// Modifier bits to strip from the HID report.
297    fn suppressed_modifiers(&self) -> key::KeyboardModifiers {
298        key::KeyboardModifiers::NONE
299    }
300}
301
302/// Events related to the keymap.
303#[derive(Debug, Clone, Copy, PartialEq, Eq)]
304pub enum KeymapEvent {
305    /// Callback event (emitted by callback key).
306    Callback(KeymapCallback),
307    /// A pressed key resolved to a state with this key output.
308    ResolvedKeyOutput {
309        /// The keymap index of the key which resolved to the output.
310        keymap_index: u16,
311        /// The resolved key output.
312        key_output: key::KeyOutput,
313    },
314}
315
316#[derive(Debug)]
317enum CallbackFunction {
318    /// C callback
319    ExternC(extern "C" fn() -> ()),
320    /// Rust callback
321    Rust(fn() -> ()),
322}
323
324/// State for a keymap that handles input, and outputs HID keyboard reports.
325pub struct Keymap<I: Index<usize, Output = R>, R, Ctx, Ev: Debug, PKS, KS, S> {
326    key_refs: I,
327    key_system: S,
328    context: Ctx,
329    pressed_inputs: heapless::Vec<input::PressedInput<R, KS>, { MAX_PRESSED_KEYS }>,
330    event_scheduler: EventScheduler<Ev>,
331    ms_per_tick: u8,
332    idle_time: u32,
333    /// Ring of recent physical presses for [KeymapContext::recent_presses].
334    recent_presses: [(u16, u32); MAX_RECENT_PRESSES],
335    recent_press_count: u8,
336    hid_reporter: HIDKeyboardReporter,
337    pending_state: Option<pending::PendingState<R, Ev, PKS>>,
338    input_queue: InputEventQueue<{ MAX_QUEUED_INPUT_EVENTS }>,
339    callbacks: heapless::LinearMap<KeymapCallback, CallbackFunction, 2>,
340}
341
342impl<
343        I: Debug + Index<usize, Output = R>,
344        R: Debug,
345        Ctx: Debug,
346        Ev: Debug,
347        PKS: Debug,
348        KS: Debug,
349        S: Debug,
350    > core::fmt::Debug for Keymap<I, R, Ctx, Ev, PKS, KS, S>
351{
352    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
353        f.debug_struct("Keymap")
354            .field("context", &self.context)
355            .field("event_scheduler", &self.event_scheduler)
356            .field("ms_per_tick", &self.ms_per_tick)
357            .field("idle_time", &self.idle_time)
358            .field("hid_reporter", &self.hid_reporter)
359            .field("input_queue", &self.input_queue)
360            .field("pending_state", &self.pending_state)
361            .field("pressed_inputs", &self.pressed_inputs)
362            .finish_non_exhaustive()
363    }
364}
365
366impl<
367        I: Debug + Index<usize, Output = R>,
368        R: Copy + Debug,
369        Ctx: Debug + key::Context<Event = Ev> + SetKeymapContext + ReportHints,
370        Ev: Copy + Debug,
371        PKS: Debug,
372        KS: Copy + Debug + From<key::NoOpKeyState>,
373        S: key::System<R, Ref = R, Context = Ctx, Event = Ev, PendingKeyState = PKS, KeyState = KS>,
374    > Keymap<I, R, Ctx, Ev, PKS, KS, S>
375{
376    /// Constructs a new keymap with the given key definitions and context.
377    pub const fn new(key_refs: I, context: Ctx, key_system: S) -> Self {
378        Self {
379            key_refs,
380            key_system,
381            context,
382            pressed_inputs: heapless::Vec::new(),
383            event_scheduler: EventScheduler::new(),
384            ms_per_tick: 1,
385            idle_time: 0,
386            recent_presses: [(0, 0); MAX_RECENT_PRESSES],
387            recent_press_count: 0,
388            hid_reporter: HIDKeyboardReporter::new(),
389            pending_state: None,
390            input_queue: InputEventQueue::new(),
391            callbacks: heapless::LinearMap::new(),
392        }
393    }
394
395    /// Initializes or resets the keyboard to an initial state.
396    ///
397    /// Resets [key::Context] from each family's config
398    ///  (clearing active layers and other runtime state while keeping that keymap's config),
399    /// and clears pressed keys, pending work, and HID report state.
400    pub fn init(&mut self) {
401        self.context.reset();
402        self.pressed_inputs.clear();
403        self.event_scheduler.init();
404        self.hid_reporter.init();
405        self.pending_state = None;
406        self.input_queue.clear();
407        self.ms_per_tick = 1;
408        self.idle_time = 0;
409        self.recent_presses = [(0, 0); MAX_RECENT_PRESSES];
410        self.recent_press_count = 0;
411    }
412
413    /// Record a physical press in the recent-press ring.
414    ///
415    /// Same-index entries are appended, not replaced.
416    /// The ring evicts the oldest entry when full.
417    fn record_recent_press(&mut self, keymap_index: u16) {
418        (self.recent_presses, self.recent_press_count) = push_recent_press(
419            self.recent_presses,
420            self.recent_press_count,
421            keymap_index,
422            self.event_scheduler.schedule_counter,
423        );
424    }
425
426    /// Clears all registered callbacks.
427    pub fn clear_callbacks(&mut self) {
428        self.callbacks.clear();
429    }
430
431    /// Registers the given callback to the keymap.
432    ///
433    /// Only one callback is set for each callback id.
434    pub fn set_callback(&mut self, callback_id: KeymapCallback, callback_fn: fn() -> ()) {
435        let _ = self
436            .callbacks
437            .insert(callback_id, CallbackFunction::Rust(callback_fn));
438    }
439
440    /// Registers the given callback to the keymap.
441    ///
442    /// Only one callback is set for each callback id.
443    pub fn set_callback_extern(
444        &mut self,
445        callback_id: KeymapCallback,
446        callback_fn: extern "C" fn() -> (),
447    ) {
448        let _ = self
449            .callbacks
450            .insert(callback_id, CallbackFunction::ExternC(callback_fn));
451    }
452
453    /// Sets the number of ms per tick().
454    pub fn set_ms_per_tick(&mut self, ms_per_tick: u8) {
455        self.ms_per_tick = ms_per_tick;
456    }
457
458    // If the pending key state is resolved,
459    //  then clear the pending key state.
460    //
461    // Replay uses only `queued_events` (the session log).
462    // Inputs still waiting in the pending `ingest_queue` (the delay line)
463    //  are intentionally omitted from replay -
464    //  they were not yet paced/applied during pending -
465    //  and are transferred to the global `input_queue` tail
466    //  to run post-resolve in normal order.
467    fn resolve_pending_key_state(&mut self, key_state: KS) {
468        if let Some(pending::PendingState {
469            keymap_index,
470            key_ref,
471            mut queued_events,
472            mut ingest_queue,
473            ..
474        }) = self.pending_state.take()
475        {
476            // Cancel events which were scheduled for the (pending) key.
477            self.event_scheduler
478                .cancel_events_for_keymap_index(keymap_index);
479
480            // Add the pending state's pressed key to pressed inputs
481            let _ = self.pressed_inputs.push(input::PressedInput::pressed_key(
482                keymap_index,
483                key_ref,
484                key_state,
485            ));
486
487            // Session-log replay is prepended onto the global queue so it
488            //  runs before any never-logged delay-line inputs transferred next.
489            pending::dispatch_replayed_events(
490                pending::KeyResolution::Resolved { keymap_index },
491                &mut queued_events,
492                &mut self.input_queue,
493                &mut self.event_scheduler,
494            );
495
496            // Transfer remaining pending delay-line traffic to the global tail.
497            let mut remaining = ingest_queue.take_all();
498            self.input_queue.append_all(&mut remaining);
499
500            self.handle_pending_events();
501
502            // The resolved key state has output. Emit this as an event.
503            if let Some(key_output) = self.key_system.key_output(&key_ref, &key_state) {
504                let km_ev = KeymapEvent::ResolvedKeyOutput {
505                    keymap_index,
506                    key_output,
507                };
508                self.handle_event(key::Event::Keymap(km_ev));
509            }
510        }
511    }
512
513    /// Handles input events.
514    ///
515    /// Physical inputs enter a delay line first so at most one is processed
516    ///  per tick (one-tick pacing gate), including while a key is pending.
517    /// While pending, that delay line is the pending session's `ingest_queue`;
518    ///  otherwise it is the global `input_queue`.
519    /// Tap-hold and chorded interrupt logic depend on that spacing
520    ///  (`tests/rust/tap_hold/hold_on_interrupt_tap.rs`).
521    ///
522    /// Silently discards the input event if the active input queue is full.
523    pub fn handle_input(&mut self, ev: input::Event) {
524        let ready = if let Some(pending_state) = self.pending_state.as_mut() {
525            pending_state.ingest_queue.push_back_or_ignore(ev);
526            pending_state.ingest_queue.pop_front_if_ready()
527        } else {
528            self.input_queue.push_back_or_ignore(ev);
529            self.input_queue.pop_front_if_ready()
530        };
531
532        if let Some(ie) = ready {
533            // Process before clearing idle_time
534            //  so families that snapshot KeymapContext
535            //  (required_idle_time, pressed_modifiers, recent presses)
536            //  still see idle accumulated since the previous input.
537            self.process_input(ie);
538            self.set_active_input_delay();
539        }
540
541        self.idle_time = 0;
542    }
543
544    /// After processing one input, re-arm the active delay line.
545    ///
546    /// If processing resolved pending state, the global queue is active;
547    ///  if a pending session remains (or was just created), its ingest queue is.
548    fn set_active_input_delay(&mut self) {
549        if let Some(pending_state) = self.pending_state.as_mut() {
550            pending_state.ingest_queue.set_delay();
551        } else {
552            self.input_queue.set_delay();
553        }
554    }
555
556    fn has_pressed_input_with_keymap_index(&self, keymap_index: u16) -> bool {
557        self.pressed_inputs.iter().any(|pi| match pi {
558            &input::PressedInput::Key(input::PressedKey {
559                keymap_index: ki, ..
560            }) => keymap_index == ki,
561            _ => false,
562        })
563    }
564
565    fn update_pending_state(&mut self, ev: key::Event<Ev>) {
566        let Some(keymap_index) = self.pending_state.as_ref().map(|p| p.keymap_index) else {
567            return;
568        };
569        let pressed_modifiers = self.aggregate_pressed_modifiers();
570
571        if let Some(pending::PendingState {
572            key_ref,
573            pending_key_state,
574            queued_events,
575            ingest_queue,
576            press_idle_time_ms,
577            ..
578        }) = self.pending_state.as_mut()
579        {
580            let press_idle_time_ms = *press_idle_time_ms;
581            let (mut maybe_npk, pke) = self.key_system.update_pending_state(
582                pending_key_state,
583                keymap_index,
584                &self.context,
585                *key_ref,
586                ev,
587            );
588
589            pke.into_iter()
590                .for_each(|sch_ev| self.event_scheduler.schedule_event(sch_ev));
591
592            while let Some(npk) = maybe_npk.take() {
593                let pkr = match npk {
594                    key::NewPressedKey::Key(new_key_ref) => {
595                        *key_ref = new_key_ref;
596                        // Drop the previous pending key's delayed events
597                        //  (e.g. the chord timeout after passthrough or
598                        //  chord resolve) so they do not steal a tick from
599                        //  the replacement key's timeout.
600                        self.event_scheduler
601                            .cancel_events_for_keymap_index(keymap_index);
602                        // Nested `new_pressed_key` uses press-time
603                        //  `idle_time_ms` and omits this still-held press
604                        //  from the recent-press ring so
605                        //  `required_idle_time` and `quick_tap_ms` are
606                        //  checked against the physical press, not the
607                        //  outer timeout.
608                        let nested_press_ctx = keymap_context_without_current_press(
609                            self.recent_presses,
610                            self.recent_press_count,
611                            press_idle_time_ms,
612                            self.event_scheduler.schedule_counter,
613                            pressed_modifiers,
614                            keymap_index,
615                        );
616                        self.context.set_keymap_context(nested_press_ctx);
617                        let (pkr, pke) = self.key_system.new_pressed_key(
618                            keymap_index,
619                            &self.context,
620                            new_key_ref,
621                        );
622                        // Decision timeouts are from this physical press.
623                        // Fold time already spent waiting (e.g. chorded
624                        //  timeout before passthrough) into After delays.
625                        // Remaining 0 is Immediate so an expired inner
626                        //  timeout resolves in this turn.
627                        let elapsed_ms = self
628                            .event_scheduler
629                            .schedule_counter
630                            .saturating_sub(nested_press_ctx.time_ms);
631                        let pke = match &pkr {
632                            key::PressedKeyResult::Pending(_) => pke.backdate(elapsed_ms),
633                            _ => pke,
634                        };
635                        pke.into_iter()
636                            .for_each(|sch_ev| self.event_scheduler.schedule_event(sch_ev));
637                        pkr
638                    }
639                    key::NewPressedKey::NoOp => {
640                        let no_op_ks: KS = key::NoOpKeyState.into();
641                        key::PressedKeyResult::Resolved(no_op_ks)
642                    }
643                };
644
645                match pkr {
646                    key::PressedKeyResult::Resolved(ks) => {
647                        self.resolve_pending_key_state(ks);
648                        break;
649                    }
650                    key::PressedKeyResult::NewPressedKey(key::NewPressedKey::Key(new_key_ref)) => {
651                        maybe_npk = Some(key::NewPressedKey::Key(new_key_ref));
652                    }
653                    key::PressedKeyResult::NewPressedKey(key::NewPressedKey::NoOp) => {
654                        self.resolve_pending_key_state(key::NoOpKeyState.into());
655                        break;
656                    }
657                    key::PressedKeyResult::Pending(pks) => {
658                        *pending_key_state = pks;
659
660                        // Nested pending: re-feed session-log inputs chronologically
661                        //  into the current pending delay line (not the global queue).
662                        pending::dispatch_replayed_events(
663                            pending::KeyResolution::Pending,
664                            queued_events,
665                            ingest_queue,
666                            &mut self.event_scheduler,
667                        );
668                    }
669                }
670            }
671        }
672    }
673
674    fn process_input(&mut self, ev: input::Event) {
675        if let Some(pending_state) = self.pending_state.as_mut() {
676            // Paced input from the delay line: record in the session log, then apply.
677            pending_state.record_input(ev);
678            self.update_pending_state(ev.into());
679        } else {
680            // Update each of the pressed keys with the event.
681            self.pressed_inputs.iter_mut().for_each(|pi| {
682                if let input::PressedInput::Key(input::PressedKey {
683                    key_ref,
684                    key_state,
685                    keymap_index,
686                }) = pi
687                {
688                    self.key_system
689                        .update_state(key_state, key_ref, &self.context, *keymap_index, ev.into())
690                        .into_iter()
691                        .for_each(|sch_ev| self.event_scheduler.schedule_event(sch_ev));
692                }
693            });
694
695            self.context
696                .handle_event(ev.into())
697                .into_iter()
698                .for_each(|sch_ev| self.event_scheduler.schedule_event(sch_ev));
699
700            match ev {
701                input::Event::Press { keymap_index }
702                    if !self.has_pressed_input_with_keymap_index(keymap_index) =>
703                {
704                    // Snapshot held mods / recent presses before branching.
705                    self.push_keymap_context();
706
707                    let mut maybe_key_ref = Some(self.key_refs[keymap_index as usize]);
708
709                    while let Some(key_ref) = maybe_key_ref.take() {
710                        let (pkr, pke) =
711                            self.key_system
712                                .new_pressed_key(keymap_index, &self.context, key_ref);
713
714                        pke.into_iter()
715                            .for_each(|sch_ev| self.event_scheduler.schedule_event(sch_ev));
716
717                        match pkr {
718                            key::PressedKeyResult::Resolved(key_state) => {
719                                let _ = self.pressed_inputs.push(input::PressedInput::pressed_key(
720                                    keymap_index,
721                                    key_ref,
722                                    key_state,
723                                ));
724
725                                // The resolved key state has output. Emit this as an event.
726                                if let Some(key_output) =
727                                    self.key_system.key_output(&key_ref, &key_state)
728                                {
729                                    let km_ev = KeymapEvent::ResolvedKeyOutput {
730                                        keymap_index,
731                                        key_output,
732                                    };
733                                    self.handle_event(key::Event::Keymap(km_ev));
734                                }
735                            }
736                            key::PressedKeyResult::NewPressedKey(key::NewPressedKey::Key(
737                                new_key_ref,
738                            )) => {
739                                maybe_key_ref = Some(new_key_ref);
740                            }
741                            key::PressedKeyResult::NewPressedKey(key::NewPressedKey::NoOp) => {
742                                let key_state: KS = key::NoOpKeyState.into();
743
744                                let _ = self.pressed_inputs.push(input::PressedInput::pressed_key(
745                                    keymap_index,
746                                    key_ref,
747                                    key_state,
748                                ));
749                            }
750                            key::PressedKeyResult::Pending(pending_key_state) => {
751                                // Fresh pending session owns its own delay line,
752                                //  armed so the next physical input is deferred.
753                                // Move any inputs already sitting in the global
754                                //  queue (e.g. a rapid release pushed before this
755                                //  press was processed) into the new local delay
756                                //  line so they are paced while pending.
757                                let mut pending_state = pending::PendingState::new(
758                                    keymap_index,
759                                    key_ref,
760                                    pending_key_state,
761                                    self.idle_time,
762                                );
763                                let mut remaining = self.input_queue.take_all();
764                                pending_state.ingest_queue.append_all(&mut remaining);
765                                self.pending_state = Some(pending_state);
766                            }
767                        }
768                    }
769
770                    // Record after press handling so quick-tap sees the *prior* press.
771                    self.record_recent_press(keymap_index);
772                }
773                input::Event::Release { keymap_index } => {
774                    self.pressed_inputs
775                        .iter()
776                        .position(|pi| match pi {
777                            &input::PressedInput::Key(input::PressedKey {
778                                keymap_index: ki,
779                                ..
780                            }) => keymap_index == ki,
781                            _ => false,
782                        })
783                        .map(|i| self.pressed_inputs.remove(i));
784                }
785
786                input::Event::VirtualKeyPress { key_output } => {
787                    let pressed_key = input::PressedInput::Virtual(key_output);
788                    let _ = self.pressed_inputs.push(pressed_key);
789                }
790                input::Event::VirtualKeyRelease { key_output } => {
791                    // Remove from pressed keys.
792                    self.pressed_inputs
793                        .iter()
794                        .position(|k| match k {
795                            input::PressedInput::Virtual(ko) => key_output == *ko,
796                            _ => false,
797                        })
798                        .map(|i| self.pressed_inputs.remove(i));
799                }
800
801                _ => {}
802            }
803        }
804
805        self.handle_pending_events();
806    }
807
808    // Called from handle_all_pending_events,
809    //  and for handling the (resolving) queue of events from pending key state.
810    fn handle_event(&mut self, ev: key::Event<Ev>) {
811        if let key::Event::Keymap(KeymapEvent::Callback(callback_id)) = ev {
812            match self.callbacks.get(&callback_id) {
813                Some(CallbackFunction::Rust(callback_fn)) => {
814                    callback_fn();
815                }
816                Some(CallbackFunction::ExternC(callback_fn)) => {
817                    callback_fn();
818                }
819                None => {}
820            }
821        }
822
823        let was_pending = self.pending_state.is_some();
824
825        // pending state needs to handle events
826        self.update_pending_state(ev);
827
828        // Update each of the pressed keys with the event.
829        self.pressed_inputs.iter_mut().for_each(|pi| {
830            if let input::PressedInput::Key(input::PressedKey {
831                key_state,
832                key_ref,
833                keymap_index,
834            }) = pi
835            {
836                self.key_system
837                    .update_state(key_state, key_ref, &self.context, *keymap_index, ev)
838                    .into_iter()
839                    .for_each(|sch_ev| self.event_scheduler.schedule_event(sch_ev));
840            }
841        });
842
843        // Update context with the event
844        self.context
845            .handle_event(ev)
846            .into_iter()
847            .for_each(|sch_ev| self.event_scheduler.schedule_event(sch_ev));
848
849        if let Event::Input(input_ev) = ev {
850            if was_pending {
851                // `update_pending_state` already ran above.
852                // Only record for replay if still pending;
853                //  do not re-apply or fall through to the non-pending press path.
854                if let Some(pending_state) = self.pending_state.as_mut() {
855                    pending_state.record_input(input_ev);
856                }
857                self.handle_pending_events();
858            } else {
859                self.process_input(input_ev);
860            }
861        }
862    }
863
864    fn handle_pending_events(&mut self) {
865        // take from pending
866        while let Some(ev) = self.event_scheduler.dequeue() {
867            self.handle_event(ev);
868        }
869    }
870
871    /// Aggregate keyboard modifiers from already-pressed inputs (no suppress).
872    ///
873    /// Includes [`key::System::pending_output`] while a pending session is live
874    ///  so the next key sees the speculated modifiers.
875    fn aggregate_pressed_modifiers(&self) -> key::KeyboardModifiers {
876        let base = self
877            .pressed_inputs
878            .iter()
879            .filter_map(|pi| match pi {
880                input::PressedInput::Key(input::PressedKey {
881                    key_ref, key_state, ..
882                }) => self.key_system.key_output(key_ref, key_state),
883                &input::PressedInput::Virtual(key_output) => Some(key_output),
884            })
885            .fold(key::KeyboardModifiers::NONE, |acc, ko| {
886                acc.union(&ko.key_modifiers())
887            });
888        let pending_mod = self
889            .pending_state
890            .as_ref()
891            .and_then(|pending| self.key_system.pending_output(&pending.pending_key_state))
892            .map_or(key::KeyboardModifiers::NONE, |ko| ko.key_modifiers());
893        base.union(&pending_mod)
894    }
895
896    fn push_keymap_context(&mut self) {
897        let km_context = KeymapContext {
898            time_ms: self.event_scheduler.schedule_counter,
899            idle_time_ms: self.idle_time,
900            pressed_modifiers: self.aggregate_pressed_modifiers(),
901            recent_presses: self.recent_presses,
902            recent_press_count: self.recent_press_count,
903        };
904        self.context.set_keymap_context(km_context);
905    }
906
907    /// Advances the state of the keymap by one tick.
908    pub fn tick(&mut self) {
909        self.push_keymap_context();
910
911        let ready = if let Some(pending_state) = self.pending_state.as_mut() {
912            pending_state.ingest_queue.pop_front_if_ready()
913        } else {
914            self.input_queue.pop_front_if_ready()
915        };
916
917        if let Some(ie) = ready {
918            self.process_input(ie);
919            self.set_active_input_delay();
920        }
921
922        // Always tick the global delay gate so it does not go stale
923        //  across a pending session (e.g. on resolve transfer).
924        self.input_queue.tick_delay();
925        if let Some(pending_state) = self.pending_state.as_mut() {
926            pending_state.ingest_queue.tick_delay();
927        }
928
929        self.event_scheduler.tick(self.ms_per_tick);
930
931        self.handle_pending_events();
932
933        self.idle_time += self.ms_per_tick as u32;
934    }
935
936    /// Returns the the pressed key outputs.
937    ///
938    /// Includes [`key::System::pending_output`] while a pending session is live.
939    pub fn pressed_keys(&self) -> heapless::Vec<key::KeyOutput, { MAX_PRESSED_KEYS }> {
940        let suppress = self.context.suppressed_modifiers();
941        let resolved = self.pressed_inputs.iter().filter_map(|pi| {
942            let ko = match pi {
943                input::PressedInput::Key(input::PressedKey {
944                    key_ref, key_state, ..
945                }) => self.key_system.key_output(key_ref, key_state)?,
946                &input::PressedInput::Virtual(key_output) => key_output,
947            };
948            let ko = ko.without_modifiers(suppress);
949            (ko != key::KeyOutput::NO_OUTPUT).then_some(ko)
950        });
951        let pending = self
952            .pending_state
953            .as_ref()
954            .and_then(|pending| self.key_system.pending_output(&pending.pending_key_state))
955            .map(|ko| ko.without_modifiers(suppress))
956            .filter(|ko| *ko != key::KeyOutput::NO_OUTPUT);
957        resolved.chain(pending).take(MAX_PRESSED_KEYS).collect()
958    }
959
960    fn tick_by(&mut self, delta_ms: u32) {
961        if delta_ms == 0 {
962            self.tick();
963        } else {
964            for _ in 0..(delta_ms / self.ms_per_tick as u32) {
965                self.tick();
966            }
967        }
968    }
969
970    /// Handles input events.
971    ///
972    /// Discards the input event if the input queue is full.
973    ///
974    /// Returns the time in ms until the next scheduled event, if any.
975    ///  (Time until next tick, if any, will always be >0, so 0 can be used as "NO EVENTS")
976    pub fn handle_input_after_time(&mut self, delta_ms: u32, ev: input::Event) -> Option<u32> {
977        self.tick_by(delta_ms);
978        self.handle_input(ev);
979        let next_event_time = self.event_scheduler.next_event_time();
980        debug_assert!(next_event_time != Some(0));
981        next_event_time
982    }
983
984    /// If the event scheduler has a next scheduled event,
985    ///  it ticks the keymap forward to that event,
986    ///  returning the time in ms until the following event.
987    ///
988    /// Otherwise, does nothing and returns None.
989    pub fn tick_to_next_scheduled_event(&mut self) -> Option<u32> {
990        if let Some(delta_ms) = self.event_scheduler.next_event_time() {
991            self.tick_by(delta_ms);
992            self.event_scheduler.next_event_time()
993        } else {
994            None
995        }
996    }
997
998    /// Updates the keymap indicating a report is sent; returns the reportable keymap output.
999    pub fn report_output(&mut self) -> KeymapOutput {
1000        self.hid_reporter.update(self.pressed_keys());
1001        self.hid_reporter.report_sent();
1002
1003        KeymapOutput::new(self.hid_reporter.reportable_key_outputs())
1004    }
1005
1006    /// Returns the current HID keyboard report.
1007    #[doc(hidden)]
1008    pub fn boot_keyboard_report(&self) -> [u8; 8] {
1009        KeymapOutput::new(self.pressed_keys()).as_hid_boot_keyboard_report()
1010    }
1011
1012    /// Whether the keymap has pending state that requires polling.
1013    pub fn requires_polling(&self) -> bool {
1014        !self.event_scheduler.pending_events.is_empty()
1015            || !self.input_queue.is_empty()
1016            || self
1017                .pending_state
1018                .as_ref()
1019                .is_some_and(|ps| !ps.ingest_queue.is_empty())
1020    }
1021
1022    #[doc(hidden)]
1023    pub fn has_scheduled_events(&self) -> bool {
1024        !self.event_scheduler.pending_events.is_empty()
1025            || !self.event_scheduler.scheduled_events.is_empty()
1026            || !self.input_queue.is_empty()
1027            || self
1028                .pending_state
1029                .as_ref()
1030                .is_some_and(|ps| !ps.ingest_queue.is_empty())
1031    }
1032}
1033
1034/// Test-only inspection hooks for pending-state / input-queue pacing.
1035///
1036/// Used by `smart-keymap-full-system-std` integration tests
1037/// (cannot live as `#[cfg(test)]` unit helpers because that crate is a separate package).
1038#[cfg(feature = "std")]
1039#[doc(hidden)]
1040impl<
1041        I: Debug + Index<usize, Output = R>,
1042        R: Copy + Debug,
1043        Ctx: Debug + key::Context<Event = Ev> + SetKeymapContext + ReportHints,
1044        Ev: Copy + Debug,
1045        PKS: Debug,
1046        KS: Copy + Debug + From<key::NoOpKeyState>,
1047        S: key::System<R, Ref = R, Context = Ctx, Event = Ev, PendingKeyState = PKS, KeyState = KS>,
1048    > Keymap<I, R, Ctx, Ev, PKS, KS, S>
1049{
1050    /// Whether a pending key state is active.
1051    pub fn test_is_pending(&self) -> bool {
1052        self.pending_state.is_some()
1053    }
1054
1055    /// Length of the pending session log, if pending.
1056    pub fn test_pending_queued_events_len(&self) -> Option<usize> {
1057        self.pending_state
1058            .as_ref()
1059            .map(|pending_state| pending_state.queued_events.len())
1060    }
1061
1062    /// Session-log input events (only `Event::Input` variants),
1063    ///  in log order.
1064    pub fn test_pending_session_log_inputs(&self) -> Option<heapless::Vec<input::Event, 16>> {
1065        self.pending_state.as_ref().map(|pending_state| {
1066            let mut inputs = heapless::Vec::new();
1067            for ev in pending_state.queued_events.iter() {
1068                if let key::Event::Input(ie) = ev {
1069                    let _ = inputs.push(*ie);
1070                }
1071            }
1072            inputs
1073        })
1074    }
1075
1076    /// Length of the active delay line
1077    ///  (pending `ingest_queue` while pending, else global `input_queue`).
1078    pub fn test_input_queue_len(&self) -> usize {
1079        if let Some(pending_state) = self.pending_state.as_ref() {
1080            pending_state.ingest_queue.len()
1081        } else {
1082            self.input_queue.len()
1083        }
1084    }
1085
1086    /// Delay gate of the active delay line
1087    ///  (pending `ingest_queue` while pending, else global `input_queue`).
1088    pub fn test_input_queue_delay(&self) -> bool {
1089        if let Some(pending_state) = self.pending_state.as_ref() {
1090            pending_state.ingest_queue.delay()
1091        } else {
1092            self.input_queue.delay()
1093        }
1094    }
1095
1096    /// Schedule an immediate key event and process pending events.
1097    pub fn test_handle_scheduled_key_event(&mut self, ev: key::Event<Ev>) {
1098        self.event_scheduler
1099            .schedule_event(key::ScheduledEvent::immediate(ev));
1100        self.handle_pending_events();
1101    }
1102}
1103
1104#[cfg(test)]
1105#[allow(clippy::unwrap_used, clippy::expect_used)]
1106mod tests {
1107    use super::*;
1108
1109    #[test]
1110    fn test_keymap_output_pressed_key_codes_includes_modifier_key_code() {
1111        // Assemble - include modifier key left ctrl
1112        let mut input: heapless::Vec<key::KeyOutput, { MAX_PRESSED_KEYS }> = heapless::Vec::new();
1113        input.push(key::KeyOutput::from_key_code(0x04)).unwrap();
1114        input.push(key::KeyOutput::from_key_code(0xE0)).unwrap();
1115
1116        // Act - construct the output
1117        let keymap_output = KeymapOutput::new(input);
1118        let pressed_key_codes = keymap_output.pressed_key_codes();
1119
1120        // Assert - check the 0xE0 gets included as a key code.
1121        assert!(pressed_key_codes.contains(&0xE0))
1122    }
1123
1124    #[test]
1125    fn test_keymap_output_as_hid_boot_keyboard_report_gathers_modifiers() {
1126        // Assemble - include modifier key left ctrl
1127        let mut input: heapless::Vec<key::KeyOutput, { MAX_PRESSED_KEYS }> = heapless::Vec::new();
1128        input.push(key::KeyOutput::from_key_code(0x04)).unwrap();
1129        input.push(key::KeyOutput::from_key_code(0xE0)).unwrap();
1130
1131        // Act - construct the output
1132        let keymap_output = KeymapOutput::new(input);
1133        let actual_report: [u8; 8] = keymap_output.as_hid_boot_keyboard_report();
1134
1135        // Assert - check the 0xE0 gets considered as a "modifier".
1136        let expected_report: [u8; 8] = [0x01, 0, 0x04, 0, 0, 0, 0, 0];
1137        assert_eq!(expected_report, actual_report);
1138    }
1139
1140    #[test]
1141    fn test_keymap_output_pressed_consumer_codes() {
1142        let mut input: heapless::Vec<key::KeyOutput, { MAX_PRESSED_KEYS }> = heapless::Vec::new();
1143        input
1144            .push(key::KeyOutput::from_consumer_code(0xE9))
1145            .unwrap();
1146
1147        let keymap_output = KeymapOutput::new(input);
1148        assert_eq!(
1149            heapless::Vec::<u8, 24>::from_slice(&[0xE9]).unwrap(),
1150            keymap_output.pressed_consumer_codes()
1151        );
1152    }
1153
1154    #[test]
1155    fn test_keymap_output_pressed_mouse_output_combines_buttons() {
1156        let mut input: heapless::Vec<key::KeyOutput, { MAX_PRESSED_KEYS }> = heapless::Vec::new();
1157        input
1158            .push(key::KeyOutput::from_mouse_output(key::MouseOutput {
1159                pressed_buttons: 0b001,
1160                ..key::MouseOutput::NO_OUTPUT
1161            }))
1162            .unwrap();
1163        input
1164            .push(key::KeyOutput::from_mouse_output(key::MouseOutput {
1165                pressed_buttons: 0b010,
1166                ..key::MouseOutput::NO_OUTPUT
1167            }))
1168            .unwrap();
1169
1170        let keymap_output = KeymapOutput::new(input);
1171        assert_eq!(
1172            key::MouseOutput {
1173                pressed_buttons: 0b011,
1174                ..key::MouseOutput::NO_OUTPUT
1175            },
1176            keymap_output.pressed_mouse_output()
1177        );
1178    }
1179
1180    #[test]
1181    fn test_keymap_context_default_is_zeroed() {
1182        let context = KeymapContext::new();
1183        assert_eq!(0, context.time_ms);
1184        assert_eq!(0, context.idle_time_ms);
1185    }
1186
1187    fn recent_presses_from(entries: &[(u16, u32)]) -> ([(u16, u32); MAX_RECENT_PRESSES], u8) {
1188        let mut presses = [(0, 0); MAX_RECENT_PRESSES];
1189        presses[..entries.len()].copy_from_slice(entries);
1190        (presses, entries.len() as u8)
1191    }
1192
1193    #[test]
1194    fn test_push_recent_press_appends_same_index() {
1195        // Assemble
1196        let presses = [(0, 0); MAX_RECENT_PRESSES];
1197
1198        // Act
1199        let (presses, count) = push_recent_press(presses, 0, 2, 0);
1200        let (presses, count) = push_recent_press(presses, count, 2, 50);
1201
1202        // Assert: both times remain so excluding the current press can still see the prior one.
1203        assert_eq!(2, count);
1204        assert_eq!([(2, 0), (2, 50)], &presses[..2]);
1205    }
1206
1207    #[test]
1208    fn test_push_recent_press_appends_distinct_indices() {
1209        // Assemble
1210        let presses = [(0, 0); MAX_RECENT_PRESSES];
1211
1212        // Act
1213        let (presses, count) = push_recent_press(presses, 0, 1, 10);
1214        let (presses, count) = push_recent_press(presses, count, 2, 20);
1215
1216        // Assert
1217        assert_eq!(2, count);
1218        assert_eq!([(1, 10), (2, 20)], &presses[..2]);
1219    }
1220
1221    #[test]
1222    fn test_push_recent_press_evicts_oldest_when_full() {
1223        // Assemble: ring filled with distinct indices.
1224        let (presses, count) = (0..MAX_RECENT_PRESSES).fold(
1225            ([(0, 0); MAX_RECENT_PRESSES], 0u8),
1226            |(presses, count), i| push_recent_press(presses, count, i as u16, i as u32 * 10),
1227        );
1228
1229        // Act
1230        let (presses, count) = push_recent_press(presses, count, 99, 1000);
1231
1232        // Assert
1233        assert_eq!(MAX_RECENT_PRESSES as u8, count);
1234        assert_eq!((1, 10), presses[0]);
1235        assert_eq!((99, 1000), presses[MAX_RECENT_PRESSES - 1]);
1236    }
1237
1238    #[test]
1239    fn test_without_current_press_keeps_prior_same_index() {
1240        // Assemble: two presses of the same index, as after a re-press.
1241        let (presses, count) = recent_presses_from(&[(2, 0), (2, 50)]);
1242
1243        // Act
1244        let ctx = keymap_context_without_current_press(
1245            presses,
1246            count,
1247            0,
1248            50,
1249            key::KeyboardModifiers::NONE,
1250            2,
1251        );
1252
1253        // Assert: current press dropped; prior press remains for quick_tap_ms.
1254        assert_eq!(50, ctx.time_ms);
1255        assert_eq!(1, ctx.recent_press_count);
1256        assert_eq!(Some(0), ctx.last_press_time_ms(2));
1257    }
1258
1259    #[test]
1260    fn test_without_current_press_uses_fallback_when_index_absent() {
1261        // Assemble
1262        let (presses, count) = recent_presses_from(&[(1, 10)]);
1263
1264        // Act
1265        let ctx = keymap_context_without_current_press(
1266            presses,
1267            count,
1268            7,
1269            99,
1270            key::KeyboardModifiers::NONE,
1271            2,
1272        );
1273
1274        // Assert
1275        assert_eq!(99, ctx.time_ms);
1276        assert_eq!(7, ctx.idle_time_ms);
1277        assert_eq!(1, ctx.recent_press_count);
1278        assert_eq!(Some(10), ctx.last_press_time_ms(1));
1279        assert_eq!(None, ctx.last_press_time_ms(2));
1280    }
1281
1282    #[test]
1283    fn test_without_current_press_compacts_after_dropped_index() {
1284        // Assemble
1285        let (presses, count) = recent_presses_from(&[(1, 10), (2, 20), (3, 30)]);
1286
1287        // Act
1288        let ctx = keymap_context_without_current_press(
1289            presses,
1290            count,
1291            0,
1292            99,
1293            key::KeyboardModifiers::NONE,
1294            2,
1295        );
1296
1297        // Assert
1298        assert_eq!(20, ctx.time_ms);
1299        assert_eq!(2, ctx.recent_press_count);
1300        assert_eq!([(1, 10), (3, 30)], &ctx.recent_presses[..2]);
1301        assert_eq!((0, 0), ctx.recent_presses[2]);
1302    }
1303
1304    #[test]
1305    fn test_without_current_press_time_is_physical_press_not_live_fallback() {
1306        // Assemble -- current press recorded at 50; live schedule_counter is 250
1307        //  (the recent-press ring impl stores (keymap_index, time_ms))
1308        let (presses, count) = recent_presses_from(&[(0, 50)]);
1309
1310        // Act -- nested replacement after 200ms pending
1311        let ctx = keymap_context_without_current_press(
1312            presses,
1313            count,
1314            40,
1315            250,
1316            key::KeyboardModifiers::NONE,
1317            0,
1318        );
1319
1320        // Assert -- time is the physical press from recent_presses, not live fallback
1321        assert_eq!(50, ctx.time_ms);
1322    }
1323
1324    #[test]
1325    fn test_without_current_press_idle_is_stored_press_idle() {
1326        // Assemble -- press recorded at 50; live idle has ticked to 200
1327        let (presses, count) = recent_presses_from(&[(2, 50)]);
1328
1329        // Act -- pass stored press idle 40, not live 200
1330        let ctx = keymap_context_without_current_press(
1331            presses,
1332            count,
1333            40,
1334            250,
1335            key::KeyboardModifiers::NONE,
1336            2,
1337        );
1338
1339        // Assert -- idle_time_ms is the stored press idle, not derived from ring/fallback
1340        assert_eq!(40, ctx.idle_time_ms);
1341        assert_eq!(50, ctx.time_ms);
1342    }
1343
1344    #[test]
1345    fn test_without_current_press_keeps_later_other_index() {
1346        // Assemble -- this press at 50, another key pressed during pending at 80
1347        //  (recent_presses ring holds both)
1348        let (presses, count) = recent_presses_from(&[(0, 50), (1, 80)]);
1349
1350        // Act -- without current press 0
1351        let ctx = keymap_context_without_current_press(
1352            presses,
1353            count,
1354            40,
1355            250,
1356            key::KeyboardModifiers::NONE,
1357            0,
1358        );
1359
1360        // Assert -- current press dropped; other index remains in ring
1361        assert_eq!(50, ctx.time_ms);
1362        assert_eq!(1, ctx.recent_press_count);
1363        assert_eq!(None, ctx.last_press_time_ms(0));
1364        assert_eq!(Some(80), ctx.last_press_time_ms(1));
1365    }
1366
1367    #[test]
1368    fn test_without_current_press_passes_modifiers_through() {
1369        // Assemble -- recent_presses ring with one entry
1370        let (presses, count) = recent_presses_from(&[(0, 50)]);
1371        let mods = key::KeyboardModifiers::LEFT_CTRL;
1372
1373        // Act
1374        let ctx = keymap_context_without_current_press(presses, count, 0, 50, mods, 0);
1375
1376        // Assert -- pressed_modifiers forwarded unchanged
1377        assert_eq!(mods, ctx.pressed_modifiers);
1378    }
1379}