├── .gitignore ├── assets └── sounds │ ├── click1.wav │ ├── click2.wav │ ├── error.wav │ ├── sticky.wav │ └── LINKS.txt ├── etc ├── 99-uinput.rules └── ktrl.service ├── .github └── workflows │ └── rust.yml ├── docs └── dropping-root.md ├── py ├── ktrl_notify_sub.py └── ktrl_client.py ├── src ├── effects │ ├── sticky.rs │ ├── dj.rs │ ├── mod.rs │ └── perform.rs ├── cfg.rs ├── actions │ ├── mod.rs │ ├── tap_mod.rs │ ├── tap_dance.rs │ └── tap_hold.rs ├── kbd_in.rs ├── ipc.rs ├── devices.rs ├── kbd_out.rs ├── ktrl.rs ├── main.rs ├── layers.rs └── keys.rs ├── Cargo.toml ├── examples └── cfg.ron ├── LICENSE ├── README.md └── Cargo.lock /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | .vscode/ 3 | -------------------------------------------------------------------------------- /assets/sounds/click1.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ItayGarin/ktrl/HEAD/assets/sounds/click1.wav -------------------------------------------------------------------------------- /assets/sounds/click2.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ItayGarin/ktrl/HEAD/assets/sounds/click2.wav -------------------------------------------------------------------------------- /assets/sounds/error.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ItayGarin/ktrl/HEAD/assets/sounds/error.wav -------------------------------------------------------------------------------- /assets/sounds/sticky.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ItayGarin/ktrl/HEAD/assets/sounds/sticky.wav -------------------------------------------------------------------------------- /etc/99-uinput.rules: -------------------------------------------------------------------------------- 1 | KERNEL=="uinput", MODE="0660", GROUP="uinput", OPTIONS+="static_node=uinput" 2 | -------------------------------------------------------------------------------- /etc/ktrl.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=ktrl 3 | 4 | [Service] 5 | User=ktrl 6 | Environment=HOME=/opt/ktrl 7 | ExecStart=/usr/local/bin/ktrl -d 8 | 9 | [Install] 10 | WantedBy=multi-user.target 11 | -------------------------------------------------------------------------------- /assets/sounds/LINKS.txt: -------------------------------------------------------------------------------- 1 | error - https://freesound.org/people/ecfike/sounds/135125/ 2 | click1 - https://freesound.org/people/OtisJames/sounds/215772/ 3 | click2 - https://freesound.org/people/Rudmer_Rotteveel/sounds/457454/ 4 | sticky - don't remember also from freesound 5 | -------------------------------------------------------------------------------- /.github/workflows/rust.yml: -------------------------------------------------------------------------------- 1 | name: Rust 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | pull_request: 7 | branches: [ master ] 8 | 9 | env: 10 | CARGO_TERM_COLOR: always 11 | 12 | jobs: 13 | build: 14 | 15 | runs-on: ubuntu-latest 16 | 17 | steps: 18 | - uses: actions/checkout@v2 19 | - name: Build 20 | run: cargo build --verbose 21 | - name: Run tests 22 | run: cargo test --verbose 23 | -------------------------------------------------------------------------------- /docs/dropping-root.md: -------------------------------------------------------------------------------- 1 | Create a user for ktrl and add it to the input groups 2 | 3 | ``` 4 | sudo useradd -r -s /bin/false ktrl 5 | sudo groupadd uinput 6 | sudo usermod -aG input ktrl 7 | sudo usermod -aG uinput ktrl 8 | 9 | # If you're using the sound feature 10 | sudo usermod -aG audio ktrl 11 | ``` 12 | 13 | Add a new udev rule 14 | 15 | ``` 16 | sudo touch /etc/udev/rules.d/99-uinput.rules 17 | ``` 18 | 19 | Add the following line to it 20 | 21 | ``` 22 | KERNEL=="uinput", MODE="0660", GROUP="uinput", OPTIONS+="static_node=uinput" 23 | ``` 24 | 25 | If you are using the default `/opt/ktrl` directory - 26 | 27 | ``` 28 | sudo chown -R ktrl:$USER /opt/ktrl 29 | sudo chmod -R 0770 /opt/ktrl 30 | ``` 31 | -------------------------------------------------------------------------------- /py/ktrl_notify_sub.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | import zmq 3 | import argparse 4 | 5 | DEFAULT_NOTIFY_PORT = 7333 6 | 7 | def main(): 8 | parser = argparse.ArgumentParser() 9 | parser.add_argument("--port", help="ktrl's notify port") 10 | args = parser.parse_args() 11 | 12 | if args.port == None: 13 | port = DEFAULT_NOTIFY_PORT 14 | else: 15 | port = int(args.port) 16 | 17 | context = zmq.Context() 18 | endpoint = "tcp://127.0.0.1:" + str(port) 19 | socket = context.socket(zmq.SUB) 20 | socket.connect(endpoint) 21 | socket.setsockopt(zmq.SUBSCRIBE, b"layer") 22 | print("Connected to ktrl's notification server: " + endpoint) 23 | 24 | while True: 25 | message = socket.recv() 26 | print("NOTIFY: [ %s ]" % message) 27 | 28 | 29 | if __name__ == "__main__": 30 | main() 31 | -------------------------------------------------------------------------------- /src/effects/sticky.rs: -------------------------------------------------------------------------------- 1 | use crate::keys::KeyCode; 2 | use crate::layers::LayersManager; 3 | use crate::layers::LockOwner::LkSticky; 4 | use log::debug; 5 | use std::collections::HashSet; 6 | 7 | pub struct StickyState { 8 | pressed: HashSet, 9 | } 10 | 11 | impl StickyState { 12 | pub fn new() -> Self { 13 | Self { 14 | pressed: HashSet::new(), 15 | } 16 | } 17 | 18 | pub fn update_pressed(&mut self, l_mgr: &mut LayersManager, key: KeyCode) { 19 | debug!("Activating sticky {:?}", key); 20 | self.pressed.insert(key); 21 | l_mgr.lock_all(LkSticky); 22 | } 23 | 24 | pub fn update_released(&mut self, l_mgr: &mut LayersManager, key: KeyCode) { 25 | debug!("Deactivating sticky {:?}", key); 26 | self.pressed.remove(&key); 27 | l_mgr.unlock_all(LkSticky); 28 | } 29 | 30 | pub fn is_pressed(&mut self, key: KeyCode) -> bool { 31 | self.pressed.contains(&key) 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "ktrl" 3 | version = "0.1.8" 4 | authors = ["Itay Garin "] 5 | description = "A Supercharged Keyboard Programming Daemon" 6 | keywords = ["cli", "linux", "daemon", "keyboard", "layout"] 7 | categories = ["command-line-utilities"] 8 | homepage = "https://github.com/ItayGarin/ktrl" 9 | repository = "https://github.com/ItayGarin/ktrl" 10 | documentation = "https://github.com/ItayGarin/ktrl" 11 | readme = "README.md" 12 | license = "GPL-3.0" 13 | edition = "2018" 14 | 15 | [dependencies] 16 | evdev-rs = "0.4.0" 17 | uinput-sys = "0.1.7" 18 | libc = "0.2.70" 19 | lazy_static = "1.4.0" 20 | inner = "0.1.1" 21 | serde = "1.0.110" 22 | ron = "0.6.0" 23 | clap = "2.33.1" 24 | log = "0.4.8" 25 | simplelog = "0.8.0" 26 | nix = "0.17.0" 27 | zmq = {version = "0.9.2", optional = true} 28 | rodio = {version = "0.11.0", optional = true} 29 | enum-iterator = {version = "0.6.0", optional = true} 30 | regex = "1.7.1" 31 | inotify = "0.10.0" 32 | retry = "2.0.0" 33 | 34 | [features] 35 | sound = ["rodio", "enum-iterator"] 36 | ipc = ["zmq"] 37 | notify = ["zmq"] 38 | -------------------------------------------------------------------------------- /py/ktrl_client.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | import zmq 3 | import argparse 4 | 5 | DEFAULT_IPC_PORT = 7331 6 | 7 | # 8 | # Example Usage: 9 | # -------------- 10 | # 11 | # ./ktrl_client.py --port 123456 "IpcDoEffect((fx: NoOp, val: Press))" 12 | # ./ktrl_client.py "IpcDoEffect((fx: NoOp, val: Press))" 13 | # 14 | 15 | def main(): 16 | parser = argparse.ArgumentParser() 17 | parser.add_argument("--port", help="ktrl's ipc port") 18 | parser.add_argument("msg", help="ipc msg to send to ktrl") 19 | args = parser.parse_args() 20 | 21 | if args.port == None: 22 | port = DEFAULT_IPC_PORT 23 | else: 24 | port = int(args.port) 25 | 26 | context = zmq.Context() 27 | 28 | endpoint = "tcp://127.0.0.1:" + str(port) 29 | print("Connecting to ktrl's ipc server: " + endpoint) 30 | socket = context.socket(zmq.REQ) 31 | socket.connect(endpoint) 32 | 33 | print("Sending request %s" % args.msg) 34 | socket.send_string(args.msg) 35 | 36 | message = socket.recv() 37 | print("Received reply [ %s ]" % message) 38 | 39 | 40 | if __name__ == "__main__": 41 | main() 42 | -------------------------------------------------------------------------------- /src/cfg.rs: -------------------------------------------------------------------------------- 1 | use crate::layers::Layers; 2 | 3 | use crate::layers::Profile; 4 | 5 | #[cfg(test)] 6 | use crate::actions::Action; 7 | #[cfg(test)] 8 | use crate::keys::KeyCode; 9 | #[cfg(test)] 10 | use crate::layers::Layer; 11 | 12 | use ron::de; 13 | use serde::Deserialize; 14 | use std::collections::HashMap; 15 | 16 | // ------------------- Cfg --------------------- 17 | 18 | /// This is a thin-wrapper around `layers::Layers`. 19 | /// It's used only for easy constructions of configuration layers. 20 | /// It encapsulates away the conversion of the input vectors to maps. 21 | #[derive(Debug, Deserialize)] 22 | pub struct Cfg { 23 | pub layers: Layers, 24 | pub layer_aliases: HashMap, 25 | pub layer_profiles: HashMap, 26 | pub tap_hold_wait_time: u64, 27 | pub tap_dance_wait_time: u64, 28 | } 29 | 30 | impl Cfg { 31 | #[cfg(test)] 32 | pub fn new( 33 | layer_aliases: HashMap, 34 | layers: Vec>, 35 | layer_profiles: HashMap, 36 | ) -> Self { 37 | let mut converted: Vec = vec![]; 38 | for layer in layers.into_iter() { 39 | converted.push(layer.into_iter().collect::()); 40 | } 41 | 42 | Self { 43 | layers: converted, 44 | layer_aliases, 45 | layer_profiles, 46 | tap_hold_wait_time: 0, 47 | tap_dance_wait_time: 0, 48 | } 49 | } 50 | } 51 | 52 | // ------------------- Util Functions --------------------- 53 | 54 | pub fn parse(cfg: &String) -> Cfg { 55 | de::from_str(cfg).expect("Failed to parse the config file") 56 | } 57 | -------------------------------------------------------------------------------- /src/actions/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod tap_dance; 2 | pub mod tap_hold; 3 | pub mod tap_mod; 4 | 5 | use crate::keys::KeyCode; 6 | use crate::effects::Effect; 7 | use serde::Deserialize; 8 | pub use tap_dance::TapDanceMgr; 9 | pub use tap_hold::TapHoldMgr; 10 | pub use tap_mod::TapModMgr; 11 | 12 | type TapEffect = Effect; 13 | type HoldEffect = Effect; 14 | type DanceEffect = Effect; 15 | type ModiEffect = Effect; 16 | type ModoEffect = Effect; 17 | type Modifier = KeyCode; 18 | type DanceLength = usize; 19 | 20 | #[derive(Clone, Debug, PartialEq, Eq, Deserialize)] 21 | pub enum Action { 22 | /// This is the default keyboard action. Use for simple key remappings. 23 | Tap(Effect), 24 | 25 | /// Do one `Effect` on a tap different, and a different one on an hold. 26 | /// E.g CapsLock on a regular tap, and Ctrl while holding. 27 | TapHold(TapEffect, HoldEffect), 28 | 29 | /// Do one `Effect` on a single tap, and a different one on multiple taps. 30 | /// E.g 'A' on a regular tap, and CapsLock on 3 quick taps. 31 | TapDance(DanceLength, TapEffect, DanceEffect), 32 | 33 | /// Do one `Effect` on a tap, and a different one while a modifier is held. 34 | /// E.g Left/Right arrow keys on a tap, and Ctrl+N/P while Ctrl is held. 35 | TapModi(Modifier, TapEffect, ModiEffect), 36 | 37 | /// Same as `TapModi` but also momentarily clears the modifier while performing the effect. 38 | /// E.g Left/Right arrow keys on a tap, and Home/End while WinKey is held. 39 | TapModo(Modifier, TapEffect, ModoEffect), 40 | 41 | /// Escape on regular tap. Tilde when shift is held. 42 | /// I.E TapMod(KEY_LEFTSHIFT, Key(KEY_ESC), Key(KEY_GRAVE)) 43 | TildeEsc, 44 | } 45 | -------------------------------------------------------------------------------- /src/actions/tap_mod.rs: -------------------------------------------------------------------------------- 1 | use crate::actions::Action; 2 | use crate::effects::Effect; 3 | use crate::effects::Effect::*; 4 | use crate::effects::EffectValue; 5 | use crate::effects::OutEffects; 6 | use crate::effects::{CONTINUE, STOP}; 7 | use crate::keys::KeyCode; 8 | use crate::keys::KeyCode::*; 9 | use crate::keys::KeyEvent; 10 | use crate::keys::KeyValue; 11 | use crate::layers::LayersManager; 12 | 13 | pub struct TapModMgr { 14 | modifiers: Vec, 15 | } 16 | 17 | impl TapModMgr { 18 | pub fn new() -> Self { 19 | let mut modifiers = Vec::new(); 20 | modifiers.resize_with(KeyCode::KEY_MAX as usize, || false); 21 | Self { modifiers } 22 | } 23 | 24 | fn process_tap_mod( 25 | &self, 26 | event: &KeyEvent, 27 | modifier: KeyCode, 28 | tap_fx: &Effect, 29 | mod_fx: &Effect, 30 | is_modo: bool, 31 | ) -> OutEffects { 32 | let mod_state = self.modifiers[modifier as usize]; 33 | let fx_vals = { 34 | match (mod_state, is_modo) { 35 | (true, true) => vec![ 36 | EffectValue::new(Key(modifier), KeyValue::Release), 37 | EffectValue::new(mod_fx.clone(), event.value), 38 | EffectValue::new(Key(modifier), KeyValue::Press), 39 | ], 40 | (true, false) => vec![EffectValue::new(mod_fx.clone(), event.value)], 41 | (false, _) => vec![EffectValue::new(tap_fx.clone(), event.value)], 42 | } 43 | }; 44 | 45 | dbg!(OutEffects::new_multiple(STOP, fx_vals)) 46 | } 47 | 48 | fn process_non_tap_mod(&mut self, event: &KeyEvent) -> OutEffects { 49 | let mod_state = &mut self.modifiers[event.code as usize]; 50 | match event.value { 51 | KeyValue::Press => *mod_state = true, 52 | KeyValue::Repeat => *mod_state = true, 53 | KeyValue::Release => *mod_state = false, 54 | } 55 | 56 | OutEffects::empty(CONTINUE) 57 | } 58 | 59 | fn get_action(l_mgr: &LayersManager, event: &KeyEvent) -> Action { 60 | let code = event.code; 61 | let action = &l_mgr.get(code).action; 62 | 63 | match action { 64 | Action::TildeEsc => Action::TapModi(KEY_LEFTSHIFT, Key(KEY_ESC), Key(KEY_GRAVE)), 65 | _ => action.clone(), 66 | } 67 | } 68 | 69 | pub fn process(&mut self, l_mgr: &LayersManager, event: &KeyEvent) -> OutEffects { 70 | let action = Self::get_action(l_mgr, event); 71 | match action { 72 | Action::TapModi(modifier, tap_fx, mod_fx) => { 73 | self.process_tap_mod(event, modifier, &tap_fx, &mod_fx, false) 74 | } 75 | Action::TapModo(modifier, tap_fx, mod_fx) => { 76 | self.process_tap_mod(event, modifier, &tap_fx, &mod_fx, true) 77 | } 78 | _ => self.process_non_tap_mod(event), 79 | } 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /examples/cfg.ron: -------------------------------------------------------------------------------- 1 | // ktrl Example Configuration File 2 | // ------------------------------- 3 | // 4 | // ktrl config files use `ron` (Rust Object Notation) to serialize 5 | // the text into the internal `cfg::Cfg` struct. 6 | // 7 | // - The full KEY_... listing can be found inside the `keys::KeyCode` enum 8 | // - Layer entries are mapping between a source `KeyCode` into an `Action` (more on that below) 9 | // 10 | ( 11 | // ktrl will register a TapHold as an hold after 300ms 12 | tap_hold_wait_time: 300, 13 | 14 | // ktrl will register a TapDance if all taps occur within a 1000ms period (1s) 15 | tap_dance_wait_time: 1000, 16 | 17 | // Gives names to layers 18 | layer_aliases: { 19 | "base": 0, 20 | "function_keys": 1, 21 | "meta-disabled": 2, 22 | "meta-enabled": 3, 23 | }, 24 | 25 | layer_profiles: { 26 | "function": Profile( 27 | indices: [], 28 | aliases: ["function_keys"], 29 | ), 30 | "meta": Profile( 31 | indices: [], 32 | aliases: ["meta-enabled", "function_keys"] 33 | ), 34 | "no-meta": Profile( 35 | indices: [], 36 | aliases: ["meta-disabled"], 37 | ), 38 | }, 39 | 40 | layers: [ 41 | // Layer 0 (Base Layer) 42 | // 43 | // Layer 0 is a bit special. 44 | // When ktrl stats-up, it automatically enables this layer. 45 | // After that, it can never be turned off. 46 | // I.E it configures the default behavior (when all other layers are off). 47 | // 48 | // All the layer entries repeat the same pattern. 49 | // They map a source `KeyCode` to an `Action`. 50 | // 51 | { 52 | KEY_LEFTMETA: Tap(Key(KEY_LEFTCTRL)), 53 | KEY_RIGHTALT: TapHold(Key(KEY_RIGHTALT), ActivateProfile("meta")), 54 | KEY_F1: TapHold(Key(KEY_F1), ToggleLayerAlias("function_keys")), 55 | KEY_F3: TapHold(Key(KEY_F3), DeactivateAllProfiles), 56 | 57 | }, 58 | // function_keys 59 | { 60 | // maps standard Mac keyboard to default to function keys. 61 | KEY_BRIGHTNESSDOWN: Tap(Key(KEY_F1)), // 224 62 | KEY_BRIGHTNESSUP: Tap(Key(KEY_F2)), // 225 63 | KEY_SCALE: Tap(Key(KEY_F3)), // 120 64 | KEY_DASHBOARD: Tap(Key(KEY_F4)), // 204 65 | KEY_KBDILLUMDOWN: Tap(Key(KEY_F5)), //229 66 | KEY_KBDILLUMUP: Tap(Key(KEY_F6)), // 230 67 | KEY_PREVIOUSSONG: Tap(Key(KEY_F7)), // 165 68 | KEY_PLAYPAUSE: Tap(Key(KEY_F8)), // 164 69 | KEY_NEXTSONG: Tap(Key(KEY_F9)), // 163 70 | KEY_MUTE: Tap(Key(KEY_F10)), // 113 71 | KEY_VOLUMEDOWN: Tap(Key(KEY_F11)), // 114 72 | KEY_VOLUMEUP: Tap(Key(KEY_F12)), // 115 73 | }, 74 | // meta-disabled 75 | { 76 | KEY_LEFTMETA: Tap(Key(KEY_LEFTCTRL)), 77 | KEY_RIGHTALT: TapHold(Key(KEY_RIGHTALT), ActivateProfile("meta")), 78 | }, 79 | // meta-enabled 80 | { 81 | KEY_LEFTMETA: Tap(Key(KEY_LEFTMETA)), 82 | KEY_RIGHTALT: TapHold(Key(KEY_RIGHTALT), ActivateProfile("no-meta")), 83 | }, 84 | ], 85 | ) 86 | -------------------------------------------------------------------------------- /src/effects/dj.rs: -------------------------------------------------------------------------------- 1 | use serde::Deserialize; 2 | 3 | use enum_iterator::IntoEnumIterator; 4 | use std::collections::HashMap; 5 | use std::fs::File; 6 | use std::io; 7 | use std::io::Read; 8 | use std::path::Path; 9 | use std::sync::Arc; 10 | 11 | use rodio; 12 | use rodio::Source; 13 | use std::convert::AsRef; 14 | 15 | //--------------------------------------------------- 16 | 17 | struct SoundImpl(Arc>); 18 | 19 | impl AsRef<[u8]> for SoundImpl { 20 | fn as_ref(&self) -> &[u8] { 21 | &self.0 22 | } 23 | } 24 | 25 | impl SoundImpl { 26 | fn load(path: &Path) -> io::Result { 27 | let mut buf = Vec::new(); 28 | let mut file = File::open(path)?; 29 | file.read_to_end(&mut buf)?; 30 | Ok(SoundImpl(Arc::new(buf))) 31 | } 32 | fn cursor(self: &Self) -> io::Cursor { 33 | io::Cursor::new(SoundImpl(self.0.clone())) 34 | } 35 | fn decoder(self: &Self) -> rodio::Decoder> { 36 | rodio::Decoder::new(self.cursor()).unwrap() 37 | } 38 | } 39 | 40 | //--------------------------------------------------- 41 | 42 | lazy_static::lazy_static! { 43 | static ref KSOUND_FILENAMES: HashMap = { 44 | [ 45 | (KSnd::Click1, "click1.wav"), 46 | (KSnd::Click2, "click2.wav"), 47 | (KSnd::Sticky, "sticky.wav"), 48 | (KSnd::Error, "error.wav"), 49 | ].iter().cloned().collect() 50 | }; 51 | } 52 | 53 | #[derive(Copy, Clone, Debug, Hash, PartialEq, Eq, IntoEnumIterator, Deserialize)] 54 | pub enum KSnd { 55 | Click1, 56 | Click2, 57 | Sticky, 58 | Error, 59 | } 60 | 61 | pub struct Dj { 62 | dev: rodio::Device, 63 | ksnds: HashMap, 64 | custom_snds: HashMap, 65 | } 66 | 67 | impl Dj { 68 | fn make_ksnds(assets_path: &Path) -> HashMap { 69 | let snds_dir = Path::new(assets_path).join("sounds"); 70 | let mut out: HashMap = HashMap::new(); 71 | for snd in KSnd::into_enum_iter() { 72 | let path = snds_dir.join(&KSOUND_FILENAMES[&snd]); 73 | out.insert(snd, SoundImpl::load(&path).unwrap()); 74 | } 75 | out 76 | } 77 | 78 | pub fn new(assets_path: &Path) -> Self { 79 | let dev = rodio::default_output_device().expect("Failed to open the default sound device"); 80 | let ksnds = Self::make_ksnds(assets_path); 81 | Self { 82 | dev, 83 | ksnds, 84 | custom_snds: HashMap::new(), 85 | } 86 | } 87 | 88 | pub fn play(&self, snd: KSnd) { 89 | let snd = &self.ksnds[&snd]; 90 | rodio::play_raw(&self.dev, snd.decoder().convert_samples()); 91 | } 92 | 93 | pub fn play_custom(&mut self, path: &String) { 94 | if !self.custom_snds.contains_key(path) { 95 | let _path = Path::new(path); 96 | self.custom_snds 97 | .insert(path.clone(), SoundImpl::load(&_path).unwrap()); 98 | } 99 | 100 | let snd = &self.custom_snds[path]; 101 | rodio::play_raw(&self.dev, snd.decoder().convert_samples()); 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /src/kbd_in.rs: -------------------------------------------------------------------------------- 1 | use log::error; 2 | // evdev-rs 3 | use evdev_rs::enums::EventType; 4 | use evdev_rs::Device; 5 | use evdev_rs::GrabMode; 6 | use evdev_rs::InputEvent; 7 | use evdev_rs::ReadFlag; 8 | use evdev_rs::ReadStatus; 9 | use retry::delay; 10 | use retry::retry; 11 | use retry::OperationResult; 12 | 13 | use std::fs::File; 14 | use std::path::Path; 15 | use std::path::PathBuf; 16 | 17 | pub struct KbdIn { 18 | device: Device, 19 | path: PathBuf, 20 | } 21 | 22 | impl KbdIn { 23 | pub fn new(dev_path: &Path) -> Result { 24 | // retry is needed because sometimes inotify event comes earlier than 25 | // udev rules are applied, which leads to permission denied error 26 | match retry( 27 | delay::Exponential::from_millis(3).take(10), 28 | || match Self::new_inner(&dev_path) { 29 | Ok(kbd_in) => OperationResult::Ok(kbd_in), 30 | Err(err) => match err.kind() { 31 | std::io::ErrorKind::PermissionDenied => OperationResult::Retry(err), 32 | _ => OperationResult::Err(err), 33 | }, 34 | }, 35 | ) { 36 | Ok(kbd_in) => Ok(kbd_in), 37 | Err(err) => { 38 | error!("Failed to open the input keyboard device: {err}. Make sure you've added ktrl to the `input` group"); 39 | return Err(err.error); 40 | } 41 | } 42 | } 43 | 44 | fn new_inner(dev_path: &Path) -> Result { 45 | let kbd_in_file = File::open(dev_path)?; 46 | let mut kbd_in_dev = Device::new_from_fd(kbd_in_file)?; 47 | if kbd_in_dev.has(&EventType::EV_ABS) { 48 | // this blocks all hotkeys, including ctrl-c 49 | log::error!("Skip device {dev_path:?}: touchapd is not supporded"); 50 | return Err(std::io::Error::new(std::io::ErrorKind::Other, "touchpad")); 51 | } 52 | if kbd_in_dev.name().unwrap_or_default() == "ktrl" { 53 | log::error!( 54 | "device {path} is our own output device: {name:?}", 55 | path = dev_path.display(), 56 | name = kbd_in_dev.name() 57 | ); 58 | return Err(std::io::Error::new( 59 | std::io::ErrorKind::Other, 60 | "ktrl output device", 61 | )); 62 | } 63 | 64 | // NOTE: This grab-ungrab-grab sequence magically 65 | // fix an issue I had with my Lenovo Yoga trackpad not working. 66 | // I honestly have no idea why this works haha. 67 | kbd_in_dev.grab(GrabMode::Grab)?; 68 | kbd_in_dev.grab(GrabMode::Ungrab)?; 69 | kbd_in_dev.grab(GrabMode::Grab)?; 70 | 71 | Ok(KbdIn { 72 | device: kbd_in_dev, 73 | path: dev_path.to_path_buf(), 74 | }) 75 | } 76 | 77 | pub fn read(&self) -> Result { 78 | let (status, event) = self 79 | .device 80 | .next_event(ReadFlag::NORMAL | ReadFlag::BLOCKING)?; 81 | if status == ReadStatus::Success { 82 | Ok(event) 83 | } else { 84 | Err(std::io::Error::new( 85 | std::io::ErrorKind::Other, 86 | format!("read {path}: bad status", path = self.path.display()), 87 | )) 88 | } 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /src/ipc.rs: -------------------------------------------------------------------------------- 1 | use std::sync::Arc; 2 | use std::sync::Mutex; 3 | use std::thread; 4 | 5 | use ron::de; 6 | use serde::Deserialize; 7 | 8 | use log::{debug, info}; 9 | 10 | use crate::effects::perform_effect; 11 | use crate::effects::EffectValue; 12 | use crate::ktrl::Ktrl; 13 | 14 | #[derive(Debug, Clone, Deserialize)] 15 | pub enum KtrlIpcReq { 16 | IpcDoEffect(EffectValue), 17 | } 18 | 19 | #[derive(Debug, Clone, Deserialize)] 20 | pub enum KtrlIpcResp { 21 | Ok, 22 | Error(String), 23 | } 24 | 25 | impl KtrlIpcResp { 26 | fn to_str(self) -> String { 27 | match self { 28 | Self::Ok => String::from("OK"), 29 | Self::Error(err) => err, 30 | } 31 | } 32 | } 33 | 34 | pub struct KtrlIpc { 35 | _ctx: zmq::Context, 36 | socket: zmq::Socket, 37 | ktrl: Arc>, 38 | } 39 | 40 | impl KtrlIpc { 41 | pub fn new(ktrl: Arc>, port: usize) -> Result { 42 | let ctx = zmq::Context::new(); 43 | let socket = ctx.socket(zmq::REP)?; 44 | let endpoint = format!("tcp://127.0.0.1:{}", port); 45 | socket.bind(&endpoint)?; 46 | info!("Listening for IPC requests on {}", endpoint); 47 | Ok(Self { 48 | _ctx: ctx, 49 | socket, 50 | ktrl, 51 | }) 52 | } 53 | 54 | fn handle_ipc_req(&self, req: &zmq::Message) -> KtrlIpcResp { 55 | let mut ktrl = self.ktrl.lock().expect("Failed to lock ktrl (poisoned)"); 56 | 57 | let req_str = match req.as_str() { 58 | Some(req_str) => req_str, 59 | _ => return KtrlIpcResp::Error("Request has an invalid string".to_string()), 60 | }; 61 | 62 | debug!("Recived an IPC req: '{}'", req_str); 63 | 64 | let req: KtrlIpcReq = match de::from_str(req_str) { 65 | Ok(req) => req, 66 | Err(err) => return KtrlIpcResp::Error(err.to_string()), 67 | }; 68 | 69 | let KtrlIpcReq::IpcDoEffect(fx_val) = req; 70 | match perform_effect(&mut ktrl, fx_val) { 71 | Ok(_) => KtrlIpcResp::Ok, 72 | Err(err) => return KtrlIpcResp::Error(err.to_string()), 73 | } 74 | } 75 | 76 | fn ipc_loop(&self) -> Result<(), std::io::Error> { 77 | let mut msg = zmq::Message::new(); 78 | 79 | loop { 80 | self.socket.recv(&mut msg, 0)?; 81 | 82 | let resp = self.handle_ipc_req(&msg); 83 | 84 | self.socket 85 | .send(&resp.to_str(), 0) 86 | .expect("Failed to send a reply"); 87 | } 88 | } 89 | 90 | pub fn spawn_ipc_thread(self) { 91 | thread::spawn(move || { 92 | self.ipc_loop().unwrap(); 93 | }); 94 | } 95 | 96 | pub fn send_ipc_req(port: usize, req: String) -> Result<(), std::io::Error> { 97 | let ctx = zmq::Context::new(); 98 | let socket = ctx.socket(zmq::REQ)?; 99 | let endpoint = format!("tcp://127.0.0.1:{}", port); 100 | 101 | info!("Sending an ipc msg to ktrl: {}", endpoint); 102 | socket.connect(&endpoint)?; 103 | socket.send(&req, 0)?; 104 | 105 | let mut msg = zmq::Message::new(); 106 | socket.recv(&mut msg, 0)?; 107 | info!( 108 | "Received: {}", 109 | msg.as_str() 110 | .expect("Couldn't parse the ipc reply as a string") 111 | ); 112 | 113 | Ok(()) 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /src/effects/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod perform; 2 | pub use perform::perform_effect; 3 | 4 | mod sticky; 5 | pub use sticky::StickyState; 6 | 7 | #[cfg(feature = "sound")] 8 | mod dj; 9 | #[cfg(feature = "sound")] 10 | pub use dj::Dj; 11 | #[cfg(feature = "sound")] 12 | pub use dj::KSnd; 13 | 14 | use crate::actions::Action; 15 | use crate::keys::KeyCode; 16 | use crate::keys::KeyEvent; 17 | use crate::keys::KeyValue; 18 | use crate::layers::LayerIndex; 19 | use crate::layers::LayersManager; 20 | use inner::inner; 21 | use serde::Deserialize; 22 | 23 | #[derive(Clone, Debug, PartialEq, Eq, Deserialize)] 24 | pub enum Effect { 25 | NoOp, 26 | 27 | Key(KeyCode), 28 | KeySticky(KeyCode), 29 | KeySeq(Vec), 30 | 31 | Meh, // Ctrl+Alt+Shift 32 | Hyper, // Ctrl+Alt+Shift+Win 33 | 34 | ActivateProfile(String), 35 | DeactivateProfile(String), 36 | DeactivateAllProfiles, 37 | 38 | TurnOnLayer(LayerIndex), 39 | TurnOffLayer(LayerIndex), 40 | ToggleLayer(LayerIndex), 41 | 42 | TurnOnLayerAlias(String), 43 | TurnOffLayerAlias(String), 44 | ToggleLayerAlias(String), 45 | 46 | MomentaryLayer(LayerIndex), 47 | 48 | #[cfg(feature = "sound")] 49 | Sound(KSnd), 50 | #[cfg(feature = "sound")] 51 | SoundEx(String), 52 | 53 | Multi(Vec), 54 | // Not Implemented Yet 55 | // --------------------- 56 | // OneShotLayer(LayerIndex), 57 | // OneShotModifier(KeyCode) 58 | // ToggleModifier(KeyCode) 59 | } 60 | 61 | pub fn key_event_to_fx_val(l_mgr: &LayersManager, event: &KeyEvent) -> EffectValue { 62 | let merged = l_mgr.get(event.code); 63 | let effect = inner!(&merged.action, if Action::Tap).clone(); 64 | 65 | EffectValue { 66 | fx: effect, 67 | val: event.value.into(), 68 | } 69 | } 70 | 71 | // ------------------- Output Effects ----------------- 72 | 73 | // These are returned by action handlers. 74 | // E.g TapHoldMgr::process 75 | 76 | #[derive(Clone, Debug, Deserialize, PartialEq, Eq)] 77 | pub struct EffectValue { 78 | pub fx: Effect, 79 | pub val: KeyValue, 80 | } 81 | 82 | impl EffectValue { 83 | pub fn new(fx: Effect, val: KeyValue) -> Self { 84 | Self { fx, val } 85 | } 86 | } 87 | 88 | pub const STOP: bool = true; 89 | pub const CONTINUE: bool = false; 90 | 91 | #[derive(Clone, Debug, PartialEq, Eq)] 92 | pub struct OutEffects { 93 | pub stop_processing: bool, 94 | pub effects: Option>, 95 | } 96 | 97 | impl OutEffects { 98 | pub fn new(stop_processing: bool, effect: Effect, value: KeyValue) -> Self { 99 | OutEffects { 100 | stop_processing, 101 | effects: Some(vec![EffectValue::new(effect, value)]), 102 | } 103 | } 104 | 105 | pub fn new_multiple(stop_processing: bool, effects: Vec) -> Self { 106 | OutEffects { 107 | stop_processing, 108 | effects: Some(effects), 109 | } 110 | } 111 | 112 | pub fn empty(stop_processing: bool) -> Self { 113 | OutEffects { 114 | stop_processing, 115 | effects: None, 116 | } 117 | } 118 | 119 | pub fn insert(&mut self, effect: Effect, value: KeyValue) { 120 | if let Some(effects) = &mut self.effects { 121 | effects.push(EffectValue::new(effect, value)); 122 | } else { 123 | self.effects = Some(vec![EffectValue::new(effect, value)]); 124 | } 125 | } 126 | } 127 | -------------------------------------------------------------------------------- /src/devices.rs: -------------------------------------------------------------------------------- 1 | use inotify::{Inotify, WatchMask}; 2 | use log::error; 3 | use regex::Regex; 4 | use std::{ 5 | collections::{HashSet, VecDeque}, 6 | io, 7 | iter::FromIterator, 8 | path::PathBuf, 9 | sync::{Arc, Mutex}, 10 | }; 11 | 12 | pub struct Devices { 13 | // enable inotify for new devices 14 | watch: bool, 15 | // watch only for events on files that were specified from command line 16 | watch_initial_only: bool, 17 | pub devices: HashSet, 18 | root_dir: PathBuf, 19 | inotify: Inotify, 20 | events: VecDeque, 21 | } 22 | 23 | pub type DevicesArc = Arc>; 24 | 25 | lazy_static::lazy_static! { 26 | static ref DEVICES_RE: Regex = Regex::new(r"event\d+").unwrap(); 27 | } 28 | 29 | impl Devices { 30 | pub fn new(watch: bool, devices: Vec) -> Result { 31 | let root_dir = PathBuf::from("/dev/input"); 32 | let mut inotify = Inotify::init()?; 33 | if watch { 34 | if let Err(err) = inotify.add_watch(&root_dir, WatchMask::CREATE) { 35 | error!("failed to add watch: {err}"); 36 | return Err(err); 37 | } 38 | } 39 | let (devices_set, watch_initial_only) = if devices.is_empty() { 40 | (Devices::get_all_input_devices()?, false) 41 | } else { 42 | (HashSet::from_iter(devices), true) 43 | }; 44 | Ok(Arc::new(Mutex::new(Self { 45 | devices: devices_set, 46 | watch_initial_only: watch_initial_only, 47 | watch: watch, 48 | inotify: inotify, 49 | root_dir: root_dir, 50 | events: VecDeque::new(), 51 | }))) 52 | } 53 | 54 | pub fn get_all_input_devices() -> io::Result> { 55 | let mut result = HashSet::new(); 56 | for entry in std::fs::read_dir("/dev/input/")? { 57 | let entry = entry?; 58 | let path = entry.path(); 59 | if DEVICES_RE.is_match( 60 | path.file_name() 61 | .unwrap_or_default() 62 | .to_str() 63 | .unwrap_or_default(), 64 | ) { 65 | result.insert(path); 66 | } 67 | } 68 | Ok(result) 69 | } 70 | 71 | pub fn watch(&mut self) -> io::Result { 72 | if !self.watch { 73 | panic!("watch() called but not enabled") 74 | } 75 | if let Some(event) = self.events.pop_back() { 76 | return Ok(event); 77 | } 78 | loop { 79 | let mut buf = [0; 1024]; 80 | let events = self.inotify.read_events_blocking(&mut buf)?; 81 | for event in events { 82 | let file_name = event.name.unwrap_or_default().to_str().unwrap_or_default(); 83 | if file_name.is_empty() { 84 | continue; 85 | } 86 | let mut device_path = self.root_dir.clone(); 87 | device_path.push(file_name); 88 | if self.watch_initial_only && !self.devices.contains(&device_path) { 89 | continue; 90 | } 91 | if DEVICES_RE.is_match(file_name) { 92 | self.events.push_front(device_path); 93 | } 94 | } 95 | if let Some(event) = self.events.pop_back() { 96 | return Ok(event); 97 | } 98 | } 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /src/kbd_out.rs: -------------------------------------------------------------------------------- 1 | // uinput-rs 2 | use uinput_sys; 3 | use uinput_sys::uinput_user_dev; 4 | 5 | use crate::keys::KeyCode; 6 | use crate::keys::KeyValue; 7 | use evdev_rs::enums::EventCode; 8 | use evdev_rs::enums::EV_SYN; 9 | use evdev_rs::InputEvent; 10 | use evdev_rs::TimeVal; 11 | use libc::input_event as raw_event; 12 | 13 | // file i/o 14 | use io::Write; 15 | use std::fs::File; 16 | use std::fs::OpenOptions; 17 | use std::io; 18 | use std::os::unix::io::AsRawFd; 19 | 20 | // unsafe 21 | use std::mem; 22 | use std::slice; 23 | 24 | // ktrl 25 | use crate::keys::KeyEvent; 26 | 27 | pub struct KbdOut { 28 | device: File, 29 | } 30 | 31 | impl KbdOut { 32 | pub fn new() -> Result { 33 | let mut uinput_out_file = OpenOptions::new() 34 | .read(true) 35 | .write(true) 36 | .open("/dev/uinput")?; 37 | 38 | unsafe { 39 | uinput_sys::ui_set_evbit(uinput_out_file.as_raw_fd(), uinput_sys::EV_SYN); 40 | uinput_sys::ui_set_evbit(uinput_out_file.as_raw_fd(), uinput_sys::EV_KEY); 41 | uinput_sys::ui_set_evbit(uinput_out_file.as_raw_fd(), uinput_sys::EV_REL); 42 | uinput_sys::ui_set_evbit(uinput_out_file.as_raw_fd(), uinput_sys::EV_MSC); 43 | 44 | for key in 0..uinput_sys::KEY_MAX { 45 | uinput_sys::ui_set_keybit(uinput_out_file.as_raw_fd(), key); 46 | } 47 | for key in 0..uinput_sys::REL_MAX { 48 | uinput_sys::ui_set_relbit(uinput_out_file.as_raw_fd(), key); 49 | } 50 | for key in 0..uinput_sys::MSC_MAX { 51 | uinput_sys::ui_set_mscbit(uinput_out_file.as_raw_fd(), key); 52 | } 53 | 54 | let mut uidev: uinput_user_dev = mem::zeroed(); 55 | uidev.name[0] = 'k' as i8; 56 | uidev.name[1] = 't' as i8; 57 | uidev.name[2] = 'r' as i8; 58 | uidev.name[3] = 'l' as i8; 59 | uidev.id.bustype = 0x3; // BUS_USB 60 | uidev.id.vendor = 0x1; 61 | uidev.id.product = 0x1; 62 | uidev.id.version = 1; 63 | 64 | let uidev_bytes = 65 | slice::from_raw_parts(mem::transmute(&uidev), mem::size_of::()); 66 | uinput_out_file.write(uidev_bytes)?; 67 | uinput_sys::ui_dev_create(uinput_out_file.as_raw_fd()); 68 | } 69 | 70 | Ok(KbdOut { 71 | device: uinput_out_file, 72 | }) 73 | } 74 | 75 | pub fn write(&mut self, event: InputEvent) -> Result<(), io::Error> { 76 | let ev = event.as_raw(); 77 | 78 | unsafe { 79 | let ev_bytes = slice::from_raw_parts( 80 | mem::transmute(&ev as *const raw_event), 81 | mem::size_of::(), 82 | ); 83 | self.device.write(ev_bytes)?; 84 | }; 85 | 86 | Ok(()) 87 | } 88 | 89 | pub fn write_key(&mut self, key: KeyCode, value: KeyValue) -> Result<(), io::Error> { 90 | let key_ev = KeyEvent::new(key, value); 91 | self.write(key_ev.into())?; 92 | 93 | let sync = InputEvent::new( 94 | &TimeVal { 95 | tv_sec: 0, 96 | tv_usec: 0, 97 | }, 98 | &EventCode::EV_SYN(EV_SYN::SYN_REPORT), 99 | 0, 100 | ); 101 | self.write(sync.into())?; 102 | 103 | Ok(()) 104 | } 105 | 106 | pub fn press_key(&mut self, key: KeyCode) -> Result<(), io::Error> { 107 | self.write_key(key, KeyValue::Press) 108 | } 109 | 110 | pub fn release_key(&mut self, key: KeyCode) -> Result<(), io::Error> { 111 | self.write_key(key, KeyValue::Release) 112 | } 113 | 114 | // pub fn tap_key(&mut self, key: KeyCode) -> Result<(), io::Error> { 115 | // self.press_key(key.clone())?; 116 | // self.release_key(key)?; 117 | // Ok(()) 118 | // } 119 | } 120 | -------------------------------------------------------------------------------- /src/ktrl.rs: -------------------------------------------------------------------------------- 1 | use evdev_rs::enums::EventType; 2 | use log::{error, info}; 3 | 4 | use std::convert::TryFrom; 5 | use std::fs::read_to_string; 6 | use std::path::PathBuf; 7 | 8 | use std::sync::Arc; 9 | use std::sync::Mutex; 10 | 11 | use crate::actions::TapDanceMgr; 12 | use crate::actions::TapHoldMgr; 13 | use crate::actions::TapModMgr; 14 | use crate::cfg; 15 | use crate::devices::Devices; 16 | use crate::devices::DevicesArc; 17 | use crate::effects::key_event_to_fx_val; 18 | use crate::effects::perform_effect; 19 | use crate::effects::StickyState; 20 | use crate::keys::KeyEvent; 21 | use crate::layers::LayersManager; 22 | use crate::KbdIn; 23 | use crate::KbdOut; 24 | 25 | #[cfg(feature = "sound")] 26 | use crate::effects::Dj; 27 | 28 | pub struct KtrlArgs { 29 | pub kbd_path: Vec, 30 | pub watch: bool, 31 | pub config_path: PathBuf, 32 | pub assets_path: PathBuf, 33 | pub ipc_port: usize, 34 | pub ipc_msg: Option, 35 | pub notify_port: usize, 36 | } 37 | 38 | pub struct Ktrl { 39 | pub devices: DevicesArc, 40 | pub kbd_out: KbdOut, 41 | pub l_mgr: LayersManager, 42 | pub th_mgr: TapHoldMgr, 43 | pub td_mgr: TapDanceMgr, 44 | pub tm_mgr: TapModMgr, 45 | pub sticky: StickyState, 46 | 47 | #[cfg(feature = "sound")] 48 | pub dj: Dj, 49 | } 50 | 51 | impl Ktrl { 52 | pub fn new(args: KtrlArgs) -> Result { 53 | let kbd_out = match KbdOut::new() { 54 | Ok(kbd_out) => kbd_out, 55 | Err(err) => { 56 | error!("Failed to open the output uinput device. Make sure you've added ktrl to the `uinput` group"); 57 | return Err(err); 58 | } 59 | }; 60 | 61 | let devices = Devices::new(args.watch, args.kbd_path)?; 62 | 63 | let cfg_str = read_to_string(args.config_path)?; 64 | let cfg = cfg::parse(&cfg_str); 65 | let mut l_mgr = LayersManager::new( 66 | &cfg.layers, 67 | &cfg.layer_aliases, 68 | &cfg.layer_profiles, 69 | #[cfg(feature = "notify")] 70 | args.notify_port, 71 | )?; 72 | l_mgr.init(); 73 | 74 | let th_mgr = TapHoldMgr::new(cfg.tap_hold_wait_time); 75 | let td_mgr = TapDanceMgr::new(cfg.tap_dance_wait_time); 76 | let tm_mgr = TapModMgr::new(); 77 | let sticky = StickyState::new(); 78 | 79 | #[cfg(feature = "sound")] 80 | let dj = Dj::new(&args.assets_path); 81 | 82 | Ok(Self { 83 | devices, 84 | kbd_out, 85 | l_mgr, 86 | th_mgr, 87 | td_mgr, 88 | tm_mgr, 89 | sticky, 90 | #[cfg(feature = "sound")] 91 | dj, 92 | }) 93 | } 94 | 95 | pub fn new_arc(args: KtrlArgs) -> Result>, std::io::Error> { 96 | Ok(Arc::new(Mutex::new(Self::new(args)?))) 97 | } 98 | 99 | // 100 | // TODO: 101 | // ---- 102 | // Refactor this to unicast if special key, 103 | // and broadcast if regular tap key. 104 | // 105 | fn handle_key_event(&mut self, event: &KeyEvent) -> Result<(), std::io::Error> { 106 | // Handle TapHold action keys 107 | let th_out = self.th_mgr.process(&mut self.l_mgr, event); 108 | if let Some(th_fx_vals) = th_out.effects { 109 | for fx_val in th_fx_vals { 110 | perform_effect(self, fx_val)? 111 | } 112 | } 113 | 114 | if th_out.stop_processing { 115 | return Ok(()); 116 | } 117 | 118 | let td_out = self.td_mgr.process(&mut self.l_mgr, event); 119 | if let Some(td_fx_vals) = td_out.effects { 120 | for fx_val in td_fx_vals { 121 | perform_effect(self, fx_val)? 122 | } 123 | } 124 | 125 | if td_out.stop_processing { 126 | return Ok(()); 127 | } 128 | 129 | let te_out = self.tm_mgr.process(&self.l_mgr, event); 130 | if let Some(te_fx_vals) = te_out.effects { 131 | for fx_val in te_fx_vals { 132 | perform_effect(self, fx_val)? 133 | } 134 | } 135 | 136 | if !te_out.stop_processing { 137 | let leftover_fx_val = key_event_to_fx_val(&self.l_mgr, event); 138 | perform_effect(self, leftover_fx_val)?; 139 | } 140 | 141 | Ok(()) 142 | } 143 | 144 | pub fn watch_loop(ktrl: Arc>) { 145 | info!("Ktrl: Entering the inotify loop"); 146 | let devices = ktrl.lock().unwrap().devices.clone(); 147 | std::thread::spawn(move || { 148 | let mut devices = devices.lock().unwrap(); 149 | while let Ok(path) = devices.watch() { 150 | Self::start_device_thread(ktrl.clone(), path); 151 | } 152 | }); 153 | } 154 | 155 | fn start_device_thread(ktrl: Arc>, path: PathBuf) -> std::thread::JoinHandle<()> { 156 | std::thread::spawn(move || { 157 | Self::event_loop_for_path(ktrl, path.clone()).unwrap_or(()); 158 | info!("event loop ended for {path:?}"); 159 | }) 160 | } 161 | 162 | pub fn event_loop(ktrl: Arc>) -> Result<(), std::io::Error> { 163 | info!("Ktrl: Entering the event loop"); 164 | 165 | let kbd_in_paths: Vec; 166 | { 167 | let ktrl = ktrl.lock().expect("Failed to lock ktrl (poisoned)"); 168 | let devices = ktrl.devices.lock().unwrap(); 169 | let devices = &devices.devices; 170 | kbd_in_paths = devices.iter().cloned().collect::>(); 171 | } 172 | 173 | kbd_in_paths.iter().for_each(|kbd_in_path| { 174 | let ktrl = ktrl.clone(); 175 | let kbd_in_path = kbd_in_path.clone(); 176 | Self::start_device_thread(ktrl.clone(), kbd_in_path); 177 | }); 178 | 179 | std::thread::park(); 180 | Ok(()) 181 | } 182 | 183 | fn event_loop_for_path( 184 | ktrl: Arc>, 185 | kbd_path: PathBuf, 186 | ) -> Result<(), std::io::Error> { 187 | let kbd_in = KbdIn::new(&kbd_path)?; 188 | info!("Event loop for device {kbd_path:?}"); 189 | loop { 190 | let in_event = kbd_in.read()?; 191 | // TODO maybe use channel instead of locking for each event? 192 | let mut ktrl = ktrl.lock().expect("Failed to lock ktrl (poisoned)"); 193 | if !(in_event.event_type == EventType::EV_SYN 194 | || in_event.event_type == EventType::EV_MSC 195 | || in_event.event_type == EventType::EV_REL) 196 | { 197 | log::debug!("event {:?}", in_event); 198 | } 199 | 200 | // Pass-through non-key events 201 | let key_event = match KeyEvent::try_from(in_event.clone()) { 202 | Ok(ev) => ev, 203 | _ => { 204 | ktrl.kbd_out.write(in_event)?; 205 | continue; 206 | } 207 | }; 208 | 209 | ktrl.handle_key_event(&key_event)?; 210 | } 211 | } 212 | } 213 | -------------------------------------------------------------------------------- /src/effects/perform.rs: -------------------------------------------------------------------------------- 1 | use crate::effects::Effect; 2 | use crate::effects::EffectValue; 3 | use crate::kbd_out::KbdOut; 4 | use crate::keys::KeyCode; 5 | use crate::keys::KeyCode::*; 6 | use crate::keys::KeyValue; 7 | use crate::ktrl::Ktrl; 8 | use crate::layers::LayerIndex; 9 | 10 | #[cfg(feature = "sound")] 11 | use crate::effects::KSnd; 12 | 13 | use std::io::Error; 14 | use std::vec::Vec; 15 | 16 | lazy_static::lazy_static! { 17 | static ref HYPER: Vec = { 18 | vec![ 19 | KEY_LEFTCTRL, KEY_LEFTALT, KEY_LEFTSHIFT, KEY_LEFTMETA 20 | ].iter() 21 | .map(|evkey| KeyCode::from(evkey.clone())) 22 | .collect() 23 | }; 24 | } 25 | 26 | lazy_static::lazy_static! { 27 | static ref MEH: Vec = { 28 | vec![ 29 | KEY_LEFTCTRL, KEY_LEFTALT, KEY_LEFTSHIFT 30 | ].iter() 31 | .map(|evkey| KeyCode::from(evkey.clone())) 32 | .collect() 33 | }; 34 | } 35 | 36 | fn perform_multiple_effects( 37 | ktrl: &mut Ktrl, 38 | effects: Vec, 39 | value: KeyValue, 40 | ) -> Result<(), Error> { 41 | for fx in effects { 42 | let sub_fx_val = EffectValue::new(fx.clone(), value); 43 | perform_effect(ktrl, sub_fx_val)?; 44 | } 45 | 46 | Ok(()) 47 | } 48 | 49 | #[cfg(feature = "sound")] 50 | fn perform_play_custom_sound( 51 | ktrl: &mut Ktrl, 52 | snd_path: String, 53 | value: KeyValue, 54 | ) -> Result<(), Error> { 55 | if value == KeyValue::Press { 56 | ktrl.dj.play_custom(&snd_path) 57 | } 58 | 59 | Ok(()) 60 | } 61 | 62 | #[cfg(feature = "sound")] 63 | fn perform_play_sound(ktrl: &mut Ktrl, snd: KSnd, value: KeyValue) -> Result<(), Error> { 64 | if value == KeyValue::Press { 65 | ktrl.dj.play(snd) 66 | } 67 | 68 | Ok(()) 69 | } 70 | 71 | fn perform_momentary_layer(ktrl: &mut Ktrl, idx: LayerIndex, value: KeyValue) -> Result<(), Error> { 72 | if value == KeyValue::Press { 73 | ktrl.l_mgr.turn_layer_on(idx) 74 | } else if value == KeyValue::Release { 75 | ktrl.l_mgr.turn_layer_off(idx) 76 | } 77 | 78 | Ok(()) 79 | } 80 | 81 | fn perform_turn_on_layer(ktrl: &mut Ktrl, idx: LayerIndex, value: KeyValue) -> Result<(), Error> { 82 | if value == KeyValue::Press { 83 | ktrl.l_mgr.turn_layer_on(idx) 84 | } 85 | 86 | Ok(()) 87 | } 88 | 89 | fn perform_turn_off_layer(ktrl: &mut Ktrl, idx: LayerIndex, value: KeyValue) -> Result<(), Error> { 90 | if value == KeyValue::Press { 91 | ktrl.l_mgr.turn_layer_off(idx) 92 | } 93 | 94 | Ok(()) 95 | } 96 | 97 | fn perform_toggle_layer(ktrl: &mut Ktrl, idx: LayerIndex, value: KeyValue) -> Result<(), Error> { 98 | if value == KeyValue::Press { 99 | ktrl.l_mgr.toggle_layer(idx) 100 | } 101 | 102 | Ok(()) 103 | } 104 | 105 | fn perform_turn_on_layer_alias( 106 | ktrl: &mut Ktrl, 107 | name: String, 108 | value: KeyValue, 109 | ) -> Result<(), Error> { 110 | if value == KeyValue::Press { 111 | ktrl.l_mgr.turn_alias_on(name) 112 | } 113 | 114 | Ok(()) 115 | } 116 | 117 | fn perform_turn_off_layer_alias( 118 | ktrl: &mut Ktrl, 119 | name: String, 120 | value: KeyValue, 121 | ) -> Result<(), Error> { 122 | if value == KeyValue::Press { 123 | ktrl.l_mgr.turn_alias_off(name) 124 | } 125 | 126 | Ok(()) 127 | } 128 | 129 | fn perform_toggle_layer_alias(ktrl: &mut Ktrl, name: String, value: KeyValue) -> Result<(), Error> { 130 | if value == KeyValue::Press { 131 | ktrl.l_mgr.toggle_layer_alias(name) 132 | } 133 | 134 | Ok(()) 135 | } 136 | 137 | fn perform_toggle_profile( 138 | ktrl: &mut Ktrl, 139 | name: String, 140 | value: KeyValue, 141 | on: bool, 142 | ) -> Result<(), Error> { 143 | if value == KeyValue::Press { 144 | // deactivate all profiles, if successful, activate new profile 145 | match perform_deactivate_all_profiles(ktrl, value) { 146 | Ok(()) => ktrl.l_mgr.toggle_profile(name, on), 147 | Err(e) => return Err(e), 148 | } 149 | } 150 | 151 | Ok(()) 152 | } 153 | 154 | fn perform_deactivate_all_profiles(ktrl: &mut Ktrl, value: KeyValue) -> Result<(), Error> { 155 | if value == KeyValue::Press { 156 | for name in ktrl.l_mgr.layer_profiles.clone().keys() { 157 | ktrl.l_mgr.toggle_profile(name.clone(), false); 158 | } 159 | } 160 | 161 | Ok(()) 162 | } 163 | 164 | fn perform_key_sticky(ktrl: &mut Ktrl, code: KeyCode, value: KeyValue) -> Result<(), Error> { 165 | if value == KeyValue::Release { 166 | return Ok(()); 167 | } 168 | 169 | if !ktrl.sticky.is_pressed(code) { 170 | ktrl.sticky.update_pressed(&mut ktrl.l_mgr, code); 171 | ktrl.kbd_out.press_key(code) 172 | } else { 173 | ktrl.sticky.update_released(&mut ktrl.l_mgr, code); 174 | ktrl.kbd_out.release_key(code) 175 | } 176 | } 177 | 178 | fn perform_keyseq(kbd_out: &mut KbdOut, seq: Vec, value: KeyValue) -> Result<(), Error> { 179 | for code in seq { 180 | perform_key(kbd_out, code, value)?; 181 | } 182 | 183 | Ok(()) 184 | } 185 | 186 | fn perform_key(kbd_out: &mut KbdOut, code: KeyCode, value: KeyValue) -> Result<(), Error> { 187 | kbd_out.write_key(code, value) 188 | } 189 | 190 | pub fn perform_effect(ktrl: &mut Ktrl, fx_val: EffectValue) -> Result<(), Error> { 191 | match fx_val.fx { 192 | Effect::NoOp => Ok(()), 193 | Effect::Key(code) => perform_key(&mut ktrl.kbd_out, code, fx_val.val), 194 | Effect::KeySeq(seq) => perform_keyseq(&mut ktrl.kbd_out, seq, fx_val.val), 195 | Effect::KeySticky(code) => perform_key_sticky(ktrl, code, fx_val.val), 196 | Effect::Meh => perform_keyseq(&mut ktrl.kbd_out, MEH.to_vec(), fx_val.val), 197 | Effect::Hyper => perform_keyseq(&mut ktrl.kbd_out, HYPER.to_vec(), fx_val.val), 198 | 199 | Effect::ActivateProfile(name) => perform_toggle_profile(ktrl, name, fx_val.val, true), 200 | Effect::DeactivateProfile(name) => perform_toggle_profile(ktrl, name, fx_val.val, false), 201 | Effect::DeactivateAllProfiles => perform_deactivate_all_profiles(ktrl, fx_val.val), 202 | 203 | Effect::TurnOnLayer(idx) => perform_turn_on_layer(ktrl, idx, fx_val.val), 204 | Effect::TurnOffLayer(idx) => perform_turn_off_layer(ktrl, idx, fx_val.val), 205 | Effect::ToggleLayer(idx) => perform_toggle_layer(ktrl, idx, fx_val.val), 206 | 207 | Effect::TurnOnLayerAlias(name) => perform_turn_on_layer_alias(ktrl, name, fx_val.val), 208 | Effect::TurnOffLayerAlias(name) => perform_turn_off_layer_alias(ktrl, name, fx_val.val), 209 | Effect::ToggleLayerAlias(name) => perform_toggle_layer_alias(ktrl, name, fx_val.val), 210 | 211 | Effect::MomentaryLayer(idx) => perform_momentary_layer(ktrl, idx, fx_val.val), 212 | Effect::Multi(fxs) => perform_multiple_effects(ktrl, fxs, fx_val.val), 213 | 214 | #[cfg(feature = "sound")] 215 | Effect::Sound(snd) => perform_play_sound(ktrl, snd, fx_val.val), 216 | #[cfg(feature = "sound")] 217 | Effect::SoundEx(snd) => perform_play_custom_sound(ktrl, snd, fx_val.val), 218 | } 219 | } 220 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | use clap::{App, Arg}; 2 | use log::info; 3 | use simplelog::*; 4 | use std::fs::File; 5 | use std::io::{Error, ErrorKind::*}; 6 | use std::path::{Path, PathBuf}; 7 | 8 | mod actions; 9 | mod cfg; 10 | mod devices; 11 | mod effects; 12 | mod kbd_in; 13 | mod kbd_out; 14 | mod keys; 15 | mod ktrl; 16 | mod layers; 17 | 18 | use kbd_in::KbdIn; 19 | use kbd_out::KbdOut; 20 | use ktrl::Ktrl; 21 | use ktrl::KtrlArgs; 22 | 23 | #[cfg(feature = "ipc")] 24 | mod ipc; 25 | #[cfg(feature = "ipc")] 26 | use ipc::KtrlIpc; 27 | 28 | const DEFAULT_CFG_PATH: &str = "/opt/ktrl/cfg.ron"; 29 | const DEFAULT_LOG_PATH: &str = "/opt/ktrl/log.txt"; 30 | const DEFAULT_ASSETS_PATH: &str = "/opt/ktrl/assets"; 31 | const DEFAULT_IPC_PORT: &str = "7331"; 32 | const DEFAULT_NOTIFY_PORT: &str = "7333"; 33 | 34 | #[doc(hidden)] 35 | fn cli_init() -> Result { 36 | let matches = App::new("ktrl") 37 | .version("0.1.7") 38 | .author("Itay G. ") 39 | .about("Unleashes your keyboard's full potential") 40 | .arg( 41 | Arg::with_name("device") 42 | .short("d") 43 | .long("device") 44 | .value_name("DEVICE") 45 | .help("Path to your keyboard's input devices. Usually in /dev/input/. This arg accepts multiple values: -d /dev/input/event*") 46 | .multiple(true) 47 | .takes_value(true) 48 | ) 49 | .arg( 50 | Arg::with_name("watch") 51 | .short("w") 52 | .long("watch") 53 | .help("Watch for new devices in /dev/input") 54 | ) 55 | .arg( 56 | Arg::with_name("cfg") 57 | .long("cfg") 58 | .value_name("CONFIG") 59 | .help(&format!( 60 | "Path to your ktrl config file. Default: {}", 61 | DEFAULT_CFG_PATH 62 | )) 63 | .takes_value(true), 64 | ) 65 | .arg( 66 | Arg::with_name("assets") 67 | .long("assets") 68 | .value_name("ASSETS") 69 | .help(&format!( 70 | "Path ktrl's assets directory. Default: {}", 71 | DEFAULT_ASSETS_PATH 72 | )) 73 | .takes_value(true), 74 | ) 75 | .arg( 76 | Arg::with_name("logfile") 77 | .long("log") 78 | .value_name("LOGFILE") 79 | .help(&format!( 80 | "Path to the log file. Default: {}", 81 | DEFAULT_LOG_PATH 82 | )) 83 | .takes_value(true), 84 | ) 85 | .arg( 86 | Arg::with_name("ipc_port") 87 | .long("ipc-port") 88 | .value_name("IPC-PORT") 89 | .help(&format!( 90 | "TCP Port to listen on for ipc requests. Default: {}", 91 | DEFAULT_IPC_PORT 92 | )) 93 | .takes_value(true), 94 | ) 95 | .arg( 96 | Arg::with_name("notify_port") 97 | .long("notify-port") 98 | .value_name("NOTIFY-PORT") 99 | .help(&format!( 100 | "TCP Port where notifications will be sent. Default: {}", 101 | DEFAULT_NOTIFY_PORT 102 | )) 103 | .takes_value(true), 104 | ) 105 | .arg( 106 | Arg::with_name("msg") 107 | .long("msg") 108 | .value_name("IPC-MSG") 109 | .help("IPC Message to the running ktrl daemon. Won't start a new ktrl instance") 110 | .takes_value(true), 111 | ) 112 | .arg( 113 | Arg::with_name("debug") 114 | .long("debug") 115 | .help("Enables debug level logging"), 116 | ) 117 | .get_matches(); 118 | 119 | let log_path = Path::new(matches.value_of("logfile").unwrap_or(DEFAULT_LOG_PATH)); 120 | let log_lvl = match matches.is_present("debug") { 121 | true => LevelFilter::Debug, 122 | _ => LevelFilter::Info, 123 | }; 124 | 125 | CombinedLogger::init(vec![ 126 | TermLogger::new(log_lvl, Config::default(), TerminalMode::Mixed), 127 | WriteLogger::new( 128 | log_lvl, 129 | Config::default(), 130 | File::create(log_path).expect("Couldn't initialize the file logger"), 131 | ), 132 | ]) 133 | .expect("Couldn't initialize the logger"); 134 | 135 | let config_path = Path::new(matches.value_of("cfg").unwrap_or(DEFAULT_CFG_PATH)); 136 | let assets_path = Path::new(matches.value_of("assets").unwrap_or(DEFAULT_ASSETS_PATH)); 137 | let kbd_paths = matches 138 | .values_of("device") 139 | .unwrap_or_default() 140 | .map(PathBuf::from) 141 | .collect::>(); 142 | let watch = matches.is_present("watch"); 143 | let ipc_port = matches 144 | .value_of("ipc_port") 145 | .unwrap_or(DEFAULT_IPC_PORT) 146 | .parse::() 147 | .expect("Bad ipc port value"); 148 | let ipc_msg = matches.value_of("msg").map(|x: &str| x.to_string()); 149 | let notify_port = matches 150 | .value_of("notify_port") 151 | .unwrap_or(DEFAULT_NOTIFY_PORT) 152 | .parse::() 153 | .expect("Bad notify port value"); 154 | 155 | if !config_path.exists() { 156 | let err = format!( 157 | "Could not find your config file ({})", 158 | config_path.to_str().unwrap_or("?") 159 | ); 160 | return Err(Error::new(NotFound, err)); 161 | } 162 | 163 | for kbd_path in &kbd_paths { 164 | if !kbd_path.exists() { 165 | let err = format!( 166 | "Could not find the keyboard device ({})", 167 | kbd_path.to_str().unwrap_or("?") 168 | ); 169 | return Err(Error::new(NotFound, err)); 170 | } 171 | } 172 | 173 | Ok(KtrlArgs { 174 | kbd_path: kbd_paths, 175 | watch: watch, 176 | config_path: config_path.to_path_buf(), 177 | assets_path: assets_path.to_path_buf(), 178 | ipc_port, 179 | ipc_msg, 180 | notify_port, 181 | }) 182 | } 183 | 184 | #[cfg(feature = "ipc")] 185 | fn main_impl(args: KtrlArgs) -> Result<(), std::io::Error> { 186 | let ipc_port = args.ipc_port; 187 | 188 | // Operate as a client, then quit 189 | if let Some(ipc_msg) = args.ipc_msg { 190 | return KtrlIpc::send_ipc_req(ipc_port, ipc_msg); 191 | } 192 | 193 | // Otherwise, startup the server 194 | let ktrl_arc = Ktrl::new_arc(args)?; 195 | info!("ktrl: Setup Complete"); 196 | 197 | let ipc = KtrlIpc::new(ktrl_arc.clone(), ipc_port)?; 198 | ipc.spawn_ipc_thread(); 199 | 200 | Ktrl::event_loop(ktrl_arc)?; 201 | Ok(()) 202 | } 203 | 204 | #[cfg(not(feature = "ipc"))] 205 | fn main_impl(args: KtrlArgs) -> Result<(), std::io::Error> { 206 | let watch = args.watch; 207 | let ktrl_arc = Ktrl::new_arc(args)?; 208 | info!("ktrl: Setup Complete"); 209 | if watch { 210 | Ktrl::watch_loop(ktrl_arc.clone()); 211 | } 212 | Ktrl::event_loop(ktrl_arc)?; 213 | Ok(()) 214 | } 215 | 216 | #[doc(hidden)] 217 | fn main() -> Result<(), std::io::Error> { 218 | let args = cli_init()?; 219 | main_impl(args) 220 | } 221 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU LESSER GENERAL PUBLIC LICENSE 2 | Version 3, 29 June 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | 9 | This version of the GNU Lesser General Public License incorporates 10 | the terms and conditions of version 3 of the GNU General Public 11 | License, supplemented by the additional permissions listed below. 12 | 13 | 0. Additional Definitions. 14 | 15 | As used herein, "this License" refers to version 3 of the GNU Lesser 16 | General Public License, and the "GNU GPL" refers to version 3 of the GNU 17 | General Public License. 18 | 19 | "The Library" refers to a covered work governed by this License, 20 | other than an Application or a Combined Work as defined below. 21 | 22 | An "Application" is any work that makes use of an interface provided 23 | by the Library, but which is not otherwise based on the Library. 24 | Defining a subclass of a class defined by the Library is deemed a mode 25 | of using an interface provided by the Library. 26 | 27 | A "Combined Work" is a work produced by combining or linking an 28 | Application with the Library. The particular version of the Library 29 | with which the Combined Work was made is also called the "Linked 30 | Version". 31 | 32 | The "Minimal Corresponding Source" for a Combined Work means the 33 | Corresponding Source for the Combined Work, excluding any source code 34 | for portions of the Combined Work that, considered in isolation, are 35 | based on the Application, and not on the Linked Version. 36 | 37 | The "Corresponding Application Code" for a Combined Work means the 38 | object code and/or source code for the Application, including any data 39 | and utility programs needed for reproducing the Combined Work from the 40 | Application, but excluding the System Libraries of the Combined Work. 41 | 42 | 1. Exception to Section 3 of the GNU GPL. 43 | 44 | You may convey a covered work under sections 3 and 4 of this License 45 | without being bound by section 3 of the GNU GPL. 46 | 47 | 2. Conveying Modified Versions. 48 | 49 | If you modify a copy of the Library, and, in your modifications, a 50 | facility refers to a function or data to be supplied by an Application 51 | that uses the facility (other than as an argument passed when the 52 | facility is invoked), then you may convey a copy of the modified 53 | version: 54 | 55 | a) under this License, provided that you make a good faith effort to 56 | ensure that, in the event an Application does not supply the 57 | function or data, the facility still operates, and performs 58 | whatever part of its purpose remains meaningful, or 59 | 60 | b) under the GNU GPL, with none of the additional permissions of 61 | this License applicable to that copy. 62 | 63 | 3. Object Code Incorporating Material from Library Header Files. 64 | 65 | The object code form of an Application may incorporate material from 66 | a header file that is part of the Library. You may convey such object 67 | code under terms of your choice, provided that, if the incorporated 68 | material is not limited to numerical parameters, data structure 69 | layouts and accessors, or small macros, inline functions and templates 70 | (ten or fewer lines in length), you do both of the following: 71 | 72 | a) Give prominent notice with each copy of the object code that the 73 | Library is used in it and that the Library and its use are 74 | covered by this License. 75 | 76 | b) Accompany the object code with a copy of the GNU GPL and this license 77 | document. 78 | 79 | 4. Combined Works. 80 | 81 | You may convey a Combined Work under terms of your choice that, 82 | taken together, effectively do not restrict modification of the 83 | portions of the Library contained in the Combined Work and reverse 84 | engineering for debugging such modifications, if you also do each of 85 | the following: 86 | 87 | a) Give prominent notice with each copy of the Combined Work that 88 | the Library is used in it and that the Library and its use are 89 | covered by this License. 90 | 91 | b) Accompany the Combined Work with a copy of the GNU GPL and this license 92 | document. 93 | 94 | c) For a Combined Work that displays copyright notices during 95 | execution, include the copyright notice for the Library among 96 | these notices, as well as a reference directing the user to the 97 | copies of the GNU GPL and this license document. 98 | 99 | d) Do one of the following: 100 | 101 | 0) Convey the Minimal Corresponding Source under the terms of this 102 | License, and the Corresponding Application Code in a form 103 | suitable for, and under terms that permit, the user to 104 | recombine or relink the Application with a modified version of 105 | the Linked Version to produce a modified Combined Work, in the 106 | manner specified by section 6 of the GNU GPL for conveying 107 | Corresponding Source. 108 | 109 | 1) Use a suitable shared library mechanism for linking with the 110 | Library. A suitable mechanism is one that (a) uses at run time 111 | a copy of the Library already present on the user's computer 112 | system, and (b) will operate properly with a modified version 113 | of the Library that is interface-compatible with the Linked 114 | Version. 115 | 116 | e) Provide Installation Information, but only if you would otherwise 117 | be required to provide such information under section 6 of the 118 | GNU GPL, and only to the extent that such information is 119 | necessary to install and execute a modified version of the 120 | Combined Work produced by recombining or relinking the 121 | Application with a modified version of the Linked Version. (If 122 | you use option 4d0, the Installation Information must accompany 123 | the Minimal Corresponding Source and Corresponding Application 124 | Code. If you use option 4d1, you must provide the Installation 125 | Information in the manner specified by section 6 of the GNU GPL 126 | for conveying Corresponding Source.) 127 | 128 | 5. Combined Libraries. 129 | 130 | You may place library facilities that are a work based on the 131 | Library side by side in a single library together with other library 132 | facilities that are not Applications and are not covered by this 133 | License, and convey such a combined library under terms of your 134 | choice, if you do both of the following: 135 | 136 | a) Accompany the combined library with a copy of the same work based 137 | on the Library, uncombined with any other library facilities, 138 | conveyed under the terms of this License. 139 | 140 | b) Give prominent notice with the combined library that part of it 141 | is a work based on the Library, and explaining where to find the 142 | accompanying uncombined form of the same work. 143 | 144 | 6. Revised Versions of the GNU Lesser General Public License. 145 | 146 | The Free Software Foundation may publish revised and/or new versions 147 | of the GNU Lesser General Public License from time to time. Such new 148 | versions will be similar in spirit to the present version, but may 149 | differ in detail to address new problems or concerns. 150 | 151 | Each version is given a distinguishing version number. If the 152 | Library as you received it specifies that a certain numbered version 153 | of the GNU Lesser General Public License "or any later version" 154 | applies to it, you have the option of following the terms and 155 | conditions either of that published version or of any later version 156 | published by the Free Software Foundation. If the Library as you 157 | received it does not specify a version number of the GNU Lesser 158 | General Public License, you may choose any version of the GNU Lesser 159 | General Public License ever published by the Free Software Foundation. 160 | 161 | If the Library as you received it specifies that a proxy can decide 162 | whether future versions of the GNU Lesser General Public License shall 163 | apply, that proxy's public statement of acceptance of any version is 164 | permanent authorization for you to choose that version for the 165 | Library. -------------------------------------------------------------------------------- /src/actions/tap_dance.rs: -------------------------------------------------------------------------------- 1 | use crate::effects::OutEffects; 2 | use crate::effects::{CONTINUE, STOP}; 3 | use crate::keys::KeyCode; 4 | use crate::keys::KeyEvent; 5 | use crate::keys::KeyValue; 6 | use crate::layers::LayersManager; 7 | use crate::layers::LockOwner; 8 | 9 | // inner 10 | use inner::inner; 11 | 12 | use crate::layers::{Action, Action::TapDance, Effect}; 13 | 14 | // This struct isn't used in Action::TapDance 15 | // due to overhead it'll create in the config file. 16 | // Lots of wrappers in the ron text 17 | struct TapDanceCfg { 18 | len: usize, 19 | tap_fx: Effect, 20 | dance_fx: Effect, 21 | } 22 | 23 | impl TapDanceCfg { 24 | fn from_action(action: &Action) -> Self { 25 | match action { 26 | TapDance(len, tap_fx, dance_fx) => Self { 27 | len: *len, 28 | tap_fx: tap_fx.clone(), 29 | dance_fx: dance_fx.clone(), 30 | }, 31 | _ => unreachable!(), 32 | } 33 | } 34 | } 35 | 36 | #[derive(Clone, Debug, PartialEq)] 37 | pub struct TapDanceWaiting { 38 | pub timestamp: evdev_rs::TimeVal, 39 | pub presses_so_far: usize, 40 | pub releases_so_far: usize, 41 | } 42 | 43 | #[derive(Clone, Debug, PartialEq)] 44 | pub enum TapDanceState { 45 | TdIdle, 46 | TdDancing(TapDanceWaiting), 47 | } 48 | 49 | pub struct TapDanceMgr { 50 | dancing: Option, 51 | state: TapDanceState, 52 | wait_time: u64, 53 | } 54 | 55 | // --------------- TapDance-specific Functions ---------------------- 56 | 57 | impl TapDanceMgr { 58 | pub fn new(wait_time: u64) -> Self { 59 | assert!(wait_time < (i64::MAX as u64)); 60 | Self { 61 | state: TapDanceState::TdIdle, 62 | dancing: None, 63 | wait_time, 64 | } 65 | } 66 | 67 | fn lock_key(l_mgr: &mut LayersManager, key: KeyCode) { 68 | l_mgr.lock_key(key, LockOwner::LkTapDance); 69 | } 70 | 71 | fn unlock_key(l_mgr: &mut LayersManager, key: KeyCode) { 72 | l_mgr.unlock_key(key, LockOwner::LkTapDance); 73 | } 74 | 75 | fn set_dancing(&mut self, l_mgr: &mut LayersManager, key: KeyCode) { 76 | self.dancing = Some(key); 77 | Self::lock_key(l_mgr, key); 78 | } 79 | 80 | fn clear_dancing(&mut self, l_mgr: &mut LayersManager) { 81 | if let Some(dancing) = self.dancing { 82 | Self::unlock_key(l_mgr, dancing); 83 | } else { 84 | unreachable!(); 85 | } 86 | 87 | self.dancing = None; 88 | } 89 | 90 | fn did_dance_timeout(&self, event: &KeyEvent) -> bool { 91 | let new_timestamp = event.time.clone(); 92 | let wait_start_timestamp = inner!(&self.state, if TapDanceState::TdDancing) 93 | .timestamp 94 | .clone(); 95 | let secs_diff = new_timestamp.tv_sec - wait_start_timestamp.tv_sec; 96 | let usecs_diff = new_timestamp.tv_usec - wait_start_timestamp.tv_usec; 97 | let diff = (secs_diff * 1_000_000) + usecs_diff; 98 | diff >= (self.wait_time as i64) * 1000 99 | } 100 | 101 | fn handle_th_dancing( 102 | &mut self, 103 | l_mgr: &mut LayersManager, 104 | event: &KeyEvent, 105 | td_cfg: &TapDanceCfg, 106 | ) -> OutEffects { 107 | let did_timeout = self.did_dance_timeout(event); 108 | let did_key_change = event.code != self.dancing.unwrap().clone(); 109 | 110 | if did_timeout || did_key_change { 111 | let mut fx_vals = self.get_buffered_key_events(td_cfg).effects.unwrap(); 112 | self.state = TapDanceState::TdIdle; 113 | self.clear_dancing(l_mgr); 114 | let idle_out = self.handle_th_idle(l_mgr, event, td_cfg); 115 | if let Some(mut new_fx_vals) = idle_out.effects { 116 | fx_vals.append(&mut new_fx_vals); 117 | } 118 | return OutEffects::new_multiple(STOP, fx_vals); 119 | } 120 | 121 | let value = KeyValue::from(event.value); 122 | let wait_state = inner!(&self.state, if TapDanceState::TdDancing); 123 | match value { 124 | KeyValue::Press => { 125 | let mut new_state = wait_state.clone(); 126 | new_state.presses_so_far += 1; 127 | 128 | if new_state.presses_so_far >= td_cfg.len { 129 | self.state = TapDanceState::TdDancing(new_state); 130 | OutEffects::new(STOP, td_cfg.dance_fx.clone(), KeyValue::Press) 131 | } else { 132 | self.state = TapDanceState::TdDancing(new_state); 133 | OutEffects::empty(STOP) 134 | } 135 | } 136 | KeyValue::Release => { 137 | let mut new_state = wait_state.clone(); 138 | new_state.releases_so_far += 1; 139 | 140 | if new_state.releases_so_far >= td_cfg.len { 141 | self.state = TapDanceState::TdIdle; 142 | self.clear_dancing(l_mgr); 143 | OutEffects::new(STOP, td_cfg.dance_fx.clone(), KeyValue::Release) 144 | } else { 145 | self.state = TapDanceState::TdDancing(new_state); 146 | OutEffects::empty(STOP) 147 | } 148 | } 149 | 150 | KeyValue::Repeat => { 151 | // Drop repeats. These aren't supported for TapDances 152 | OutEffects::empty(STOP) 153 | } 154 | } 155 | } 156 | 157 | fn handle_th_idle( 158 | &mut self, 159 | l_mgr: &mut LayersManager, 160 | event: &KeyEvent, 161 | td_cfg: &TapDanceCfg, 162 | ) -> OutEffects { 163 | assert!(self.state == TapDanceState::TdIdle); 164 | 165 | let keycode: KeyCode = event.code; 166 | let value = KeyValue::from(event.value); 167 | 168 | match value { 169 | KeyValue::Press => { 170 | self.state = TapDanceState::TdDancing(TapDanceWaiting { 171 | timestamp: event.time.clone(), 172 | presses_so_far: 1, 173 | releases_so_far: 0, 174 | }); 175 | self.set_dancing(l_mgr, keycode); 176 | OutEffects::empty(STOP) 177 | } 178 | 179 | KeyValue::Release => { 180 | // Forward the release 181 | OutEffects::new(STOP, td_cfg.tap_fx.clone(), KeyValue::Release) 182 | } 183 | 184 | KeyValue::Repeat => { 185 | // Drop repeats. These aren't supported for TapDances 186 | OutEffects::empty(STOP) 187 | } 188 | } 189 | } 190 | 191 | // Assumes this is an event tied to a TapDance assigned MergedKey 192 | fn process_tap_dance_key( 193 | &mut self, 194 | l_mgr: &mut LayersManager, 195 | event: &KeyEvent, 196 | td_cfg: &TapDanceCfg, 197 | ) -> OutEffects { 198 | let state = &self.state; 199 | match state { 200 | TapDanceState::TdIdle => self.handle_th_idle(l_mgr, event, td_cfg), 201 | TapDanceState::TdDancing(_) => self.handle_th_dancing(l_mgr, event, td_cfg), 202 | } 203 | } 204 | 205 | // --------------- Non-TapDance Functions ---------------------- 206 | 207 | fn get_buffered_key_events(&self, td_cfg: &TapDanceCfg) -> OutEffects { 208 | let mut out = OutEffects::empty(STOP); 209 | let wait_state = inner!(&self.state, if TapDanceState::TdDancing); 210 | 211 | for _i in 0..wait_state.presses_so_far { 212 | out.insert(td_cfg.tap_fx.clone(), KeyValue::Press); 213 | } 214 | for _i in 0..wait_state.releases_so_far { 215 | out.insert(td_cfg.tap_fx.clone(), KeyValue::Release); 216 | } 217 | 218 | out 219 | } 220 | 221 | fn process_non_tap_dance_key( 222 | &mut self, 223 | l_mgr: &mut LayersManager, 224 | _event: &KeyEvent, 225 | ) -> OutEffects { 226 | match self.dancing { 227 | None => OutEffects::empty(CONTINUE), 228 | 229 | Some(dancing) => { 230 | let action = &l_mgr.get(dancing).action; 231 | let td_cfg = TapDanceCfg::from_action(action); 232 | 233 | let mut out = self.get_buffered_key_events(&td_cfg); 234 | out.stop_processing = CONTINUE; 235 | 236 | self.state = TapDanceState::TdIdle; 237 | self.clear_dancing(l_mgr); 238 | 239 | out 240 | } 241 | } 242 | } 243 | 244 | // --------------- High-Level Functions ---------------------- 245 | 246 | // Returns true if processed, false if skipped 247 | pub fn process(&mut self, l_mgr: &mut LayersManager, event: &KeyEvent) -> OutEffects { 248 | let code = event.code; 249 | let action = &l_mgr.get(code).action; 250 | 251 | if let Action::TapDance(..) = action { 252 | let td_cfg = TapDanceCfg::from_action(action); 253 | self.process_tap_dance_key(l_mgr, event, &td_cfg) 254 | } else { 255 | self.process_non_tap_dance_key(l_mgr, event) 256 | } 257 | } 258 | 259 | #[cfg(test)] 260 | pub fn is_idle(&self) -> bool { 261 | self.dancing.is_none() 262 | } 263 | } 264 | 265 | #[cfg(test)] 266 | use crate::cfg::*; 267 | #[cfg(test)] 268 | use crate::effects::Effect::*; 269 | #[cfg(test)] 270 | use crate::keys::KeyCode::*; 271 | 272 | #[test] 273 | fn test_tap_dance() { 274 | use std::collections::HashMap; 275 | let mut h = HashMap::new(); 276 | h.insert("base".to_string(), 0); 277 | let cfg = Cfg::new( 278 | h, 279 | vec![vec![(KEY_A, TapDance(3, Key(KEY_A), Key(KEY_LEFTCTRL)))]], 280 | HashMap::new(), 281 | ); 282 | 283 | let mut l_mgr = LayersManager::new(&cfg.layers, &cfg.layer_aliases, &cfg.layer_profiles).unwrap(); 284 | let mut th_mgr = TapDanceMgr::new(500); 285 | 286 | l_mgr.init(); 287 | 288 | let ev_th_press = KeyEvent::new_press(KEY_A); 289 | let ev_th_release = KeyEvent::new_release(KEY_A); 290 | 291 | // 1st 292 | assert_eq!( 293 | th_mgr.process(&mut l_mgr, &ev_th_press), 294 | OutEffects::empty(STOP) 295 | ); 296 | assert_eq!(th_mgr.is_idle(), false); 297 | assert_eq!(l_mgr.is_key_locked(KEY_A), true); 298 | assert_eq!( 299 | th_mgr.process(&mut l_mgr, &ev_th_release), 300 | OutEffects::empty(STOP) 301 | ); 302 | 303 | assert_eq!( 304 | th_mgr.process(&mut l_mgr, &ev_th_press), 305 | OutEffects::empty(STOP) 306 | ); 307 | assert_eq!(th_mgr.is_idle(), false); 308 | assert_eq!(l_mgr.is_key_locked(KEY_A), true); 309 | assert_eq!( 310 | th_mgr.process(&mut l_mgr, &ev_th_release), 311 | OutEffects::empty(STOP) 312 | ); 313 | 314 | assert_eq!( 315 | th_mgr.process(&mut l_mgr, &ev_th_press), 316 | OutEffects::new(STOP, Key(KEY_LEFTCTRL), KeyValue::Press) 317 | ); 318 | assert_eq!(th_mgr.is_idle(), false); 319 | assert_eq!(l_mgr.is_key_locked(KEY_A), true); 320 | assert_eq!( 321 | th_mgr.process(&mut l_mgr, &ev_th_release), 322 | OutEffects::new(STOP, Key(KEY_LEFTCTRL), KeyValue::Release) 323 | ); 324 | 325 | assert_eq!(th_mgr.is_idle(), true); 326 | assert_eq!(l_mgr.is_key_locked(KEY_A), false); 327 | } 328 | 329 | // TODO 330 | // 1. Test Action::Tap interruption 331 | // 2. Test Action::TapHold interruption 332 | // 3. Test other Action::TapDance interruption 333 | // 4. Test dance timeout 334 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ktrl 2 | 3 |

