Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion rmk/src/keyboard.rs
Original file line number Diff line number Diff line change
Expand Up @@ -450,7 +450,7 @@ impl<'a> Keyboard<'a> {
self.held_buffer.push(HeldKey::new(
event,
*key_action,
KeyState::ProcessedButReleaseNotReportedYet(action),
KeyState::FlowTapped(action),
now,
time_out,
));
Expand Down
5 changes: 5 additions & 0 deletions rmk/src/keyboard/held_buffer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,11 @@ pub enum KeyState {
/// remains in the buffer to allow hold_after_tap continuation.
EarlyFired(MorsePattern),

/// After flow-tap resolved the key as a tap: the tap action's press HID report
/// is sent and held while the key is physically held. On release the action is
/// released and the key is kept as `EarlyFired` so hold_after_tap can continue.
FlowTapped(Action),

/// The corresponding action is already executed (so the Pressed HID report is sent),
/// but the release HID report is not sent yet (will be sent only when the corresponding
/// key is really released).
Expand Down
20 changes: 20 additions & 0 deletions rmk/src/keyboard/morse.rs
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,26 @@ impl<'a> Keyboard<'a> {
debug!("[morse] Releasing morse key: {:?}", event);
self.process_key_action_normal(action, event).await;
}
KeyState::FlowTapped(action) => {
// Flow-tap fired the tap action and is holding it down; release it now.
debug!("[morse] Releasing flow-tapped morse key: {:?}", event);
self.process_key_action_normal(action, event).await;
// If the key has a hold-after-tap action, keep it in the buffer as if it
// had been early-fired so a re-press within the gap timeout continues into
// hold-after-tap (the tap-then-hold repeat). Without this a flow-tapped
// tap leaves no trace and the next press-and-hold resolves as a fresh hold.
if Self::action_from_pattern(self.keymap, key_action, TAP.followed_by_hold()) != Action::No {
let now = Instant::now();
let timeout = Self::morse_timeout(self.keymap, key_action, false);
if let Some(k) = self.held_buffer.find_pos_mut(event.pos) {
k.state = KeyState::EarlyFired(TAP);
k.press_time = now;
k.timeout_time = now + timeout;
}
} else {
let _ = self.held_buffer.remove(event.pos);
}
}
_ => {}
};
}
Expand Down
40 changes: 40 additions & 0 deletions rmk/tests/keyboard_morse_tap_dance_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -650,3 +650,43 @@ fn test_flow_tap_after_early_fire_does_not_jam() {
]
};
}

/// Regression test: a tap resolved by flow-tap (e.g. right after a burst of typing) must
/// still allow a hold-after-tap continuation, so press-and-hold after that tap repeats the
/// tap action instead of resolving as a fresh hold.
///
/// Before the fix, flow-tap fired the tap and removed the key from the held buffer on
/// release, leaving no trace. A subsequent press-and-hold was therefore a brand-new press
/// and resolved to the hold action (RShift here) instead of hold-after-tap (Backspace). The
/// early-fire path did not have this problem because it leaves an EarlyFired breadcrumb; the
/// fix makes flow-tapped taps leave the same breadcrumb when a hold-after-tap action exists.
#[test]
fn test_flow_tapped_tap_then_hold_after_tap() {
key_sequence_test! {
keyboard: create_flow_tap_early_fire_keyboard(),
sequence: [
// Type A, then tap td!(0) within prior_idle_time so the tap is resolved by flow-tap.
[0, 1, true, 200],
[0, 1, false, 30],
[0, 0, true, 50],
[0, 0, false, 30],
// Re-press td!(0) within the gap timeout and hold past the hold timeout.
// With the fix this continues into hold-after-tap (Backspace held); before it
// resolved as a fresh hold (RShift).
[0, 0, true, 150],
[0, 0, false, 400],
],
expected_reports: [
// Type A.
[0, [kc_to_u8!(A), 0, 0, 0, 0, 0]],
[0, [0, 0, 0, 0, 0, 0]],
// Flow-tapped tap: Backspace press (held) then release on key-up.
[0, [kc_to_u8!(Backspace), 0, 0, 0, 0, 0]],
[0, [0, 0, 0, 0, 0, 0]],
// Re-press held: hold-after-tap fires Backspace (held), released on key-up.
// RShift would mean the continuation breadcrumb was lost.
[0, [kc_to_u8!(Backspace), 0, 0, 0, 0, 0]],
[0, [0, 0, 0, 0, 0, 0]]
]
};
}