From fa202be7da9428477084896239fa63eb33727d9e Mon Sep 17 00:00:00 2001 From: moparisthebest Date: Tue, 21 Sep 2021 23:56:24 -0400 Subject: [PATCH] Determine keyboard devices by whether they support KEY_A or not --- src/lib.rs | 1 + src/linux/mod.rs | 111 ++++++++++++++++++++++++++++++----------------- 2 files changed, 73 insertions(+), 39 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 0da995b..850fd90 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,5 +1,6 @@ #![recursion_limit = "1000"] +pub const NAME: &'static str = env!("CARGO_PKG_NAME"); pub const VERSION: &'static str = env!("CARGO_PKG_VERSION"); pub mod error; diff --git a/src/linux/mod.rs b/src/linux/mod.rs index 9ecbe30..71bfb03 100644 --- a/src/linux/mod.rs +++ b/src/linux/mod.rs @@ -23,6 +23,8 @@ use std::{env, thread}; use std::sync::mpsc; use std::sync::mpsc::Sender; +const INPUT_FOLDER: &str = "/dev/input/"; + // 1 is down, 0 is up const DOWN: i32 = 1; const UP: i32 = 0; @@ -123,7 +125,7 @@ pub fn main_res() -> Result<()> { let device = open("/dev/uinput") .or_else(|_| open("/dev/input/uinput")) .or_else(|_| default())? - .name("rusty-keys")? + .name(NAME)? .event(key_map.values())? .create()?; @@ -161,7 +163,7 @@ pub fn main_res() -> Result<()> { // we want to wait forever starting new threads for any new keyboard devices let mut inotify = Inotify::init().expect("Failed to initialize inotify"); - inotify.add_watch("/dev/input/", WatchMask::CREATE).expect("Failed to add inotify watch"); + inotify.add_watch(INPUT_FOLDER, WatchMask::CREATE).expect("Failed to add inotify watch"); let device_files = get_keyboard_device_filenames(); println!("Detected devices: {:?}", device_files); @@ -178,12 +180,14 @@ pub fn main_res() -> Result<()> { if !event.mask.contains(EventMask::ISDIR) { if let Some(device_file) = event.name.and_then(|name|name.to_str()) { // check if this is an eligible keyboard device - let device_files = get_keyboard_device_filenames(); - if !device_files.contains(&device_file.to_string()) { - continue; + let mut path = std::path::PathBuf::new(); + path.push(INPUT_FOLDER); + path.push(device_file); + + if valid_keyboard_device(path) { + println!("starting mapping thread for: {}", device_file); + inotify_spawn_thread(&tx, device_file.clone()); } - println!("starting mapping thread for: {}", device_file); - inotify_spawn_thread(&tx, device_file.clone()); } } } @@ -210,7 +214,7 @@ fn send_event(key_map: &mut LinuxKeyMaps, mut event: input_event, device: &Devic } fn inotify_spawn_thread(tx: &Sender, device_file: &str) { - let mut filename = "/dev/input/".to_string(); + let mut filename = INPUT_FOLDER.to_string(); filename.push_str(&device_file); let tx = tx.clone(); thread::spawn(move || { @@ -256,7 +260,7 @@ fn parse_args() -> Config { } if matches.opt_present("v") { - println!("rusty-keys {}", VERSION); + println!("{} {}", NAME, VERSION); exit(0); } @@ -265,41 +269,69 @@ fn parse_args() -> Config { Config::new(matches.free, config_file) } -// Detects and returns the name of the keyboard device file. This function uses -// the fact that all device information is shown in /proc/bus/input/devices and -// the keyboard device file should always have an EV of 120013 -// grep -E 'Handlers|EV' /proc/bus/input/devices | grep -B1 120013 | grep -Eo event[0-9]+ -fn get_keyboard_device_filenames() -> Vec { - use std::io::BufReader; - use std::io::prelude::*; - use std::fs::File; +nix::ioctl_read_buf!(eviocgname, b'E', 0x06, u8); +nix::ioctl_read_buf!(eviocgbit, b'E', 0x20, u8); +nix::ioctl_read_buf!(eviocgbit_ev_key, b'E', 0x20 + EV_KEY, u8); - let f = File::open("/proc/bus/input/devices"); - if f.is_err() { - return Vec::new(); +fn valid_keyboard_device_res>(path: P) -> Result { + use std::fs::File; + use std::os::unix::fs::FileTypeExt; + use std::os::unix::io::AsRawFd; + + let device_file = File::open(path)?; + + // must be a character device + if !device_file.metadata()?.file_type().is_char_device() { + return Ok(false); } - let f = BufReader::new(f.unwrap()); - let mut filename = None; - let mut filenames = Vec::new(); - for line in f.lines() { - if let Ok(line) = line { - if line.starts_with("H: Handlers=") { - if let Some(event_index) = line.find("event") { - let last_index = line[event_index..line.len()-1].find(" ").and_then(|i| Some(i + event_index)).unwrap_or(line.len() - 1); - filename = Some(line[event_index..last_index].to_owned()); - } - // the following line checked for only 120013 for years, which works across every device I've tested, dozens at least - // until I bought a Monoprice Dark Matter Aether keyboard which presents 2 keyboard devices, one with 120013, - // another with 100013 the one with 120013 can only be read from a few times before it stops sending events, then events - // start coming from 100013, if I lock+read them both, it *just works* as expected... buggy keyboard firmware or ? - } else if line.starts_with("B: EV=") && (line.contains("120013") || line.contains("100013")) { - if let Some(ref filename) = filename { - filenames.push(filename.clone()); + + // does it support EV_KEY + let mut evbit = [0u8; 8]; + unsafe { + eviocgbit(device_file.as_raw_fd(), &mut evbit)?; + }; + let evbit = u64::from_ne_bytes(evbit); + if (evbit & (1 << EV_KEY)) == 0 { + return Ok(false); + } + + // does it support KEY_A ? todo: check other keys ? + let mut key_bits = [0u8; (KEY_MAX as usize / 8) + 1]; + unsafe { + eviocgbit_ev_key(device_file.as_raw_fd(), &mut key_bits)?; + }; + if (key_bits[KEY_A as usize / 8] & (1 << (KEY_A % 8))) == 0 { + return Ok(false); + } + + // is it another running copy of rusty-keys ? + let mut name = [0u8; NAME.len()]; + unsafe { + eviocgname(device_file.as_raw_fd(), &mut name)? + }; + if NAME.as_bytes() == &name { + return Ok(false); + } + return Ok(true); +} + +fn valid_keyboard_device>(path: P) -> bool { + valid_keyboard_device_res(path).unwrap_or(false) +} + +fn get_keyboard_device_filenames() -> Vec { + let mut res = Vec::new(); + if let Ok(entries) = std::fs::read_dir(INPUT_FOLDER) { + for entry in entries { + if let Ok(entry) = entry { + if valid_keyboard_device(entry.path()) { + // these unwrap()'s should not be able to fail if valid_keyboard_device() returns true + res.push(entry.path().file_name().unwrap().to_str().unwrap().to_owned()); } } } } - filenames + res } pub fn key_map() -> HashMap<&'static str, u16> { @@ -611,4 +643,5 @@ pub fn key_map() -> HashMap<&'static str, u16> { ("PDOT", KEY_KPDOT), ("PENT", KEY_KPENTER), ].iter().cloned().map(|(m, v)| (m, v as u16)).collect() - } \ No newline at end of file + } +