4 | 5 | Crates.io 6 | 7 |

8 | 9 | **TL;DR** 10 | 11 | **ktrl** is a **Linux keyboard programming daemon**. 12 | It aims to aid you in the never-ending quest of achieving the ultimate keybinding setup. 13 | 14 | You can dip your toes by remapping a few modifier keys (e.g `CapLock` to `Ctrl`). 15 | Or you can go all-in by creating a sophisticated layering setup with dual-function keys, tap-dancing, etc... 16 | 17 | ktrl is heavily inspired by the amazing open-source keyboard firmware project [QMK](https://docs.qmk.fm/#/). 18 | You can think of ktrl as an attempt to re-implement QMK as a Linux daemon. 19 | 20 | This is an **alpha** state project. 21 | If you find any bugs or quirks please reach out to me. 22 | 23 | 24 | **Table of Contents** 25 | 26 | - [ktrl](#ktrl) 27 | - [Intro](#intro) 28 | - [Features Overview](#features-overview) 29 | - [Layers](#layers) 30 | - [Tap-Hold (Dual Function Keys)](#tap-hold-dual-function-keys) 31 | - [Tap-Dancing (Multi-Tap)](#tap-dancing-multi-tap) 32 | - [`Meh` and `Hyper`](#meh-and-hyper) 33 | - [Audible Feedback](#audible-feedback) 34 | - [Installation](#installation) 35 | - [Configuration](#configuration) 36 | - [Primitives](#primitives) 37 | - [Input Event Codes](#input-event-codes) 38 | - [Actions](#actions) 39 | - [Effects](#effects) 40 | - [Configuration File Format](#configuration-file-format) 41 | - [Examples](#examples) 42 | - [Remapping `Ctrl` to `Capslock`](#remapping-ctrl-to-capslock) 43 | - [Home-row Modifiers](#home-row-modifiers) 44 | - [Limitations](#limitations) 45 | 46 | 47 | 48 | ## Intro 49 | 50 | ktrl sits right in the middle of the human-interface software stack. 51 | It lives in userspace, between the kernel and your display server (a.k.a X11 / Wayland). 52 | 53 | This position allows ktrl complete control over the events your keyboard generates. 54 | These events are either transparently passed-on or transformed into ktrl's "Effects" (more on that later). 55 | 56 | ## Features Overview 57 | 58 | Aside from the obvious key remapping capability, 59 | here's a taste of some of the major things you can do with ktrl - 60 | 61 | ### Layers 62 | 63 | Although "layers" might seem like a foreign idea, it's something you're already very familiar with. 64 | After all, you apply "layers" all the time by using modifier and function keys :) 65 | 66 | QMK takes this mechanism and generalizes it. 67 | Letting you design your own custom keyboard's layers! 68 | 69 | If that sounds confusing, I encourage you to head over to [QMK's documentation about layers](https://beta.docs.qmk.fm/using-qmk/guides/keymap#keymap-and-layers). 70 | 71 | ### Tap-Hold (Dual Function Keys) 72 | 73 | Tap-Hold keys let you do one thing when the key is pressed, and another thing when it is held. 74 | For example, you can make your Spacebar act as normal when tapping, but serve as Ctrl when held. 75 | 76 | ### Tap-Dancing (Multi-Tap) 77 | 78 | Tap-dancing is quite similar to Tap-Hold. The key will act in one way if tapped once, 79 | and will act differently if tapped multiple times. 80 | 81 | ### `Meh` and `Hyper` 82 | 83 | Again, both of these were shamelessly taken from QMK. `Meh` and `Hyper` are special modifiers 84 | for creating keybindings that'll probably never conflict with existing ones. 85 | That's possible since `Hyper` is equal to pressing `Ctrl+Alt+Shift+Win` and `Meh` is the same as pressing `Ctrl+Alt+Shift`. 86 | 87 | ### Audible Feedback 88 | 89 | Ever wanted to bind your favorite 8bit tunes to your key-presses? Well, now you can! 90 | Though, aside from making your hacking session more musical, this feature as some very practical uses as well. 91 | 92 | For example, it can help you build new muscle-memory connections using audible feedback. 93 | See the Capslock <-> Ctrl example below for more on that. 94 | 95 | ## Installation 96 | 97 | #### Getting the Executable 98 | 99 | Start off by grabbing the main `ktrl` executable. Here's how you do that - 100 | 101 | ``` 102 | sudo cargo install --root /usr/local ktrl 103 | ``` 104 | 105 | Note: you may need to install `alsa` development bindings, `autoconf` and `libtool`. For Debian/Ubuntu distributions this can be done with 106 | 107 | ``` 108 | # apt install libalsa-ocaml-dev autoconf libtool libtool-bin 109 | ``` 110 | 111 | #### Setting up ktrl's User and Groups 112 | 113 | Although a bit cumbersome, this step makes sure we can run ktrl without root privileges. 114 | Instead of running it as root, we'll make a new user for ktrl. Then, we'll add the 115 | new user to the input and audio groups. Let's get started - 116 | 117 | ``` 118 | sudo useradd -r -s /bin/false ktrl 119 | sudo groupadd uinput 120 | sudo usermod -aG input ktrl 121 | sudo usermod -aG uinput ktrl 122 | 123 | # If you're using the sound effects 124 | sudo usermod -aG audio ktrl 125 | ``` 126 | 127 | Now, let's add a new udev rule that'll allow ktrl to write to `/dev/uinput`. 128 | `/dev/uinput` is ktrl's output device. Your keyboard being the input device. 129 | 130 | ``` 131 | git clone https://github.com/itaygarin/ktrl 132 | cd ktrl 133 | sudo cp ./etc/99-uinput.rules /etc/udev/rules.d/ 134 | ``` 135 | 136 | Note that'll need to reboot your machine for the changes to take effect... 137 | 138 | #### Setting up ktrl's Assets and Config 139 | 140 | Now, it's time to decide where you'd like ktrl's assets and config to live. 141 | 142 | By default, ktrl will assume you've placed these under `/opt/ktrl`. 143 | Specifically, `/opt/ktrl/cfg.ron` and `/opt/ktrl/assets`. 144 | Though, you can override these defaults with the `--cfg` and `--assets` cli arguments. 145 | 146 | To set-up the defaults, you can follow these steps - 147 | 148 | ``` 149 | # Asumming you've already cloned and cd`d into the ktrl project 150 | 151 | sudo mkdir /opt/ktrl 152 | sudo cp -r ./assets /opt/ktrl 153 | sudo cp examples/cfg.ron /opt/ktrl 154 | 155 | sudo chown -R ktrl:$USER /opt/ktrl 156 | sudo chmod -R 0770 /opt/ktrl 157 | ``` 158 | 159 | #### Locating your Keyboard's input device 160 | 161 | #### The easy and obvious way 162 | If you want ktrl to process events from all input devices, just run it without arguments. 163 | If you also want ktrl to watch for new devices in `/dev/input`, you can pass `--watch` as an argument. 164 | This could be useful if you use bluetooth devices that connect and reconnect all the time. 165 | 166 | #### Specifying devices manually 167 | 168 | If you want to process events only from selected devices, you have to supply it with a path to your keyboard's input device. 169 | Input devices reside in the `/dev/input` directory. 170 | 171 | Linux provides two convenient symlinks-filled directories to make the search process easier. 172 | These directories are `/dev/input/by-id` and `/dev/input/by-path`. 173 | 174 | Within these two directories keyboard devices usually have a `-kbd` suffix. 175 | For example, in my laptop, the path is `/dev/input/by-path/platform-i8042-serio-0-event-kbd`. 176 | 177 | If you provide both `--watch` and `--device` arguments, then ktrl will watch for file events only on those devices. 178 | 179 | ##### Bluetooth keyboard location 180 | 181 | If you are using a bluetooth keyboard, you will need to locate the associated `/dev/input/event<#>` as `/dev/input/by-*` only lists usb connected inputs. The best way to do this is to run `cat /proc/bus/input/devices` and search for the associated bluetooth keyboard (the `Phys` output will have the MAC address). Here is an example: 182 | ``` 183 | I: Bus=0005 Vendor=05ac Product=0239 Version=0050 184 | N: Name="My Keyboard" 185 | P: Phys=SO:ME:MA:CA:DD:RS 186 | S: Sysfs=/devices/pci0000:00/0000:00:01.2/0000:02:00.0/0000:03:08.0/0000:07:00.3/usb3/3-5/3-5:1.0/bluetooth/hci0/hci0:256/0005:05AC:0239.0009/input/input40 187 | U: Uniq=SO:ME:MA:CA:DD:RS 188 | H: Handlers=sysrq kbd event25 leds 189 | 190 | ``` 191 | Notice in `Handlers`, it tells us the event # we need. So our path to our device is `/dev/input/event25`. 192 | 193 | #### Listening on all input devices 194 | 195 | If you still can not figure out which device to use, or want to map events from 196 | several devices, you can pass all (or chosen) devices as an arg: 197 | ``` 198 | --device /dev/input/event* 199 | # or 200 | --device /dev/input/event2 /dev/input/event10 201 | ``` 202 | 203 | #### Setting up ktrl as a Service (Optional) 204 | 205 | ktrl is a daemon that's designed to run as a background process. 206 | Therefore, you might want to set it up as a service. Feel free 207 | to skip this step if you just want to experiment with it. 208 | 209 | Creating a service will vary from distro to distro, 210 | though, here are some basic steps that'll get you started on `systemd` based systems - 211 | 212 | ``` 213 | # Again, asumming you've cloned and cd`d into the ktrl project 214 | 215 | edit ./etc/ktrl.service # change your device path 216 | sudo cp ./etc/ktrl.service /etc/systemd/system 217 | sudo systemctl daemon-reload 218 | sudo systemctl start ktrl.service 219 | ``` 220 | 221 | ## Configuration 222 | 223 | Finally, we get to the cool part! 224 | Though, let's briefly go over ktrl's config primitives before assembling our first config file 225 | 226 | ### Primitives 227 | 228 | #### Input Event Codes 229 | 230 | ktrl uses Linux's input-event-codes everywhere. 231 | The full list can be found either in Linux's codebase [here](https://github.com/torvalds/linux/blob/master/include/uapi/linux/input-event-codes.h#L75) 232 | or under ktrl's [KeyCode](https://github.com/ItayGarin/ktrl/blob/master/src/keys.rs) enum. 233 | 234 | Specifically, ktrl uses a subset of the event codes that describe keyboard keys. 235 | E.g `KEY_A` and `KEY_LEFTCTRL` describe the 'A' and Left-Ctrl keys. 236 | 237 | #### Actions 238 | 239 | Within a layer, we map a source key (ex: `KEY_A`) into an `Action`. 240 | Actions describe the **physical input** movements you'll apply to the source key. 241 | E.g A `TapHold` describes a **tap** and a **hold**. 242 | 243 | ##### Actions List 244 | 245 | - `Tap`: This is the default keyboard action. Use for simple key remappings. 246 | - `TapHold`: Attach different `Effect`s for a tap and hold. 247 | - `TapDance`: Attach different `Effect`s for a tap and multiple taps. 248 | 249 | #### Effects 250 | 251 | An `Action` will contain one or more `Effect`s. 252 | These are the **virtual output** effects that'll manifest following the action. 253 | E.g Playing a sound, toggling a layer, sending a key sequence, etc... 254 | 255 | ##### Effects List 256 | 257 | - `NoOp`: As the name suggests. This won't do anything. 258 | - `Key`: This is the default effect you're "used to". 259 | - `KeySticky`: Once pressed, the key will remain active until pressed again (like Capslock). 260 | - `KeySeq`: Outputs multiple keys at once. E.g `Meh` and `Hyper` are `KeySeq`s 261 | - `Meh`: A shorthand for `KeySeq(KEY_LEFTCTRL, KEY_LEFTALT, KEY_LEFTSHIFT)` 262 | - `Hyper`: A shorthand for `KeySeq(KEY_LEFTCTRL, KEY_LEFTALT, KEY_LEFTSHIFT, KEY_LEFTMETA)` 263 | - `ActivateProfile`: Activates a user-defined profile 264 | - `DeactivateProfile`: Deactivates a user-defined profile 265 | - `DeactivateAllProfiles`: Deactivates all user-defined profiles 266 | - `ToggleLayerAlias`: When pressed, either turns on or off a named layer. 267 | - `ToggleLayer`: When pressed, either turns on or off a layer. 268 | - `MomentaryLayer`: While pressed, the relevant layer will remain active 269 | - `Sound`: Plays one of the pre-built sounds 270 | - `SoundEx`: Plays a custom sound provided by you. 271 | - `Multi`: Lets you combine all the above effects. E.g `Multi([Sound(Sticky), KeySticky(KEY_LEFTCTRL)])` 272 | 273 | ### [Configuration File Format](https://github.com/ItayGarin/ktrl/blob/master/examples/cfg.ron) 274 | 275 | ktrl uses the wonderful [ron](https://github.com/ron-rs/ron) (Rust Object Notation) to make serializing 276 | configuration much easier. The format should be pretty intuitive, though please refer to the supplied [cfg.ron](https://github.com/ItayGarin/ktrl/blob/master/examples/cfg.ron) for a practical example. 277 | 278 | ## Examples 279 | 280 | ### Remapping `Ctrl` to `Capslock` 281 | 282 | This is probably one of the most effective yet simple changes you can make right now. 283 | You're left pinky will greatly appreciate this change in the long-run. 284 | 285 | Doing this with ktrl is easy. In one of your layers, add the following - 286 | 287 | ``` 288 | KEY_CAPSLOCK: Tap(Key(KEY_LEFTCTRL)), 289 | KEY_LEFTCTRL: Tap(Key(KEY_LEFTCTRL))), 290 | ``` 291 | 292 | Though, let's make this more interesting, shall we? 293 | 294 | To make the transition smoother, let's add an error sound effect to the left Ctrl. 295 | This'll remind you you're doing something wrong - 296 | 297 | ``` 298 | KEY_CAPSLOCK: Tap(Key(KEY_LEFTCTRL)), 299 | KEY_LEFTCTRL: Tap(Multi([Key(KEY_LEFTCTRL), Sound(Error)])), 300 | ``` 301 | 302 | Ah, much better! 303 | 304 | Of course, you can also go cold turkey and only leave the sound effect. Like so - 305 | 306 | ``` 307 | KEY_CAPSLOCK: Tap(Key(KEY_LEFTCTRL)), 308 | KEY_LEFTCTRL: Tap(Sound(Error)), 309 | ``` 310 | 311 | ### Home-row Modifiers 312 | 313 | Another change I've been experimenting with is mapping modifiers to home row keys 314 | Though, due note you'll have to calibrate the `tap_hold_wait_time` config value to 315 | avoid false-positives. 316 | 317 | Here's an example setup - 318 | 319 | ``` 320 | KEY_A: TapHold(Key(KEY_A), Key(KEY_LEFTCTRL)), 321 | KEY_S: TapHold(Key(KEY_S), Key(KEY_LEFTSHIFT)), 322 | KEY_D: TapHold(Key(KEY_D), Key(KEY_LEFTALT)), 323 | ``` 324 | 325 | This will make `A`, `S` and `D` act as usual on taps and as modifiers when held. 326 | 327 | ### Remap mouse button to control musical player 328 | 329 | If you have a mouse with a side buttons, you can remap a button to act as a media button. 330 | This example shows how to map tap to play/pause and long tap to next song: 331 | ``` 332 | BTN_SIDE: TapHold(Key(KEY_PLAYPAUSE), Key(KEY_NEXTSONG)) 333 | ``` 334 | 335 | ## Limitations 336 | 337 | - `TapHold` and `TapDance` require calibration and tinkering. as stated above, you'll have to tweak the wait times for both 338 | of these to minimize false-positives. 339 | 340 | ## Similar Projects 341 | - [alt](https://github.com/ItayGarin/alt): An Event Aggregator that connects to ktrl 342 | - [QMK](https://docs.qmk.fm/#/): An open source keyboard firmware (ktrl's inspiration) 343 | - [kmonad](https://github.com/david-janssen/kmonad): Very similar to ktrl (written in Haskell) 344 | - [xcape](https://github.com/alols/xcape): Implements tap-hold only for modifiers (Linux) 345 | - [Space2Ctrl](https://github.com/r0adrunner/Space2Ctrl): Similar to `xcape` 346 | - [interception tools](https://gitlab.com/interception/linux/tools): A framework for implementing tools like ktrl 347 | - [karabiner-elements](https://karabiner-elements.pqrs.org/): A mature keyboard customizer for Mac 348 | -------------------------------------------------------------------------------- /src/actions/tap_hold.rs: -------------------------------------------------------------------------------- 1 | // std 2 | use std::collections::HashSet; 3 | use std::vec::Vec; 4 | 5 | // ktrl 6 | use crate::effects::EffectValue; 7 | use crate::effects::OutEffects; 8 | use crate::effects::{CONTINUE, STOP}; 9 | use crate::keys::KeyCode; 10 | use crate::keys::KeyEvent; 11 | use crate::keys::KeyValue; 12 | use crate::layers::LayersManager; 13 | use crate::layers::LockOwner; 14 | 15 | // inner 16 | use inner::inner; 17 | 18 | use crate::layers::{Action, Action::TapHold, Effect}; 19 | 20 | // This struct isn't used in Action::TapHold 21 | // due to overhead it'll create in the config file. 22 | // Lots of wrappers in the ron text 23 | struct TapHoldCfg { 24 | tap_fx: Effect, 25 | hold_fx: Effect, 26 | } 27 | 28 | impl TapHoldCfg { 29 | fn from_action(action: &Action) -> Self { 30 | match action { 31 | TapHold(tap_fx, hold_fx) => Self { 32 | tap_fx: tap_fx.clone(), 33 | hold_fx: hold_fx.clone(), 34 | }, 35 | _ => unreachable!(), 36 | } 37 | } 38 | } 39 | 40 | #[derive(Clone, Debug, PartialEq)] 41 | pub struct TapHoldWaiting { 42 | pub timestamp: evdev_rs::TimeVal, 43 | } 44 | 45 | #[derive(Clone, Debug, PartialEq)] 46 | pub enum TapHoldState { 47 | ThIdle, 48 | ThWaiting(TapHoldWaiting), 49 | ThHolding, 50 | } 51 | 52 | pub struct TapHoldMgr { 53 | // KEY_MAX elements 54 | states: Vec, 55 | 56 | // A list of keys that are currently in ThWaiting 57 | waiting_keys: Vec, 58 | 59 | // A list of keys that are currently in ThHolding 60 | holding_keys: HashSet, 61 | 62 | wait_time: u64, 63 | } 64 | 65 | // --------------- TapHold-specific Functions ---------------------- 66 | 67 | impl TapHoldMgr { 68 | pub fn new(wait_time: u64) -> Self { 69 | assert!(wait_time < (i64::MAX as u64)); 70 | 71 | let mut states = Vec::new(); 72 | states.resize_with(KeyCode::KEY_MAX as usize, || TapHoldState::ThIdle); 73 | 74 | Self { 75 | states, 76 | waiting_keys: Vec::new(), 77 | holding_keys: HashSet::new(), 78 | wait_time, 79 | } 80 | } 81 | 82 | fn lock_key(l_mgr: &mut LayersManager, key: KeyCode) { 83 | l_mgr.lock_key(key, LockOwner::LkTapHold); 84 | } 85 | 86 | fn unlock_key(l_mgr: &mut LayersManager, key: KeyCode) { 87 | l_mgr.unlock_key(key, LockOwner::LkTapHold); 88 | } 89 | 90 | fn insert_waiting(&mut self, l_mgr: &mut LayersManager, key: KeyCode) { 91 | self.waiting_keys.push(key); 92 | Self::lock_key(l_mgr, key); 93 | } 94 | 95 | fn clear_waiting(&mut self, l_mgr: &mut LayersManager) { 96 | for key in self.waiting_keys.drain(..) { 97 | Self::unlock_key(l_mgr, key); 98 | } 99 | } 100 | 101 | fn insert_holding(&mut self, l_mgr: &mut LayersManager, key: KeyCode) { 102 | self.holding_keys.insert(key); 103 | Self::lock_key(l_mgr, key); 104 | } 105 | 106 | fn remove_holding(&mut self, l_mgr: &mut LayersManager, key: KeyCode) { 107 | self.holding_keys.remove(&key); 108 | Self::unlock_key(l_mgr, key); 109 | } 110 | 111 | fn handle_th_holding( 112 | &mut self, 113 | l_mgr: &mut LayersManager, 114 | event: &KeyEvent, 115 | th_cfg: &TapHoldCfg, 116 | ) -> OutEffects { 117 | let state = &mut self.states[event.code as usize]; 118 | assert!(*state == TapHoldState::ThHolding); 119 | let value = KeyValue::from(event.value); 120 | 121 | match value { 122 | KeyValue::Press => { 123 | // Should never happen. 124 | // Should only see this in the idle state 125 | unreachable!() 126 | } 127 | 128 | KeyValue::Release => { 129 | // Cleanup the hold 130 | *state = TapHoldState::ThIdle; 131 | self.clear_waiting(l_mgr); 132 | self.remove_holding(l_mgr, event.code); 133 | OutEffects::new(STOP, th_cfg.hold_fx.clone(), KeyValue::Release) 134 | // forward the release 135 | } 136 | 137 | KeyValue::Repeat => { 138 | // Drop repeats. These aren't supported for TapHolds 139 | OutEffects::empty(STOP) 140 | } 141 | } 142 | } 143 | 144 | fn handle_th_waiting( 145 | &mut self, 146 | l_mgr: &mut LayersManager, 147 | event: &KeyEvent, 148 | th_cfg: &TapHoldCfg, 149 | ) -> OutEffects { 150 | let value = KeyValue::from(event.value); 151 | 152 | match value { 153 | KeyValue::Press => { 154 | // Should never happen. 155 | // Should only see this in the idle state 156 | unreachable!() 157 | } 158 | 159 | KeyValue::Release => { 160 | let state = &self.states[event.code as usize]; 161 | let is_wait_over = self.is_waiting_over(state, event); 162 | 163 | let state = &mut self.states[event.code as usize]; 164 | *state = TapHoldState::ThIdle; 165 | 166 | self.clear_waiting(l_mgr); 167 | if is_wait_over { 168 | OutEffects::new_multiple( 169 | STOP, 170 | vec![ 171 | EffectValue::new(th_cfg.hold_fx.clone(), KeyValue::Press), 172 | EffectValue::new(th_cfg.hold_fx.clone(), KeyValue::Release), 173 | ], 174 | ) 175 | } else { 176 | OutEffects::new_multiple( 177 | STOP, 178 | vec![ 179 | EffectValue::new(th_cfg.tap_fx.clone(), KeyValue::Press), 180 | EffectValue::new(th_cfg.tap_fx.clone(), KeyValue::Release), 181 | ], 182 | ) 183 | } 184 | } 185 | 186 | KeyValue::Repeat => { 187 | // Drop repeats. These aren't supported for TapHolds 188 | OutEffects::empty(STOP) 189 | } 190 | } 191 | } 192 | 193 | fn handle_th_idle( 194 | &mut self, 195 | l_mgr: &mut LayersManager, 196 | event: &KeyEvent, 197 | th_cfg: &TapHoldCfg, 198 | ) -> OutEffects { 199 | let state = &mut self.states[event.code as usize]; 200 | assert!(*state == TapHoldState::ThIdle); 201 | 202 | let keycode: KeyCode = event.code; 203 | let value = KeyValue::from(event.value); 204 | 205 | match value { 206 | KeyValue::Press => { 207 | // Transition to the waiting state. 208 | // I.E waiting for either an interruptions => Press+Release the Tap effect 209 | // or for the TapHold wait period => Send a Hold effect press 210 | *state = TapHoldState::ThWaiting(TapHoldWaiting { 211 | timestamp: event.time.clone(), 212 | }); 213 | self.insert_waiting(l_mgr, keycode); 214 | OutEffects::empty(STOP) 215 | } 216 | 217 | KeyValue::Release => { 218 | // Forward the release 219 | OutEffects::new(STOP, th_cfg.tap_fx.clone(), KeyValue::Release) 220 | } 221 | 222 | KeyValue::Repeat => { 223 | // Drop repeats. These aren't supported for TapHolds 224 | OutEffects::empty(STOP) 225 | } 226 | } 227 | } 228 | 229 | // Assumes this is an event tied to a TapHold assigned MergedKey 230 | fn process_tap_hold_key( 231 | &mut self, 232 | l_mgr: &mut LayersManager, 233 | event: &KeyEvent, 234 | th_cfg: &TapHoldCfg, 235 | ) -> OutEffects { 236 | match self.states[event.code as usize] { 237 | TapHoldState::ThIdle => self.handle_th_idle(l_mgr, event, th_cfg), 238 | TapHoldState::ThWaiting(_) => self.handle_th_waiting(l_mgr, event, th_cfg), 239 | TapHoldState::ThHolding => self.handle_th_holding(l_mgr, event, th_cfg), 240 | } 241 | } 242 | 243 | // --------------- Non-TapHold Functions ---------------------- 244 | 245 | fn is_waiting_over(&self, key_state: &TapHoldState, event: &KeyEvent) -> bool { 246 | let new_timestamp = event.time.clone(); 247 | let wait_start_timestamp = inner!(key_state, if TapHoldState::ThWaiting) 248 | .timestamp 249 | .clone(); 250 | 251 | let secs_diff = new_timestamp.tv_sec - wait_start_timestamp.tv_sec; 252 | let usecs_diff = new_timestamp.tv_usec - wait_start_timestamp.tv_usec; 253 | let diff = (secs_diff * 1_000_000) + usecs_diff; 254 | diff >= (self.wait_time as i64) * 1000 255 | } 256 | 257 | fn process_non_tap_hold_key( 258 | &mut self, 259 | l_mgr: &mut LayersManager, 260 | event: &KeyEvent, 261 | ) -> OutEffects { 262 | let mut out = OutEffects::empty(CONTINUE); 263 | let waiting_keys: Vec = self.waiting_keys.drain(..).collect(); 264 | 265 | for waiting in waiting_keys { 266 | let action = &l_mgr.get(waiting).action; 267 | let th_cfg = TapHoldCfg::from_action(&action); 268 | 269 | Self::unlock_key(l_mgr, waiting); 270 | 271 | let state = &self.states[waiting as usize]; 272 | let is_wait_over = self.is_waiting_over(state, event); 273 | 274 | if is_wait_over { 275 | // Append the press hold_fx to the output 276 | out.insert(th_cfg.hold_fx.clone(), KeyValue::Press); 277 | let state = &mut self.states[waiting as usize]; 278 | *state = TapHoldState::ThHolding; 279 | self.insert_holding(l_mgr, waiting); 280 | } else { 281 | // Flush the press and release tap_fx 282 | out.insert(th_cfg.tap_fx, KeyValue::Press); 283 | 284 | // Revert to the idle state 285 | let state = &mut self.states[waiting as usize]; 286 | *state = TapHoldState::ThIdle; 287 | } 288 | } 289 | 290 | out 291 | } 292 | 293 | // --------------- High-Level Functions ---------------------- 294 | 295 | // Returns true if processed, false if skipped 296 | pub fn process(&mut self, l_mgr: &mut LayersManager, event: &KeyEvent) -> OutEffects { 297 | let code = event.code; 298 | let action = &l_mgr.get(code).action; 299 | 300 | if let Action::TapHold(..) = action { 301 | let th_cfg = TapHoldCfg::from_action(action); 302 | self.process_tap_hold_key(l_mgr, event, &th_cfg) 303 | } else { 304 | self.process_non_tap_hold_key(l_mgr, event) 305 | } 306 | } 307 | 308 | #[cfg(test)] 309 | pub fn is_idle(&self) -> bool { 310 | self.waiting_keys.len() == 0 && self.holding_keys.len() == 0 311 | } 312 | } 313 | 314 | #[cfg(test)] 315 | use crate::cfg::*; 316 | #[cfg(test)] 317 | use crate::effects::Effect::*; 318 | #[cfg(test)] 319 | use crate::keys::KeyCode::*; 320 | 321 | #[cfg(test)] 322 | const TEST_TAP_HOLD_WAIT_PERIOD: u64 = 550; 323 | 324 | #[test] 325 | fn test_skipped() { 326 | use std::collections::HashMap; 327 | let mut th_mgr = TapHoldMgr::new(TEST_TAP_HOLD_WAIT_PERIOD); 328 | let mut l_mgr = LayersManager::new(&vec![], &HashMap::new(), &HashMap::new()).unwrap(); 329 | let ev_non_th_press = KeyEvent::new_press(KEY_A); 330 | let ev_non_th_release = KeyEvent::new_release(KEY_A); 331 | assert_eq!( 332 | th_mgr.process(&mut l_mgr, &ev_non_th_press), 333 | OutEffects::empty(CONTINUE) 334 | ); 335 | assert_eq!( 336 | th_mgr.process(&mut l_mgr, &ev_non_th_release), 337 | OutEffects::empty(CONTINUE) 338 | ); 339 | assert_eq!(th_mgr.is_idle(), true); 340 | } 341 | 342 | #[test] 343 | fn test_tap() { 344 | use std::collections::HashMap; 345 | let cfg = Cfg::new( 346 | HashMap::new(), 347 | vec![vec![ 348 | (KEY_A, TapHold(Key(KEY_A), Key(KEY_LEFTCTRL))), 349 | (KEY_S, TapHold(Key(KEY_S), Key(KEY_LEFTALT))), 350 | ]], 351 | HashMap::new(), 352 | ); 353 | 354 | let mut l_mgr = LayersManager::new(&cfg.layers, &cfg.layer_aliases, &cfg.layer_profiles).unwrap(); 355 | let mut th_mgr = TapHoldMgr::new(TEST_TAP_HOLD_WAIT_PERIOD); 356 | 357 | l_mgr.init(); 358 | 359 | let ev_th_press = KeyEvent::new_press(KEY_A); 360 | let mut ev_th_release = KeyEvent::new_release(KEY_A); 361 | ev_th_release.time.tv_usec += 100; 362 | 363 | // 1st 364 | assert_eq!( 365 | th_mgr.process(&mut l_mgr, &ev_th_press), 366 | OutEffects::empty(STOP) 367 | ); 368 | assert_eq!(th_mgr.is_idle(), false); 369 | assert_eq!(l_mgr.is_key_locked(KEY_A), true); 370 | assert_eq!( 371 | th_mgr.process(&mut l_mgr, &ev_th_release), 372 | OutEffects::new_multiple( 373 | STOP, 374 | vec![ 375 | EffectValue::new(Effect::Key(KEY_A.into()), KeyValue::Press), 376 | EffectValue::new(Effect::Key(KEY_A.into()), KeyValue::Release), 377 | ] 378 | ) 379 | ); 380 | assert_eq!(th_mgr.is_idle(), true); 381 | assert_eq!(l_mgr.is_key_locked(KEY_A), false); 382 | 383 | // 2nd 384 | assert_eq!( 385 | th_mgr.process(&mut l_mgr, &ev_th_press), 386 | OutEffects::empty(STOP) 387 | ); 388 | assert_eq!(th_mgr.is_idle(), false); 389 | assert_eq!(l_mgr.is_key_locked(KEY_A), true); 390 | assert_eq!( 391 | th_mgr.process(&mut l_mgr, &ev_th_release), 392 | OutEffects::new_multiple( 393 | STOP, 394 | vec![ 395 | EffectValue::new(Effect::Key(KEY_A.into()), KeyValue::Press), 396 | EffectValue::new(Effect::Key(KEY_A.into()), KeyValue::Release), 397 | ] 398 | ) 399 | ); 400 | assert_eq!(th_mgr.is_idle(), true); 401 | assert_eq!(l_mgr.is_key_locked(KEY_A), false); 402 | 403 | // interruptions: 1 404 | ev_th_release.time.tv_usec = (TEST_TAP_HOLD_WAIT_PERIOD * 1000 + 1) as i64; 405 | let ev_non_th_press = KeyEvent::new_press(KEY_W); 406 | let ev_non_th_release = KeyEvent::new_release(KEY_W); 407 | assert_eq!( 408 | th_mgr.process(&mut l_mgr, &ev_th_press), 409 | OutEffects::empty(STOP) 410 | ); 411 | assert_eq!( 412 | th_mgr.process(&mut l_mgr, &ev_non_th_press), 413 | OutEffects::new(CONTINUE, Effect::Key(KEY_A.into()), KeyValue::Press) 414 | ); 415 | assert_eq!( 416 | th_mgr.process(&mut l_mgr, &ev_th_release), 417 | OutEffects::new(STOP, Effect::Key(KEY_A.into()), KeyValue::Release) 418 | ); 419 | assert_eq!( 420 | th_mgr.process(&mut l_mgr, &ev_non_th_release), 421 | OutEffects::empty(CONTINUE) 422 | ); 423 | assert_eq!(th_mgr.is_idle(), true); 424 | 425 | // interruptions: 2 426 | ev_th_release.time.tv_usec = (TEST_TAP_HOLD_WAIT_PERIOD * 1000 + 1) as i64; 427 | let ev_non_th_press = KeyEvent::new_press(KEY_W); 428 | let ev_non_th_release = KeyEvent::new_release(KEY_W); 429 | assert_eq!( 430 | th_mgr.process(&mut l_mgr, &ev_th_press), 431 | OutEffects::empty(STOP) 432 | ); 433 | assert_eq!( 434 | th_mgr.process(&mut l_mgr, &ev_non_th_press), 435 | OutEffects::new(CONTINUE, Effect::Key(KEY_A.into()), KeyValue::Press) 436 | ); 437 | assert_eq!( 438 | th_mgr.process(&mut l_mgr, &ev_non_th_release), 439 | OutEffects::empty(CONTINUE) 440 | ); 441 | assert_eq!( 442 | th_mgr.process(&mut l_mgr, &ev_th_release), 443 | OutEffects::new(STOP, Effect::Key(KEY_A.into()), KeyValue::Release) 444 | ); 445 | assert_eq!(th_mgr.is_idle(), true); 446 | assert_eq!(l_mgr.is_key_locked(KEY_A), false); 447 | assert_eq!(l_mgr.is_key_locked(KEY_W), false); 448 | } 449 | 450 | #[test] 451 | fn test_hold() { 452 | use std::collections::HashMap; 453 | let mut h = HashMap::new(); 454 | h.insert("base".to_string(), 0); 455 | let cfg = Cfg::new( 456 | h, 457 | vec![vec![ 458 | (KEY_A, TapHold(Key(KEY_A), Key(KEY_LEFTCTRL))), 459 | (KEY_S, TapHold(Key(KEY_S), Key(KEY_LEFTALT))), 460 | ]], 461 | HashMap::new(), 462 | ); 463 | 464 | let mut l_mgr = LayersManager::new(&cfg.layers, &cfg.layer_aliases, &cfg.layer_profiles).unwrap(); 465 | let mut th_mgr = TapHoldMgr::new(TEST_TAP_HOLD_WAIT_PERIOD); 466 | 467 | l_mgr.init(); 468 | 469 | let ev_th_press = KeyEvent::new_press(KEY_A); 470 | let mut ev_th_release = KeyEvent::new_release(KEY_A); 471 | ev_th_release.time.tv_usec = 1; 472 | 473 | // No hold + other key chord 474 | assert_eq!( 475 | th_mgr.process(&mut l_mgr, &ev_th_press), 476 | OutEffects::empty(STOP) 477 | ); 478 | assert_eq!(l_mgr.is_key_locked(KEY_A), true); 479 | assert_eq!( 480 | th_mgr.process(&mut l_mgr, &ev_th_release), 481 | OutEffects::new_multiple( 482 | STOP, 483 | vec![ 484 | EffectValue::new(Effect::Key(KEY_A.into()), KeyValue::Press), 485 | EffectValue::new(Effect::Key(KEY_A.into()), KeyValue::Release), 486 | ] 487 | ) 488 | ); 489 | assert_eq!(th_mgr.is_idle(), true); 490 | assert_eq!(l_mgr.is_key_locked(KEY_A), false); 491 | 492 | // ------------------------------- 493 | 494 | // Hold with other key 495 | let mut ev_non_th_press = KeyEvent::new_press(KEY_W); 496 | let mut ev_non_th_release = KeyEvent::new_release(KEY_W); 497 | ev_non_th_press.time.tv_usec = (TEST_TAP_HOLD_WAIT_PERIOD * 1000 + 1) as i64; 498 | ev_non_th_release.time.tv_usec = (TEST_TAP_HOLD_WAIT_PERIOD * 1000 + 2) as i64; 499 | ev_th_release.time.tv_usec = (TEST_TAP_HOLD_WAIT_PERIOD * 1000 + 3) as i64; 500 | 501 | assert_eq!( 502 | th_mgr.process(&mut l_mgr, &ev_th_press), 503 | OutEffects::empty(STOP) 504 | ); 505 | assert_eq!( 506 | th_mgr.process(&mut l_mgr, &ev_non_th_press), 507 | OutEffects::new(CONTINUE, Effect::Key(KEY_LEFTCTRL.into()), KeyValue::Press) 508 | ); 509 | assert_eq!(th_mgr.is_idle(), false); 510 | assert_eq!(l_mgr.is_key_locked(KEY_A), true); 511 | assert_eq!( 512 | th_mgr.process(&mut l_mgr, &ev_non_th_release), 513 | OutEffects::empty(CONTINUE) 514 | ); 515 | assert_eq!(l_mgr.is_key_locked(KEY_A), true); 516 | assert_eq!( 517 | th_mgr.process(&mut l_mgr, &ev_th_release), 518 | OutEffects::new(STOP, Effect::Key(KEY_LEFTCTRL.into()), KeyValue::Release) 519 | ); 520 | assert_eq!(l_mgr.is_key_locked(KEY_A), false); 521 | 522 | // ------------------------------- 523 | 524 | // Hold with other key (different order) 525 | ev_non_th_press.time.tv_usec = (TEST_TAP_HOLD_WAIT_PERIOD * 1000 + 1) as i64; 526 | ev_th_release.time.tv_usec = (TEST_TAP_HOLD_WAIT_PERIOD * 1000 + 2) as i64; 527 | ev_non_th_release.time.tv_usec = (TEST_TAP_HOLD_WAIT_PERIOD * 1000 + 3) as i64; 528 | 529 | assert_eq!( 530 | th_mgr.process(&mut l_mgr, &ev_th_press), 531 | OutEffects::empty(STOP) 532 | ); 533 | assert_eq!( 534 | th_mgr.process(&mut l_mgr, &ev_non_th_press), 535 | OutEffects::new(CONTINUE, Effect::Key(KEY_LEFTCTRL.into()), KeyValue::Press) 536 | ); 537 | assert_eq!(th_mgr.is_idle(), false); 538 | assert_eq!(l_mgr.is_key_locked(KEY_A), true); 539 | assert_eq!( 540 | th_mgr.process(&mut l_mgr, &ev_th_release), 541 | OutEffects::new(STOP, Effect::Key(KEY_LEFTCTRL.into()), KeyValue::Release) 542 | ); 543 | assert_eq!(l_mgr.is_key_locked(KEY_A), false); 544 | assert_eq!( 545 | th_mgr.process(&mut l_mgr, &ev_non_th_release), 546 | OutEffects::empty(CONTINUE) 547 | ); 548 | assert_eq!(l_mgr.is_key_locked(KEY_A), false); 549 | } 550 | -------------------------------------------------------------------------------- /src/layers.rs: -------------------------------------------------------------------------------- 1 | use crate::keys::KeyCode::*; 2 | use log::{debug, warn}; 3 | use serde::Deserialize; 4 | use std::collections::HashMap; 5 | use std::convert::TryFrom; 6 | use std::vec::Vec; 7 | 8 | #[cfg(feature = "notify")] 9 | use log::info; 10 | 11 | pub use crate::actions::tap_hold::TapHoldState; 12 | pub use crate::actions::Action; 13 | pub use crate::effects::Effect; 14 | use crate::keys::KeyCode; 15 | 16 | // -------------- Constants ------------- 17 | 18 | const MAX_KEY: usize = KEY_MAX as usize; 19 | 20 | // ---------------- Types --------------- 21 | 22 | pub type LayerIndex = usize; 23 | pub type Layer = HashMap; 24 | pub type LayerAliases = HashMap; 25 | pub type LayerProfiles = HashMap; 26 | 27 | #[derive(Deserialize, Clone, Debug)] 28 | pub struct Profile { 29 | pub indices: Vec, 30 | pub aliases: Vec, 31 | } 32 | 33 | #[derive(Clone, Debug)] 34 | pub struct MergedKey { 35 | pub code: KeyCode, 36 | pub action: Action, 37 | pub layer_index: LayerIndex, 38 | } 39 | 40 | // MergedKey is wrapped in an Option because 41 | // not all integer in the KEY_MAX range 42 | // have a matching `KeyCode` 43 | pub type Merged = Vec>; 44 | 45 | pub type Layers = Vec; 46 | type LayersStates = Vec; 47 | 48 | #[derive(Copy, Clone, Debug, PartialEq, Eq)] 49 | pub enum LockOwner { 50 | LkTapHold, 51 | LkTapDance, 52 | LkSticky, 53 | } 54 | 55 | pub struct LayersManager { 56 | // Serves as a cache of the result 57 | // of stacking all the layers on top of each other. 58 | pub merged: Merged, 59 | 60 | // This is a read-only representation of the user's layer configuration. 61 | // The 0th layer is the base and will always be active 62 | pub layers: Layers, 63 | 64 | pub layer_aliases: LayerAliases, 65 | 66 | pub layer_profiles: LayerProfiles, 67 | 68 | // Holds the on/off state for each layer 69 | pub layers_states: LayersStates, 70 | 71 | // Allows stateful modules (like TapHold) 72 | // to lock certain keys. This'll prevent them 73 | // from being changed by layer changes 74 | pub key_locks: HashMap, 75 | 76 | // Similar to key_locks, 77 | // but locks prevents layer changes globally. 78 | // I.E not key-specific like key_locks 79 | pub global_lock: Option, 80 | 81 | // For sending notifications about layer changes 82 | #[cfg(feature = "notify")] 83 | notify_socket: zmq::Socket, 84 | } 85 | 86 | // -------------- Implementation ------------- 87 | 88 | fn init_merged() -> Merged { 89 | let mut merged: Merged = Vec::with_capacity(MAX_KEY); 90 | 91 | for i in 0..MAX_KEY { 92 | if let Ok(code) = KeyCode::try_from(i) { 93 | let effect = Effect::Key(code); 94 | let action = Action::Tap(effect); 95 | let layer_index = 0; 96 | merged.push(Some(MergedKey { 97 | code, 98 | action, 99 | layer_index, 100 | })); 101 | } else { 102 | merged.push(None); 103 | } 104 | } 105 | 106 | assert!(merged.len() == MAX_KEY); 107 | merged 108 | } 109 | 110 | impl LayersManager { 111 | pub fn new( 112 | layers: &Layers, 113 | layer_aliases: &LayerAliases, 114 | layer_profiles: &LayerProfiles, 115 | #[cfg(feature = "notify")] notify_port: usize, 116 | ) -> Result { 117 | let merged = init_merged(); 118 | let layers = layers.clone(); 119 | let layer_aliases = layer_aliases.clone(); 120 | let layer_profiles = layer_profiles.clone(); 121 | let layers_count = layers.len(); 122 | let key_locks = HashMap::new(); 123 | 124 | let mut layers_states = Vec::new(); 125 | layers_states.resize_with(layers_count, Default::default); 126 | 127 | #[cfg(feature = "notify")] 128 | let notify_socket = { 129 | let ctx = zmq::Context::new(); 130 | let socket = ctx.socket(zmq::PUB)?; 131 | let endpoint = format!("tcp://127.0.0.1:{}", notify_port); 132 | socket.bind(&endpoint)?; 133 | info!("Sending notifications on {}", endpoint); 134 | socket 135 | }; 136 | 137 | Ok(LayersManager { 138 | merged, 139 | layers, 140 | layer_aliases, 141 | layer_profiles, 142 | layers_states, 143 | key_locks, 144 | global_lock: None, 145 | #[cfg(feature = "notify")] 146 | notify_socket, 147 | }) 148 | } 149 | 150 | // ---------------- Locks ------------------------- 151 | 152 | pub fn lock_key(&mut self, key: KeyCode, owner: LockOwner) { 153 | assert!(!self.key_locks.contains_key(&key)); 154 | self.key_locks.insert(key, owner); 155 | } 156 | 157 | pub fn unlock_key(&mut self, key: KeyCode, owner: LockOwner) { 158 | assert!(self.key_locks[&key] == owner); 159 | self.key_locks.remove(&key); 160 | } 161 | 162 | pub fn lock_all(&mut self, owner: LockOwner) { 163 | assert!(self.global_lock.is_none()); 164 | self.global_lock = Some(owner); 165 | } 166 | 167 | pub fn unlock_all(&mut self, _owner: LockOwner) { 168 | assert!(self.global_lock.is_some()); 169 | self.global_lock = None; 170 | } 171 | 172 | pub fn is_all_locked(&self) -> bool { 173 | self.global_lock.is_some() 174 | } 175 | 176 | #[cfg(test)] 177 | pub fn is_key_locked(&self, key: KeyCode) -> bool { 178 | self.key_locks.contains_key(&key) 179 | } 180 | 181 | // ---------------- Layers Changes ------------------------- 182 | 183 | pub fn init(&mut self) { 184 | self.turn_layer_on(0); 185 | } 186 | 187 | fn is_overriding_key( 188 | &self, 189 | candidate_code: KeyCode, 190 | candidate_layer_index: LayerIndex, 191 | ) -> bool { 192 | let current = self.get(candidate_code); 193 | return candidate_layer_index >= current.layer_index; 194 | } 195 | 196 | fn get_replacement_merged_key(&self, layers: &Layers, removed_code: KeyCode) -> MergedKey { 197 | let current = self.get(removed_code).layer_index; 198 | for i in (0..current).rev() { 199 | if !self.layers_states[i] { 200 | continue; 201 | } 202 | 203 | let lower_layer = &layers[i]; 204 | if !lower_layer.contains_key(&removed_code) { 205 | continue; 206 | } 207 | 208 | let lower_action = &layers[i][&removed_code]; 209 | let replacement = MergedKey { 210 | code: removed_code, 211 | action: lower_action.clone(), 212 | layer_index: i, 213 | }; 214 | 215 | return replacement; 216 | } 217 | 218 | MergedKey { 219 | code: removed_code, 220 | action: Action::Tap(Effect::Key(removed_code)), 221 | layer_index: 0, 222 | } 223 | } 224 | 225 | #[cfg(feature = "notify")] 226 | fn send_notification(&mut self, index: LayerIndex, state: bool) { 227 | let str_state = match state { 228 | true => "on", 229 | false => "off", 230 | }; 231 | 232 | let msg = format!("layer {}:{}", index, str_state); 233 | if let Err(err) = self.notify_socket.send(&msg, 0) { 234 | warn!("Failed to send a notification. {}", err); 235 | } else { 236 | debug!("Sent a notification: '{}'", &msg); 237 | } 238 | } 239 | 240 | pub fn get(&self, key: KeyCode) -> &MergedKey { 241 | match &self.merged[usize::from(key)] { 242 | Some(merged_key) => merged_key, 243 | _ => panic!("Invalid KeyCode"), 244 | } 245 | } 246 | 247 | // Returns None if false. Some(KeyCode) with the locked key 248 | fn will_layer_override_held_lock(&self, layer: &Layer) -> Option { 249 | for key in layer.keys() { 250 | if self.key_locks.contains_key(key) { 251 | return Some(*key); 252 | } 253 | } 254 | 255 | None 256 | } 257 | 258 | fn is_layer_change_safe(&self, index: LayerIndex, layer: &Layer) -> bool { 259 | if self.is_all_locked() { 260 | warn!( 261 | "Can't turn layer {} on. You're currently using a blocking action/effect", 262 | index 263 | ); 264 | return false; 265 | } 266 | 267 | if let Some(locked) = self.will_layer_override_held_lock(&layer) { 268 | warn!("Can't turn layer {} on. {:?} is in use", index, locked); 269 | return false; 270 | } 271 | 272 | true 273 | } 274 | 275 | pub fn turn_layer_on(&mut self, index: LayerIndex) { 276 | if !self.layers_states[index] { 277 | let layer = &self.layers[index]; 278 | 279 | if !self.is_layer_change_safe(index, layer) { 280 | return; 281 | } 282 | 283 | for (code, action) in layer { 284 | let is_overriding = self.is_overriding_key(*code, index); 285 | 286 | if is_overriding { 287 | let new_entry = MergedKey { 288 | code: *code, 289 | action: action.clone(), 290 | layer_index: index, 291 | }; 292 | 293 | self.merged[usize::from(*code)] = Some(new_entry); 294 | } 295 | } 296 | 297 | self.layers_states[index] = true; 298 | debug!("Turned layer {} on", index); 299 | 300 | #[cfg(feature = "notify")] 301 | self.send_notification(index, true); 302 | } 303 | } 304 | 305 | pub fn turn_layer_off(&mut self, index: LayerIndex) { 306 | if index > 0 { 307 | if self.layers_states[index] { 308 | let layer = &self.layers[index]; 309 | if !self.is_layer_change_safe(index, layer) { 310 | return; 311 | } 312 | 313 | for (code, _action) in layer { 314 | let replacement_entry = self.get_replacement_merged_key(&self.layers, *code); 315 | self.merged[usize::from(*code)] = Some(replacement_entry); 316 | } 317 | 318 | self.layers_states[index] = false; 319 | debug!("Turned layer {} off", index); 320 | 321 | #[cfg(feature = "notify")] 322 | self.send_notification(index, false); 323 | } 324 | } 325 | } 326 | 327 | pub fn toggle_layer(&mut self, index: LayerIndex) { 328 | let is_layer_on = self.layers_states[index]; 329 | 330 | if is_layer_on { 331 | self.turn_layer_off(index); 332 | } else { 333 | self.turn_layer_on(index); 334 | } 335 | } 336 | 337 | pub fn toggle_layer_alias(&mut self, name: String) { 338 | if let Some(index) = self.get_idx_from_alias(name) { 339 | // clone into idx to avoid mutable borrow reservation conflict 340 | let idx = index.clone(); 341 | self.toggle_layer(idx); 342 | } 343 | } 344 | 345 | pub fn turn_alias_on(&mut self, name: String) { 346 | if let Some(index) = self.get_idx_from_alias(name) { 347 | let idx = index.clone(); 348 | self.turn_layer_on(idx); 349 | } 350 | } 351 | pub fn turn_alias_off(&mut self, name: String) { 352 | if let Some(index) = self.get_idx_from_alias(name) { 353 | let idx = index.clone(); 354 | self.turn_layer_off(idx); 355 | } 356 | } 357 | 358 | pub fn toggle_profile(&mut self, name: String, on: bool) { 359 | match self.layer_profiles.get(&name) { 360 | Some(profile) => { 361 | let profile = profile.clone(); 362 | if on { 363 | for index in profile.indices.iter() { 364 | self.turn_layer_on(*index); 365 | } 366 | for alias in profile.aliases.iter() { 367 | self.turn_alias_on(alias.clone()) 368 | } 369 | } else { 370 | for index in profile.indices.iter() { 371 | self.turn_layer_off(*index) 372 | } 373 | for alias in profile.aliases.iter() { 374 | self.turn_alias_off(alias.clone()) 375 | } 376 | } 377 | } 378 | _ => {} 379 | } 380 | } 381 | 382 | fn get_idx_from_alias(&self, name: String) -> Option<&usize> { 383 | self.layer_aliases.get(&name) 384 | } 385 | } 386 | 387 | // ---------------------------------------------------------- 388 | // ----------------------- Tests ---------------------------- 389 | // ---------------------------------------------------------- 390 | 391 | #[cfg(test)] 392 | use std::collections::HashSet; 393 | 394 | #[cfg(test)] 395 | lazy_static::lazy_static! { 396 | static ref MISSING_KEYCODES: HashSet = { 397 | let mut m = HashSet::new(); 398 | let ranges = vec![ 399 | 84..85, 400 | 195..200, 401 | 249..352, 402 | 443..448, 403 | 452..464, 404 | 485..497, 405 | 507..512, 406 | 543..560, 407 | 562..576, 408 | 585..592, 409 | 594..608, 410 | 633..767 411 | ]; 412 | 413 | for range in ranges { 414 | for i in range { 415 | m.insert(i); 416 | } 417 | } 418 | 419 | m 420 | }; 421 | } 422 | 423 | #[cfg(test)] 424 | use crate::effects::Effect::*; 425 | 426 | #[cfg(test)] 427 | use crate::actions::Action::*; 428 | 429 | #[cfg(test)] 430 | use crate::cfg::Cfg; 431 | 432 | #[test] 433 | fn test_mgr() { 434 | let mut h = HashMap::new(); 435 | h.insert("base".to_string(), 0); 436 | h.insert("arrows".to_string(), 1); 437 | h.insert("asdf".to_string(), 2); 438 | let cfg = Cfg::new( 439 | h, 440 | vec![ 441 | vec![ 442 | // Ex: switch CTRL <--> Capslock 443 | (KEY_LEFTCTRL, Tap(Key(KEY_CAPSLOCK))), 444 | (KEY_CAPSLOCK, Tap(Key(KEY_LEFTCTRL))), 445 | ], 446 | // 1: arrows layer 447 | vec![ 448 | // Ex: switch CTRL <--> Capslock 449 | (KEY_H, Tap(Key(KEY_LEFT))), 450 | (KEY_J, Tap(Key(KEY_DOWN))), 451 | (KEY_K, Tap(Key(KEY_UP))), 452 | (KEY_L, Tap(Key(KEY_RIGHT))), 453 | (KEY_A, Tap(Key(KEY_Q))), 454 | (KEY_S, Tap(Key(KEY_W))), 455 | (KEY_D, Tap(Key(KEY_E))), 456 | ], 457 | // 2: asdf modifiers 458 | vec![ 459 | // Ex: switch CTRL <--> Capslock 460 | (KEY_A, TapHold(Key(KEY_A), Key(KEY_LEFTCTRL))), 461 | (KEY_S, TapHold(Key(KEY_S), Key(KEY_LEFTSHIFT))), 462 | (KEY_D, TapHold(Key(KEY_D), Key(KEY_LEFTALT))), 463 | ], 464 | ], 465 | HashMap::new(), 466 | ); 467 | 468 | let mut mgr = LayersManager::new(&cfg.layers, &cfg.layer_aliases, &cfg.layer_profiles).unwrap(); 469 | mgr.init(); 470 | assert_eq!(mgr.layers_states.len(), 3); 471 | assert_eq!(mgr.layers_states[0], true); 472 | 473 | mgr.turn_layer_on(2); 474 | assert_eq!(mgr.get(KEY_H.into()).action, Tap(Key(KEY_H))); 475 | assert_eq!(mgr.get(KEY_J.into()).action, Tap(Key(KEY_J))); 476 | assert_eq!(mgr.get(KEY_K.into()).action, Tap(Key(KEY_K))); 477 | assert_eq!(mgr.get(KEY_L.into()).action, Tap(Key(KEY_L))); 478 | 479 | assert_eq!( 480 | mgr.get(KEY_A.into()).action, 481 | TapHold(Key(KEY_A), Key(KEY_LEFTCTRL)) 482 | ); 483 | assert_eq!( 484 | mgr.get(KEY_S.into()).action, 485 | TapHold(Key(KEY_S), Key(KEY_LEFTSHIFT)) 486 | ); 487 | assert_eq!( 488 | mgr.get(KEY_D.into()).action, 489 | TapHold(Key(KEY_D), Key(KEY_LEFTALT)) 490 | ); 491 | 492 | mgr.turn_layer_off(2); 493 | assert_eq!(mgr.get(KEY_A.into()).action, Tap(Key(KEY_A))); 494 | assert_eq!(mgr.get(KEY_S.into()).action, Tap(Key(KEY_S))); 495 | assert_eq!(mgr.get(KEY_D.into()).action, Tap(Key(KEY_D))); 496 | 497 | mgr.turn_layer_on(2); 498 | mgr.turn_layer_on(1); 499 | assert_eq!(mgr.get(KEY_H.into()).action, Tap(Key(KEY_LEFT))); 500 | assert_eq!(mgr.get(KEY_J.into()).action, Tap(Key(KEY_DOWN))); 501 | assert_eq!(mgr.get(KEY_K.into()).action, Tap(Key(KEY_UP))); 502 | assert_eq!(mgr.get(KEY_L.into()).action, Tap(Key(KEY_RIGHT))); 503 | 504 | assert_eq!( 505 | mgr.get(KEY_A.into()).action, 506 | TapHold(Key(KEY_A), Key(KEY_LEFTCTRL)) 507 | ); 508 | assert_eq!( 509 | mgr.get(KEY_S.into()).action, 510 | TapHold(Key(KEY_S), Key(KEY_LEFTSHIFT)) 511 | ); 512 | assert_eq!( 513 | mgr.get(KEY_D.into()).action, 514 | TapHold(Key(KEY_D), Key(KEY_LEFTALT)) 515 | ); 516 | 517 | mgr.turn_layer_off(2); 518 | assert_eq!(mgr.get(KEY_H.into()).action, Tap(Key(KEY_LEFT))); 519 | assert_eq!(mgr.get(KEY_J.into()).action, Tap(Key(KEY_DOWN))); 520 | assert_eq!(mgr.get(KEY_K.into()).action, Tap(Key(KEY_UP))); 521 | assert_eq!(mgr.get(KEY_L.into()).action, Tap(Key(KEY_RIGHT))); 522 | 523 | mgr.toggle_layer(1); 524 | assert_eq!(mgr.get(KEY_H.into()).action, Tap(Key(KEY_H))); 525 | assert_eq!(mgr.get(KEY_J.into()).action, Tap(Key(KEY_J))); 526 | assert_eq!(mgr.get(KEY_K.into()).action, Tap(Key(KEY_K))); 527 | assert_eq!(mgr.get(KEY_L.into()).action, Tap(Key(KEY_L))); 528 | 529 | assert_eq!(mgr.get(KEY_A.into()).action, Tap(Key(KEY_A))); 530 | assert_eq!(mgr.get(KEY_S.into()).action, Tap(Key(KEY_S))); 531 | assert_eq!(mgr.get(KEY_D.into()).action, Tap(Key(KEY_D))); 532 | 533 | mgr.toggle_layer(1); 534 | assert_eq!(mgr.get(KEY_H.into()).action, Tap(Key(KEY_LEFT))); 535 | assert_eq!(mgr.get(KEY_J.into()).action, Tap(Key(KEY_DOWN))); 536 | assert_eq!(mgr.get(KEY_K.into()).action, Tap(Key(KEY_UP))); 537 | assert_eq!(mgr.get(KEY_L.into()).action, Tap(Key(KEY_RIGHT))); 538 | 539 | assert_eq!(mgr.get(KEY_A.into()).action, Tap(Key(KEY_Q))); 540 | assert_eq!(mgr.get(KEY_S.into()).action, Tap(Key(KEY_W))); 541 | assert_eq!(mgr.get(KEY_D.into()).action, Tap(Key(KEY_E))); 542 | 543 | mgr.lock_all(LockOwner::LkTapHold); 544 | mgr.toggle_layer(1); 545 | assert_eq!(mgr.get(KEY_H.into()).action, Tap(Key(KEY_LEFT))); 546 | mgr.unlock_all(LockOwner::LkTapHold); 547 | mgr.toggle_layer(1); 548 | assert_eq!(mgr.get(KEY_H.into()).action, Tap(Key(KEY_H))); 549 | } 550 | 551 | #[test] 552 | fn test_overlapping_keys() { 553 | let mut h = HashMap::new(); 554 | h.insert("base".to_string(), 0); 555 | h.insert("arrows".to_string(), 1); 556 | let cfg = Cfg::new( 557 | h, 558 | vec![ 559 | // 0: base layer 560 | vec![(KEY_A, TapHold(Key(KEY_A), Key(KEY_LEFTSHIFT)))], 561 | // 1: arrows layer 562 | // Ex: switch CTRL <--> Capslock 563 | vec![(KEY_A, TapHold(Key(KEY_A), Key(KEY_LEFTSHIFT)))], 564 | ], 565 | HashMap::new(), 566 | ); 567 | 568 | let mut mgr = LayersManager::new(&cfg.layers, &cfg.layer_aliases, &cfg.layer_profiles).unwrap(); 569 | mgr.init(); 570 | 571 | assert_eq!(mgr.layers_states.len(), 2); 572 | assert_eq!( 573 | mgr.get(KEY_A.into()).action, 574 | TapHold(Key(KEY_A), Key(KEY_LEFTSHIFT)) 575 | ); 576 | mgr.turn_layer_on(1); 577 | assert_eq!( 578 | mgr.get(KEY_A.into()).action, 579 | TapHold(Key(KEY_A), Key(KEY_LEFTSHIFT)) 580 | ); 581 | mgr.turn_layer_off(1); 582 | assert_eq!( 583 | mgr.get(KEY_A.into()).action, 584 | TapHold(Key(KEY_A), Key(KEY_LEFTSHIFT)) 585 | ); 586 | } 587 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "aho-corasick" 7 | version = "0.7.20" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "cc936419f96fa211c1b9166887b38e5e40b19958e5b895be7c1f93adec7071ac" 10 | dependencies = [ 11 | "memchr", 12 | ] 13 | 14 | [[package]] 15 | name = "alsa-sys" 16 | version = "0.1.2" 17 | source = "registry+https://github.com/rust-lang/crates.io-index" 18 | checksum = "b0edcbbf9ef68f15ae1b620f722180b82a98b6f0628d30baa6b8d2a5abc87d58" 19 | dependencies = [ 20 | "libc", 21 | "pkg-config", 22 | ] 23 | 24 | [[package]] 25 | name = "ansi_term" 26 | version = "0.11.0" 27 | source = "registry+https://github.com/rust-lang/crates.io-index" 28 | checksum = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b" 29 | dependencies = [ 30 | "winapi", 31 | ] 32 | 33 | [[package]] 34 | name = "atty" 35 | version = "0.2.14" 36 | source = "registry+https://github.com/rust-lang/crates.io-index" 37 | checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" 38 | dependencies = [ 39 | "hermit-abi", 40 | "libc", 41 | "winapi", 42 | ] 43 | 44 | [[package]] 45 | name = "autocfg" 46 | version = "1.0.0" 47 | source = "registry+https://github.com/rust-lang/crates.io-index" 48 | checksum = "f8aac770f1885fd7e387acedd76065302551364496e46b3dd00860b2f8359b9d" 49 | 50 | [[package]] 51 | name = "base64" 52 | version = "0.12.1" 53 | source = "registry+https://github.com/rust-lang/crates.io-index" 54 | checksum = "53d1ccbaf7d9ec9537465a97bf19edc1a4e158ecb49fc16178202238c569cc42" 55 | 56 | [[package]] 57 | name = "bindgen" 58 | version = "0.53.3" 59 | source = "registry+https://github.com/rust-lang/crates.io-index" 60 | checksum = "c72a978d268b1d70b0e963217e60fdabd9523a941457a6c42a7315d15c7e89e5" 61 | dependencies = [ 62 | "bitflags", 63 | "cexpr", 64 | "cfg-if 0.1.10", 65 | "clang-sys", 66 | "lazy_static", 67 | "lazycell", 68 | "peeking_take_while", 69 | "proc-macro2", 70 | "quote", 71 | "regex", 72 | "rustc-hash", 73 | "shlex", 74 | ] 75 | 76 | [[package]] 77 | name = "bitflags" 78 | version = "1.2.1" 79 | source = "registry+https://github.com/rust-lang/crates.io-index" 80 | checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" 81 | 82 | [[package]] 83 | name = "byteorder" 84 | version = "1.3.4" 85 | source = "registry+https://github.com/rust-lang/crates.io-index" 86 | checksum = "08c48aae112d48ed9f069b33538ea9e3e90aa263cfa3d1c24309612b1f7472de" 87 | 88 | [[package]] 89 | name = "cc" 90 | version = "1.0.54" 91 | source = "registry+https://github.com/rust-lang/crates.io-index" 92 | checksum = "7bbb73db36c1246e9034e307d0fba23f9a2e251faa47ade70c1bd252220c8311" 93 | 94 | [[package]] 95 | name = "cexpr" 96 | version = "0.4.0" 97 | source = "registry+https://github.com/rust-lang/crates.io-index" 98 | checksum = "f4aedb84272dbe89af497cf81375129abda4fc0a9e7c5d317498c15cc30c0d27" 99 | dependencies = [ 100 | "nom", 101 | ] 102 | 103 | [[package]] 104 | name = "cfg-if" 105 | version = "0.1.10" 106 | source = "registry+https://github.com/rust-lang/crates.io-index" 107 | checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" 108 | 109 | [[package]] 110 | name = "cfg-if" 111 | version = "1.0.0" 112 | source = "registry+https://github.com/rust-lang/crates.io-index" 113 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 114 | 115 | [[package]] 116 | name = "chrono" 117 | version = "0.4.11" 118 | source = "registry+https://github.com/rust-lang/crates.io-index" 119 | checksum = "80094f509cf8b5ae86a4966a39b3ff66cd7e2a3e594accec3743ff3fabeab5b2" 120 | dependencies = [ 121 | "num-integer", 122 | "num-traits", 123 | "time", 124 | ] 125 | 126 | [[package]] 127 | name = "clang-sys" 128 | version = "0.29.3" 129 | source = "registry+https://github.com/rust-lang/crates.io-index" 130 | checksum = "fe6837df1d5cba2397b835c8530f51723267e16abbf83892e9e5af4f0e5dd10a" 131 | dependencies = [ 132 | "glob", 133 | "libc", 134 | "libloading", 135 | ] 136 | 137 | [[package]] 138 | name = "clap" 139 | version = "2.33.1" 140 | source = "registry+https://github.com/rust-lang/crates.io-index" 141 | checksum = "bdfa80d47f954d53a35a64987ca1422f495b8d6483c0fe9f7117b36c2a792129" 142 | dependencies = [ 143 | "ansi_term", 144 | "atty", 145 | "bitflags", 146 | "strsim", 147 | "textwrap", 148 | "unicode-width", 149 | "vec_map", 150 | ] 151 | 152 | [[package]] 153 | name = "claxon" 154 | version = "0.4.2" 155 | source = "registry+https://github.com/rust-lang/crates.io-index" 156 | checksum = "f86c952727a495bda7abaf09bafdee1a939194dd793d9a8e26281df55ac43b00" 157 | 158 | [[package]] 159 | name = "core-foundation-sys" 160 | version = "0.6.2" 161 | source = "registry+https://github.com/rust-lang/crates.io-index" 162 | checksum = "e7ca8a5221364ef15ce201e8ed2f609fc312682a8f4e0e3d4aa5879764e0fa3b" 163 | 164 | [[package]] 165 | name = "coreaudio-rs" 166 | version = "0.9.1" 167 | source = "registry+https://github.com/rust-lang/crates.io-index" 168 | checksum = "f229761965dad3e9b11081668a6ea00f1def7aa46062321b5ec245b834f6e491" 169 | dependencies = [ 170 | "bitflags", 171 | "coreaudio-sys", 172 | ] 173 | 174 | [[package]] 175 | name = "coreaudio-sys" 176 | version = "0.2.4" 177 | source = "registry+https://github.com/rust-lang/crates.io-index" 178 | checksum = "e81f1c165c33ffab90a03077ac3b03462b34d5947145dfa48102e063d581502c" 179 | dependencies = [ 180 | "bindgen", 181 | ] 182 | 183 | [[package]] 184 | name = "cpal" 185 | version = "0.11.0" 186 | source = "registry+https://github.com/rust-lang/crates.io-index" 187 | checksum = "6b55d55d69f403f62a95bd3c04b431e0aedf5120c70f15d07a8edd234443dd59" 188 | dependencies = [ 189 | "alsa-sys", 190 | "core-foundation-sys", 191 | "coreaudio-rs", 192 | "lazy_static", 193 | "libc", 194 | "num-traits", 195 | "stdweb", 196 | "thiserror", 197 | "winapi", 198 | ] 199 | 200 | [[package]] 201 | name = "enum-iterator" 202 | version = "0.6.0" 203 | source = "registry+https://github.com/rust-lang/crates.io-index" 204 | checksum = "c79a6321a1197d7730510c7e3f6cb80432dfefecb32426de8cea0aa19b4bb8d7" 205 | dependencies = [ 206 | "enum-iterator-derive", 207 | ] 208 | 209 | [[package]] 210 | name = "enum-iterator-derive" 211 | version = "0.6.0" 212 | source = "registry+https://github.com/rust-lang/crates.io-index" 213 | checksum = "1e94aa31f7c0dc764f57896dc615ddd76fc13b0d5dca7eb6cc5e018a5a09ec06" 214 | dependencies = [ 215 | "proc-macro2", 216 | "quote", 217 | "syn", 218 | ] 219 | 220 | [[package]] 221 | name = "error-chain" 222 | version = "0.10.0" 223 | source = "registry+https://github.com/rust-lang/crates.io-index" 224 | checksum = "d9435d864e017c3c6afeac1654189b06cdb491cf2ff73dbf0d73b0f292f42ff8" 225 | 226 | [[package]] 227 | name = "evdev-rs" 228 | version = "0.4.0" 229 | source = "registry+https://github.com/rust-lang/crates.io-index" 230 | checksum = "b92abc30d5fd1e4f6440dee4d626abc68f4a9b5014dc1de575901e23c2e02321" 231 | dependencies = [ 232 | "bitflags", 233 | "evdev-sys", 234 | "libc", 235 | "log", 236 | ] 237 | 238 | [[package]] 239 | name = "evdev-sys" 240 | version = "0.2.1" 241 | source = "registry+https://github.com/rust-lang/crates.io-index" 242 | checksum = "9c6aa4e801c7267f2f66c9efd5c4071ba8ca9d7d9de515bb09a14bbe6f639ed1" 243 | dependencies = [ 244 | "cc", 245 | "libc", 246 | "pkg-config", 247 | ] 248 | 249 | [[package]] 250 | name = "futures-core" 251 | version = "0.3.26" 252 | source = "registry+https://github.com/rust-lang/crates.io-index" 253 | checksum = "ec90ff4d0fe1f57d600049061dc6bb68ed03c7d2fbd697274c41805dcb3f8608" 254 | 255 | [[package]] 256 | name = "getrandom" 257 | version = "0.2.8" 258 | source = "registry+https://github.com/rust-lang/crates.io-index" 259 | checksum = "c05aeb6a22b8f62540c194aac980f2115af067bfe15a0734d7277a768d396b31" 260 | dependencies = [ 261 | "cfg-if 1.0.0", 262 | "libc", 263 | "wasi", 264 | ] 265 | 266 | [[package]] 267 | name = "glob" 268 | version = "0.3.0" 269 | source = "registry+https://github.com/rust-lang/crates.io-index" 270 | checksum = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574" 271 | 272 | [[package]] 273 | name = "hermit-abi" 274 | version = "0.1.13" 275 | source = "registry+https://github.com/rust-lang/crates.io-index" 276 | checksum = "91780f809e750b0a89f5544be56617ff6b1227ee485bcb06ebe10cdf89bd3b71" 277 | dependencies = [ 278 | "libc", 279 | ] 280 | 281 | [[package]] 282 | name = "hound" 283 | version = "3.4.0" 284 | source = "registry+https://github.com/rust-lang/crates.io-index" 285 | checksum = "8a164bb2ceaeff4f42542bdb847c41517c78a60f5649671b2a07312b6e117549" 286 | 287 | [[package]] 288 | name = "inner" 289 | version = "0.1.1" 290 | source = "registry+https://github.com/rust-lang/crates.io-index" 291 | checksum = "d9368e93322f271c5ca078ed2ddcfad3511f1a40f564e522ade34e6e5c8e6680" 292 | 293 | [[package]] 294 | name = "inotify" 295 | version = "0.10.0" 296 | source = "registry+https://github.com/rust-lang/crates.io-index" 297 | checksum = "abf888f9575c290197b2c948dc9e9ff10bd1a39ad1ea8585f734585fa6b9d3f9" 298 | dependencies = [ 299 | "bitflags", 300 | "futures-core", 301 | "inotify-sys", 302 | "libc", 303 | "tokio", 304 | ] 305 | 306 | [[package]] 307 | name = "inotify-sys" 308 | version = "0.1.5" 309 | source = "registry+https://github.com/rust-lang/crates.io-index" 310 | checksum = "e05c02b5e89bff3b946cedeca278abc628fe811e604f027c45a8aa3cf793d0eb" 311 | dependencies = [ 312 | "libc", 313 | ] 314 | 315 | [[package]] 316 | name = "ioctl-sys" 317 | version = "0.5.2" 318 | source = "registry+https://github.com/rust-lang/crates.io-index" 319 | checksum = "5e2c4b26352496eaaa8ca7cfa9bd99e93419d3f7983dc6e99c2a35fe9e33504a" 320 | 321 | [[package]] 322 | name = "ktrl" 323 | version = "0.1.8" 324 | dependencies = [ 325 | "clap", 326 | "enum-iterator", 327 | "evdev-rs", 328 | "inner", 329 | "inotify", 330 | "lazy_static", 331 | "libc", 332 | "log", 333 | "nix", 334 | "regex", 335 | "retry", 336 | "rodio", 337 | "ron", 338 | "serde", 339 | "simplelog", 340 | "uinput-sys", 341 | "zmq", 342 | ] 343 | 344 | [[package]] 345 | name = "lazy_static" 346 | version = "1.4.0" 347 | source = "registry+https://github.com/rust-lang/crates.io-index" 348 | checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" 349 | 350 | [[package]] 351 | name = "lazycell" 352 | version = "1.2.1" 353 | source = "registry+https://github.com/rust-lang/crates.io-index" 354 | checksum = "b294d6fa9ee409a054354afc4352b0b9ef7ca222c69b8812cbea9e7d2bf3783f" 355 | 356 | [[package]] 357 | name = "lewton" 358 | version = "0.10.1" 359 | source = "registry+https://github.com/rust-lang/crates.io-index" 360 | checksum = "be42bea7971f4ba0ea1e215730c29bc1ff9bd2a9c10013912f42a8dcf8d77c0d" 361 | dependencies = [ 362 | "byteorder", 363 | "ogg", 364 | "tinyvec", 365 | ] 366 | 367 | [[package]] 368 | name = "libc" 369 | version = "0.2.139" 370 | source = "registry+https://github.com/rust-lang/crates.io-index" 371 | checksum = "201de327520df007757c1f0adce6e827fe8562fbc28bfd9c15571c66ca1f5f79" 372 | 373 | [[package]] 374 | name = "libloading" 375 | version = "0.5.2" 376 | source = "registry+https://github.com/rust-lang/crates.io-index" 377 | checksum = "f2b111a074963af1d37a139918ac6d49ad1d0d5e47f72fd55388619691a7d753" 378 | dependencies = [ 379 | "cc", 380 | "winapi", 381 | ] 382 | 383 | [[package]] 384 | name = "log" 385 | version = "0.4.8" 386 | source = "registry+https://github.com/rust-lang/crates.io-index" 387 | checksum = "14b6052be84e6b71ab17edffc2eeabf5c2c3ae1fdb464aae35ac50c67a44e1f7" 388 | dependencies = [ 389 | "cfg-if 0.1.10", 390 | ] 391 | 392 | [[package]] 393 | name = "mach" 394 | version = "0.3.2" 395 | source = "registry+https://github.com/rust-lang/crates.io-index" 396 | checksum = "b823e83b2affd8f40a9ee8c29dbc56404c1e34cd2710921f2801e2cf29527afa" 397 | dependencies = [ 398 | "libc", 399 | ] 400 | 401 | [[package]] 402 | name = "memchr" 403 | version = "2.5.0" 404 | source = "registry+https://github.com/rust-lang/crates.io-index" 405 | checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" 406 | 407 | [[package]] 408 | name = "metadeps" 409 | version = "1.1.2" 410 | source = "registry+https://github.com/rust-lang/crates.io-index" 411 | checksum = "73b122901b3a675fac8cecf68dcb2f0d3036193bc861d1ac0e1c337f7d5254c2" 412 | dependencies = [ 413 | "error-chain", 414 | "pkg-config", 415 | "toml", 416 | ] 417 | 418 | [[package]] 419 | name = "minimp3" 420 | version = "0.3.5" 421 | source = "registry+https://github.com/rust-lang/crates.io-index" 422 | checksum = "dce0cff6a0bfd3f8b6b2350819bbddd63bc65cc45e53888bdd0ff49dde16d2d5" 423 | dependencies = [ 424 | "minimp3-sys", 425 | "slice-deque", 426 | ] 427 | 428 | [[package]] 429 | name = "minimp3-sys" 430 | version = "0.3.2" 431 | source = "registry+https://github.com/rust-lang/crates.io-index" 432 | checksum = "e21c73734c69dc95696c9ed8926a2b393171d98b3f5f5935686a26a487ab9b90" 433 | dependencies = [ 434 | "cc", 435 | ] 436 | 437 | [[package]] 438 | name = "mio" 439 | version = "0.7.14" 440 | source = "registry+https://github.com/rust-lang/crates.io-index" 441 | checksum = "8067b404fe97c70829f082dec8bcf4f71225d7eaea1d8645349cb76fa06205cc" 442 | dependencies = [ 443 | "libc", 444 | "log", 445 | "miow", 446 | "ntapi", 447 | "winapi", 448 | ] 449 | 450 | [[package]] 451 | name = "miow" 452 | version = "0.3.7" 453 | source = "registry+https://github.com/rust-lang/crates.io-index" 454 | checksum = "b9f1c5b025cda876f66ef43a113f91ebc9f4ccef34843000e0adf6ebbab84e21" 455 | dependencies = [ 456 | "winapi", 457 | ] 458 | 459 | [[package]] 460 | name = "nix" 461 | version = "0.17.0" 462 | source = "registry+https://github.com/rust-lang/crates.io-index" 463 | checksum = "50e4785f2c3b7589a0d0c1dd60285e1188adac4006e8abd6dd578e1567027363" 464 | dependencies = [ 465 | "bitflags", 466 | "cc", 467 | "cfg-if 0.1.10", 468 | "libc", 469 | "void", 470 | ] 471 | 472 | [[package]] 473 | name = "nom" 474 | version = "5.1.1" 475 | source = "registry+https://github.com/rust-lang/crates.io-index" 476 | checksum = "0b471253da97532da4b61552249c521e01e736071f71c1a4f7ebbfbf0a06aad6" 477 | dependencies = [ 478 | "memchr", 479 | "version_check", 480 | ] 481 | 482 | [[package]] 483 | name = "ntapi" 484 | version = "0.3.4" 485 | source = "registry+https://github.com/rust-lang/crates.io-index" 486 | checksum = "7a31937dea023539c72ddae0e3571deadc1414b300483fa7aaec176168cfa9d2" 487 | dependencies = [ 488 | "winapi", 489 | ] 490 | 491 | [[package]] 492 | name = "num-integer" 493 | version = "0.1.42" 494 | source = "registry+https://github.com/rust-lang/crates.io-index" 495 | checksum = "3f6ea62e9d81a77cd3ee9a2a5b9b609447857f3d358704331e4ef39eb247fcba" 496 | dependencies = [ 497 | "autocfg", 498 | "num-traits", 499 | ] 500 | 501 | [[package]] 502 | name = "num-traits" 503 | version = "0.2.11" 504 | source = "registry+https://github.com/rust-lang/crates.io-index" 505 | checksum = "c62be47e61d1842b9170f0fdeec8eba98e60e90e5446449a0545e5152acd7096" 506 | dependencies = [ 507 | "autocfg", 508 | ] 509 | 510 | [[package]] 511 | name = "ogg" 512 | version = "0.7.0" 513 | source = "registry+https://github.com/rust-lang/crates.io-index" 514 | checksum = "d79f1db9148be9d0e174bb3ac890f6030fcb1ed947267c5a91ee4c91b5a91e15" 515 | dependencies = [ 516 | "byteorder", 517 | ] 518 | 519 | [[package]] 520 | name = "peeking_take_while" 521 | version = "0.1.2" 522 | source = "registry+https://github.com/rust-lang/crates.io-index" 523 | checksum = "19b17cddbe7ec3f8bc800887bab5e717348c95ea2ca0b1bf0837fb964dc67099" 524 | 525 | [[package]] 526 | name = "pin-project-lite" 527 | version = "0.2.9" 528 | source = "registry+https://github.com/rust-lang/crates.io-index" 529 | checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116" 530 | 531 | [[package]] 532 | name = "pkg-config" 533 | version = "0.3.17" 534 | source = "registry+https://github.com/rust-lang/crates.io-index" 535 | checksum = "05da548ad6865900e60eaba7f589cc0783590a92e940c26953ff81ddbab2d677" 536 | 537 | [[package]] 538 | name = "ppv-lite86" 539 | version = "0.2.17" 540 | source = "registry+https://github.com/rust-lang/crates.io-index" 541 | checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" 542 | 543 | [[package]] 544 | name = "proc-macro2" 545 | version = "1.0.18" 546 | source = "registry+https://github.com/rust-lang/crates.io-index" 547 | checksum = "beae6331a816b1f65d04c45b078fd8e6c93e8071771f41b8163255bbd8d7c8fa" 548 | dependencies = [ 549 | "unicode-xid", 550 | ] 551 | 552 | [[package]] 553 | name = "quote" 554 | version = "1.0.7" 555 | source = "registry+https://github.com/rust-lang/crates.io-index" 556 | checksum = "aa563d17ecb180e500da1cfd2b028310ac758de548efdd203e18f283af693f37" 557 | dependencies = [ 558 | "proc-macro2", 559 | ] 560 | 561 | [[package]] 562 | name = "rand" 563 | version = "0.8.5" 564 | source = "registry+https://github.com/rust-lang/crates.io-index" 565 | checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" 566 | dependencies = [ 567 | "libc", 568 | "rand_chacha", 569 | "rand_core", 570 | ] 571 | 572 | [[package]] 573 | name = "rand_chacha" 574 | version = "0.3.1" 575 | source = "registry+https://github.com/rust-lang/crates.io-index" 576 | checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" 577 | dependencies = [ 578 | "ppv-lite86", 579 | "rand_core", 580 | ] 581 | 582 | [[package]] 583 | name = "rand_core" 584 | version = "0.6.4" 585 | source = "registry+https://github.com/rust-lang/crates.io-index" 586 | checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" 587 | dependencies = [ 588 | "getrandom", 589 | ] 590 | 591 | [[package]] 592 | name = "regex" 593 | version = "1.7.1" 594 | source = "registry+https://github.com/rust-lang/crates.io-index" 595 | checksum = "48aaa5748ba571fb95cd2c85c09f629215d3a6ece942baa100950af03a34f733" 596 | dependencies = [ 597 | "aho-corasick", 598 | "memchr", 599 | "regex-syntax", 600 | ] 601 | 602 | [[package]] 603 | name = "regex-syntax" 604 | version = "0.6.28" 605 | source = "registry+https://github.com/rust-lang/crates.io-index" 606 | checksum = "456c603be3e8d448b072f410900c09faf164fbce2d480456f50eea6e25f9c848" 607 | 608 | [[package]] 609 | name = "retry" 610 | version = "2.0.0" 611 | source = "registry+https://github.com/rust-lang/crates.io-index" 612 | checksum = "9166d72162de3575f950507683fac47e30f6f2c3836b71b7fbc61aa517c9c5f4" 613 | dependencies = [ 614 | "rand", 615 | ] 616 | 617 | [[package]] 618 | name = "rodio" 619 | version = "0.11.0" 620 | source = "registry+https://github.com/rust-lang/crates.io-index" 621 | checksum = "73bbf260262fd5501b7a17d6827e0d25c1127e921eb177150a060faf6e217a70" 622 | dependencies = [ 623 | "claxon", 624 | "cpal", 625 | "hound", 626 | "lazy_static", 627 | "lewton", 628 | "minimp3", 629 | ] 630 | 631 | [[package]] 632 | name = "ron" 633 | version = "0.6.0" 634 | source = "registry+https://github.com/rust-lang/crates.io-index" 635 | checksum = "a91260f321dbf3b5a16ff91c451dc9eb644ce72775a6812f9c3dfffe63818f8f" 636 | dependencies = [ 637 | "base64", 638 | "bitflags", 639 | "serde", 640 | ] 641 | 642 | [[package]] 643 | name = "rustc-hash" 644 | version = "1.1.0" 645 | source = "registry+https://github.com/rust-lang/crates.io-index" 646 | checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" 647 | 648 | [[package]] 649 | name = "serde" 650 | version = "1.0.111" 651 | source = "registry+https://github.com/rust-lang/crates.io-index" 652 | checksum = "c9124df5b40cbd380080b2cc6ab894c040a3070d995f5c9dc77e18c34a8ae37d" 653 | dependencies = [ 654 | "serde_derive", 655 | ] 656 | 657 | [[package]] 658 | name = "serde_derive" 659 | version = "1.0.111" 660 | source = "registry+https://github.com/rust-lang/crates.io-index" 661 | checksum = "3f2c3ac8e6ca1e9c80b8be1023940162bf81ae3cffbb1809474152f2ce1eb250" 662 | dependencies = [ 663 | "proc-macro2", 664 | "quote", 665 | "syn", 666 | ] 667 | 668 | [[package]] 669 | name = "shlex" 670 | version = "0.1.1" 671 | source = "registry+https://github.com/rust-lang/crates.io-index" 672 | checksum = "7fdf1b9db47230893d76faad238fd6097fd6d6a9245cd7a4d90dbd639536bbd2" 673 | 674 | [[package]] 675 | name = "simplelog" 676 | version = "0.8.0" 677 | source = "registry+https://github.com/rust-lang/crates.io-index" 678 | checksum = "2b2736f58087298a448859961d3f4a0850b832e72619d75adc69da7993c2cd3c" 679 | dependencies = [ 680 | "chrono", 681 | "log", 682 | "termcolor", 683 | ] 684 | 685 | [[package]] 686 | name = "slice-deque" 687 | version = "0.3.0" 688 | source = "registry+https://github.com/rust-lang/crates.io-index" 689 | checksum = "31ef6ee280cdefba6d2d0b4b78a84a1c1a3f3a4cec98c2d4231c8bc225de0f25" 690 | dependencies = [ 691 | "libc", 692 | "mach", 693 | "winapi", 694 | ] 695 | 696 | [[package]] 697 | name = "stdweb" 698 | version = "0.1.3" 699 | source = "registry+https://github.com/rust-lang/crates.io-index" 700 | checksum = "ef5430c8e36b713e13b48a9f709cc21e046723fe44ce34587b73a830203b533e" 701 | 702 | [[package]] 703 | name = "strsim" 704 | version = "0.8.0" 705 | source = "registry+https://github.com/rust-lang/crates.io-index" 706 | checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" 707 | 708 | [[package]] 709 | name = "syn" 710 | version = "1.0.30" 711 | source = "registry+https://github.com/rust-lang/crates.io-index" 712 | checksum = "93a56fabc59dce20fe48b6c832cc249c713e7ed88fa28b0ee0a3bfcaae5fe4e2" 713 | dependencies = [ 714 | "proc-macro2", 715 | "quote", 716 | "unicode-xid", 717 | ] 718 | 719 | [[package]] 720 | name = "termcolor" 721 | version = "1.1.0" 722 | source = "registry+https://github.com/rust-lang/crates.io-index" 723 | checksum = "bb6bfa289a4d7c5766392812c0a1f4c1ba45afa1ad47803c11e1f407d846d75f" 724 | dependencies = [ 725 | "winapi-util", 726 | ] 727 | 728 | [[package]] 729 | name = "textwrap" 730 | version = "0.11.0" 731 | source = "registry+https://github.com/rust-lang/crates.io-index" 732 | checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060" 733 | dependencies = [ 734 | "unicode-width", 735 | ] 736 | 737 | [[package]] 738 | name = "thiserror" 739 | version = "1.0.19" 740 | source = "registry+https://github.com/rust-lang/crates.io-index" 741 | checksum = "b13f926965ad00595dd129fa12823b04bbf866e9085ab0a5f2b05b850fbfc344" 742 | dependencies = [ 743 | "thiserror-impl", 744 | ] 745 | 746 | [[package]] 747 | name = "thiserror-impl" 748 | version = "1.0.19" 749 | source = "registry+https://github.com/rust-lang/crates.io-index" 750 | checksum = "893582086c2f98cde18f906265a65b5030a074b1046c674ae898be6519a7f479" 751 | dependencies = [ 752 | "proc-macro2", 753 | "quote", 754 | "syn", 755 | ] 756 | 757 | [[package]] 758 | name = "time" 759 | version = "0.1.43" 760 | source = "registry+https://github.com/rust-lang/crates.io-index" 761 | checksum = "ca8a50ef2360fbd1eeb0ecd46795a87a19024eb4b53c5dc916ca1fd95fe62438" 762 | dependencies = [ 763 | "libc", 764 | "winapi", 765 | ] 766 | 767 | [[package]] 768 | name = "tinyvec" 769 | version = "0.3.3" 770 | source = "registry+https://github.com/rust-lang/crates.io-index" 771 | checksum = "53953d2d3a5ad81d9f844a32f14ebb121f50b650cd59d0ee2a07cf13c617efed" 772 | 773 | [[package]] 774 | name = "tokio" 775 | version = "1.16.1" 776 | source = "registry+https://github.com/rust-lang/crates.io-index" 777 | checksum = "0c27a64b625de6d309e8c57716ba93021dccf1b3b5c97edd6d3dd2d2135afc0a" 778 | dependencies = [ 779 | "libc", 780 | "mio", 781 | "pin-project-lite", 782 | "winapi", 783 | ] 784 | 785 | [[package]] 786 | name = "toml" 787 | version = "0.2.1" 788 | source = "registry+https://github.com/rust-lang/crates.io-index" 789 | checksum = "736b60249cb25337bc196faa43ee12c705e426f3d55c214d73a4e7be06f92cb4" 790 | 791 | [[package]] 792 | name = "uinput-sys" 793 | version = "0.1.7" 794 | source = "registry+https://github.com/rust-lang/crates.io-index" 795 | checksum = "9aabddd8174ccadd600afeab346bb276cb1db5fafcf6a7c5c5708b8cc4b2cac7" 796 | dependencies = [ 797 | "ioctl-sys", 798 | "libc", 799 | ] 800 | 801 | [[package]] 802 | name = "unicode-width" 803 | version = "0.1.7" 804 | source = "registry+https://github.com/rust-lang/crates.io-index" 805 | checksum = "caaa9d531767d1ff2150b9332433f32a24622147e5ebb1f26409d5da67afd479" 806 | 807 | [[package]] 808 | name = "unicode-xid" 809 | version = "0.2.0" 810 | source = "registry+https://github.com/rust-lang/crates.io-index" 811 | checksum = "826e7639553986605ec5979c7dd957c7895e93eabed50ab2ffa7f6128a75097c" 812 | 813 | [[package]] 814 | name = "vec_map" 815 | version = "0.8.2" 816 | source = "registry+https://github.com/rust-lang/crates.io-index" 817 | checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191" 818 | 819 | [[package]] 820 | name = "version_check" 821 | version = "0.9.2" 822 | source = "registry+https://github.com/rust-lang/crates.io-index" 823 | checksum = "b5a972e5669d67ba988ce3dc826706fb0a8b01471c088cb0b6110b805cc36aed" 824 | 825 | [[package]] 826 | name = "void" 827 | version = "1.0.2" 828 | source = "registry+https://github.com/rust-lang/crates.io-index" 829 | checksum = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d" 830 | 831 | [[package]] 832 | name = "wasi" 833 | version = "0.11.0+wasi-snapshot-preview1" 834 | source = "registry+https://github.com/rust-lang/crates.io-index" 835 | checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" 836 | 837 | [[package]] 838 | name = "winapi" 839 | version = "0.3.8" 840 | source = "registry+https://github.com/rust-lang/crates.io-index" 841 | checksum = "8093091eeb260906a183e6ae1abdba2ef5ef2257a21801128899c3fc699229c6" 842 | dependencies = [ 843 | "winapi-i686-pc-windows-gnu", 844 | "winapi-x86_64-pc-windows-gnu", 845 | ] 846 | 847 | [[package]] 848 | name = "winapi-i686-pc-windows-gnu" 849 | version = "0.4.0" 850 | source = "registry+https://github.com/rust-lang/crates.io-index" 851 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 852 | 853 | [[package]] 854 | name = "winapi-util" 855 | version = "0.1.5" 856 | source = "registry+https://github.com/rust-lang/crates.io-index" 857 | checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" 858 | dependencies = [ 859 | "winapi", 860 | ] 861 | 862 | [[package]] 863 | name = "winapi-x86_64-pc-windows-gnu" 864 | version = "0.4.0" 865 | source = "registry+https://github.com/rust-lang/crates.io-index" 866 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 867 | 868 | [[package]] 869 | name = "zmq" 870 | version = "0.9.2" 871 | source = "registry+https://github.com/rust-lang/crates.io-index" 872 | checksum = "aad98a7a617d608cd9e1127147f630d24af07c7cd95ba1533246d96cbdd76c66" 873 | dependencies = [ 874 | "bitflags", 875 | "libc", 876 | "log", 877 | "zmq-sys", 878 | ] 879 | 880 | [[package]] 881 | name = "zmq-sys" 882 | version = "0.11.0" 883 | source = "registry+https://github.com/rust-lang/crates.io-index" 884 | checksum = "d33a2c51dde24d5b451a2ed4b488266df221a5eaee2ee519933dc46b9a9b3648" 885 | dependencies = [ 886 | "libc", 887 | "metadeps", 888 | ] 889 | -------------------------------------------------------------------------------- /src/keys.rs: -------------------------------------------------------------------------------- 1 | // use std::fmt; 2 | use evdev_rs::enums::{EventCode, EventType, EV_KEY}; 3 | use evdev_rs::{InputEvent, TimeVal}; 4 | use serde::Deserialize; 5 | use std::convert::TryFrom; 6 | 7 | // ------------------ KeyCode -------------------- 8 | 9 | /// This is a shameless copy of evdev_rs::enums::EV_KEY. 10 | /// I've added the Copy trait and I'll be able 11 | /// to added my own Impl(s) to it 12 | #[allow(non_camel_case_types)] 13 | #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Deserialize)] 14 | pub enum KeyCode { 15 | KEY_RESERVED = 0, 16 | KEY_ESC = 1, 17 | KEY_1 = 2, 18 | KEY_2 = 3, 19 | KEY_3 = 4, 20 | KEY_4 = 5, 21 | KEY_5 = 6, 22 | KEY_6 = 7, 23 | KEY_7 = 8, 24 | KEY_8 = 9, 25 | KEY_9 = 10, 26 | KEY_0 = 11, 27 | KEY_MINUS = 12, 28 | KEY_EQUAL = 13, 29 | KEY_BACKSPACE = 14, 30 | KEY_TAB = 15, 31 | KEY_Q = 16, 32 | KEY_W = 17, 33 | KEY_E = 18, 34 | KEY_R = 19, 35 | KEY_T = 20, 36 | KEY_Y = 21, 37 | KEY_U = 22, 38 | KEY_I = 23, 39 | KEY_O = 24, 40 | KEY_P = 25, 41 | KEY_LEFTBRACE = 26, 42 | KEY_RIGHTBRACE = 27, 43 | KEY_ENTER = 28, 44 | KEY_LEFTCTRL = 29, 45 | KEY_A = 30, 46 | KEY_S = 31, 47 | KEY_D = 32, 48 | KEY_F = 33, 49 | KEY_G = 34, 50 | KEY_H = 35, 51 | KEY_J = 36, 52 | KEY_K = 37, 53 | KEY_L = 38, 54 | KEY_SEMICOLON = 39, 55 | KEY_APOSTROPHE = 40, 56 | KEY_GRAVE = 41, 57 | KEY_LEFTSHIFT = 42, 58 | KEY_BACKSLASH = 43, 59 | KEY_Z = 44, 60 | KEY_X = 45, 61 | KEY_C = 46, 62 | KEY_V = 47, 63 | KEY_B = 48, 64 | KEY_N = 49, 65 | KEY_M = 50, 66 | KEY_COMMA = 51, 67 | KEY_DOT = 52, 68 | KEY_SLASH = 53, 69 | KEY_RIGHTSHIFT = 54, 70 | KEY_KPASTERISK = 55, 71 | KEY_LEFTALT = 56, 72 | KEY_SPACE = 57, 73 | KEY_CAPSLOCK = 58, 74 | KEY_F1 = 59, 75 | KEY_F2 = 60, 76 | KEY_F3 = 61, 77 | KEY_F4 = 62, 78 | KEY_F5 = 63, 79 | KEY_F6 = 64, 80 | KEY_F7 = 65, 81 | KEY_F8 = 66, 82 | KEY_F9 = 67, 83 | KEY_F10 = 68, 84 | KEY_NUMLOCK = 69, 85 | KEY_SCROLLLOCK = 70, 86 | KEY_KP7 = 71, 87 | KEY_KP8 = 72, 88 | KEY_KP9 = 73, 89 | KEY_KPMINUS = 74, 90 | KEY_KP4 = 75, 91 | KEY_KP5 = 76, 92 | KEY_KP6 = 77, 93 | KEY_KPPLUS = 78, 94 | KEY_KP1 = 79, 95 | KEY_KP2 = 80, 96 | KEY_KP3 = 81, 97 | KEY_KP0 = 82, 98 | KEY_KPDOT = 83, 99 | KEY_ZENKAKUHANKAKU = 85, 100 | KEY_102ND = 86, 101 | KEY_F11 = 87, 102 | KEY_F12 = 88, 103 | KEY_RO = 89, 104 | KEY_KATAKANA = 90, 105 | KEY_HIRAGANA = 91, 106 | KEY_HENKAN = 92, 107 | KEY_KATAKANAHIRAGANA = 93, 108 | KEY_MUHENKAN = 94, 109 | KEY_KPJPCOMMA = 95, 110 | KEY_KPENTER = 96, 111 | KEY_RIGHTCTRL = 97, 112 | KEY_KPSLASH = 98, 113 | KEY_SYSRQ = 99, 114 | KEY_RIGHTALT = 100, 115 | KEY_LINEFEED = 101, 116 | KEY_HOME = 102, 117 | KEY_UP = 103, 118 | KEY_PAGEUP = 104, 119 | KEY_LEFT = 105, 120 | KEY_RIGHT = 106, 121 | KEY_END = 107, 122 | KEY_DOWN = 108, 123 | KEY_PAGEDOWN = 109, 124 | KEY_INSERT = 110, 125 | KEY_DELETE = 111, 126 | KEY_MACRO = 112, 127 | KEY_MUTE = 113, 128 | KEY_VOLUMEDOWN = 114, 129 | KEY_VOLUMEUP = 115, 130 | KEY_POWER = 116, 131 | KEY_KPEQUAL = 117, 132 | KEY_KPPLUSMINUS = 118, 133 | KEY_PAUSE = 119, 134 | KEY_SCALE = 120, 135 | KEY_KPCOMMA = 121, 136 | KEY_HANGEUL = 122, 137 | KEY_HANJA = 123, 138 | KEY_YEN = 124, 139 | KEY_LEFTMETA = 125, 140 | KEY_RIGHTMETA = 126, 141 | KEY_COMPOSE = 127, 142 | KEY_STOP = 128, 143 | KEY_AGAIN = 129, 144 | KEY_PROPS = 130, 145 | KEY_UNDO = 131, 146 | KEY_FRONT = 132, 147 | KEY_COPY = 133, 148 | KEY_OPEN = 134, 149 | KEY_PASTE = 135, 150 | KEY_FIND = 136, 151 | KEY_CUT = 137, 152 | KEY_HELP = 138, 153 | KEY_MENU = 139, 154 | KEY_CALC = 140, 155 | KEY_SETUP = 141, 156 | KEY_SLEEP = 142, 157 | KEY_WAKEUP = 143, 158 | KEY_FILE = 144, 159 | KEY_SENDFILE = 145, 160 | KEY_DELETEFILE = 146, 161 | KEY_XFER = 147, 162 | KEY_PROG1 = 148, 163 | KEY_PROG2 = 149, 164 | KEY_WWW = 150, 165 | KEY_MSDOS = 151, 166 | KEY_COFFEE = 152, 167 | KEY_ROTATE_DISPLAY = 153, 168 | KEY_CYCLEWINDOWS = 154, 169 | KEY_MAIL = 155, 170 | KEY_BOOKMARKS = 156, 171 | KEY_COMPUTER = 157, 172 | KEY_BACK = 158, 173 | KEY_FORWARD = 159, 174 | KEY_CLOSECD = 160, 175 | KEY_EJECTCD = 161, 176 | KEY_EJECTCLOSECD = 162, 177 | KEY_NEXTSONG = 163, 178 | KEY_PLAYPAUSE = 164, 179 | KEY_PREVIOUSSONG = 165, 180 | KEY_STOPCD = 166, 181 | KEY_RECORD = 167, 182 | KEY_REWIND = 168, 183 | KEY_PHONE = 169, 184 | KEY_ISO = 170, 185 | KEY_CONFIG = 171, 186 | KEY_HOMEPAGE = 172, 187 | KEY_REFRESH = 173, 188 | KEY_EXIT = 174, 189 | KEY_MOVE = 175, 190 | KEY_EDIT = 176, 191 | KEY_SCROLLUP = 177, 192 | KEY_SCROLLDOWN = 178, 193 | KEY_KPLEFTPAREN = 179, 194 | KEY_KPRIGHTPAREN = 180, 195 | KEY_NEW = 181, 196 | KEY_REDO = 182, 197 | KEY_F13 = 183, 198 | KEY_F14 = 184, 199 | KEY_F15 = 185, 200 | KEY_F16 = 186, 201 | KEY_F17 = 187, 202 | KEY_F18 = 188, 203 | KEY_F19 = 189, 204 | KEY_F20 = 190, 205 | KEY_F21 = 191, 206 | KEY_F22 = 192, 207 | KEY_F23 = 193, 208 | KEY_F24 = 194, 209 | KEY_PLAYCD = 200, 210 | KEY_PAUSECD = 201, 211 | KEY_PROG3 = 202, 212 | KEY_PROG4 = 203, 213 | KEY_DASHBOARD = 204, 214 | KEY_SUSPEND = 205, 215 | KEY_CLOSE = 206, 216 | KEY_PLAY = 207, 217 | KEY_FASTFORWARD = 208, 218 | KEY_BASSBOOST = 209, 219 | KEY_PRINT = 210, 220 | KEY_HP = 211, 221 | KEY_CAMERA = 212, 222 | KEY_SOUND = 213, 223 | KEY_QUESTION = 214, 224 | KEY_EMAIL = 215, 225 | KEY_CHAT = 216, 226 | KEY_SEARCH = 217, 227 | KEY_CONNECT = 218, 228 | KEY_FINANCE = 219, 229 | KEY_SPORT = 220, 230 | KEY_SHOP = 221, 231 | KEY_ALTERASE = 222, 232 | KEY_CANCEL = 223, 233 | KEY_BRIGHTNESSDOWN = 224, 234 | KEY_BRIGHTNESSUP = 225, 235 | KEY_MEDIA = 226, 236 | KEY_SWITCHVIDEOMODE = 227, 237 | KEY_KBDILLUMTOGGLE = 228, 238 | KEY_KBDILLUMDOWN = 229, 239 | KEY_KBDILLUMUP = 230, 240 | KEY_SEND = 231, 241 | KEY_REPLY = 232, 242 | KEY_FORWARDMAIL = 233, 243 | KEY_SAVE = 234, 244 | KEY_DOCUMENTS = 235, 245 | KEY_BATTERY = 236, 246 | KEY_BLUETOOTH = 237, 247 | KEY_WLAN = 238, 248 | KEY_UWB = 239, 249 | KEY_UNKNOWN = 240, 250 | KEY_VIDEO_NEXT = 241, 251 | KEY_VIDEO_PREV = 242, 252 | KEY_BRIGHTNESS_CYCLE = 243, 253 | KEY_BRIGHTNESS_AUTO = 244, 254 | KEY_DISPLAY_OFF = 245, 255 | KEY_WWAN = 246, 256 | KEY_RFKILL = 247, 257 | KEY_MICMUTE = 248, 258 | KEY_OK = 352, 259 | KEY_SELECT = 353, 260 | KEY_GOTO = 354, 261 | KEY_CLEAR = 355, 262 | KEY_POWER2 = 356, 263 | KEY_OPTION = 357, 264 | KEY_INFO = 358, 265 | KEY_TIME = 359, 266 | KEY_VENDOR = 360, 267 | KEY_ARCHIVE = 361, 268 | KEY_PROGRAM = 362, 269 | KEY_CHANNEL = 363, 270 | KEY_FAVORITES = 364, 271 | KEY_EPG = 365, 272 | KEY_PVR = 366, 273 | KEY_MHP = 367, 274 | KEY_LANGUAGE = 368, 275 | KEY_TITLE = 369, 276 | KEY_SUBTITLE = 370, 277 | KEY_ANGLE = 371, 278 | KEY_FULL_SCREEN = 372, 279 | KEY_MODE = 373, 280 | KEY_KEYBOARD = 374, 281 | KEY_ASPECT_RATIO = 375, 282 | KEY_PC = 376, 283 | KEY_TV = 377, 284 | KEY_TV2 = 378, 285 | KEY_VCR = 379, 286 | KEY_VCR2 = 380, 287 | KEY_SAT = 381, 288 | KEY_SAT2 = 382, 289 | KEY_CD = 383, 290 | KEY_TAPE = 384, 291 | KEY_RADIO = 385, 292 | KEY_TUNER = 386, 293 | KEY_PLAYER = 387, 294 | KEY_TEXT = 388, 295 | KEY_DVD = 389, 296 | KEY_AUX = 390, 297 | KEY_MP3 = 391, 298 | KEY_AUDIO = 392, 299 | KEY_VIDEO = 393, 300 | KEY_DIRECTORY = 394, 301 | KEY_LIST = 395, 302 | KEY_MEMO = 396, 303 | KEY_CALENDAR = 397, 304 | KEY_RED = 398, 305 | KEY_GREEN = 399, 306 | KEY_YELLOW = 400, 307 | KEY_BLUE = 401, 308 | KEY_CHANNELUP = 402, 309 | KEY_CHANNELDOWN = 403, 310 | KEY_FIRST = 404, 311 | KEY_LAST = 405, 312 | KEY_AB = 406, 313 | KEY_NEXT = 407, 314 | KEY_RESTART = 408, 315 | KEY_SLOW = 409, 316 | KEY_SHUFFLE = 410, 317 | KEY_BREAK = 411, 318 | KEY_PREVIOUS = 412, 319 | KEY_DIGITS = 413, 320 | KEY_TEEN = 414, 321 | KEY_TWEN = 415, 322 | KEY_VIDEOPHONE = 416, 323 | KEY_GAMES = 417, 324 | KEY_ZOOMIN = 418, 325 | KEY_ZOOMOUT = 419, 326 | KEY_ZOOMRESET = 420, 327 | KEY_WORDPROCESSOR = 421, 328 | KEY_EDITOR = 422, 329 | KEY_SPREADSHEET = 423, 330 | KEY_GRAPHICSEDITOR = 424, 331 | KEY_PRESENTATION = 425, 332 | KEY_DATABASE = 426, 333 | KEY_NEWS = 427, 334 | KEY_VOICEMAIL = 428, 335 | KEY_ADDRESSBOOK = 429, 336 | KEY_MESSENGER = 430, 337 | KEY_DISPLAYTOGGLE = 431, 338 | KEY_SPELLCHECK = 432, 339 | KEY_LOGOFF = 433, 340 | KEY_DOLLAR = 434, 341 | KEY_EURO = 435, 342 | KEY_FRAMEBACK = 436, 343 | KEY_FRAMEFORWARD = 437, 344 | KEY_CONTEXT_MENU = 438, 345 | KEY_MEDIA_REPEAT = 439, 346 | KEY_10CHANNELSUP = 440, 347 | KEY_10CHANNELSDOWN = 441, 348 | KEY_IMAGES = 442, 349 | KEY_DEL_EOL = 448, 350 | KEY_DEL_EOS = 449, 351 | KEY_INS_LINE = 450, 352 | KEY_DEL_LINE = 451, 353 | KEY_FN = 464, 354 | KEY_FN_ESC = 465, 355 | KEY_FN_F1 = 466, 356 | KEY_FN_F2 = 467, 357 | KEY_FN_F3 = 468, 358 | KEY_FN_F4 = 469, 359 | KEY_FN_F5 = 470, 360 | KEY_FN_F6 = 471, 361 | KEY_FN_F7 = 472, 362 | KEY_FN_F8 = 473, 363 | KEY_FN_F9 = 474, 364 | KEY_FN_F10 = 475, 365 | KEY_FN_F11 = 476, 366 | KEY_FN_F12 = 477, 367 | KEY_FN_1 = 478, 368 | KEY_FN_2 = 479, 369 | KEY_FN_D = 480, 370 | KEY_FN_E = 481, 371 | KEY_FN_F = 482, 372 | KEY_FN_S = 483, 373 | KEY_FN_B = 484, 374 | KEY_BRL_DOT1 = 497, 375 | KEY_BRL_DOT2 = 498, 376 | KEY_BRL_DOT3 = 499, 377 | KEY_BRL_DOT4 = 500, 378 | KEY_BRL_DOT5 = 501, 379 | KEY_BRL_DOT6 = 502, 380 | KEY_BRL_DOT7 = 503, 381 | KEY_BRL_DOT8 = 504, 382 | KEY_BRL_DOT9 = 505, 383 | KEY_BRL_DOT10 = 506, 384 | KEY_NUMERIC_0 = 512, 385 | KEY_NUMERIC_1 = 513, 386 | KEY_NUMERIC_2 = 514, 387 | KEY_NUMERIC_3 = 515, 388 | KEY_NUMERIC_4 = 516, 389 | KEY_NUMERIC_5 = 517, 390 | KEY_NUMERIC_6 = 518, 391 | KEY_NUMERIC_7 = 519, 392 | KEY_NUMERIC_8 = 520, 393 | KEY_NUMERIC_9 = 521, 394 | KEY_NUMERIC_STAR = 522, 395 | KEY_NUMERIC_POUND = 523, 396 | KEY_NUMERIC_A = 524, 397 | KEY_NUMERIC_B = 525, 398 | KEY_NUMERIC_C = 526, 399 | KEY_NUMERIC_D = 527, 400 | KEY_CAMERA_FOCUS = 528, 401 | KEY_WPS_BUTTON = 529, 402 | KEY_TOUCHPAD_TOGGLE = 530, 403 | KEY_TOUCHPAD_ON = 531, 404 | KEY_TOUCHPAD_OFF = 532, 405 | KEY_CAMERA_ZOOMIN = 533, 406 | KEY_CAMERA_ZOOMOUT = 534, 407 | KEY_CAMERA_UP = 535, 408 | KEY_CAMERA_DOWN = 536, 409 | KEY_CAMERA_LEFT = 537, 410 | KEY_CAMERA_RIGHT = 538, 411 | KEY_ATTENDANT_ON = 539, 412 | KEY_ATTENDANT_OFF = 540, 413 | KEY_ATTENDANT_TOGGLE = 541, 414 | KEY_LIGHTS_TOGGLE = 542, 415 | KEY_ALS_TOGGLE = 560, 416 | KEY_ROTATE_LOCK_TOGGLE = 561, 417 | KEY_BUTTONCONFIG = 576, 418 | KEY_TASKMANAGER = 577, 419 | KEY_JOURNAL = 578, 420 | KEY_CONTROLPANEL = 579, 421 | KEY_APPSELECT = 580, 422 | KEY_SCREENSAVER = 581, 423 | KEY_VOICECOMMAND = 582, 424 | KEY_ASSISTANT = 583, 425 | KEY_KBD_LAYOUT_NEXT = 584, 426 | KEY_BRIGHTNESS_MIN = 592, 427 | KEY_BRIGHTNESS_MAX = 593, 428 | KEY_KBDINPUTASSIST_PREV = 608, 429 | KEY_KBDINPUTASSIST_NEXT = 609, 430 | KEY_KBDINPUTASSIST_PREVGROUP = 610, 431 | KEY_KBDINPUTASSIST_NEXTGROUP = 611, 432 | KEY_KBDINPUTASSIST_ACCEPT = 612, 433 | KEY_KBDINPUTASSIST_CANCEL = 613, 434 | KEY_RIGHT_UP = 614, 435 | KEY_RIGHT_DOWN = 615, 436 | KEY_LEFT_UP = 616, 437 | KEY_LEFT_DOWN = 617, 438 | KEY_ROOT_MENU = 618, 439 | KEY_MEDIA_TOP_MENU = 619, 440 | KEY_NUMERIC_11 = 620, 441 | KEY_NUMERIC_12 = 621, 442 | KEY_AUDIO_DESC = 622, 443 | KEY_3D_MODE = 623, 444 | KEY_NEXT_FAVORITE = 624, 445 | KEY_STOP_RECORD = 625, 446 | KEY_PAUSE_RECORD = 626, 447 | KEY_VOD = 627, 448 | KEY_UNMUTE = 628, 449 | KEY_FASTREVERSE = 629, 450 | KEY_SLOWREVERSE = 630, 451 | KEY_DATA = 631, 452 | KEY_ONSCREEN_KEYBOARD = 632, 453 | KEY_MAX = 767, 454 | BTN_0 = 256, 455 | BTN_1 = 257, 456 | BTN_2 = 258, 457 | BTN_3 = 259, 458 | BTN_4 = 260, 459 | BTN_5 = 261, 460 | BTN_6 = 262, 461 | BTN_7 = 263, 462 | BTN_8 = 264, 463 | BTN_9 = 265, 464 | BTN_LEFT = 272, 465 | BTN_RIGHT = 273, 466 | BTN_MIDDLE = 274, 467 | BTN_SIDE = 275, 468 | BTN_EXTRA = 276, 469 | BTN_FORWARD = 277, 470 | BTN_BACK = 278, 471 | BTN_TASK = 279, 472 | BTN_TRIGGER = 288, 473 | BTN_THUMB = 289, 474 | BTN_THUMB2 = 290, 475 | BTN_TOP = 291, 476 | BTN_TOP2 = 292, 477 | BTN_PINKIE = 293, 478 | BTN_BASE = 294, 479 | BTN_BASE2 = 295, 480 | BTN_BASE3 = 296, 481 | BTN_BASE4 = 297, 482 | BTN_BASE5 = 298, 483 | BTN_BASE6 = 299, 484 | BTN_DEAD = 303, 485 | BTN_SOUTH = 304, 486 | BTN_EAST = 305, 487 | BTN_C = 306, 488 | BTN_NORTH = 307, 489 | BTN_WEST = 308, 490 | BTN_Z = 309, 491 | BTN_TL = 310, 492 | BTN_TR = 311, 493 | BTN_TL2 = 312, 494 | BTN_TR2 = 313, 495 | BTN_SELECT = 314, 496 | BTN_START = 315, 497 | BTN_MODE = 316, 498 | BTN_THUMBL = 317, 499 | BTN_THUMBR = 318, 500 | BTN_TOOL_PEN = 320, 501 | BTN_TOOL_RUBBER = 321, 502 | BTN_TOOL_BRUSH = 322, 503 | BTN_TOOL_PENCIL = 323, 504 | BTN_TOOL_AIRBRUSH = 324, 505 | BTN_TOOL_FINGER = 325, 506 | BTN_TOOL_MOUSE = 326, 507 | BTN_TOOL_LENS = 327, 508 | BTN_TOOL_QUINTTAP = 328, 509 | BTN_STYLUS3 = 329, 510 | BTN_TOUCH = 330, 511 | BTN_STYLUS = 331, 512 | BTN_STYLUS2 = 332, 513 | BTN_TOOL_DOUBLETAP = 333, 514 | BTN_TOOL_TRIPLETAP = 334, 515 | BTN_TOOL_QUADTAP = 335, 516 | BTN_GEAR_DOWN = 336, 517 | BTN_GEAR_UP = 337, 518 | BTN_DPAD_UP = 544, 519 | BTN_DPAD_DOWN = 545, 520 | BTN_DPAD_LEFT = 546, 521 | BTN_DPAD_RIGHT = 547, 522 | BTN_TRIGGER_HAPPY1 = 704, 523 | BTN_TRIGGER_HAPPY2 = 705, 524 | BTN_TRIGGER_HAPPY3 = 706, 525 | BTN_TRIGGER_HAPPY4 = 707, 526 | BTN_TRIGGER_HAPPY5 = 708, 527 | BTN_TRIGGER_HAPPY6 = 709, 528 | BTN_TRIGGER_HAPPY7 = 710, 529 | BTN_TRIGGER_HAPPY8 = 711, 530 | BTN_TRIGGER_HAPPY9 = 712, 531 | BTN_TRIGGER_HAPPY10 = 713, 532 | BTN_TRIGGER_HAPPY11 = 714, 533 | BTN_TRIGGER_HAPPY12 = 715, 534 | BTN_TRIGGER_HAPPY13 = 716, 535 | BTN_TRIGGER_HAPPY14 = 717, 536 | BTN_TRIGGER_HAPPY15 = 718, 537 | BTN_TRIGGER_HAPPY16 = 719, 538 | BTN_TRIGGER_HAPPY17 = 720, 539 | BTN_TRIGGER_HAPPY18 = 721, 540 | BTN_TRIGGER_HAPPY19 = 722, 541 | BTN_TRIGGER_HAPPY20 = 723, 542 | BTN_TRIGGER_HAPPY21 = 724, 543 | BTN_TRIGGER_HAPPY22 = 725, 544 | BTN_TRIGGER_HAPPY23 = 726, 545 | BTN_TRIGGER_HAPPY24 = 727, 546 | BTN_TRIGGER_HAPPY25 = 728, 547 | BTN_TRIGGER_HAPPY26 = 729, 548 | BTN_TRIGGER_HAPPY27 = 730, 549 | BTN_TRIGGER_HAPPY28 = 731, 550 | BTN_TRIGGER_HAPPY29 = 732, 551 | BTN_TRIGGER_HAPPY30 = 733, 552 | BTN_TRIGGER_HAPPY31 = 734, 553 | BTN_TRIGGER_HAPPY32 = 735, 554 | BTN_TRIGGER_HAPPY33 = 736, 555 | BTN_TRIGGER_HAPPY34 = 737, 556 | BTN_TRIGGER_HAPPY35 = 738, 557 | BTN_TRIGGER_HAPPY36 = 739, 558 | BTN_TRIGGER_HAPPY37 = 740, 559 | BTN_TRIGGER_HAPPY38 = 741, 560 | BTN_TRIGGER_HAPPY39 = 742, 561 | BTN_TRIGGER_HAPPY40 = 743, 562 | } 563 | 564 | impl KeyCode { 565 | pub fn from_u32(code: u32) -> Option { 566 | match code { 567 | 0 => Some(KeyCode::KEY_RESERVED), 568 | 1 => Some(KeyCode::KEY_ESC), 569 | 2 => Some(KeyCode::KEY_1), 570 | 3 => Some(KeyCode::KEY_2), 571 | 4 => Some(KeyCode::KEY_3), 572 | 5 => Some(KeyCode::KEY_4), 573 | 6 => Some(KeyCode::KEY_5), 574 | 7 => Some(KeyCode::KEY_6), 575 | 8 => Some(KeyCode::KEY_7), 576 | 9 => Some(KeyCode::KEY_8), 577 | 10 => Some(KeyCode::KEY_9), 578 | 11 => Some(KeyCode::KEY_0), 579 | 12 => Some(KeyCode::KEY_MINUS), 580 | 13 => Some(KeyCode::KEY_EQUAL), 581 | 14 => Some(KeyCode::KEY_BACKSPACE), 582 | 15 => Some(KeyCode::KEY_TAB), 583 | 16 => Some(KeyCode::KEY_Q), 584 | 17 => Some(KeyCode::KEY_W), 585 | 18 => Some(KeyCode::KEY_E), 586 | 19 => Some(KeyCode::KEY_R), 587 | 20 => Some(KeyCode::KEY_T), 588 | 21 => Some(KeyCode::KEY_Y), 589 | 22 => Some(KeyCode::KEY_U), 590 | 23 => Some(KeyCode::KEY_I), 591 | 24 => Some(KeyCode::KEY_O), 592 | 25 => Some(KeyCode::KEY_P), 593 | 26 => Some(KeyCode::KEY_LEFTBRACE), 594 | 27 => Some(KeyCode::KEY_RIGHTBRACE), 595 | 28 => Some(KeyCode::KEY_ENTER), 596 | 29 => Some(KeyCode::KEY_LEFTCTRL), 597 | 30 => Some(KeyCode::KEY_A), 598 | 31 => Some(KeyCode::KEY_S), 599 | 32 => Some(KeyCode::KEY_D), 600 | 33 => Some(KeyCode::KEY_F), 601 | 34 => Some(KeyCode::KEY_G), 602 | 35 => Some(KeyCode::KEY_H), 603 | 36 => Some(KeyCode::KEY_J), 604 | 37 => Some(KeyCode::KEY_K), 605 | 38 => Some(KeyCode::KEY_L), 606 | 39 => Some(KeyCode::KEY_SEMICOLON), 607 | 40 => Some(KeyCode::KEY_APOSTROPHE), 608 | 41 => Some(KeyCode::KEY_GRAVE), 609 | 42 => Some(KeyCode::KEY_LEFTSHIFT), 610 | 43 => Some(KeyCode::KEY_BACKSLASH), 611 | 44 => Some(KeyCode::KEY_Z), 612 | 45 => Some(KeyCode::KEY_X), 613 | 46 => Some(KeyCode::KEY_C), 614 | 47 => Some(KeyCode::KEY_V), 615 | 48 => Some(KeyCode::KEY_B), 616 | 49 => Some(KeyCode::KEY_N), 617 | 50 => Some(KeyCode::KEY_M), 618 | 51 => Some(KeyCode::KEY_COMMA), 619 | 52 => Some(KeyCode::KEY_DOT), 620 | 53 => Some(KeyCode::KEY_SLASH), 621 | 54 => Some(KeyCode::KEY_RIGHTSHIFT), 622 | 55 => Some(KeyCode::KEY_KPASTERISK), 623 | 56 => Some(KeyCode::KEY_LEFTALT), 624 | 57 => Some(KeyCode::KEY_SPACE), 625 | 58 => Some(KeyCode::KEY_CAPSLOCK), 626 | 59 => Some(KeyCode::KEY_F1), 627 | 60 => Some(KeyCode::KEY_F2), 628 | 61 => Some(KeyCode::KEY_F3), 629 | 62 => Some(KeyCode::KEY_F4), 630 | 63 => Some(KeyCode::KEY_F5), 631 | 64 => Some(KeyCode::KEY_F6), 632 | 65 => Some(KeyCode::KEY_F7), 633 | 66 => Some(KeyCode::KEY_F8), 634 | 67 => Some(KeyCode::KEY_F9), 635 | 68 => Some(KeyCode::KEY_F10), 636 | 69 => Some(KeyCode::KEY_NUMLOCK), 637 | 70 => Some(KeyCode::KEY_SCROLLLOCK), 638 | 71 => Some(KeyCode::KEY_KP7), 639 | 72 => Some(KeyCode::KEY_KP8), 640 | 73 => Some(KeyCode::KEY_KP9), 641 | 74 => Some(KeyCode::KEY_KPMINUS), 642 | 75 => Some(KeyCode::KEY_KP4), 643 | 76 => Some(KeyCode::KEY_KP5), 644 | 77 => Some(KeyCode::KEY_KP6), 645 | 78 => Some(KeyCode::KEY_KPPLUS), 646 | 79 => Some(KeyCode::KEY_KP1), 647 | 80 => Some(KeyCode::KEY_KP2), 648 | 81 => Some(KeyCode::KEY_KP3), 649 | 82 => Some(KeyCode::KEY_KP0), 650 | 83 => Some(KeyCode::KEY_KPDOT), 651 | 85 => Some(KeyCode::KEY_ZENKAKUHANKAKU), 652 | 86 => Some(KeyCode::KEY_102ND), 653 | 87 => Some(KeyCode::KEY_F11), 654 | 88 => Some(KeyCode::KEY_F12), 655 | 89 => Some(KeyCode::KEY_RO), 656 | 90 => Some(KeyCode::KEY_KATAKANA), 657 | 91 => Some(KeyCode::KEY_HIRAGANA), 658 | 92 => Some(KeyCode::KEY_HENKAN), 659 | 93 => Some(KeyCode::KEY_KATAKANAHIRAGANA), 660 | 94 => Some(KeyCode::KEY_MUHENKAN), 661 | 95 => Some(KeyCode::KEY_KPJPCOMMA), 662 | 96 => Some(KeyCode::KEY_KPENTER), 663 | 97 => Some(KeyCode::KEY_RIGHTCTRL), 664 | 98 => Some(KeyCode::KEY_KPSLASH), 665 | 99 => Some(KeyCode::KEY_SYSRQ), 666 | 100 => Some(KeyCode::KEY_RIGHTALT), 667 | 101 => Some(KeyCode::KEY_LINEFEED), 668 | 102 => Some(KeyCode::KEY_HOME), 669 | 103 => Some(KeyCode::KEY_UP), 670 | 104 => Some(KeyCode::KEY_PAGEUP), 671 | 105 => Some(KeyCode::KEY_LEFT), 672 | 106 => Some(KeyCode::KEY_RIGHT), 673 | 107 => Some(KeyCode::KEY_END), 674 | 108 => Some(KeyCode::KEY_DOWN), 675 | 109 => Some(KeyCode::KEY_PAGEDOWN), 676 | 110 => Some(KeyCode::KEY_INSERT), 677 | 111 => Some(KeyCode::KEY_DELETE), 678 | 112 => Some(KeyCode::KEY_MACRO), 679 | 113 => Some(KeyCode::KEY_MUTE), 680 | 114 => Some(KeyCode::KEY_VOLUMEDOWN), 681 | 115 => Some(KeyCode::KEY_VOLUMEUP), 682 | 116 => Some(KeyCode::KEY_POWER), 683 | 117 => Some(KeyCode::KEY_KPEQUAL), 684 | 118 => Some(KeyCode::KEY_KPPLUSMINUS), 685 | 119 => Some(KeyCode::KEY_PAUSE), 686 | 120 => Some(KeyCode::KEY_SCALE), 687 | 121 => Some(KeyCode::KEY_KPCOMMA), 688 | 122 => Some(KeyCode::KEY_HANGEUL), 689 | 123 => Some(KeyCode::KEY_HANJA), 690 | 124 => Some(KeyCode::KEY_YEN), 691 | 125 => Some(KeyCode::KEY_LEFTMETA), 692 | 126 => Some(KeyCode::KEY_RIGHTMETA), 693 | 127 => Some(KeyCode::KEY_COMPOSE), 694 | 128 => Some(KeyCode::KEY_STOP), 695 | 129 => Some(KeyCode::KEY_AGAIN), 696 | 130 => Some(KeyCode::KEY_PROPS), 697 | 131 => Some(KeyCode::KEY_UNDO), 698 | 132 => Some(KeyCode::KEY_FRONT), 699 | 133 => Some(KeyCode::KEY_COPY), 700 | 134 => Some(KeyCode::KEY_OPEN), 701 | 135 => Some(KeyCode::KEY_PASTE), 702 | 136 => Some(KeyCode::KEY_FIND), 703 | 137 => Some(KeyCode::KEY_CUT), 704 | 138 => Some(KeyCode::KEY_HELP), 705 | 139 => Some(KeyCode::KEY_MENU), 706 | 140 => Some(KeyCode::KEY_CALC), 707 | 141 => Some(KeyCode::KEY_SETUP), 708 | 142 => Some(KeyCode::KEY_SLEEP), 709 | 143 => Some(KeyCode::KEY_WAKEUP), 710 | 144 => Some(KeyCode::KEY_FILE), 711 | 145 => Some(KeyCode::KEY_SENDFILE), 712 | 146 => Some(KeyCode::KEY_DELETEFILE), 713 | 147 => Some(KeyCode::KEY_XFER), 714 | 148 => Some(KeyCode::KEY_PROG1), 715 | 149 => Some(KeyCode::KEY_PROG2), 716 | 150 => Some(KeyCode::KEY_WWW), 717 | 151 => Some(KeyCode::KEY_MSDOS), 718 | 152 => Some(KeyCode::KEY_COFFEE), 719 | 153 => Some(KeyCode::KEY_ROTATE_DISPLAY), 720 | 154 => Some(KeyCode::KEY_CYCLEWINDOWS), 721 | 155 => Some(KeyCode::KEY_MAIL), 722 | 156 => Some(KeyCode::KEY_BOOKMARKS), 723 | 157 => Some(KeyCode::KEY_COMPUTER), 724 | 158 => Some(KeyCode::KEY_BACK), 725 | 159 => Some(KeyCode::KEY_FORWARD), 726 | 160 => Some(KeyCode::KEY_CLOSECD), 727 | 161 => Some(KeyCode::KEY_EJECTCD), 728 | 162 => Some(KeyCode::KEY_EJECTCLOSECD), 729 | 163 => Some(KeyCode::KEY_NEXTSONG), 730 | 164 => Some(KeyCode::KEY_PLAYPAUSE), 731 | 165 => Some(KeyCode::KEY_PREVIOUSSONG), 732 | 166 => Some(KeyCode::KEY_STOPCD), 733 | 167 => Some(KeyCode::KEY_RECORD), 734 | 168 => Some(KeyCode::KEY_REWIND), 735 | 169 => Some(KeyCode::KEY_PHONE), 736 | 170 => Some(KeyCode::KEY_ISO), 737 | 171 => Some(KeyCode::KEY_CONFIG), 738 | 172 => Some(KeyCode::KEY_HOMEPAGE), 739 | 173 => Some(KeyCode::KEY_REFRESH), 740 | 174 => Some(KeyCode::KEY_EXIT), 741 | 175 => Some(KeyCode::KEY_MOVE), 742 | 176 => Some(KeyCode::KEY_EDIT), 743 | 177 => Some(KeyCode::KEY_SCROLLUP), 744 | 178 => Some(KeyCode::KEY_SCROLLDOWN), 745 | 179 => Some(KeyCode::KEY_KPLEFTPAREN), 746 | 180 => Some(KeyCode::KEY_KPRIGHTPAREN), 747 | 181 => Some(KeyCode::KEY_NEW), 748 | 182 => Some(KeyCode::KEY_REDO), 749 | 183 => Some(KeyCode::KEY_F13), 750 | 184 => Some(KeyCode::KEY_F14), 751 | 185 => Some(KeyCode::KEY_F15), 752 | 186 => Some(KeyCode::KEY_F16), 753 | 187 => Some(KeyCode::KEY_F17), 754 | 188 => Some(KeyCode::KEY_F18), 755 | 189 => Some(KeyCode::KEY_F19), 756 | 190 => Some(KeyCode::KEY_F20), 757 | 191 => Some(KeyCode::KEY_F21), 758 | 192 => Some(KeyCode::KEY_F22), 759 | 193 => Some(KeyCode::KEY_F23), 760 | 194 => Some(KeyCode::KEY_F24), 761 | 200 => Some(KeyCode::KEY_PLAYCD), 762 | 201 => Some(KeyCode::KEY_PAUSECD), 763 | 202 => Some(KeyCode::KEY_PROG3), 764 | 203 => Some(KeyCode::KEY_PROG4), 765 | 204 => Some(KeyCode::KEY_DASHBOARD), 766 | 205 => Some(KeyCode::KEY_SUSPEND), 767 | 206 => Some(KeyCode::KEY_CLOSE), 768 | 207 => Some(KeyCode::KEY_PLAY), 769 | 208 => Some(KeyCode::KEY_FASTFORWARD), 770 | 209 => Some(KeyCode::KEY_BASSBOOST), 771 | 210 => Some(KeyCode::KEY_PRINT), 772 | 211 => Some(KeyCode::KEY_HP), 773 | 212 => Some(KeyCode::KEY_CAMERA), 774 | 213 => Some(KeyCode::KEY_SOUND), 775 | 214 => Some(KeyCode::KEY_QUESTION), 776 | 215 => Some(KeyCode::KEY_EMAIL), 777 | 216 => Some(KeyCode::KEY_CHAT), 778 | 217 => Some(KeyCode::KEY_SEARCH), 779 | 218 => Some(KeyCode::KEY_CONNECT), 780 | 219 => Some(KeyCode::KEY_FINANCE), 781 | 220 => Some(KeyCode::KEY_SPORT), 782 | 221 => Some(KeyCode::KEY_SHOP), 783 | 222 => Some(KeyCode::KEY_ALTERASE), 784 | 223 => Some(KeyCode::KEY_CANCEL), 785 | 224 => Some(KeyCode::KEY_BRIGHTNESSDOWN), 786 | 225 => Some(KeyCode::KEY_BRIGHTNESSUP), 787 | 226 => Some(KeyCode::KEY_MEDIA), 788 | 227 => Some(KeyCode::KEY_SWITCHVIDEOMODE), 789 | 228 => Some(KeyCode::KEY_KBDILLUMTOGGLE), 790 | 229 => Some(KeyCode::KEY_KBDILLUMDOWN), 791 | 230 => Some(KeyCode::KEY_KBDILLUMUP), 792 | 231 => Some(KeyCode::KEY_SEND), 793 | 232 => Some(KeyCode::KEY_REPLY), 794 | 233 => Some(KeyCode::KEY_FORWARDMAIL), 795 | 234 => Some(KeyCode::KEY_SAVE), 796 | 235 => Some(KeyCode::KEY_DOCUMENTS), 797 | 236 => Some(KeyCode::KEY_BATTERY), 798 | 237 => Some(KeyCode::KEY_BLUETOOTH), 799 | 238 => Some(KeyCode::KEY_WLAN), 800 | 239 => Some(KeyCode::KEY_UWB), 801 | 240 => Some(KeyCode::KEY_UNKNOWN), 802 | 241 => Some(KeyCode::KEY_VIDEO_NEXT), 803 | 242 => Some(KeyCode::KEY_VIDEO_PREV), 804 | 243 => Some(KeyCode::KEY_BRIGHTNESS_CYCLE), 805 | 244 => Some(KeyCode::KEY_BRIGHTNESS_AUTO), 806 | 245 => Some(KeyCode::KEY_DISPLAY_OFF), 807 | 246 => Some(KeyCode::KEY_WWAN), 808 | 247 => Some(KeyCode::KEY_RFKILL), 809 | 248 => Some(KeyCode::KEY_MICMUTE), 810 | 352 => Some(KeyCode::KEY_OK), 811 | 353 => Some(KeyCode::KEY_SELECT), 812 | 354 => Some(KeyCode::KEY_GOTO), 813 | 355 => Some(KeyCode::KEY_CLEAR), 814 | 356 => Some(KeyCode::KEY_POWER2), 815 | 357 => Some(KeyCode::KEY_OPTION), 816 | 358 => Some(KeyCode::KEY_INFO), 817 | 359 => Some(KeyCode::KEY_TIME), 818 | 360 => Some(KeyCode::KEY_VENDOR), 819 | 361 => Some(KeyCode::KEY_ARCHIVE), 820 | 362 => Some(KeyCode::KEY_PROGRAM), 821 | 363 => Some(KeyCode::KEY_CHANNEL), 822 | 364 => Some(KeyCode::KEY_FAVORITES), 823 | 365 => Some(KeyCode::KEY_EPG), 824 | 366 => Some(KeyCode::KEY_PVR), 825 | 367 => Some(KeyCode::KEY_MHP), 826 | 368 => Some(KeyCode::KEY_LANGUAGE), 827 | 369 => Some(KeyCode::KEY_TITLE), 828 | 370 => Some(KeyCode::KEY_SUBTITLE), 829 | 371 => Some(KeyCode::KEY_ANGLE), 830 | 372 => Some(KeyCode::KEY_FULL_SCREEN), 831 | 373 => Some(KeyCode::KEY_MODE), 832 | 374 => Some(KeyCode::KEY_KEYBOARD), 833 | 375 => Some(KeyCode::KEY_ASPECT_RATIO), 834 | 376 => Some(KeyCode::KEY_PC), 835 | 377 => Some(KeyCode::KEY_TV), 836 | 378 => Some(KeyCode::KEY_TV2), 837 | 379 => Some(KeyCode::KEY_VCR), 838 | 380 => Some(KeyCode::KEY_VCR2), 839 | 381 => Some(KeyCode::KEY_SAT), 840 | 382 => Some(KeyCode::KEY_SAT2), 841 | 383 => Some(KeyCode::KEY_CD), 842 | 384 => Some(KeyCode::KEY_TAPE), 843 | 385 => Some(KeyCode::KEY_RADIO), 844 | 386 => Some(KeyCode::KEY_TUNER), 845 | 387 => Some(KeyCode::KEY_PLAYER), 846 | 388 => Some(KeyCode::KEY_TEXT), 847 | 389 => Some(KeyCode::KEY_DVD), 848 | 390 => Some(KeyCode::KEY_AUX), 849 | 391 => Some(KeyCode::KEY_MP3), 850 | 392 => Some(KeyCode::KEY_AUDIO), 851 | 393 => Some(KeyCode::KEY_VIDEO), 852 | 394 => Some(KeyCode::KEY_DIRECTORY), 853 | 395 => Some(KeyCode::KEY_LIST), 854 | 396 => Some(KeyCode::KEY_MEMO), 855 | 397 => Some(KeyCode::KEY_CALENDAR), 856 | 398 => Some(KeyCode::KEY_RED), 857 | 399 => Some(KeyCode::KEY_GREEN), 858 | 400 => Some(KeyCode::KEY_YELLOW), 859 | 401 => Some(KeyCode::KEY_BLUE), 860 | 402 => Some(KeyCode::KEY_CHANNELUP), 861 | 403 => Some(KeyCode::KEY_CHANNELDOWN), 862 | 404 => Some(KeyCode::KEY_FIRST), 863 | 405 => Some(KeyCode::KEY_LAST), 864 | 406 => Some(KeyCode::KEY_AB), 865 | 407 => Some(KeyCode::KEY_NEXT), 866 | 408 => Some(KeyCode::KEY_RESTART), 867 | 409 => Some(KeyCode::KEY_SLOW), 868 | 410 => Some(KeyCode::KEY_SHUFFLE), 869 | 411 => Some(KeyCode::KEY_BREAK), 870 | 412 => Some(KeyCode::KEY_PREVIOUS), 871 | 413 => Some(KeyCode::KEY_DIGITS), 872 | 414 => Some(KeyCode::KEY_TEEN), 873 | 415 => Some(KeyCode::KEY_TWEN), 874 | 416 => Some(KeyCode::KEY_VIDEOPHONE), 875 | 417 => Some(KeyCode::KEY_GAMES), 876 | 418 => Some(KeyCode::KEY_ZOOMIN), 877 | 419 => Some(KeyCode::KEY_ZOOMOUT), 878 | 420 => Some(KeyCode::KEY_ZOOMRESET), 879 | 421 => Some(KeyCode::KEY_WORDPROCESSOR), 880 | 422 => Some(KeyCode::KEY_EDITOR), 881 | 423 => Some(KeyCode::KEY_SPREADSHEET), 882 | 424 => Some(KeyCode::KEY_GRAPHICSEDITOR), 883 | 425 => Some(KeyCode::KEY_PRESENTATION), 884 | 426 => Some(KeyCode::KEY_DATABASE), 885 | 427 => Some(KeyCode::KEY_NEWS), 886 | 428 => Some(KeyCode::KEY_VOICEMAIL), 887 | 429 => Some(KeyCode::KEY_ADDRESSBOOK), 888 | 430 => Some(KeyCode::KEY_MESSENGER), 889 | 431 => Some(KeyCode::KEY_DISPLAYTOGGLE), 890 | 432 => Some(KeyCode::KEY_SPELLCHECK), 891 | 433 => Some(KeyCode::KEY_LOGOFF), 892 | 434 => Some(KeyCode::KEY_DOLLAR), 893 | 435 => Some(KeyCode::KEY_EURO), 894 | 436 => Some(KeyCode::KEY_FRAMEBACK), 895 | 437 => Some(KeyCode::KEY_FRAMEFORWARD), 896 | 438 => Some(KeyCode::KEY_CONTEXT_MENU), 897 | 439 => Some(KeyCode::KEY_MEDIA_REPEAT), 898 | 440 => Some(KeyCode::KEY_10CHANNELSUP), 899 | 441 => Some(KeyCode::KEY_10CHANNELSDOWN), 900 | 442 => Some(KeyCode::KEY_IMAGES), 901 | 448 => Some(KeyCode::KEY_DEL_EOL), 902 | 449 => Some(KeyCode::KEY_DEL_EOS), 903 | 450 => Some(KeyCode::KEY_INS_LINE), 904 | 451 => Some(KeyCode::KEY_DEL_LINE), 905 | 464 => Some(KeyCode::KEY_FN), 906 | 465 => Some(KeyCode::KEY_FN_ESC), 907 | 466 => Some(KeyCode::KEY_FN_F1), 908 | 467 => Some(KeyCode::KEY_FN_F2), 909 | 468 => Some(KeyCode::KEY_FN_F3), 910 | 469 => Some(KeyCode::KEY_FN_F4), 911 | 470 => Some(KeyCode::KEY_FN_F5), 912 | 471 => Some(KeyCode::KEY_FN_F6), 913 | 472 => Some(KeyCode::KEY_FN_F7), 914 | 473 => Some(KeyCode::KEY_FN_F8), 915 | 474 => Some(KeyCode::KEY_FN_F9), 916 | 475 => Some(KeyCode::KEY_FN_F10), 917 | 476 => Some(KeyCode::KEY_FN_F11), 918 | 477 => Some(KeyCode::KEY_FN_F12), 919 | 478 => Some(KeyCode::KEY_FN_1), 920 | 479 => Some(KeyCode::KEY_FN_2), 921 | 480 => Some(KeyCode::KEY_FN_D), 922 | 481 => Some(KeyCode::KEY_FN_E), 923 | 482 => Some(KeyCode::KEY_FN_F), 924 | 483 => Some(KeyCode::KEY_FN_S), 925 | 484 => Some(KeyCode::KEY_FN_B), 926 | 497 => Some(KeyCode::KEY_BRL_DOT1), 927 | 498 => Some(KeyCode::KEY_BRL_DOT2), 928 | 499 => Some(KeyCode::KEY_BRL_DOT3), 929 | 500 => Some(KeyCode::KEY_BRL_DOT4), 930 | 501 => Some(KeyCode::KEY_BRL_DOT5), 931 | 502 => Some(KeyCode::KEY_BRL_DOT6), 932 | 503 => Some(KeyCode::KEY_BRL_DOT7), 933 | 504 => Some(KeyCode::KEY_BRL_DOT8), 934 | 505 => Some(KeyCode::KEY_BRL_DOT9), 935 | 506 => Some(KeyCode::KEY_BRL_DOT10), 936 | 512 => Some(KeyCode::KEY_NUMERIC_0), 937 | 513 => Some(KeyCode::KEY_NUMERIC_1), 938 | 514 => Some(KeyCode::KEY_NUMERIC_2), 939 | 515 => Some(KeyCode::KEY_NUMERIC_3), 940 | 516 => Some(KeyCode::KEY_NUMERIC_4), 941 | 517 => Some(KeyCode::KEY_NUMERIC_5), 942 | 518 => Some(KeyCode::KEY_NUMERIC_6), 943 | 519 => Some(KeyCode::KEY_NUMERIC_7), 944 | 520 => Some(KeyCode::KEY_NUMERIC_8), 945 | 521 => Some(KeyCode::KEY_NUMERIC_9), 946 | 522 => Some(KeyCode::KEY_NUMERIC_STAR), 947 | 523 => Some(KeyCode::KEY_NUMERIC_POUND), 948 | 524 => Some(KeyCode::KEY_NUMERIC_A), 949 | 525 => Some(KeyCode::KEY_NUMERIC_B), 950 | 526 => Some(KeyCode::KEY_NUMERIC_C), 951 | 527 => Some(KeyCode::KEY_NUMERIC_D), 952 | 528 => Some(KeyCode::KEY_CAMERA_FOCUS), 953 | 529 => Some(KeyCode::KEY_WPS_BUTTON), 954 | 530 => Some(KeyCode::KEY_TOUCHPAD_TOGGLE), 955 | 531 => Some(KeyCode::KEY_TOUCHPAD_ON), 956 | 532 => Some(KeyCode::KEY_TOUCHPAD_OFF), 957 | 533 => Some(KeyCode::KEY_CAMERA_ZOOMIN), 958 | 534 => Some(KeyCode::KEY_CAMERA_ZOOMOUT), 959 | 535 => Some(KeyCode::KEY_CAMERA_UP), 960 | 536 => Some(KeyCode::KEY_CAMERA_DOWN), 961 | 537 => Some(KeyCode::KEY_CAMERA_LEFT), 962 | 538 => Some(KeyCode::KEY_CAMERA_RIGHT), 963 | 539 => Some(KeyCode::KEY_ATTENDANT_ON), 964 | 540 => Some(KeyCode::KEY_ATTENDANT_OFF), 965 | 541 => Some(KeyCode::KEY_ATTENDANT_TOGGLE), 966 | 542 => Some(KeyCode::KEY_LIGHTS_TOGGLE), 967 | 560 => Some(KeyCode::KEY_ALS_TOGGLE), 968 | 561 => Some(KeyCode::KEY_ROTATE_LOCK_TOGGLE), 969 | 576 => Some(KeyCode::KEY_BUTTONCONFIG), 970 | 577 => Some(KeyCode::KEY_TASKMANAGER), 971 | 578 => Some(KeyCode::KEY_JOURNAL), 972 | 579 => Some(KeyCode::KEY_CONTROLPANEL), 973 | 580 => Some(KeyCode::KEY_APPSELECT), 974 | 581 => Some(KeyCode::KEY_SCREENSAVER), 975 | 582 => Some(KeyCode::KEY_VOICECOMMAND), 976 | 583 => Some(KeyCode::KEY_ASSISTANT), 977 | 584 => Some(KeyCode::KEY_KBD_LAYOUT_NEXT), 978 | 592 => Some(KeyCode::KEY_BRIGHTNESS_MIN), 979 | 593 => Some(KeyCode::KEY_BRIGHTNESS_MAX), 980 | 608 => Some(KeyCode::KEY_KBDINPUTASSIST_PREV), 981 | 609 => Some(KeyCode::KEY_KBDINPUTASSIST_NEXT), 982 | 610 => Some(KeyCode::KEY_KBDINPUTASSIST_PREVGROUP), 983 | 611 => Some(KeyCode::KEY_KBDINPUTASSIST_NEXTGROUP), 984 | 612 => Some(KeyCode::KEY_KBDINPUTASSIST_ACCEPT), 985 | 613 => Some(KeyCode::KEY_KBDINPUTASSIST_CANCEL), 986 | 614 => Some(KeyCode::KEY_RIGHT_UP), 987 | 615 => Some(KeyCode::KEY_RIGHT_DOWN), 988 | 616 => Some(KeyCode::KEY_LEFT_UP), 989 | 617 => Some(KeyCode::KEY_LEFT_DOWN), 990 | 618 => Some(KeyCode::KEY_ROOT_MENU), 991 | 619 => Some(KeyCode::KEY_MEDIA_TOP_MENU), 992 | 620 => Some(KeyCode::KEY_NUMERIC_11), 993 | 621 => Some(KeyCode::KEY_NUMERIC_12), 994 | 622 => Some(KeyCode::KEY_AUDIO_DESC), 995 | 623 => Some(KeyCode::KEY_3D_MODE), 996 | 624 => Some(KeyCode::KEY_NEXT_FAVORITE), 997 | 625 => Some(KeyCode::KEY_STOP_RECORD), 998 | 626 => Some(KeyCode::KEY_PAUSE_RECORD), 999 | 627 => Some(KeyCode::KEY_VOD), 1000 | 628 => Some(KeyCode::KEY_UNMUTE), 1001 | 629 => Some(KeyCode::KEY_FASTREVERSE), 1002 | 630 => Some(KeyCode::KEY_SLOWREVERSE), 1003 | 631 => Some(KeyCode::KEY_DATA), 1004 | 632 => Some(KeyCode::KEY_ONSCREEN_KEYBOARD), 1005 | 767 => Some(KeyCode::KEY_MAX), 1006 | 256 => Some(KeyCode::BTN_0), 1007 | 257 => Some(KeyCode::BTN_1), 1008 | 258 => Some(KeyCode::BTN_2), 1009 | 259 => Some(KeyCode::BTN_3), 1010 | 260 => Some(KeyCode::BTN_4), 1011 | 261 => Some(KeyCode::BTN_5), 1012 | 262 => Some(KeyCode::BTN_6), 1013 | 263 => Some(KeyCode::BTN_7), 1014 | 264 => Some(KeyCode::BTN_8), 1015 | 265 => Some(KeyCode::BTN_9), 1016 | 272 => Some(KeyCode::BTN_LEFT), 1017 | 273 => Some(KeyCode::BTN_RIGHT), 1018 | 274 => Some(KeyCode::BTN_MIDDLE), 1019 | 275 => Some(KeyCode::BTN_SIDE), 1020 | 276 => Some(KeyCode::BTN_EXTRA), 1021 | 277 => Some(KeyCode::BTN_FORWARD), 1022 | 278 => Some(KeyCode::BTN_BACK), 1023 | 279 => Some(KeyCode::BTN_TASK), 1024 | 288 => Some(KeyCode::BTN_TRIGGER), 1025 | 289 => Some(KeyCode::BTN_THUMB), 1026 | 290 => Some(KeyCode::BTN_THUMB2), 1027 | 291 => Some(KeyCode::BTN_TOP), 1028 | 292 => Some(KeyCode::BTN_TOP2), 1029 | 293 => Some(KeyCode::BTN_PINKIE), 1030 | 294 => Some(KeyCode::BTN_BASE), 1031 | 295 => Some(KeyCode::BTN_BASE2), 1032 | 296 => Some(KeyCode::BTN_BASE3), 1033 | 297 => Some(KeyCode::BTN_BASE4), 1034 | 298 => Some(KeyCode::BTN_BASE5), 1035 | 299 => Some(KeyCode::BTN_BASE6), 1036 | 303 => Some(KeyCode::BTN_DEAD), 1037 | 304 => Some(KeyCode::BTN_SOUTH), 1038 | 305 => Some(KeyCode::BTN_EAST), 1039 | 306 => Some(KeyCode::BTN_C), 1040 | 307 => Some(KeyCode::BTN_NORTH), 1041 | 308 => Some(KeyCode::BTN_WEST), 1042 | 309 => Some(KeyCode::BTN_Z), 1043 | 310 => Some(KeyCode::BTN_TL), 1044 | 311 => Some(KeyCode::BTN_TR), 1045 | 312 => Some(KeyCode::BTN_TL2), 1046 | 313 => Some(KeyCode::BTN_TR2), 1047 | 314 => Some(KeyCode::BTN_SELECT), 1048 | 315 => Some(KeyCode::BTN_START), 1049 | 316 => Some(KeyCode::BTN_MODE), 1050 | 317 => Some(KeyCode::BTN_THUMBL), 1051 | 318 => Some(KeyCode::BTN_THUMBR), 1052 | 320 => Some(KeyCode::BTN_TOOL_PEN), 1053 | 321 => Some(KeyCode::BTN_TOOL_RUBBER), 1054 | 322 => Some(KeyCode::BTN_TOOL_BRUSH), 1055 | 323 => Some(KeyCode::BTN_TOOL_PENCIL), 1056 | 324 => Some(KeyCode::BTN_TOOL_AIRBRUSH), 1057 | 325 => Some(KeyCode::BTN_TOOL_FINGER), 1058 | 326 => Some(KeyCode::BTN_TOOL_MOUSE), 1059 | 327 => Some(KeyCode::BTN_TOOL_LENS), 1060 | 328 => Some(KeyCode::BTN_TOOL_QUINTTAP), 1061 | 329 => Some(KeyCode::BTN_STYLUS3), 1062 | 330 => Some(KeyCode::BTN_TOUCH), 1063 | 331 => Some(KeyCode::BTN_STYLUS), 1064 | 332 => Some(KeyCode::BTN_STYLUS2), 1065 | 333 => Some(KeyCode::BTN_TOOL_DOUBLETAP), 1066 | 334 => Some(KeyCode::BTN_TOOL_TRIPLETAP), 1067 | 335 => Some(KeyCode::BTN_TOOL_QUADTAP), 1068 | 336 => Some(KeyCode::BTN_GEAR_DOWN), 1069 | 337 => Some(KeyCode::BTN_GEAR_UP), 1070 | 544 => Some(KeyCode::BTN_DPAD_UP), 1071 | 545 => Some(KeyCode::BTN_DPAD_DOWN), 1072 | 546 => Some(KeyCode::BTN_DPAD_LEFT), 1073 | 547 => Some(KeyCode::BTN_DPAD_RIGHT), 1074 | 704 => Some(KeyCode::BTN_TRIGGER_HAPPY1), 1075 | 705 => Some(KeyCode::BTN_TRIGGER_HAPPY2), 1076 | 706 => Some(KeyCode::BTN_TRIGGER_HAPPY3), 1077 | 707 => Some(KeyCode::BTN_TRIGGER_HAPPY4), 1078 | 708 => Some(KeyCode::BTN_TRIGGER_HAPPY5), 1079 | 709 => Some(KeyCode::BTN_TRIGGER_HAPPY6), 1080 | 710 => Some(KeyCode::BTN_TRIGGER_HAPPY7), 1081 | 711 => Some(KeyCode::BTN_TRIGGER_HAPPY8), 1082 | 712 => Some(KeyCode::BTN_TRIGGER_HAPPY9), 1083 | 713 => Some(KeyCode::BTN_TRIGGER_HAPPY10), 1084 | 714 => Some(KeyCode::BTN_TRIGGER_HAPPY11), 1085 | 715 => Some(KeyCode::BTN_TRIGGER_HAPPY12), 1086 | 716 => Some(KeyCode::BTN_TRIGGER_HAPPY13), 1087 | 717 => Some(KeyCode::BTN_TRIGGER_HAPPY14), 1088 | 718 => Some(KeyCode::BTN_TRIGGER_HAPPY15), 1089 | 719 => Some(KeyCode::BTN_TRIGGER_HAPPY16), 1090 | 720 => Some(KeyCode::BTN_TRIGGER_HAPPY17), 1091 | 721 => Some(KeyCode::BTN_TRIGGER_HAPPY18), 1092 | 722 => Some(KeyCode::BTN_TRIGGER_HAPPY19), 1093 | 723 => Some(KeyCode::BTN_TRIGGER_HAPPY20), 1094 | 724 => Some(KeyCode::BTN_TRIGGER_HAPPY21), 1095 | 725 => Some(KeyCode::BTN_TRIGGER_HAPPY22), 1096 | 726 => Some(KeyCode::BTN_TRIGGER_HAPPY23), 1097 | 727 => Some(KeyCode::BTN_TRIGGER_HAPPY24), 1098 | 728 => Some(KeyCode::BTN_TRIGGER_HAPPY25), 1099 | 729 => Some(KeyCode::BTN_TRIGGER_HAPPY26), 1100 | 730 => Some(KeyCode::BTN_TRIGGER_HAPPY27), 1101 | 731 => Some(KeyCode::BTN_TRIGGER_HAPPY28), 1102 | 732 => Some(KeyCode::BTN_TRIGGER_HAPPY29), 1103 | 733 => Some(KeyCode::BTN_TRIGGER_HAPPY30), 1104 | 734 => Some(KeyCode::BTN_TRIGGER_HAPPY31), 1105 | 735 => Some(KeyCode::BTN_TRIGGER_HAPPY32), 1106 | 736 => Some(KeyCode::BTN_TRIGGER_HAPPY33), 1107 | 737 => Some(KeyCode::BTN_TRIGGER_HAPPY34), 1108 | 738 => Some(KeyCode::BTN_TRIGGER_HAPPY35), 1109 | 739 => Some(KeyCode::BTN_TRIGGER_HAPPY36), 1110 | 740 => Some(KeyCode::BTN_TRIGGER_HAPPY37), 1111 | 741 => Some(KeyCode::BTN_TRIGGER_HAPPY38), 1112 | 742 => Some(KeyCode::BTN_TRIGGER_HAPPY39), 1113 | 743 => Some(KeyCode::BTN_TRIGGER_HAPPY40), 1114 | _ => None, 1115 | } 1116 | } 1117 | } 1118 | 1119 | impl TryFrom for KeyCode { 1120 | type Error = (); 1121 | fn try_from(item: usize) -> Result { 1122 | match Self::from_u32(item as u32) { 1123 | Some(kc) => Ok(kc), 1124 | _ => Err(()), 1125 | } 1126 | } 1127 | } 1128 | 1129 | impl From for KeyCode { 1130 | fn from(item: u32) -> Self { 1131 | Self::from_u32(item).expect(&format!("Invalid KeyCode: {}", item)) 1132 | } 1133 | } 1134 | 1135 | impl From for usize { 1136 | fn from(item: KeyCode) -> Self { 1137 | item as usize 1138 | } 1139 | } 1140 | 1141 | impl From for KeyCode { 1142 | fn from(item: EventCode) -> Self { 1143 | match item { 1144 | EventCode::EV_KEY(evkey) => Self::from(evkey), 1145 | _ => unreachable!(), 1146 | } 1147 | } 1148 | } 1149 | 1150 | impl From for EventCode { 1151 | fn from(item: KeyCode) -> Self { 1152 | let evkey = item.into(); 1153 | EventCode::EV_KEY(evkey) 1154 | } 1155 | } 1156 | 1157 | impl From for KeyCode { 1158 | fn from(item: EV_KEY) -> Self { 1159 | (item as u32).into() 1160 | } 1161 | } 1162 | 1163 | impl From for EV_KEY { 1164 | fn from(item: KeyCode) -> Self { 1165 | evdev_rs::enums::int_to_ev_key(item as u32) 1166 | .expect(&format!("Invalid KeyCode: {}", item as u32)) 1167 | } 1168 | } 1169 | 1170 | // ------------------ KeyValue -------------------- 1171 | 1172 | #[derive(Copy, Clone, Debug, Deserialize, PartialEq, Eq)] 1173 | pub enum KeyValue { 1174 | Release = 0, 1175 | Press = 1, 1176 | Repeat = 2, 1177 | } 1178 | 1179 | impl From for KeyValue { 1180 | fn from(item: i32) -> Self { 1181 | match item { 1182 | 0 => Self::Release, 1183 | 1 => Self::Press, 1184 | 2 => Self::Repeat, 1185 | _ => unreachable!(), 1186 | } 1187 | } 1188 | } 1189 | 1190 | // ------------------ KeyEvent -------------------- 1191 | 1192 | pub struct KeyEvent { 1193 | pub time: TimeVal, 1194 | pub code: KeyCode, 1195 | pub value: KeyValue, 1196 | } 1197 | 1198 | impl KeyEvent { 1199 | pub fn new(code: KeyCode, value: KeyValue) -> Self { 1200 | let time = TimeVal::new(0, 0); 1201 | Self { time, code, value } 1202 | } 1203 | 1204 | #[cfg(test)] 1205 | pub fn new_press(code: KeyCode) -> Self { 1206 | Self::new(code, KeyValue::Press) 1207 | } 1208 | 1209 | #[cfg(test)] 1210 | pub fn new_release(code: KeyCode) -> Self { 1211 | Self::new(code, KeyValue::Release) 1212 | } 1213 | } 1214 | 1215 | impl TryFrom for KeyEvent { 1216 | type Error = (); 1217 | fn try_from(item: InputEvent) -> Result { 1218 | match &item.event_type { 1219 | EventType::EV_KEY => Ok(Self { 1220 | time: item.time, 1221 | code: item.event_code.into(), 1222 | value: item.value.into(), 1223 | }), 1224 | _ => Err(()), 1225 | } 1226 | } 1227 | } 1228 | 1229 | impl From for InputEvent { 1230 | fn from(item: KeyEvent) -> Self { 1231 | Self { 1232 | time: item.time, 1233 | event_type: EventType::EV_KEY, 1234 | event_code: item.code.into(), 1235 | value: item.value as i32, 1236 | } 1237 | } 1238 | } 1239 | --------------------------------------------------------------------------------