Add modal keybindings

main
Tait Hoyem 2 years ago
parent dd8573c379
commit 78179cc836

@ -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 <aakashsensharma@gmail.com>\n",
"Angelo Fallaria <ba.fallaria@gmail.com>\n",
"EdenQwQ <lsahlm1eden@gmail.com>\n"
"EdenQwQ <lsahlm1eden@gmail.com>\n",
"Tait Hoyem <tait@tait.tech>\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"

@ -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<Modifier>,
pub command: String,
@ -76,8 +77,8 @@ pub enum Modifier {
}
impl Hotkey {
pub fn new(keysym: evdev::Key, modifiers: Vec<Modifier>, command: String) -> Self {
Hotkey { keysym, modifiers, command }
pub fn new(mode: String, keysym: evdev::Key, modifiers: Vec<Modifier>, command: String) -> Self {
Hotkey { mode, keysym, modifiers, command }
}
}
@ -281,9 +282,9 @@ fn parse_contents(contents: String) -> Result<Vec<Hotkey>, 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<Modifier>), Error> {
) -> Result<(String, evdev::Key, Vec<Modifier>), Error> {
let line = line.split('#').next().unwrap();
let tokens: Vec<String> =
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::<Vec<String>>()
.get(0)
.unwrap_or(&"".to_string())
.to_string();
let mod_index = if mode.is_empty() { 0 } else { 1 };
let modifiers: Vec<Modifier> = tokens[0..(tokens.len() - 1)]
let modifiers: Vec<Modifier> = 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<String> {
@ -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(),

@ -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<Mutex<String>> = 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<T>. TODO: Change this to Option<T>
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<config::Modifier> = Vec::new();
let mut state_keysyms: Vec<evdev::Key> = 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!");
}

Loading…
Cancel
Save