unblocked evtest
This commit is contained in:
parent
d4b8208762
commit
b8183d6861
@ -1,7 +1,8 @@
|
||||
// client/src/app.rs
|
||||
|
||||
use anyhow::{Context, Result};
|
||||
use tokio::{sync::mpsc, time::Duration, task::JoinHandle};
|
||||
use anyhow::Result;
|
||||
use std::time::Duration;
|
||||
use tokio::{sync::mpsc, task::JoinHandle};
|
||||
use tokio_stream::wrappers::ReceiverStream;
|
||||
use tonic::Request;
|
||||
use tracing::{info, warn, error};
|
||||
@ -9,7 +10,7 @@ use navka_common::navka::{relay_client::RelayClient, HidReport};
|
||||
use crate::input::inputs::InputAggregator;
|
||||
|
||||
pub struct NavkaClientApp {
|
||||
aggregator: InputAggregator,
|
||||
aggregator: Option<InputAggregator>,
|
||||
server_addr: String,
|
||||
dev_mode: bool,
|
||||
}
|
||||
@ -21,11 +22,11 @@ impl NavkaClientApp {
|
||||
.nth(1)
|
||||
.or_else(|| std::env::var("NAVKA_SERVER_ADDR").ok())
|
||||
.unwrap_or_else(|| "http://127.0.0.1:50051".to_owned());
|
||||
let mut aggregator = InputAggregator::new();
|
||||
let mut aggregator = InputAggregator::new(dev_mode);
|
||||
aggregator.init()?; // discover & grab
|
||||
|
||||
|
||||
Ok(Self {
|
||||
aggregator,
|
||||
aggregator: Some(aggregator),
|
||||
server_addr: addr,
|
||||
dev_mode,
|
||||
})
|
||||
@ -33,15 +34,16 @@ impl NavkaClientApp {
|
||||
|
||||
pub async fn run(&mut self) -> Result<()> {
|
||||
// spawn aggregator
|
||||
let mut aggregator = std::mem::take(&mut self.aggregator).expect("aggregator must exist");
|
||||
let aggregator_task: JoinHandle<Result<()>> = tokio::spawn(async move {
|
||||
self.aggregator.run().await
|
||||
aggregator.run().await
|
||||
});
|
||||
|
||||
// dev-mode kill future
|
||||
let suicide_future = async {
|
||||
if self.dev_mode {
|
||||
info!("DEV-mode: will kill itself in 30 s");
|
||||
tokio::time::sleep(Duration::from_secs(30)).await;
|
||||
info!("DEV-mode: will kill itself in 30s");
|
||||
tokio::time::sleep(std::time::Duration::from_secs(30)).await;
|
||||
Err::<(), _>(anyhow::anyhow!("dev-mode timer expired - goodbye cruel world..."))
|
||||
} else {
|
||||
futures::future::pending().await
|
||||
@ -50,16 +52,30 @@ impl NavkaClientApp {
|
||||
|
||||
// Combine aggregator + dev + reconnect logic
|
||||
tokio::select! {
|
||||
res = aggregator_task => {
|
||||
error!("Aggregator ended: {res:?}");
|
||||
std::process::exit(1);
|
||||
// aggregator finishes
|
||||
agg_res = aggregator_task => {
|
||||
match agg_res {
|
||||
Ok(Ok(())) => {
|
||||
error!("Aggregator ended normally, exiting.");
|
||||
std::process::exit(0);
|
||||
}
|
||||
Ok(Err(e)) => {
|
||||
error!("Aggregator ended with error: {e}");
|
||||
std::process::exit(1);
|
||||
}
|
||||
Err(join_err) => {
|
||||
error!("Aggregator task panicked or was cancelled: {join_err}");
|
||||
std::process::exit(1);
|
||||
}
|
||||
}
|
||||
},
|
||||
// dev-mode
|
||||
res = suicide_future => {
|
||||
warn!("Dev-mode: {res:?}");
|
||||
std::process::exit(0);
|
||||
},
|
||||
// reconnect loop
|
||||
_ = self.reconnect_loop() => {
|
||||
// if that loop ends, e.g. user or server
|
||||
warn!("Reconnect loop ended?? We exit");
|
||||
std::process::exit(0);
|
||||
}
|
||||
@ -70,26 +86,27 @@ impl NavkaClientApp {
|
||||
/// and waits for inbound to end. Then tries again, unless aggregator ended.
|
||||
async fn reconnect_loop(&self) {
|
||||
loop {
|
||||
// dial the server
|
||||
// dial the servers
|
||||
info!("Dialing server at: {}", self.server_addr);
|
||||
let mut client = match RelayClient::connect(self.server_addr.clone()).await {
|
||||
Ok(c) => c,
|
||||
Err(e) => {
|
||||
error!("connect error {e}, sleeping 5s");
|
||||
tokio::time::sleep(Duration::from_secs(5)).await;
|
||||
error!("connect error {e}, sleeping 1s");
|
||||
tokio::time::sleep(Duration::from_secs(1)).await;
|
||||
continue;
|
||||
}
|
||||
};
|
||||
|
||||
// fresh channel for aggregator => we do this with new (tx, rx)
|
||||
let (tx, rx) = mpsc::channel::<HidReport>(32);
|
||||
let (_tx, rx) = mpsc::channel::<HidReport>(32);
|
||||
// aggregator can hold 'tx' by storing in some global or so?
|
||||
|
||||
let outbound = ReceiverStream::new(rx);
|
||||
let response = match client.stream(Request::new(outbound)).await {
|
||||
Ok(r) => r,
|
||||
Err(e) => {
|
||||
error!("stream RPC error: {e}, sleeping 5s");
|
||||
tokio::time::sleep(Duration::from_secs(5)).await;
|
||||
error!("stream RPC error: {e}, sleeping 1s");
|
||||
tokio::time::sleep(Duration::from_secs(1)).await;
|
||||
continue;
|
||||
}
|
||||
};
|
||||
@ -107,15 +124,8 @@ impl NavkaClientApp {
|
||||
}
|
||||
}
|
||||
}
|
||||
warn!("Inbound ended. Will try to reconnect in 5s");
|
||||
tokio::time::sleep(Duration::from_secs(5)).await;
|
||||
warn!("Inbound ended. Will try to reconnect in 1s");
|
||||
tokio::time::sleep(Duration::from_secs(1)).await;
|
||||
}
|
||||
}
|
||||
|
||||
fn make_channel(&self) -> Result<mpsc::Sender<HidReport>> {
|
||||
// aggregator's main channel
|
||||
let (tx, _rx) = mpsc::channel(32);
|
||||
// aggregator would store tx in self
|
||||
Ok(tx)
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,9 +1,9 @@
|
||||
// client/src/input/inputs.rs
|
||||
|
||||
use anyhow::{bail, Context, Result};
|
||||
use evdev::{Device, EventType, KeyCode, RelativeAxisType};
|
||||
use evdev::{Device, EventType, KeyCode, RelativeAxisCode};
|
||||
use tokio::time::{interval, Duration};
|
||||
use tracing::info;
|
||||
use tracing::{debug, info};
|
||||
|
||||
use crate::input::keyboard::KeyboardAggregator;
|
||||
use crate::input::mouse::MouseAggregator;
|
||||
@ -15,16 +15,17 @@ use crate::input::microphone::MicrophoneCapture;
|
||||
/// 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>,
|
||||
pub dev_mode: bool,
|
||||
keyboards: Vec<KeyboardAggregator>,
|
||||
mice: Vec<MouseAggregator>,
|
||||
camera: Option<CameraCapture>,
|
||||
mic: Option<MicrophoneCapture>,
|
||||
}
|
||||
|
||||
impl InputAggregator {
|
||||
pub fn new() -> Self {
|
||||
pub fn new(dev_mode: bool) -> Self {
|
||||
Self {
|
||||
dev_mode,
|
||||
keyboards: Vec::new(),
|
||||
mice: Vec::new(),
|
||||
camera: None,
|
||||
@ -45,9 +46,7 @@ impl InputAggregator {
|
||||
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"))
|
||||
{
|
||||
if !path.file_name().map_or(false, |f| f.to_string_lossy().starts_with("event")) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@ -60,23 +59,32 @@ impl InputAggregator {
|
||||
}
|
||||
};
|
||||
|
||||
// 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))?;
|
||||
dev.grab().with_context(|| format!("grabbing keyboard {path:?}"))?;
|
||||
info!("Grabbed keyboard {:?}", dev.name().unwrap_or("UNKNOWN"));
|
||||
let kbd_agg = KeyboardAggregator::new(dev);
|
||||
|
||||
// pass dev_mode to aggregator
|
||||
let kbd_agg = KeyboardAggregator::new(dev, self.dev_mode);
|
||||
self.keyboards.push(kbd_agg);
|
||||
found_any = true;
|
||||
continue;
|
||||
}
|
||||
DeviceKind::Mouse => {
|
||||
dev.grab().with_context(|| format!("grabbing mouse {:?}", path))?;
|
||||
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;
|
||||
continue;
|
||||
}
|
||||
DeviceKind::Other => {
|
||||
tracing::debug!("Skipping non-kbd/mouse device: {:?}", dev.name().unwrap_or("UNKNOWN"));
|
||||
debug!("Skipping non-kbd/mouse device: {:?}", dev.name().unwrap_or("UNKNOWN"));
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -110,11 +118,17 @@ impl InputAggregator {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct Classification {
|
||||
keyboard: Option<()>,
|
||||
mouse: Option<()>,
|
||||
}
|
||||
|
||||
/// The classification function
|
||||
fn classify_device(dev: &Device) -> DeviceKind {
|
||||
let evbits = dev.supported_events();
|
||||
|
||||
// If it supports typed scancodes => Keyboard
|
||||
// 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) {
|
||||
@ -122,12 +136,17 @@ fn classify_device(dev: &Device) -> DeviceKind {
|
||||
}
|
||||
}
|
||||
}
|
||||
// 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)
|
||||
{
|
||||
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
|
||||
@ -2,60 +2,81 @@
|
||||
|
||||
use std::collections::HashSet;
|
||||
use evdev::{Device, InputEvent, KeyCode, EventType};
|
||||
use tracing::warn;
|
||||
use tracing::{warn, error, info, debug};
|
||||
|
||||
use navka_common::navka::HidReport;
|
||||
use crate::input::keymap::{keycode_to_usage, is_modifier};
|
||||
|
||||
/// The aggregator logic for a single keyboard device.
|
||||
pub struct KeyboardAggregator {
|
||||
dev: Device,
|
||||
dev_mode: bool,
|
||||
pressed_keys: HashSet<KeyCode>,
|
||||
}
|
||||
|
||||
impl KeyboardAggregator {
|
||||
pub fn new(dev: Device) -> Self {
|
||||
pub fn new(mut dev: Device, dev_mode: bool) -> Self {
|
||||
let _ = dev.set_nonblocking(true);
|
||||
Self {
|
||||
dev,
|
||||
dev_mode,
|
||||
pressed_keys: HashSet::new(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Called frequently (e.g. every ~10ms) to fetch + handle events
|
||||
pub fn process_events(&mut self) {
|
||||
match self.dev.fetch_events() {
|
||||
Ok(events) => {
|
||||
for ev in events {
|
||||
self.handle_event(ev);
|
||||
let events: Vec<InputEvent> = {
|
||||
match self.dev.fetch_events() {
|
||||
Ok(it) => it.collect(),
|
||||
Err(e) if e.kind() == std::io::ErrorKind::WouldBlock => return,
|
||||
Err(e) => {
|
||||
error!("Keyboard device read error: {e}");
|
||||
return;
|
||||
}
|
||||
}
|
||||
Err(e) if e.kind() == std::io::ErrorKind::WouldBlock => {
|
||||
// nothing
|
||||
}
|
||||
Err(e) => {
|
||||
tracing::error!("Keyboard device read error: {e}");
|
||||
}
|
||||
};
|
||||
|
||||
if self.dev_mode && !events.is_empty() {
|
||||
info!(
|
||||
"Got {} events from dev: {}",
|
||||
events.len(),
|
||||
self.dev.name().unwrap_or("???")
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
for ev in events {
|
||||
self.handle_event(ev);
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_event(&mut self, ev: InputEvent) {
|
||||
if ev.event_type() == EventType::KEY {
|
||||
let code = KeyCode::new(ev.code());
|
||||
match ev.value() {
|
||||
1 => { self.pressed_keys.insert(code); }
|
||||
0 => { self.pressed_keys.remove(&code); }
|
||||
2 => { /* repeats, if needed */ }
|
||||
let val = ev.value(); // 1 = press, 0 = release, 2 = repeat
|
||||
|
||||
if self.dev_mode {
|
||||
info!(
|
||||
"Keyboard event: code={:?}, value={}, name={:?}",
|
||||
code, val, self.dev.name()
|
||||
);
|
||||
}
|
||||
|
||||
match val {
|
||||
1 => {self.pressed_keys.insert(code);}
|
||||
0 => {self.pressed_keys.remove(&code);}
|
||||
2 => {/* repeat */}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
let report = self.build_report();
|
||||
// TODO: send this somewhere (e.g. an mpsc::Sender)
|
||||
// For now, just log:
|
||||
tracing::debug!(?report, "Keyboard HID report");
|
||||
debug!(?report, "Keyboard HID report");
|
||||
// optional: magic chord
|
||||
if self.is_magic_chord() {
|
||||
warn!("Magic chord pressed => exit aggregator??");
|
||||
// Or do something else
|
||||
std::process::exit(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -82,8 +103,7 @@ impl KeyboardAggregator {
|
||||
}
|
||||
|
||||
fn is_magic_chord(&self) -> bool {
|
||||
// example logic
|
||||
self.pressed_keys.contains(&KeyCode::KEY_LEFTCTRL) &&
|
||||
self.pressed_keys.contains(&KeyCode::KEY_ESC)
|
||||
self.pressed_keys.contains(&KeyCode::KEY_LEFTCTRL)
|
||||
&& self.pressed_keys.contains(&KeyCode::KEY_ESC)
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
// client/src/input/mouse.rs
|
||||
|
||||
use evdev::{Device, InputEvent, EventType, RelativeAxisType};
|
||||
use evdev::{Device, InputEvent, EventType, RelativeAxisCode};
|
||||
use tracing::error;
|
||||
|
||||
/// Aggregator for a single mouse device
|
||||
pub struct MouseAggregator {
|
||||
@ -20,26 +21,29 @@ impl MouseAggregator {
|
||||
}
|
||||
|
||||
pub fn process_events(&mut self) {
|
||||
match self.dev.fetch_events() {
|
||||
Ok(events) => {
|
||||
for ev in events {
|
||||
self.handle_event(ev);
|
||||
}
|
||||
let events_vec: Vec<InputEvent> = match self.dev.fetch_events() {
|
||||
Ok(it) => it.collect(),
|
||||
Err(e) if e.kind() == std::io::ErrorKind::WouldBlock => {
|
||||
return;
|
||||
}
|
||||
Err(e) if e.kind() == std::io::ErrorKind::WouldBlock => {}
|
||||
Err(e) => {
|
||||
tracing::error!("Mouse device read error: {e}");
|
||||
error!("Mouse device read error: {e}");
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
for ev in events_vec {
|
||||
self.handle_event(ev);
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_event(&mut self, ev: InputEvent) {
|
||||
if ev.event_type() == EventType::REL {
|
||||
match ev.code() {
|
||||
x if x == RelativeAxisType::REL_X.0 => {
|
||||
if ev.event_type() == EventType::RELATIVE {
|
||||
match RelativeAxisCode(ev.code()) {
|
||||
RelativeAxisCode::REL_X => {
|
||||
self.dx += ev.value();
|
||||
}
|
||||
y if y == RelativeAxisType::REL_Y.0 => {
|
||||
RelativeAxisCode::REL_Y => {
|
||||
self.dy += ev.value();
|
||||
}
|
||||
_ => {}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user