// 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, dev_mode: bool, pressed_keys: HashSet, } impl KeyboardAggregator { pub fn new(dev: Device, dev_mode: bool, tx: Sender) -> 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 = 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) } }