use anyhow::bail;
use derive_where::derive_where;
use evdev::uinput::VirtualDevice;
use evdev::{AttributeSet, BusType, Device, FetchEventsSynced, InputId, KeyCode as Key, RelativeAxisCode};
use log::debug;
use nix::libc::{EBUSY, ENODEV};
use std::collections::HashMap;
#[cfg(feature = "udev")]
use std::fs::metadata;
use std::fs::{self, read_dir};
use std::os::fd::{AsFd, BorrowedFd};
#[cfg(feature = "udev")]
use std::os::linux::fs::MetadataExt;
use std::os::unix::ffi::OsStrExt;
use std::path::{Path, PathBuf};
use std::time::Duration;
use std::{io, process};
#[cfg(feature = "udev")]
use udev::DeviceType;
use crate::util::{evdev_enums_to_string, print_table};
pub fn choose_device_name() -> String {
let name_already_taken = match input_devices() {
Ok(devices) => devices.iter().any(|device| device.device_name().contains("xremap")),
Err(_) => true, };
if name_already_taken {
format!("xremap pid={}", process::id())
} else {
"xremap".to_string()
}
}
pub fn output_device(
bus_type: Option<BusType>,
enable_wheel: bool,
vendor: u16,
product: u16,
own_device: &str,
) -> anyhow::Result<VirtualDevice> {
let mut keys: AttributeSet<Key> = AttributeSet::new();
for code in Key::KEY_RESERVED.code()..Key::BTN_TRIGGER_HAPPY40.code() {
let key = Key::new(code);
let name = format!("{key:?}");
if name.starts_with("KEY_") || name.starts_with("BTN_") {
keys.insert(key);
}
}
let mut relative_axes: AttributeSet<RelativeAxisCode> = AttributeSet::new();
relative_axes.insert(RelativeAxisCode::REL_X);
relative_axes.insert(RelativeAxisCode::REL_Y);
if enable_wheel {
relative_axes.insert(RelativeAxisCode::REL_HWHEEL);
relative_axes.insert(RelativeAxisCode::REL_WHEEL);
}
relative_axes.insert(RelativeAxisCode::REL_MISC);
let device = VirtualDevice::builder()?
.input_id(InputId::new(bus_type.unwrap_or(BusType::BUS_USB), vendor, product, 0x111))
.name(own_device)
.with_keys(&keys)?
.with_relative_axes(&relative_axes)?
.build()?;
Ok(device)
}
fn input_devices() -> anyhow::Result<Vec<InputDevice>> {
Ok(read_dir("/dev/input")
.map_err(|err| anyhow::format_err!("Failed to read /dev/input: {err}"))?
.filter_map(|entry| {
open_device(entry.ok()?.path())
})
.collect())
}
pub fn select_input_devices(
device_opts: &[String],
ignore_opts: &[String],
mouse: bool,
watch: bool,
own_device: &str,
) -> anyhow::Result<HashMap<PathBuf, InputDevice>> {
let mut devices = input_devices()?;
devices.sort();
if devices.is_empty() {
println!("No input devices. It's probably because you lack the required permissions.");
} else {
println!("Selecting devices from the following list:");
println!("{SEPARATOR}");
devices.iter().for_each(InputDevice::print);
}
println!("{SEPARATOR}");
if device_opts.is_empty() {
if mouse {
print!("Selected keyboards and mice automatically since --device options weren't specified");
} else {
print!("Selected keyboards automatically since --device options weren't specified");
}
} else {
print!("Selected devices matching {device_opts:?}");
};
if ignore_opts.is_empty() {
println!(":")
} else {
println!(", ignoring {ignore_opts:?}:");
}
println!("{SEPARATOR}");
let mut selected: Vec<InputDevice> = vec![];
for mut device in devices.into_iter() {
if device.is_input_device(device_opts, ignore_opts, mouse, own_device) && device.grab() {
device.print();
selected.push(device)
}
}
if selected.is_empty() {
if watch {
println!("No device was selected, but --watch is waiting for new devices.");
} else {
bail!("Failed to prepare input devices: No device was selected!");
}
}
println!("{SEPARATOR}");
Ok(selected.into_iter().map(From::from).collect())
}
pub fn open_device(path: PathBuf) -> Option<InputDevice> {
path.file_name()?
.as_bytes()
.starts_with(b"event")
.then_some(InputDevice {
device: Device::open(&path).ok()?,
path,
})
}
#[derive(Debug, Clone)]
pub struct InputDeviceInfo {
pub name: String,
pub path: PathBuf,
pub product: u16,
pub vendor: u16,
}
impl InputDeviceInfo {
pub fn matches(&self, filter: &str) -> bool {
if self.path.as_os_str() == filter || self.name == filter {
return true;
}
if filter.starts_with("event") && self.path.file_name().expect("every device path has a file name") == filter {
return true;
}
if filter.starts_with("ids:") {
let args = filter.split(':').collect::<Vec<&str>>();
if args.len() == 3 {
let vid = u16::from_str_radix(args[1].trim_start_matches("0x"), 16).unwrap_or(0);
let pid = u16::from_str_radix(args[2].trim_start_matches("0x"), 16).unwrap_or(0);
match (vid, pid) {
(0, 0) => {}
(v, 0) if v == self.vendor => {
return true;
}
(0, p) if p == self.product => {
return true;
}
(v, p) if v == self.vendor && p == self.product => {
return true;
}
(_, _) => {}
}
}
}
if self.name.contains(filter) {
return true;
}
if Path::new(filter).is_absolute() {
if let Ok(resolved_filter) = fs::canonicalize(filter) {
if self.path == resolved_filter {
return true;
}
}
}
#[cfg(feature = "udev")]
{
if filter.starts_with("props:") {
if let Ok(meta) = metadata(&self.path) {
let args = filter.split(':').collect::<Vec<&str>>();
if args.len() == 3 {
if let Ok(ud) = udev::Device::from_devnum(DeviceType::Character, meta.st_rdev()) {
for _ in 0..10 {
if ud.is_initialized() {
break;
}
std::thread::sleep(std::time::Duration::from_millis(10));
}
let props = ud.properties();
for p in props.filter(|p| p.name() == args[1]) {
if p.value() == args[2] {
return true;
}
}
}
}
}
}
}
false
}
}
#[derive_where(PartialEq, PartialOrd, Ord)]
pub struct InputDevice {
path: PathBuf,
#[derive_where(skip)]
device: Device,
}
impl Eq for InputDevice {}
impl From<InputDevice> for (PathBuf, InputDevice) {
fn from(device: InputDevice) -> Self {
(device.path.clone(), device)
}
}
impl AsFd for InputDevice {
fn as_fd(&self) -> BorrowedFd<'_> {
self.device.as_fd()
}
}
impl InputDevice {
pub fn wait_for_all_keys_up(&self) -> bool {
#[cfg(not(feature = "device-test"))]
let count = 50;
#[cfg(feature = "device-test")]
let count = 2;
for _ in 0..count {
match self.device.get_key_state() {
Ok(keys) => {
if keys.iter().filter(|&key| key != Key::KEY_UNKNOWN).count() == 0 {
return true;
} else {
std::thread::sleep(Duration::from_millis(100));
}
}
Err(err) if err.raw_os_error() == Some(ENODEV) => {
return false;
}
Err(err) => {
eprintln!(
"Error: {err}. Error happened while waiting for keys to be released on: '{}'",
self.device_name()
);
return false;
}
};
}
eprintln!("Timed out waiting for keys to be released on: '{}'", self.device_name());
false
}
pub fn grab(&mut self) -> bool {
if !self.wait_for_all_keys_up() {
return false;
}
match self.device.grab() {
Ok(_) => true,
Err(err) if err.raw_os_error() == Some(ENODEV) => {
false
}
Err(err) if err.raw_os_error() == Some(EBUSY) => {
eprintln!("Error: {err}. Another program might have grabbed the device: '{}'", self.device_name());
false
}
Err(error) => {
eprintln!(
"warning: Failed to grab device '{}' at '{}'. You may need to grant permissions. Error: {}",
self.device_name(),
self.path.display(),
error
);
false
}
}
}
pub fn ungrab(&mut self) {
if let Err(error) = self.device.ungrab() {
println!("Failed to ungrab device '{}' at '{}' due to: {error}", self.device_name(), self.path.display());
}
}
pub fn fetch_events(&mut self) -> io::Result<FetchEventsSynced<'_>> {
self.device.fetch_events()
}
pub fn device_name(&self) -> &str {
self.device.name().unwrap_or("<Unnamed device>")
}
pub fn bus_type(&self) -> BusType {
self.device.input_id().bus_type()
}
pub fn product(&self) -> u16 {
self.device.input_id().product()
}
pub fn vendor(&self) -> u16 {
self.device.input_id().vendor()
}
pub fn to_info(&self) -> InputDeviceInfo {
InputDeviceInfo {
name: self.device_name().into(),
product: self.product(),
vendor: self.vendor(),
path: self.path.clone(),
}
}
pub fn is_input_device(
&self,
device_filter: &[String],
ignore_filter: &[String],
mouse: bool,
own_device: &str,
) -> bool {
if self.device_name() == own_device {
return false;
}
(if device_filter.is_empty() {
self.is_keyboard() || (mouse && self.is_mouse())
} else {
self.matches_any(device_filter)
}) && (ignore_filter.is_empty() || !self.matches_any(ignore_filter))
}
fn matches_any(&self, filter: &[String]) -> bool {
filter.iter().any(|f| self.to_info().matches(f))
}
fn is_keyboard(&self) -> bool {
match self.device.supported_keys() {
Some(keys) => keys.contains(Key::KEY_SPACE) && keys.contains(Key::KEY_A) && keys.contains(Key::KEY_Z),
None => false,
}
}
fn is_mouse(&self) -> bool {
if self.device.supported_absolute_axes().is_some() {
debug!("Ignoring absolute device {:18} {}", self.path.display(), self.device_name());
return false;
}
self.device
.supported_keys()
.is_some_and(|keys| keys.contains(Key::BTN_LEFT))
}
pub fn print(&self) {
println!("{:18}: {}", self.path.display(), self.device_name())
}
pub fn print_details(&self) {
let properties = evdev_enums_to_string(self.device.properties());
let events = evdev_enums_to_string(self.device.supported_events());
let keys = evdev_enums_to_string(self.device.supported_keys().unwrap_or_default());
let relative = evdev_enums_to_string(self.device.supported_relative_axes().unwrap_or_default());
let absolute = evdev_enums_to_string(self.device.supported_absolute_axes().unwrap_or_default());
let leds = evdev_enums_to_string(self.device.supported_leds().unwrap_or_default());
let switches = evdev_enums_to_string(self.device.supported_switches().unwrap_or_default());
println!("{}", self.device_name());
println!("");
println!(" Path: {}", self.path.display());
println!(" Type: {}", self.bus_type());
println!(
" Vendor/product: {}:{} (0x{:x}:0x{:x}) ",
self.vendor(),
self.product(),
self.vendor(),
self.product()
);
println!(" Properties: {}", properties);
println!(" Events: {}", events);
println!(" Keys: {}", keys);
println!(" Relative axes: {}", relative);
println!(" Absolute axes: {}", absolute);
println!(" Leds: {}", leds);
println!(" Switches: {}", switches);
println!("");
}
}
pub fn print_device_list() -> anyhow::Result<()> {
let mut devices = input_devices()?;
devices.sort();
let mut table: Vec<Vec<String>> = vec![];
table.push(vec![
"PATH".into(),
"NAME".into(),
"IS_KEYBOARD".into(),
"IS_MOUSE".into(),
"TYPE".into(),
"VENDOR".into(),
"PRODUCT".into(),
]);
for device in devices {
table.push(vec![
device.path.display().to_string(),
device.device_name().to_string(),
format!("{}", device.is_keyboard()),
format!("{}", device.is_mouse()),
device.bus_type().to_string(),
format!("0x{:x}", device.vendor()),
format!("0x{:x}", device.product()),
]);
}
print_table(table);
Ok(())
}
pub fn print_device_details() -> anyhow::Result<()> {
let mut devices = input_devices()?;
devices.sort();
for device in devices {
device.print_details();
}
Ok(())
}
pub const SEPARATOR: &str = "------------------------------------------------------------------------------";