lesavka/client/src/input/inputs.rs

192 lines
6.7 KiB
Rust
Raw Normal View History

2025-06-08 22:24:14 -05:00
// client/src/input/inputs.rs
use anyhow::{bail, Context, Result};
2025-06-29 04:54:39 -05:00
use evdev::{Device, EventType, KeyCode, RelativeAxisCode};
2025-06-17 20:54:31 -05:00
use tokio::{sync::broadcast::Sender, time::{interval, Duration}};
2025-07-03 15:22:30 -05:00
use tracing::{debug, info, warn, trace};
2025-06-08 22:24:14 -05:00
2025-06-23 07:18:26 -05:00
use lesavka_common::lesavka::{KeyboardReport, MouseReport};
2025-07-04 01:56:59 -05:00
use super::{keyboard::KeyboardAggregator, mouse::MouseAggregator};
2025-06-29 04:54:39 -05:00
use crate::layout::{Layout, apply as apply_layout};
2025-06-08 22:24:14 -05:00
pub struct InputAggregator {
2025-06-17 20:54:31 -05:00
kbd_tx: Sender<KeyboardReport>,
mou_tx: Sender<MouseReport>,
dev_mode: bool,
2025-06-28 15:45:35 -05:00
released: bool,
2025-06-28 17:55:15 -05:00
magic_active: bool,
2025-06-17 20:54:31 -05:00
keyboards: Vec<KeyboardAggregator>,
mice: Vec<MouseAggregator>,
2025-06-08 22:24:14 -05:00
}
impl InputAggregator {
2025-06-17 20:54:31 -05:00
pub fn new(dev_mode: bool,
kbd_tx: Sender<KeyboardReport>,
mou_tx: Sender<MouseReport>) -> Self {
2025-06-28 17:55:15 -05:00
Self { kbd_tx, mou_tx, dev_mode, released: false, magic_active: false,
2025-07-04 01:56:59 -05:00
keyboards: Vec::new(), mice: Vec::new()
}
2025-06-08 22:24:14 -05:00
}
/// Called once at startup: enumerates input devices,
/// classifies them, and constructs a aggregator struct per type.
pub fn init(&mut self) -> Result<()> {
let paths = std::fs::read_dir("/dev/input")
.context("Failed to read /dev/input")?;
let mut found_any = false;
for entry in paths {
let entry = entry?;
let path = entry.path();
// skip anything that isn't "event*"
2025-06-11 00:37:01 -05:00
if !path.file_name().map_or(false, |f| f.to_string_lossy().starts_with("event")) {
2025-06-08 22:24:14 -05:00
continue;
}
2025-06-29 22:39:17 -05:00
// ─── open the event node read-write *without* unsafe ──────────
2025-06-29 04:54:39 -05:00
let mut dev = match Device::open(&path) {
2025-06-08 22:24:14 -05:00
Ok(d) => d,
2025-06-29 04:17:44 -05:00
Err(e) => {
2025-06-29 04:54:39 -05:00
warn!("❌ open {}: {e}", path.display());
2025-06-29 04:17:44 -05:00
continue;
}
2025-06-08 22:24:14 -05:00
};
2025-06-28 15:45:35 -05:00
// non-blocking so fetch_events never stalls the whole loop
2025-06-11 00:37:01 -05:00
dev.set_nonblocking(true).with_context(|| format!("set_non_blocking {:?}", path))?;
2025-06-08 22:24:14 -05:00
match classify_device(&dev) {
DeviceKind::Keyboard => {
2025-06-11 00:37:01 -05:00
dev.grab().with_context(|| format!("grabbing keyboard {path:?}"))?;
2025-06-30 15:45:37 -05:00
info!("🤏🖱️ Grabbed keyboard {:?}", dev.name().unwrap_or("UNKNOWN"));
2025-06-11 00:37:01 -05:00
// pass dev_mode to aggregator
// let kbd_agg = KeyboardAggregator::new(dev, self.dev_mode);
2025-06-17 08:17:23 -05:00
let kbd_agg = KeyboardAggregator::new(dev, self.dev_mode, self.kbd_tx.clone());
2025-06-08 22:24:14 -05:00
self.keyboards.push(kbd_agg);
found_any = true;
2025-06-11 00:37:01 -05:00
continue;
2025-06-08 22:24:14 -05:00
}
DeviceKind::Mouse => {
2025-06-11 00:37:01 -05:00
dev.grab().with_context(|| format!("grabbing mouse {path:?}"))?;
2025-06-30 15:45:37 -05:00
info!("🤏⌨️ Grabbed mouse {:?}", dev.name().unwrap_or("UNKNOWN"));
2025-06-11 00:37:01 -05:00
2025-06-17 20:54:31 -05:00
// let mouse_agg = MouseAggregator::new(dev);
2025-06-17 08:17:23 -05:00
let mouse_agg = MouseAggregator::new(dev, self.dev_mode, self.mou_tx.clone());
2025-06-08 22:24:14 -05:00
self.mice.push(mouse_agg);
found_any = true;
2025-06-11 00:37:01 -05:00
continue;
2025-06-08 22:24:14 -05:00
}
DeviceKind::Other => {
2025-06-11 00:37:01 -05:00
debug!("Skipping non-kbd/mouse device: {:?}", dev.name().unwrap_or("UNKNOWN"));
continue;
2025-06-08 22:24:14 -05:00
}
}
}
if !found_any {
bail!("No suitable keyboard/mouse devices found or none grabbed.");
}
Ok(())
}
/// We spawn the sub-aggregators in a loop or using separate tasks.
/// (For a real system: you'd spawn a separate task for each aggregator.)
pub async fn run(&mut self) -> Result<()> {
// Example approach: poll each aggregator in a simple loop
let mut tick = interval(Duration::from_millis(10));
2025-06-29 04:54:39 -05:00
let mut current = Layout::SideBySide;
2025-06-08 22:24:14 -05:00
loop {
2025-06-28 17:55:15 -05:00
let magic_now = self.keyboards.iter().any(|k| k.magic_grab());
2025-06-29 04:54:39 -05:00
let magic_left = self.keyboards.iter().any(|k| k.magic_left());
let magic_right = self.keyboards.iter().any(|k| k.magic_right());
2025-06-28 15:45:35 -05:00
let mut want_kill = false;
2025-06-08 22:24:14 -05:00
for kbd in &mut self.keyboards {
kbd.process_events();
2025-06-28 15:45:35 -05:00
want_kill |= kbd.magic_kill();
2025-06-08 22:24:14 -05:00
}
2025-06-28 15:45:35 -05:00
2025-06-28 17:55:15 -05:00
if magic_now && !self.magic_active { self.toggle_grab(); }
2025-06-29 04:54:39 -05:00
if (magic_left || magic_right) && self.magic_active {
current = match current {
Layout::SideBySide => Layout::FullLeft,
Layout::FullLeft => Layout::FullRight,
Layout::FullRight => Layout::SideBySide,
};
apply_layout(current);
}
2025-06-28 15:45:35 -05:00
if want_kill {
warn!("🧙 magic chord - killing 🪄 AVADA KEDAVRA!!! 💥💀⚰️");
std::process::exit(0);
}
2025-06-08 22:24:14 -05:00
for mouse in &mut self.mice {
mouse.process_events();
}
2025-06-28 17:55:15 -05:00
self.magic_active = magic_now;
2025-06-08 22:24:14 -05:00
tick.tick().await;
}
}
2025-06-28 15:45:35 -05:00
fn toggle_grab(&mut self) {
if self.released {
tracing::info!("🧙 magic chord - restricting devices 🪄 IMPERIUS!!! 🎮🔒");
} else {
tracing::info!("🧙 magic chord - freeing devices 🪄 EXPELLIARMUS!!! 🔓🕊️");
}
2025-06-28 18:51:13 -05:00
for k in &mut self.keyboards { k.set_grab(self.released); k.set_send(self.released); }
for m in &mut self.mice { m.set_grab(self.released); m.set_send(self.released); }
2025-06-28 15:45:35 -05:00
self.released = !self.released;
}
2025-06-08 22:24:14 -05:00
}
2025-06-11 00:37:01 -05:00
#[derive(Debug)]
struct Classification {
keyboard: Option<()>,
mouse: Option<()>,
}
2025-06-08 22:24:14 -05:00
/// The classification function
fn classify_device(dev: &Device) -> DeviceKind {
let evbits = dev.supported_events();
2025-06-11 00:37:01 -05:00
// Keyboard logic
2025-06-08 22:24:14 -05:00
if evbits.contains(EventType::KEY) {
if let Some(keys) = dev.supported_keys() {
if keys.contains(KeyCode::KEY_A) || keys.contains(KeyCode::KEY_ENTER) {
return DeviceKind::Keyboard;
}
}
}
2025-06-11 00:37:01 -05:00
// Mouse logic
if evbits.contains(EventType::RELATIVE) {
if let (Some(rel), Some(keys)) =
(dev.supported_relative_axes(), dev.supported_keys())
{
let has_xy = rel.contains(RelativeAxisCode::REL_X)
&& rel.contains(RelativeAxisCode::REL_Y);
let has_btn = keys.contains(KeyCode::BTN_LEFT)
|| keys.contains(KeyCode::BTN_RIGHT);
if has_xy && has_btn {
2025-06-08 22:24:14 -05:00
return DeviceKind::Mouse;
}
}
}
DeviceKind::Other
}
/// Internal enum for device classification
#[derive(Debug, Clone, Copy)]
enum DeviceKind {
Keyboard,
Mouse,
Other,
}