// This code is a port of the libxkbcommon "interactive-evdev.c" example // https://github.com/xkbcommon/libxkbcommon/blob/master/tools/interactive-evdev.c use anyhow::Result; use libc::{input_event, size_t, ssize_t, EWOULDBLOCK, O_CLOEXEC, O_NONBLOCK, O_RDONLY}; use log::trace; use scopeguard::ScopeGuard; use std::collections::HashMap; use std::os::raw::c_char; use std::os::unix::io::AsRawFd; use std::{ ffi::{c_void, CStr}, fs::OpenOptions, }; use std::{fs::File, os::unix::fs::OpenOptionsExt}; use thiserror::Error; use super::sync::ModifiersState; use super::{ ffi::{ is_keyboard_or_mouse, xkb_key_direction, xkb_keycode_t, xkb_keymap_key_repeats, xkb_state, xkb_state_get_keymap, xkb_state_key_get_one_sym, xkb_state_key_get_utf8, xkb_state_new, xkb_state_unref, xkb_state_update_key, EV_KEY, }, keymap::Keymap, }; const EVDEV_OFFSET: i32 = 8; pub const KEY_STATE_RELEASE: i32 = 0; pub const KEY_STATE_PRESS: i32 = 1; pub const KEY_STATE_REPEAT: i32 = 2; #[derive(Debug)] pub enum RawInputEvent { Keyboard(RawKeyboardEvent), Mouse(RawMouseEvent), } #[derive(Debug)] pub struct RawKeyboardEvent { pub sym: u32, pub code: u32, pub value: String, pub state: i32, } #[derive(Debug)] pub struct RawMouseEvent { pub code: u16, pub is_down: bool, } pub struct Device { path: String, file: File, state: *mut xkb_state, } impl Device { pub fn from(path: &str, keymap: &Keymap) -> Result { let file = OpenOptions::new() .read(true) .custom_flags(O_NONBLOCK | O_CLOEXEC | O_RDONLY) .open(&path)?; if unsafe { is_keyboard_or_mouse(file.as_raw_fd()) == 0 } { return Err(DeviceError::InvalidDevice(path.to_string()).into()); } let raw_state = unsafe { xkb_state_new(keymap.get_handle()) }; // Automatically close the state if the function does not return correctly let state = scopeguard::guard(raw_state, |raw_state| unsafe { xkb_state_unref(raw_state); }); if raw_state.is_null() { return Err(DeviceError::InvalidState(path.to_string()).into()); } Ok(Self { path: path.to_string(), file, // Release the state without freeing it state: ScopeGuard::into_inner(state), }) } pub fn get_state(&self) -> *mut xkb_state { self.state } pub fn get_raw_fd(&self) -> i32 { self.file.as_raw_fd() } pub fn get_path(&self) -> String { self.path.to_string() } pub fn read(&self) -> Result> { let errno_ptr = unsafe { libc::__errno_location() }; let mut len: ssize_t; let mut evs: [input_event; 16] = unsafe { std::mem::zeroed() }; let mut events = Vec::new(); loop { len = unsafe { libc::read( self.file.as_raw_fd(), evs.as_mut_ptr() as *mut c_void, std::mem::size_of_val(&evs), ) }; if len <= 0 { break; } let nevs: size_t = len as usize / std::mem::size_of::(); #[allow(clippy::needless_range_loop)] for i in 0..nevs { let event = self.process_event(evs[i].type_, evs[i].code, evs[i].value); if let Some(event) = event { events.push(event); } } } if len < 0 && unsafe { *errno_ptr } != EWOULDBLOCK { return Err(DeviceError::BlockingReadOperation().into()); } Ok(events) } fn process_event(&self, _type: u16, code: u16, value: i32) -> Option { if _type != EV_KEY { return None; } let is_down = value == KEY_STATE_PRESS; // Check if the current event originated from a mouse if (0x110..=0x117).contains(&code) { // Mouse event return Some(RawInputEvent::Mouse(RawMouseEvent { code, is_down })); } // Keyboard event let keycode: xkb_keycode_t = EVDEV_OFFSET as u32 + code as u32; let keymap = unsafe { xkb_state_get_keymap(self.get_state()) }; if value == KEY_STATE_REPEAT && unsafe { xkb_keymap_key_repeats(keymap, keycode) } == 0 { return None; } let sym = unsafe { xkb_state_key_get_one_sym(self.get_state(), keycode) }; if sym == 0 { return None; } // Extract the utf8 char let mut buffer: [c_char; 16] = [0; 16]; unsafe { xkb_state_key_get_utf8( self.get_state(), keycode, buffer.as_mut_ptr(), std::mem::size_of_val(&buffer), ) }; let content_raw = unsafe { CStr::from_ptr(buffer.as_ptr()) }; let content = content_raw.to_string_lossy().to_string(); let event = RawKeyboardEvent { state: value, code: keycode, sym, value: content, }; if value == KEY_STATE_RELEASE { unsafe { xkb_state_update_key(self.get_state(), keycode, xkb_key_direction::UP) }; } else { unsafe { xkb_state_update_key(self.get_state(), keycode, xkb_key_direction::DOWN) }; } Some(RawInputEvent::Keyboard(event)) } pub fn update_key(&mut self, code: u32, pressed: bool) { let direction = if pressed { super::ffi::xkb_key_direction::DOWN } else { super::ffi::xkb_key_direction::UP }; unsafe { xkb_state_update_key(self.get_state(), code, direction); } } pub fn update_modifier_state( &mut self, modifiers_state: &ModifiersState, modifiers_map: &HashMap, ) { if modifiers_state.alt { self.update_key( *modifiers_map .get("alt") .expect("unable to find modifiers key in map"), true, ); } if modifiers_state.ctrl { self.update_key( *modifiers_map .get("ctrl") .expect("unable to find modifiers key in map"), true, ); } if modifiers_state.meta { self.update_key( *modifiers_map .get("meta") .expect("unable to find modifiers key in map"), true, ); } if modifiers_state.num_lock { self.update_key( *modifiers_map .get("num_lock") .expect("unable to find modifiers key in map"), true, ); self.update_key( *modifiers_map .get("num_lock") .expect("unable to find modifiers key in map"), false, ); } if modifiers_state.shift { self.update_key( *modifiers_map .get("shift") .expect("unable to find modifiers key in map"), true, ); } if modifiers_state.caps_lock { self.update_key( *modifiers_map .get("caps_lock") .expect("unable to find modifiers key in map"), true, ); self.update_key( *modifiers_map .get("caps_lock") .expect("unable to find modifiers key in map"), false, ); } } } impl Drop for Device { fn drop(&mut self) { unsafe { xkb_state_unref(self.state); } } } pub fn get_devices(keymap: &Keymap) -> Result> { let mut keyboards = Vec::new(); let dirs = std::fs::read_dir("/dev/input/")?; for entry in dirs { match entry { Ok(device) => { // Skip non-eventX devices if !device.file_name().to_string_lossy().starts_with("event") { continue; } let path = device.path().to_string_lossy().to_string(); let keyboard = Device::from(&path, keymap); match keyboard { Ok(keyboard) => { keyboards.push(keyboard); } Err(error) => { trace!("error opening keyboard: {}", error); } } } Err(error) => { trace!("could not read keyboard device: {}", error); } } } if keyboards.is_empty() { return Err(DeviceError::NoDevicesFound().into()); } Ok(keyboards) } #[derive(Error, Debug)] pub enum DeviceError { #[error("could not create xkb state for `{0}`")] InvalidState(String), #[error("`{0}` is not a valid device")] InvalidDevice(String), #[error("no devices found")] NoDevicesFound(), #[error("read operation can't block device")] BlockingReadOperation(), }