You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
xremap/src/action_dispatcher.rs

135 lines
5.6 KiB
Rust

use std::thread;
use evdev::{uinput::VirtualDevice, EventType, InputEvent, Key};
use fork::{fork, setsid, Fork};
use log::debug;
use log::error;
use nix::sys::signal;
use nix::sys::signal::{sigaction, SaFlags, SigAction, SigHandler, SigSet};
use std::process::{exit, Command, Stdio};
use crate::event::RelativeEvent;
use crate::{action::Action, event::KeyEvent};
pub struct ActionDispatcher {
// Device to emit events
device: VirtualDevice,
// Whether we've called a sigaction for spawing commands or not
sigaction_set: bool,
}
impl ActionDispatcher {
pub fn new(device: VirtualDevice) -> ActionDispatcher {
ActionDispatcher {
device,
sigaction_set: false,
}
}
// Execute Actions created by EventHandler. This should be the only public method of ActionDispatcher.
pub fn on_action(&mut self, action: Action) -> anyhow::Result<()> {
match action {
Action::KeyEvent(key_event) => self.on_key_event(key_event)?,
Action::RelativeEvent(relative_event) => self.on_relative_event(relative_event)?,
Action::MouseMovementEventCollection(mouse_movement_events) => {
// Sending all mouse movement events at once, unseparated by synchronization events.
self.send_mousemovement_event_batch(mouse_movement_events)?;
// Mouse movement events need to be sent all at once because they would otherwise be separated by a synchronization event¹,
// which the OS handles differently from two unseparated mouse movement events.
// For example,
// a REL_X event², followed by a SYNCHRONIZATION event, followed by a REL_Y event³, followed by a SYNCHRONIZATION event,
// will move the mouse cursor by a different amount than
// a REL_X event followed by a REL_Y event followed by a SYNCHRONIZATION event.
// ¹Because Xremap usually sends events one by one through evdev's "emit" function, which adds a synchronization event during each call.
// ²Mouse movement along the X (horizontal) axis.
// ³Mouse movement along the Y (vertical) axis.
}
Action::InputEvent(event) => self.send_event(event)?,
Action::Command(command) => self.run_command(command),
Action::Delay(duration) => thread::sleep(duration),
}
Ok(())
}
fn on_key_event(&mut self, event: KeyEvent) -> std::io::Result<()> {
let event = InputEvent::new_now(EventType::KEY, event.code(), event.value());
self.send_event(event)
}
fn on_relative_event(&mut self, event: RelativeEvent) -> std::io::Result<()> {
let event = InputEvent::new_now(EventType::RELATIVE, event.code, event.value);
self.send_event(event)
}
// a function that takes mouse movement events to send in a single batch, unseparated by synchronization events.
fn send_mousemovement_event_batch(&mut self, eventbatch: Vec<RelativeEvent>) -> std::io::Result<()> {
let mut mousemovementbatch: Vec<InputEvent> = Vec::new();
for mouse_movement in eventbatch {
mousemovementbatch.push(InputEvent::new_now(
EventType::RELATIVE,
mouse_movement.code,
mouse_movement.value,
));
}
self.device.emit(&mousemovementbatch)
}
fn send_event(&mut self, event: InputEvent) -> std::io::Result<()> {
if event.event_type() == EventType::KEY {
debug!("{}: {:?}", event.value(), Key::new(event.code()))
}
self.device.emit(&[event])
}
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 fork() {
Ok(Fork::Child) => {
// Child process should fork again, and the parent should exit 0, while the child
// should spawn the user command then exit as well.
match fork() {
Ok(Fork::Child) => {
setsid().expect("Failed to setsid.");
match Command::new(&command[0])
.args(&command[1..])
.stdin(Stdio::null())
.stdout(Stdio::null())
.stderr(Stdio::null())
.spawn()
{
Ok(child) => {
debug!("Process started: {:?}, pid {}", command, child.id());
exit(0);
}
Err(e) => {
error!("Error running command: {:?}", e);
exit(1);
}
}
}
Ok(Fork::Parent(_)) => exit(0),
Err(e) => {
error!("Error spawning process: {:?}", e);
exit(1);
}
}
}
// Parent should simply continue.
Ok(Fork::Parent(_)) => (),
Err(e) => error!("Error spawning process: {:?}", e),
}
}
}