// client/src/input/mouse.rs use anyhow::Result; use evdev::{Device, InputEvent, EventType, KeyCode, RelativeAxisCode}; use tokio::sync::broadcast::{self, Sender}; use tracing::{debug, error, warn}; use navka_common::navka::{HidReport, hid_report}; /// Aggregator for a single mouse device pub struct MouseAggregator { dev: Device, tx: Sender, dev_mode: bool, buttons: u8, dx: i8, dy: i8, wheel: i8, } impl MouseAggregator { pub fn new(dev: Device, dev_mode: bool, tx: Sender) -> Self { Self { dev, tx, dev_mode, buttons: 0, dx: 0, dy: 0, wheel: 0, } } #[inline] fn dev_log(&self, record: impl FnOnce()) { if self.dev_mode { record(); } } #[inline] fn set_btn(&mut self, bit: u8, val: i32) { if val != 0 { self.buttons |= 1 << bit; } else { self.buttons &= !(1 << bit); } } pub fn process_events(&mut self) { /* 1 ─ read a non‑blocking batch */ let events: Vec = match self.dev.fetch_events() { Ok(it) => it.collect(), Err(ref 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() { self.dev_log(|| debug!("🖱️ {} events from {}", events.len(), self.dev.name().unwrap_or("UNKNOWN"))); } /* 2 ─ aggregate */ for ev in events { match ev.event_type() { /* ---------- buttons ---------- */ EventType::KEY => match ev.code() { c if c == KeyCode::BTN_LEFT.0 => self.set_btn(0, ev.value()), c if c == KeyCode::BTN_RIGHT.0 => self.set_btn(1, ev.value()), c if c == KeyCode::BTN_MIDDLE.0 => self.set_btn(2, ev.value()), _ => {} }, /* ----------- axes ------------ */ EventType::RELATIVE => match ev.code() { c if c == RelativeAxisCode::REL_X.0 => { self.dx = self.dx.saturating_add(ev.value().clamp(-127, 127) as i8); } c if c == RelativeAxisCode::REL_Y.0 => { self.dy = self.dy.saturating_add(ev.value().clamp(-127, 127) as i8); } c if c == RelativeAxisCode::REL_WHEEL.0 => { self.wheel = self.wheel.saturating_add(ev.value().clamp(-1, 1) as i8); } _ => {} }, /* ---- batch delimiter -------- */ EventType::SYNCHRONIZATION => { // Any sync event is fine – we only care about boundaries self.flush_report(); } _ => {} } } } /// Build & send HID packet, then clear deltas fn flush_report(&mut self) { /* Nothing changed ⇒ nothing to send */ if self.dx == 0 && self.dy == 0 && self.wheel == 0 { return; } let report = [ self.buttons, self.dx.clamp(-127, 127) as u8, self.dy.clamp(-127, 127) as u8, self.wheel as u8, ]; /* broadcast — this is non‑blocking just like `try_send` on mpsc */ if let Err(broadcast::error::SendError(_)) = self.tx.send(HidReport { kind: Some(hid_report::Kind::MouseReport(report.to_vec())), }) { self.dev_log(|| warn!("❌ no HID receiver (mouse)")); } else { self.dev_log(|| debug!("📤 HID mouse {:?}", report)); } /* reset deltas for next frame */ self.dx = 0; self.dy = 0; self.wheel = 0; } }