#[cfg(target_os = "linux")] mod linux { use evdev::{Device, EventSummary, KeyCode}; use std::collections::HashSet; use std::fs::{self, OpenOptions}; use std::os::unix::io::AsRawFd; use std::path::Path; pub struct EvdevInput { devices: Vec, held_keys: HashSet, } impl EvdevInput { pub fn new() -> std::io::Result { let mut devices = Vec::new(); // Find all keyboard devices in /dev/input let input_dir = Path::new("/dev/input"); if let Ok(entries) = fs::read_dir(input_dir) { for entry in entries.flatten() { let path = entry.path(); if let Some(name) = path.file_name() { if name.to_string_lossy().starts_with("event") { if let Ok(file) = OpenOptions::new().read(true).open(&path) { // Set non-blocking mode let fd = file.as_raw_fd(); unsafe { let flags = libc::fcntl(fd, libc::F_GETFL, 0); if flags != -1 { libc::fcntl(fd, libc::F_SETFL, flags | libc::O_NONBLOCK); } } if let Ok(device) = Device::from_fd(file.into()) { // Check if this device supports keyboard events if device.supported_keys().map_or(false, |keys| { keys.contains(KeyCode::KEY_A) || keys.contains(KeyCode::KEY_SPACE) }) { devices.push(device); } } } } } } } if devices.is_empty() { eprintln!("Warning: No keyboard devices found. Make sure you have read permissions for /dev/input/event* devices."); eprintln!("You may need to add your user to the 'input' group or run with appropriate permissions."); } Ok(Self { devices, held_keys: HashSet::new(), }) } pub fn query_keymap(&mut self) -> HashSet { // Process events from all devices for device in &mut self.devices { while let Ok(events) = device.fetch_events() { for event in events { if let EventSummary::Key(_, key, value) = event.destructure() { let code = key.code(); match value { 0 => { // Key released self.held_keys.remove(&code); } 1 | 2 => { // Key pressed (1) or repeated (2) self.held_keys.insert(code); } _ => {} } } } } } self.held_keys.clone() } } // Key code constants #[allow(dead_code)] pub const KEY_A: u16 = KeyCode::KEY_A.code(); pub const KEY_C: u16 = KeyCode::KEY_C.code(); pub const KEY_CAPSLOCK: u16 = KeyCode::KEY_CAPSLOCK.code(); pub const KEY_DELETE: u16 = KeyCode::KEY_DELETE.code(); pub const KEY_BACKSPACE: u16 = KeyCode::KEY_BACKSPACE.code(); pub const KEY_UP: u16 = KeyCode::KEY_UP.code(); pub const KEY_DOWN: u16 = KeyCode::KEY_DOWN.code(); pub const KEY_LEFT: u16 = KeyCode::KEY_LEFT.code(); pub const KEY_RIGHT: u16 = KeyCode::KEY_RIGHT.code(); pub const KEY_LEFTSHIFT: u16 = KeyCode::KEY_LEFTSHIFT.code(); pub const KEY_RIGHTSHIFT: u16 = KeyCode::KEY_RIGHTSHIFT.code(); pub const KEY_LEFTCTRL: u16 = KeyCode::KEY_LEFTCTRL.code(); pub const KEY_RIGHTCTRL: u16 = KeyCode::KEY_RIGHTCTRL.code(); pub const KEY_LEFTALT: u16 = KeyCode::KEY_LEFTALT.code(); pub const KEY_RIGHTALT: u16 = KeyCode::KEY_RIGHTALT.code(); pub const KEY_LEFTMETA: u16 = KeyCode::KEY_LEFTMETA.code(); pub const KEY_RIGHTMETA: u16 = KeyCode::KEY_RIGHTMETA.code(); pub fn is_modifier_key(key: u16) -> bool { matches!( key, KEY_LEFTSHIFT | KEY_RIGHTSHIFT | KEY_LEFTCTRL | KEY_RIGHTCTRL | KEY_RIGHTALT | KEY_LEFTALT | KEY_RIGHTMETA | KEY_LEFTMETA | KEY_CAPSLOCK ) } } #[cfg(not(target_os = "linux"))] mod fallback { use device_query::{DeviceQuery, DeviceState, Keycode}; use std::collections::HashSet; pub struct DeviceQueryInput { state: DeviceState, } impl DeviceQueryInput { pub fn new() -> Self { Self { state: DeviceState::new(), } } pub fn query_keymap(&mut self) -> HashSet { self.state .query_keymap() .into_iter() .map(keycode_to_u16) .collect() } } fn keycode_to_u16(keycode: Keycode) -> u16 { // Map device_query keycodes to arbitrary u16 values // We use values that won't collide with Linux key codes match keycode { Keycode::A => 1000, Keycode::B => 1001, Keycode::C => 1002, Keycode::D => 1003, Keycode::E => 1004, Keycode::F => 1005, Keycode::G => 1006, Keycode::H => 1007, Keycode::I => 1008, Keycode::J => 1009, Keycode::K => 1010, Keycode::L => 1011, Keycode::M => 1012, Keycode::N => 1013, Keycode::O => 1014, Keycode::P => 1015, Keycode::Q => 1016, Keycode::R => 1017, Keycode::S => 1018, Keycode::T => 1019, Keycode::U => 1020, Keycode::V => 1021, Keycode::W => 1022, Keycode::X => 1023, Keycode::Y => 1024, Keycode::Z => 1025, Keycode::CapsLock => 1026, Keycode::Delete => 1027, Keycode::Backspace => 1028, Keycode::Up => 1029, Keycode::Down => 1030, Keycode::Left => 1031, Keycode::Right => 1032, Keycode::LShift => 1033, Keycode::RShift => 1034, Keycode::LControl => 1035, Keycode::RControl => 1036, Keycode::LAlt => 1037, Keycode::RAlt => 1038, Keycode::LMeta => 1039, Keycode::RMeta => 1040, _ => { // For any other key, use a hash of the debug string use std::collections::hash_map::DefaultHasher; use std::hash::{Hash, Hasher}; let mut hasher = DefaultHasher::new(); format!("{:?}", keycode).hash(&mut hasher); (hasher.finish() as u16).wrapping_add(2000) } } } // Key code constants - matching the values in keycode_to_u16 #[allow(dead_code)] pub const KEY_A: u16 = 1000; pub const KEY_C: u16 = 1002; pub const KEY_CAPSLOCK: u16 = 1026; pub const KEY_DELETE: u16 = 1027; pub const KEY_BACKSPACE: u16 = 1028; pub const KEY_UP: u16 = 1029; pub const KEY_DOWN: u16 = 1030; pub const KEY_LEFT: u16 = 1031; pub const KEY_RIGHT: u16 = 1032; pub const KEY_LEFTSHIFT: u16 = 1033; pub const KEY_RIGHTSHIFT: u16 = 1034; pub const KEY_LEFTCTRL: u16 = 1035; pub const KEY_RIGHTCTRL: u16 = 1036; pub const KEY_LEFTALT: u16 = 1037; pub const KEY_RIGHTALT: u16 = 1038; pub const KEY_LEFTMETA: u16 = 1039; pub const KEY_RIGHTMETA: u16 = 1040; pub fn is_modifier_key(key: u16) -> bool { matches!( key, KEY_LEFTSHIFT | KEY_RIGHTSHIFT | KEY_LEFTCTRL | KEY_RIGHTCTRL | KEY_RIGHTALT | KEY_LEFTALT | KEY_RIGHTMETA | KEY_LEFTMETA | KEY_CAPSLOCK ) } } #[cfg(target_os = "linux")] pub use linux::*; #[cfg(not(target_os = "linux"))] pub use fallback::*; #[cfg(target_os = "linux")] pub type Input = linux::EvdevInput; #[cfg(not(target_os = "linux"))] pub type Input = fallback::DeviceQueryInput; pub fn create_input() -> std::io::Result { Input::new() }