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