// client/src/input/keyboard.rs use std::collections::HashSet; use evdev::{Device, InputEvent, KeyCode, EventType}; use tracing::warn; 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, pressed_keys: HashSet, } impl KeyboardAggregator { pub fn new(dev: Device) -> Self { Self { dev, pressed_keys: HashSet::new(), } } /// Called frequently (e.g. every ~10ms) to fetch + handle events pub fn process_events(&mut self) { match self.dev.fetch_events() { Ok(events) => { for ev in events { self.handle_event(ev); } } Err(e) if e.kind() == std::io::ErrorKind::WouldBlock => { // nothing } Err(e) => { tracing::error!("Keyboard device read error: {e}"); } } } fn handle_event(&mut self, ev: InputEvent) { if ev.event_type() == EventType::KEY { let code = KeyCode::new(ev.code()); match ev.value() { 1 => { self.pressed_keys.insert(code); } 0 => { self.pressed_keys.remove(&code); } 2 => { /* repeats, if needed */ } _ => {} } let report = self.build_report(); // TODO: send this somewhere (e.g. an mpsc::Sender) // For now, just log: tracing::debug!(?report, "Keyboard HID report"); // optional: magic chord if self.is_magic_chord() { warn!("Magic chord pressed => exit aggregator??"); // Or do something else } } } 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 is_magic_chord(&self) -> bool { // example logic self.pressed_keys.contains(&KeyCode::KEY_LEFTCTRL) && self.pressed_keys.contains(&KeyCode::KEY_ESC) } }