From cdbb379b9b0ea322df63f44a8e5bf0f2afbfc5c7 Mon Sep 17 00:00:00 2001 From: Alexis Janon <98706738+ajanon@users.noreply.github.com> Date: Wed, 14 Sep 2022 10:32:56 +0200 Subject: [PATCH] [wip] udev support via tokio-udev to add/remove devices dynamically Support for listening asynchronously to udev DeviceRemoved/DeviceAdded events with the `tokio-udev` crate. For now, this only works with the latest commits on the tokio-udev repo. --- CHANGELOG.md | 3 +++ Cargo.lock | 38 +++++++++++++++++++++++++++++++++ swhkd/Cargo.toml | 1 + swhkd/src/daemon.rs | 52 +++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 94 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index d8af034..fec1fcd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - `DESTDIR` variable for the `install` target in the `Makefile` to help packaging and installation. To install in a subdirectory, just call `make DESTDIR=subdir install`. +- Detection of added/removed devices (e.g., when plugging or unplugging a + keyboard). The devices are grabbed by `swhkd` if they match the `--device` + parameters if present or if they are recognized as keyboard devices otherwise. ### Changed diff --git a/Cargo.lock b/Cargo.lock index 5d41aac..cfd9419 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -18,6 +18,7 @@ dependencies = [ "sysinfo", "tokio", "tokio-stream", + "tokio-udev", ] [[package]] @@ -272,6 +273,16 @@ version = "0.2.138" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "db6d7e329c562c5dfab7a46a2afabc8b987ab9a4834c9d1ca04dc54c1546cef8" +[[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.9" @@ -409,6 +420,12 @@ version = "0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116" +[[package]] +name = "pkg-config" +version = "0.3.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26072860ba924cbfa98ea39c8c19b4dd6a4a25423dbdf219c1eca91aa0cf6964" + [[package]] name = "proc-macro2" version = "1.0.49" @@ -660,6 +677,27 @@ dependencies = [ "tokio", ] +[[package]] +name = "tokio-udev" +version = "0.8.0" +source = "git+https://github.com/jeandudey/tokio-udev.git#f8c4981dd57bab442d1c1b4a2aac0751ed20bc19" +dependencies = [ + "futures-core", + "tokio", + "udev", +] + +[[package]] +name = "udev" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ebdbbd670373442a12fe9ef7aeb53aec4147a5a27a00bbc3ab639f08f48191a" +dependencies = [ + "libc", + "libudev-sys", + "pkg-config", +] + [[package]] name = "unicode-ident" version = "1.0.6" diff --git a/swhkd/Cargo.toml b/swhkd/Cargo.toml index 9f31808..765b30e 100644 --- a/swhkd/Cargo.toml +++ b/swhkd/Cargo.toml @@ -24,6 +24,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" +tokio-udev = { git = "https://github.com/jeandudey/tokio-udev.git" } [[bin]] name = "swhkd" diff --git a/swhkd/src/daemon.rs b/swhkd/src/daemon.rs index 781e490..c1c5386 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 tokio_udev::{AsyncMonitorSocket, EventType, MonitorBuilder}; mod config; mod perms; @@ -183,6 +184,9 @@ async fn main() -> Result<(), Box> { } }; + let mut udev = + AsyncMonitorSocket::new(MonitorBuilder::new()?.match_subsystem("input")?.listen()?)?; + let modifiers_map: HashMap = HashMap::from([ (Key::KEY_LEFTMETA, config::Modifier::Super), (Key::KEY_RIGHTMETA, config::Modifier::Super), @@ -275,6 +279,54 @@ async fn main() -> Result<(), Box> { } } + Some(Ok(event)) = udev.next() => { + 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];