// 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::KeyboardReport; 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) { let events: Vec = match self.dev.fetch_events() { Ok(it) => it.collect(), Err(e) if e.kind() == std::io::ErrorKind::WouldBlock => return, Err(e) => { self.d(|| error!("read err: {e}")); return } }; if self.dev_mode && !events.is_empty() { self.d(|| info!("{} kbd evts from {}", events.len(), self.dev.name().unwrap_or("???"))); } for ev in events { if ev.event_type() != EventType::KEY { continue } let code = KeyCode::new(ev.code()); match ev.value() { 1 => { self.pressed.insert(code); } // press 0 => { self.pressed.remove(&code); } // release _ => {} } let report = self.build_report(); self.d(|| debug!(?report, "kbd report")); self.send_report(report); if self.magic() { warn!("🧙 magic chord, exiting 🪄 AVADA KEDAVRA!!! 💥💀"); std::process::exit(0) } } } fn build_report(&self) -> [u8; 8] { let mut out = [0u8; 8]; let mut mods = 0u8; let mut keys = Vec::new(); for &kc in &self.pressed { if let Some(m) = is_modifier(kc) { mods |= m } else if let Some(u) = keycode_to_usage(kc) { keys.push(u) } } out[0] = mods; for (i, k) in keys.into_iter().take(6).enumerate() { out[2+i] = k } out } fn send_report(&self, report: [u8; 8]) { let msg = KeyboardReport { data: 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); } } } #[inline] fn magic(&self) -> bool { self.pressed.contains(&KeyCode::KEY_LEFTCTRL) && self.pressed.contains(&KeyCode::KEY_ESC) } }