diff --git a/client/src/app.rs b/client/src/app.rs index 8316d94..0d31304 100644 --- a/client/src/app.rs +++ b/client/src/app.rs @@ -98,8 +98,8 @@ impl NavkaClientApp { }; // fresh channel for aggregator => we do this with new (tx, rx) - let (_tx, rx) = mpsc::channel::(32); - // aggregator can hold 'tx' by storing in some global or so? + let (tx, rx) = mpsc::channel::(64); + let mut aggregator = InputAggregator::new(dev_mode, tx.clone()); let outbound = ReceiverStream::new(rx); let response = match client.stream(Request::new(outbound)).await { diff --git a/client/src/input/inputs.rs b/client/src/input/inputs.rs index 05f02ed..dfb7f99 100644 --- a/client/src/input/inputs.rs +++ b/client/src/input/inputs.rs @@ -3,8 +3,11 @@ use anyhow::{bail, Context, Result}; use evdev::{Device, EventType, KeyCode, RelativeAxisCode}; use tokio::time::{interval, Duration}; +use tokio::sync::mpsc::{self, Sender}; use tracing::{debug, info}; +use navka_common::navka::HidReport; + use crate::input::keyboard::KeyboardAggregator; use crate::input::mouse::MouseAggregator; use crate::input::camera::CameraCapture; @@ -15,6 +18,7 @@ use crate::input::microphone::MicrophoneCapture; /// spawns specialized aggregator objects, and can also /// create stubs for camera/microphone logic if needed. pub struct InputAggregator { + tx: Sender, pub dev_mode: bool, keyboards: Vec, mice: Vec, @@ -68,7 +72,8 @@ impl InputAggregator { 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); + let kbd_agg = KeyboardAggregator::new(dev.clone(), self.dev_mode, self.tx.clone()); self.keyboards.push(kbd_agg); found_any = true; continue; @@ -77,7 +82,8 @@ impl InputAggregator { 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);np + let mouse_agg = MouseAggregator::new(dev.clone(), self.tx.clone()); self.mice.push(mouse_agg); found_any = true; continue; @@ -118,6 +124,10 @@ impl InputAggregator { } } +pub fn new(dev_mode: bool, tx: Sender) -> Self { + Self { tx, dev_mode, keyboards: Vec::new(), mice: Vec::new(), camera: None, mic: None } +} + #[derive(Debug)] struct Classification { keyboard: Option<()>, diff --git a/client/src/input/keyboard.rs b/client/src/input/keyboard.rs index c3bcf0b..db19626 100644 --- a/client/src/input/keyboard.rs +++ b/client/src/input/keyboard.rs @@ -9,15 +9,17 @@ use crate::input::keymap::{keycode_to_usage, is_modifier}; /// The aggregator logic for a single keyboard device. pub struct KeyboardAggregator { dev: Device, + tx: Sender, dev_mode: bool, pressed_keys: HashSet, } impl KeyboardAggregator { - pub fn new(mut dev: Device, dev_mode: bool) -> Self { + pub fn new(dev: Device, dev_mode: bool, tx: Sender) -> Self { let _ = dev.set_nonblocking(true); Self { dev, + tx: Sender, dev_mode, pressed_keys: HashSet::new(), } @@ -102,6 +104,10 @@ impl KeyboardAggregator { bytes } + let _ = self.tx.try_send(HidReport { + kind: Some(navka_common::navka::hid_report::Kind::KeyboardReport(report.to_vec())), + }); + fn is_magic_chord(&self) -> bool { self.pressed_keys.contains(&KeyCode::KEY_LEFTCTRL) && self.pressed_keys.contains(&KeyCode::KEY_ESC) diff --git a/client/src/input/mouse.rs b/client/src/input/mouse.rs index 18dbed4..ed747f2 100644 --- a/client/src/input/mouse.rs +++ b/client/src/input/mouse.rs @@ -6,9 +6,11 @@ use tracing::error; /// Aggregator for a single mouse device pub struct MouseAggregator { dev: Device, - dx: i32, - dy: i32, - // etc + tx: Sender, + buttons: u8, + dx: i8, + dy: i8, + wheel: i8, } impl MouseAggregator { @@ -38,18 +40,27 @@ impl MouseAggregator { } fn handle_event(&mut self, ev: InputEvent) { - if ev.event_type() == EventType::RELATIVE { - match RelativeAxisCode(ev.code()) { - RelativeAxisCode::REL_X => { - self.dx += ev.value(); + match ev.event_type() { + EventType::RELATIVE => { /* fill dx/dy/wheel as i8 */ } + EventType::KEY => { // buttons are REPORTED as KEYS (BTN_LEFT…) + let pressed = ev.value() != 0; + match ev.code() { + c if c == KeyCode::BTN_LEFT.0 => self.set_btn(0, pressed), + c if c == KeyCode::BTN_RIGHT.0 => self.set_btn(1, pressed), + c if c == KeyCode::BTN_MIDDLE.0=> self.set_btn(2, pressed), + _ => {} } - RelativeAxisCode::REL_Y => { - self.dy += ev.value(); - } - _ => {} } - tracing::debug!("mouse dx={} dy={}", self.dx, self.dy); + _ => {} } - // etc. Also handle buttons with KEY_B? + + // whenever we changed something, emit: + let rep = [self.buttons, self.dx as u8, self.dy as u8, self.wheel as u8]; + let _ = self.tx.try_send(HidReport { + kind: Some(navka_common::navka::hid_report::Kind::MouseReport(rep.to_vec())), + }); + // reset deltas so we send *relative* movement + self.dx = 0; self.dy = 0; self.wheel = 0; } + } diff --git a/common/proto/navka.proto b/common/proto/navka.proto index cf1f202..3eeea2e 100644 --- a/common/proto/navka.proto +++ b/common/proto/navka.proto @@ -1,7 +1,13 @@ syntax = "proto3"; package navka; -message HidReport { bytes data = 1; } +message HidReport { + oneof kind { + bytes keyboard_report = 1; // exactly 8 bytes + bytes mouse_report = 2; // exactly 4 bytes (btn, dx, dy, wheel) + } +} + service Relay { rpc Stream (stream HidReport) returns (stream HidReport); diff --git a/scripts/navka-core.sh b/scripts/navka-core.sh index 9034911..6deb27b 100644 --- a/scripts/navka-core.sh +++ b/scripts/navka-core.sh @@ -45,6 +45,14 @@ echo 8 >"$G/functions/hid.usb0/report_length" printf '\x05\x01\x09\x06\xa1\x01\x05\x07\x19\xe0\x29\xe7\x15\x00\x25\x01\x75\x01\x95\x08\x81\x02\x95\x01\x75\x08\x81\x01\x95\x05\x75\x01\x05\x08\x19\x01\x29\x05\x91\x02\x95\x01\x75\x03\x91\x01\x95\x06\x75\x08\x15\x00\x25\x65\x05\x07\x19\x00\x29\x65\x81\x00\xc0' \ > "$G/functions/hid.usb0/report_desc" +# usb1 = mouse +mkdir -p "$G/functions/hid.usb1" +echo 2 > "$G/functions/hid.usb1/protocol" +echo 1 > "$G/functions/hid.usb1/subclass" +echo 4 > "$G/functions/hid.usb1/report_length" +printf '\x05\x01\x09\x06\xa1\x01\x05\x07\x19\xe0\x29\xe7\x15\x00\x25\x01\x75\x01\x95\x08\x81\x02\x95\x01\x75\x08\x81\x01\x95\x05\x75\x01\x05\x08\x19\x01\x29\x05\x91\x02\x95\x01\x75\x03\x91\x01\x95\x06\x75\x08\x15\x00\x25\x65\x05\x07\x19\x00\x29\x65\x81\x00\xc0' \ + > functions/hid.usb1/report_desc + # # -- UAC2 Audio # mkdir -p $G/functions/uac2.usb0 # echo 48000 > $G/functions/uac2.usb0/c_srate diff --git a/server/src/main.rs b/server/src/main.rs index c1cc092..710cd51 100644 --- a/server/src/main.rs +++ b/server/src/main.rs @@ -13,10 +13,20 @@ use navka_common::navka::{ }; struct Handler { - /// shared async handle to /dev/hidg0 - hid: Arc>, + kb: Arc>, + ms: Arc>, } +let kb = OpenOptions::new().write(true).read(true) + .custom_flags(libc::O_NONBLOCK) + .open("/dev/hidg0").await?; +let ms = OpenOptions::new().write(true).read(true) + .custom_flags(libc::O_NONBLOCK) + .open("/dev/hidg1").await?; + +let handler = Handler { kb: Arc::new(Mutex::new(kb)), + ms: Arc::new(Mutex::new(ms)) }; + #[tonic::async_trait] impl Relay for Handler { type StreamStream = @@ -32,16 +42,21 @@ impl Relay for Handler { tokio::spawn(async move { while let Some(msg) = in_stream.next().await.transpose()? { - let data = msg.data.get(..8).ok_or_else(|| Status::invalid_argument("short"))?; - { - let mut f = hid.lock().await; - if let Err(e) = f.write_all(data).await { - error!("USB write failed: {e}"); - return Err(Status::internal(e.to_string())); + match msg.kind { + Some(navka::hid_report::Kind::KeyboardReport(ref v)) if v.len() == 8 => { + let mut f = kb.lock().await; + f.write_all(v).await?; + } + Some(navka::hid_report::Kind::MouseReport(ref v)) if v.len() == 4 => { + let mut f = ms.lock().await; + f.write_all(v).await?; + } + _ => { + error!("bad packet len={}", msg.data.len()); + continue; } - f.flush().await.ok(); } - info!(bytes=?data, len=data.len(), "HID report received"); + info!("HID report forwarded"); let _ = tx.send(Ok(msg)).await; } Ok::<_, Status>(())