2025-06-08 22:24:14 -05:00
|
|
|
// client/src/input/inputs.rs
|
|
|
|
|
|
|
|
|
|
use anyhow::{bail, Context, Result};
|
2025-06-29 03:46:34 -05:00
|
|
|
use evdev::{Device, EventType, KeyCode, RelativeAxisCode, ReadFlag, WriteFlag};
|
2025-06-17 20:54:31 -05:00
|
|
|
use tokio::{sync::broadcast::Sender, time::{interval, Duration}};
|
2025-06-28 15:45:35 -05:00
|
|
|
use tracing::{debug, info, warn};
|
2025-06-08 22:24:14 -05:00
|
|
|
|
2025-06-23 07:18:26 -05:00
|
|
|
use lesavka_common::lesavka::{KeyboardReport, MouseReport};
|
2025-06-11 22:01:16 -05:00
|
|
|
|
2025-06-17 20:54:31 -05:00
|
|
|
use super::{keyboard::KeyboardAggregator, mouse::MouseAggregator,
|
|
|
|
|
camera::CameraCapture, microphone::MicrophoneCapture};
|
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>,
|
|
|
|
|
camera: Option<CameraCapture>,
|
|
|
|
|
mic: Option<MicrophoneCapture>,
|
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-06-17 20:54:31 -05:00
|
|
|
keyboards: Vec::new(), mice: Vec::new(),
|
|
|
|
|
camera: None, mic: None }
|
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;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// attempt open
|
2025-06-29 03:46:34 -05:00
|
|
|
let mut dev = match Device::open_with_options(
|
|
|
|
|
&path,
|
|
|
|
|
ReadFlag::NORMAL | ReadFlag::BLOCKING,
|
|
|
|
|
WriteFlag::NORMAL, // <-- write access needed for EVIOCGRAB
|
|
|
|
|
) {
|
2025-06-08 22:24:14 -05:00
|
|
|
Ok(d) => d,
|
2025-06-29 03:46:34 -05:00
|
|
|
Err(e) => { … }
|
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-08 22:24:14 -05:00
|
|
|
info!("Grabbed keyboard {:?}", dev.name().unwrap_or("UNKNOWN"));
|
2025-06-11 00:37:01 -05:00
|
|
|
|
|
|
|
|
// pass dev_mode to aggregator
|
2025-06-11 22:01:16 -05:00
|
|
|
// 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-08 22:24:14 -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.");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Stubs for camera / mic:
|
|
|
|
|
self.camera = Some(CameraCapture::new_stub());
|
|
|
|
|
self.mic = Some(MicrophoneCapture::new_stub());
|
|
|
|
|
|
|
|
|
|
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));
|
|
|
|
|
loop {
|
2025-06-28 17:55:15 -05:00
|
|
|
let magic_now = self.keyboards.iter().any(|k| k.magic_grab());
|
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-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
|
|
|
|
|
|
|
|
// camera / mic stubs go here
|
|
|
|
|
|
|
|
|
|
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,
|
|
|
|
|
}
|