diff --git a/keyberon/src/layout.rs b/keyberon/src/layout.rs index 84de7bc14..4115b4ae5 100644 --- a/keyberon/src/layout.rs +++ b/keyberon/src/layout.rs @@ -72,12 +72,16 @@ type PressedQueue = ArrayDeque; /// activation of multiple switch cases using fallthrough. pub const ACTION_QUEUE_LEN: usize = 8; +pub const CUSTOM_EVENT_RELEASE_QUEUE_LEN: usize = 16; + /// The queue is currently only used for chord decomposition when a longer chord does not result in /// an action, but splitting it into smaller chords would. The buffer size of 8 should be more than /// enough for real world usage, but if one wanted to be extra safe, this should be ChordKeys::BITS /// since that should guarantee that all potentially queueable actions can fit. type ActionQueue<'a, T> = ArrayDeque, ACTION_QUEUE_LEN, arraydeque::behavior::Wrapping>; +type CustomEventReleaseQueue<'a, T> = + ArrayDeque<&'a T, CUSTOM_EVENT_RELEASE_QUEUE_LEN, arraydeque::behavior::Wrapping>; type Delay = u16; pub(crate) type QueuedAction<'a, T> = Option<(KCoord, Delay, &'a Action<'a, T>, LayerStack)>; @@ -112,6 +116,7 @@ where pub last_press_tracker: LastPressTracker, pub active_sequences: ArrayDeque, 4, arraydeque::behavior::Wrapping>, pub action_queue: ActionQueue<'a, T>, + pub custom_event_release_queue: CustomEventReleaseQueue<'a, T>, pub rpt_action: Option<&'a Action<'a, T>>, pub historical_keys: History, pub historical_inputs: History, @@ -346,7 +351,12 @@ impl<'a, T: 'a> State<'a, T> { } } /// Returns None if the key has been released and Some otherwise. - pub fn release(&self, c: KCoord, custom: &mut CustomEvent<'a, T>) -> Option { + pub fn release( + &self, + c: KCoord, + custom: &mut CustomEvent<'a, T>, + custom_release_queue: &mut CustomEventReleaseQueue<'a, T>, + ) -> Option { match *self { NormalKey { coord, .. } | LayerModifier { coord, .. } @@ -357,7 +367,14 @@ impl<'a, T: 'a> State<'a, T> { None } Custom { value, coord } if coord == c => { - custom.update(CustomEvent::Release(value)); + match custom { + CustomEvent::NoEvent => custom.update(CustomEvent::Release(value)), + _ => { + if custom_release_queue.push_back(value).is_some() { + panic!("overflowed custom action release queue"); + } + } + } None } _ => Some(*self), @@ -1196,6 +1213,7 @@ impl<'a, const C: usize, const R: usize, T: 'a + Copy + std::fmt::Debug> Layout< last_press_tracker: Default::default(), active_sequences: ArrayDeque::new(), action_queue: ArrayDeque::new(), + custom_event_release_queue: ArrayDeque::new(), rpt_action: None, historical_keys: History::new(), historical_inputs: History::new(), @@ -1257,7 +1275,15 @@ impl<'a, const C: usize, const R: usize, T: 'a + Copy + std::fmt::Debug> Layout< // the rapidity of the release can cause issues. See pause_input_processing_delay // comments for more detail. self.oneshot.pause_input_processing_ticks = self.oneshot.pause_input_processing_delay; - self.do_action(hold, coord, delay, false, &mut layer_stack.into_iter()) + let mut custom_activation_count = 0; + self.do_action( + hold, + coord, + delay, + false, + &mut layer_stack.into_iter(), + &mut custom_activation_count, + ) } else { CustomEvent::NoEvent } @@ -1282,15 +1308,18 @@ impl<'a, const C: usize, const R: usize, T: 'a + Copy + std::fmt::Debug> Layout< } else { self.extra_waiting.remove(idx as usize); } + let mut custom_activation_count = 0; let ret = self.do_action( tap, coord, delay, false, &mut layer_stack.clone().into_iter(), + &mut custom_activation_count, ); if let Some(pq) = pq { + let mut custom_activation_count = 0; self.contextual_execution.pause_historical_keys_updates = true; match tap { Action::KeyCode(_) @@ -1309,6 +1338,7 @@ impl<'a, const C: usize, const R: usize, T: 'a + Copy + std::fmt::Debug> Layout< delay, false, &mut layer_stack.clone().into_iter(), + &mut custom_activation_count, ); } } @@ -1329,6 +1359,7 @@ impl<'a, const C: usize, const R: usize, T: 'a + Copy + std::fmt::Debug> Layout< delay, false, &mut layer_stack.clone().into_iter(), + &mut custom_activation_count, ); } } @@ -1371,12 +1402,14 @@ impl<'a, const C: usize, const R: usize, T: 'a + Copy + std::fmt::Debug> Layout< if coord == self.last_press_tracker.coord { self.last_press_tracker.tap_hold_timeout = 0; } + let mut custom_activation_count = 0; self.do_action( timeout_action, coord, delay, false, &mut layer_stack.into_iter(), + &mut custom_activation_count, ) } else { CustomEvent::NoEvent @@ -1393,6 +1426,16 @@ impl<'a, const C: usize, const R: usize, T: 'a + Copy + std::fmt::Debug> Layout< /// Returns the corresponding `CustomEvent`, allowing to manage /// custom actions thanks to the `Action::Custom` variant. pub fn tick(&mut self) -> CustomEvent<'a, T> { + self.tick_layout() + } + + fn tick_layout(&mut self) -> CustomEvent<'a, T> { + // Eagerly process released queued custom actions + // then return; other items will be processed later! + if let Some(released_custom_event) = self.custom_event_release_queue.pop_front() { + return CustomEvent::Release(released_custom_event); + } + let active_layer = self.current_layer() as u16; if let Some(chv2) = self.chords_v2.as_mut() { self.queue.extend(chv2.tick_chv2(active_layer).drain(0..)); @@ -1406,7 +1449,15 @@ impl<'a, const C: usize, const R: usize, T: 'a + Copy + std::fmt::Debug> Layout< if let Some(Some((coord, delay, action, layer_stack))) = self.action_queue.pop_front() { // If there's anything in the action queue, don't process anything else yet - execute // everything. Otherwise an action may never be released. - return self.do_action(action, coord, delay, false, &mut layer_stack.into_iter()); + let mut custom_activation_count = 0; + return self.do_action( + action, + coord, + delay, + false, + &mut layer_stack.into_iter(), + &mut custom_activation_count, + ); } self.queue.iter_mut().for_each(Queued::tick_qd); self.last_press_tracker.tick_lpt(); @@ -1620,7 +1671,9 @@ impl<'a, const C: usize, const R: usize, T: 'a + Copy + std::fmt::Debug> Layout< let (do_release, overflow_key) = self.oneshot.handle_release((i, j)); if do_release { self.states.retain(|s| { - !s.clear_on_next_release() && s.release((i, j), &mut custom).is_some() + !s.clear_on_next_release() + && s.release((i, j), &mut custom, &mut self.custom_event_release_queue) + .is_some() }); } else { // Fix #1874: @@ -1656,20 +1709,28 @@ impl<'a, const C: usize, const R: usize, T: 'a + Copy + std::fmt::Debug> Layout< // as a oneshot key, it should still be released here. _ => { !s.clear_on_next_release() - && s.release((i, j), &mut custom).is_some() + && s.release( + (i, j), + &mut custom, + &mut self.custom_event_release_queue, + ) + .is_some() } } }); } if let Some((i2, j2)) = overflow_key { - self.states - .retain(|s| s.release((i2, j2), &mut custom).is_some()); + self.states.retain(|s| { + s.release((i2, j2), &mut custom, &mut self.custom_event_release_queue) + .is_some() + }); } custom } Press(i, j) => { let mut layer_stack = self.trans_resolution_layer_order().into_iter(); + let mut custom_activation_count = 0; if let Some(tde) = &mut self.tap_dance_eager { if (i, j) == self.last_press_tracker.coord && !tde.is_expired() { let tde_action = tde.actions[usize::from(tde.num_taps)]; @@ -1680,6 +1741,7 @@ impl<'a, const C: usize, const R: usize, T: 'a + Copy + std::fmt::Debug> Layout< queue.since, false, &mut layer_stack.skip(1), + &mut custom_activation_count, ); custom } else { @@ -1688,10 +1750,24 @@ impl<'a, const C: usize, const R: usize, T: 'a + Copy + std::fmt::Debug> Layout< if i == REAL_KEY_ROW { tde.set_expired(); } - self.do_action(&Action::Trans, (i, j), queue.since, false, &mut layer_stack) + self.do_action( + &Action::Trans, + (i, j), + queue.since, + false, + &mut layer_stack, + &mut custom_activation_count, + ) } } else { - self.do_action(&Action::Trans, (i, j), queue.since, false, &mut layer_stack) + self.do_action( + &Action::Trans, + (i, j), + queue.since, + false, + &mut layer_stack, + &mut custom_activation_count, + ) } } } @@ -1756,6 +1832,7 @@ impl<'a, const C: usize, const R: usize, T: 'a + Copy + std::fmt::Debug> Layout< delay: u16, is_oneshot: bool, layer_stack: &mut (impl Iterator + Clone), // used to resolve Trans action + custom_activation_count: &mut u8, ) -> CustomEvent<'a, T> { let mut action = action; if let Trans = action { @@ -1819,7 +1896,14 @@ impl<'a, const C: usize, const R: usize, T: 'a + Copy + std::fmt::Debug> Layout< // Risk: infinite recursive resulting in stack overflow. // In practice this is not expected to happen. // The `src_keys` actions are all expected to be `KeyCode` or `NoOp` actions. - self.do_action(action, coord, delay, is_oneshot, &mut std::iter::empty()); + self.do_action( + action, + coord, + delay, + is_oneshot, + &mut std::iter::empty(), + custom_activation_count, + ); } Trans => { // Transparent action should be resolved to non-transparent one near the top @@ -1842,7 +1926,14 @@ impl<'a, const C: usize, const R: usize, T: 'a + Copy + std::fmt::Debug> Layout< // not the outer (tap-dance|hold) but multi will repeat the entire outer multi // action. if let Some(ac) = self.rpt_action { - self.do_action(ac, coord, delay, is_oneshot, &mut std::iter::empty()); + self.do_action( + ac, + coord, + delay, + is_oneshot, + &mut std::iter::empty(), + custom_activation_count, + ); } } HoldTap(HoldTapAction { @@ -1866,7 +1957,14 @@ impl<'a, const C: usize, const R: usize, T: 'a + Copy + std::fmt::Debug> Layout< .find(|prior| prior.event.0 == REAL_KEY_ROW && prior.event != coord) .is_some_and(|prior| prior.ticks_since_occurrence <= idle_threshold); if prior_idle_tap { - let custom = self.do_action(tap, coord, delay, is_oneshot, layer_stack); + let custom = self.do_action( + tap, + coord, + delay, + is_oneshot, + layer_stack, + custom_activation_count, + ); self.last_press_tracker.update_coord(coord); return custom; } @@ -1902,7 +2000,14 @@ impl<'a, const C: usize, const R: usize, T: 'a + Copy + std::fmt::Debug> Layout< self.last_press_tracker.tap_hold_timeout = *tap_hold_interval; } else { self.last_press_tracker.tap_hold_timeout = 0; - custom.update(self.do_action(tap, coord, delay, is_oneshot, layer_stack)); + custom.update(self.do_action( + tap, + coord, + delay, + is_oneshot, + layer_stack, + custom_activation_count, + )); } // Need to set tap_hold_tracker coord AFTER the checks. self.last_press_tracker.update_coord(coord); @@ -1910,8 +2015,14 @@ impl<'a, const C: usize, const R: usize, T: 'a + Copy + std::fmt::Debug> Layout< } &OneShot(oneshot) => { self.last_press_tracker.update_coord(coord); - let custom = - self.do_action(oneshot.action, coord, delay, true, &mut std::iter::empty()); + let custom = self.do_action( + oneshot.action, + coord, + delay, + true, + &mut std::iter::empty(), + custom_activation_count, + ); // Note - set rpt_action after doing the inner oneshot action. This means that the // whole oneshot will be repeated by rpt-any rather than only the inner action. self.rpt_action = Some(action); @@ -1974,7 +2085,14 @@ impl<'a, const C: usize, const R: usize, T: 'a + Copy + std::fmt::Debug> Layout< } } }; - return self.do_action(td.actions[0], coord, delay, false, layer_stack); + return self.do_action( + td.actions[0], + coord, + delay, + false, + layer_stack, + custom_activation_count, + ); } } } @@ -2113,6 +2231,7 @@ impl<'a, const C: usize, const R: usize, T: 'a + Copy + std::fmt::Debug> Layout< delay, is_oneshot, &mut layer_stack.clone(), + custom_activation_count, )); } // Save the whole multi action instead of the final action in multi so that Repeat @@ -2195,10 +2314,23 @@ impl<'a, const C: usize, const R: usize, T: 'a + Copy + std::fmt::Debug> Layout< self.oneshot .handle_press(OneShotHandlePressKey::Other(coord)); } + *custom_activation_count = custom_activation_count.saturating_add(1); self.rpt_action = Some(action); - if self.states.push(State::Custom { value, coord }).is_ok() { - return CustomEvent::Press(value); - } + return match custom_activation_count { + 0 | 1 => { + let _ = self.states.push(State::Custom { value, coord }); + CustomEvent::Press(value) + } + _ => { + self.action_queue.push_back(Some(( + coord, + delay, + action, + layer_stack.clone().collect(), + ))); + CustomEvent::NoEvent + } + }; } ReleaseState(rs) => { self.states.retain(|s| s.release_state(*rs).is_some()); @@ -2215,12 +2347,22 @@ impl<'a, const C: usize, const R: usize, T: 'a + Copy + std::fmt::Debug> Layout< } _ => false, }) { - false => { - self.do_action(&fcfg.left, coord, delay, false, &mut layer_stack.clone()) - } - true => { - self.do_action(&fcfg.right, coord, delay, false, &mut layer_stack.clone()) - } + false => self.do_action( + &fcfg.left, + coord, + delay, + false, + &mut layer_stack.clone(), + custom_activation_count, + ), + true => self.do_action( + &fcfg.right, + coord, + delay, + false, + &mut layer_stack.clone(), + custom_activation_count, + ), }; // Repeat the fork rather than the terminal action. self.rpt_action = Some(action); diff --git a/parser/src/cfg/alloc.rs b/parser/src/cfg/alloc.rs index 9baf70b92..2510c2858 100644 --- a/parser/src/cfg/alloc.rs +++ b/parser/src/cfg/alloc.rs @@ -105,12 +105,6 @@ impl Allocations { self.bref_slice(v.into_boxed_slice()) } - /// Returns a `&'static [&'static T]` by leaking a newly created box and boxed slice of `v`. - pub(crate) fn sref_slice(&self, v: T) -> &'static [&'static T] { - log::debug!("sref_slice {}", std::any::type_name::()); - self.bref_slice(vec![self.sref(v)].into_boxed_slice()) - } - /// Returns a `&'static str` by leaking a String. pub(crate) fn sref_str(&self, v: String) -> &'static str { if !v.capacity() == 0 { diff --git a/parser/src/cfg/arbitrary_code.rs b/parser/src/cfg/arbitrary_code.rs index 7df047af7..118f4029f 100644 --- a/parser/src/cfg/arbitrary_code.rs +++ b/parser/src/cfg/arbitrary_code.rs @@ -16,7 +16,5 @@ pub(crate) fn parse_arbitrary_code( .map(str::parse::) .and_then(|c| c.ok()) .ok_or_else(|| anyhow!("{ERR_MSG}: got {:?}", ac_params[0]))?; - Ok(s.a.sref(Action::Custom( - s.a.sref(s.a.sref_slice(CustomAction::SendArbitraryCode(code))), - ))) + custom(CustomAction::SendArbitraryCode(code), &s.a) } diff --git a/parser/src/cfg/caps_word.rs b/parser/src/cfg/caps_word.rs index 021af80d8..5c88a04aa 100644 --- a/parser/src/cfg/caps_word.rs +++ b/parser/src/cfg/caps_word.rs @@ -12,7 +12,7 @@ pub(crate) fn parse_caps_word( bail!("{ERR_STR}\nFound {} params instead of 1", ac_params.len()); } let timeout = parse_non_zero_u16(&ac_params[0], s, "timeout")?; - Ok(s.a.sref(Action::Custom(s.a.sref(s.a.sref_slice( + custom( CustomAction::CapsWord(CapsWordCfg { repress_behaviour, keys_to_capitalize: &[ @@ -74,7 +74,8 @@ pub(crate) fn parse_caps_word( ], timeout, }), - ))))) + &s.a, + ) } pub(crate) fn parse_caps_word_custom( @@ -87,24 +88,23 @@ pub(crate) fn parse_caps_word_custom( bail!("{ERR_STR}\nFound {} params instead of 3", ac_params.len()); } let timeout = parse_non_zero_u16(&ac_params[0], s, "timeout")?; - Ok(s.a.sref(Action::Custom( - s.a.sref( - s.a.sref_slice(CustomAction::CapsWord(CapsWordCfg { - repress_behaviour, - keys_to_capitalize: s.a.sref_vec( - parse_key_list(&ac_params[1], s, "keys-to-capitalize")? - .into_iter() - .map(KeyCode::from) - .collect(), - ), - keys_nonterminal: s.a.sref_vec( - parse_key_list(&ac_params[2], s, "extra-non-terminal-keys")? - .into_iter() - .map(KeyCode::from) - .collect(), - ), - timeout, - })), - ), - ))) + custom( + CustomAction::CapsWord(CapsWordCfg { + repress_behaviour, + keys_to_capitalize: s.a.sref_vec( + parse_key_list(&ac_params[1], s, "keys-to-capitalize")? + .into_iter() + .map(KeyCode::from) + .collect(), + ), + keys_nonterminal: s.a.sref_vec( + parse_key_list(&ac_params[2], s, "extra-non-terminal-keys")? + .into_iter() + .map(KeyCode::from) + .collect(), + ), + timeout, + }), + &s.a, + ) } diff --git a/parser/src/cfg/chord.rs b/parser/src/cfg/chord.rs index 33ff97dcc..092860d74 100644 --- a/parser/src/cfg/chord.rs +++ b/parser/src/cfg/chord.rs @@ -237,7 +237,7 @@ impl<'a> ChordTranslation<'a> { timeout: &'a SExpr, release_behaviour: &'a SExpr, disabled_layers: &'a SExpr, - first_layer: &[Action<'static, &&[&CustomAction]>], + first_layer: &[Action<'static, KanataCustom>], ) -> Self { let postprocess_map: FxHashMap = [ ("semicolon", ";"), diff --git a/parser/src/cfg/chord_v1.rs b/parser/src/cfg/chord_v1.rs index 9fad1ba73..55c3bbceb 100644 --- a/parser/src/cfg/chord_v1.rs +++ b/parser/src/cfg/chord_v1.rs @@ -232,7 +232,7 @@ pub(crate) fn find_chords_coords( } pub(crate) fn fill_chords( - chord_groups: &[&'static ChordsGroup<&&[&CustomAction]>], + chord_groups: &[&'static ChordsGroup], action: &KanataAction, s: &ParserState, ) -> Option { diff --git a/parser/src/cfg/clipboard.rs b/parser/src/cfg/clipboard.rs index 1f1ad6eac..1e06b6d66 100644 --- a/parser/src/cfg/clipboard.rs +++ b/parser/src/cfg/clipboard.rs @@ -20,9 +20,10 @@ pub(crate) fn parse_clipboard_set( } }; let clip_string = clip_string.t.trim_atom_quotes(); - Ok(s.a.sref(Action::Custom(s.a.sref(s.a.sref_slice( + custom( CustomAction::ClipboardSet(s.a.sref_str(clip_string.to_string())), - ))))) + &s.a, + ) } pub(crate) fn parse_clipboard_save( @@ -34,9 +35,7 @@ pub(crate) fn parse_clipboard_save( bail!("{CLIPBOARD_SAVE} {ERR_MSG}, found {}", ac_params.len()); } let id = parse_u16(&ac_params[0], s, "clipboard save ID")?; - Ok(s.a.sref(Action::Custom( - s.a.sref(s.a.sref_slice(CustomAction::ClipboardSave(id))), - ))) + custom(CustomAction::ClipboardSave(id), &s.a) } pub(crate) fn parse_clipboard_restore( @@ -48,9 +47,7 @@ pub(crate) fn parse_clipboard_restore( bail!("{CLIPBOARD_RESTORE} {ERR_MSG}, found {}", ac_params.len()); } let id = parse_u16(&ac_params[0], s, "clipboard save ID")?; - Ok(s.a.sref(Action::Custom( - s.a.sref(s.a.sref_slice(CustomAction::ClipboardRestore(id))), - ))) + custom(CustomAction::ClipboardRestore(id), &s.a) } pub(crate) fn parse_clipboard_save_swap( @@ -64,9 +61,7 @@ pub(crate) fn parse_clipboard_save_swap( } let id1 = parse_u16(&ac_params[0], s, "clipboard save ID")?; let id2 = parse_u16(&ac_params[1], s, "clipboard save ID")?; - Ok(s.a.sref(Action::Custom( - s.a.sref(s.a.sref_slice(CustomAction::ClipboardSaveSwap(id1, id2))), - ))) + custom(CustomAction::ClipboardSaveSwap(id1, id2), &s.a) } pub(crate) fn parse_clipboard_save_set( @@ -81,7 +76,8 @@ pub(crate) fn parse_clipboard_save_set( let save_content = ac_params[1] .atom(s.vars()) .ok_or_else(|| anyhow_expr!(&ac_params[1], "save content must be a string"))?; - Ok(s.a.sref(Action::Custom(s.a.sref(s.a.sref_slice( + custom( CustomAction::ClipboardSaveSet(id, s.a.sref_str(save_content.into())), - ))))) + &s.a, + ) } diff --git a/parser/src/cfg/cmd.rs b/parser/src/cfg/cmd.rs index a48958687..b9d40574b 100644 --- a/parser/src/cfg/cmd.rs +++ b/parser/src/cfg/cmd.rs @@ -46,9 +46,10 @@ pub(crate) fn parse_cmd_log(ac_params: &[SExpr], s: &ParserState) -> Result<&'st bail!(ERR_STR); } let cmds = cmd.into_iter().map(|v| s.a.sref_str(v)).collect(); - Ok(s.a.sref(Action::Custom(s.a.sref(s.a.sref_slice( + custom( CustomAction::CmdLog(log_level, error_log_level, s.a.sref_vec(cmds)), - ))))) + &s.a, + ) } #[allow(unused_variables)] @@ -80,9 +81,10 @@ pub(crate) fn parse_cmd( bail_expr!(&ac_params[1], "{CLIPBOARD_SAVE_CMD_SET} {ERR_STR}"); } let cmds = cmd.into_iter().map(|v| s.a.sref_str(v)).collect(); - return Ok(s.a.sref(Action::Custom(s.a.sref(s.a.sref_slice( + return custom( CustomAction::ClipboardSaveCmdSet(save_id, s.a.sref_vec(cmds)), - ))))); + &s.a, + ); } const ERR_STR: &str = "cmd expects at least one string"; @@ -96,13 +98,15 @@ pub(crate) fn parse_cmd( } let cmds = cmd.into_iter().map(|v| s.a.sref_str(v)).collect(); let cmds = s.a.sref_vec(cmds); - Ok(s.a - .sref(Action::Custom(s.a.sref(s.a.sref_slice(match cmd_type { + custom( + match cmd_type { CmdType::Standard => CustomAction::Cmd(cmds), CmdType::OutputKeys => CustomAction::CmdOutputKeys(cmds), CmdType::ClipboardSet => CustomAction::ClipboardCmdSet(cmds), CmdType::ClipboardSaveSet => unreachable!(), - }))))) + }, + &s.a, + ) } } diff --git a/parser/src/cfg/fake_key.rs b/parser/src/cfg/fake_key.rs index bfe38c089..aacaa30ff 100644 --- a/parser/src/cfg/fake_key.rs +++ b/parser/src/cfg/fake_key.rs @@ -110,9 +110,7 @@ pub(crate) fn parse_on_press_fake_key_op( ) -> Result<&'static KanataAction> { let (coord, action) = parse_fake_key_op_coord_action(ac_params, s, ON_PRESS_FAKEKEY)?; set_virtual_key_reference_lsp_hint(&ac_params[0], s); - Ok(s.a.sref(Action::Custom( - s.a.sref(s.a.sref_slice(CustomAction::FakeKey { coord, action })), - ))) + custom(CustomAction::FakeKey { coord, action }, &s.a) } pub(crate) fn parse_on_release_fake_key_op( @@ -121,9 +119,7 @@ pub(crate) fn parse_on_release_fake_key_op( ) -> Result<&'static KanataAction> { let (coord, action) = parse_fake_key_op_coord_action(ac_params, s, ON_RELEASE_FAKEKEY)?; set_virtual_key_reference_lsp_hint(&ac_params[0], s); - Ok(s.a.sref(Action::Custom(s.a.sref( - s.a.sref_slice(CustomAction::FakeKeyOnRelease { coord, action }), - )))) + custom(CustomAction::FakeKeyOnRelease { coord, action }, &s.a) } pub(crate) fn parse_on_idle_fakekey( @@ -170,13 +166,14 @@ pub(crate) fn parse_on_idle_fakekey( let (x, y) = get_fake_key_coords(y); let coord = Coord { x, y }; set_virtual_key_reference_lsp_hint(&ac_params[0], s); - Ok(s.a.sref(Action::Custom(s.a.sref(s.a.sref_slice( + custom( CustomAction::FakeKeyOnIdle(FakeKeyOnIdle { coord, action, idle_duration, }), - ))))) + &s.a, + ) } fn parse_fake_key_op_coord_action( @@ -256,11 +253,13 @@ fn parse_delay( .map(str::parse::) .ok_or_else(|| anyhow!("{ERR_MSG}"))? .map_err(|e| anyhow!("{ERR_MSG}: {e}"))?; - Ok(s.a - .sref(Action::Custom(s.a.sref(s.a.sref_slice(match is_release { + custom( + match is_release { false => CustomAction::Delay(delay), true => CustomAction::DelayOnRelease(delay), - }))))) + }, + &s.a, + ) } pub(crate) fn parse_vkey_coord(param: &SExpr, s: &ParserState) -> Result { @@ -309,9 +308,7 @@ pub(crate) fn parse_on_press( let action = parse_vkey_action(&ac_params[0], s)?; let coord = parse_vkey_coord(&ac_params[1], s)?; - Ok(s.a.sref(Action::Custom( - s.a.sref(s.a.sref_slice(CustomAction::FakeKey { coord, action })), - ))) + custom(CustomAction::FakeKey { coord, action }, &s.a) } pub(crate) fn parse_on_release( @@ -325,9 +322,7 @@ pub(crate) fn parse_on_release( let action = parse_vkey_action(&ac_params[0], s)?; let coord = parse_vkey_coord(&ac_params[1], s)?; - Ok(s.a.sref(Action::Custom(s.a.sref( - s.a.sref_slice(CustomAction::FakeKeyOnRelease { coord, action }), - )))) + custom(CustomAction::FakeKeyOnRelease { coord, action }, &s.a) } pub(crate) fn parse_on_idle(ac_params: &[SExpr], s: &ParserState) -> Result<&'static KanataAction> { @@ -339,13 +334,14 @@ pub(crate) fn parse_on_idle(ac_params: &[SExpr], s: &ParserState) -> Result<&'st let action = parse_vkey_action(&ac_params[1], s)?; let coord = parse_vkey_coord(&ac_params[2], s)?; - Ok(s.a.sref(Action::Custom(s.a.sref(s.a.sref_slice( + custom( CustomAction::FakeKeyOnIdle(FakeKeyOnIdle { coord, action, idle_duration, }), - ))))) + &s.a, + ) } pub(crate) fn parse_on_physical_idle( @@ -361,13 +357,14 @@ pub(crate) fn parse_on_physical_idle( let action = parse_vkey_action(&ac_params[1], s)?; let coord = parse_vkey_coord(&ac_params[2], s)?; - Ok(s.a.sref(Action::Custom(s.a.sref(s.a.sref_slice( + custom( CustomAction::FakeKeyOnPhysicalIdle(FakeKeyOnIdle { coord, action, idle_duration, }), - ))))) + &s.a, + ) } pub(crate) fn parse_hold_for_duration( @@ -381,10 +378,11 @@ pub(crate) fn parse_hold_for_duration( let hold_duration = parse_non_zero_u16(&ac_params[0], s, "hold-duration")?; let coord = parse_vkey_coord(&ac_params[1], s)?; - Ok(s.a.sref(Action::Custom(s.a.sref(s.a.sref_slice( + custom( CustomAction::FakeKeyHoldForDuration(FakeKeyHoldForDuration { coord, hold_duration, }), - ))))) + &s.a, + ) } diff --git a/parser/src/cfg/key_outputs.rs b/parser/src/cfg/key_outputs.rs index 2fa345525..2341d8517 100644 --- a/parser/src/cfg/key_outputs.rs +++ b/parser/src/cfg/key_outputs.rs @@ -113,18 +113,14 @@ pub(crate) fn add_key_output_from_action_to_key_pos( add_key_output_from_action_to_key_pos(osc_slot, case.1, outputs, overrides); } } - Action::Custom(cacs) => { - for ac in cacs.iter() { - match ac { - CustomAction::Unmodded { keys, .. } | CustomAction::Unshifted { keys } => { - for k in keys.iter() { - add_kc_output(osc_slot, k.into(), outputs, overrides); - } - } - _ => {} + Action::Custom(ac) => match ac { + CustomAction::Unmodded { keys, .. } | CustomAction::Unshifted { keys } => { + for k in keys.iter() { + add_kc_output(osc_slot, k.into(), outputs, overrides); } } - } + _ => {} + }, Action::Src => { add_kc_output(osc_slot, osc_slot, outputs, overrides); } diff --git a/parser/src/cfg/live_reload.rs b/parser/src/cfg/live_reload.rs index 5d8d1ab49..fba2e3191 100644 --- a/parser/src/cfg/live_reload.rs +++ b/parser/src/cfg/live_reload.rs @@ -12,11 +12,9 @@ pub(crate) fn parse_live_reload_num( bail!("{LIVE_RELOAD_NUM} {ERR_MSG}, found {}", ac_params.len()); } let num = parse_non_zero_u16(&ac_params[0], s, "config argument position")?; - Ok(s.a.sref(Action::Custom( - // Note: for user-friendliness (hopefully), begin at 1 for parsing. - // But for use as an index when stored as data, subtract 1 for 0-based indexing. - s.a.sref(s.a.sref_slice(CustomAction::LiveReloadNum(num - 1))), - ))) + // Note: for user-friendliness (hopefully), begin at 1 for parsing. + // But for use as an index when stored as data, subtract 1 for 0-based indexing. + custom(CustomAction::LiveReloadNum(num - 1), &s.a) } pub(crate) fn parse_live_reload_file( @@ -35,7 +33,8 @@ pub(crate) fn parse_live_reload_file( } }; let lrld_file_path = spanned_filepath.t.trim_atom_quotes(); - Ok(s.a.sref(Action::Custom(s.a.sref(s.a.sref_slice( + custom( CustomAction::LiveReloadFile(s.a.sref_str(lrld_file_path.to_string())), - ))))) + &s.a, + ) } diff --git a/parser/src/cfg/macro.rs b/parser/src/cfg/macro.rs index 6633cc4b5..e84097e90 100644 --- a/parser/src/cfg/macro.rs +++ b/parser/src/cfg/macro.rs @@ -54,7 +54,7 @@ pub(crate) fn parse_macro_release_cancel( let macro_action = parse_macro(ac_params, s, repeat)?; Ok(s.a.sref(Action::MultipleActions(s.a.sref(s.a.sref_vec(vec![ *macro_action, - Action::Custom(s.a.sref(s.a.sref_slice(CustomAction::CancelMacroOnRelease))), + Action::Custom(s.a.sref(CustomAction::CancelMacroOnRelease)), ]))))) } @@ -72,9 +72,7 @@ pub(crate) fn parse_macro_cancel_on_next_press( }; Ok(s.a.sref(Action::MultipleActions(s.a.sref(s.a.sref_vec(vec![ *macro_action, - Action::Custom( - s.a.sref(s.a.sref_slice(CustomAction::CancelMacroOnNextPress(macro_duration))), - ), + Action::Custom(s.a.sref(CustomAction::CancelMacroOnNextPress(macro_duration))), ]))))) } @@ -92,10 +90,8 @@ pub(crate) fn parse_macro_cancel_on_next_press_cancel_on_release( }; Ok(s.a.sref(Action::MultipleActions(s.a.sref(s.a.sref_vec(vec![ *macro_action, - Action::Custom(s.a.sref(s.a.sref_vec(vec![ - &CustomAction::CancelMacroOnRelease, - s.a.sref(CustomAction::CancelMacroOnNextPress(macro_duration)), - ]))), + Action::Custom(s.a.sref(CustomAction::CancelMacroOnRelease)), + Action::Custom(s.a.sref(CustomAction::CancelMacroOnNextPress(macro_duration))), ]))))) } @@ -118,9 +114,7 @@ pub(crate) fn parse_macro_record_stop_truncate( bail!("{ERR_STR}\nFound {} params instead of 1", ac_params.len()); } let num_to_truncate = parse_u16(&ac_params[0], s, "num-keys-to-truncate")?; - Ok(s.a.sref(Action::Custom(s.a.sref( - s.a.sref_slice(CustomAction::DynamicMacroRecordStop(num_to_truncate)), - )))) + custom(CustomAction::DynamicMacroRecordStop(num_to_truncate), &s.a) } #[derive(PartialEq)] @@ -133,10 +127,7 @@ pub(crate) enum MacroNumberParseMode { pub(crate) fn parse_macro_item<'a>( acs: &'a [SExpr], s: &ParserState, -) -> Result<( - Vec>, - &'a [SExpr], -)> { +) -> Result<(Vec>, &'a [SExpr])> { parse_macro_item_impl(acs, s, MacroNumberParseMode::Delay) } @@ -145,10 +136,7 @@ pub(crate) fn parse_macro_item_impl<'a>( acs: &'a [SExpr], s: &ParserState, num_parse_mode: MacroNumberParseMode, -) -> Result<( - Vec>, - &'a [SExpr], -)> { +) -> Result<(Vec>, &'a [SExpr])> { if num_parse_mode == MacroNumberParseMode::Delay { if let Some(a) = acs[0].atom(s.vars()) { match parse_non_zero_u16(&acs[0], s, "delay") { @@ -280,9 +268,7 @@ pub(crate) fn parse_dynamic_macro_record( bail!("{ERR_MSG}, found {}", ac_params.len()); } let key = parse_u16(&ac_params[0], s, "macro ID")?; - Ok(s.a.sref(Action::Custom( - s.a.sref(s.a.sref_slice(CustomAction::DynamicMacroRecord(key))), - ))) + custom(CustomAction::DynamicMacroRecord(key), &s.a) } pub(crate) fn parse_dynamic_macro_play( @@ -294,7 +280,5 @@ pub(crate) fn parse_dynamic_macro_play( bail!("{ERR_MSG}, found {}", ac_params.len()); } let key = parse_u16(&ac_params[0], s, "macro ID")?; - Ok(s.a.sref(Action::Custom( - s.a.sref(s.a.sref_slice(CustomAction::DynamicMacroPlay(key))), - ))) + custom(CustomAction::DynamicMacroPlay(key), &s.a) } diff --git a/parser/src/cfg/mod.rs b/parser/src/cfg/mod.rs index 71e0f08c9..a7176aa78 100644 --- a/parser/src/cfg/mod.rs +++ b/parser/src/cfg/mod.rs @@ -247,13 +247,12 @@ impl<'a> FileContentProvider<'a> { } } -pub type KanataCustom = &'static &'static [&'static CustomAction]; pub type KanataAction = Action<'static, KanataCustom>; type KLayout = Layout<'static, KEYS_IN_ROW, 2, KanataCustom>; type TapHoldCustomFunc = fn(&[OsCode], &Allocations) -> &'static custom_tap_hold::CustomTapHoldFn; -pub type BorrowedKLayout<'a> = Layout<'a, KEYS_IN_ROW, 2, &'a &'a [&'a CustomAction]>; +pub type BorrowedKLayout<'a> = Layout<'a, KEYS_IN_ROW, 2, &'a CustomAction>; pub type KeySeqsToFKeys = Trie<(u8, u16)>; pub struct KanataLayout { @@ -1336,7 +1335,7 @@ fn parse_action(expr: &SExpr, s: &ParserState) -> Result<&'static KanataAction> /// Returns a single custom action in the proper wrapped type. fn custom(ca: CustomAction, a: &Allocations) -> Result<&'static KanataAction> { - Ok(a.sref(Action::Custom(a.sref(a.sref_slice(ca))))) + Ok(a.sref(Action::Custom(a.sref(ca)))) } /// Parse a `kanata_keyberon::action::Action` from a string. diff --git a/parser/src/cfg/mouse.rs b/parser/src/cfg/mouse.rs index 82a513749..1d8305094 100644 --- a/parser/src/cfg/mouse.rs +++ b/parser/src/cfg/mouse.rs @@ -21,14 +21,15 @@ pub(crate) fn parse_mwheel( } let interval = parse_non_zero_u16(&ac_params[0], s, "interval")?; let distance = parse_distance(&ac_params[1], s, "distance")?; - Ok(s.a.sref(Action::Custom(s.a.sref(s.a.sref_slice( + custom( CustomAction::MWheel { direction, interval, distance, inertial_scroll_params: None, }, - ))))) + &s.a, + ) } pub(crate) fn parse_mwheel_accel( @@ -48,7 +49,7 @@ pub(crate) fn parse_mwheel_accel( parse_f32(&ac_params[2], s, "acceleration multiplier", 1.0, 1000.0)?; let deceleration_multiplier = parse_f32(&ac_params[3], s, "deceleration multiplier", 0.0, 0.99)?; - Ok(s.a.sref(Action::Custom(s.a.sref(s.a.sref_slice( + custom( CustomAction::MWheel { direction, interval: 16, @@ -60,7 +61,8 @@ pub(crate) fn parse_mwheel_accel( deceleration_multiplier, })), }, - ))))) + &s.a, + ) } pub(crate) fn parse_move_mouse( @@ -74,13 +76,14 @@ pub(crate) fn parse_move_mouse( } let interval = parse_non_zero_u16(&ac_params[0], s, "interval")?; let distance = parse_distance(&ac_params[1], s, "distance")?; - Ok(s.a.sref(Action::Custom(s.a.sref(s.a.sref_slice( + custom( CustomAction::MoveMouse { direction, interval, distance, }, - ))))) + &s.a, + ) } pub(crate) fn parse_move_mouse_accel( @@ -101,7 +104,7 @@ pub(crate) fn parse_move_mouse_accel( if min_distance > max_distance { bail!("min distance should be less than max distance") } - Ok(s.a.sref(Action::Custom(s.a.sref(s.a.sref_slice( + custom( CustomAction::MoveMouseAccel { direction, interval, @@ -109,7 +112,8 @@ pub(crate) fn parse_move_mouse_accel( min_distance, max_distance, }, - ))))) + &s.a, + ) } pub(crate) fn parse_move_mouse_speed( @@ -123,9 +127,7 @@ pub(crate) fn parse_move_mouse_speed( ); } let speed = parse_non_zero_u16(&ac_params[0], s, "speed scaling %")?; - Ok(s.a.sref(Action::Custom( - s.a.sref(s.a.sref_slice(CustomAction::MoveMouseSpeed { speed })), - ))) + custom(CustomAction::MoveMouseSpeed { speed }, &s.a) } pub(crate) fn parse_set_mouse( @@ -140,7 +142,5 @@ pub(crate) fn parse_set_mouse( } let x = parse_u16(&ac_params[0], s, "x")?; let y = parse_u16(&ac_params[1], s, "y")?; - Ok(s.a.sref(Action::Custom( - s.a.sref(s.a.sref_slice(CustomAction::SetMouse { x, y })), - ))) + custom(CustomAction::SetMouse { x, y }, &s.a) } diff --git a/parser/src/cfg/multi.rs b/parser/src/cfg/multi.rs index 02e07b8f1..445e4ebec 100644 --- a/parser/src/cfg/multi.rs +++ b/parser/src/cfg/multi.rs @@ -9,34 +9,38 @@ pub(crate) fn parse_multi(ac_params: &[SExpr], s: &ParserState) -> Result<&'stat s.multi_action_nest_count .replace(s.multi_action_nest_count.get().saturating_add(1)); let mut actions = Vec::new(); - let mut custom_actions: Vec<&'static CustomAction> = Vec::new(); for expr in ac_params { let ac = parse_action(expr, s)?; match ac { - Action::Custom(acs) => { - for ac in acs.iter() { - custom_actions.push(ac); - } - } // Flatten multi actions Action::MultipleActions(acs) => { for ac in acs.iter() { - match ac { - Action::Custom(acs) => { - for ac in acs.iter() { - custom_actions.push(ac); - } - } - _ => actions.push(*ac), - } + actions.push(*ac); } } _ => actions.push(*ac), } } - if !custom_actions.is_empty() { - actions.push(Action::Custom(s.a.sref(s.a.sref_vec(custom_actions)))); + // Transform all but the last Mouse actions into MouseTap. + // Need to transform mouse actions to preserve old v<=1.11.0 mouse behaviour where an action like: + // (multi mlft mlft) + // should result in an event sequence like: + // click-release-click ... held until key release ... release + // + // See test `multi_mouse_button_does_multi_click_release_single_hold`. + for ca in actions + .iter_mut() + .rev() + .filter(|ac| matches!(ac, Action::Custom(CustomAction::Mouse(..)))) + .skip(1) + { + *ca = match ca { + Action::Custom(CustomAction::Mouse(btn)) => { + Action::Custom(s.a.sref(CustomAction::MouseTap(*btn))) + } + _ => *ca, + }; } if actions diff --git a/parser/src/cfg/sequence.rs b/parser/src/cfg/sequence.rs index c823554d4..0283e20b3 100644 --- a/parser/src/cfg/sequence.rs +++ b/parser/src/cfg/sequence.rs @@ -28,9 +28,7 @@ pub(crate) fn parse_sequence_start( } else { s.default_sequence_input_mode }; - Ok(s.a.sref(Action::Custom(s.a.sref( - s.a.sref_slice(CustomAction::SequenceLeader(timeout, input_mode)), - )))) + custom(CustomAction::SequenceLeader(timeout, input_mode), &s.a) } pub(crate) fn parse_sequence_noerase( @@ -42,9 +40,7 @@ pub(crate) fn parse_sequence_noerase( bail!("{ERR_MSG}\nfound {} items", ac_params.len()); } let count = parse_non_zero_u16(&ac_params[0], s, "noerase-count")?; - Ok(s.a.sref(Action::Custom( - s.a.sref(s.a.sref_slice(CustomAction::SequenceNoerase(count))), - ))) + custom(CustomAction::SequenceNoerase(count), &s.a) } pub(crate) fn parse_sequences(exprs: &[&Vec], s: &ParserState) -> Result { diff --git a/parser/src/cfg/tests.rs b/parser/src/cfg/tests.rs index 084521c09..d481773aa 100644 --- a/parser/src/cfg/tests.rs +++ b/parser/src/cfg/tests.rs @@ -424,19 +424,21 @@ fn recursive_multi_is_flattened() { ]; let s = ParserState::default(); if let KanataAction::MultipleActions(parsed_multi) = parse_multi(¶ms, &s).unwrap() { - assert_eq!(parsed_multi.len(), 4); + assert_eq!(parsed_multi.len(), 6); assert_eq!(parsed_multi[0], Action::KeyCode(KeyCode::A)); - assert_eq!(parsed_multi[1], Action::KeyCode(KeyCode::B)); - assert_eq!(parsed_multi[2], Action::KeyCode(KeyCode::C)); + assert_eq!(parsed_multi[2], Action::KeyCode(KeyCode::B)); + assert_eq!(parsed_multi[4], Action::KeyCode(KeyCode::C)); + assert_eq!( + parsed_multi[1], + Action::Custom(&CustomAction::MouseTap(Btn::Mid)) + ); assert_eq!( parsed_multi[3], - Action::Custom( - &&[ - &CustomAction::MouseTap(Btn::Mid), - &CustomAction::MouseTap(Btn::Left), - &CustomAction::MouseTap(Btn::Right), - ][..] - ) + Action::Custom(&CustomAction::MouseTap(Btn::Left)) + ); + assert_eq!( + parsed_multi[5], + Action::Custom(&CustomAction::MouseTap(Btn::Right)) ); } else { panic!("multi did not parse into multi"); @@ -935,43 +937,31 @@ fn parse_virtualkeys() { let (klayers, _) = res.klayers.get(); assert_eq!( klayers[0][0][OsCode::KEY_A.as_u16() as usize], - Action::Custom( - &[&CustomAction::FakeKey { - coord: Coord { x: 1, y: 0 }, - action: FakeKeyAction::Press, - }] - .as_ref() - ), + Action::Custom(&CustomAction::FakeKey { + coord: Coord { x: 1, y: 0 }, + action: FakeKeyAction::Press, + }), ); assert_eq!( klayers[0][0][OsCode::KEY_F.as_u16() as usize], - Action::Custom( - &[&CustomAction::FakeKey { - coord: Coord { x: 1, y: 1 }, - action: FakeKeyAction::Release, - }] - .as_ref() - ), + Action::Custom(&CustomAction::FakeKey { + coord: Coord { x: 1, y: 1 }, + action: FakeKeyAction::Release, + }), ); assert_eq!( klayers[0][0][OsCode::KEY_K.as_u16() as usize], - Action::Custom( - &[&CustomAction::FakeKeyOnRelease { - coord: Coord { x: 1, y: 0 }, - action: FakeKeyAction::Toggle, - }] - .as_ref() - ), + Action::Custom(&CustomAction::FakeKeyOnRelease { + coord: Coord { x: 1, y: 0 }, + action: FakeKeyAction::Toggle, + }), ); assert_eq!( klayers[0][0][OsCode::KEY_P.as_u16() as usize], - Action::Custom( - &[&CustomAction::FakeKeyOnRelease { - coord: Coord { x: 1, y: 1 }, - action: FakeKeyAction::Tap, - }] - .as_ref() - ), + Action::Custom(&CustomAction::FakeKeyOnRelease { + coord: Coord { x: 1, y: 1 }, + action: FakeKeyAction::Tap, + }), ); } @@ -1000,14 +990,11 @@ fn parse_on_idle_fakekey() { let (klayers, _) = res.klayers.get(); assert_eq!( klayers[0][0][OsCode::KEY_A.as_u16() as usize], - Action::Custom( - &[&CustomAction::FakeKeyOnIdle(FakeKeyOnIdle { - coord: Coord { x: 1, y: 0 }, - action: FakeKeyAction::Tap, - idle_duration: 200 - })] - .as_ref() - ), + Action::Custom(&CustomAction::FakeKeyOnIdle(FakeKeyOnIdle { + coord: Coord { x: 1, y: 0 }, + action: FakeKeyAction::Tap, + idle_duration: 200 + })), ); } diff --git a/parser/src/cfg/unicode.rs b/parser/src/cfg/unicode.rs index 45296e49d..ec77f0c71 100644 --- a/parser/src/cfg/unicode.rs +++ b/parser/src/cfg/unicode.rs @@ -30,9 +30,7 @@ pub(crate) fn parse_unicode(ac_params: &[SExpr], s: &ParserState) -> Result<&'st } } }; - Ok(s.a.sref(Action::Custom( - s.a.sref(s.a.sref_slice(CustomAction::Unicode(unicode_char))), - ))) + custom(CustomAction::Unicode(unicode_char), &s.a) }) .ok_or_else(|| anyhow_expr!(&ac_params[0], "{ERR_STR}"))? } diff --git a/parser/src/cfg/unmod.rs b/parser/src/cfg/unmod.rs index 9d456629b..9218fe18d 100644 --- a/parser/src/cfg/unmod.rs +++ b/parser/src/cfg/unmod.rs @@ -81,12 +81,8 @@ pub(crate) fn parse_unmod( })?; let keys = s.a.sref_vec(keys); match unmod_type { - UNMOD => Ok(s.a.sref(Action::Custom( - s.a.sref(s.a.sref_slice(CustomAction::Unmodded { keys, mods })), - ))), - UNSHIFT => Ok(s.a.sref(Action::Custom( - s.a.sref(s.a.sref_slice(CustomAction::Unshifted { keys })), - ))), + UNMOD => custom(CustomAction::Unmodded { keys, mods }, &s.a), + UNSHIFT => custom(CustomAction::Unshifted { keys }, &s.a), _ => panic!("Unknown unmod type {unmod_type}"), } } diff --git a/parser/src/layers.rs b/parser/src/layers.rs index 2d39115fa..b798ae327 100644 --- a/parser/src/layers.rs +++ b/parser/src/layers.rs @@ -14,13 +14,12 @@ pub const LAYER_ROWS: usize = 2; pub const DEFAULT_ACTION: KanataAction = KanataAction::KeyCode(KeyCode::ErrorUndefined); pub type IntermediateLayers = Box<[[Row; LAYER_ROWS]]>; +pub type KanataCustom = &'static CustomAction; -pub type KLayers = - Layers<'static, KEYS_IN_ROW, LAYER_ROWS, &'static &'static [&'static CustomAction]>; +pub type KLayers = Layers<'static, KEYS_IN_ROW, LAYER_ROWS, KanataCustom>; pub struct KanataLayers { - pub(crate) layers: - Layers<'static, KEYS_IN_ROW, LAYER_ROWS, &'static &'static [&'static CustomAction]>, + pub(crate) layers: Layers<'static, KEYS_IN_ROW, LAYER_ROWS, KanataCustom>, _allocations: Arc, } @@ -30,8 +29,7 @@ impl std::fmt::Debug for KanataLayers { } } -pub type Row = [kanata_keyberon::action::Action<'static, &'static &'static [&'static CustomAction]>; - KEYS_IN_ROW]; +pub type Row = [kanata_keyberon::action::Action<'static, KanataCustom>; KEYS_IN_ROW]; pub fn new_layers(layers: usize) -> IntermediateLayers { let actual_num_layers = layers; diff --git a/src/kanata/mod.rs b/src/kanata/mod.rs index e2f5efa5b..6926d5bff 100644 --- a/src/kanata/mod.rs +++ b/src/kanata/mod.rs @@ -1235,36 +1235,28 @@ impl Kanata { // remember that it is intentional. However, since unmodded needs to modify the key lists, // it should come before. match custom_event { - CustomEvent::Press(custacts) => { - for custact in custacts.iter() { - match custact { - CustomAction::Unmodded { keys, mods } => { - self.unmodded_keys.extend(keys.iter()); - self.unmodded_mods = *mods; - } - CustomAction::Unshifted { keys } => { - self.unshifted_keys.extend(keys.iter()); - } - _ => {} - } + CustomEvent::Press(custact) => match custact { + CustomAction::Unmodded { keys, mods } => { + self.unmodded_keys.extend(keys.iter()); + self.unmodded_mods = *mods; } - } - CustomEvent::Release(custacts) => { - for custact in custacts.iter() { - match custact { - CustomAction::Unmodded { keys, mods: _ } => { - self.unmodded_keys.retain(|k| !keys.contains(k)); - } - CustomAction::Unshifted { keys } => { - self.unshifted_keys.retain(|k| !keys.contains(k)); - } - CustomAction::ReverseReleaseOrder => { - reverse_release_order = true; - } - _ => {} - } + CustomAction::Unshifted { keys } => { + self.unshifted_keys.extend(keys.iter()); } - } + _ => {} + }, + CustomEvent::Release(custact) => match custact { + CustomAction::Unmodded { keys, mods: _ } => { + self.unmodded_keys.retain(|k| !keys.contains(k)); + } + CustomAction::Unshifted { keys } => { + self.unshifted_keys.retain(|k| !keys.contains(k)); + } + CustomAction::ReverseReleaseOrder => { + reverse_release_order = true; + } + _ => {} + }, _ => {} } if !self.unmodded_keys.is_empty() { @@ -1439,400 +1431,392 @@ impl Kanata { // Handle custom events. This used to be in a separate function but lifetime issues cause // it to now be here. match custom_event { - CustomEvent::Press(custacts) => { + CustomEvent::Press(custact) => { #[cfg(feature = "cmd")] let mut cmds = vec![]; - let mut prev_mouse_btn = None; + let mut reload_action: Option = None; - for custact in custacts.iter() { - match custact { - // For unicode, only send on the press. No repeat action is supported for this for - // now. - CustomAction::Unicode(c) => self.kbd_out.send_unicode(*c)?, - CustomAction::LiveReload => { - reload_action = Some(ReloadAction::Reload); - } - CustomAction::LiveReloadNext => { - reload_action = Some(ReloadAction::ReloadNext); - } - CustomAction::LiveReloadPrev => { - reload_action = Some(ReloadAction::ReloadPrev); - } - CustomAction::LiveReloadNum(n) => { - reload_action = Some(ReloadAction::ReloadNum(usize::from(*n))); - } - CustomAction::LiveReloadFile(path) => { - reload_action = Some(ReloadAction::ReloadFile(path.to_string())); - } - CustomAction::Mouse(btn) => { - log::debug!("click {:?}", btn); - if let Some(pbtn) = prev_mouse_btn { - log::debug!("unclick {:?}", pbtn); - self.kbd_out.release_btn(pbtn)?; - } - self.kbd_out.click_btn(*btn)?; - prev_mouse_btn = Some(*btn); - } - CustomAction::MouseTap(btn) => { - log::debug!("click {:?}", btn); - self.kbd_out.click_btn(*btn)?; - log::debug!("unclick {:?}", btn); - self.kbd_out.release_btn(*btn)?; - } - CustomAction::MWheel { - direction, - interval, - distance, - inertial_scroll_params, - } => match direction { - MWheelDirection::Up | MWheelDirection::Down => { - self.scroll_state = Some(ScrollState { - direction: *direction, - distance: *distance, - ticks_until_scroll: 0, - interval: *interval, - scroll_accel_state: inertial_scroll_params.as_ref().map(|isp| - ScrollAccelState { - deceleration_multiplier: isp.deceleration_multiplier.0, - acceleration_multiplier: isp.acceleration_multiplier.0, - max_velocity: isp.maximum_velocity.0, - current_velocity: isp.initial_velocity.0, - scroll_released: false, - } - ), - }) - } - MWheelDirection::Left | MWheelDirection::Right => { - self.hscroll_state = Some(ScrollState { - direction: *direction, - distance: *distance, - ticks_until_scroll: 0, - interval: *interval, - scroll_accel_state: None, - }) + match custact { + // For unicode, only send on the press. No repeat action is supported for this for + // now. + CustomAction::Unicode(c) => self.kbd_out.send_unicode(*c)?, + CustomAction::LiveReload => { + reload_action = Some(ReloadAction::Reload); + } + CustomAction::LiveReloadNext => { + reload_action = Some(ReloadAction::ReloadNext); + } + CustomAction::LiveReloadPrev => { + reload_action = Some(ReloadAction::ReloadPrev); + } + CustomAction::LiveReloadNum(n) => { + reload_action = Some(ReloadAction::ReloadNum(usize::from(*n))); + } + CustomAction::LiveReloadFile(path) => { + reload_action = Some(ReloadAction::ReloadFile(path.to_string())); + } + CustomAction::Mouse(btn) => { + self.kbd_out.click_btn(*btn)?; + } + CustomAction::MouseTap(btn) => { + log::debug!("click {:?}", btn); + self.kbd_out.click_btn(*btn)?; + log::debug!("unclick {:?}", btn); + self.kbd_out.release_btn(*btn)?; + } + CustomAction::MWheel { + direction, + interval, + distance, + inertial_scroll_params, + } => match direction { + MWheelDirection::Up | MWheelDirection::Down => { + self.scroll_state = Some(ScrollState { + direction: *direction, + distance: *distance, + ticks_until_scroll: 0, + interval: *interval, + scroll_accel_state: inertial_scroll_params.as_ref().map(|isp| + ScrollAccelState { + deceleration_multiplier: isp.deceleration_multiplier.0, + acceleration_multiplier: isp.acceleration_multiplier.0, + max_velocity: isp.maximum_velocity.0, + current_velocity: isp.initial_velocity.0, + scroll_released: false, + } + ), + }) + } + MWheelDirection::Left | MWheelDirection::Right => { + self.hscroll_state = Some(ScrollState { + direction: *direction, + distance: *distance, + ticks_until_scroll: 0, + interval: *interval, + scroll_accel_state: None, + }) + } + }, + CustomAction::MWheelNotch { direction } => { + self.kbd_out + .scroll(*direction, HI_RES_SCROLL_UNITS_IN_LO_RES)?; + } + CustomAction::MoveMouse { + direction, + interval, + distance, + } => match direction { + MoveDirection::Up | MoveDirection::Down => { + self.move_mouse_state_vertical = Some(MoveMouseState { + direction: *direction, + distance: *distance, + ticks_until_move: 0, + interval: *interval, + move_mouse_accel_state: None, + }) + } + MoveDirection::Left | MoveDirection::Right => { + self.move_mouse_state_horizontal = Some(MoveMouseState { + direction: *direction, + distance: *distance, + ticks_until_move: 0, + interval: *interval, + move_mouse_accel_state: None, + }) + } + }, + CustomAction::MoveMouseAccel { + direction, + interval, + accel_time, + min_distance, + max_distance, + } => { + let move_mouse_accel_state = match ( + self.movemouse_inherit_accel_state, + &self.move_mouse_state_horizontal, + &self.move_mouse_state_vertical, + ) { + ( + true, + Some(MoveMouseState { + move_mouse_accel_state: Some(s), + .. + }), + _, + ) + | ( + true, + _, + Some(MoveMouseState { + move_mouse_accel_state: Some(s), + .. + }), + ) => *s, + _ => { + let f_max_distance: f64 = *max_distance as f64; + let f_min_distance: f64 = *min_distance as f64; + let f_accel_time: f64 = *accel_time as f64; + let increment = + (f_max_distance - f_min_distance) / f_accel_time; + + MoveMouseAccelState { + accel_ticks_from_min: 0, + accel_ticks_until_max: *accel_time, + accel_increment: increment, + min_distance: *min_distance, + max_distance: *max_distance, + } } - }, - CustomAction::MWheelNotch { direction } => { - self.kbd_out - .scroll(*direction, HI_RES_SCROLL_UNITS_IN_LO_RES)?; - } - CustomAction::MoveMouse { - direction, - interval, - distance, - } => match direction { + }; + + match direction { MoveDirection::Up | MoveDirection::Down => { self.move_mouse_state_vertical = Some(MoveMouseState { direction: *direction, - distance: *distance, + distance: *min_distance, ticks_until_move: 0, interval: *interval, - move_mouse_accel_state: None, + move_mouse_accel_state: Some(move_mouse_accel_state), }) } MoveDirection::Left | MoveDirection::Right => { self.move_mouse_state_horizontal = Some(MoveMouseState { direction: *direction, - distance: *distance, + distance: *min_distance, ticks_until_move: 0, interval: *interval, - move_mouse_accel_state: None, + move_mouse_accel_state: Some(move_mouse_accel_state), }) } - }, - CustomAction::MoveMouseAccel { - direction, - interval, - accel_time, - min_distance, - max_distance, - } => { - let move_mouse_accel_state = match ( - self.movemouse_inherit_accel_state, - &self.move_mouse_state_horizontal, - &self.move_mouse_state_vertical, - ) { - ( - true, - Some(MoveMouseState { - move_mouse_accel_state: Some(s), - .. - }), - _, - ) - | ( - true, - _, - Some(MoveMouseState { - move_mouse_accel_state: Some(s), - .. - }), - ) => *s, - _ => { - let f_max_distance: f64 = *max_distance as f64; - let f_min_distance: f64 = *min_distance as f64; - let f_accel_time: f64 = *accel_time as f64; - let increment = - (f_max_distance - f_min_distance) / f_accel_time; - - MoveMouseAccelState { - accel_ticks_from_min: 0, - accel_ticks_until_max: *accel_time, - accel_increment: increment, - min_distance: *min_distance, - max_distance: *max_distance, - } - } - }; - - match direction { - MoveDirection::Up | MoveDirection::Down => { - self.move_mouse_state_vertical = Some(MoveMouseState { - direction: *direction, - distance: *min_distance, - ticks_until_move: 0, - interval: *interval, - move_mouse_accel_state: Some(move_mouse_accel_state), - }) - } - MoveDirection::Left | MoveDirection::Right => { - self.move_mouse_state_horizontal = Some(MoveMouseState { - direction: *direction, - distance: *min_distance, - ticks_until_move: 0, - interval: *interval, - move_mouse_accel_state: Some(move_mouse_accel_state), - }) - } - } - } - CustomAction::MoveMouseSpeed { speed } => { - self.move_mouse_speed_modifiers.push(*speed); - log::debug!( - "movemousespeed modifiers: {:?}", - self.move_mouse_speed_modifiers - ); - } - CustomAction::Cmd(_cmd) => { - #[cfg(feature = "cmd")] - cmds.push(( - Some(log::Level::Info), - Some(log::Level::Error), - Vec::from_iter(_cmd.iter().map(|s| s.to_string())), - )); - } - CustomAction::CmdLog(_log_level, _error_log_level, _cmd) => { - #[cfg(feature = "cmd")] - cmds.push(( - _log_level.get_level(), - _error_log_level.get_level(), - Vec::from_iter(_cmd.iter().map(|s| s.to_string())), - )); } - CustomAction::CmdOutputKeys(_cmd) => { - #[cfg(feature = "cmd")] - { - // Maybe improvement in the future: - // A delay here, as in KeyAction::Delay, will pause the entire - // state machine loop. That is _probably_ OK, but ideally this - // would be done in a separate thread or somehow - for key_action in keys_for_cmd_output(_cmd) { - match key_action { - KeyAction::Press(osc) => press_key(&mut self.kbd_out, osc)?, - KeyAction::Release(osc) => { - release_key(&mut self.kbd_out, osc)? - } - KeyAction::Delay(delay) => std::thread::sleep( - std::time::Duration::from_millis(u64::from(delay)), - ), + } + CustomAction::MoveMouseSpeed { speed } => { + self.move_mouse_speed_modifiers.push(*speed); + log::debug!( + "movemousespeed modifiers: {:?}", + self.move_mouse_speed_modifiers + ); + } + CustomAction::Cmd(_cmd) => { + #[cfg(feature = "cmd")] + cmds.push(( + Some(log::Level::Info), + Some(log::Level::Error), + Vec::from_iter(_cmd.iter().map(|s| s.to_string())), + )); + } + CustomAction::CmdLog(_log_level, _error_log_level, _cmd) => { + #[cfg(feature = "cmd")] + cmds.push(( + _log_level.get_level(), + _error_log_level.get_level(), + Vec::from_iter(_cmd.iter().map(|s| s.to_string())), + )); + } + CustomAction::CmdOutputKeys(_cmd) => { + #[cfg(feature = "cmd")] + { + // Maybe improvement in the future: + // A delay here, as in KeyAction::Delay, will pause the entire + // state machine loop. That is _probably_ OK, but ideally this + // would be done in a separate thread or somehow + for key_action in keys_for_cmd_output(_cmd) { + match key_action { + KeyAction::Press(osc) => press_key(&mut self.kbd_out, osc)?, + KeyAction::Release(osc) => { + release_key(&mut self.kbd_out, osc)? } + KeyAction::Delay(delay) => std::thread::sleep( + std::time::Duration::from_millis(u64::from(delay)), + ), } } } - CustomAction::PushMessage(_message) => { - log::debug!("Action push-msg"); - #[cfg(feature = "tcp_server")] - if let Some(tx) = _tx { - let message = simple_sexpr_to_json_array(_message); - log::debug!("Action push-msg message: {}", message); - match tx.try_send(ServerMessage::MessagePush { message }) { - Ok(_) => {} - Err(error) => { - log::error!( - "could not send {} event notification: {}", - PUSH_MESSAGE, - error - ); - } + } + CustomAction::PushMessage(_message) => { + log::debug!("Action push-msg"); + #[cfg(feature = "tcp_server")] + if let Some(tx) = _tx { + let message = simple_sexpr_to_json_array(_message); + log::debug!("Action push-msg message: {}", message); + match tx.try_send(ServerMessage::MessagePush { message }) { + Ok(_) => {} + Err(error) => { + log::error!( + "could not send {} event notification: {}", + PUSH_MESSAGE, + error + ); } } - #[cfg(feature = "tcp_server")] - if self.tcp_server_address.is_none() { - log::warn!("{} was used, but TCP server is not running. did you specify a port?", PUSH_MESSAGE); - } - #[cfg(not(feature = "tcp_server"))] - log::warn!( - "{} was used, but Kanata was compiled with TCP server disabled.", - PUSH_MESSAGE - ); - } - CustomAction::FakeKey { coord, action } => { - let (x, y) = (coord.x, coord.y); - log::debug!( - "fake key on press {action:?} {:?},{x:?},{y:?} {:?}", - layout.default_layer, - layout.layers[layout.default_layer][x as usize][y as usize] - ); - handle_fakekey_action(*action, layout, x, y); } - CustomAction::Delay(delay) => { - log::debug!("on-press: sleeping for {delay} ms"); - std::thread::sleep(time::Duration::from_millis((*delay).into())); + #[cfg(feature = "tcp_server")] + if self.tcp_server_address.is_none() { + log::warn!("{} was used, but TCP server is not running. did you specify a port?", PUSH_MESSAGE); } - CustomAction::SequenceCancel => { - if let Some(state) = self.sequence_state.get_active() { - log::debug!("pressed cancel sequence key"); - cancel_sequence(state, &mut self.kbd_out)?; - } + #[cfg(not(feature = "tcp_server"))] + log::warn!( + "{} was used, but Kanata was compiled with TCP server disabled.", + PUSH_MESSAGE + ); + } + CustomAction::FakeKey { coord, action } => { + let (x, y) = (coord.x, coord.y); + log::debug!( + "fake key on press {action:?} {:?},{x:?},{y:?} {:?}", + layout.default_layer, + layout.layers[layout.default_layer][x as usize][y as usize] + ); + handle_fakekey_action(*action, layout, x, y); + } + CustomAction::Delay(delay) => { + log::debug!("on-press: sleeping for {delay} ms"); + std::thread::sleep(time::Duration::from_millis((*delay).into())); + } + CustomAction::SequenceCancel => { + if let Some(state) = self.sequence_state.get_active() { + log::debug!("pressed cancel sequence key"); + cancel_sequence(state, &mut self.kbd_out)?; } - CustomAction::SequenceLeader(timeout, input_mode) => { - if self.sequence_state.is_inactive() { - log::debug!("entering sequence mode"); - self.sequence_state.activate(*input_mode, *timeout); - } else if *input_mode == SequenceInputMode::HiddenSuppressed { - log::debug!("retriggering sequence mode"); - self.sequence_state.activate(*input_mode, *timeout); - } + } + CustomAction::SequenceLeader(timeout, input_mode) => { + if self.sequence_state.is_inactive() { + log::debug!("entering sequence mode"); + self.sequence_state.activate(*input_mode, *timeout); + } else if *input_mode == SequenceInputMode::HiddenSuppressed { + log::debug!("retriggering sequence mode"); + self.sequence_state.activate(*input_mode, *timeout); } - CustomAction::SequenceNoerase(noerase_count) => { - if let Some(state) = self.sequence_state.get_active() { - log::debug!("pressed cancel sequence key"); - add_noerase(state, *noerase_count); - } + } + CustomAction::SequenceNoerase(noerase_count) => { + if let Some(state) = self.sequence_state.get_active() { + log::debug!("pressed cancel sequence key"); + add_noerase(state, *noerase_count); } - CustomAction::Repeat => { - let keycode = self.last_pressed_key; - let osc: OsCode = keycode.into(); - log::debug!("repeating a keypress {osc:?}"); - let mut do_caps_word = false; - if !cur_keys.contains(&KeyCode::LShift) - && let Some(ref mut cw) = self.caps_word { - cur_keys.push(keycode); - let prev_len = cur_keys.len(); - cw.tick_maybe_add_lsft(cur_keys); - if cur_keys.len() > prev_len { - do_caps_word = true; - press_key(&mut self.kbd_out, OsCode::KEY_LEFTSHIFT)?; - } + } + CustomAction::Repeat => { + let keycode = self.last_pressed_key; + let osc: OsCode = keycode.into(); + log::debug!("repeating a keypress {osc:?}"); + let mut do_caps_word = false; + if !cur_keys.contains(&KeyCode::LShift) + && let Some(ref mut cw) = self.caps_word { + cur_keys.push(keycode); + let prev_len = cur_keys.len(); + cw.tick_maybe_add_lsft(cur_keys); + if cur_keys.len() > prev_len { + do_caps_word = true; + press_key(&mut self.kbd_out, OsCode::KEY_LEFTSHIFT)?; } - // Release key in case the most recently pressed key is still pressed. - release_key(&mut self.kbd_out, osc)?; - press_key(&mut self.kbd_out, osc)?; - release_key(&mut self.kbd_out, osc)?; - if do_caps_word { - self.kbd_out.release_key(OsCode::KEY_LEFTSHIFT)?; - } - } - CustomAction::DynamicMacroRecord(macro_id) => { - if let Some((macro_id, prev_recorded_macro)) = - begin_record_macro(*macro_id, &mut self.dynamic_macro_record_state) - { - log::debug!("saving macro {prev_recorded_macro:?}"); - self.dynamic_macros.insert(macro_id, prev_recorded_macro); - } - } - CustomAction::DynamicMacroRecordStop(num_actions_to_remove) => { - if let Some((macro_id, prev_recorded_macro)) = stop_macro( - &mut self.dynamic_macro_record_state, - *num_actions_to_remove, - ) { - log::debug!("saving macro {prev_recorded_macro:?}"); - self.dynamic_macros.insert(macro_id, prev_recorded_macro); - } - } - CustomAction::DynamicMacroPlay(macro_id) => { - play_macro( - *macro_id, - &mut self.dynamic_macro_replay_state, - &self.dynamic_macros, - ); - } - CustomAction::CancelMacroOnNextPress(duration) => { - self.macro_on_press_cancel_duration = *duration; - } - CustomAction::SendArbitraryCode(code) => { - #[cfg(all(not(feature = "simulated_output"), target_os = "windows"))] - { - self.kbd_out.write_code_raw(*code, KeyValue::Press)?; } - #[cfg(any(feature = "simulated_output", not(target_os = "windows")))] - { - self.kbd_out.write_code(*code as u32, KeyValue::Press)?; - } - } - CustomAction::CapsWord(cfg) => match cfg.repress_behaviour { - CapsWordRepressBehaviour::Overwrite => { - log::trace!("caps-word overwrite"); - self.caps_word = Some(CapsWordState::new(cfg)); - } - CapsWordRepressBehaviour::Toggle => { - log::trace!("caps-word toggle"); - self.caps_word = match self.caps_word { - Some(_) => None, - None => Some(CapsWordState::new(cfg)), - }; - } - }, - CustomAction::SetMouse { x, y } => { - self.kbd_out.set_mouse(*x, *y)?; - } - CustomAction::FakeKeyOnIdle(fkd) => { - self.ticks_since_idle = 0; - self.waiting_for_idle.insert(*fkd); - } - CustomAction::FakeKeyOnPhysicalIdle(fkd) => { - self.ticks_since_physical_idle = 0; - self.waiting_for_physical_idle.insert(*fkd); - } - CustomAction::FakeKeyHoldForDuration(fk_hfd) => { - let duration = fk_hfd.hold_duration; - self.vkeys_pending_release.entry(fk_hfd.coord) - .and_modify(|d| *d = duration) - .or_insert_with(|| { - let Coord { x, y } = fk_hfd.coord; - layout.event(Event::Press(x, y)); - duration - }); - } - CustomAction::ClipboardSet(clipboard_string) => { - clpb_set(clipboard_string); - } - CustomAction::ClipboardCmdSet(cmd_params) => { - clpb_cmd_set(cmd_params); + // Release key in case the most recently pressed key is still pressed. + release_key(&mut self.kbd_out, osc)?; + press_key(&mut self.kbd_out, osc)?; + release_key(&mut self.kbd_out, osc)?; + if do_caps_word { + self.kbd_out.release_key(OsCode::KEY_LEFTSHIFT)?; } - CustomAction::ClipboardSave(id) => { - clpb_save(*id, &mut self.saved_clipboard_content); + } + CustomAction::DynamicMacroRecord(macro_id) => { + if let Some((macro_id, prev_recorded_macro)) = + begin_record_macro(*macro_id, &mut self.dynamic_macro_record_state) + { + log::debug!("saving macro {prev_recorded_macro:?}"); + self.dynamic_macros.insert(macro_id, prev_recorded_macro); } - CustomAction::ClipboardRestore(id) => { - clpb_restore(*id, &self.saved_clipboard_content); + } + CustomAction::DynamicMacroRecordStop(num_actions_to_remove) => { + if let Some((macro_id, prev_recorded_macro)) = stop_macro( + &mut self.dynamic_macro_record_state, + *num_actions_to_remove, + ) { + log::debug!("saving macro {prev_recorded_macro:?}"); + self.dynamic_macros.insert(macro_id, prev_recorded_macro); } - CustomAction::ClipboardSaveSet(id, clipboard_string) => { - clpb_save_set(*id, clipboard_string, &mut self.saved_clipboard_content); + } + CustomAction::DynamicMacroPlay(macro_id) => { + play_macro( + *macro_id, + &mut self.dynamic_macro_replay_state, + &self.dynamic_macros, + ); + } + CustomAction::CancelMacroOnNextPress(duration) => { + self.macro_on_press_cancel_duration = *duration; + } + CustomAction::SendArbitraryCode(code) => { + #[cfg(all(not(feature = "simulated_output"), target_os = "windows"))] + { + self.kbd_out.write_code_raw(*code, KeyValue::Press)?; } - CustomAction::ClipboardSaveCmdSet(id, cmd_params) => { - clpb_save_cmd_set(*id, cmd_params, &mut self.saved_clipboard_content); + #[cfg(any(feature = "simulated_output", not(target_os = "windows")))] + { + self.kbd_out.write_code(*code as u32, KeyValue::Press)?; } - CustomAction::ClipboardSaveSwap(id1, id2) => { - clpb_save_swap(*id1, *id2, &mut self.saved_clipboard_content); + } + CustomAction::CapsWord(cfg) => match cfg.repress_behaviour { + CapsWordRepressBehaviour::Overwrite => { + log::trace!("caps-word overwrite"); + self.caps_word = Some(CapsWordState::new(cfg)); + } + CapsWordRepressBehaviour::Toggle => { + log::trace!("caps-word toggle"); + self.caps_word = match self.caps_word { + Some(_) => None, + None => Some(CapsWordState::new(cfg)), + }; } - CustomAction::FakeKeyOnRelease { .. } - | CustomAction::DelayOnRelease(_) - | CustomAction::Unmodded { .. } - | CustomAction::Unshifted { .. } - // Note: ReverseReleaseOrder is already handled earlier on. - | CustomAction::ReverseReleaseOrder - | CustomAction::CancelMacroOnRelease => {} + }, + CustomAction::SetMouse { x, y } => { + self.kbd_out.set_mouse(*x, *y)?; + } + CustomAction::FakeKeyOnIdle(fkd) => { + self.ticks_since_idle = 0; + self.waiting_for_idle.insert(*fkd); + } + CustomAction::FakeKeyOnPhysicalIdle(fkd) => { + self.ticks_since_physical_idle = 0; + self.waiting_for_physical_idle.insert(*fkd); + } + CustomAction::FakeKeyHoldForDuration(fk_hfd) => { + let duration = fk_hfd.hold_duration; + self.vkeys_pending_release.entry(fk_hfd.coord) + .and_modify(|d| *d = duration) + .or_insert_with(|| { + let Coord { x, y } = fk_hfd.coord; + layout.event(Event::Press(x, y)); + duration + }); + } + CustomAction::ClipboardSet(clipboard_string) => { + clpb_set(clipboard_string); + } + CustomAction::ClipboardCmdSet(cmd_params) => { + clpb_cmd_set(cmd_params); + } + CustomAction::ClipboardSave(id) => { + clpb_save(*id, &mut self.saved_clipboard_content); + } + CustomAction::ClipboardRestore(id) => { + clpb_restore(*id, &self.saved_clipboard_content); } + CustomAction::ClipboardSaveSet(id, clipboard_string) => { + clpb_save_set(*id, clipboard_string, &mut self.saved_clipboard_content); + } + CustomAction::ClipboardSaveCmdSet(id, cmd_params) => { + clpb_save_cmd_set(*id, cmd_params, &mut self.saved_clipboard_content); + } + CustomAction::ClipboardSaveSwap(id1, id2) => { + clpb_save_swap(*id1, *id2, &mut self.saved_clipboard_content); + } + CustomAction::FakeKeyOnRelease { .. } + | CustomAction::DelayOnRelease(_) + | CustomAction::Unmodded { .. } + | CustomAction::Unshifted { .. } + // Note: ReverseReleaseOrder is already handled earlier on. + | CustomAction::ReverseReleaseOrder + | CustomAction::CancelMacroOnRelease => {} } #[cfg(feature = "cmd")] run_multi_cmd(cmds); @@ -1876,131 +1860,102 @@ impl Kanata { } } - CustomEvent::Release(custacts) => { - // Unclick only the last mouse button - if let Some(Err(e)) = custacts - .iter() - .fold(None, |pbtn, ac| match ac { - CustomAction::Mouse(btn) => Some(btn), - CustomAction::MWheel { direction, .. } => { - match direction { - MWheelDirection::Up | MWheelDirection::Down => { - if let Some(ss) = &mut self.scroll_state - && ss.direction == *direction - { - ss.distance = 0; - if let Some(acs) = &mut ss.scroll_accel_state { - acs.scroll_released = true - } - } - } - MWheelDirection::Left | MWheelDirection::Right => { - if let Some(ss) = &mut self.hscroll_state - && ss.direction == *direction - { - ss.distance = 0; - if let Some(acs) = &mut ss.scroll_accel_state { - acs.scroll_released = true - } - } - } + CustomEvent::Release(custact) => match custact { + CustomAction::Mouse(btn) => { + self.kbd_out.release_btn(*btn)?; + } + CustomAction::MWheel { direction, .. } => match direction { + MWheelDirection::Up | MWheelDirection::Down => { + if let Some(ss) = &mut self.scroll_state + && ss.direction == *direction + { + ss.distance = 0; + if let Some(acs) = &mut ss.scroll_accel_state { + acs.scroll_released = true } - pbtn } - CustomAction::MoveMouse { direction, .. } - | CustomAction::MoveMouseAccel { direction, .. } => { - match direction { - MoveDirection::Up | MoveDirection::Down => { - if let Some(move_mouse_state_vertical) = - &self.move_mouse_state_vertical - && move_mouse_state_vertical.direction == *direction - { - self.move_mouse_state_vertical = None; - } - } - MoveDirection::Left | MoveDirection::Right => { - if let Some(move_mouse_state_horizontal) = - &self.move_mouse_state_horizontal - && move_mouse_state_horizontal.direction == *direction - { - self.move_mouse_state_horizontal = None; - } - } - } - if self.movemouse_smooth_diagonals { - self.movemouse_buffer = None + } + MWheelDirection::Left | MWheelDirection::Right => { + if let Some(ss) = &mut self.hscroll_state + && ss.direction == *direction + { + ss.distance = 0; + if let Some(acs) = &mut ss.scroll_accel_state { + acs.scroll_released = true } - pbtn } - CustomAction::MoveMouseSpeed { speed, .. } => { - if let Some(idx) = self - .move_mouse_speed_modifiers - .iter() - .position(|s| *s == *speed) + } + }, + CustomAction::MoveMouse { direction, .. } + | CustomAction::MoveMouseAccel { direction, .. } => { + match direction { + MoveDirection::Up | MoveDirection::Down => { + if let Some(move_mouse_state_vertical) = &self.move_mouse_state_vertical + && move_mouse_state_vertical.direction == *direction { - self.move_mouse_speed_modifiers.remove(idx); + self.move_mouse_state_vertical = None; } - log::debug!( - "movemousespeed modifiers: {:?}", - self.move_mouse_speed_modifiers - ); - pbtn - } - CustomAction::DelayOnRelease(delay) => { - log::debug!("on-release: sleeping for {delay} ms"); - std::thread::sleep(time::Duration::from_millis((*delay).into())); - pbtn - } - CustomAction::FakeKeyOnRelease { coord, action } => { - let (x, y) = (coord.x, coord.y); - log::debug!("fake key on release {action:?} {x:?},{y:?}"); - handle_fakekey_action(*action, layout, x, y); - pbtn - } - CustomAction::CancelMacroOnRelease => { - log::debug!("cancelling all macros: releasable macro"); - layout.active_sequences.clear(); - self.macro_on_press_cancel_duration = 0; - layout.states.retain(|s| { - !matches!( - s, - State::FakeKey { .. } | State::RepeatingSequence { .. } - ) - }); - pbtn } - CustomAction::SendArbitraryCode(code) => { - if let Err(e) = { - #[cfg(all( - not(feature = "simulated_output"), - target_os = "windows" - ))] - { - self.kbd_out.write_code_raw(*code, KeyValue::Release) - } - #[cfg(any( - feature = "simulated_output", - not(target_os = "windows") - ))] - { - self.kbd_out.write_code(*code as u32, KeyValue::Release) - } - } { - log::error!("failed to release arbitrary code {e:?}"); + MoveDirection::Left | MoveDirection::Right => { + if let Some(move_mouse_state_horizontal) = + &self.move_mouse_state_horizontal + && move_mouse_state_horizontal.direction == *direction + { + self.move_mouse_state_horizontal = None; } - pbtn } - _ => pbtn, - }) - .map(|btn| { - log::debug!("unclick {:?}", btn); - self.kbd_out.release_btn(*btn) - }) - { - bail!(e); + } + if self.movemouse_smooth_diagonals { + self.movemouse_buffer = None + } } - } - _ => {} + CustomAction::MoveMouseSpeed { speed, .. } => { + if let Some(idx) = self + .move_mouse_speed_modifiers + .iter() + .position(|s| *s == *speed) + { + self.move_mouse_speed_modifiers.remove(idx); + } + log::debug!( + "movemousespeed modifiers: {:?}", + self.move_mouse_speed_modifiers + ); + } + CustomAction::DelayOnRelease(delay) => { + log::debug!("on-release: sleeping for {delay} ms"); + std::thread::sleep(time::Duration::from_millis((*delay).into())); + } + CustomAction::FakeKeyOnRelease { coord, action } => { + let (x, y) = (coord.x, coord.y); + log::debug!("fake key on release {action:?} {x:?},{y:?}"); + handle_fakekey_action(*action, layout, x, y); + } + CustomAction::CancelMacroOnRelease => { + log::debug!("cancelling all macros: releasable macro"); + layout.active_sequences.clear(); + self.macro_on_press_cancel_duration = 0; + layout.states.retain(|s| { + !matches!(s, State::FakeKey { .. } | State::RepeatingSequence { .. }) + }); + } + CustomAction::SendArbitraryCode(code) => { + if let Err(e) = { + #[cfg(all(not(feature = "simulated_output"), target_os = "windows"))] + { + self.kbd_out.write_code_raw(*code, KeyValue::Release) + } + #[cfg(any(feature = "simulated_output", not(target_os = "windows")))] + { + self.kbd_out.write_code(*code as u32, KeyValue::Release) + } + } { + log::error!("failed to release arbitrary code {e:?}"); + } + } + _ => {} + }, + CustomEvent::NoEvent => {} }; self.check_handle_layer_change(_tx); @@ -2530,6 +2485,7 @@ impl Kanata { && layout.active_sequences.is_empty() && layout.tap_dance_eager.is_none() && layout.action_queue.is_empty() + && layout.custom_event_release_queue.is_empty() && self.sequence_state.is_inactive() && self.scroll_state.is_none() && self.hscroll_state.is_none() diff --git a/src/kanata/sequences.rs b/src/kanata/sequences.rs index e9efb4e99..f184e40f7 100644 --- a/src/kanata/sequences.rs +++ b/src/kanata/sequences.rs @@ -274,7 +274,7 @@ use kanata_keyberon::key_code::KeyCode::*; pub(super) fn do_successful_sequence_termination( kbd_out: &mut KbdOut, state: &mut SequenceState, - layout: &mut Layout<'_, 767, 2, &&[&CustomAction]>, + layout: &mut Layout<'_, 767, 2, &CustomAction>, i: u8, j: u16, seq_type: EndSequenceType, diff --git a/src/tests/sim_tests/chord_sim_tests.rs b/src/tests/sim_tests/chord_sim_tests.rs index 443a3633c..10b83267e 100644 --- a/src/tests/sim_tests/chord_sim_tests.rs +++ b/src/tests/sim_tests/chord_sim_tests.rs @@ -307,8 +307,7 @@ fn sim_chord_eager_tapholdpress_activation() { ) .to_ascii(); assert_eq!( - "t:11ms dn:LCtrl t:7ms dn:BSpace t:92ms \ - dn:BSpace t:10ms dn:BSpace t:14ms up:BSpace t:96ms up:LCtrl", + "t:12ms dn:LCtrl t:7ms dn:BSpace t:91ms dn:BSpace t:10ms dn:BSpace t:15ms up:BSpace t:95ms up:LCtrl", result ); } @@ -342,7 +341,7 @@ fn sim_chord_eager_tapholdrelease_activation() { ) .to_ascii(); assert_eq!( - "t:20ms dn:LCtrl t:7ms dn:BSpace t:5ms up:BSpace t:88ms up:LCtrl", + "t:20ms dn:LCtrl t:7ms dn:BSpace t:6ms up:BSpace t:87ms up:LCtrl", result ); } diff --git a/src/tests/sim_tests/macro_sim_tests.rs b/src/tests/sim_tests/macro_sim_tests.rs index fc6321775..2293801d7 100644 --- a/src/tests/sim_tests/macro_sim_tests.rs +++ b/src/tests/sim_tests/macro_sim_tests.rs @@ -52,32 +52,32 @@ fn macro_release_cancel_and_cancel_on_press() { fn test_release_and_on_press(cfg: &str) { // Cancellation should happen for press. let result = simulate(cfg, "d:a t:50 d:c t:100").to_ascii(); - assert_eq!("t:1ms dn:Z t:1ms up:Z t:48ms dn:C", result); + assert_eq!("t:2ms dn:Z t:1ms up:Z t:47ms dn:C", result); // Cancellation should happen for release let result = simulate(cfg, "d:a u:a t:150 d:c t:100").to_ascii(); - assert_eq!("t:1ms dn:Z t:1ms up:Z t:148ms dn:C", result); + assert_eq!("t:2ms dn:Z t:1ms up:Z t:147ms dn:C", result); // Macro should complete if allowed to. let result = simulate(cfg, "d:a t:150 d:c t:100").to_ascii(); assert_eq!( - "t:1ms dn:Z t:1ms up:Z t:101ms dn:Y t:1ms up:Y t:46ms dn:C", + "t:2ms dn:Z t:1ms up:Z t:101ms dn:Y t:1ms up:Y t:45ms dn:C", result ); // The window for macro cancellation should not persist to a new macro that is not cancellable. let result = simulate(cfg, "d:a t:120 d:b t:20 d:c t:100").to_ascii(); assert_eq!( - "t:1ms dn:Z t:1ms up:Z t:101ms dn:Y t:1ms up:Y \ - t:17ms dn:X t:1ms up:X t:18ms dn:C t:83ms dn:W t:1ms up:W", + "t:2ms dn:Z t:1ms up:Z t:101ms dn:Y t:1ms up:Y \ + t:16ms dn:X t:1ms up:X t:18ms dn:C t:83ms dn:W t:1ms up:W", result ); let result = simulate(cfg, "d:a t:10 d:c u:c t:10 d:b t:20 d:c t:100").to_ascii(); assert_eq!( - "t:1ms dn:Z t:1ms up:Z t:8ms dn:C t:1ms up:C t:10ms \ + "t:2ms dn:Z t:1ms up:Z t:7ms dn:C t:1ms up:C t:10ms \ dn:X t:1ms up:X t:18ms dn:C t:83ms dn:W t:1ms up:W", result ); let result = simulate(cfg, "d:a u:a t:10 t:10 d:b u:b t:20 d:c t:100").to_ascii(); assert_eq!( - "t:1ms dn:Z t:1ms up:Z t:19ms \ + "t:2ms dn:Z t:1ms up:Z t:18ms \ dn:X t:1ms up:X t:18ms dn:C t:83ms dn:W t:1ms up:W", result ); @@ -109,7 +109,7 @@ fn macro_repeat() { ); let result = simulate(cfg, "d:d t:125 u:d").to_ascii(); assert_eq!( - "t:1ms dn:Kb1 t:1ms up:Kb1 t:52ms dn:Kb1 t:1ms up:Kb1 t:52ms dn:Kb1 t:1ms up:Kb1", + "t:2ms dn:Kb1 t:1ms up:Kb1 t:52ms dn:Kb1 t:1ms up:Kb1 t:52ms dn:Kb1 t:1ms up:Kb1", result ); } diff --git a/src/tests/sim_tests/mod.rs b/src/tests/sim_tests/mod.rs index 154578f1c..a4b2681e8 100644 --- a/src/tests/sim_tests/mod.rs +++ b/src/tests/sim_tests/mod.rs @@ -61,6 +61,7 @@ mod chord_sim_tests; mod delay_tests; mod layer_sim_tests; mod macro_sim_tests; +mod mouse_sim_tests; mod oneshot_tests; mod output_chord_tests; mod override_tests; diff --git a/src/tests/sim_tests/mouse_sim_tests.rs b/src/tests/sim_tests/mouse_sim_tests.rs new file mode 100644 index 000000000..649abfaeb --- /dev/null +++ b/src/tests/sim_tests/mouse_sim_tests.rs @@ -0,0 +1,14 @@ +use super::*; + +#[test] +fn multi_mouse_button_does_multi_click_release_single_hold() { + let result = simulate( + "(defsrc) (deflayermap (base) a (multi mmid mmid mrgt mlft))", + "d:a t:50 u:a t:50", + ) + .to_ascii(); + assert_eq!( + "out🖰:↓Mid out🖰:↑Mid t:1ms out🖰:↓Mid out🖰:↑Mid t:1ms out🖰:↓Right out🖰:↑Right t:1ms out🖰:↓Left t:50ms out🖰:↑Left", + result + ); +} diff --git a/src/tests/sim_tests/unicode_sim_tests.rs b/src/tests/sim_tests/unicode_sim_tests.rs index 5b9e7573c..387394042 100644 --- a/src/tests/sim_tests/unicode_sim_tests.rs +++ b/src/tests/sim_tests/unicode_sim_tests.rs @@ -53,3 +53,16 @@ fn unicode_pulus() { .no_time(); assert_eq!("outU:🚆 outU:🚆", result); } + +#[test] +fn unicode_multi() { + let result = simulate( + " + (defsrc a) + (deflayer l (multi (unicode a) (fork (unicode b) XX ()))) + ", + "d:KeyA t:5 u:KeyA t:5", + ) + .to_ascii(); + assert_eq!("outU:a t:1ms outU:b", result); +}