kanata_parser/
custom_action.rs

1//! This module contains the "Custom" actions that are used with the keyberon layout.
2//!
3//! When adding a new custom action, the macro section of the config.adoc documentation may need to
4//! be updated, to include the new action to the documented list of supported actions in macro.
5
6use anyhow::{Result, anyhow};
7use core::fmt;
8use kanata_keyberon::key_code::KeyCode;
9
10use crate::{cfg::SimpleSExpr, keys::OsCode};
11
12// TODO:
13// Live reload has a memory leak
14// on allocated types in this enum.
15// To fix, the types should be changed to &'static allocation types
16// that are created by calls to `Allocations`.
17#[derive(Debug, Clone, PartialEq, Eq, Hash)]
18pub enum CustomAction {
19    Cmd(Vec<String>),
20    CmdLog(LogLevel, LogLevel, Vec<String>),
21    CmdOutputKeys(Vec<String>),
22    PushMessage(Vec<SimpleSExpr>),
23    Unicode(char),
24    Mouse(Btn),
25    MouseTap(Btn),
26    FakeKey {
27        coord: Coord,
28        action: FakeKeyAction,
29    },
30    FakeKeyOnRelease {
31        coord: Coord,
32        action: FakeKeyAction,
33    },
34    FakeKeyOnIdle(FakeKeyOnIdle),
35    FakeKeyOnPhysicalIdle(FakeKeyOnIdle),
36    FakeKeyHoldForDuration(FakeKeyHoldForDuration),
37    Delay(u16),
38    DelayOnRelease(u16),
39    MWheel {
40        direction: MWheelDirection,
41        interval: u16,
42        distance: u16,
43        inertial_scroll_params: Option<Box<MWheelInertial>>,
44    },
45    MWheelNotch {
46        direction: MWheelDirection,
47    },
48    MoveMouse {
49        direction: MoveDirection,
50        interval: u16,
51        distance: u16,
52    },
53    MoveMouseAccel {
54        direction: MoveDirection,
55        interval: u16,
56        accel_time: u16,
57        min_distance: u16,
58        max_distance: u16,
59    },
60    MoveMouseSpeed {
61        speed: u16,
62    },
63    SequenceCancel,
64    SequenceLeader(u16, SequenceInputMode),
65    /// Purpose:
66    /// In case the user has dead keys in their OS layout, they may wish to send fewer backspaces upon
67    /// a successful completion of visible-backspaced sequences, because the number of key events
68    /// is larger than the number of backspace-able symbols typed within the application.
69    /// This custom action is a marker to accomplish the use case.
70    SequenceNoerase(u16),
71    LiveReload,
72    LiveReloadNext,
73    LiveReloadPrev,
74    /// Live-reload the n'th configuration file provided on the CLI. This should begin with 0 as
75    /// the first configuration file provided. The rest of the parser code is free to choose 0 or 1
76    /// as the user-facing value though.
77    LiveReloadNum(u16),
78    LiveReloadFile(String),
79    Repeat,
80    CancelMacroOnRelease,
81    CancelMacroOnNextPress(u32),
82    DynamicMacroRecord(u16),
83    DynamicMacroRecordStop(u16),
84    DynamicMacroPlay(u16),
85    SendArbitraryCode(u16),
86    CapsWord(CapsWordCfg),
87    SetMouse {
88        x: u16,
89        y: u16,
90    },
91    Unmodded {
92        keys: Box<[KeyCode]>,
93        mods: UnmodMods,
94    },
95    Unshifted {
96        keys: Box<[KeyCode]>,
97    },
98    ReverseReleaseOrder,
99    ClipboardSet(String),
100    ClipboardCmdSet(Vec<String>),
101    ClipboardSave(u16),
102    ClipboardRestore(u16),
103    ClipboardSaveSet(u16, String),
104    ClipboardSaveCmdSet(u16, Vec<String>),
105    ClipboardSaveSwap(u16, u16),
106}
107
108#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
109pub enum Btn {
110    Left,
111    Right,
112    Mid,
113    Forward,
114    Backward,
115}
116
117impl fmt::Display for Btn {
118    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
119        match self {
120            Btn::Left => write!(f, "‹🖰"),
121            Btn::Right => write!(f, "🖰›"),
122            Btn::Mid => write!(f, "🖱"),
123            Btn::Backward => write!(f, "⎌🖰"),
124            Btn::Forward => write!(f, "🖰↷"),
125        }
126    }
127}
128
129#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
130pub struct Coord {
131    pub x: u8,
132    pub y: u16,
133}
134
135#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
136pub enum FakeKeyAction {
137    Press,
138    Release,
139    Tap,
140    Toggle,
141}
142
143/// An active waiting-for-idle state.
144#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
145pub struct FakeKeyOnIdle {
146    pub coord: Coord,
147    pub action: FakeKeyAction,
148    pub idle_duration: u16,
149}
150
151/// Information for an action that presses a fake key / vkey that becomes released on a
152/// renewable-when-reactivated deadline.
153#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
154pub struct FakeKeyHoldForDuration {
155    pub coord: Coord,
156    pub hold_duration: u16,
157}
158
159#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
160pub enum MWheelDirection {
161    Up,
162    Down,
163    Left,
164    Right,
165}
166impl fmt::Display for MWheelDirection {
167    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
168        match self {
169            MWheelDirection::Up => write!(f, "🖱↑"),
170            MWheelDirection::Down => write!(f, "🖱↓"),
171            MWheelDirection::Left => write!(f, "🖱←"),
172            MWheelDirection::Right => write!(f, "🖱→"),
173        }
174    }
175}
176
177impl TryFrom<OsCode> for MWheelDirection {
178    type Error = ();
179    fn try_from(value: OsCode) -> Result<Self, Self::Error> {
180        use OsCode::*;
181        Ok(match value {
182            MouseWheelUp => MWheelDirection::Up,
183            MouseWheelDown => MWheelDirection::Down,
184            MouseWheelLeft => MWheelDirection::Left,
185            MouseWheelRight => MWheelDirection::Right,
186            _ => return Err(()),
187        })
188    }
189}
190
191#[derive(Debug, Copy, Clone, PartialEq, Hash, Eq)]
192pub struct MWheelInertial {
193    pub initial_velocity: ordered_float::OrderedFloat<f32>,
194    pub maximum_velocity: ordered_float::OrderedFloat<f32>,
195    pub acceleration_multiplier: ordered_float::OrderedFloat<f32>,
196    pub deceleration_multiplier: ordered_float::OrderedFloat<f32>,
197}
198
199#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
200pub enum MoveDirection {
201    Up,
202    Down,
203    Left,
204    Right,
205}
206impl fmt::Display for MoveDirection {
207    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
208        match self {
209            MoveDirection::Up => write!(f, "↑"),
210            MoveDirection::Down => write!(f, "↓"),
211            MoveDirection::Left => write!(f, "←"),
212            MoveDirection::Right => write!(f, "→"),
213        }
214    }
215}
216
217#[derive(Debug, Clone, PartialEq, Eq, Hash)]
218pub struct CapsWordCfg {
219    pub keys_to_capitalize: &'static [KeyCode],
220    pub keys_nonterminal: &'static [KeyCode],
221    pub timeout: u16,
222    pub repress_behaviour: CapsWordRepressBehaviour,
223}
224
225#[derive(Debug, Clone, PartialEq, Eq, Hash)]
226pub enum CapsWordRepressBehaviour {
227    Overwrite,
228    Toggle,
229}
230
231/// This controls the behaviour of kanata when sequence mode is initiated by the sequence leader
232/// action.
233///
234/// - `HiddenSuppressed` hides the keys typed as part of the sequence and does not output the keys
235///   typed when an invalid sequence is the result of an invalid sequence character or a timeout.
236/// - `HiddenDelayType` hides the keys typed as part of the sequence and outputs the keys when an
237///   typed when an invalid sequence is the result of an invalid sequence character or a timeout.
238/// - `VisibleBackspaced` will type the keys that are typed as part of the sequence but will
239///   backspace the typed sequence keys before performing the fake key tap when a valid sequence is
240///   the result.
241#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
242pub enum SequenceInputMode {
243    HiddenSuppressed,
244    HiddenDelayType,
245    VisibleBackspaced,
246}
247
248const SEQ_VISIBLE_BACKSPACED: &str = "visible-backspaced";
249const SEQ_HIDDEN_SUPPRESSED: &str = "hidden-suppressed";
250const SEQ_HIDDEN_DELAY_TYPE: &str = "hidden-delay-type";
251
252impl SequenceInputMode {
253    pub fn try_from_str(s: &str) -> Result<Self> {
254        match s {
255            SEQ_VISIBLE_BACKSPACED => Ok(SequenceInputMode::VisibleBackspaced),
256            SEQ_HIDDEN_SUPPRESSED => Ok(SequenceInputMode::HiddenSuppressed),
257            SEQ_HIDDEN_DELAY_TYPE => Ok(SequenceInputMode::HiddenDelayType),
258            _ => Err(anyhow!(SequenceInputMode::err_msg())),
259        }
260    }
261
262    pub fn err_msg() -> String {
263        format!(
264            "sequence input mode must be one of: {SEQ_VISIBLE_BACKSPACED}, {SEQ_HIDDEN_SUPPRESSED}, {SEQ_HIDDEN_DELAY_TYPE}"
265        )
266    }
267}
268
269const LOG_LEVEL_DEBUG: &str = "debug";
270const LOG_LEVEL_INFO: &str = "info";
271const LOG_LEVEL_WARN: &str = "warn";
272const LOG_LEVEL_ERROR: &str = "error";
273const LOG_LEVEL_NONE: &str = "none";
274
275#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
276pub enum LogLevel {
277    // No trace here because that wouldn't make sense
278    Debug,
279    Info,
280    Warn,
281    Error,
282    None,
283}
284
285impl LogLevel {
286    pub fn try_from_str(s: &str) -> Result<Self> {
287        match s {
288            LOG_LEVEL_DEBUG => Ok(LogLevel::Debug),
289            LOG_LEVEL_INFO => Ok(LogLevel::Info),
290            LOG_LEVEL_WARN => Ok(LogLevel::Warn),
291            LOG_LEVEL_ERROR => Ok(LogLevel::Error),
292            LOG_LEVEL_NONE => Ok(LogLevel::None),
293            _ => Err(anyhow!(LogLevel::err_msg())),
294        }
295    }
296
297    pub fn get_level(&self) -> Option<log::Level> {
298        match self {
299            LogLevel::Debug => Some(log::Level::Debug),
300            LogLevel::Info => Some(log::Level::Info),
301            LogLevel::Warn => Some(log::Level::Warn),
302            LogLevel::Error => Some(log::Level::Error),
303            LogLevel::None => None,
304        }
305    }
306
307    pub fn err_msg() -> String {
308        format!(
309            "log level must be one of: {LOG_LEVEL_DEBUG}, {LOG_LEVEL_INFO}, {LOG_LEVEL_WARN}, {LOG_LEVEL_ERROR}, {LOG_LEVEL_NONE}"
310        )
311    }
312}
313
314#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
315pub struct UnmodMods(u8);
316
317bitflags::bitflags! {
318    impl UnmodMods: u8 {
319        const LSft = 0b00000001;
320        const RSft = 0b00000010;
321        const LAlt = 0b00000100;
322        const RAlt = 0b00001000;
323        const LCtl = 0b00010000;
324        const RCtl = 0b00100000;
325        const LMet = 0b01000000;
326        const RMet = 0b10000000;
327    }
328}