diff --git a/README.md b/README.md index 9de2ca9..6691ff5 100644 --- a/README.md +++ b/README.md @@ -143,6 +143,9 @@ keymap: MOD2-KEY_YYY: MOD3-KEY_ZZZ # key press (MOD1-KEY_XXX) -> sequence (MOD2-KEY_YYY, MOD3-KEY_ZZZ) MOD1-KEY_XXX: [MOD2-KEY_YYY, MOD3-KEY_ZZZ] + # execute a command + MOD1-KEY_XXX: + launch: ["bash", "-c", "echo hello > /tmp/test"] application: # Optional not: [Application, ...] # or diff --git a/src/config/action.rs b/src/config/action.rs index 22aa2ef..e343ece 100644 --- a/src/config/action.rs +++ b/src/config/action.rs @@ -12,6 +12,8 @@ pub enum Action { KeyPress(KeyPress), #[serde(deserialize_with = "deserialize_remap")] Remap(HashMap>), + #[serde(deserialize_with = "deserialize_launch")] + Launch(Vec), } fn deserialize_remap<'de, D>(deserializer: D) -> Result>, D::Error> @@ -27,6 +29,19 @@ where Err(de::Error::custom("not a map with a single \"remap\" key")) } +fn deserialize_launch<'de, D>(deserializer: D) -> Result, D::Error> +where + D: Deserializer<'de>, +{ + let mut action = HashMap::>::deserialize(deserializer)?; + if let Some(launch) = action.remove("launch") { + if action.is_empty() { + return Ok(launch); + } + } + Err(de::Error::custom("not a map with a single \"launch\" key")) +} + // Used only for deserializing Vec #[derive(Deserialize)] #[serde(untagged)] diff --git a/src/config/tests.rs b/src/config/tests.rs index 1337ba6..a0706b8 100644 --- a/src/config/tests.rs +++ b/src/config/tests.rs @@ -101,6 +101,19 @@ fn test_keymap_remap() { "}) } +#[test] +fn test_keymap_launch() { + assert_parse(indoc! {r#" + keymap: + - remap: + KEY_GRAVE: + launch: + - "/bin/sh" + - "-c" + - "date > /tmp/hotkey_test" + "#}) +} + fn assert_parse(yaml: &str) { let result: Result = serde_yaml::from_str(&yaml); if let Err(e) = result { diff --git a/src/event_handler.rs b/src/event_handler.rs index 7def18d..3843ca9 100644 --- a/src/event_handler.rs +++ b/src/event_handler.rs @@ -7,35 +7,40 @@ use crate::Config; use evdev::uinput::VirtualDevice; use evdev::{EventType, InputEvent, Key}; use lazy_static::lazy_static; -use log::debug; +use log::{debug, error}; +use nix::sys::signal; +use nix::sys::signal::{sigaction, SaFlags, SigAction, SigHandler, SigSet}; use std::collections::HashMap; use std::error::Error; +use std::process::{Command, Stdio}; use std::time::Instant; pub struct EventHandler { device: VirtualDevice, - wm_client: WMClient, - multi_purpose_keys: HashMap, - override_remap: Option>>, - application_cache: Option, shift: PressState, control: PressState, alt: PressState, windows: PressState, + wm_client: WMClient, + application_cache: Option, + multi_purpose_keys: HashMap, + override_remap: Option>>, + sigaction_set: bool, } impl EventHandler { pub fn new(device: VirtualDevice) -> EventHandler { EventHandler { device, - wm_client: build_client(), - multi_purpose_keys: HashMap::new(), - override_remap: None, - application_cache: None, shift: PressState::new(false), control: PressState::new(false), alt: PressState::new(false), windows: PressState::new(false), + wm_client: build_client(), + application_cache: None, + multi_purpose_keys: HashMap::new(), + override_remap: None, + sigaction_set: false, } } @@ -208,10 +213,34 @@ impl EventHandler { } self.override_remap = Some(override_remap) } + Action::Launch(command) => self.run_command(command.clone()), } Ok(()) } + pub fn run_command(&mut self, command: Vec) { + if !self.sigaction_set { + // Avoid defunct processes + let sig_action = SigAction::new(SigHandler::SigDfl, SaFlags::SA_NOCLDWAIT, SigSet::empty()); + unsafe { + sigaction(signal::SIGCHLD, &sig_action).expect("Failed to register SIGCHLD handler"); + } + self.sigaction_set = true; + } + + debug!("Running command: {:?}", command); + match Command::new(&command[0]) + .args(&command[1..]) + .stdin(Stdio::null()) + .stdout(Stdio::null()) + .stderr(Stdio::null()) + .spawn() + { + Ok(child) => debug!("Process spawned: {:?}, pid {}", command, child.id()), + Err(e) => error!("Error running command: {:?}", e), + } + } + fn send_modifier(&mut self, modifier: Modifier, desired: &PressState) -> Result> { let mut current = match modifier { Modifier::Shift => &self.shift,