lesavka/client/src/input/mouse.rs

137 lines
4.1 KiB
Rust
Raw Normal View History

2025-06-08 22:24:14 -05:00
// client/src/input/mouse.rs
use anyhow::Result;
2025-06-12 01:48:48 -05:00
use evdev::{Device, InputEvent, EventType, KeyCode, RelativeAxisCode};
use tokio::sync::broadcast::{self, Sender};
use tracing::{debug, error, warn};
2025-06-15 22:33:44 -05:00
use navka_common::navka::{HidReport, hid_report};
2025-06-08 22:24:14 -05:00
/// Aggregator for a single mouse device
pub struct MouseAggregator {
dev: Device,
tx: Sender<HidReport>,
dev_mode: bool,
buttons: u8,
2025-06-16 20:40:03 -05:00
last_buttons: u8,
dx: i8,
dy: i8,
wheel: i8,
2025-06-08 22:24:14 -05:00
}
impl MouseAggregator {
pub fn new(dev: Device, dev_mode: bool, tx: Sender<HidReport>) -> Self {
2025-06-08 22:24:14 -05:00
Self {
dev,
2025-06-12 01:48:48 -05:00
tx,
dev_mode,
2025-06-12 01:48:48 -05:00
buttons: 0,
2025-06-16 20:40:03 -05:00
last_buttons: 0,
2025-06-08 22:24:14 -05:00
dx: 0,
dy: 0,
2025-06-12 01:48:48 -05:00
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;
2025-06-12 01:48:48 -05:00
} else {
self.buttons &= !(1 << bit);
2025-06-08 22:24:14 -05:00
}
}
pub fn process_events(&mut self) {
/* 1 ─ read a nonblocking batch */
let events: Vec<InputEvent> = match self.dev.fetch_events() {
2025-06-11 00:37:01 -05:00
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; }
2025-06-11 00:37:01 -05:00
};
if self.dev_mode && !events.is_empty() {
self.dev_log(|| debug!("🖱️ {} events from {}", events.len(),
self.dev.name().unwrap_or("UNKNOWN")));
2025-06-08 22:24:14 -05:00
}
/* 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()),
2025-06-15 20:19:27 -05:00
_ => {}
},
/* ----------- 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();
2025-06-08 22:24:14 -05:00
}
_ => {}
2025-06-08 22:24:14 -05:00
}
}
}
/// Build & send HID packet, then clear deltas
fn flush_report(&mut self) {
/* Nothing changed ⇒ nothing to send */
2025-06-16 20:40:03 -05:00
if self.dx == 0
&& self.dy == 0
&& self.wheel == 0
&& self.buttons == self.last_buttons
{
return;
}
2025-06-15 20:19:27 -05:00
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 nonblocking just like `try_send` on mpsc */
if let Err(broadcast::error::SendError(_)) = self.tx.send(HidReport {
2025-06-15 21:39:07 -05:00
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));
2025-06-15 21:39:07 -05:00
}
/* reset deltas for next frame */
2025-06-16 20:40:03 -05:00
self.dx = 0;
self.dy = 0;
self.wheel = 0;
self.last_buttons = self.buttons;
2025-06-15 21:39:07 -05:00
}
2025-06-08 22:24:14 -05:00
}