328 lines
7.9 KiB
Rust
328 lines
7.9 KiB
Rust
// 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<Device> {
|
|
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<Vec<RawInputEvent>> {
|
|
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::<input_event>();
|
|
|
|
#[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<RawInputEvent> {
|
|
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<String, u32>,
|
|
) {
|
|
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<Vec<Device>> {
|
|
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(),
|
|
}
|