From efbdbd7328af7c7434a5099f7b69511ebf19e822 Mon Sep 17 00:00:00 2001 From: Wez Furlong Date: Wed, 24 Nov 2021 09:51:27 -0700 Subject: Improve handling ambiguous device names * Adds the `phys` info for the device to the list * Orders names by name first then by the eventXXX number component so that there is a "stable" ordering for a given set of inputs * When a name has ambiguous matches, warnings are logged to advise the user how to resolve them. Example: ``` ; cat /tmp/evremap.toml device_name = "Power Button" ``` ``` sudo ./target/debug/evremap remap /tmp/evremap.toml 2021-11-24T10:04:40.885 ERROR evremap > Short delay: release any keys now! 2021-11-24T10:04:43.084 WARN evremap::deviceinfo > The following devices match name `Power Button`: 2021-11-24T10:04:43.084 WARN evremap::deviceinfo > DeviceInfo { name: "Power Button", path: "/dev/input/event0", phys: "PNP0C0C/button/input0" } 2021-11-24T10:04:43.084 WARN evremap::deviceinfo > DeviceInfo { name: "Power Button", path: "/dev/input/event1", phys: "LNXPWRBN/button/input0" } 2021-11-24T10:04:43.084 WARN evremap::deviceinfo > evremap will use the first entry. If you want to use one of the others, add the corresponding phys value to your configuration, for example, `phys = "LNXPWRBN/button/input0"` for the second entry in the list. 2021-11-24T10:04:43.085 ERROR evremap::remapper > Going into read loop ``` refs: #2 closes: #3 --- README.md | 4 ++++ src/deviceinfo.rs | 69 ++++++++++++++++++++++++++++++++++++++++++++++++------- src/main.rs | 17 ++++++++++++-- src/mapping.rs | 4 ++++ 4 files changed, 84 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index 6656baf..0b4b2ca 100644 --- a/README.md +++ b/README.md @@ -29,6 +29,10 @@ Here's an example configuration that makes capslock useful: # on your system. device_name = "AT Translated Set 2 keyboard" +# If you have multiple devices with the same name, you can optionally +# specify the `phys` value that is printed by the `list-devices` subcommand +# phys = "usb-0000:07:00.3-2.1.1/input0" + # Configure CAPSLOCK as a Dual Role key. # Holding it produces LEFTCTRL, but tapping it # will produce ESC. diff --git a/src/deviceinfo.rs b/src/deviceinfo.rs index cc05e47..9097433 100644 --- a/src/deviceinfo.rs +++ b/src/deviceinfo.rs @@ -1,11 +1,13 @@ use anyhow::*; use evdev_rs::Device; +use std::cmp::Ordering; use std::path::PathBuf; #[derive(Debug, Clone)] pub struct DeviceInfo { pub name: String, pub path: PathBuf, + pub phys: String, } impl DeviceInfo { @@ -18,18 +20,51 @@ impl DeviceInfo { Ok(Self { name: input.name().unwrap_or("").to_string(), + phys: input.phys().unwrap_or("").to_string(), path, }) } - pub fn with_name(name: &str) -> Result { - let devices = Self::obtain_device_list()?; - for item in devices { - if item.name == name { - return Ok(item); + pub fn with_name(name: &str, phys: Option<&str>) -> Result { + let mut devices = Self::obtain_device_list()?; + + if let Some(phys) = phys { + match devices.iter().position(|item| item.phys == phys) { + Some(idx) => return Ok(devices.remove(idx)), + None => { + bail!( + "Requested device `{}` with phys=`{}` was not found", + name, + phys + ); + } + } + } + + let mut devices_with_name: Vec<_> = devices + .into_iter() + .filter(|item| item.name == name) + .collect(); + + if devices_with_name.is_empty() { + bail!("No device found with name `{}`", name); + } + + if devices_with_name.len() > 1 { + log::warn!("The following devices match name `{}`:", name); + for dev in &devices_with_name { + log::warn!("{:?}", dev); } + log::warn!( + "evremap will use the first entry. If you want to \ + use one of the others, add the corresponding phys \ + value to your configuration, for example, \ + `phys = \"{}\"` for the second entry in the list.", + devices_with_name[1].phys + ); } - bail!("No device found with name `{}`", name); + + Ok(devices_with_name.remove(0)) } fn obtain_device_list() -> Result> { @@ -52,20 +87,38 @@ impl DeviceInfo { match DeviceInfo::with_path(path) { Ok(item) => devices.push(item), - Err(err) => log::error!("{}", err), + Err(err) => log::error!("{:#}", err), } } - devices.sort_by(|a, b| a.name.cmp(&b.name)); + // Order by name, but when multiple devices have the same name, + // order by the event device unit number + devices.sort_by(|a, b| match a.name.cmp(&b.name) { + Ordering::Equal => { + event_number_from_path(&a.path).cmp(&event_number_from_path(&b.path)) + } + different => different, + }); Ok(devices) } } +fn event_number_from_path(path: &PathBuf) -> u32 { + match path.to_str() { + Some(s) => match s.rfind("event") { + Some(idx) => s[idx + 5..].parse().unwrap_or(0), + None => 0, + }, + None => 0, + } +} + pub fn list_devices() -> Result<()> { let devices = DeviceInfo::obtain_device_list()?; for item in &devices { println!("Name: {}", item.name); println!("Path: {}", item.path.display()); + println!("Phys: {}", item.phys); println!(); } Ok(()) diff --git a/src/main.rs b/src/main.rs index 64a83ae..b1feb46 100644 --- a/src/main.rs +++ b/src/main.rs @@ -49,8 +49,18 @@ pub fn list_keys() -> Result<()> { Ok(()) } +fn setup_logger() { + let mut builder = pretty_env_logger::formatted_timed_builder(); + if let Ok(s) = std::env::var("EVREMAP_LOG") { + builder.parse_filters(&s); + } else { + builder.filter(None, log::LevelFilter::Info); + } + builder.init(); +} + fn main() -> Result<()> { - pretty_env_logger::init(); + setup_logger(); let opt = Opt::from_args(); match opt { @@ -65,7 +75,10 @@ fn main() -> Result<()> { log::error!("Short delay: release any keys now!"); std::thread::sleep(Duration::new(2, 0)); - let device_info = deviceinfo::DeviceInfo::with_name(&mapping_config.device_name)?; + let device_info = deviceinfo::DeviceInfo::with_name( + &mapping_config.device_name, + mapping_config.phys.as_deref(), + )?; let mut mapper = InputMapper::create_mapper(device_info.path, mapping_config.mappings)?; mapper.run_mapper() diff --git a/src/mapping.rs b/src/mapping.rs index c2dca77..6ffc274 100644 --- a/src/mapping.rs +++ b/src/mapping.rs @@ -8,6 +8,7 @@ use thiserror::Error; #[derive(Debug, Clone)] pub struct MappingConfig { pub device_name: String, + pub phys: Option, pub mappings: Vec, } @@ -27,6 +28,7 @@ impl MappingConfig { } Ok(Self { device_name: config_file.device_name, + phys: config_file.phys, mappings, }) } @@ -113,6 +115,8 @@ impl Into for RemapConfig { #[derive(Debug, Deserialize)] struct ConfigFile { device_name: String, + #[serde(default)] + phys: Option, #[serde(default)] dual_role: Vec, -- cgit v1.2.3