diff --git a/keyberon/src/layout.rs b/keyberon/src/layout.rs index 93bf0d6a0..95b5935a8 100644 --- a/keyberon/src/layout.rs +++ b/keyberon/src/layout.rs @@ -2002,7 +2002,14 @@ impl<'a, const C: usize, const R: usize, T: 'a + Copy + std::fmt::Debug> Layout< // Disregard any presses that are actually still in the queue and // unresolved. && !self.queue.iter().any(|q| { - q.since == prior.ticks_since_occurrence + // Events drained out of the chordsv2 queue are ticked + // by both tick_chv2 and the layout queue tick on the drain cycle, + // so a queued event's `since` can run ahead of its history entry's + // age. + // + // It is possibly troublesome to fix that issue so give some leeway + // in the comparison. + q.since.abs_diff(prior.ticks_since_occurrence) <= 2 && match q.event { Event::Press(0, j) => j == prior.event.1, _ => false diff --git a/src/tests/sim_tests/chord_sim_tests.rs b/src/tests/sim_tests/chord_sim_tests.rs index e910d89cf..b72726f87 100644 --- a/src/tests/sim_tests/chord_sim_tests.rs +++ b/src/tests/sim_tests/chord_sim_tests.rs @@ -532,3 +532,33 @@ d:t u:t t:10 result ); } + +#[test] +fn prior_idle_chord_participants() { + // Tests a past bug where chordv2->tap-hold singles would not be evaluated correctly for + // tap-hold require-prior-idle. + let result = simulate( + " + (defcfg + process-unmapped-keys yes + concurrent-tap-hold yes + tap-hold-require-prior-idle 150 + ) + (defsrc) + (defchordsv2 + (j c) M-c 75 first-release () + ) + (deflayermap (main) + j (tap-hold 200 300 j rmet) + k (tap-hold 200 300 k rsft) + ) + ", + "d:j t:40 d:k t:320 d:spc t:40 u:spc t:40 u:k t:20 u:j t:200", + ) + .to_ascii(); + assert_eq!( + "t:298ms dn:RGui t:40ms dn:RShift t:23ms dn:Space \ + t:39ms up:Space t:40ms up:RShift t:20ms up:RGui", + result + ); +}