Merge pull request #52 from k0kubun/launch-action

Add launch action
This commit is contained in:
Takashi Kokubun 2021-12-30 13:37:39 -08:00 committed by GitHub
commit 67cff94344
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 69 additions and 9 deletions

View File

@ -143,6 +143,9 @@ keymap:
MOD2-KEY_YYY: MOD3-KEY_ZZZ MOD2-KEY_YYY: MOD3-KEY_ZZZ
# key press (MOD1-KEY_XXX) -> sequence (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] MOD1-KEY_XXX: [MOD2-KEY_YYY, MOD3-KEY_ZZZ]
# execute a command
MOD1-KEY_XXX:
launch: ["bash", "-c", "echo hello > /tmp/test"]
application: # Optional application: # Optional
not: [Application, ...] not: [Application, ...]
# or # or

View File

@ -12,6 +12,8 @@ pub enum Action {
KeyPress(KeyPress), KeyPress(KeyPress),
#[serde(deserialize_with = "deserialize_remap")] #[serde(deserialize_with = "deserialize_remap")]
Remap(HashMap<KeyPress, Vec<Action>>), Remap(HashMap<KeyPress, Vec<Action>>),
#[serde(deserialize_with = "deserialize_launch")]
Launch(Vec<String>),
} }
fn deserialize_remap<'de, D>(deserializer: D) -> Result<HashMap<KeyPress, Vec<Action>>, D::Error> fn deserialize_remap<'de, D>(deserializer: D) -> Result<HashMap<KeyPress, Vec<Action>>, D::Error>
@ -27,6 +29,19 @@ where
Err(de::Error::custom("not a map with a single \"remap\" key")) Err(de::Error::custom("not a map with a single \"remap\" key"))
} }
fn deserialize_launch<'de, D>(deserializer: D) -> Result<Vec<String>, D::Error>
where
D: Deserializer<'de>,
{
let mut action = HashMap::<String, Vec<String>>::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<Action> // Used only for deserializing Vec<Action>
#[derive(Deserialize)] #[derive(Deserialize)]
#[serde(untagged)] #[serde(untagged)]

View File

@ -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) { fn assert_parse(yaml: &str) {
let result: Result<Config, Error> = serde_yaml::from_str(&yaml); let result: Result<Config, Error> = serde_yaml::from_str(&yaml);
if let Err(e) = result { if let Err(e) = result {

View File

@ -7,35 +7,40 @@ use crate::Config;
use evdev::uinput::VirtualDevice; use evdev::uinput::VirtualDevice;
use evdev::{EventType, InputEvent, Key}; use evdev::{EventType, InputEvent, Key};
use lazy_static::lazy_static; 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::collections::HashMap;
use std::error::Error; use std::error::Error;
use std::process::{Command, Stdio};
use std::time::Instant; use std::time::Instant;
pub struct EventHandler { pub struct EventHandler {
device: VirtualDevice, device: VirtualDevice,
wm_client: WMClient,
multi_purpose_keys: HashMap<Key, MultiPurposeKeyState>,
override_remap: Option<HashMap<KeyPress, Vec<Action>>>,
application_cache: Option<String>,
shift: PressState, shift: PressState,
control: PressState, control: PressState,
alt: PressState, alt: PressState,
windows: PressState, windows: PressState,
wm_client: WMClient,
application_cache: Option<String>,
multi_purpose_keys: HashMap<Key, MultiPurposeKeyState>,
override_remap: Option<HashMap<KeyPress, Vec<Action>>>,
sigaction_set: bool,
} }
impl EventHandler { impl EventHandler {
pub fn new(device: VirtualDevice) -> EventHandler { pub fn new(device: VirtualDevice) -> EventHandler {
EventHandler { EventHandler {
device, device,
wm_client: build_client(),
multi_purpose_keys: HashMap::new(),
override_remap: None,
application_cache: None,
shift: PressState::new(false), shift: PressState::new(false),
control: PressState::new(false), control: PressState::new(false),
alt: PressState::new(false), alt: PressState::new(false),
windows: 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) self.override_remap = Some(override_remap)
} }
Action::Launch(command) => self.run_command(command.clone()),
} }
Ok(()) Ok(())
} }
pub fn run_command(&mut self, command: Vec<String>) {
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<PressState, Box<dyn Error>> { fn send_modifier(&mut self, modifier: Modifier, desired: &PressState) -> Result<PressState, Box<dyn Error>> {
let mut current = match modifier { let mut current = match modifier {
Modifier::Shift => &self.shift, Modifier::Shift => &self.shift,