From ed114ff635d7fe884deba085e1a17e134ad74176 Mon Sep 17 00:00:00 2001 From: AethanFoot Date: Wed, 7 Feb 2024 21:42:54 +0000 Subject: [PATCH] Lets try and make this headless --- Cargo.lock | 25 ++++ lefthk-core/Cargo.toml | 15 ++- lefthk-core/src/] | 120 ++++++++++++++++++ lefthk-core/src/config/command/chord.rs | 2 +- lefthk-core/src/evdev.rs | 158 ++++++++++++++++++++++++ lefthk-core/src/lib.rs | 1 + lefthk-core/src/worker/context/chord.rs | 2 +- lefthk-core/src/worker/mod.rs | 124 ++++++++++--------- lefthk/src/config/mod.rs | 2 +- 9 files changed, 382 insertions(+), 67 deletions(-) create mode 100644 lefthk-core/src/] create mode 100644 lefthk-core/src/evdev.rs diff --git a/Cargo.lock b/Cargo.lock index 008a9aa..339a4be 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -135,6 +135,30 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "evdev-rs" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9812d5790fb6fcce449333eb6713dad335e8c979225ed98755c84a3987e06dba" +dependencies = [ + "bitflags 1.3.2", + "evdev-sys", + "libc", + "log", + "serde", +] + +[[package]] +name = "evdev-sys" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14ead42b547b15d47089c1243d907bcf0eb94e457046d3b315a26ac9c9e9ea6d" +dependencies = [ + "cc", + "libc", + "pkg-config", +] + [[package]] name = "fastrand" version = "2.0.1" @@ -210,6 +234,7 @@ dependencies = [ name = "lefthk-core" version = "0.2.1" dependencies = [ + "evdev-rs", "inventory", "mio", "nix", diff --git a/lefthk-core/Cargo.toml b/lefthk-core/Cargo.toml index f4c5d72..cb94bcc 100644 --- a/lefthk-core/Cargo.toml +++ b/lefthk-core/Cargo.toml @@ -8,15 +8,24 @@ repository = "https://github.com/leftwm/lefthk" description = "A hotkey daemon for Adventurers" [dependencies] +evdev-rs = { version = "0.6.1", features = ["serde"] } mio = "0.8.0" -nix = {version = "0.27.1", features = ["fs", "signal"]} +nix = { version = "0.27.1", features = ["fs", "signal"] } signal-hook = "0.3.4" thiserror = "1.0.30" -tokio = { version = "1.14.0", features = ["fs", "io-util", "macros", "net", "rt-multi-thread", "sync", "time"] } +tokio = { version = "1.14.0", features = [ + "fs", + "io-util", + "macros", + "net", + "rt-multi-thread", + "sync", + "time", +] } x11-dl = "2.19.1" xdg = "2.4.0" ron = "0.8.0" -serde = { version = "1.0.145", features= ["derive"]} +serde = { version = "1.0.145", features = ["derive"] } inventory = "0.3.2" # logging diff --git a/lefthk-core/src/] b/lefthk-core/src/] new file mode 100644 index 0000000..11e0c9a --- /dev/null +++ b/lefthk-core/src/] @@ -0,0 +1,120 @@ +use evdev::Device; +use std::cmp::Ordering; +use std::path::PathBuf; + +use crate::errors::{self, Error, LeftError, Result}; + +#[derive(Debug, Clone)] +pub struct EvDev { + pub name: String, + pub path: PathBuf, + pub phys: String, +} + +impl EvDev { + pub fn with_path(path: PathBuf) -> Result { + let mut input = Device::open(path)?; + + Ok(Self { + name: input.name().unwrap_or("").to_string(), + phys: input.phys().unwrap_or("").to_string(), + path, + }) + } + + 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 => { + tracing::error!("Exiting due to error: 1"); + std::process::exit(1); + } + } + } + + let mut devices_with_name: Vec<_> = devices + .into_iter() + .filter(|item| item.name == name) + .collect(); + + if devices_with_name.is_empty() { + tracing::error!("Exiting due to error: No devices"); + std::process::exit(1); + } + + 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 + ); + } + + Ok(devices_with_name.remove(0)) + } + + fn obtain_device_list() -> Result> { + let mut devices = vec![]; + for entry in std::fs::read_dir("/dev/input")? { + let entry = entry?; + + if !entry + .file_name() + .to_str() + .unwrap_or("") + .starts_with("event") + { + continue; + } + let path = entry.path(); + if path.is_dir() { + continue; + } + + match EvDev::with_path(path) { + Ok(item) => devices.push(item), + Err(err) => log::error!("{:#}", err), + } + } + + // 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() -> Error { + let devices = EvDev::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/lefthk-core/src/config/command/chord.rs b/lefthk-core/src/config/command/chord.rs index 25321de..b640b5f 100644 --- a/lefthk-core/src/config/command/chord.rs +++ b/lefthk-core/src/config/command/chord.rs @@ -32,7 +32,7 @@ impl Command for Chord { } fn execute(&self, worker: &mut Worker) -> Error { - worker.xwrap.grab_keys(&self.0); + // worker.xwrap.grab_keys(&self.0); worker.chord_ctx.keybinds = Some(self.0.clone()); Ok(()) } diff --git a/lefthk-core/src/evdev.rs b/lefthk-core/src/evdev.rs new file mode 100644 index 0000000..cabdc4f --- /dev/null +++ b/lefthk-core/src/evdev.rs @@ -0,0 +1,158 @@ +use evdev_rs::{Device, DeviceWrapper}; +use std::cmp::Ordering; +use std::future::Future; +use std::os::fd::AsRawFd; +use std::path::PathBuf; +use std::pin::Pin; +use std::ptr; +use std::sync::Arc; +use std::task::{Context, Waker}; +use tokio::sync::{oneshot, Notify}; +use tokio::time::Duration; + +use crate::errors::{self, Error, LeftError, Result}; + +pub struct EvDev { + pub devices: Vec, + pub task_notify: Arc, + _task_guards: Vec>, +} + +// impl From<(PathBuf, Device)> for EvDev { +// fn from(value: (PathBuf, Device)) -> Self { +// Self { +// name: value.1.name().unwrap_or("").to_string(), +// phys: value.1.physical_path().unwrap_or("").to_string(), +// path: value.0, +// } +// } +// } + +impl EvDev { + pub fn new() -> Self { + let task_notify = Arc::new(Notify::new()); + + let mut task_guards: Vec> = vec![]; + let mut devices = vec![]; + for entry in errors::exit_on_error!(std::fs::read_dir("/dev/input")) { + let entry = errors::exit_on_error!(entry); + + if !entry + .file_name() + .to_str() + .unwrap_or("") + .starts_with("event") + { + continue; + } + let path = entry.path(); + if path.is_dir() { + continue; + } + + match device_with_path(path) { + Ok(item) => devices.push(item), + Err(err) => tracing::error!("{:#}", err), + } + } + devices + .iter() + .filter(|device| { + device.has(evdev_rs::enums::EventType::EV_KEY) + && device.phys().unwrap().contains("input0") + }) + .for_each(|device| { + let (guard, task_guard) = oneshot::channel(); + let notify = task_notify.clone(); + const SERVER: mio::Token = mio::Token(0); + let fd = device.file().as_raw_fd(); + let mut poll = errors::exit_on_error!(mio::Poll::new()); + let mut events = mio::Events::with_capacity(1); + errors::exit_on_error!(poll.registry().register( + &mut mio::unix::SourceFd(&fd), + SERVER, + mio::Interest::READABLE, + )); + let timeout = Duration::from_millis(100); + tokio::task::spawn_blocking(move || loop { + if guard.is_closed() { + println!("Bye"); + return; + } + + if let Err(err) = poll.poll(&mut events, Some(timeout)) { + tracing::warn!("Xlib socket poll failed with {:?}", err); + continue; + } + + events + .iter() + .filter(|event| SERVER == event.token()) + .for_each(|_| notify.notify_one()); + }); + task_guards.push(task_guard); + }); + + Self { + devices, + task_notify, + _task_guards: task_guards, + } + } + + pub fn wait_readable(&mut self) -> Pin>> { + let task_notify = self.task_notify.clone(); + Box::pin(async move { + task_notify.notified().await; + }) + } + + // fn obtain_device_list() -> Result> { + // let mut devices: Vec = evdev::enumerate() + // .filter(|(_, device)| { + // device + // .supported_keys() + // .map_or(false, |keys| keys.contains(evdev::Key::KEY_ENTER)) + // }) + // .map(|device| Self::from(device)) + // .collect(); + // + // // Order by name, but when multiple devices have the same name, + // // order by the event device unit number + // devices.sort_by(|a, b| { + // match event_number_from_path(&a.path).cmp(&event_number_from_path(&b.path)) { + // Ordering::Equal => { + // event_number_from_path(&a.path).cmp(&event_number_from_path(&b.path)) + // } + // different => different, + // } + // }); + // Ok(devices) + // } +} + +pub fn device_with_path(path: PathBuf) -> Result { + let f = std::fs::File::open(&path)?; + Ok(Device::new_from_path(path)?) +} + +// 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() -> Error { +// let devices = EvDev::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/lefthk-core/src/lib.rs b/lefthk-core/src/lib.rs index 5cc427a..a49abd6 100644 --- a/lefthk-core/src/lib.rs +++ b/lefthk-core/src/lib.rs @@ -3,6 +3,7 @@ mod tests; pub mod child; pub mod config; pub mod errors; +pub mod evdev; pub mod ipc; pub mod worker; pub mod xkeysym_lookup; diff --git a/lefthk-core/src/worker/context/chord.rs b/lefthk-core/src/worker/context/chord.rs index e1ae8d2..1f745c9 100644 --- a/lefthk-core/src/worker/context/chord.rs +++ b/lefthk-core/src/worker/context/chord.rs @@ -18,7 +18,7 @@ impl Chord { impl Worker { pub fn evaluate_chord(&mut self) { if self.chord_ctx.elapsed { - self.xwrap.grab_keys(&self.keybinds); + // self.xwrap.grab_keys(&self.keybinds); self.chord_ctx.keybinds = None; self.chord_ctx.elapsed = false; } diff --git a/lefthk-core/src/worker/mod.rs b/lefthk-core/src/worker/mod.rs index 0f1a00a..23769f5 100644 --- a/lefthk-core/src/worker/mod.rs +++ b/lefthk-core/src/worker/mod.rs @@ -1,12 +1,13 @@ pub mod context; +use std::path::Path; + use crate::child::Children; use crate::config::{command, Keybind}; use crate::errors::{self, Error, LeftError}; +use crate::evdev; +use crate::evdev::EvDev; use crate::ipc::Pipe; -use crate::xkeysym_lookup; -use crate::xwrap::XWrap; -use x11_dl::xlib; use xdg::BaseDirectories; #[derive(Debug, Clone, PartialEq, Eq, Hash)] @@ -20,7 +21,7 @@ pub struct Worker { keybinds: Vec, base_directory: BaseDirectories, - pub xwrap: XWrap, + pub evdev: EvDev, pub children: Children, pub status: Status, @@ -34,30 +35,31 @@ impl Worker { status: Status::Continue, keybinds, base_directory, - xwrap: XWrap::new(), + evdev: EvDev::new(), children: Children::default(), chord_ctx: context::Chord::new(), } } pub async fn event_loop(mut self) -> Status { - self.xwrap.grab_keys(&self.keybinds); let mut pipe = self.get_pipe().await; while self.status == Status::Continue { - self.xwrap.flush(); - self.evaluate_chord(); tokio::select! { () = self.children.wait_readable() => { self.children.reap(); } - () = self.xwrap.wait_readable() => { - let event_in_queue = self.xwrap.queue_len(); - for _ in 0..event_in_queue { - let xlib_event = self.xwrap.get_next_event(); - self.handle_event(&xlib_event); + () = self.evdev.wait_readable() => { + for device in &self.evdev.devices { + while device.has_event_pending() { + match device.next_event(evdev_rs::ReadFlag::NORMAL) { + Ok((_, event)) if event.value == 1 => println!("{:?}", event), + Err(_) => println!("Boo"), + _ => {}, + } + } } } Some(command) = pipe.get_next_command() => { @@ -75,52 +77,52 @@ impl Worker { errors::exit_on_error!(Pipe::new(pipe_file).await) } - fn handle_event(&mut self, xlib_event: &xlib::XEvent) { - let error = match xlib_event.get_type() { - xlib::KeyPress => self.handle_key_press(&xlib::XKeyEvent::from(xlib_event)), - xlib::MappingNotify => { - self.handle_mapping_notify(&mut xlib::XMappingEvent::from(xlib_event)) - } - _ => return, - }; - errors::log_on_error!(error); - } - - fn handle_key_press(&mut self, event: &xlib::XKeyEvent) -> Error { - let key = self.xwrap.keycode_to_keysym(event.keycode); - let mask = xkeysym_lookup::clean_mask(event.state); - if let Some(keybind) = self.get_keybind((mask, key)) { - if let Ok(command) = command::denormalize(&keybind.command) { - return command.execute(self); - } - } else { - return Err(LeftError::CommandNotFound); - } - Ok(()) - } - - fn get_keybind(&self, mask_key_pair: (u32, u32)) -> Option { - let keybinds = if let Some(keybinds) = &self.chord_ctx.keybinds { - keybinds - } else { - &self.keybinds - }; - keybinds - .iter() - .find(|keybind| { - if let Some(key) = xkeysym_lookup::into_keysym(&keybind.key) { - let mask = xkeysym_lookup::into_modmask(&keybind.modifier); - return mask_key_pair == (mask, key); - } - false - }) - .cloned() - } - - fn handle_mapping_notify(&self, event: &mut xlib::XMappingEvent) -> Error { - if event.request == xlib::MappingModifier || event.request == xlib::MappingKeyboard { - return self.xwrap.refresh_keyboard(event); - } - Ok(()) - } + // fn handle_event(&mut self, xlib_event: &xlib::XEvent) { + // let error = match xlib_event.get_type() { + // xlib::KeyPress => self.handle_key_press(&xlib::XKeyEvent::from(xlib_event)), + // xlib::MappingNotify => { + // self.handle_mapping_notify(&mut xlib::XMappingEvent::from(xlib_event)) + // } + // _ => return, + // }; + // errors::log_on_error!(error); + // } + // + // fn handle_key_press(&mut self, event: &xlib::XKeyEvent) -> Error { + // let key = self.xwrap.keycode_to_keysym(event.keycode); + // let mask = xkeysym_lookup::clean_mask(event.state); + // if let Some(keybind) = self.get_keybind((mask, key)) { + // if let Ok(command) = command::denormalize(&keybind.command) { + // return command.execute(self); + // } + // } else { + // return Err(LeftError::CommandNotFound); + // } + // Ok(()) + // } + // + // fn get_keybind(&self, mask_key_pair: (u32, u32)) -> Option { + // let keybinds = if let Some(keybinds) = &self.chord_ctx.keybinds { + // keybinds + // } else { + // &self.keybinds + // }; + // keybinds + // .iter() + // .find(|keybind| { + // if let Some(key) = xkeysym_lookup::into_keysym(&keybind.key) { + // let mask = xkeysym_lookup::into_modmask(&keybind.modifier); + // return mask_key_pair == (mask, key); + // } + // false + // }) + // .cloned() + // } + // + // fn handle_mapping_notify(&self, event: &mut xlib::XMappingEvent) -> Error { + // if event.request == xlib::MappingModifier || event.request == xlib::MappingKeyboard { + // return self.xwrap.refresh_keyboard(event); + // } + // Ok(()) + // } } diff --git a/lefthk/src/config/mod.rs b/lefthk/src/config/mod.rs index 3ae27a2..dcfb93c 100644 --- a/lefthk/src/config/mod.rs +++ b/lefthk/src/config/mod.rs @@ -70,7 +70,7 @@ pub fn load() -> Result { let file_name = path.place_config_file("config.ron")?; if Path::new(&file_name).exists() { let contents = fs::read_to_string(file_name)?; - Config::try_from(contents)?; + return Config::try_from(contents); } Err(LeftError::NoConfigFound) }