#![allow(clippy::new_without_default)]
#![allow(clippy::comparison_to_empty)]
#![allow(clippy::collapsible_if)]
#![allow(clippy::match_like_matches_macro)]
#![warn(clippy::explicit_iter_loop)]
#![warn(clippy::explicit_into_iter_loop)]
pub mod capability;
pub mod control_fifo;
pub mod daemon;
pub mod domain;
pub mod ecodes;
pub mod error;
pub mod event;
pub mod key;
pub mod loopback;
pub mod predevice;
pub mod range;
pub mod signal;
pub mod state;
pub mod stream;
pub mod subprocess;
pub mod utils;
#[cfg(feature = "auto-scan")]
pub mod scancodes;
pub mod io {
pub mod epoll;
pub mod fd;
pub mod fifo;
pub mod input;
pub mod internal_pipe;
pub mod output;
}
pub mod persist {
pub mod blueprint;
pub mod inotify;
pub mod interface;
pub mod subsystem;
}
pub mod arguments {
pub mod control_fifo;
pub mod delay;
pub mod hook;
pub mod input;
pub mod lib;
pub mod map;
pub mod merge;
pub mod output;
pub mod parser;
pub mod print;
pub mod test;
pub mod toggle;
pub mod withhold;
}
pub mod bindings {
#[allow(warnings)]
pub mod libevdev;
}
#[macro_use]
extern crate lazy_static;
use std::os::unix::prelude::{AsRawFd, RawFd};
use arguments::parser::Implementation;
use control_fifo::ControlFifo;
use error::{Context, RuntimeError};
use io::epoll::{Epoll, FileIndex, Message};
use io::fd::HasFixedFd;
use io::input::InputDevice;
use persist::interface::HostInterfaceState;
use signal::{SigMask, SignalFd};
use stream::Setup;
use crate::event::EventCode;
use crate::persist::subsystem::Report;
use crate::predevice::PersistMode;
fn main() {
let result = run_and_interpret_exit_code();
daemon::await_completion();
subprocess::terminate_all();
std::process::exit(result)
}
fn run_and_interpret_exit_code() -> i32 {
let result = std::panic::catch_unwind(run);
match result {
Ok(Ok(())) => 0,
Ok(Err(error)) => {
eprintln!("{}", error);
1
}
Err(_) => {
eprintln!("Internal error: a panic happened. This is a bug.");
1
}
}
}
pub enum Pollable {
InputDevice(InputDevice),
SignalFd(SignalFd),
ControlFifo(ControlFifo),
PersistSubsystem(persist::interface::HostInterface),
}
unsafe impl HasFixedFd for Pollable {}
impl AsRawFd for Pollable {
fn as_raw_fd(&self) -> RawFd {
match self {
Pollable::InputDevice(device) => device.as_raw_fd(),
Pollable::SignalFd(fd) => fd.as_raw_fd(),
Pollable::ControlFifo(fifo) => fifo.as_raw_fd(),
Pollable::PersistSubsystem(interface) => interface.as_raw_fd(),
}
}
}
struct Program {
epoll: Epoll<Pollable>,
setup: Setup,
persist_subsystem: HostInterfaceState,
}
const TERMINATION_SIGNALS: [libc::c_int; 3] = [libc::SIGTERM, libc::SIGINT, libc::SIGHUP];
fn run() -> Result<(), RuntimeError> {
let args: Vec<String> = std::env::args().collect();
if arguments::parser::check_help_and_version(&args) {
daemon::notify_ready_async();
return Ok(());
}
let mut sigmask = SigMask::new();
sigmask.add(libc::SIGPIPE);
for &signal in &TERMINATION_SIGNALS {
sigmask.add(signal);
}
let signal_fd = signal::SignalFd::new(&sigmask)?;
let mut epoll = Epoll::new()?;
epoll.add_file(Pollable::SignalFd(signal_fd))?;
sigmask.add(libc::SIGCHLD);
let _signal_block = unsafe { signal::SignalBlock::new(&sigmask)? };
let Implementation {
setup,
input_devices,
control_fifos,
} = arguments::parser::implement(args)?;
for device in input_devices {
epoll.add_file(Pollable::InputDevice(device))?;
}
for fifo in control_fifos {
epoll.add_file(Pollable::ControlFifo(fifo))?;
}
let persist_subsystem: HostInterfaceState = HostInterfaceState::new();
let mut program = Program {
epoll,
setup,
persist_subsystem,
};
daemon::notify_ready_async();
if has_no_activity(&program.epoll) {
println!("Warning: no input devices available. Evsieve will exit now.");
return Ok(());
}
enter_main_loop(&mut program)?;
program.persist_subsystem.await_shutdown(&mut program.epoll);
Ok(())
}
enum Action {
Continue,
Exit,
}
fn enter_main_loop(program: &mut Program) -> Result<(), RuntimeError> {
loop {
let timeout: i32 = match program.setup.time_until_next_wakeup() {
loopback::Delay::Now => {
stream::wakeup(&mut program.setup);
continue;
}
loopback::Delay::Never => crate::io::epoll::INDEFINITE_TIMEOUT,
loopback::Delay::Wait(time) => time.get(),
};
let messages = program
.epoll
.poll(timeout)
.with_context("While polling the epoll for events:")?;
for message in messages {
let action = match message {
Message::Ready(index) => match handle_ready_file(program, index) {
Ok(action) => action,
Err(error) => {
error.print_err();
handle_broken_file(program, index)
}
},
Message::Broken(index) => handle_broken_file(program, index),
Message::Hup(index) => {
match program.epoll.get(index) {
Some(Pollable::ControlFifo(_)) => Action::Continue,
_ => handle_broken_file(program, index),
}
}
};
match action {
Action::Continue => continue,
Action::Exit => return Ok(()),
}
}
}
}
fn handle_ready_file(program: &mut Program, index: FileIndex) -> Result<Action, RuntimeError> {
let file = match program.epoll.get_mut(index) {
Some(file) => file,
None => {
eprintln!("Internal error: an epoll reported ready on a device that is not registered with it. This is a bug.");
return Ok(Action::Continue);
}
};
match file {
Pollable::InputDevice(device) => {
let events = device.poll().with_context_of(|| {
format!(
"While polling the input device {}:",
device.path().display()
)
})?;
for event in events {
stream::run(&mut program.setup, event);
}
Ok(Action::Continue)
}
Pollable::SignalFd(fd) => {
let siginfo = fd.read_raw()?;
let signal_no = siginfo.ssi_signo as i32;
if TERMINATION_SIGNALS.contains(&signal_no) {
Ok(Action::Exit)
} else {
Ok(Action::Continue)
}
}
Pollable::ControlFifo(fifo) => {
let commands = fifo
.poll()
.with_context_of(|| format!("While polling commands from {}:", fifo.path()))?;
for command in commands {
command
.execute(&mut program.setup)
.with_context("While executing a command:")
.print_err();
}
Ok(Action::Continue)
}
Pollable::PersistSubsystem(ref mut interface) => {
let report = interface
.recv()
.with_context("While polling the persistence subsystem from the main thread:")?;
Ok(handle_persist_subsystem_report(program, index, report))
}
}
}
fn handle_broken_file(program: &mut Program, index: FileIndex) -> Action {
let broken_device = match program.epoll.remove(index) {
Some(file) => file,
None => {
eprintln!("Internal error: epoll reported a file as broken despite that file not being registered with said epoll.");
return Action::Continue;
}
};
match broken_device {
Pollable::InputDevice(mut device) => {
eprintln!(
"The device {} has been disconnected.",
device.path().display()
);
let pressed_keys: Vec<EventCode> = device.get_pressed_keys().collect();
for key_code in pressed_keys {
let release_event = device.synthesize_event(key_code, 0);
stream::run(&mut program.setup, release_event);
}
stream::syn(&mut program.setup);
match device.persist_mode() {
PersistMode::None => {}
PersistMode::Exit => return Action::Exit,
PersistMode::Reopen => {
if let Some(interface) = program.persist_subsystem.require(&mut program.epoll) {
interface
.add_blueprint(device.to_blueprint())
.with_context(
"While trying to register a disconnected device for reopening:",
)
.print_err()
} else {
eprintln!("Internal error: cannot reopen device: persistence subsystem not available.")
}
}
};
}
Pollable::ControlFifo(fifo) => {
eprintln!("Error: the FIFO at {} is no longer available.", fifo.path());
}
Pollable::SignalFd(_fd) => {
eprintln!("Fatal error: signal file descriptor broken.");
return Action::Exit;
}
Pollable::PersistSubsystem(mut interface) => {
eprintln!("Internal error: the persistence subsystem has broken. Evsieve may fail to open devices specified with the persist flag.");
let _ = interface.request_shutdown();
program.persist_subsystem.mark_as_broken();
}
}
if has_no_activity(&program.epoll) {
println!("No devices to poll events from. Evsieve will exit now.");
Action::Exit
} else {
Action::Continue
}
}
fn handle_persist_subsystem_report(
program: &mut Program,
index: FileIndex,
report: Report,
) -> Action {
match report {
Report::Shutdown => {
let _ = program.epoll.remove(index);
program.persist_subsystem.mark_as_shutdown();
Action::Continue
}
Report::BlueprintDropped => {
if has_no_activity(&program.epoll) {
println!("No devices remaining that can possibly generate events. Evsieve will exit now.");
Action::Exit
} else {
Action::Continue
}
}
Report::DeviceOpened(mut device) => {
if let Err(error) = device.grab_if_desired() {
error
.with_context(format!(
"While grabbing the device {}:",
device.path().display()
))
.print_err();
eprintln!("Warning: unable to reopen device {}. The device is most likely grabbed by another program.", device.path().display());
return Action::Continue;
}
let device_path = device.path().to_owned();
program.setup.update_caps(&device);
match program.epoll.add_file(Pollable::InputDevice(device)) {
Ok(_) => println!("The device {} has been reconnected.", device_path.display()),
Err(error) => {
error
.with_context("While adding a newly opened device to the epoll:")
.print_err();
}
}
Action::Continue
}
}
}
fn has_no_activity(epoll: &Epoll<Pollable>) -> bool {
for file in epoll.files() {
match file {
Pollable::InputDevice(_) => return false,
Pollable::PersistSubsystem(_) => return false,
Pollable::ControlFifo(_) => (),
Pollable::SignalFd(_) => (),
}
}
true
}