// 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; 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(), } } /// 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) => { error!("Keyboard device read error: {e}"); return; } } }; if self.dev_mode && !events.is_empty() { 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 { 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: debug!(?report, "Keyboard HID report"); self.send_report(report); // optional: magic chord if self.is_magic_chord() { warn!("Magic chord pressed => exit aggregator??"); // Or do something else 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 _ = self.tx.send(HidReport { kind: Some(hid_report::Kind::KeyboardReport(report.to_vec())), }); } fn is_magic_chord(&self) -> bool { self.pressed_keys.contains(&KeyCode::KEY_LEFTCTRL) && self.pressed_keys.contains(&KeyCode::KEY_ESC) } }