lesavka/client/src/input/keyboard.rs
2025-06-26 14:05:23 -05:00

86 lines
3.0 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// client/src/input/keyboard.rs
use std::{collections::HashSet, sync::atomic::{AtomicU32, Ordering}};
use evdev::{Device, EventType, InputEvent, KeyCode};
use tokio::sync::broadcast::Sender;
use tracing::{debug, error, warn, trace};
use lesavka_common::lesavka::KeyboardReport;
use super::keymap::{is_modifier, keycode_to_usage};
pub struct KeyboardAggregator {
dev: Device,
tx: Sender<KeyboardReport>,
dev_mode: bool,
pressed_keys: HashSet<KeyCode>,
}
/*───────── helpers ───────────────────────────────────────────────────*/
/// Monotonicallyincreasing ID that can be logged on server & client.
static SEQ: AtomicU32 = AtomicU32::new(0);
impl KeyboardAggregator {
pub fn new(dev: Device, dev_mode: bool, tx: Sender<KeyboardReport>) -> Self {
let _ = dev.set_nonblocking(true);
Self { dev, tx, dev_mode, pressed_keys: HashSet::new() }
}
pub fn process_events(&mut self) {
// --- first fetch, then log (avoids aliasing borrow) ---
let events: Vec<InputEvent> = match self.dev.fetch_events() {
Ok(it) => it.collect(),
Err(e) if e.kind() == std::io::ErrorKind::WouldBlock => return,
Err(e) => { if self.dev_mode { error!("read error: {e}"); } return }
};
if self.dev_mode && !events.is_empty() {
trace!("{} 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_keys.insert(code); } // press
0 => { self.pressed_keys.remove(&code); } // release
_ => {}
}
let report = self.build_report();
// Generate a local sequence number for debugging/logmerge only.
let id = SEQ.fetch_add(1, Ordering::Relaxed);
if self.dev_mode { debug!(seq = id, ?report, "kbd"); }
let _ = self.tx.send(KeyboardReport {
data: report.to_vec()
});
if self.is_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_keys {
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
}
#[inline]
fn is_magic(&self) -> bool {
self.pressed_keys.contains(&KeyCode::KEY_LEFTCTRL)
&& self.pressed_keys.contains(&KeyCode::KEY_ESC)
}
}