lesavka/client/src/input/keyboard.rs
2025-06-16 18:11:14 -05:00

143 lines
4.2 KiB
Rust

// client/src/input/keyboard.rs
use std::collections::HashSet;
use evdev::{Device, InputEvent, KeyCode, EventType};
use tokio::sync::broadcast::Sender;
use tracing::{warn, error, info, debug};
use navka_common::navka::{HidReport, hid_report};
use crate::input::keymap::{keycode_to_usage, is_modifier};
/// The aggregator logic for a single keyboard device.
pub struct KeyboardAggregator {
dev: Device,
tx: Sender<HidReport>,
dev_mode: bool,
pressed_keys: HashSet<KeyCode>,
}
impl KeyboardAggregator {
pub fn new(dev: Device, dev_mode: bool, tx: Sender<HidReport>) -> Self {
let _ = dev.set_nonblocking(true);
Self {
dev,
tx,
dev_mode,
pressed_keys: HashSet::new(),
}
}
#[inline]
fn dev_log(&self, record: impl FnOnce()) {
if self.dev_mode {
record();
}
}
/// Called frequently (e.g. every ~10ms) to fetch + handle events
pub fn process_events(&mut self) {
// Fetch once. Any borrow of `self.dev` ends right here.
let events: Vec<InputEvent> = match self.dev.fetch_events() {
Ok(iter) => iter.collect(),
// Would-block → nothing new
Err(ref e) if e.kind() == std::io::ErrorKind::WouldBlock => return,
// Any other error → log without touching `self.dev`
Err(e) => {
if self.dev_mode {
error!("Keyboard device read error: {e}");
}
return;
}
};
// Safe: no mutable borrow is active now
if self.dev_mode && !events.is_empty() {
self.dev_log(|| info!(
"Got {} events from dev: {}",
events.len(),
self.dev.name().unwrap_or("???")
));
}
for ev in events {
self.handle_event(ev);
}
}
fn handle_event(&mut self, ev: InputEvent) {
if ev.event_type() == EventType::KEY {
let code = KeyCode::new(ev.code());
let val = ev.value(); // 1 = press, 0 = release, 2 = repeat
if self.dev_mode {
self.dev_log(|| info!(
"Keyboard event: code={:?}, value={}, name={:?}",
code, val, self.dev.name()
));
}
match val {
1 => {self.pressed_keys.insert(code);}
0 => {self.pressed_keys.remove(&code);}
2 => {/* repeat */}
_ => {}
}
let report = self.build_report();
// TODO: send this somewhere (e.g. an mpsc::Sender)
// For now, just log:
self.dev_log(|| debug!(?report, "Keyboard HID report"));
self.send_report(report);
if self.is_magic_chord() {
self.dev_log(|| warn!("Magic chord pressed => 🧙‍♂️🪄💥 AVADA KEDAVRA!!! 💀"));
std::process::exit(0);
}
}
}
fn build_report(&self) -> [u8; 8] {
let mut bytes = [0u8; 8];
let mut normal_keys = Vec::new();
let mut modifiers = 0u8;
for &kc in &self.pressed_keys {
if let Some(m) = is_modifier(kc) {
modifiers |= m;
} else if let Some(usage) = keycode_to_usage(kc) {
normal_keys.push(usage);
}
}
bytes[0] = modifiers;
for (i, keycode) in normal_keys.into_iter().take(6).enumerate() {
bytes[2 + i] = keycode;
}
bytes
}
fn send_report(&self, report: [u8; 8]) {
let msg = HidReport {
kind: Some(hid_report::Kind::KeyboardReport(report.to_vec())),
};
match self.tx.send(msg.clone()) {
Ok(n) => {
self.dev_log(|| info!("📤 sent HID report → {n} subscriber(s)"));
}
Err(e) => {
self.dev_log(|| warn!("❌ send failed: {e}"));
let _ = self.tx.send(msg);
}
}
}
fn is_magic_chord(&self) -> bool {
self.pressed_keys.contains(&KeyCode::KEY_LEFTCTRL)
&& self.pressed_keys.contains(&KeyCode::KEY_ESC)
}
}