lesavka/client/src/input/mouse.rs

412 lines
13 KiB
Rust
Raw Normal View History

2025-06-08 22:24:14 -05:00
// client/src/input/mouse.rs
use evdev::{AbsoluteAxisCode, Device, EventType, InputEvent, KeyCode, RelativeAxisCode};
2025-06-26 14:05:23 -05:00
use std::time::{Duration, Instant};
use tokio::sync::broadcast::Sender;
use tracing::{debug, error, trace, warn};
2025-06-15 22:33:44 -05:00
2025-06-23 07:18:26 -05:00
use lesavka_common::lesavka::MouseReport;
2025-06-08 22:24:14 -05:00
2025-06-28 16:03:19 -05:00
const SEND_INTERVAL: Duration = Duration::from_millis(1);
2025-06-26 14:05:23 -05:00
struct MouseEventState<'a> {
buttons: &'a mut u8,
dx: &'a mut i8,
dy: &'a mut i8,
wheel: &'a mut i8,
last_abs_x: &'a mut Option<i32>,
last_abs_y: &'a mut Option<i32>,
abs_scale: i32,
abs_jump_x: i32,
abs_jump_y: i32,
has_touch_state: bool,
touch_guarded: &'a mut bool,
touch_active: &'a mut bool,
}
impl MouseEventState<'_> {
fn set_btn(&mut self, bit: u8, val: i32) {
if val != 0 {
*self.buttons |= 1 << bit;
} else {
*self.buttons &= !(1 << bit);
}
}
fn apply_event(&mut self, event: &InputEvent) {
match event.event_type() {
EventType::KEY => match event.code() {
c if c == KeyCode::BTN_LEFT.0 => self.set_btn(0, event.value()),
c if c == KeyCode::BTN_RIGHT.0 => self.set_btn(1, event.value()),
c if c == KeyCode::BTN_MIDDLE.0 => self.set_btn(2, event.value()),
c if c == KeyCode::BTN_TOUCH.0 => {
*self.touch_guarded = true;
*self.touch_active = event.value() != 0;
if !*self.touch_active {
*self.last_abs_x = None;
*self.last_abs_y = None;
}
self.set_btn(0, event.value());
}
_ => {}
},
EventType::RELATIVE => match event.code() {
c if c == RelativeAxisCode::REL_X.0 => {
*self.dx = self.dx.saturating_add(event.value().clamp(-127, 127) as i8)
}
c if c == RelativeAxisCode::REL_Y.0 => {
*self.dy = self.dy.saturating_add(event.value().clamp(-127, 127) as i8)
}
c if c == RelativeAxisCode::REL_WHEEL.0 => {
*self.wheel = self.wheel.saturating_add(event.value().clamp(-1, 1) as i8)
}
_ => {}
},
EventType::ABSOLUTE => match event.code() {
c if c == AbsoluteAxisCode::ABS_X.0
|| c == AbsoluteAxisCode::ABS_MT_POSITION_X.0 =>
{
if *self.touch_guarded && !*self.touch_active {
*self.last_abs_x = Some(event.value());
return;
}
if let Some(prev) = *self.last_abs_x {
if !self.has_touch_state {
let delta = (event.value() - prev).abs();
if delta > self.abs_jump_x {
*self.last_abs_x = Some(event.value());
return;
}
}
let delta = (event.value() - prev) / self.abs_scale;
if delta != 0 {
*self.dx = self.dx.saturating_add(delta.clamp(-127, 127) as i8);
}
}
*self.last_abs_x = Some(event.value());
}
c if c == AbsoluteAxisCode::ABS_Y.0
|| c == AbsoluteAxisCode::ABS_MT_POSITION_Y.0 =>
{
if *self.touch_guarded && !*self.touch_active {
*self.last_abs_y = Some(event.value());
return;
}
if let Some(prev) = *self.last_abs_y {
if !self.has_touch_state {
let delta = (event.value() - prev).abs();
if delta > self.abs_jump_y {
*self.last_abs_y = Some(event.value());
return;
}
}
let delta = (event.value() - prev) / self.abs_scale;
if delta != 0 {
*self.dy = self.dy.saturating_add(delta.clamp(-127, 127) as i8);
}
}
*self.last_abs_y = Some(event.value());
}
c if c == AbsoluteAxisCode::ABS_MT_TRACKING_ID.0 => {
if event.value() < 0 {
*self.touch_guarded = true;
*self.touch_active = false;
*self.last_abs_x = None;
*self.last_abs_y = None;
} else {
*self.touch_guarded = true;
*self.touch_active = true;
}
}
_ => {}
},
_ => {}
}
}
}
struct MouseRuntime<'a> {
tx: &'a Sender<MouseReport>,
dev_mode: bool,
sending_disabled: bool,
next_send: &'a mut Instant,
last_buttons: &'a mut u8,
event_state: MouseEventState<'a>,
}
impl MouseRuntime<'_> {
fn replay_events(&mut self, events: Vec<InputEvent>) {
for event in events {
if event.event_type() == EventType::SYNCHRONIZATION {
self.flush();
} else {
self.event_state.apply_event(&event);
}
}
}
fn flush(&mut self) {
let buttons = *self.event_state.buttons;
if buttons == *self.last_buttons && Instant::now() < *self.next_send {
return;
}
*self.next_send = Instant::now() + SEND_INTERVAL;
let pkt = [
buttons,
(*self.event_state.dx).clamp(-127, 127) as u8,
(*self.event_state.dy).clamp(-127, 127) as u8,
*self.event_state.wheel as u8,
];
if !self.sending_disabled {
#[cfg(not(coverage))]
if let Err(tokio::sync::broadcast::error::SendError(_)) =
self.tx.send(MouseReport { data: pkt.to_vec() })
{
if self.dev_mode {
warn!("❌🖱️ no HID receiver (mouse)");
}
} else if self.dev_mode {
debug!("📤🖱️ mouse {:?}", pkt);
}
#[cfg(coverage)]
{
let _ = self.tx.send(MouseReport { data: pkt.to_vec() });
}
}
*self.event_state.dx = 0;
*self.event_state.dy = 0;
*self.event_state.wheel = 0;
*self.last_buttons = buttons;
}
}
fn collect_fetched_events<I>(
fetch_result: Result<I, std::io::Error>,
dev_mode: bool,
) -> Option<Vec<InputEvent>>
where
I: IntoIterator<Item = InputEvent>,
{
match fetch_result {
Ok(events) => Some(events.into_iter().collect()),
Err(e) if e.kind() == std::io::ErrorKind::WouldBlock => None,
Err(e) => {
if dev_mode {
error!("🖱️❌ mouse read err: {e}");
}
None
}
}
}
fn log_event_batch(dev_mode: bool, device_name: Option<&str>, evts: &[InputEvent]) {
if dev_mode && !evts.is_empty() {
trace!("🖱️ {} evts from {}", evts.len(), device_name.unwrap_or("?"));
}
}
2025-06-08 22:24:14 -05:00
pub struct MouseAggregator {
dev: Device,
tx: Sender<MouseReport>,
dev_mode: bool,
2025-06-28 18:51:13 -05:00
sending_disabled: bool,
2025-06-26 14:05:23 -05:00
next_send: Instant,
buttons: u8,
2025-06-16 20:40:03 -05:00
last_buttons: u8,
dx: i8,
dy: i8,
wheel: i8,
last_abs_x: Option<i32>,
last_abs_y: Option<i32>,
abs_scale: i32,
abs_jump_x: i32,
abs_jump_y: i32,
has_touch_state: bool,
touch_guarded: bool,
touch_active: bool,
2025-06-08 22:24:14 -05:00
}
impl MouseAggregator {
2025-06-17 08:17:23 -05:00
pub fn new(dev: Device, dev_mode: bool, tx: Sender<MouseReport>) -> Self {
let abs_scale = std::env::var("LESAVKA_TOUCHPAD_SCALE")
.ok()
.and_then(|v| v.parse::<i32>().ok())
.unwrap_or(8)
.max(1);
let has_touch_state = dev
.supported_keys()
.map(|keys| keys.contains(KeyCode::BTN_TOUCH))
.unwrap_or(false)
|| dev
.supported_absolute_axes()
.map(|abs| abs.contains(AbsoluteAxisCode::ABS_MT_TRACKING_ID))
.unwrap_or(false);
let abs_jump_x = Self::abs_jump_threshold(
&dev,
&[AbsoluteAxisCode::ABS_X, AbsoluteAxisCode::ABS_MT_POSITION_X],
abs_scale,
);
let abs_jump_y = Self::abs_jump_threshold(
&dev,
&[AbsoluteAxisCode::ABS_Y, AbsoluteAxisCode::ABS_MT_POSITION_Y],
abs_scale,
);
Self {
dev,
tx,
dev_mode,
sending_disabled: false,
next_send: Instant::now(),
buttons: 0,
last_buttons: 0,
dx: 0,
dy: 0,
wheel: 0,
last_abs_x: None,
last_abs_y: None,
abs_scale,
abs_jump_x,
abs_jump_y,
has_touch_state,
touch_guarded: false,
touch_active: true,
}
2025-06-12 01:48:48 -05:00
}
2025-12-01 00:11:23 -03:00
#[inline]
#[allow(dead_code)]
fn slog(&self, f: impl FnOnce()) {
if self.dev_mode {
f()
}
}
2025-06-28 17:55:15 -05:00
pub fn set_grab(&mut self, grab: bool) {
let _ = if grab {
self.dev.grab()
} else {
self.dev.ungrab()
};
2025-06-28 17:55:15 -05:00
}
2025-06-08 22:24:14 -05:00
2025-06-28 18:51:13 -05:00
pub fn set_send(&mut self, send: bool) {
self.sending_disabled = !send;
}
2025-06-08 22:24:14 -05:00
pub fn process_events(&mut self) {
let Some(evts) = collect_fetched_events(self.dev.fetch_events(), self.dev_mode) else {
return;
2025-06-11 00:37:01 -05:00
};
log_event_batch(self.dev_mode, self.dev.name(), &evts);
self.runtime().replay_events(evts);
}
pub fn reset_state(&mut self) {
self.buttons = 0;
self.last_buttons = 0;
self.dx = 0;
self.dy = 0;
self.wheel = 0;
self.last_abs_x = None;
self.last_abs_y = None;
if !self.sending_disabled {
let _ = self.tx.send(MouseReport {
data: [0; 4].into(),
});
}
}
#[allow(dead_code)]
2025-06-17 20:54:31 -05:00
fn flush(&mut self) {
self.runtime().flush();
2025-06-17 20:54:31 -05:00
}
#[inline]
#[allow(dead_code)]
fn set_btn(&mut self, bit: u8, val: i32) {
MouseEventState {
buttons: &mut self.buttons,
dx: &mut self.dx,
dy: &mut self.dy,
wheel: &mut self.wheel,
last_abs_x: &mut self.last_abs_x,
last_abs_y: &mut self.last_abs_y,
abs_scale: self.abs_scale,
abs_jump_x: self.abs_jump_x,
abs_jump_y: self.abs_jump_y,
has_touch_state: self.has_touch_state,
touch_guarded: &mut self.touch_guarded,
touch_active: &mut self.touch_active,
}
.set_btn(bit, val);
2025-06-15 21:39:07 -05:00
}
#[cfg(not(coverage))]
fn abs_jump_threshold(dev: &Device, codes: &[AbsoluteAxisCode], abs_scale: i32) -> i32 {
let mut range: Option<i32> = None;
if let Ok(iter) = dev.get_absinfo() {
for (code, info) in iter {
if codes.contains(&code) {
range = Some(info.maximum() - info.minimum());
break;
}
}
}
let mut threshold = range.unwrap_or(0).abs() / 3;
let min = (abs_scale * 40).max(50);
if threshold < min {
threshold = min;
}
if threshold == 0 {
threshold = min;
}
threshold
}
#[cfg(coverage)]
fn abs_jump_threshold(_dev: &Device, _codes: &[AbsoluteAxisCode], abs_scale: i32) -> i32 {
(abs_scale * 40).max(50)
}
fn runtime(&mut self) -> MouseRuntime<'_> {
MouseRuntime {
tx: &self.tx,
dev_mode: self.dev_mode,
sending_disabled: self.sending_disabled,
next_send: &mut self.next_send,
last_buttons: &mut self.last_buttons,
event_state: MouseEventState {
buttons: &mut self.buttons,
dx: &mut self.dx,
dy: &mut self.dy,
wheel: &mut self.wheel,
last_abs_x: &mut self.last_abs_x,
last_abs_y: &mut self.last_abs_y,
abs_scale: self.abs_scale,
abs_jump_x: self.abs_jump_x,
abs_jump_y: self.abs_jump_y,
has_touch_state: self.has_touch_state,
touch_guarded: &mut self.touch_guarded,
touch_active: &mut self.touch_active,
},
}
}
2025-06-08 22:24:14 -05:00
}
2025-06-17 20:54:31 -05:00
2025-06-29 03:46:34 -05:00
impl Drop for MouseAggregator {
fn drop(&mut self) {
let _ = self.dev.ungrab();
let _ = self.tx.send(MouseReport {
data: [0; 8].into(),
});
}
}
#[cfg(test)]
#[path = "mouse_event_contract_tests.rs"]
mod mouse_event_contract_tests;