#![cfg_attr(target_os = "freebsd", allow(dead_code, unused_imports, unused_variables))]
use crate::client::print_open_windows;
use crate::config::Config;
use crate::device::{
choose_device_name, open_device, output_device, print_device_details, print_device_list, select_input_devices,
};
use crate::event_handler::EventHandler;
use crate::main_controller::MainController;
use crate::operator_handler::OperatorHandler;
use crate::throttle_emit::ThrottleEmit;
use crate::timeout_manager::TimeoutManager;
use action_dispatcher::ActionDispatcher;
use anyhow::{anyhow, bail, Context};
use clap::{CommandFactory, Parser, ValueEnum};
use clap_complete::Shell;
use device::InputDevice;
use event::Event;
use nix::libc::ENODEV;
use nix::sys::select::{select, FdSet};
use nix::sys::timerfd::{ClockId, TimerFd, TimerFlags};
use std::collections::HashMap;
use std::io::stdout;
use std::os::fd::{AsFd, RawFd};
use std::os::unix::io::AsRawFd;
use std::path::PathBuf;
use std::rc::Rc;
use std::time::Duration;
#[cfg(target_os = "linux")]
mod platform_linux;
#[cfg(target_os = "linux")]
use crate::platform_linux::{ConfigWatcher, DeviceWatcher};
#[cfg(target_os = "freebsd")]
mod platform_freebsd;
#[cfg(target_os = "freebsd")]
use crate::platform_freebsd::{ConfigWatcher, DeviceWatcher};
mod action;
mod action_dispatcher;
mod bridge;
mod client;
mod command_runner;
mod config;
mod device;
mod emit_handler;
mod event;
mod event_handler;
mod main_controller;
mod operator_double_tap;
mod operator_handler;
mod operator_sim;
mod operators;
#[cfg(test)]
mod tests;
#[cfg(test)]
mod tests_any_key;
#[cfg(test)]
mod tests_disguised_events_in;
#[cfg(test)]
mod tests_extra_modifiers;
#[cfg(test)]
mod tests_keymap_mark;
#[cfg(test)]
mod tests_keymap_mode;
#[cfg(test)]
mod tests_modmap_keys;
#[cfg(test)]
mod tests_modmap_mul_purpose;
#[cfg(test)]
mod tests_modmap_mul_purpose_tap_preferred;
#[cfg(test)]
mod tests_modmap_press_release_key;
#[cfg(test)]
mod tests_nested_remap;
#[cfg(test)]
mod tests_operator_double_tap;
#[cfg(test)]
mod tests_operator_handler;
#[cfg(test)]
mod tests_operator_sim;
#[cfg(test)]
mod tests_throttle_emit;
#[cfg(test)]
mod tests_virtual_modifier;
mod throttle_emit;
mod timeout_manager;
mod util;
#[derive(Parser, Debug)]
#[command(version)]
struct Args {
#[arg(long, value_delimiter = ',')]
device: Vec<String>,
#[arg(long, value_delimiter = ',')]
ignore: Vec<String>,
#[arg(long, verbatim_doc_comment)]
mouse: bool,
#[arg(long, value_enum, num_args = 0.., value_delimiter = ',', require_equals = true,
default_missing_value = "device", verbatim_doc_comment)]
watch: Vec<WatchTargets>,
#[arg(long, value_enum, display_order = 100, value_name = "SHELL", verbatim_doc_comment)]
completions: Option<Shell>,
#[arg(long)]
output_device_name: Option<String>,
#[arg(required_unless_present = "completions",
required_unless_present = "list_devices",
required_unless_present = "device_details",
required_unless_present = "list_windows",
required_unless_present = "bridge",
num_args = 1.., verbatim_doc_comment)]
configs: Vec<PathBuf>,
#[arg(long, verbatim_doc_comment)]
vendor: Option<String>,
#[arg(long, verbatim_doc_comment)]
product: Option<String>,
#[arg(long)]
list_devices: bool,
#[arg(long)]
device_details: bool,
#[arg(long, verbatim_doc_comment)]
list_windows: bool,
#[arg(long, verbatim_doc_comment)]
no_window_logging: bool,
#[arg(long)]
allow_launch: Option<bool>,
#[arg(long, verbatim_doc_comment)]
bridge: bool,
}
#[derive(ValueEnum, Clone, Copy, Debug, PartialEq, Eq)]
enum WatchTargets {
Device,
Config,
}
fn main() -> anyhow::Result<()> {
env_logger::init();
let Args {
device: device_filter,
ignore: ignore_filter,
mouse,
watch,
configs: config_paths,
completions,
output_device_name,
product,
vendor,
list_devices,
device_details,
list_windows,
no_window_logging,
allow_launch,
bridge,
} = Args::parse();
if let Some(shell) = completions {
clap_complete::generate(shell, &mut Args::command(), "xremap", &mut stdout());
return Ok(());
}
if device_details {
print_device_details()?;
return Ok(());
}
if list_devices {
print_device_list()?;
return Ok(());
}
if list_windows {
return print_open_windows();
}
if bridge {
return bridge::main(!no_window_logging, allow_launch.unwrap_or(false));
}
let mut config = match config::load_configs(&config_paths) {
Ok(config) => config,
Err(e) => bail!(
"Failed to load config '{}': {}",
config_paths
.iter()
.map(|p| p.to_string_lossy())
.collect::<Vec<_>>()
.join("', '"),
e
),
};
let watch_devices = watch.contains(&WatchTargets::Device);
let watch_config = watch.contains(&WatchTargets::Config);
let timeout_manager = Rc::new(TimeoutManager::new());
let own_device: String = output_device_name.unwrap_or_else(choose_device_name);
let timer = TimerFd::new(ClockId::CLOCK_MONOTONIC, TimerFlags::empty())?;
let delay = Duration::from_millis(config.keypress_delay_ms);
let mut input_devices = select_input_devices(&device_filter, &ignore_filter, mouse, watch_devices, &own_device)?;
let device_watcher = DeviceWatcher::new(watch_devices).context("Setting up device watcher")?;
let mut config_watcher =
ConfigWatcher::new(watch_config, config_paths, config.config_watch_debounce_ms, config.notifications)?;
let mut mainctrl = MainController::new(!no_window_logging, allow_launch.unwrap_or(true));
let mut handler = EventHandler::new(timer, &config.default_mode, delay);
let vendor = u16::from_str_radix(vendor.unwrap_or_default().trim_start_matches("0x"), 16).unwrap_or(0x1234);
let product = u16::from_str_radix(product.unwrap_or_default().trim_start_matches("0x"), 16).unwrap_or(0x5678);
let output_device = output_device(
input_devices.values().next().map(InputDevice::bus_type),
config.enable_wheel,
vendor,
product,
&own_device,
)
.context("Failed to prepare an output device")?;
let throttle_emit = if config.throttle_ms == 0 {
None
} else {
Some(ThrottleEmit::new(Duration::from_millis(config.throttle_ms)))
};
let mut operator_handler = if config.experimental_map.len() > 0 {
Some(OperatorHandler::new(&config.experimental_map, timeout_manager.clone()))
} else {
None
};
let mut dispatcher = ActionDispatcher::new(output_device, throttle_emit);
loop {
if config.notifications {
mainctrl.show_popup("Ready", None);
}
'event_loop: loop {
let readable_fds =
select_readable(input_devices.values(), &device_watcher, &config_watcher, &handler, &timeout_manager)?;
if readable_fds.contains(&handler.as_fd().as_raw_fd()) {
if let Err(error) = handle_events(
&mut handler,
&mut dispatcher,
&config,
vec![Event::OverrideTimeout],
&mut operator_handler,
&mut mainctrl,
) {
println!("Error on remap timeout: {error}")
}
}
if readable_fds.contains(&timeout_manager.as_fd().as_raw_fd()) {
if timeout_manager.need_timeout()? {
if let Err(error) = handle_events(
&mut handler,
&mut dispatcher,
&mut config,
vec![Event::Tick],
&mut operator_handler,
&mut mainctrl,
) {
println!("Error on timeout: {error}")
}
}
}
for input_device in input_devices.values_mut() {
if !readable_fds.contains(&input_device.as_fd().as_raw_fd()) {
continue;
}
if !handle_input_events(
input_device,
&mut handler,
&mut dispatcher,
&config,
&mut operator_handler,
&mut mainctrl,
)? {
let device_info = input_device.to_info();
println!("Found a removed device: {:?}", device_info.name);
input_devices.retain(|path, _| device_info.path != *path);
if input_devices.is_empty() {
if watch_devices {
println!("No device was selected, but --watch is waiting for new devices.");
} else {
bail!("Last device was removed, and not watching for new devices");
}
}
continue 'event_loop;
}
}
if let Some(device_watcher) = &device_watcher {
if let Ok(events) = device_watcher.read_events() {
handle_device_changes(
events,
&mut input_devices,
&device_filter,
&ignore_filter,
mouse,
&own_device,
);
}
}
if let Some(config_watcher) = config_watcher.as_mut() {
match config_watcher.handle(readable_fds, &mut mainctrl) {
Ok(Some(c)) => {
config = c;
break 'event_loop;
}
_ => {
continue 'event_loop;
}
};
}
}
}
}
fn select_readable<'a>(
devices: impl Iterator<Item = &'a InputDevice>,
device_watcher: &Option<DeviceWatcher>,
config_watcher: &Option<ConfigWatcher>,
event_handler: &impl AsFd,
timeout_manager: &Rc<TimeoutManager>,
) -> anyhow::Result<Vec<RawFd>> {
let mut read_fds = FdSet::new();
read_fds.insert(event_handler.as_fd());
read_fds.insert(timeout_manager.as_fd());
for device in devices {
read_fds.insert(device.as_fd());
}
#[cfg(target_os = "linux")]
if let Some(device_watcher) = device_watcher {
read_fds.insert(device_watcher.as_fd());
}
#[cfg(target_os = "linux")]
if let Some(config_watcher) = config_watcher {
read_fds.insert(config_watcher.borrow_timer());
read_fds.insert(config_watcher.borrow_inotify());
}
select(None, &mut read_fds, None, None, None)?;
Ok(read_fds.fds(None).map(|fd| fd.as_raw_fd()).collect())
}
fn handle_input_events(
input_device: &mut InputDevice,
handler: &mut EventHandler,
dispatcher: &mut ActionDispatcher,
config: &Config,
operator_handler: &mut Option<OperatorHandler>,
mainctrl: &mut MainController,
) -> anyhow::Result<bool> {
let events: Vec<_> = match input_device.fetch_events() {
Err(err) if err.raw_os_error() == Some(ENODEV) => {
return Ok(false);
}
events => events.context("Error fetching input events")?,
}
.collect();
let info = Rc::new(input_device.to_info());
let input_events = events.iter().map(|e| Event::new(info.clone(), *e)).collect();
handle_events(handler, dispatcher, config, input_events, operator_handler, mainctrl)?;
Ok(true)
}
fn handle_events(
handler: &mut EventHandler,
dispatcher: &mut ActionDispatcher,
config: &Config,
events: Vec<Event>,
operator_handler: &mut Option<OperatorHandler>,
mainctrl: &mut MainController,
) -> anyhow::Result<()> {
let actions = handler
.on_events(events, config, mainctrl.wmclient(), operator_handler)
.map_err(|err| anyhow!("EventHandler failed: {err:?}"))?;
for action in actions {
dispatcher.on_action(action, mainctrl)?;
}
Ok(())
}
fn handle_device_changes(
events: Vec<PathBuf>,
input_devices: &mut HashMap<PathBuf, InputDevice>,
device_filter: &[String],
ignore_filter: &[String],
mouse: bool,
own_device: &str,
) {
let mut ignore: Vec<PathBuf> = input_devices.iter().map(|(path, _)| path).cloned().collect();
input_devices.extend(events.into_iter().filter_map(|path| {
if ignore.contains(&path) {
return None;
}
ignore.push(path.clone());
let mut device = open_device(path)?;
if device.is_input_device(device_filter, ignore_filter, mouse, own_device) && device.grab() {
device.print();
Some(device.into())
} else {
None
}
}));
}