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