lesavka/client/src/input/inputs.rs
2025-06-17 20:54:31 -05:00

163 lines
5.3 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// client/src/input/inputs.rs
use anyhow::{bail, Context, Result};
use evdev::{Device, EventType, KeyCode, RelativeAxisCode};
use tokio::{sync::broadcast::Sender, time::{interval, Duration}};
use tracing::{debug, info};
use navka_common::navka::{KeyboardReport, MouseReport};
use super::{keyboard::KeyboardAggregator, mouse::MouseAggregator,
camera::CameraCapture, microphone::MicrophoneCapture};
pub struct InputAggregator {
kbd_tx: Sender<KeyboardReport>,
mou_tx: Sender<MouseReport>,
dev_mode: bool,
keyboards: Vec<KeyboardAggregator>,
mice: Vec<MouseAggregator>,
camera: Option<CameraCapture>,
mic: Option<MicrophoneCapture>,
}
impl InputAggregator {
pub fn new(dev_mode: bool,
kbd_tx: Sender<KeyboardReport>,
mou_tx: Sender<MouseReport>) -> Self {
Self { kbd_tx, mou_tx, dev_mode,
keyboards: Vec::new(), mice: Vec::new(),
camera: None, mic: None }
}
/// 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*"
if !path.file_name().map_or(false, |f| f.to_string_lossy().starts_with("event")) {
continue;
}
// attempt open
let mut dev = match Device::open(&path) {
Ok(d) => d,
Err(e) => {
tracing::debug!("Skipping {:?}, open error {e}", path);
continue;
}
};
// nonblocking so fetch_events never stalls the whole loop
dev.set_nonblocking(true).with_context(|| format!("set_non_blocking {:?}", path))?;
match classify_device(&dev) {
DeviceKind::Keyboard => {
dev.grab().with_context(|| format!("grabbing keyboard {path:?}"))?;
info!("Grabbed keyboard {:?}", dev.name().unwrap_or("UNKNOWN"));
// pass dev_mode to aggregator
// let kbd_agg = KeyboardAggregator::new(dev, self.dev_mode);
let kbd_agg = KeyboardAggregator::new(dev, self.dev_mode, self.kbd_tx.clone());
self.keyboards.push(kbd_agg);
found_any = true;
continue;
}
DeviceKind::Mouse => {
dev.grab().with_context(|| format!("grabbing mouse {path:?}"))?;
info!("Grabbed mouse {:?}", dev.name().unwrap_or("UNKNOWN"));
// let mouse_agg = MouseAggregator::new(dev);
let mouse_agg = MouseAggregator::new(dev, self.dev_mode, self.mou_tx.clone());
self.mice.push(mouse_agg);
found_any = true;
continue;
}
DeviceKind::Other => {
debug!("Skipping non-kbd/mouse device: {:?}", dev.name().unwrap_or("UNKNOWN"));
continue;
}
}
}
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 {
for kbd in &mut self.keyboards {
kbd.process_events();
}
for mouse in &mut self.mice {
mouse.process_events();
}
// camera / mic stubs could go here
tick.tick().await;
}
}
}
#[derive(Debug)]
struct Classification {
keyboard: Option<()>,
mouse: Option<()>,
}
/// The classification function
fn classify_device(dev: &Device) -> DeviceKind {
let evbits = dev.supported_events();
// Keyboard logic
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;
}
}
}
// 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 {
return DeviceKind::Mouse;
}
}
}
DeviceKind::Other
}
/// Internal enum for device classification
#[derive(Debug, Clone, Copy)]
enum DeviceKind {
Keyboard,
Mouse,
Other,
}