146 lines
4.7 KiB
Rust
146 lines
4.7 KiB
Rust
|
|
// client/src/input/inputs.rs
|
||
|
|
|
||
|
|
use anyhow::{bail, Context, Result};
|
||
|
|
use evdev::{Device, EventType, KeyCode, RelativeAxisType};
|
||
|
|
use tokio::time::{interval, Duration};
|
||
|
|
use tracing::info;
|
||
|
|
|
||
|
|
use crate::input::keyboard::KeyboardAggregator;
|
||
|
|
use crate::input::mouse::MouseAggregator;
|
||
|
|
use crate::input::camera::CameraCapture;
|
||
|
|
use crate::input::microphone::MicrophoneCapture;
|
||
|
|
|
||
|
|
/// A top-level aggregator that enumerates /dev/input/event*,
|
||
|
|
/// classifies them as keyboard vs. mouse vs. other,
|
||
|
|
/// spawns specialized aggregator objects, and can also
|
||
|
|
/// create stubs for camera/microphone logic if needed.
|
||
|
|
pub struct InputAggregator {
|
||
|
|
keyboards: Vec<KeyboardAggregator>,
|
||
|
|
mice: Vec<MouseAggregator>,
|
||
|
|
// Possibly store camera or mic aggregator as well:
|
||
|
|
camera: Option<CameraCapture>,
|
||
|
|
mic: Option<MicrophoneCapture>,
|
||
|
|
}
|
||
|
|
|
||
|
|
impl InputAggregator {
|
||
|
|
pub fn new() -> Self {
|
||
|
|
Self {
|
||
|
|
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;
|
||
|
|
}
|
||
|
|
};
|
||
|
|
|
||
|
|
match classify_device(&dev) {
|
||
|
|
DeviceKind::Keyboard => {
|
||
|
|
dev.grab().with_context(|| format!("grabbing keyboard {:?}", path))?;
|
||
|
|
info!("Grabbed keyboard {:?}", dev.name().unwrap_or("UNKNOWN"));
|
||
|
|
let kbd_agg = KeyboardAggregator::new(dev);
|
||
|
|
self.keyboards.push(kbd_agg);
|
||
|
|
found_any = true;
|
||
|
|
}
|
||
|
|
DeviceKind::Mouse => {
|
||
|
|
dev.grab().with_context(|| format!("grabbing mouse {:?}", path))?;
|
||
|
|
info!("Grabbed mouse {:?}", dev.name().unwrap_or("UNKNOWN"));
|
||
|
|
let mouse_agg = MouseAggregator::new(dev);
|
||
|
|
self.mice.push(mouse_agg);
|
||
|
|
found_any = true;
|
||
|
|
}
|
||
|
|
DeviceKind::Other => {
|
||
|
|
tracing::debug!("Skipping non-kbd/mouse device: {:?}", dev.name().unwrap_or("UNKNOWN"));
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
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;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
/// The classification function
|
||
|
|
fn classify_device(dev: &Device) -> DeviceKind {
|
||
|
|
let evbits = dev.supported_events();
|
||
|
|
|
||
|
|
// If it supports typed scancodes => Keyboard
|
||
|
|
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;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
// If it supports REL_X / REL_Y => Mouse
|
||
|
|
if evbits.contains(EventType::REL) {
|
||
|
|
if let Some(rel) = dev.supported_relative_axes() {
|
||
|
|
if rel.contains(RelativeAxisType::REL_X) &&
|
||
|
|
rel.contains(RelativeAxisType::REL_Y)
|
||
|
|
{
|
||
|
|
return DeviceKind::Mouse;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
DeviceKind::Other
|
||
|
|
}
|
||
|
|
|
||
|
|
/// Internal enum for device classification
|
||
|
|
#[derive(Debug, Clone, Copy)]
|
||
|
|
enum DeviceKind {
|
||
|
|
Keyboard,
|
||
|
|
Mouse,
|
||
|
|
Other,
|
||
|
|
}
|