diff --git a/features/keymap/key/tap_hold-config-pending_output.feature b/features/keymap/key/tap_hold-config-pending_output.feature new file mode 100644 index 000000000..b48962e39 --- /dev/null +++ b/features/keymap/key/tap_hold-config-pending_output.feature @@ -0,0 +1,91 @@ +Feature: TapHold Key (configure pending_output) + + The `pending_output` config for tap-hold keys controls speculative HID + while the tap-vs-hold decision is still pending. + + Default `NoOutput` produces no output while pending. + No HID is emitted until timeout, interrupt, or release settles tap vs hold. + `Hold` emits the hold binding's HID while still pending, then keeps it + or retracts it when tap vs hold settles. Decision logic is unchanged. + Only the timing of hold appearance changes. + + This matches FAK `eager_decision = 'hold'`, ZMK + `hold-while-undecided`, and QMK Speculative Hold. + + For examples of this key in other smart keyboard firmware, see e.g.: + + - [FAK's eager_decision](https://github.com/semickolon/fak) + + - [ZMK's hold-while-undecided](https://zmk.dev/docs/keymaps/behaviors/hold-tap#hold-while-undecided) + + - [QMK's Speculative Hold](https://docs.qmk.fm/tap_hold#speculative-hold) + + Background: + + Given a keymap.ncl: + """ + let K = import "keys.ncl" in + { + config.tap_hold.pending_output = "Hold", + keys = [ + K.A & K.hold K.LeftCtrl, + K.B + ] + } + """ + + Example: Hold with timeout shows mod from first tick and stays hold + + When the keymap registers the following input + """ + [ + press (K.A & K.hold K.LeftCtrl), + wait 1, + ] + """ + Then the HID keyboard report should equal + """ + { modifiers = { left_ctrl = true } } + """ + + Example: Hold with quick release retracts mod and ends as tap + + When the keymap registers the following input + """ + [ + press (K.A & K.hold K.LeftCtrl), + wait 1, + release (K.A & K.hold K.LeftCtrl), + ] + """ + Then the HID keyboard report should equal + """ + { key_codes = [K.A] } + """ + + Example: Hold with HoldOnKeyPress interrupt shows mod already down when other key taps + + Given a keymap.ncl: + """ + let K = import "keys.ncl" in + { + config.tap_hold.pending_output = "Hold", + config.tap_hold.interrupt_response = "HoldOnKeyPress", + keys = [ + K.A & K.hold K.LeftCtrl, + K.B + ] + } + """ + When the keymap registers the following input + """ + [ + press (K.A & K.hold K.LeftCtrl), + wait 1, + tap K.B, + ] + """ + Then the HID keyboard report should equal + """ + { modifiers = { left_ctrl = true }, key_codes = [K.B] } + """ diff --git a/ncl/key_system/keymap-codegen.ncl b/ncl/key_system/keymap-codegen.ncl index 876dfb68d..fbaadc1bf 100644 --- a/ncl/key_system/keymap-codegen.ncl +++ b/ncl/key_system/keymap-codegen.ncl @@ -575,6 +575,19 @@ Ref::%{f.variant}(key_ref) => { ) ++ "\n(_, _) => None,", + pending_output_arms = + if pending_systems == [] then + "_ => None," + else + ( + pending_systems + |> std.array.map (fun f => + "PendingKeyState::%{f.variant}(pks) => self.%{f.field}.pending_output(pks)," + ) + |> join + ) + ++ "\n_ => None,", + key_state_from_family = systems |> std.array.map (fun f => @@ -861,6 +874,15 @@ pub mod key_system { %{key_output_arms} } } + + fn pending_output( + &self, + pending_key_state: &Self::PendingKeyState, + ) -> Option { + match pending_key_state { +%{pending_output_arms} + } + } } } "%, diff --git a/ncl/keymap-ncl-to-json.ncl b/ncl/keymap-ncl-to-json.ncl index d725f5c59..400343cd1 100644 --- a/ncl/keymap-ncl-to-json.ncl +++ b/ncl/keymap-ncl-to-json.ncl @@ -699,6 +699,12 @@ else {} ) + & ( + if std.record.has_field "pending_output" th_config then + { pending_output = th_config.pending_output } + else + {} + ) in let profiles_array = th_profile_names diff --git a/ncl/smart_keys/tap_hold/keymap-codegen.ncl b/ncl/smart_keys/tap_hold/keymap-codegen.ncl index 2414f4714..c225d36da 100644 --- a/ncl/smart_keys/tap_hold/keymap-codegen.ncl +++ b/ncl/smart_keys/tap_hold/keymap-codegen.ncl @@ -87,6 +87,71 @@ }"%, }, }, + + check_profile_with_pending_output = + let rust_expr = + smart_keymap.tap_hold.profile_rust_expr { + pending_output = "Hold", + } + in + { + check_rust_expr = { + actual = rust_expr, + expected = m%"smart_keymap::key::tap_hold::Profile { + pending_output: smart_keymap::key::tap_hold::PendingOutput::Hold, + ..smart_keymap::key::tap_hold::Profile::new() + }"%, + }, + }, + + check_speculative_hold_output = { + check_left_ctrl_modifiers = + let cv = + smart_keymap.keyboard.key.codegen_values { modifiers = 1 } + in + { + actual = smart_keymap.tap_hold.speculative_hold_output cv, + expected = + 'Some { + json = { key_modifiers = 1 }, + rust_expr = "smart_keymap::key::KeyOutput::from_key_modifiers(smart_keymap::key::KeyboardModifiers::from_byte(1))", + }, + }, + + check_left_ctrl_keycode = + let cv = + smart_keymap.keyboard.key.codegen_values { key_code = 224 } + in + { + actual = smart_keymap.tap_hold.speculative_hold_output cv, + expected = + 'Some { + json = { key_modifiers = 1 }, + rust_expr = "smart_keymap::key::KeyOutput::from_key_modifiers(smart_keymap::key::KeyboardModifiers::from_byte(1))", + }, + }, + + check_left_gui_modifiers_refused = + let cv = + smart_keymap.keyboard.key.codegen_values { modifiers = 8 } + in + smart_keymap.tap_hold.speculative_hold_output cv == 'None, + + check_left_gui_keycode_refused = + let cv = + smart_keymap.keyboard.key.codegen_values { key_code = 227 } + in + smart_keymap.tap_hold.speculative_hold_output cv == 'None, + + check_non_keyboard_refused = + let cv = + smart_keymap.tap_hold.key.codegen_values { + tap = { key_code = 4 }, + hold = { key_code = 224 }, + } + in + smart_keymap.tap_hold.speculative_hold_output cv == 'None, + }, }, }, @@ -102,8 +167,97 @@ ] ), + TapHoldPendingOutputJson = + std.contract.from_validator ( + validators.is_elem_of [ + "NoOutput", + "Hold", + ] + ), + module = "smart_keymap::key::tap_hold", + # HID of a keyboard hold leaf, or 'None if it must not be speculated + # (non-keyboard, or LeftGUI / RightGUI). + speculative_hold_output = fun hold_cv => + if std.record.has_field "module" hold_cv + && hold_cv.module == "smart_keymap::key::keyboard" then + let json = hold_cv.json in + let key_code = + if std.record.has_field "key_code" json then + json.key_code + else + 0 + in + let modifiers = + if std.record.has_field "modifiers" json then + json.modifiers + else + 0 + in + let is_odd = fun n => n - 2 * std.number.floor (n / 2) == 1 in + let bit_set = fun value bit => is_odd (std.number.floor (value / bit)) in + let bit_or8 = fun a b => + std.array.fold_left + (fun acc i => + let bit = std.number.pow 2 i in + if bit_set a bit || bit_set b bit then + acc + bit + else + acc + ) + 0 + [0, 1, 2, 3, 4, 5, 6, 7] + in + let kc_mod = + key_code + |> match { + 224 => 1, + 225 => 2, + 226 => 4, + 227 => 8, + 228 => 16, + 229 => 32, + 230 => 64, + 231 => 128, + _ => 0, + } + in + let remaining_kc = if kc_mod != 0 then 0 else key_code in + let merged = bit_or8 modifiers kc_mod in + let has_gui = bit_set merged 8 || bit_set merged 128 in + if has_gui || (remaining_kc == 0 && merged == 0) then + 'None + else + let rust_expr = + if remaining_kc == 0 then + "smart_keymap::key::KeyOutput::from_key_modifiers(smart_keymap::key::KeyboardModifiers::from_byte(%{std.to_string merged}))" + else if merged == 0 then + "smart_keymap::key::KeyOutput::from_key_code(%{std.to_string remaining_kc})" + else + "smart_keymap::key::KeyOutput::from_key_code_with_modifiers(%{std.to_string remaining_kc}, smart_keymap::key::KeyboardModifiers::from_byte(%{std.to_string merged}))" + in + let json = + ( + if remaining_kc == 0 then + {} + else + { key_code = { Keyboard = remaining_kc } } + ) + & ( + if merged == 0 then + {} + else + { key_modifiers = merged } + ) + in + 'Some { + include json, + include rust_expr, + } + else + 'None, + hold_trigger_positions_expr = fun positions => let idxs = positions @@ -156,6 +310,14 @@ } else {} + ) + & ( + if std.record.has_field "pending_output" c then + { + pending_output = "%{module}::PendingOutput::%{c.pending_output}", + } + else + {} ), profile_rust_expr = fun c => @@ -257,18 +419,35 @@ else { profile = profile_id } in + let hold_output = smart_keymap.tap_hold.speculative_hold_output hold_cv in + let hold_output_json = + hold_output + |> match { + 'None => {}, + 'Some { json, .. } => { hold_output = json }, + } + in + let hold_output_rust = + hold_output + |> match { + 'None => "None", + 'Some { rust_expr, .. } => "Some(%{rust_expr})", + } + in let new_key = { json = { tap = tap_ref.json, hold = hold_ref.json, } - & profile_json, + & profile_json + & hold_output_json, rust_expr = m%" %{module}::Key { tap: %{tap_ref.rust_expr}, hold: %{hold_ref.rust_expr}, profile: %{std.to_string profile_id}, + hold_output: %{hold_output_rust}, } "%, } @@ -297,6 +476,7 @@ required_idle_time | optional | Number, hold_trigger_key_positions | optional | Array Number, quick_tap_ms | optional | Number, + pending_output | optional | TapHoldPendingOutputJson, }, # Lowered JSON form: nested default_profile + profiles array. diff --git a/ncl/smart_keys/tap_hold/keymap-ncl-to-json.ncl b/ncl/smart_keys/tap_hold/keymap-ncl-to-json.ncl index fc365ef03..c474c549b 100644 --- a/ncl/smart_keys/tap_hold/keymap-ncl-to-json.ncl +++ b/ncl/smart_keys/tap_hold/keymap-ncl-to-json.ncl @@ -63,6 +63,14 @@ ] ), + TapHoldPendingOutput = + std.contract.from_validator ( + validators.is_elem_of [ + "NoOutput", + "Hold", + ] + ), + # One behavior profile. Profile 0 is Config's default (flat on config.tap_hold); # extras live under config.tap_hold.profiles. Profile = { @@ -80,6 +88,7 @@ hold_trigger_key_positions | optional | Array Number, # Re-press of same key within this many ms forces tap (ZMK quick-tap-ms). quick_tap_ms | optional | Number, + pending_output | optional | TapHoldPendingOutput, }, # Authoring keeps default-profile knobs flat on config.tap_hold; @@ -99,6 +108,7 @@ hold_trigger_key_positions | optional | Array Number, # Re-press of same key within this many ms forces tap (ZMK quick-tap-ms). quick_tap_ms | optional | Number, + pending_output | optional | TapHoldPendingOutput, # Authoring: name → profile record. Lowered to a JSON array (indices 1..). profiles | optional | { _ | Profile }, }, diff --git a/smart-keymap-core/src/key.rs b/smart-keymap-core/src/key.rs index e37ab4ed3..008756476 100644 --- a/smart-keymap-core/src/key.rs +++ b/smart-keymap-core/src/key.rs @@ -287,6 +287,15 @@ pub trait System: Debug { fn key_output(&self, _ref: &Self::Ref, _key_state: &Self::KeyState) -> Option { None } + + /// HID while a pending session is live. + /// + /// Default: no output until the pending key resolves. + /// [`crate::keymap::Keymap`] includes this in pressed-key HID + /// and aggregated modifiers while a pending session is live. + fn pending_output(&self, _pending_key_state: &Self::PendingKeyState) -> Option { + None + } } /// Used to provide state that may affect behaviour when pressing the key. diff --git a/smart-keymap-core/src/key/tap_hold.rs b/smart-keymap-core/src/key/tap_hold.rs index 1d5b111e8..703d0f773 100644 --- a/smart-keymap-core/src/key/tap_hold.rs +++ b/smart-keymap-core/src/key/tap_hold.rs @@ -76,6 +76,15 @@ pub struct Profile { /// scoped to re-presses of the same keymap index. #[serde(default)] pub quick_tap_ms: Option, + + /// Speculative HID while this tap-hold is pending. + /// + /// - [`PendingOutput::NoOutput`] (default): no HID until tap vs hold settles. + /// - [`PendingOutput::Hold`]: emit the hold binding's HID while still pending + /// (ZMK `hold-while-undecided` / QMK Speculative Hold / + /// FAK `eager_decision = 'hold`). + #[serde(default = "default_pending_output")] + pub pending_output: PendingOutput, } impl Profile { @@ -123,6 +132,16 @@ pub struct Key { /// Behavior profile index: `0` = [`Config::default_profile`]; `1..` = [`Config::profiles`]. #[serde(default)] pub profile: u8, + /// HID of the hold binding when it is a keyboard key that is safe to emit + /// while pending. + /// + /// Nickel fills this in keymap codegen + /// (`ncl/smart_keys/tap_hold/keymap-codegen.ncl`) + /// from the hold leaf. + /// LeftGUI / RightGUI and non-keyboard holds are `None`. + /// Used when [`Profile::pending_output`] is [`PendingOutput::Hold`]. + #[serde(default)] + pub hold_output: Option, } impl Key { @@ -132,12 +151,18 @@ impl Key { tap, hold, profile: 0, + hold_output: None, } } /// Constructs a tap-hold key that uses the given behavior profile index. pub const fn with_profile(tap: R, hold: R, profile: u8) -> Key { - Key { tap, hold, profile } + Key { + tap, + hold, + profile, + hold_output: None, + } } } @@ -148,10 +173,20 @@ impl Default for Key { tap: R::default(), hold: R::default(), profile: 0, + hold_output: None, } } } +/// Speculative output while tap-hold is pending. +#[derive(Deserialize, Debug, Clone, Copy, PartialEq)] +pub enum PendingOutput { + /// No speculative output (default). + NoOutput, + /// Emit hold binding while still pending. + Hold, +} + /// How the tap hold key should respond to interruptions (input events from other keys). #[derive(Deserialize, Debug, Clone, Copy, PartialEq)] pub enum InterruptResponse { @@ -199,6 +234,13 @@ fn default_interrupt_response() -> InterruptResponse { DEFAULT_INTERRUPT_RESPONSE } +/// The default pending output. +pub const DEFAULT_PENDING_OUTPUT: PendingOutput = PendingOutput::NoOutput; + +fn default_pending_output() -> PendingOutput { + DEFAULT_PENDING_OUTPUT +} + fn default_profile_value() -> Profile { DEFAULT_PROFILE } @@ -210,6 +252,7 @@ pub const DEFAULT_PROFILE: Profile = Profile { required_idle_time: None, hold_trigger_positions: None, quick_tap_ms: None, + pending_output: DEFAULT_PENDING_OUTPUT, }; /// Default tap hold config. @@ -340,6 +383,8 @@ pub enum Event { pub struct PendingKeyState { // For tracking 'tap' interruptions other_pressed_keymap_index: Option, + /// Speculative HID while undecided ([`PendingOutput::Hold`]). + output: Option, } impl PendingKeyState { @@ -347,6 +392,15 @@ impl PendingKeyState { fn new() -> PendingKeyState { PendingKeyState { other_pressed_keymap_index: None, + output: None, + } + } + + /// Constructs with speculative HID. + fn new_with_output(output: Option) -> Self { + Self { + other_pressed_keymap_index: None, + output, } } @@ -474,8 +528,12 @@ impl>> System { &self, profile: &Profile, keymap_index: u16, + hold_output: Option, ) -> (PendingKeyState, Option>) { - let pending = PendingKeyState::new(); + let pending = match profile.pending_output { + PendingOutput::Hold => PendingKeyState::new_with_output(hold_output), + PendingOutput::NoOutput => PendingKeyState::new(), + }; let scheduled = profile.timeout.map(|timeout| { key::ScheduledEvent::after( timeout, @@ -535,7 +593,8 @@ impl>> key::System { if context.idle_time_ms >= required_idle_time as u32 { // Keymap has been idle long enough; use pending tap-hold key state. - let (th_pks, maybe_sch_ev) = self.new_pending_key(&profile, keymap_index); + let (th_pks, maybe_sch_ev) = + self.new_pending_key(&profile, keymap_index, key_def.hold_output); let pk = key::PressedKeyResult::Pending(th_pks); let pke = match maybe_sch_ev { Some(sch_ev) => { @@ -552,7 +611,8 @@ impl>> key::System { // Idle time not considered. Use pending tap-hold key state. - let (th_pks, maybe_sch_ev) = self.new_pending_key(&profile, keymap_index); + let (th_pks, maybe_sch_ev) = + self.new_pending_key(&profile, keymap_index, key_def.hold_output); let pk = key::PressedKeyResult::Pending(th_pks); let pke = match maybe_sch_ev { Some(sch_ev) => key::KeyEvents::scheduled_event(sch_ev.into_scheduled_event()), @@ -608,6 +668,10 @@ impl>> key::System Option { panic!() // tap_hold has no key state } + + fn pending_output(&self, pending_key_state: &Self::PendingKeyState) -> Option { + pending_key_state.output + } } #[cfg(test)] @@ -642,6 +706,7 @@ mod tests { required_idle_time, hold_trigger_positions: None, quick_tap_ms: None, + pending_output: PendingOutput::NoOutput, }, profiles: Slice::from_slice(&[]), } @@ -1214,6 +1279,7 @@ mod tests { required_idle_time: Some(10), hold_trigger_positions: None, quick_tap_ms: None, + pending_output: PendingOutput::NoOutput, }, profiles: Slice::from_slice(&[]), }; @@ -1237,6 +1303,7 @@ mod tests { required_idle_time: None, hold_trigger_positions: None, quick_tap_ms: None, + pending_output: PendingOutput::NoOutput, }; let config = Config { default_profile: Profile { @@ -1245,6 +1312,7 @@ mod tests { required_idle_time: None, hold_trigger_positions: None, quick_tap_ms: None, + pending_output: PendingOutput::NoOutput, }, profiles: Slice::from_slice(&[extra]), }; @@ -1263,6 +1331,7 @@ mod tests { required_idle_time: None, hold_trigger_positions: None, quick_tap_ms: None, + pending_output: PendingOutput::NoOutput, }, profiles: Slice::from_slice(&[]), }; @@ -1337,6 +1406,7 @@ mod tests { required_idle_time: None, hold_trigger_positions: Some(Slice::from_slice(&[2])), quick_tap_ms: None, + pending_output: PendingOutput::NoOutput, }, profiles: Slice::from_slice(&[]), }); @@ -1359,6 +1429,7 @@ mod tests { required_idle_time: None, hold_trigger_positions: Some(Slice::from_slice(&[2])), quick_tap_ms: None, + pending_output: PendingOutput::NoOutput, }, profiles: Slice::from_slice(&[]), }); @@ -1381,6 +1452,7 @@ mod tests { required_idle_time: None, hold_trigger_positions: Some(Slice::from_slice(&[2])), quick_tap_ms: None, + pending_output: PendingOutput::NoOutput, }, profiles: Slice::from_slice(&[]), }); @@ -1405,6 +1477,7 @@ mod tests { required_idle_time: None, hold_trigger_positions: Some(triggers), quick_tap_ms: None, + pending_output: PendingOutput::NoOutput, }, profiles: Slice::from_slice(&[]), }; @@ -1426,6 +1499,7 @@ mod tests { required_idle_time: None, hold_trigger_positions: Some(triggers), quick_tap_ms: None, + pending_output: PendingOutput::NoOutput, }; let config = Config { default_profile: Profile::new(), diff --git a/smart-keymap-core/src/keymap.rs b/smart-keymap-core/src/keymap.rs index b60695892..394f31404 100644 --- a/smart-keymap-core/src/keymap.rs +++ b/smart-keymap-core/src/keymap.rs @@ -869,8 +869,12 @@ impl< } /// Aggregate keyboard modifiers from already-pressed inputs (no suppress). + /// + /// Includes [`key::System::pending_output`] while a pending session is live + /// so the next key sees the speculated modifiers. fn aggregate_pressed_modifiers(&self) -> key::KeyboardModifiers { - self.pressed_inputs + let base = self + .pressed_inputs .iter() .filter_map(|pi| match pi { input::PressedInput::Key(input::PressedKey { @@ -880,7 +884,13 @@ impl< }) .fold(key::KeyboardModifiers::NONE, |acc, ko| { acc.union(&ko.key_modifiers()) - }) + }); + let pending_mod = self + .pending_state + .as_ref() + .and_then(|pending| self.key_system.pending_output(&pending.pending_key_state)) + .map_or(key::KeyboardModifiers::NONE, |ko| ko.key_modifiers()); + base.union(&pending_mod) } fn push_keymap_context(&mut self) { @@ -924,9 +934,11 @@ impl< } /// Returns the the pressed key outputs. + /// + /// Includes [`key::System::pending_output`] while a pending session is live. pub fn pressed_keys(&self) -> heapless::Vec { let suppress = self.context.suppressed_modifiers(); - let pressed_key_codes = self.pressed_inputs.iter().filter_map(|pi| { + let resolved = self.pressed_inputs.iter().filter_map(|pi| { let ko = match pi { input::PressedInput::Key(input::PressedKey { key_ref, key_state, .. @@ -934,15 +946,15 @@ impl< &input::PressedInput::Virtual(key_output) => key_output, }; let ko = ko.without_modifiers(suppress); - // Drop pure-mod outputs that are fully suppressed. - if ko == key::KeyOutput::NO_OUTPUT { - None - } else { - Some(ko) - } + (ko != key::KeyOutput::NO_OUTPUT).then_some(ko) }); - - pressed_key_codes.collect() + let pending = self + .pending_state + .as_ref() + .and_then(|pending| self.key_system.pending_output(&pending.pending_key_state)) + .map(|ko| ko.without_modifiers(suppress)) + .filter(|ko| *ko != key::KeyOutput::NO_OUTPUT); + resolved.chain(pending).collect() } fn tick_by(&mut self, delta_ms: u32) { diff --git a/smart-keymap-full-system-std/tests/keymap_full_system.rs b/smart-keymap-full-system-std/tests/keymap_full_system.rs index c2addd443..76ada2bf5 100644 --- a/smart-keymap-full-system-std/tests/keymap_full_system.rs +++ b/smart-keymap-full-system-std/tests/keymap_full_system.rs @@ -49,6 +49,7 @@ fn tap_hold_interrupt_keymap( tap: key_system::Ref::Keyboard(smart_keymap::key::keyboard::Ref::KeyCode(0x04)), hold: key_system::Ref::Keyboard(smart_keymap::key::keyboard::Ref::KeyCode(0xE0)), profile: 0, + hold_output: None, }]), smart_keymap::key::tri_state::System::new(Vec::new()), ), diff --git a/tests/ncl/keymap-1key-2layer-th-lmod/expected.rs b/tests/ncl/keymap-1key-2layer-th-lmod/expected.rs index 765078a3d..d626747ab 100644 --- a/tests/ncl/keymap-1key-2layer-th-lmod/expected.rs +++ b/tests/ncl/keymap-1key-2layer-th-lmod/expected.rs @@ -449,6 +449,16 @@ pub mod init { (_, _) => None, } } + + fn pending_output( + &self, + pending_key_state: &Self::PendingKeyState, + ) -> Option { + match pending_key_state { + PendingKeyState::TapHold(pks) => self.tap_hold.pending_output(pks), + _ => None, + } + } } } @@ -495,6 +505,9 @@ pub mod init { tap: key_system::Ref::Keyboard(smart_keymap::key::keyboard::Ref::KeyCode(4)), hold: key_system::Ref::Keyboard(smart_keymap::key::keyboard::Ref::Modifiers(1)), profile: 0, + hold_output: Some(smart_keymap::key::KeyOutput::from_key_modifiers( + smart_keymap::key::KeyboardModifiers::from_byte(1), + )), }]), ); diff --git a/tests/ncl/keymap-1key-abbrev-ent/expected.rs b/tests/ncl/keymap-1key-abbrev-ent/expected.rs index 78679571c..148b17dff 100644 --- a/tests/ncl/keymap-1key-abbrev-ent/expected.rs +++ b/tests/ncl/keymap-1key-abbrev-ent/expected.rs @@ -276,6 +276,15 @@ pub mod init { (_, _) => None, } } + + fn pending_output( + &self, + pending_key_state: &Self::PendingKeyState, + ) -> Option { + match pending_key_state { + _ => None, + } + } } } diff --git a/tests/ncl/keymap-1key-automation/expected.rs b/tests/ncl/keymap-1key-automation/expected.rs index 8187a7d96..7f7ada6c3 100644 --- a/tests/ncl/keymap-1key-automation/expected.rs +++ b/tests/ncl/keymap-1key-automation/expected.rs @@ -290,6 +290,15 @@ pub mod init { (_, _) => None, } } + + fn pending_output( + &self, + pending_key_state: &Self::PendingKeyState, + ) -> Option { + match pending_key_state { + _ => None, + } + } } } diff --git a/tests/ncl/keymap-1key-callback-custom/expected.rs b/tests/ncl/keymap-1key-callback-custom/expected.rs index df2e8a973..92e9d88c5 100644 --- a/tests/ncl/keymap-1key-callback-custom/expected.rs +++ b/tests/ncl/keymap-1key-callback-custom/expected.rs @@ -260,6 +260,15 @@ pub mod init { (_, _) => None, } } + + fn pending_output( + &self, + pending_key_state: &Self::PendingKeyState, + ) -> Option { + match pending_key_state { + _ => None, + } + } } } diff --git a/tests/ncl/keymap-1key-custom/expected.rs b/tests/ncl/keymap-1key-custom/expected.rs index de40e1990..f738d94d7 100644 --- a/tests/ncl/keymap-1key-custom/expected.rs +++ b/tests/ncl/keymap-1key-custom/expected.rs @@ -253,6 +253,15 @@ pub mod init { (_, _) => None, } } + + fn pending_output( + &self, + pending_key_state: &Self::PendingKeyState, + ) -> Option { + match pending_key_state { + _ => None, + } + } } } diff --git a/tests/ncl/keymap-1key-simple/expected.rs b/tests/ncl/keymap-1key-simple/expected.rs index 7ce71eded..c398e7b0f 100644 --- a/tests/ncl/keymap-1key-simple/expected.rs +++ b/tests/ncl/keymap-1key-simple/expected.rs @@ -276,6 +276,15 @@ pub mod init { (_, _) => None, } } + + fn pending_output( + &self, + pending_key_state: &Self::PendingKeyState, + ) -> Option { + match pending_key_state { + _ => None, + } + } } } diff --git a/tests/ncl/keymap-1key-tap_dance/expected.rs b/tests/ncl/keymap-1key-tap_dance/expected.rs index a084b61a6..f80db4648 100644 --- a/tests/ncl/keymap-1key-tap_dance/expected.rs +++ b/tests/ncl/keymap-1key-tap_dance/expected.rs @@ -367,6 +367,16 @@ pub mod init { (_, _) => None, } } + + fn pending_output( + &self, + pending_key_state: &Self::PendingKeyState, + ) -> Option { + match pending_key_state { + PendingKeyState::TapDance(pks) => self.tap_dance.pending_output(pks), + _ => None, + } + } } } diff --git a/tests/ncl/keymap-1key-tap_hold/expected.rs b/tests/ncl/keymap-1key-tap_hold/expected.rs index f2d550deb..e8e869339 100644 --- a/tests/ncl/keymap-1key-tap_hold/expected.rs +++ b/tests/ncl/keymap-1key-tap_hold/expected.rs @@ -359,6 +359,16 @@ pub mod init { (_, _) => None, } } + + fn pending_output( + &self, + pending_key_state: &Self::PendingKeyState, + ) -> Option { + match pending_key_state { + PendingKeyState::TapHold(pks) => self.tap_hold.pending_output(pks), + _ => None, + } + } } } @@ -394,6 +404,9 @@ pub mod init { tap: key_system::Ref::Keyboard(smart_keymap::key::keyboard::Ref::KeyCode(4)), hold: key_system::Ref::Keyboard(smart_keymap::key::keyboard::Ref::KeyCode(224)), profile: 0, + hold_output: Some(smart_keymap::key::KeyOutput::from_key_modifiers( + smart_keymap::key::KeyboardModifiers::from_byte(1), + )), }]), ); diff --git a/tests/ncl/keymap-2key-2layer-composite/expected.rs b/tests/ncl/keymap-2key-2layer-composite/expected.rs index 78ca20ede..f2d9ad3ea 100644 --- a/tests/ncl/keymap-2key-2layer-composite/expected.rs +++ b/tests/ncl/keymap-2key-2layer-composite/expected.rs @@ -449,6 +449,16 @@ pub mod init { (_, _) => None, } } + + fn pending_output( + &self, + pending_key_state: &Self::PendingKeyState, + ) -> Option { + match pending_key_state { + PendingKeyState::TapHold(pks) => self.tap_hold.pending_output(pks), + _ => None, + } + } } } @@ -496,6 +506,7 @@ pub mod init { tap: key_system::Ref::Keyboard(smart_keymap::key::keyboard::Ref::KeyCode(4)), hold: key_system::Ref::Layered(smart_keymap::key::layered::Ref::Modifier(0)), profile: 0, + hold_output: None, }]), ); diff --git a/tests/ncl/keymap-2key-2layer-named-layer_string/expected.rs b/tests/ncl/keymap-2key-2layer-named-layer_string/expected.rs index 5d8242071..f18ea62bf 100644 --- a/tests/ncl/keymap-2key-2layer-named-layer_string/expected.rs +++ b/tests/ncl/keymap-2key-2layer-named-layer_string/expected.rs @@ -364,6 +364,15 @@ pub mod init { (_, _) => None, } } + + fn pending_output( + &self, + pending_key_state: &Self::PendingKeyState, + ) -> Option { + match pending_key_state { + _ => None, + } + } } } diff --git a/tests/ncl/keymap-2key-2layer-named/expected.rs b/tests/ncl/keymap-2key-2layer-named/expected.rs index 5d8242071..f18ea62bf 100644 --- a/tests/ncl/keymap-2key-2layer-named/expected.rs +++ b/tests/ncl/keymap-2key-2layer-named/expected.rs @@ -364,6 +364,15 @@ pub mod init { (_, _) => None, } } + + fn pending_output( + &self, + pending_key_state: &Self::PendingKeyState, + ) -> Option { + match pending_key_state { + _ => None, + } + } } } diff --git a/tests/ncl/keymap-2key-2layer-simple/expected.rs b/tests/ncl/keymap-2key-2layer-simple/expected.rs index f02758c1c..877c3c951 100644 --- a/tests/ncl/keymap-2key-2layer-simple/expected.rs +++ b/tests/ncl/keymap-2key-2layer-simple/expected.rs @@ -364,6 +364,15 @@ pub mod init { (_, _) => None, } } + + fn pending_output( + &self, + pending_key_state: &Self::PendingKeyState, + ) -> Option { + match pending_key_state { + _ => None, + } + } } } diff --git a/tests/ncl/keymap-2key-chorded-named-th-lmod/expected.rs b/tests/ncl/keymap-2key-chorded-named-th-lmod/expected.rs index ad4896f27..b8c799714 100644 --- a/tests/ncl/keymap-2key-chorded-named-th-lmod/expected.rs +++ b/tests/ncl/keymap-2key-chorded-named-th-lmod/expected.rs @@ -600,6 +600,17 @@ pub mod init { (_, _) => None, } } + + fn pending_output( + &self, + pending_key_state: &Self::PendingKeyState, + ) -> Option { + match pending_key_state { + PendingKeyState::Chorded(pks) => self.chorded.pending_output(pks), + PendingKeyState::TapHold(pks) => self.tap_hold.pending_output(pks), + _ => None, + } + } } } @@ -677,6 +688,7 @@ pub mod init { tap: key_system::Ref::Keyboard(smart_keymap::key::keyboard::Ref::KeyCode(41)), hold: key_system::Ref::Layered(smart_keymap::key::layered::Ref::Modifier(0)), profile: 0, + hold_output: None, }]), ); diff --git a/tests/ncl/keymap-2key-chorded/expected.rs b/tests/ncl/keymap-2key-chorded/expected.rs index db3c4ab4c..a167e7230 100644 --- a/tests/ncl/keymap-2key-chorded/expected.rs +++ b/tests/ncl/keymap-2key-chorded/expected.rs @@ -427,6 +427,16 @@ pub mod init { (_, _) => None, } } + + fn pending_output( + &self, + pending_key_state: &Self::PendingKeyState, + ) -> Option { + match pending_key_state { + PendingKeyState::Chorded(pks) => self.chorded.pending_output(pks), + _ => None, + } + } } } diff --git a/tests/ncl/keymap-34key-seniply/expected.rs b/tests/ncl/keymap-34key-seniply/expected.rs index 0b110037a..51ad2931f 100644 --- a/tests/ncl/keymap-34key-seniply/expected.rs +++ b/tests/ncl/keymap-34key-seniply/expected.rs @@ -441,6 +441,15 @@ pub mod init { (_, _) => None, } } + + fn pending_output( + &self, + pending_key_state: &Self::PendingKeyState, + ) -> Option { + match pending_key_state { + _ => None, + } + } } } diff --git a/tests/ncl/keymap-48key-basic/expected.rs b/tests/ncl/keymap-48key-basic/expected.rs index c7c51f3ed..fc18f64ee 100644 --- a/tests/ncl/keymap-48key-basic/expected.rs +++ b/tests/ncl/keymap-48key-basic/expected.rs @@ -419,6 +419,15 @@ pub mod init { (_, _) => None, } } + + fn pending_output( + &self, + pending_key_state: &Self::PendingKeyState, + ) -> Option { + match pending_key_state { + _ => None, + } + } } } diff --git a/tests/ncl/keymap-48key-rgoulter/expected.rs b/tests/ncl/keymap-48key-rgoulter/expected.rs index d77556622..61e10331b 100644 --- a/tests/ncl/keymap-48key-rgoulter/expected.rs +++ b/tests/ncl/keymap-48key-rgoulter/expected.rs @@ -934,6 +934,18 @@ pub mod init { (_, _) => None, } } + + fn pending_output( + &self, + pending_key_state: &Self::PendingKeyState, + ) -> Option { + match pending_key_state { + PendingKeyState::Chorded(pks) => self.chorded.pending_output(pks), + PendingKeyState::TapDance(pks) => self.tap_dance.pending_output(pks), + PendingKeyState::TapHold(pks) => self.tap_hold.pending_output(pks), + _ => None, + } + } } } @@ -2829,211 +2841,277 @@ pub mod init { tap: key_system::Ref::Keyboard(smart_keymap::key::keyboard::Ref::KeyCode(4)), hold: key_system::Ref::Keyboard(smart_keymap::key::keyboard::Ref::Modifiers(4)), profile: 0, + hold_output: Some(smart_keymap::key::KeyOutput::from_key_modifiers( + smart_keymap::key::KeyboardModifiers::from_byte(4), + )), }, smart_keymap::key::tap_hold::Key { tap: key_system::Ref::Keyboard(smart_keymap::key::keyboard::Ref::KeyCode(4)), hold: key_system::Ref::Keyboard(smart_keymap::key::keyboard::Ref::Modifiers(4)), profile: 0, + hold_output: Some(smart_keymap::key::KeyOutput::from_key_modifiers( + smart_keymap::key::KeyboardModifiers::from_byte(4), + )), }, smart_keymap::key::tap_hold::Key { tap: key_system::Ref::Keyboard(smart_keymap::key::keyboard::Ref::KeyCode(18)), hold: key_system::Ref::Keyboard(smart_keymap::key::keyboard::Ref::Modifiers(8)), profile: 0, + hold_output: None, }, smart_keymap::key::tap_hold::Key { tap: key_system::Ref::Keyboard(smart_keymap::key::keyboard::Ref::KeyCode(22)), hold: key_system::Ref::Keyboard(smart_keymap::key::keyboard::Ref::Modifiers(8)), profile: 0, + hold_output: None, }, smart_keymap::key::tap_hold::Key { tap: key_system::Ref::Keyboard(smart_keymap::key::keyboard::Ref::KeyCode(8)), hold: key_system::Ref::Keyboard(smart_keymap::key::keyboard::Ref::Modifiers(1)), profile: 0, + hold_output: Some(smart_keymap::key::KeyOutput::from_key_modifiers( + smart_keymap::key::KeyboardModifiers::from_byte(1), + )), }, smart_keymap::key::tap_hold::Key { tap: key_system::Ref::Keyboard(smart_keymap::key::keyboard::Ref::KeyCode(7)), hold: key_system::Ref::Keyboard(smart_keymap::key::keyboard::Ref::Modifiers(1)), profile: 0, + hold_output: Some(smart_keymap::key::KeyOutput::from_key_modifiers( + smart_keymap::key::KeyboardModifiers::from_byte(1), + )), }, smart_keymap::key::tap_hold::Key { tap: key_system::Ref::Keyboard(smart_keymap::key::keyboard::Ref::KeyCode(24)), hold: key_system::Ref::Keyboard(smart_keymap::key::keyboard::Ref::Modifiers(2)), profile: 0, + hold_output: Some(smart_keymap::key::KeyOutput::from_key_modifiers( + smart_keymap::key::KeyboardModifiers::from_byte(2), + )), }, smart_keymap::key::tap_hold::Key { tap: key_system::Ref::Keyboard(smart_keymap::key::keyboard::Ref::KeyCode(9)), hold: key_system::Ref::Keyboard(smart_keymap::key::keyboard::Ref::Modifiers(2)), profile: 0, + hold_output: Some(smart_keymap::key::KeyOutput::from_key_modifiers( + smart_keymap::key::KeyboardModifiers::from_byte(2), + )), }, smart_keymap::key::tap_hold::Key { tap: key_system::Ref::Keyboard(smart_keymap::key::keyboard::Ref::KeyCode(11)), hold: key_system::Ref::Keyboard(smart_keymap::key::keyboard::Ref::Modifiers(32)), profile: 0, + hold_output: Some(smart_keymap::key::KeyOutput::from_key_modifiers( + smart_keymap::key::KeyboardModifiers::from_byte(32), + )), }, smart_keymap::key::tap_hold::Key { tap: key_system::Ref::Keyboard(smart_keymap::key::keyboard::Ref::KeyCode(13)), hold: key_system::Ref::Keyboard(smart_keymap::key::keyboard::Ref::Modifiers(32)), profile: 0, + hold_output: Some(smart_keymap::key::KeyOutput::from_key_modifiers( + smart_keymap::key::KeyboardModifiers::from_byte(32), + )), }, smart_keymap::key::tap_hold::Key { tap: key_system::Ref::Keyboard(smart_keymap::key::keyboard::Ref::KeyCode(23)), hold: key_system::Ref::Keyboard(smart_keymap::key::keyboard::Ref::Modifiers(1)), profile: 0, + hold_output: Some(smart_keymap::key::KeyOutput::from_key_modifiers( + smart_keymap::key::KeyboardModifiers::from_byte(1), + )), }, smart_keymap::key::tap_hold::Key { tap: key_system::Ref::Keyboard(smart_keymap::key::keyboard::Ref::KeyCode(14)), hold: key_system::Ref::Keyboard(smart_keymap::key::keyboard::Ref::Modifiers(1)), profile: 0, + hold_output: Some(smart_keymap::key::KeyOutput::from_key_modifiers( + smart_keymap::key::KeyboardModifiers::from_byte(1), + )), }, smart_keymap::key::tap_hold::Key { tap: key_system::Ref::Keyboard(smart_keymap::key::keyboard::Ref::KeyCode(17)), hold: key_system::Ref::Keyboard(smart_keymap::key::keyboard::Ref::Modifiers(128)), profile: 0, + hold_output: None, }, smart_keymap::key::tap_hold::Key { tap: key_system::Ref::Keyboard(smart_keymap::key::keyboard::Ref::KeyCode(15)), hold: key_system::Ref::Keyboard(smart_keymap::key::keyboard::Ref::Modifiers(128)), profile: 0, + hold_output: None, }, smart_keymap::key::tap_hold::Key { tap: key_system::Ref::Keyboard(smart_keymap::key::keyboard::Ref::KeyCode(22)), hold: key_system::Ref::Keyboard(smart_keymap::key::keyboard::Ref::Modifiers(4)), profile: 0, + hold_output: Some(smart_keymap::key::KeyOutput::from_key_modifiers( + smart_keymap::key::KeyboardModifiers::from_byte(4), + )), }, smart_keymap::key::tap_hold::Key { tap: key_system::Ref::Keyboard(smart_keymap::key::keyboard::Ref::KeyCode(51)), hold: key_system::Ref::Keyboard(smart_keymap::key::keyboard::Ref::Modifiers(4)), profile: 0, + hold_output: Some(smart_keymap::key::KeyOutput::from_key_modifiers( + smart_keymap::key::KeyboardModifiers::from_byte(4), + )), }, smart_keymap::key::tap_hold::Key { tap: key_system::Ref::Keyboard(smart_keymap::key::keyboard::Ref::KeyCode(43)), hold: key_system::Ref::Layered(smart_keymap::key::layered::Ref::Modifier(24)), profile: 0, + hold_output: None, }, smart_keymap::key::tap_hold::Key { tap: key_system::Ref::Keyboard(smart_keymap::key::keyboard::Ref::KeyCode(43)), hold: key_system::Ref::Layered(smart_keymap::key::layered::Ref::Modifier(25)), profile: 0, + hold_output: None, }, smart_keymap::key::tap_hold::Key { tap: key_system::Ref::Keyboard(smart_keymap::key::keyboard::Ref::KeyCode(43)), hold: key_system::Ref::Layered(smart_keymap::key::layered::Ref::Modifier(26)), profile: 0, + hold_output: None, }, smart_keymap::key::tap_hold::Key { tap: key_system::Ref::Keyboard(smart_keymap::key::keyboard::Ref::KeyCode(43)), hold: key_system::Ref::Layered(smart_keymap::key::layered::Ref::Modifier(27)), profile: 0, + hold_output: None, }, smart_keymap::key::tap_hold::Key { tap: key_system::Ref::Keyboard(smart_keymap::key::keyboard::Ref::KeyCode(43)), hold: key_system::Ref::Layered(smart_keymap::key::layered::Ref::Modifier(28)), profile: 0, + hold_output: None, }, smart_keymap::key::tap_hold::Key { tap: key_system::Ref::Keyboard(smart_keymap::key::keyboard::Ref::KeyCode(41)), hold: key_system::Ref::Layered(smart_keymap::key::layered::Ref::Modifier(29)), profile: 0, + hold_output: None, }, smart_keymap::key::tap_hold::Key { tap: key_system::Ref::Keyboard(smart_keymap::key::keyboard::Ref::KeyCode(41)), hold: key_system::Ref::Layered(smart_keymap::key::layered::Ref::Modifier(30)), profile: 0, + hold_output: None, }, smart_keymap::key::tap_hold::Key { tap: key_system::Ref::Keyboard(smart_keymap::key::keyboard::Ref::KeyCode(41)), hold: key_system::Ref::Layered(smart_keymap::key::layered::Ref::Modifier(31)), profile: 0, + hold_output: None, }, smart_keymap::key::tap_hold::Key { tap: key_system::Ref::Keyboard(smart_keymap::key::keyboard::Ref::KeyCode(41)), hold: key_system::Ref::Layered(smart_keymap::key::layered::Ref::Modifier(32)), profile: 0, + hold_output: None, }, smart_keymap::key::tap_hold::Key { tap: key_system::Ref::Keyboard(smart_keymap::key::keyboard::Ref::KeyCode(44)), hold: key_system::Ref::Layered(smart_keymap::key::layered::Ref::Modifier(33)), profile: 0, + hold_output: None, }, smart_keymap::key::tap_hold::Key { tap: key_system::Ref::Keyboard(smart_keymap::key::keyboard::Ref::KeyCode(44)), hold: key_system::Ref::Layered(smart_keymap::key::layered::Ref::Modifier(34)), profile: 0, + hold_output: None, }, smart_keymap::key::tap_hold::Key { tap: key_system::Ref::Keyboard(smart_keymap::key::keyboard::Ref::KeyCode(44)), hold: key_system::Ref::Layered(smart_keymap::key::layered::Ref::Modifier(35)), profile: 0, + hold_output: None, }, smart_keymap::key::tap_hold::Key { tap: key_system::Ref::Keyboard(smart_keymap::key::keyboard::Ref::KeyCode(44)), hold: key_system::Ref::Layered(smart_keymap::key::layered::Ref::Modifier(36)), profile: 0, + hold_output: None, }, smart_keymap::key::tap_hold::Key { tap: key_system::Ref::Keyboard(smart_keymap::key::keyboard::Ref::KeyCode(76)), hold: key_system::Ref::Layered(smart_keymap::key::layered::Ref::Modifier(37)), profile: 0, + hold_output: None, }, smart_keymap::key::tap_hold::Key { tap: key_system::Ref::Keyboard(smart_keymap::key::keyboard::Ref::KeyCode(40)), hold: key_system::Ref::Layered(smart_keymap::key::layered::Ref::Modifier(38)), profile: 0, + hold_output: None, }, smart_keymap::key::tap_hold::Key { tap: key_system::Ref::Keyboard(smart_keymap::key::keyboard::Ref::KeyCode(40)), hold: key_system::Ref::Layered(smart_keymap::key::layered::Ref::Modifier(39)), profile: 0, + hold_output: None, }, smart_keymap::key::tap_hold::Key { tap: key_system::Ref::Keyboard(smart_keymap::key::keyboard::Ref::KeyCode(40)), hold: key_system::Ref::Layered(smart_keymap::key::layered::Ref::Modifier(40)), profile: 0, + hold_output: None, }, smart_keymap::key::tap_hold::Key { tap: key_system::Ref::Keyboard(smart_keymap::key::keyboard::Ref::KeyCode(40)), hold: key_system::Ref::Layered(smart_keymap::key::layered::Ref::Modifier(41)), profile: 0, + hold_output: None, }, smart_keymap::key::tap_hold::Key { tap: key_system::Ref::Keyboard(smart_keymap::key::keyboard::Ref::KeyCode(42)), hold: key_system::Ref::Layered(smart_keymap::key::layered::Ref::Modifier(42)), profile: 0, + hold_output: None, }, smart_keymap::key::tap_hold::Key { tap: key_system::Ref::Keyboard(smart_keymap::key::keyboard::Ref::KeyCode(42)), hold: key_system::Ref::Layered(smart_keymap::key::layered::Ref::Modifier(43)), profile: 0, + hold_output: None, }, smart_keymap::key::tap_hold::Key { tap: key_system::Ref::Keyboard(smart_keymap::key::keyboard::Ref::KeyCode(42)), hold: key_system::Ref::Layered(smart_keymap::key::layered::Ref::Modifier(44)), profile: 0, + hold_output: None, }, smart_keymap::key::tap_hold::Key { tap: key_system::Ref::Keyboard(smart_keymap::key::keyboard::Ref::KeyCode(42)), hold: key_system::Ref::Layered(smart_keymap::key::layered::Ref::Modifier(45)), profile: 0, + hold_output: None, }, smart_keymap::key::tap_hold::Key { tap: key_system::Ref::Keyboard(smart_keymap::key::keyboard::Ref::KeyCode(76)), hold: key_system::Ref::Layered(smart_keymap::key::layered::Ref::Modifier(46)), profile: 0, + hold_output: None, }, smart_keymap::key::tap_hold::Key { tap: key_system::Ref::Keyboard(smart_keymap::key::keyboard::Ref::KeyCode(76)), hold: key_system::Ref::Layered(smart_keymap::key::layered::Ref::Modifier(47)), profile: 0, + hold_output: None, }, smart_keymap::key::tap_hold::Key { tap: key_system::Ref::Keyboard(smart_keymap::key::keyboard::Ref::KeyCode(76)), hold: key_system::Ref::Layered(smart_keymap::key::layered::Ref::Modifier(48)), profile: 0, + hold_output: None, }, smart_keymap::key::tap_hold::Key { tap: key_system::Ref::Keyboard(smart_keymap::key::keyboard::Ref::KeyCode(76)), hold: key_system::Ref::Layered(smart_keymap::key::layered::Ref::Modifier(49)), profile: 0, + hold_output: None, }, ]), ); diff --git a/tests/ncl/keymap-60key-dvorak-simple-with-tap_hold/expected.rs b/tests/ncl/keymap-60key-dvorak-simple-with-tap_hold/expected.rs index ef16b229e..35d2d76c4 100644 --- a/tests/ncl/keymap-60key-dvorak-simple-with-tap_hold/expected.rs +++ b/tests/ncl/keymap-60key-dvorak-simple-with-tap_hold/expected.rs @@ -359,6 +359,16 @@ pub mod init { (_, _) => None, } } + + fn pending_output( + &self, + pending_key_state: &Self::PendingKeyState, + ) -> Option { + match pending_key_state { + PendingKeyState::TapHold(pks) => self.tap_hold.pending_output(pks), + _ => None, + } + } } } @@ -454,41 +464,61 @@ pub mod init { tap: key_system::Ref::Keyboard(smart_keymap::key::keyboard::Ref::KeyCode(4)), hold: key_system::Ref::Keyboard(smart_keymap::key::keyboard::Ref::Modifiers(4)), profile: 0, + hold_output: Some(smart_keymap::key::KeyOutput::from_key_modifiers( + smart_keymap::key::KeyboardModifiers::from_byte(4), + )), }, smart_keymap::key::tap_hold::Key { tap: key_system::Ref::Keyboard(smart_keymap::key::keyboard::Ref::KeyCode(18)), hold: key_system::Ref::Keyboard(smart_keymap::key::keyboard::Ref::Modifiers(8)), profile: 0, + hold_output: None, }, smart_keymap::key::tap_hold::Key { tap: key_system::Ref::Keyboard(smart_keymap::key::keyboard::Ref::KeyCode(8)), hold: key_system::Ref::Keyboard(smart_keymap::key::keyboard::Ref::Modifiers(1)), profile: 0, + hold_output: Some(smart_keymap::key::KeyOutput::from_key_modifiers( + smart_keymap::key::KeyboardModifiers::from_byte(1), + )), }, smart_keymap::key::tap_hold::Key { tap: key_system::Ref::Keyboard(smart_keymap::key::keyboard::Ref::KeyCode(24)), hold: key_system::Ref::Keyboard(smart_keymap::key::keyboard::Ref::Modifiers(2)), profile: 0, + hold_output: Some(smart_keymap::key::KeyOutput::from_key_modifiers( + smart_keymap::key::KeyboardModifiers::from_byte(2), + )), }, smart_keymap::key::tap_hold::Key { tap: key_system::Ref::Keyboard(smart_keymap::key::keyboard::Ref::KeyCode(11)), hold: key_system::Ref::Keyboard(smart_keymap::key::keyboard::Ref::Modifiers(32)), profile: 0, + hold_output: Some(smart_keymap::key::KeyOutput::from_key_modifiers( + smart_keymap::key::KeyboardModifiers::from_byte(32), + )), }, smart_keymap::key::tap_hold::Key { tap: key_system::Ref::Keyboard(smart_keymap::key::keyboard::Ref::KeyCode(23)), hold: key_system::Ref::Keyboard(smart_keymap::key::keyboard::Ref::Modifiers(16)), profile: 0, + hold_output: Some(smart_keymap::key::KeyOutput::from_key_modifiers( + smart_keymap::key::KeyboardModifiers::from_byte(16), + )), }, smart_keymap::key::tap_hold::Key { tap: key_system::Ref::Keyboard(smart_keymap::key::keyboard::Ref::KeyCode(17)), hold: key_system::Ref::Keyboard(smart_keymap::key::keyboard::Ref::Modifiers(128)), profile: 0, + hold_output: None, }, smart_keymap::key::tap_hold::Key { tap: key_system::Ref::Keyboard(smart_keymap::key::keyboard::Ref::KeyCode(22)), hold: key_system::Ref::Keyboard(smart_keymap::key::keyboard::Ref::Modifiers(64)), profile: 0, + hold_output: Some(smart_keymap::key::KeyOutput::from_key_modifiers( + smart_keymap::key::KeyboardModifiers::from_byte(64), + )), }, ]), ); diff --git a/tests/ncl/keymap-60key-dvorak-simple/expected.rs b/tests/ncl/keymap-60key-dvorak-simple/expected.rs index fe9da5f69..dad056e92 100644 --- a/tests/ncl/keymap-60key-dvorak-simple/expected.rs +++ b/tests/ncl/keymap-60key-dvorak-simple/expected.rs @@ -276,6 +276,15 @@ pub mod init { (_, _) => None, } } + + fn pending_output( + &self, + pending_key_state: &Self::PendingKeyState, + ) -> Option { + match pending_key_state { + _ => None, + } + } } } diff --git a/tests/rust/tap_hold.rs b/tests/rust/tap_hold.rs index 39d7c4bd5..b99d25357 100644 --- a/tests/rust/tap_hold.rs +++ b/tests/rust/tap_hold.rs @@ -6,6 +6,7 @@ mod interrupt_ignore; mod layered; mod nested_hold; mod no_timeout; +mod pending_output; mod profiles; mod quick_tap; mod required_idle_time; diff --git a/tests/rust/tap_hold/pending_output.rs b/tests/rust/tap_hold/pending_output.rs new file mode 100644 index 000000000..c1b356fc4 --- /dev/null +++ b/tests/rust/tap_hold/pending_output.rs @@ -0,0 +1,207 @@ +use smart_keymap::input; +use smart_keymap::keymap::ObservedKeymap; +use smart_keymap_macros::keymap; + +use crate::hid_keycodes::*; + +/// Returns true if any HID report contains `modifier` byte. +fn has_report_with_mod(reports: &[[u8; 8]], modifier: u8) -> bool { + reports.iter().any(|r| r[0] == modifier) +} + +/// Returns true if any report has `modifier` and `key_code` together. +fn has_report_with_mod_and_key(reports: &[[u8; 8]], modifier: u8, key_code: u8) -> bool { + reports.iter().any(|r| r[0] == modifier && r[2] == key_code) +} + +#[test] +fn default_no_output_is_silent_while_pending() { + // Assemble -- tap-hold with default pending_output + let mut keymap = ObservedKeymap::new(keymap!( + r#" + let K = import "keys.ncl" in + { keys = [ K.A & K.hold K.LeftCtrl ] } + "# + )); + + // Act -- press TH + keymap.handle_input(input::Event::Press { keymap_index: 0 }); + + // Assert -- silent while pending + let reports = keymap.distinct_reports().reports().to_vec(); + assert_eq!( + vec![[0, 0, 0, 0, 0, 0, 0, 0]], + reports, + "should be silent while pending" + ); +} + +#[test] +fn default_no_output_becomes_hold_after_timeout() { + // Assemble -- tap-hold with default pending_output + let mut keymap = ObservedKeymap::new(keymap!( + r#" + let K = import "keys.ncl" in + { keys = [ K.A & K.hold K.LeftCtrl ] } + "# + )); + + // Act -- press, then tick past timeout + keymap.handle_input(input::Event::Press { keymap_index: 0 }); + for _ in 0..210 { + keymap.tick(); + } + + // Assert -- after timeout should be hold + let reports = keymap.distinct_reports().reports().to_vec(); + assert!( + has_report_with_mod(&reports, MOD_LCTL), + "after timeout should be hold" + ); +} + +#[test] +fn hold_pending_shows_mod_after_first_tick() { + // Assemble -- tap-hold with Hold pending_output + let mut keymap = ObservedKeymap::new(keymap!( + r#" + let K = import "keys.ncl" in + { + config.tap_hold.pending_output = "Hold", + keys = [ K.A & K.hold K.LeftCtrl ], + } + "# + )); + + // Act -- press, tick once + keymap.handle_input(input::Event::Press { keymap_index: 0 }); + keymap.tick(); + + // Assert -- mod from first tick + let reports = keymap.distinct_reports().reports().to_vec(); + assert!( + has_report_with_mod(&reports, MOD_LCTL), + "speculative hold should appear after first tick, got {:?}", + reports + ); +} + +#[test] +fn hold_pending_timeout_stays_hold() { + // Assemble -- tap-hold with Hold pending_output + let mut keymap = ObservedKeymap::new(keymap!( + r#" + let K = import "keys.ncl" in + { + config.tap_hold.pending_output = "Hold", + keys = [ K.A & K.hold K.LeftCtrl ], + } + "# + )); + + // Act -- press, tick, then timeout + keymap.handle_input(input::Event::Press { keymap_index: 0 }); + keymap.tick(); + for _ in 0..210 { + keymap.tick(); + } + + // Assert -- still hold after timeout + let reports = keymap.distinct_reports().reports().to_vec(); + assert!(has_report_with_mod(&reports, MOD_LCTL)); +} + +#[test] +fn hold_pending_quick_release_cancels_then_tap() { + // Assemble -- tap-hold with Hold pending_output + let mut keymap = ObservedKeymap::new(keymap!( + r#" + let K = import "keys.ncl" in + { + config.tap_hold.pending_output = "Hold", + keys = [ K.A & K.hold K.LeftCtrl ], + } + "# + )); + + // Act -- press and tick to show speculative hold + keymap.handle_input(input::Event::Press { keymap_index: 0 }); + keymap.tick(); + let before = keymap.distinct_reports().reports().to_vec(); + assert!( + has_report_with_mod(&before, MOD_LCTL), + "speculative hold should be present" + ); + + // Act -- quick release before timeout + keymap.handle_input(input::Event::Release { keymap_index: 0 }); + keymap.tick_until_no_scheduled_events(); + let reports = keymap.distinct_reports().reports().to_vec(); + + // Assert -- tap A without mod, and no Ctrl+A flash + assert!( + reports.iter().any(|r| r[2] == KC_A && r[0] == 0), + "tap A without mod after cancel" + ); + assert!( + !has_report_with_mod_and_key(&reports, MOD_LCTL, KC_A), + "should not have Ctrl+A flash, reports {:?}", + reports + ); +} + +#[test] +fn hold_pending_interrupt_mod_already_down() { + // Assemble -- Hold pending_output with HoldOnKeyPress + let mut keymap = ObservedKeymap::new(keymap!( + r#" + let K = import "keys.ncl" in + { + config.tap_hold.pending_output = "Hold", + config.tap_hold.interrupt_response = "HoldOnKeyPress", + keys = [ K.A & K.hold K.LeftCtrl, K.B ], + } + "# + )); + + // Act -- press TH, tick, then interrupt with B + keymap.handle_input(input::Event::Press { keymap_index: 0 }); + keymap.tick(); + keymap.handle_input(input::Event::Press { keymap_index: 1 }); + keymap.tick_until_no_scheduled_events(); + let reports = keymap.distinct_reports().reports().to_vec(); + + // Assert -- mod already down when B taps + assert!(has_report_with_mod(&reports, MOD_LCTL)); + assert!( + has_report_with_mod_and_key(&reports, MOD_LCTL, KC_B), + "B should be with mod, reports {:?}", + reports + ); +} + +#[test] +fn gui_hold_is_not_speculated() { + // Assemble -- Hold pending_output with GUI hold + let mut keymap = ObservedKeymap::new(keymap!( + r#" + let K = import "keys.ncl" in + { + config.tap_hold.pending_output = "Hold", + keys = [ K.A & K.hold K.LeftGUI ], + } + "# + )); + + // Act -- press GUI hold + keymap.handle_input(input::Event::Press { keymap_index: 0 }); + keymap.tick(); + let reports = keymap.distinct_reports().reports().to_vec(); + + // Assert -- remain silent (GUI not speculated) + assert!( + !has_report_with_mod(&reports, MOD_LGUI), + "GUI should not speculate, reports {:?}", + reports + ); +}