From cc8d3d35a78182fd55627d4d154474d9291ef438 Mon Sep 17 00:00:00 2001 From: AethanFoot Date: Fri, 8 Mar 2024 17:42:02 +0000 Subject: [PATCH] Robust keyboard finding with udev and respond to removed/added keyboards --- Cargo.lock | 32 ++----- lefthk-core/Cargo.toml | 4 +- lefthk-core/src/evdev.rs | 175 ++++++++++++++++++++++------------ lefthk-core/src/worker/mod.rs | 3 + 4 files changed, 125 insertions(+), 89 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index ef2258e..85dd747 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -87,9 +87,9 @@ checksum = "a2bd12c1caf447e69cd4528f47f94d203fd2582878ecb9e9465484c4148a8223" [[package]] name = "cc" -version = "1.0.89" +version = "1.0.90" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a0ba8f7aaa012f30d5b2861462f6708eccd49c3c39863fe083a308035f63d723" +checksum = "8cd6604a82acf3039f1144f54b8eb34e91ffba622051189e71b781822d5ee1f5" [[package]] name = "cfg-if" @@ -199,26 +199,6 @@ dependencies = [ "hashbrown", ] -[[package]] -name = "input" -version = "0.8.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6e74cd82cedcd66db78742a8337bdc48f188c4d2c12742cbc5cd85113f0b059" -dependencies = [ - "bitflags 1.3.2", - "input-sys", - "io-lifetimes", - "libc", - "log", - "udev", -] - -[[package]] -name = "input-sys" -version = "1.18.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd4f5b4d1c00331c5245163aacfe5f20be75b564c7112d45893d4ae038119eb0" - [[package]] name = "inventory" version = "0.3.15" @@ -263,7 +243,6 @@ name = "lefthk-core" version = "0.2.1" dependencies = [ "evdev-rs", - "input", "inventory", "mio", "nix", @@ -274,6 +253,7 @@ dependencies = [ "thiserror", "tokio", "tracing", + "udev", "xdg", ] @@ -733,12 +713,14 @@ dependencies = [ [[package]] name = "udev" -version = "0.7.0" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ebdbbd670373442a12fe9ef7aeb53aec4147a5a27a00bbc3ab639f08f48191a" +checksum = "50051c6e22be28ee6f217d50014f3bc29e81c20dc66ff7ca0d5c5226e1dcc5a1" dependencies = [ + "io-lifetimes", "libc", "libudev-sys", + "mio", "pkg-config", ] diff --git a/lefthk-core/Cargo.toml b/lefthk-core/Cargo.toml index 3ed88ce..44e4b6b 100644 --- a/lefthk-core/Cargo.toml +++ b/lefthk-core/Cargo.toml @@ -9,8 +9,7 @@ description = "A hotkey daemon for Adventurers" [dependencies] evdev-rs = { version = "0.6.1", features = ["serde"] } -input = "0.8" -mio = "0.8.0" +mio = "0.8.11" nix = { version = "0.27.1", features = ["fs", "signal"] } signal-hook = "0.3.4" thiserror = "1.0.30" @@ -23,6 +22,7 @@ tokio = { version = "1.14.0", features = [ "sync", "time", ] } +udev = {version = "0.8.0", features = ["mio"] } xdg = "2.4.0" ron = "0.8.0" serde = { version = "1.0.145", features = ["derive"] } diff --git a/lefthk-core/src/evdev.rs b/lefthk-core/src/evdev.rs index 0df9086..227f096 100644 --- a/lefthk-core/src/evdev.rs +++ b/lefthk-core/src/evdev.rs @@ -1,37 +1,32 @@ use evdev_rs::{Device, DeviceWrapper, InputEvent, ReadFlag, ReadStatus}; -use input::event::{DeviceEvent, EventTrait}; -use input::{Event, Libinput, LibinputInterface}; -use nix::libc::{O_RDWR, O_WRONLY}; -use std::ffi::OsStr; -use std::fs::{File, OpenOptions}; -use std::os::fd::{AsRawFd, OwnedFd}; -use std::os::unix::fs::OpenOptionsExt; -use std::path::{Path, PathBuf}; +use std::os::fd::AsRawFd; +use std::path::PathBuf; +use std::{collections::HashMap, ffi::OsStr}; use tokio::sync::{mpsc, oneshot}; -use tokio::time::Duration; use crate::errors::{self, LeftError}; #[derive(Debug)] pub enum Task { KeyboardEvent(InputEvent), - KeyboardAdded(String), + KeyboardAdded(PathBuf), + KeyboardRemoved(PathBuf), } pub struct EvDev { pub task_receiver: mpsc::Receiver, task_transmitter: mpsc::Sender, - task_guards: Vec>, + task_guards: HashMap>, _keyboard_watcher: KeyboardWatcher, } impl Default for EvDev { fn default() -> Self { - let (task_transmitter, task_receiver) = mpsc::channel(100); + let (task_transmitter, task_receiver) = mpsc::channel(128); let keyboard_watcher = KeyboardWatcher::new(task_transmitter.clone()); - let task_guards: Vec> = vec![]; + let task_guards: HashMap> = HashMap::new(); let devices = find_keyboards(); @@ -41,17 +36,22 @@ impl Default for EvDev { task_guards, _keyboard_watcher: keyboard_watcher, }; - for device in devices { - evdev.add_device(device); + match devices { + Some(devices) => { + for device in devices { + evdev.add_device(device); + } + } + None => tracing::warn!("No devices found on intialization."), } - evdev } } impl EvDev { pub fn add_device(&mut self, path: PathBuf) { - if let Some(device) = device_with_path(path) { + tracing::info!("Adding device with path: {:?}", path); + if let Some(device) = device_with_path(path.clone()) { let (guard, task_guard) = oneshot::channel(); let transmitter = self.task_transmitter.clone(); const SERVER: mio::Token = mio::Token(0); @@ -63,7 +63,7 @@ impl EvDev { SERVER, mio::Interest::READABLE, )); - let timeout = Duration::from_millis(100); + tokio::task::spawn(async move { loop { if guard.is_closed() { @@ -71,7 +71,7 @@ impl EvDev { return; } - if let Err(err) = poll.poll(&mut events, Some(timeout)) { + if let Err(err) = poll.poll(&mut events, None) { tracing::warn!("Evdev device poll failed with {:?}", err); continue; } @@ -81,60 +81,43 @@ impl EvDev { Ok((ReadStatus::Success, event)) => { transmitter.send(Task::KeyboardEvent(event)).await.unwrap(); } - Err(_) => println!("Boo"), + Err(_) => break, _ => {} } } } }); - self.task_guards.push(task_guard); + self.task_guards.insert(path, task_guard); } } -} - -struct Interface; - -impl LibinputInterface for Interface { - fn open_restricted(&mut self, path: &Path, flags: i32) -> Result { - OpenOptions::new() - .custom_flags(flags) - .read((flags != 0) | (flags & O_RDWR != 0)) - .write((flags & O_WRONLY != 0) | (flags & O_RDWR != 0)) - .open(path) - .map(|file| file.into()) - .map_err(|err| err.raw_os_error().unwrap()) - } - fn close_restricted(&mut self, fd: OwnedFd) { - let _ = File::from(fd); + pub fn remove_device(&mut self, path: PathBuf) { + tracing::info!("Removing device with path: {:?}", path); + drop(self.task_guards.remove(&path)); } } -fn find_keyboards() -> Vec { - let mut context = Libinput::new_with_udev(Interface); - context.udev_assign_seat("seat0").unwrap(); - context.dispatch().unwrap(); +fn find_keyboards() -> Option> { let mut devices = vec![]; - for event in &mut context { - if let Event::Device(DeviceEvent::Added(_)) = &event { - unsafe { - if let Some(device) = event.device().udev_device() { - let is_keyboard = device - .property_value("ID_INPUT_KEYBOARD") - .unwrap_or(OsStr::new("0")) - == "1" - && device - .property_value("ID_INPUT_MOUSE") - .unwrap_or(OsStr::new("0")) - == "0"; - if is_keyboard { - let path = device.property_value("DEVNAME").unwrap_or(OsStr::new("")); - devices.push(PathBuf::from(path)) - } - } + let mut enumerator = udev::Enumerator::new().ok()?; + enumerator.match_is_initialized().ok()?; + enumerator.match_subsystem("input").ok()?; + let enum_devices = enumerator.scan_devices().ok()?; + for device in enum_devices { + if let Some(devnode) = device.devnode() { + let is_keyboard = device + .property_value("ID_INPUT_KEYBOARD") + .unwrap_or(OsStr::new("0")) + == "1" + && device + .property_value("ID_INPUT_MOUSE") + .unwrap_or(OsStr::new("0")) + == "0"; + if is_keyboard { + devices.push(PathBuf::from(devnode)); } } } - devices + Some(devices) } fn device_with_path(path: PathBuf) -> Option { @@ -153,9 +136,77 @@ struct KeyboardWatcher { } impl KeyboardWatcher { - pub fn new(_task_transmitter: mpsc::Sender) -> Self { - let (_guard, task_guard) = oneshot::channel(); - tokio::task::spawn(async move {}); + pub fn new(task_transmitter: mpsc::Sender) -> Self { + let (guard, task_guard) = oneshot::channel(); + + tokio::task::spawn_blocking(move || { + let mut socket = udev::MonitorBuilder::new() + .expect("Failed to create monitor") + .match_subsystem("input") + .expect("Failed to match subsystem") + .listen() + .expect("Failed to listen"); + const SERVER: mio::Token = mio::Token(0); + let mut poll = mio::Poll::new().expect("Failed to create poll"); + let mut events = mio::Events::with_capacity(1); + poll.registry() + .register(&mut socket, SERVER, mio::Interest::READABLE) + .expect("Failed to register"); + loop { + if guard.is_closed() { + println!("Bye"); + return; + } + if let Err(err) = poll.poll(&mut events, None) { + tracing::warn!("KeyboardWatcher poll failed with {:?}", err); + continue; + } + + for e in socket.iter() { + let device = e.device(); + let is_keyboard = device + .property_value("ID_INPUT_KEYBOARD") + .unwrap_or(OsStr::new("0")) + == "1" + && device + .property_value("ID_INPUT_MOUSE") + .unwrap_or(OsStr::new("0")) + == "0"; + if is_keyboard { + let path = device + .property_value("DEVNAME") + .unwrap_or(OsStr::new("")) + .to_owned(); + if path.is_empty() { + continue; + } + match e.event_type() { + udev::EventType::Add => { + if let Err(err) = task_transmitter + .try_send(Task::KeyboardAdded(PathBuf::from(path))) + { + tracing::warn!( + "Failed to send keyboard added event: {:?}", + err + ); + } + } + udev::EventType::Remove => { + if let Err(err) = task_transmitter + .try_send(Task::KeyboardRemoved(PathBuf::from(path))) + { + tracing::warn!( + "Failed to send keyboard removed event: {:?}", + err + ); + } + } + _ => {} + } + } + } + } + }); Self { _task_guard: task_guard, } diff --git a/lefthk-core/src/worker/mod.rs b/lefthk-core/src/worker/mod.rs index 9d402c9..e56b842 100644 --- a/lefthk-core/src/worker/mod.rs +++ b/lefthk-core/src/worker/mod.rs @@ -84,6 +84,9 @@ impl Worker { Task::KeyboardAdded(path) => { self.evdev.add_device(path.into()); } + Task::KeyboardRemoved(path) => { + self.evdev.remove_device(path.into()); + } } } Some(command) = pipe.get_next_command() => {