diff --git a/Cargo.lock b/Cargo.lock index ea439c56..95dd7045 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -17,6 +17,7 @@ dependencies = [ "sysinfo", "tokio", "tokio-stream", + "udev", ] [[package]] @@ -248,6 +249,16 @@ version = "0.2.132" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8371e4e5341c3a96db127eb2465ac681ced4c433e01dd0e938adbef26ba93ba5" +[[package]] +name = "libudev-sys" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c8469b4a23b962c1396b9b451dda50ef5b283e8dd309d69033475fa9b334324" +dependencies = [ + "libc", + "pkg-config", +] + [[package]] name = "lock_api" version = "0.4.7" @@ -367,6 +378,12 @@ version = "0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116" +[[package]] +name = "pkg-config" +version = "0.3.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1df8c4ec4b0627e53bdf214615ad287367e482558cf84b109250b37464dc03ae" + [[package]] name = "proc-macro2" version = "1.0.43" @@ -621,6 +638,17 @@ dependencies = [ "tokio", ] +[[package]] +name = "udev" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c960764f7e816eed851a96c364745d37f9fe71a2e7dba79fbd40104530b5dd0" +dependencies = [ + "libc", + "libudev-sys", + "pkg-config", +] + [[package]] name = "unicode-ident" version = "1.0.3" diff --git a/swhkd/Cargo.toml b/swhkd/Cargo.toml index f0e9f309..3ccc4739 100644 --- a/swhkd/Cargo.toml +++ b/swhkd/Cargo.toml @@ -21,6 +21,7 @@ signal-hook-tokio = { version = "0.3.1", features = ["futures-v0_3"] } sysinfo = "0.23.5" tokio = { version = "1.17.0", features = ["full"] } tokio-stream = "0.1.8" +udev = "0.6.3" [[bin]] name = "swhkd" diff --git a/swhkd/src/daemon.rs b/swhkd/src/daemon.rs index 317e8c3e..b630dafd 100644 --- a/swhkd/src/daemon.rs +++ b/swhkd/src/daemon.rs @@ -23,6 +23,7 @@ use tokio::select; use tokio::time::Duration; use tokio::time::{sleep, Instant}; use tokio_stream::{StreamExt, StreamMap}; +use udev::{EventType, MonitorBuilder}; mod config; mod perms; @@ -138,22 +139,16 @@ async fn main() -> Result<(), Box> { // Escalate back to the root user after reading the config file. perms::raise_privileges(); + let arg_devices: Vec<&str> = args.values_of("device").unwrap_or_default().collect(); + let keyboard_devices: Vec = { - if let Some(arg_devices) = args.values_of("device") { - // for device in arg_devices { - // let device_path = Path::new(device); - // if let Ok(device_to_use) = Device::open(device_path) { - // log::info!("Using device: {}", device_to_use.name().unwrap_or(device)); - // keyboard_devices.push(device_to_use); - // } - // } - let arg_devices = arg_devices.collect::>(); + if arg_devices.is_empty() { + log::trace!("Attempting to find all keyboard file descriptors."); + evdev::enumerate().filter(check_device_is_keyboard).collect() + } else { evdev::enumerate() .filter(|device| arg_devices.contains(&device.name().unwrap_or(""))) .collect() - } else { - log::trace!("Attempting to find all keyboard file descriptors."); - evdev::enumerate().filter(check_device_is_keyboard).collect() } }; @@ -172,6 +167,8 @@ async fn main() -> Result<(), Box> { } }; + let mut udev = tokio_stream::iter(MonitorBuilder::new()?.match_subsystem("input")?.listen()?); + let modifiers_map: HashMap = HashMap::from([ (Key::KEY_LEFTMETA, config::Modifier::Super), (Key::KEY_RIGHTMETA, config::Modifier::Super), @@ -264,6 +261,58 @@ async fn main() -> Result<(), Box> { } } + udev_item = udev.next() => { + if udev_item.is_none() { + continue; + } + let event = udev_item.unwrap(); + if !event.is_initialized() { + log::warn!("Received udev event with uninitialized device."); + } + match event.event_type() { + EventType::Add => { + if let Some(devnode) = event.devnode() { + let mut device = match Device::open(devnode) { + Err(e) => { + log::error!("Could not open evdev device at {}: {}", devnode.to_string_lossy(), e); + continue; + }, + Ok(device) => device + }; + if arg_devices.contains(&device.name().unwrap_or("")) || check_device_is_keyboard(&device) { + log::info!("Device '{}' at '{}' added.", &device.name().unwrap_or(""), devnode.to_string_lossy()); + let _ = device.grab(); + keyboard_states.push(KeyboardState::new()); + keyboard_stream_map.insert(keyboard_states.len() - 1, device.into_event_stream()?); + } + } + } + EventType::Remove => { + let udev_physical_path = event.property_value("PHYS") + .and_then(|os_str| os_str.to_str()) + .map(|s| s.replace('\"', "")); + keyboard_stream_map + .iter() + .filter(|(_, stream)| stream.device().physical_path() == udev_physical_path.as_deref()) + .map(|(key, _)| *key) + // Collect all to not remove values while iterating + .collect::>() + .into_iter() + // Reverse to avoid dealing with changing indices for the keyboard_states vector + .rev() + .for_each(|key| { + keyboard_states.remove(key); + if let Some(stream) = keyboard_stream_map.remove(&key) { + log::info!("Device '{}' removed", stream.device().name().unwrap_or("")); + } + }); + } + _ => { + log::trace!("Ignored udev event of type: {:?}", event.event_type()); + } + } + } + Some((i, Ok(event))) = keyboard_stream_map.next() => { let keyboard_state = &mut keyboard_states[i];