163 lines
5.3 KiB
Rust
163 lines
5.3 KiB
Rust
// 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;
|
||
}
|
||
};
|
||
|
||
// non‑blocking 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,
|
||
}
|