From 78179cc8366639500b36518ac12bd4323882151c Mon Sep 17 00:00:00 2001 From: Tait Hoyem Date: Fri, 18 Feb 2022 16:56:14 -0700 Subject: [PATCH] Add modal keybindings --- Cargo.toml | 8 +++-- src/config.rs | 45 +++++++++++++++++++--------- src/daemon.rs | 83 ++++++++++++++++++++++++++++++++++++++++++++++++--- 3 files changed, 115 insertions(+), 21 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index da090ea..8f92bc5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,11 +1,12 @@ [package] -name = "Simple-Wayland-HotKey-Daemon" -version = "1.0.0" +name = "Simple-Odilia-HotKey-Daemon" +version = "1.0.1" edition = "2021" authors = [ "Shinyzenith \n", "Angelo Fallaria \n", - "EdenQwQ \n" + "EdenQwQ \n", + "Tait Hoyem \n" ] [dependencies] @@ -15,6 +16,7 @@ evdev = "0.11.3" log = "0.4.0" nix = "0.23.1" sysinfo = "0.23.0" +once_cell = "^1.9.0" [[bin]] name = "swhkd" diff --git a/src/config.rs b/src/config.rs index 62e9f02..232e929 100644 --- a/src/config.rs +++ b/src/config.rs @@ -52,6 +52,7 @@ impl fmt::Display for Error { #[derive(Debug, Clone, PartialEq)] pub struct Hotkey { + pub mode: String, pub keysym: evdev::Key, pub modifiers: Vec, pub command: String, @@ -76,8 +77,8 @@ pub enum Modifier { } impl Hotkey { - pub fn new(keysym: evdev::Key, modifiers: Vec, command: String) -> Self { - Hotkey { keysym, modifiers, command } + pub fn new(mode: String, keysym: evdev::Key, modifiers: Vec, command: String) -> Self { + Hotkey { mode, keysym, modifiers, command } } } @@ -281,9 +282,9 @@ fn parse_contents(contents: String) -> Result, Error> { 'hotkey_parse: for (key, command) in extracted_keys.iter().zip(extracted_commands.iter()) { println!("{} {}", key, command); - let (keysym, modifiers) = + let (mode, keysym, modifiers) = parse_keybind(key, line_number + 1, &key_to_evdev_key, &mod_to_mod_enum)?; - let hotkey = Hotkey { keysym, modifiers, command: command.to_string() }; + let hotkey = Hotkey { mode, keysym, modifiers, command: command.to_string() }; // Ignore duplicate hotkeys for i in hotkeys.iter() { @@ -306,7 +307,7 @@ fn parse_keybind( line_nr: u32, key_to_evdev_key: &HashMap<&str, evdev::Key>, mod_to_mod_enum: &HashMap<&str, Modifier>, -) -> Result<(evdev::Key, Vec), Error> { +) -> Result<(String, evdev::Key, Vec), Error> { let line = line.split('#').next().unwrap(); let tokens: Vec = line.split('+').map(|s| s.trim().to_lowercase()).filter(|s| s != "_").collect(); @@ -314,6 +315,9 @@ fn parse_keybind( // Check if each token is valid for token in &tokens { + if token.starts_with("[") && token.ends_with("]") { + continue; + } if key_to_evdev_key.contains_key(token.as_str()) { // Can't have a key that's like a modifier if token != last_token { @@ -331,13 +335,22 @@ fn parse_keybind( // Translate keypress into evdev key let keysym = key_to_evdev_key.get(last_token).unwrap(); + let mode = tokens + .iter() + .filter(|s| s.starts_with("[") && s.ends_with("]")) + .map(|s| s.replace(&['[', ']'][..], "")) + .collect::>() + .get(0) + .unwrap_or(&"".to_string()) + .to_string(); + let mod_index = if mode.is_empty() { 0 } else { 1 }; - let modifiers: Vec = tokens[0..(tokens.len() - 1)] + let modifiers: Vec = tokens[mod_index..(tokens.len() - 1)] .iter() .map(|token| *mod_to_mod_enum.get(token.as_str()).unwrap()) .collect(); - Ok((*keysym, modifiers)) + Ok((mode, *keysym, modifiers)) } fn extract_curly_brace(line: &str) -> Vec { @@ -561,7 +574,7 @@ r eval_config_test( contents, - vec![Hotkey::new(evdev::Key::KEY_R, vec![], String::from("alacritty"))], + vec![Hotkey::new("".to_string(), evdev::Key::KEY_R, vec![], String::from("alacritty"))], ) } @@ -578,9 +591,9 @@ t /bin/firefox "; - let hotkey_1 = Hotkey::new(evdev::Key::KEY_R, vec![], String::from("alacritty")); - let hotkey_2 = Hotkey::new(evdev::Key::KEY_W, vec![], String::from("kitty")); - let hotkey_3 = Hotkey::new(evdev::Key::KEY_T, vec![], String::from("/bin/firefox")); + let hotkey_1 = Hotkey::new("".to_string(), evdev::Key::KEY_R, vec![], String::from("alacritty")); + let hotkey_2 = Hotkey::new("".to_string(), evdev::Key::KEY_W, vec![], String::from("kitty")); + let hotkey_3 = Hotkey::new("".to_string(), evdev::Key::KEY_T, vec![], String::from("/bin/firefox")); eval_config_test(contents, vec![hotkey_1, hotkey_2, hotkey_3]) } @@ -599,8 +612,8 @@ w "; let expected_keybinds = vec![ - Hotkey::new(evdev::Key::KEY_R, vec![], String::from("alacritty")), - Hotkey::new(evdev::Key::KEY_W, vec![], String::from("kitty")), + Hotkey::new("".to_string(), evdev::Key::KEY_R, vec![], String::from("alacritty")), + Hotkey::new("".to_string(), evdev::Key::KEY_W, vec![], String::from("kitty")), ]; eval_config_test(contents, expected_keybinds) @@ -614,7 +627,7 @@ super + 5 "; let expected_keybinds = - vec![Hotkey::new(evdev::Key::KEY_5, vec![Modifier::Super], String::from("alacritty"))]; + vec![Hotkey::new("".to_string(), evdev::Key::KEY_5, vec![Modifier::Super], String::from("alacritty"))]; eval_config_test(contents, expected_keybinds) } @@ -679,21 +692,25 @@ super + z let expected_hotkeys = vec![ Hotkey::new( + "".to_string(), evdev::Key::KEY_K, vec![Modifier::Shift], "notify-send 'Hello world!'".to_string(), ), Hotkey::new( + "".to_string(), evdev::Key::KEY_5, vec![Modifier::Control], "notify-send 'Hello world!'".to_string(), ), Hotkey::new( + "".to_string(), evdev::Key::KEY_2, vec![Modifier::Alt], "notify-send 'Hello world!'".to_string(), ), Hotkey::new( + "".to_string(), evdev::Key::KEY_Z, vec![Modifier::Super], "notify-send 'Hello world!'".to_string(), diff --git a/src/daemon.rs b/src/daemon.rs index 2856bf1..7785d39 100644 --- a/src/daemon.rs +++ b/src/daemon.rs @@ -1,11 +1,15 @@ use clap::{arg, App}; use evdev::{AttributeSet, Device, Key}; use nix::unistd::{Group, Uid}; +use once_cell::sync::OnceCell; use std::{ + thread, + sync::Mutex, collections::HashMap, env, fs, io::prelude::*, os::unix::net::UnixStream, + os::unix::net::UnixListener, path::Path, process::{exit, id}, thread::sleep, @@ -22,7 +26,39 @@ pub struct LastHotkey { ran_at: SystemTime, } -pub fn main() { +static MODE_SOCK: &str = "/tmp/odilia-mode.sock"; +static STATE_MODE: OnceCell> = OnceCell::new(); + +fn listen_for_mode_change() -> std::io::Result<()> { + log::trace!("Mode change listener started!"); + let listener = UnixListener::bind(MODE_SOCK)?; + match listener.accept() { + Ok((mut socket, _addr)) => { + let mut response = String::new(); + socket.read_to_string(&mut response)?; + log::trace!("reponse from odilia-mode.sock: '{}'", response); + let mode_m = STATE_MODE.get(); + match mode_m { + Some(mm) => { + let mut mode = mm.lock(); + match mode { + Ok(mut m) => { + *m = response; + println!("Mode updated to '{}'", m); + log::trace!("Mode updated to '{}'", m); + }, + Err(e) => log::error!("Could not lock mode."), + } + }, + _ => log::error!("Could not get static STATE_MODE."), + } + }, + Err(e) => log::error!("accept function failed: {:?}", e), + } + Ok(()) +} + +pub fn key_listener() { let args = set_flags().get_matches(); env::set_var("RUST_LOG", "swhkd=warn"); @@ -30,12 +66,16 @@ pub fn main() { env::set_var("RUST_LOG", "swhkd=trace"); } - env_logger::init(); log::trace!("Logger initialized."); + match STATE_MODE.set(Mutex::new(String::new())) { + Err(e) => log::error!("could not initialize STATE_MODE"), + _ => log::trace!("STATE_MODE initialized"), + } + let pidfile: String = String::from("/tmp/swhkd.pid"); if Path::new(&pidfile).exists() { - log::trace!("Reading {} file and checking for running instances.", pidfile); + log::trace!("Reading {} file and checking for running instances.", pidfile); let swhkd_pid = match fs::read_to_string(&pidfile) { Ok(swhkd_pid) => swhkd_pid, Err(e) => { @@ -131,6 +171,7 @@ pub fn main() { let mut last_hotkey = LastHotkey { // just a dummy last_hotkey so I don't need to mess with Option. TODO: Change this to Option hotkey: config::Hotkey::new( + "".to_string(), evdev::Key::KEY_A, default_test_modifier, String::from("notify-send \"it works\""), @@ -158,6 +199,21 @@ pub fn main() { let mut state_modifiers: Vec = Vec::new(); let mut state_keysyms: Vec = Vec::new(); + let state_mode: String = match STATE_MODE.get() { + Some(mm) => { + match mm.lock() { + Ok(m) => m.to_string(), + Err(e) => { + log::error!("Could not lock state_mode within main event loop."); + "".to_string() + }, + } + } + None => { + log::error!("Could not get STATE_MODE in main event loop."); + "".to_string() + } + }; for key in state.iter() { if let Some(modifier) = modifiers_map.get(&key) { state_modifiers.push(*modifier); @@ -167,12 +223,14 @@ pub fn main() { } log::debug!("state_modifiers: {:#?}", state_modifiers); log::debug!("state_keysyms: {:#?}", state_keysyms); + log::debug!("state_mode: {:#?}", state_mode); log::debug!("hotkey: {:#?}", possible_hotkeys); for hotkey in &possible_hotkeys { // this should check if state_modifiers and hotkey.modifiers have the same elements if state_modifiers.iter().all(|x| hotkey.modifiers.contains(x)) && state_modifiers.len() == hotkey.modifiers.len() && state_keysyms.contains(&hotkey.keysym) + && state_mode == hotkey.mode { if last_hotkey.hotkey == hotkey.clone() { let time_since_ran_at = @@ -214,6 +272,24 @@ pub fn main() { sleep(Duration::from_millis(10)); // without this, swhkd will start to chew through your cpu. } } + +pub fn main() { + env_logger::init(); + let key_handler = thread::spawn(|| { + key_listener(); + }); + let mode_updater = thread::spawn(|| { + let res = listen_for_mode_change(); + match res { + Ok(listener) => log::trace!("Finished mode changer!"), + Err(e) => log::error!("Error setting up mode socket: {}", e), + } + }); + log::trace!("Threads set up"); + mode_updater.join().unwrap(); + key_handler.join().unwrap(); +} + pub fn permission_check() { if !Uid::current().is_root() { let groups = nix::unistd::getgroups(); @@ -227,7 +303,6 @@ pub fn permission_check() { } } log::error!("Consider using `pkexec swhkd ...`"); - exit(1); } else { log::warn!("Running swhkd as root!"); }