1use core::fmt::Debug;
2use core::ops::Index;
3
4use serde::Deserialize;
5
6use crate::input;
7use crate::key;
8use crate::keymap;
9use crate::slice::Slice;
10
11#[derive(Deserialize, Debug, Clone, Copy, PartialEq)]
13pub struct Ref(pub u8);
14
15pub const MAX_EXTRA_PROFILES: usize = 7;
21
22pub const MAX_HOLD_TRIGGER_POSITIONS: usize = 16;
27
28#[derive(Deserialize, Debug, Clone, Copy, PartialEq)]
34pub struct Profile {
35 #[serde(default = "default_timeout")]
46 pub timeout: Option<u16>,
47
48 #[serde(default = "default_interrupt_response")]
50 pub interrupt_response: InterruptResponse,
51
52 #[serde(default)]
58 pub required_idle_time: Option<u16>,
59
60 #[serde(default, rename = "hold_trigger_key_positions")]
67 pub hold_trigger_positions: Option<Slice<u16, MAX_HOLD_TRIGGER_POSITIONS>>,
68
69 #[serde(default)]
78 pub quick_tap_ms: Option<u16>,
79
80 #[serde(default = "default_pending_output")]
87 pub pending_output: PendingOutput,
88}
89
90impl Profile {
91 pub const fn new() -> Self {
93 DEFAULT_PROFILE
94 }
95
96 pub const fn allows_hold_trigger(&self, other_keymap_index: u16) -> bool {
102 match self.hold_trigger_positions {
103 None => true,
104 Some(positions) => {
105 let list = positions.as_slice();
106 let mut i = 0;
107 while i < list.len() {
108 if list[i] == other_keymap_index {
109 return true;
110 }
111 i += 1;
112 }
113 false
114 }
115 }
116 }
117}
118
119impl Default for Profile {
120 fn default() -> Self {
121 DEFAULT_PROFILE
122 }
123}
124
125#[derive(Deserialize, Debug, Clone, Copy, PartialEq)]
127pub struct Key<R> {
128 pub tap: R,
130 pub hold: R,
132 #[serde(default)]
134 pub profile: u8,
135 #[serde(default)]
144 pub hold_output: Option<key::KeyOutput>,
145}
146
147impl<R> Key<R> {
148 pub const fn new(tap: R, hold: R) -> Key<R> {
150 Key {
151 tap,
152 hold,
153 profile: 0,
154 hold_output: None,
155 }
156 }
157
158 pub const fn with_profile(tap: R, hold: R, profile: u8) -> Key<R> {
160 Key {
161 tap,
162 hold,
163 profile,
164 hold_output: None,
165 }
166 }
167}
168
169#[cfg(feature = "std")]
170impl<R: Default> Default for Key<R> {
171 fn default() -> Self {
172 Key {
173 tap: R::default(),
174 hold: R::default(),
175 profile: 0,
176 hold_output: None,
177 }
178 }
179}
180
181#[derive(Deserialize, Debug, Clone, Copy, PartialEq)]
183pub enum PendingOutput {
184 NoOutput,
186 Hold,
188}
189
190#[derive(Deserialize, Debug, Clone, Copy, PartialEq)]
192pub enum InterruptResponse {
193 Ignore,
196 HoldOnKeyPress,
198 HoldOnKeyTap,
201}
202
203#[derive(Deserialize, Debug, Clone, Copy, PartialEq)]
213pub struct Config {
214 #[serde(default = "default_profile_value")]
216 pub default_profile: Profile,
217
218 #[serde(default)]
220 pub profiles: Slice<Profile, MAX_EXTRA_PROFILES>,
221}
222
223pub const DEFAULT_TIMEOUT: u16 = 200;
225
226pub const DEFAULT_INTERRUPT_RESPONSE: InterruptResponse = InterruptResponse::Ignore;
228
229fn default_timeout() -> Option<u16> {
230 Some(DEFAULT_TIMEOUT)
231}
232
233fn default_interrupt_response() -> InterruptResponse {
234 DEFAULT_INTERRUPT_RESPONSE
235}
236
237pub const DEFAULT_PENDING_OUTPUT: PendingOutput = PendingOutput::NoOutput;
239
240fn default_pending_output() -> PendingOutput {
241 DEFAULT_PENDING_OUTPUT
242}
243
244fn default_profile_value() -> Profile {
245 DEFAULT_PROFILE
246}
247
248pub const DEFAULT_PROFILE: Profile = Profile {
250 timeout: Some(DEFAULT_TIMEOUT),
251 interrupt_response: DEFAULT_INTERRUPT_RESPONSE,
252 required_idle_time: None,
253 hold_trigger_positions: None,
254 quick_tap_ms: None,
255 pending_output: DEFAULT_PENDING_OUTPUT,
256};
257
258pub const DEFAULT_CONFIG: Config = Config {
260 default_profile: DEFAULT_PROFILE,
261 profiles: Slice::from_slice(&[]),
262};
263
264impl Config {
265 pub const fn new() -> Self {
267 DEFAULT_CONFIG
268 }
269
270 pub const fn profile(&self, profile_id: u8) -> Profile {
277 if profile_id == 0 {
278 return self.default_profile;
279 }
280 let idx = (profile_id - 1) as usize;
281 let extras = self.profiles.as_slice();
282 if idx < extras.len() {
283 extras[idx]
284 } else {
285 self.default_profile
286 }
287 }
288}
289
290impl Default for Config {
291 fn default() -> Self {
293 DEFAULT_CONFIG
294 }
295}
296
297#[derive(Debug, Clone, Copy, PartialEq)]
299pub struct Context {
300 config: Config,
301 idle_time_ms: u32,
302 time_ms: u32,
303 recent_presses: [(u16, u32); keymap::MAX_RECENT_PRESSES],
304 recent_press_count: u8,
305}
306
307impl Context {
308 pub const fn from_config(config: Config) -> Context {
310 Context {
311 config,
312 idle_time_ms: 0,
313 time_ms: 0,
314 recent_presses: [(0, 0); keymap::MAX_RECENT_PRESSES],
315 recent_press_count: 0,
316 }
317 }
318
319 pub fn reset(&mut self) {
321 *self = Self::from_config(self.config);
322 }
323
324 pub fn update_keymap_context(
326 &mut self,
327 keymap::KeymapContext {
328 idle_time_ms,
329 time_ms,
330 recent_presses,
331 recent_press_count,
332 ..
333 }: &keymap::KeymapContext,
334 ) {
335 self.idle_time_ms = *idle_time_ms;
336 self.time_ms = *time_ms;
337 self.recent_presses = *recent_presses;
338 self.recent_press_count = *recent_press_count;
339 }
340
341 pub const fn profile(&self, profile_id: u8) -> Profile {
343 self.config.profile(profile_id)
344 }
345
346 fn last_press_time_ms(&self, keymap_index: u16) -> Option<u32> {
347 self.recent_presses[..self.recent_press_count as usize]
348 .iter()
349 .rev()
350 .find(|(ki, _)| *ki == keymap_index)
351 .map(|(_, t)| *t)
352 }
353
354 fn is_quick_tap(&self, profile: &Profile, keymap_index: u16) -> bool {
356 profile
357 .quick_tap_ms
358 .zip(self.last_press_time_ms(keymap_index))
359 .is_some_and(|(quick_tap_ms, last_t)| {
360 self.time_ms.saturating_sub(last_t) < quick_tap_ms as u32
361 })
362 }
363}
364
365#[derive(Debug, Clone, Copy, PartialEq)]
367pub enum TapHoldState {
368 Tap,
370 Hold,
372}
373
374#[derive(Debug, Clone, Copy, PartialEq)]
376pub enum Event {
377 TapHoldTimeout,
379}
380
381#[derive(Debug, Clone, PartialEq)]
383pub struct PendingKeyState {
384 other_pressed_keymap_index: Option<u16>,
386 output: Option<key::KeyOutput>,
388}
389
390impl PendingKeyState {
391 fn new() -> PendingKeyState {
393 PendingKeyState {
394 other_pressed_keymap_index: None,
395 output: None,
396 }
397 }
398
399 fn new_with_output(output: Option<key::KeyOutput>) -> Self {
401 Self {
402 other_pressed_keymap_index: None,
403 output,
404 }
405 }
406
407 fn hold_resolution(
410 &self,
411 profile: &Profile,
412 keymap_index: u16,
413 event: key::Event<Event>,
414 ) -> Option<TapHoldState> {
415 match profile.interrupt_response {
416 InterruptResponse::HoldOnKeyPress => {
417 match event {
418 key::Event::Input(input::Event::Press { keymap_index: ki }) => {
419 if profile.allows_hold_trigger(ki) {
422 Some(TapHoldState::Hold)
423 } else {
424 None
425 }
426 }
427 key::Event::Input(input::Event::Release { keymap_index: ki }) => {
428 if keymap_index == ki {
429 Some(TapHoldState::Tap)
431 } else {
432 None
433 }
434 }
435 key::Event::Key {
436 key_event: Event::TapHoldTimeout,
437 ..
438 } => {
439 Some(TapHoldState::Hold)
441 }
442 _ => None,
443 }
444 }
445 InterruptResponse::HoldOnKeyTap => {
446 match event {
447 key::Event::Input(input::Event::Release { keymap_index: ki }) => {
448 if keymap_index == ki {
449 Some(TapHoldState::Tap)
451 } else if Some(ki) == self.other_pressed_keymap_index
452 && profile.allows_hold_trigger(ki)
453 {
454 Some(TapHoldState::Hold)
456 } else {
457 None
458 }
459 }
460 key::Event::Key {
461 key_event: Event::TapHoldTimeout,
462 ..
463 } => {
464 Some(TapHoldState::Hold)
466 }
467 _ => None,
468 }
469 }
470 InterruptResponse::Ignore => {
471 match event {
472 key::Event::Input(input::Event::Release { keymap_index: ki }) => {
473 if keymap_index == ki {
474 Some(TapHoldState::Tap)
476 } else {
477 None
478 }
479 }
480 key::Event::Key {
481 key_event: Event::TapHoldTimeout,
482 ..
483 } => {
484 Some(TapHoldState::Hold)
486 }
487 _ => None,
488 }
489 }
490 }
491 }
492
493 pub fn handle_event(
495 &mut self,
496 profile: &Profile,
497 keymap_index: u16,
498 event: key::Event<Event>,
499 ) -> Option<TapHoldState> {
500 if let key::Event::Input(input::Event::Press { keymap_index: ki }) = event {
503 self.other_pressed_keymap_index = Some(ki);
504 }
505
506 self.hold_resolution(profile, keymap_index, event)
508 }
509}
510
511#[derive(Debug, Clone, Copy, PartialEq)]
513pub struct KeyState;
514
515#[derive(Debug, Clone, Copy, PartialEq)]
517pub struct System<R, Keys: Index<usize, Output = Key<R>>> {
518 keys: Keys,
519}
520
521impl<R, Keys: Index<usize, Output = Key<R>>> System<R, Keys> {
522 pub const fn new(key_data: Keys) -> Self {
524 Self { keys: key_data }
525 }
526
527 fn new_pending_key(
528 &self,
529 profile: &Profile,
530 keymap_index: u16,
531 hold_output: Option<key::KeyOutput>,
532 ) -> (PendingKeyState, Option<key::ScheduledEvent<Event>>) {
533 let pending = match profile.pending_output {
534 PendingOutput::Hold => PendingKeyState::new_with_output(hold_output),
535 PendingOutput::NoOutput => PendingKeyState::new(),
536 };
537 let scheduled = profile.timeout.map(|timeout| {
538 key::ScheduledEvent::after(
539 timeout,
540 key::Event::key_event(keymap_index, Event::TapHoldTimeout),
541 )
542 });
543 (pending, scheduled)
544 }
545
546 fn resolve_as_tap(
547 &self,
548 key_index: u8,
549 ) -> (
550 key::PressedKeyResult<R, PendingKeyState, KeyState>,
551 key::KeyEvents<Event>,
552 )
553 where
554 R: Copy,
555 {
556 let Key {
557 tap: tap_key_ref, ..
558 } = self.keys[key_index as usize];
559 (
560 key::PressedKeyResult::NewPressedKey(key::NewPressedKey::key(tap_key_ref)),
561 key::KeyEvents::no_events(),
562 )
563 }
564}
565
566impl<R: Copy + Debug, Keys: Debug + Index<usize, Output = Key<R>>> key::System<R>
567 for System<R, Keys>
568{
569 type Ref = Ref;
570 type Context = Context;
571 type Event = Event;
572 type PendingKeyState = PendingKeyState;
573 type KeyState = KeyState;
574
575 fn new_pressed_key(
576 &self,
577 keymap_index: u16,
578 context: &Self::Context,
579 Ref(key_index): Ref,
580 ) -> (
581 key::PressedKeyResult<R, Self::PendingKeyState, Self::KeyState>,
582 key::KeyEvents<Self::Event>,
583 ) {
584 let key_def = &self.keys[key_index as usize];
585 let profile = context.profile(key_def.profile);
586
587 if context.is_quick_tap(&profile, keymap_index) {
589 return self.resolve_as_tap(key_index);
590 }
591
592 match profile.required_idle_time {
593 Some(required_idle_time) => {
594 if context.idle_time_ms >= required_idle_time as u32 {
595 let (th_pks, maybe_sch_ev) =
597 self.new_pending_key(&profile, keymap_index, key_def.hold_output);
598 let pk = key::PressedKeyResult::Pending(th_pks);
599 let pke = match maybe_sch_ev {
600 Some(sch_ev) => {
601 key::KeyEvents::scheduled_event(sch_ev.into_scheduled_event())
602 }
603 None => key::KeyEvents::no_events(),
604 };
605 (pk, pke)
606 } else {
607 self.resolve_as_tap(key_index)
610 }
611 }
612 None => {
613 let (th_pks, maybe_sch_ev) =
615 self.new_pending_key(&profile, keymap_index, key_def.hold_output);
616 let pk = key::PressedKeyResult::Pending(th_pks);
617 let pke = match maybe_sch_ev {
618 Some(sch_ev) => key::KeyEvents::scheduled_event(sch_ev.into_scheduled_event()),
619 None => key::KeyEvents::no_events(),
620 };
621 (pk, pke)
622 }
623 }
624 }
625
626 fn update_pending_state(
627 &self,
628 pending_state: &mut Self::PendingKeyState,
629 keymap_index: u16,
630 context: &Self::Context,
631 Ref(key_index): Ref,
632 event: key::Event<Self::Event>,
633 ) -> (Option<key::NewPressedKey<R>>, key::KeyEvents<Self::Event>) {
634 let key_def = &self.keys[key_index as usize];
635 let profile = context.profile(key_def.profile);
636 let th_state = pending_state.handle_event(&profile, keymap_index, event);
637 if let Some(th_state) = th_state {
638 let Key { tap, hold, .. } = *key_def;
639 let new_key_ref = match th_state {
640 key::tap_hold::TapHoldState::Tap => tap,
641 key::tap_hold::TapHoldState::Hold => hold,
642 };
643
644 (
645 Some(key::NewPressedKey::key(new_key_ref)),
646 key::KeyEvents::no_events(),
647 )
648 } else {
649 (None, key::KeyEvents::no_events())
650 }
651 }
652
653 fn update_state(
654 &self,
655 _key_state: &mut Self::KeyState,
656 _ref: &Self::Ref,
657 _context: &Self::Context,
658 _keymap_index: u16,
659 _event: key::Event<Self::Event>,
660 ) -> key::KeyEvents<Self::Event> {
661 panic!() }
663
664 fn key_output(
665 &self,
666 _key_ref: &Self::Ref,
667 _key_state: &Self::KeyState,
668 ) -> Option<key::KeyOutput> {
669 panic!() }
671
672 fn pending_output(&self, pending_key_state: &Self::PendingKeyState) -> Option<key::KeyOutput> {
673 pending_key_state.output
674 }
675}
676
677#[cfg(test)]
678mod tests {
679 use super::*;
680
681 use crate::key::System as _;
682 use crate::keymap::KeymapContext;
683
684 const TAP: u8 = 1;
685 const HOLD: u8 = 2;
686 const KEYMAP_INDEX: u16 = 0;
687 const OTHER_INDEX: u16 = 1;
688
689 fn context_with(config: Config) -> Context {
690 Context::from_config(config)
691 }
692
693 fn default_context() -> Context {
694 context_with(Config::new())
695 }
696
697 fn config(
698 timeout: Option<u16>,
699 interrupt_response: InterruptResponse,
700 required_idle_time: Option<u16>,
701 ) -> Config {
702 Config {
703 default_profile: Profile {
704 timeout,
705 interrupt_response,
706 required_idle_time,
707 hold_trigger_positions: None,
708 quick_tap_ms: None,
709 pending_output: PendingOutput::NoOutput,
710 },
711 profiles: Slice::from_slice(&[]),
712 }
713 }
714
715 fn system() -> System<u8, [Key<u8>; 1]> {
716 System::new([Key::new(TAP, HOLD)])
717 }
718
719 fn timeout_event(keymap_index: u16) -> key::Event<Event> {
720 key::Event::key_event(keymap_index, Event::TapHoldTimeout)
721 }
722
723 fn press(keymap_index: u16) -> key::Event<Event> {
724 key::Event::Input(input::Event::Press { keymap_index })
725 }
726
727 fn release(keymap_index: u16) -> key::Event<Event> {
728 key::Event::Input(input::Event::Release { keymap_index })
729 }
730
731 #[test]
734 fn test_sizeof_ref() {
735 assert_eq!(1, core::mem::size_of::<Ref>());
736 }
737
738 #[test]
739 fn test_sizeof_event() {
740 assert_eq!(0, core::mem::size_of::<Event>());
741 }
742
743 #[test]
746 fn ignore_own_release_resolves_as_tap() {
747 let ctx = context_with(config(
749 Some(DEFAULT_TIMEOUT),
750 InterruptResponse::Ignore,
751 None,
752 ));
753 let mut pks = PendingKeyState::new();
754
755 let resolution = pks.handle_event(&ctx.profile(0), KEYMAP_INDEX, release(KEYMAP_INDEX));
757
758 assert_eq!(Some(TapHoldState::Tap), resolution);
760 }
761
762 #[test]
763 fn ignore_timeout_resolves_as_hold() {
764 let ctx = context_with(config(
766 Some(DEFAULT_TIMEOUT),
767 InterruptResponse::Ignore,
768 None,
769 ));
770 let mut pks = PendingKeyState::new();
771
772 let resolution =
774 pks.handle_event(&ctx.profile(0), KEYMAP_INDEX, timeout_event(KEYMAP_INDEX));
775
776 assert_eq!(Some(TapHoldState::Hold), resolution);
778 }
779
780 #[test]
781 fn ignore_other_press_does_not_resolve() {
782 let ctx = context_with(config(
784 Some(DEFAULT_TIMEOUT),
785 InterruptResponse::Ignore,
786 None,
787 ));
788 let mut pks = PendingKeyState::new();
789
790 let resolution = pks.handle_event(&ctx.profile(0), KEYMAP_INDEX, press(OTHER_INDEX));
792
793 assert_eq!(None, resolution);
795 }
796
797 #[test]
798 fn ignore_other_release_does_not_resolve() {
799 let ctx = context_with(config(
801 Some(DEFAULT_TIMEOUT),
802 InterruptResponse::Ignore,
803 None,
804 ));
805 let mut pks = PendingKeyState::new();
806
807 let resolution = pks.handle_event(&ctx.profile(0), KEYMAP_INDEX, release(OTHER_INDEX));
809
810 assert_eq!(None, resolution);
812 }
813
814 #[test]
817 fn hold_on_key_press_other_press_resolves_as_hold() {
818 let ctx = context_with(config(
820 Some(DEFAULT_TIMEOUT),
821 InterruptResponse::HoldOnKeyPress,
822 None,
823 ));
824 let mut pks = PendingKeyState::new();
825
826 let resolution = pks.handle_event(&ctx.profile(0), KEYMAP_INDEX, press(OTHER_INDEX));
828
829 assert_eq!(Some(TapHoldState::Hold), resolution);
831 }
832
833 #[test]
834 fn hold_on_key_press_own_release_resolves_as_tap() {
835 let ctx = context_with(config(
837 Some(DEFAULT_TIMEOUT),
838 InterruptResponse::HoldOnKeyPress,
839 None,
840 ));
841 let mut pks = PendingKeyState::new();
842
843 let resolution = pks.handle_event(&ctx.profile(0), KEYMAP_INDEX, release(KEYMAP_INDEX));
845
846 assert_eq!(Some(TapHoldState::Tap), resolution);
848 }
849
850 #[test]
851 fn hold_on_key_press_timeout_resolves_as_hold() {
852 let ctx = context_with(config(
854 Some(DEFAULT_TIMEOUT),
855 InterruptResponse::HoldOnKeyPress,
856 None,
857 ));
858 let mut pks = PendingKeyState::new();
859
860 let resolution =
862 pks.handle_event(&ctx.profile(0), KEYMAP_INDEX, timeout_event(KEYMAP_INDEX));
863
864 assert_eq!(Some(TapHoldState::Hold), resolution);
866 }
867
868 #[test]
869 fn hold_on_key_press_other_release_does_not_resolve() {
870 let ctx = context_with(config(
872 Some(DEFAULT_TIMEOUT),
873 InterruptResponse::HoldOnKeyPress,
874 None,
875 ));
876 let mut pks = PendingKeyState::new();
877
878 let resolution = pks.handle_event(&ctx.profile(0), KEYMAP_INDEX, release(OTHER_INDEX));
880
881 assert_eq!(None, resolution);
883 }
884
885 #[test]
888 fn hold_on_key_tap_own_release_resolves_as_tap() {
889 let ctx = context_with(config(
891 Some(DEFAULT_TIMEOUT),
892 InterruptResponse::HoldOnKeyTap,
893 None,
894 ));
895 let mut pks = PendingKeyState::new();
896
897 let resolution = pks.handle_event(&ctx.profile(0), KEYMAP_INDEX, release(KEYMAP_INDEX));
899
900 assert_eq!(Some(TapHoldState::Tap), resolution);
902 }
903
904 #[test]
905 fn hold_on_key_tap_timeout_resolves_as_hold() {
906 let ctx = context_with(config(
908 Some(DEFAULT_TIMEOUT),
909 InterruptResponse::HoldOnKeyTap,
910 None,
911 ));
912 let mut pks = PendingKeyState::new();
913
914 let resolution =
916 pks.handle_event(&ctx.profile(0), KEYMAP_INDEX, timeout_event(KEYMAP_INDEX));
917
918 assert_eq!(Some(TapHoldState::Hold), resolution);
920 }
921
922 #[test]
923 fn hold_on_key_tap_other_press_does_not_resolve() {
924 let ctx = context_with(config(
926 Some(DEFAULT_TIMEOUT),
927 InterruptResponse::HoldOnKeyTap,
928 None,
929 ));
930 let mut pks = PendingKeyState::new();
931
932 let resolution = pks.handle_event(&ctx.profile(0), KEYMAP_INDEX, press(OTHER_INDEX));
934
935 assert_eq!(None, resolution);
937 }
938
939 #[test]
940 fn hold_on_key_tap_other_tap_resolves_as_hold() {
941 let ctx = context_with(config(
943 Some(DEFAULT_TIMEOUT),
944 InterruptResponse::HoldOnKeyTap,
945 None,
946 ));
947 let mut pks = PendingKeyState::new();
948 let _ = pks.handle_event(&ctx.profile(0), KEYMAP_INDEX, press(OTHER_INDEX));
949
950 let resolution = pks.handle_event(&ctx.profile(0), KEYMAP_INDEX, release(OTHER_INDEX));
952
953 assert_eq!(Some(TapHoldState::Hold), resolution);
955 }
956
957 #[test]
958 fn hold_on_key_tap_unmatched_other_release_does_not_resolve() {
959 let ctx = context_with(config(
961 Some(DEFAULT_TIMEOUT),
962 InterruptResponse::HoldOnKeyTap,
963 None,
964 ));
965 let mut pks = PendingKeyState::new();
966
967 let resolution = pks.handle_event(&ctx.profile(0), KEYMAP_INDEX, release(OTHER_INDEX));
969
970 assert_eq!(None, resolution);
972 }
973
974 #[test]
975 fn hold_on_key_tap_release_of_different_key_does_not_resolve() {
976 let ctx = context_with(config(
978 Some(DEFAULT_TIMEOUT),
979 InterruptResponse::HoldOnKeyTap,
980 None,
981 ));
982 let mut pks = PendingKeyState::new();
983 let _ = pks.handle_event(&ctx.profile(0), KEYMAP_INDEX, press(OTHER_INDEX));
984
985 let resolution = pks.handle_event(&ctx.profile(0), KEYMAP_INDEX, release(2));
987
988 assert_eq!(None, resolution);
990 }
991
992 #[test]
995 fn new_pressed_key_is_pending_by_default() {
996 let system = system();
998 let ctx = default_context();
999
1000 let (pkr, _) = system.new_pressed_key(KEYMAP_INDEX, &ctx, Ref(0));
1002
1003 assert!(matches!(pkr, key::PressedKeyResult::Pending(_)));
1005 }
1006
1007 #[test]
1008 fn new_pressed_key_schedules_default_timeout() {
1009 let system = system();
1011 let ctx = default_context();
1012
1013 let (_, events) = system.new_pressed_key(KEYMAP_INDEX, &ctx, Ref(0));
1015
1016 let expected = key::KeyEvents::scheduled_event(key::ScheduledEvent::after(
1018 DEFAULT_TIMEOUT,
1019 timeout_event(KEYMAP_INDEX),
1020 ));
1021 assert_eq!(expected, events);
1022 }
1023
1024 #[test]
1025 fn new_pressed_key_with_no_timeout_schedules_nothing() {
1026 let system = system();
1028 let ctx = context_with(config(None, InterruptResponse::Ignore, None));
1029
1030 let (_, events) = system.new_pressed_key(KEYMAP_INDEX, &ctx, Ref(0));
1032
1033 assert_eq!(key::KeyEvents::no_events(), events);
1035 }
1036
1037 #[test]
1038 fn new_pressed_key_with_no_timeout_is_still_pending() {
1039 let system = system();
1041 let ctx = context_with(config(None, InterruptResponse::Ignore, None));
1042
1043 let (pkr, _) = system.new_pressed_key(KEYMAP_INDEX, &ctx, Ref(0));
1045
1046 assert!(matches!(pkr, key::PressedKeyResult::Pending(_)));
1048 }
1049
1050 #[test]
1051 fn new_pressed_key_resolves_as_tap_when_idle_time_insufficient() {
1052 let system = system();
1054 let mut ctx = context_with(config(
1055 Some(DEFAULT_TIMEOUT),
1056 InterruptResponse::Ignore,
1057 Some(100),
1058 ));
1059 ctx.idle_time_ms = 50;
1060
1061 let (pkr, _) = system.new_pressed_key(KEYMAP_INDEX, &ctx, Ref(0));
1063
1064 assert_eq!(
1066 key::PressedKeyResult::NewPressedKey(key::NewPressedKey::key(TAP)),
1067 pkr
1068 );
1069 }
1070
1071 #[test]
1072 fn new_pressed_key_insufficient_idle_emits_no_events() {
1073 let system = system();
1075 let mut ctx = context_with(config(
1076 Some(DEFAULT_TIMEOUT),
1077 InterruptResponse::Ignore,
1078 Some(100),
1079 ));
1080 ctx.idle_time_ms = 50;
1081
1082 let (_, events) = system.new_pressed_key(KEYMAP_INDEX, &ctx, Ref(0));
1084
1085 assert_eq!(key::KeyEvents::no_events(), events);
1087 }
1088
1089 #[test]
1090 fn new_pressed_key_is_pending_when_idle_time_sufficient() {
1091 let system = system();
1093 let mut ctx = context_with(config(
1094 Some(DEFAULT_TIMEOUT),
1095 InterruptResponse::Ignore,
1096 Some(100),
1097 ));
1098 ctx.idle_time_ms = 100;
1099
1100 let (pkr, _) = system.new_pressed_key(KEYMAP_INDEX, &ctx, Ref(0));
1102
1103 assert!(matches!(pkr, key::PressedKeyResult::Pending(_)));
1105 }
1106
1107 #[test]
1108 fn new_pressed_key_schedules_timeout_when_idle_time_sufficient() {
1109 let system = system();
1111 let mut ctx = context_with(config(
1112 Some(DEFAULT_TIMEOUT),
1113 InterruptResponse::Ignore,
1114 Some(100),
1115 ));
1116 ctx.idle_time_ms = 100;
1117
1118 let (_, events) = system.new_pressed_key(KEYMAP_INDEX, &ctx, Ref(0));
1120
1121 let expected = key::KeyEvents::scheduled_event(key::ScheduledEvent::after(
1123 DEFAULT_TIMEOUT,
1124 timeout_event(KEYMAP_INDEX),
1125 ));
1126 assert_eq!(expected, events);
1127 }
1128
1129 #[test]
1132 fn update_pending_own_release_resolves_to_tap_ref() {
1133 let system = system();
1135 let ctx = default_context();
1136 let mut pks = PendingKeyState::new();
1137
1138 let (resolved, _) = system.update_pending_state(
1140 &mut pks,
1141 KEYMAP_INDEX,
1142 &ctx,
1143 Ref(0),
1144 release(KEYMAP_INDEX),
1145 );
1146
1147 assert_eq!(Some(key::NewPressedKey::key(TAP)), resolved);
1149 }
1150
1151 #[test]
1152 fn update_pending_timeout_resolves_to_hold_ref() {
1153 let system = system();
1155 let ctx = default_context();
1156 let mut pks = PendingKeyState::new();
1157
1158 let (resolved, _) = system.update_pending_state(
1160 &mut pks,
1161 KEYMAP_INDEX,
1162 &ctx,
1163 Ref(0),
1164 timeout_event(KEYMAP_INDEX),
1165 );
1166
1167 assert_eq!(Some(key::NewPressedKey::key(HOLD)), resolved);
1169 }
1170
1171 #[test]
1172 fn update_pending_hold_on_press_interrupt_resolves_to_hold_ref() {
1173 let system = system();
1175 let ctx = context_with(config(
1176 Some(DEFAULT_TIMEOUT),
1177 InterruptResponse::HoldOnKeyPress,
1178 None,
1179 ));
1180 let mut pks = PendingKeyState::new();
1181
1182 let (resolved, _) =
1184 system.update_pending_state(&mut pks, KEYMAP_INDEX, &ctx, Ref(0), press(OTHER_INDEX));
1185
1186 assert_eq!(Some(key::NewPressedKey::key(HOLD)), resolved);
1188 }
1189
1190 #[test]
1191 fn update_pending_non_resolving_event_yields_none() {
1192 let system = system();
1194 let ctx = default_context();
1195 let mut pks = PendingKeyState::new();
1196
1197 let (resolved, _) =
1199 system.update_pending_state(&mut pks, KEYMAP_INDEX, &ctx, Ref(0), press(OTHER_INDEX));
1200
1201 assert_eq!(None, resolved);
1203 }
1204
1205 #[test]
1206 fn update_pending_resolution_emits_no_events() {
1207 let system = system();
1209 let ctx = default_context();
1210 let mut pks = PendingKeyState::new();
1211
1212 let (_, events) = system.update_pending_state(
1214 &mut pks,
1215 KEYMAP_INDEX,
1216 &ctx,
1217 Ref(0),
1218 release(KEYMAP_INDEX),
1219 );
1220
1221 assert_eq!(key::KeyEvents::no_events(), events);
1223 }
1224
1225 #[test]
1228 fn context_update_keymap_context_sets_idle_time() {
1229 let mut ctx = default_context();
1231 let km_ctx = KeymapContext {
1232 idle_time_ms: 42,
1233 ..KeymapContext::new()
1234 };
1235
1236 ctx.update_keymap_context(&km_ctx);
1238
1239 assert_eq!(42, ctx.idle_time_ms);
1241 }
1242
1243 #[test]
1244 fn context_reset_clears_idle_time() {
1245 let mut ctx = default_context();
1247 ctx.idle_time_ms = 99;
1248
1249 ctx.reset();
1251
1252 assert_eq!(0, ctx.idle_time_ms);
1254 }
1255
1256 #[test]
1257 fn context_reset_preserves_config() {
1258 let config = config(None, InterruptResponse::HoldOnKeyPress, Some(50));
1260 let mut ctx = context_with(config);
1261 ctx.idle_time_ms = 99;
1262
1263 ctx.reset();
1265
1266 assert_eq!(config, ctx.config);
1268 }
1269
1270 #[test]
1273 fn test_profile_zero_is_default_fields() {
1274 let config = Config {
1276 default_profile: Profile {
1277 timeout: Some(50),
1278 interrupt_response: InterruptResponse::HoldOnKeyPress,
1279 required_idle_time: Some(10),
1280 hold_trigger_positions: None,
1281 quick_tap_ms: None,
1282 pending_output: PendingOutput::NoOutput,
1283 },
1284 profiles: Slice::from_slice(&[]),
1285 };
1286
1287 let p = config.profile(0);
1289
1290 assert_eq!(p, config.default_profile);
1292 assert_eq!(p.timeout, Some(50));
1293 assert_eq!(p.interrupt_response, InterruptResponse::HoldOnKeyPress);
1294 assert_eq!(p.required_idle_time, Some(10));
1295 }
1296
1297 #[test]
1298 fn test_profile_extra_lookup() {
1299 let extra = Profile {
1301 timeout: Some(300),
1302 interrupt_response: InterruptResponse::HoldOnKeyTap,
1303 required_idle_time: None,
1304 hold_trigger_positions: None,
1305 quick_tap_ms: None,
1306 pending_output: PendingOutput::NoOutput,
1307 };
1308 let config = Config {
1309 default_profile: Profile {
1310 timeout: Some(200),
1311 interrupt_response: InterruptResponse::Ignore,
1312 required_idle_time: None,
1313 hold_trigger_positions: None,
1314 quick_tap_ms: None,
1315 pending_output: PendingOutput::NoOutput,
1316 },
1317 profiles: Slice::from_slice(&[extra]),
1318 };
1319
1320 assert_eq!(config.profile(1), extra);
1322 }
1323
1324 #[test]
1325 fn test_profile_out_of_range_falls_back_to_default() {
1326 let config = Config {
1328 default_profile: Profile {
1329 timeout: Some(200),
1330 interrupt_response: InterruptResponse::Ignore,
1331 required_idle_time: None,
1332 hold_trigger_positions: None,
1333 quick_tap_ms: None,
1334 pending_output: PendingOutput::NoOutput,
1335 },
1336 profiles: Slice::from_slice(&[]),
1337 };
1338
1339 assert_eq!(config.profile(9), config.default_profile);
1341 }
1342
1343 #[test]
1344 fn test_key_new_uses_profile_zero() {
1345 let key = Key::new(0u8, 1u8);
1347
1348 assert_eq!(key.profile, 0);
1350 }
1351
1352 #[test]
1353 fn test_key_with_profile_stores_index() {
1354 let key = Key::with_profile(0u8, 1u8, 2);
1356
1357 assert_eq!(key.profile, 2);
1359 }
1360
1361 #[test]
1364 fn allows_hold_trigger_when_unset_accepts_any_index() {
1365 let profile = Profile::new();
1367
1368 assert!(profile.allows_hold_trigger(0));
1370 assert!(profile.allows_hold_trigger(99));
1371 }
1372
1373 #[test]
1374 fn allows_hold_trigger_when_set_accepts_trigger_position() {
1375 let profile = Profile {
1377 hold_trigger_positions: Some(Slice::from_slice(&[2, 3])),
1378 ..Profile::new()
1379 };
1380
1381 assert!(profile.allows_hold_trigger(2));
1383 assert!(profile.allows_hold_trigger(3));
1384 }
1385
1386 #[test]
1387 fn allows_hold_trigger_when_set_rejects_non_trigger_position() {
1388 let profile = Profile {
1390 hold_trigger_positions: Some(Slice::from_slice(&[2, 3])),
1391 ..Profile::new()
1392 };
1393
1394 assert!(!profile.allows_hold_trigger(0));
1396 assert!(!profile.allows_hold_trigger(1));
1397 }
1398
1399 #[test]
1400 fn hold_on_key_press_non_trigger_press_does_not_resolve() {
1401 let ctx = context_with(Config {
1403 default_profile: Profile {
1404 timeout: Some(DEFAULT_TIMEOUT),
1405 interrupt_response: InterruptResponse::HoldOnKeyPress,
1406 required_idle_time: None,
1407 hold_trigger_positions: Some(Slice::from_slice(&[2])),
1408 quick_tap_ms: None,
1409 pending_output: PendingOutput::NoOutput,
1410 },
1411 profiles: Slice::from_slice(&[]),
1412 });
1413 let mut pks = PendingKeyState::new();
1414
1415 let resolution = pks.handle_event(&ctx.profile(0), KEYMAP_INDEX, press(OTHER_INDEX));
1417
1418 assert_eq!(None, resolution);
1420 }
1421
1422 #[test]
1423 fn hold_on_key_press_trigger_position_resolves_as_hold() {
1424 let ctx = context_with(Config {
1426 default_profile: Profile {
1427 timeout: Some(DEFAULT_TIMEOUT),
1428 interrupt_response: InterruptResponse::HoldOnKeyPress,
1429 required_idle_time: None,
1430 hold_trigger_positions: Some(Slice::from_slice(&[2])),
1431 quick_tap_ms: None,
1432 pending_output: PendingOutput::NoOutput,
1433 },
1434 profiles: Slice::from_slice(&[]),
1435 });
1436 let mut pks = PendingKeyState::new();
1437
1438 let resolution = pks.handle_event(&ctx.profile(0), KEYMAP_INDEX, press(2));
1440
1441 assert_eq!(Some(TapHoldState::Hold), resolution);
1443 }
1444
1445 #[test]
1446 fn hold_on_key_tap_non_trigger_tap_does_not_resolve() {
1447 let ctx = context_with(Config {
1449 default_profile: Profile {
1450 timeout: Some(DEFAULT_TIMEOUT),
1451 interrupt_response: InterruptResponse::HoldOnKeyTap,
1452 required_idle_time: None,
1453 hold_trigger_positions: Some(Slice::from_slice(&[2])),
1454 quick_tap_ms: None,
1455 pending_output: PendingOutput::NoOutput,
1456 },
1457 profiles: Slice::from_slice(&[]),
1458 });
1459 let mut pks = PendingKeyState::new();
1460 let _ = pks.handle_event(&ctx.profile(0), KEYMAP_INDEX, press(OTHER_INDEX));
1461
1462 let resolution = pks.handle_event(&ctx.profile(0), KEYMAP_INDEX, release(OTHER_INDEX));
1464
1465 assert_eq!(None, resolution);
1467 }
1468
1469 #[test]
1470 fn profile_zero_includes_hold_trigger_positions() {
1471 let triggers = Slice::from_slice(&[2, 3]);
1473 let config = Config {
1474 default_profile: Profile {
1475 timeout: Some(DEFAULT_TIMEOUT),
1476 interrupt_response: InterruptResponse::HoldOnKeyPress,
1477 required_idle_time: None,
1478 hold_trigger_positions: Some(triggers),
1479 quick_tap_ms: None,
1480 pending_output: PendingOutput::NoOutput,
1481 },
1482 profiles: Slice::from_slice(&[]),
1483 };
1484
1485 let p = config.profile(0);
1487
1488 assert_eq!(p.hold_trigger_positions, Some(triggers));
1490 }
1491
1492 #[test]
1493 fn profile_extra_includes_hold_trigger_positions() {
1494 let triggers = Slice::from_slice(&[5]);
1496 let extra = Profile {
1497 timeout: Some(300),
1498 interrupt_response: InterruptResponse::HoldOnKeyTap,
1499 required_idle_time: None,
1500 hold_trigger_positions: Some(triggers),
1501 quick_tap_ms: None,
1502 pending_output: PendingOutput::NoOutput,
1503 };
1504 let config = Config {
1505 default_profile: Profile::new(),
1506 profiles: Slice::from_slice(&[extra]),
1507 };
1508
1509 assert_eq!(config.profile(1).hold_trigger_positions, Some(triggers));
1511 }
1512
1513 #[test]
1516 fn is_quick_tap_when_repress_within_window() {
1517 let mut ctx = context_with(Config {
1519 default_profile: Profile {
1520 quick_tap_ms: Some(175),
1521 ..Profile::new()
1522 },
1523 profiles: Slice::from_slice(&[]),
1524 });
1525 ctx.update_keymap_context(&KeymapContext {
1526 time_ms: 100,
1527 idle_time_ms: 50,
1528 pressed_modifiers: key::KeyboardModifiers::NONE,
1529 recent_presses: {
1530 let mut presses = [(0, 0); keymap::MAX_RECENT_PRESSES];
1531 presses[0] = (KEYMAP_INDEX, 50);
1532 presses
1533 },
1534 recent_press_count: 1,
1535 });
1536
1537 assert!(ctx.is_quick_tap(&ctx.profile(0), KEYMAP_INDEX));
1539 }
1540
1541 #[test]
1542 fn is_quick_tap_false_after_window() {
1543 let mut ctx = context_with(Config {
1545 default_profile: Profile {
1546 quick_tap_ms: Some(100),
1547 ..Profile::new()
1548 },
1549 profiles: Slice::from_slice(&[]),
1550 });
1551 ctx.update_keymap_context(&KeymapContext {
1552 time_ms: 200,
1553 idle_time_ms: 150,
1554 pressed_modifiers: key::KeyboardModifiers::NONE,
1555 recent_presses: {
1556 let mut presses = [(0, 0); keymap::MAX_RECENT_PRESSES];
1557 presses[0] = (KEYMAP_INDEX, 50);
1558 presses
1559 },
1560 recent_press_count: 1,
1561 });
1562
1563 assert!(!ctx.is_quick_tap(&ctx.profile(0), KEYMAP_INDEX));
1565 }
1566
1567 #[test]
1568 fn is_quick_tap_false_for_other_keymap_index() {
1569 let mut ctx = context_with(Config {
1571 default_profile: Profile {
1572 quick_tap_ms: Some(175),
1573 ..Profile::new()
1574 },
1575 profiles: Slice::from_slice(&[]),
1576 });
1577 ctx.update_keymap_context(&KeymapContext {
1578 time_ms: 100,
1579 idle_time_ms: 50,
1580 pressed_modifiers: key::KeyboardModifiers::NONE,
1581 recent_presses: {
1582 let mut presses = [(0, 0); keymap::MAX_RECENT_PRESSES];
1583 presses[0] = (OTHER_INDEX, 50);
1584 presses
1585 },
1586 recent_press_count: 1,
1587 });
1588
1589 assert!(!ctx.is_quick_tap(&ctx.profile(0), KEYMAP_INDEX));
1591 }
1592
1593 #[test]
1594 fn is_quick_tap_disabled_when_unset() {
1595 let mut ctx = default_context();
1597 ctx.update_keymap_context(&KeymapContext {
1598 time_ms: 100,
1599 idle_time_ms: 50,
1600 pressed_modifiers: key::KeyboardModifiers::NONE,
1601 recent_presses: {
1602 let mut presses = [(0, 0); keymap::MAX_RECENT_PRESSES];
1603 presses[0] = (KEYMAP_INDEX, 50);
1604 presses
1605 },
1606 recent_press_count: 1,
1607 });
1608
1609 assert!(!ctx.is_quick_tap(&ctx.profile(0), KEYMAP_INDEX));
1611 }
1612
1613 #[test]
1614 fn new_pressed_key_quick_tap_resolves_as_tap() {
1615 let mut ctx = context_with(Config {
1617 default_profile: Profile {
1618 quick_tap_ms: Some(175),
1619 ..Profile::new()
1620 },
1621 profiles: Slice::from_slice(&[]),
1622 });
1623 ctx.update_keymap_context(&KeymapContext {
1624 time_ms: 100,
1625 idle_time_ms: 999,
1626 pressed_modifiers: key::KeyboardModifiers::NONE,
1627 recent_presses: {
1628 let mut presses = [(0, 0); keymap::MAX_RECENT_PRESSES];
1629 presses[0] = (KEYMAP_INDEX, 50);
1630 presses
1631 },
1632 recent_press_count: 1,
1633 });
1634 let sys = system();
1635
1636 let (pkr, _) = sys.new_pressed_key(KEYMAP_INDEX, &ctx, Ref(0));
1638
1639 assert!(matches!(
1641 pkr,
1642 key::PressedKeyResult::NewPressedKey(key::NewPressedKey::Key(TAP))
1643 ));
1644 }
1645
1646 #[test]
1647 fn profile_zero_includes_quick_tap_ms() {
1648 let config = Config {
1650 default_profile: Profile {
1651 quick_tap_ms: Some(175),
1652 ..Profile::new()
1653 },
1654 profiles: Slice::from_slice(&[]),
1655 };
1656
1657 assert_eq!(config.profile(0).quick_tap_ms, Some(175));
1659 }
1660}