161 lines
5.3 KiB
Rust
161 lines
5.3 KiB
Rust
|
|
use std::collections::HashSet;
|
||
|
|
use anyhow::{bail, Context, Result};
|
||
|
|
use evdev::{Device, InputEvent, KeyCode, EventType};
|
||
|
|
use tokio::sync::mpsc::Sender;
|
||
|
|
use tokio::time::{interval, Duration};
|
||
|
|
|
||
|
|
use navka_common::navka::HidReport;
|
||
|
|
use crate::input::keymap::{keycode_to_usage, is_modifier};
|
||
|
|
|
||
|
|
/// Up to 6 normal keys. Byte[0] = modifiers, Byte[1] = reserved, Byte[2..7] = pressed keys.
|
||
|
|
/// Magic chord example: LeftCtrl + LeftAlt + LeftShift + Esc
|
||
|
|
const MAGIC_CHORD: &[KeyCode] = &[
|
||
|
|
KeyCode::KEY_LEFTCTRL,
|
||
|
|
KeyCode::KEY_LEFTSHIFT,
|
||
|
|
KeyCode::KEY_LEFTALT,
|
||
|
|
KeyCode::KEY_ESC,
|
||
|
|
];
|
||
|
|
|
||
|
|
pub struct KeyboardAggregator {
|
||
|
|
// MPSC channel to server
|
||
|
|
tx: Sender<HidReport>,
|
||
|
|
// The list of evdev devices we are reading
|
||
|
|
devices: Vec<Device>,
|
||
|
|
// Pressed keys for building HID reports
|
||
|
|
pressed_keys: HashSet<KeyCode>,
|
||
|
|
}
|
||
|
|
|
||
|
|
impl KeyboardAggregator {
|
||
|
|
pub fn new(tx: Sender<HidReport>) -> Self {
|
||
|
|
Self {
|
||
|
|
tx,
|
||
|
|
devices: Vec::new(),
|
||
|
|
pressed_keys: HashSet::new(),
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
/// Discover all /dev/input/event* devices, filter ones with EV_KEY, and grab them.
|
||
|
|
pub fn init_devices(&mut self) -> Result<()> {
|
||
|
|
let paths = std::fs::read_dir("/dev/input")
|
||
|
|
.context("Failed to read /dev/input")?;
|
||
|
|
|
||
|
|
for entry in paths {
|
||
|
|
let entry = entry?;
|
||
|
|
let path = entry.path();
|
||
|
|
|
||
|
|
// skip anything that isn't "event*"
|
||
|
|
if !path.file_name().unwrap_or_default().to_string_lossy().starts_with("event") {
|
||
|
|
continue;
|
||
|
|
}
|
||
|
|
let mut dev = Device::open(&path).with_context(|| format!("opening {:?}", path))?;
|
||
|
|
|
||
|
|
let maybe_keys = dev.supported_keys();
|
||
|
|
if let Some(supported) = maybe_keys {
|
||
|
|
if supported.iter().next().is_none() {
|
||
|
|
// no real keys
|
||
|
|
continue;
|
||
|
|
}
|
||
|
|
} else {
|
||
|
|
// Not a keyboard at all
|
||
|
|
continue;
|
||
|
|
}
|
||
|
|
|
||
|
|
// Attempt to grab
|
||
|
|
dev.grab().with_context(|| format!("grabbing {:?}", path))?;
|
||
|
|
|
||
|
|
tracing::info!("Grabbed keyboard device: {:?}", path);
|
||
|
|
self.devices.push(dev);
|
||
|
|
}
|
||
|
|
if self.devices.is_empty() {
|
||
|
|
bail!("No keyboard devices found or none grabbed successfully.");
|
||
|
|
}
|
||
|
|
Ok(())
|
||
|
|
}
|
||
|
|
|
||
|
|
/// Main loop: read events from all devices in a round-robin style
|
||
|
|
/// building HID reports as needed. If MAGIC_CHORD is detected, exit.
|
||
|
|
pub async fn run(&mut self) -> Result<()> {
|
||
|
|
let mut tick = interval(Duration::from_millis(10));
|
||
|
|
|
||
|
|
loop {
|
||
|
|
// We'll poll each device in turn
|
||
|
|
for i in 0..self.devices.len() {
|
||
|
|
// Non-blocking fetch
|
||
|
|
let evs = match self.devices[i].fetch_events() {
|
||
|
|
Ok(iter) => iter.collect::<Vec<_>>(),
|
||
|
|
Err(e) if e.kind() == std::io::ErrorKind::WouldBlock => Vec::new(),
|
||
|
|
Err(e) => {
|
||
|
|
tracing::error!("Device read error: {e}");
|
||
|
|
Vec::new()
|
||
|
|
}
|
||
|
|
};
|
||
|
|
for ev in evs {
|
||
|
|
self.handle_event(ev)?;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
if self.is_magic_chord() {
|
||
|
|
tracing::warn!("Magic chord pressed; stopping navka-client.");
|
||
|
|
// ungrab all before exit
|
||
|
|
for dev in &mut self.devices {
|
||
|
|
let _ = dev.ungrab();
|
||
|
|
}
|
||
|
|
std::process::exit(0);
|
||
|
|
}
|
||
|
|
tick.tick().await;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
fn handle_event(&mut self, ev: InputEvent) -> Result<()> {
|
||
|
|
// We only care about KEY events
|
||
|
|
if ev.event_type() == EventType::KEY {
|
||
|
|
let code = KeyCode::new(ev.code());
|
||
|
|
let value = ev.value();
|
||
|
|
match value {
|
||
|
|
1 => { self.pressed_keys.insert(code); }
|
||
|
|
0 => { self.pressed_keys.remove(&code); }
|
||
|
|
2 => { /* repeats, if needed */ }
|
||
|
|
_ => {}
|
||
|
|
}
|
||
|
|
|
||
|
|
// Build new HID report
|
||
|
|
let report_bytes = self.build_hid_report();
|
||
|
|
let _ = self.tx.try_send(HidReport {
|
||
|
|
data: report_bytes.to_vec(),
|
||
|
|
});
|
||
|
|
}
|
||
|
|
Ok(())
|
||
|
|
}
|
||
|
|
|
||
|
|
fn build_hid_report(&self) -> [u8; 8] {
|
||
|
|
// Byte 0: modifiers (bitmask)
|
||
|
|
// Byte 1: reserved
|
||
|
|
// Byte 2..7: up to 6 keys
|
||
|
|
// We'll do a naive approach: gather up to 6 non-modifier codes.
|
||
|
|
let mut report = [0u8; 8];
|
||
|
|
let mut normal_keys = Vec::new();
|
||
|
|
let mut modifier_mask = 0u8;
|
||
|
|
|
||
|
|
for &key in &self.pressed_keys {
|
||
|
|
if let Some(modbits) = is_modifier(key) {
|
||
|
|
// e.g. KEY_LEFTSHIFT => 0x02
|
||
|
|
modifier_mask |= modbits;
|
||
|
|
} else if let Some(usage) = keycode_to_usage(key) {
|
||
|
|
// Normal key
|
||
|
|
normal_keys.push(usage);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
report[0] = modifier_mask;
|
||
|
|
// Byte[1] is reserved 0
|
||
|
|
for (i, code) in normal_keys.into_iter().take(6).enumerate() {
|
||
|
|
report[2 + i] = code;
|
||
|
|
}
|
||
|
|
report
|
||
|
|
}
|
||
|
|
|
||
|
|
fn is_magic_chord(&self) -> bool {
|
||
|
|
// All the keys in MAGIC_CHORD must be pressed
|
||
|
|
MAGIC_CHORD.iter().all(|k| self.pressed_keys.contains(k))
|
||
|
|
}
|
||
|
|
}
|