2025-06-08 22:24:14 -05:00
|
|
|
// client/src/input/mouse.rs
|
|
|
|
|
|
2026-04-08 20:00:14 -03:00
|
|
|
use evdev::{AbsoluteAxisCode, Device, EventType, InputEvent, KeyCode, RelativeAxisCode};
|
2025-06-26 14:05:23 -05:00
|
|
|
use std::time::{Duration, Instant};
|
2025-12-01 11:38:51 -03:00
|
|
|
use tokio::sync::broadcast::{self, 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
|
|
|
|
2025-06-08 22:24:14 -05:00
|
|
|
pub struct MouseAggregator {
|
|
|
|
|
dev: Device,
|
2025-12-01 11:38:51 -03:00
|
|
|
tx: Sender<MouseReport>,
|
2025-06-16 17:54:47 -05:00
|
|
|
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,
|
2025-06-16 17:54:47 -05:00
|
|
|
|
2025-06-11 22:01:16 -05:00
|
|
|
buttons: u8,
|
2025-06-16 20:40:03 -05:00
|
|
|
last_buttons: u8,
|
2025-12-01 11:38:51 -03:00
|
|
|
dx: i8,
|
|
|
|
|
dy: i8,
|
2025-06-11 22:01:16 -05:00
|
|
|
wheel: i8,
|
2026-04-08 20:00:14 -03:00
|
|
|
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 {
|
2026-04-08 20:00:14 -03:00
|
|
|
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,
|
|
|
|
|
);
|
2025-12-01 11:38:51 -03:00
|
|
|
Self {
|
|
|
|
|
dev,
|
|
|
|
|
tx,
|
|
|
|
|
dev_mode,
|
|
|
|
|
sending_disabled: false,
|
|
|
|
|
next_send: Instant::now(),
|
|
|
|
|
buttons: 0,
|
|
|
|
|
last_buttons: 0,
|
|
|
|
|
dx: 0,
|
|
|
|
|
dy: 0,
|
|
|
|
|
wheel: 0,
|
2026-04-08 20:00:14 -03:00
|
|
|
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-12-01 11:38:51 -03:00
|
|
|
}
|
2025-06-12 01:48:48 -05:00
|
|
|
}
|
|
|
|
|
|
2025-12-01 00:11:23 -03:00
|
|
|
#[inline]
|
|
|
|
|
#[allow(dead_code)]
|
2025-12-01 11:38:51 -03:00
|
|
|
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) {
|
2025-12-01 11:38:51 -03:00
|
|
|
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-12-01 11:38:51 -03:00
|
|
|
|
2025-06-08 22:24:14 -05:00
|
|
|
pub fn process_events(&mut self) {
|
2025-06-17 22:02:33 -05:00
|
|
|
let evts: Vec<InputEvent> = match self.dev.fetch_events() {
|
2025-06-11 00:37:01 -05:00
|
|
|
Ok(it) => it.collect(),
|
2025-06-17 20:54:31 -05:00
|
|
|
Err(e) if e.kind() == std::io::ErrorKind::WouldBlock => return,
|
2025-12-01 11:38:51 -03:00
|
|
|
Err(e) => {
|
|
|
|
|
if self.dev_mode {
|
|
|
|
|
error!("🖱️❌ mouse read err: {e}");
|
|
|
|
|
}
|
|
|
|
|
return;
|
|
|
|
|
}
|
2025-06-11 00:37:01 -05:00
|
|
|
};
|
|
|
|
|
|
2025-06-17 20:54:31 -05:00
|
|
|
if self.dev_mode && !evts.is_empty() {
|
2025-12-01 11:38:51 -03:00
|
|
|
trace!(
|
|
|
|
|
"🖱️ {} evts from {}",
|
|
|
|
|
evts.len(),
|
|
|
|
|
self.dev.name().unwrap_or("?")
|
|
|
|
|
);
|
2025-06-08 22:24:14 -05:00
|
|
|
}
|
|
|
|
|
|
2025-06-17 20:54:31 -05:00
|
|
|
for e in evts {
|
|
|
|
|
match e.event_type() {
|
|
|
|
|
EventType::KEY => match e.code() {
|
2025-12-01 11:38:51 -03:00
|
|
|
c if c == KeyCode::BTN_LEFT.0 => self.set_btn(0, e.value()),
|
|
|
|
|
c if c == KeyCode::BTN_RIGHT.0 => self.set_btn(1, e.value()),
|
2025-06-17 20:54:31 -05:00
|
|
|
c if c == KeyCode::BTN_MIDDLE.0 => self.set_btn(2, e.value()),
|
2026-04-08 20:00:14 -03:00
|
|
|
c if c == KeyCode::BTN_TOUCH.0 => {
|
|
|
|
|
self.touch_guarded = true;
|
|
|
|
|
self.touch_active = e.value() != 0;
|
|
|
|
|
if !self.touch_active {
|
|
|
|
|
self.last_abs_x = None;
|
|
|
|
|
self.last_abs_y = None;
|
|
|
|
|
}
|
|
|
|
|
self.set_btn(0, e.value());
|
|
|
|
|
}
|
2025-06-15 20:19:27 -05:00
|
|
|
_ => {}
|
2025-06-16 17:54:47 -05:00
|
|
|
},
|
2025-06-17 20:54:31 -05:00
|
|
|
EventType::RELATIVE => match e.code() {
|
2025-12-01 11:38:51 -03:00
|
|
|
c if c == RelativeAxisCode::REL_X.0 => {
|
|
|
|
|
self.dx = self.dx.saturating_add(e.value().clamp(-127, 127) as i8)
|
|
|
|
|
}
|
|
|
|
|
c if c == RelativeAxisCode::REL_Y.0 => {
|
|
|
|
|
self.dy = self.dy.saturating_add(e.value().clamp(-127, 127) as i8)
|
|
|
|
|
}
|
|
|
|
|
c if c == RelativeAxisCode::REL_WHEEL.0 => {
|
|
|
|
|
self.wheel = self.wheel.saturating_add(e.value().clamp(-1, 1) as i8)
|
|
|
|
|
}
|
2025-06-11 22:01:16 -05:00
|
|
|
_ => {}
|
2025-06-16 17:54:47 -05:00
|
|
|
},
|
2026-04-08 20:00:14 -03:00
|
|
|
EventType::ABSOLUTE => match e.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(e.value());
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
if let Some(prev) = self.last_abs_x {
|
|
|
|
|
if !self.has_touch_state {
|
|
|
|
|
let delta = (e.value() - prev).abs();
|
|
|
|
|
if delta > self.abs_jump_x {
|
|
|
|
|
self.last_abs_x = Some(e.value());
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
let delta = (e.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(e.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(e.value());
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
if let Some(prev) = self.last_abs_y {
|
|
|
|
|
if !self.has_touch_state {
|
|
|
|
|
let delta = (e.value() - prev).abs();
|
|
|
|
|
if delta > self.abs_jump_y {
|
|
|
|
|
self.last_abs_y = Some(e.value());
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
let delta = (e.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(e.value());
|
|
|
|
|
}
|
|
|
|
|
c if c == AbsoluteAxisCode::ABS_MT_TRACKING_ID.0 => {
|
|
|
|
|
if e.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;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
_ => {}
|
|
|
|
|
},
|
2025-06-17 20:54:31 -05:00
|
|
|
EventType::SYNCHRONIZATION => self.flush(),
|
2025-06-16 17:54:47 -05:00
|
|
|
_ => {}
|
2025-06-08 22:24:14 -05:00
|
|
|
}
|
|
|
|
|
}
|
2025-06-16 17:54:47 -05:00
|
|
|
}
|
2025-06-11 22:01:16 -05:00
|
|
|
|
2026-04-08 20:00:14 -03:00
|
|
|
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(),
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-06-17 20:54:31 -05:00
|
|
|
fn flush(&mut self) {
|
2025-12-01 11:38:51 -03:00
|
|
|
if self.buttons == self.last_buttons && Instant::now() < self.next_send {
|
|
|
|
|
return;
|
|
|
|
|
}
|
2025-06-29 03:46:34 -05:00
|
|
|
self.next_send = Instant::now() + SEND_INTERVAL;
|
2025-06-15 20:19:27 -05:00
|
|
|
|
2025-06-17 20:54:31 -05:00
|
|
|
let pkt = [
|
2025-06-16 17:54:47 -05:00
|
|
|
self.buttons,
|
2025-12-01 11:38:51 -03:00
|
|
|
self.dx.clamp(-127, 127) as u8,
|
|
|
|
|
self.dy.clamp(-127, 127) as u8,
|
2025-06-16 17:54:47 -05:00
|
|
|
self.wheel as u8,
|
|
|
|
|
];
|
2025-06-11 22:01:16 -05:00
|
|
|
|
2025-06-28 18:51:13 -05:00
|
|
|
if !self.sending_disabled {
|
|
|
|
|
if let Err(broadcast::error::SendError(_)) =
|
|
|
|
|
self.tx.send(MouseReport { data: pkt.to_vec() })
|
|
|
|
|
{
|
2025-12-01 11:38:51 -03:00
|
|
|
if self.dev_mode {
|
|
|
|
|
warn!("❌🖱️ no HID receiver (mouse)");
|
|
|
|
|
}
|
2025-06-28 18:51:13 -05:00
|
|
|
} else if self.dev_mode {
|
2025-07-01 19:42:34 -05:00
|
|
|
debug!("📤🖱️ mouse {:?}", pkt);
|
2025-06-28 18:51:13 -05:00
|
|
|
}
|
|
|
|
|
}
|
2025-06-16 17:54:47 -05:00
|
|
|
|
2025-12-01 11:38:51 -03:00
|
|
|
self.dx = 0;
|
|
|
|
|
self.dy = 0;
|
|
|
|
|
self.wheel = 0;
|
|
|
|
|
self.last_buttons = self.buttons;
|
2025-06-17 20:54:31 -05:00
|
|
|
}
|
|
|
|
|
|
2025-12-01 11:38:51 -03:00
|
|
|
#[inline]
|
|
|
|
|
fn set_btn(&mut self, bit: u8, val: i32) {
|
|
|
|
|
if val != 0 {
|
|
|
|
|
self.buttons |= 1 << bit
|
|
|
|
|
} else {
|
|
|
|
|
self.buttons &= !(1 << bit)
|
|
|
|
|
}
|
2025-06-15 21:39:07 -05:00
|
|
|
}
|
2026-04-08 20:00:14 -03:00
|
|
|
|
|
|
|
|
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.iter().any(|c| *c == 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
|
|
|
|
|
}
|
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(),
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
}
|