established full keyboard functionality, working on mouse
This commit is contained in:
parent
5cc1e92de0
commit
d5fe1898a4
@ -1,16 +1,17 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "navka_client"
|
name = "navka_client"
|
||||||
version = "0.2.0"
|
version = "0.3.0"
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
tokio = { version = "1.45", features = ["full", "fs"] }
|
tokio = { version = "1.45", features = ["full", "fs", "rt-multi-thread", "macros", "sync", "time"] }
|
||||||
tonic = { version = "0.13", features = ["transport"] }
|
tonic = { version = "0.13", features = ["transport"] }
|
||||||
tokio-stream = { version = "0.1", features = ["sync"] }
|
tokio-stream = { version = "0.1", features = ["sync"] }
|
||||||
anyhow = "1.0"
|
anyhow = "1.0"
|
||||||
navka_common = { path = "../common" }
|
navka_common = { path = "../common" }
|
||||||
tracing = { version = "0.1", features = ["std"] }
|
tracing = { version = "0.1", features = ["std"] }
|
||||||
tracing-subscriber = "0.3"
|
tracing-subscriber = { version = "0.3", features = ["fmt", "env-filter"] }
|
||||||
|
tracing-appender = "0.2"
|
||||||
futures = "0.3"
|
futures = "0.3"
|
||||||
evdev = "0.13"
|
evdev = "0.13"
|
||||||
|
|
||||||
|
|||||||
@ -1,11 +1,9 @@
|
|||||||
// client/src/app.rs
|
// client/src/app.rs
|
||||||
#![forbid(unsafe_code)]
|
#![forbid(unsafe_code)]
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use futures::{FutureExt, StreamExt};
|
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
use tokio::{sync::mpsc, sync::broadcast, task::JoinHandle};
|
use tokio::{sync::broadcast, task::JoinHandle};
|
||||||
use tokio_stream::StreamExt as _;
|
use tokio_stream::{wrappers::BroadcastStream, StreamExt};
|
||||||
use tokio_stream::wrappers::{ReceiverStream, BroadcastStream};
|
|
||||||
use tonic::Request;
|
use tonic::Request;
|
||||||
use tracing::{info, warn, error, debug};
|
use tracing::{info, warn, error, debug};
|
||||||
|
|
||||||
@ -21,6 +19,7 @@ pub struct NavkaClientApp {
|
|||||||
|
|
||||||
impl NavkaClientApp {
|
impl NavkaClientApp {
|
||||||
pub fn new() -> Result<Self> {
|
pub fn new() -> Result<Self> {
|
||||||
|
info!("Creating navka-client app!");
|
||||||
let dev_mode = std::env::var("NAVKA_DEV_MODE").is_ok();
|
let dev_mode = std::env::var("NAVKA_DEV_MODE").is_ok();
|
||||||
let addr = std::env::args()
|
let addr = std::env::args()
|
||||||
.nth(1)
|
.nth(1)
|
||||||
@ -40,6 +39,7 @@ impl NavkaClientApp {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub async fn run(&mut self) -> Result<()> {
|
pub async fn run(&mut self) -> Result<()> {
|
||||||
|
info!("Running navka-client app!");
|
||||||
// spawn aggregator
|
// spawn aggregator
|
||||||
let mut aggregator = std::mem::take(&mut self.aggregator).expect("aggregator must exist");
|
let mut aggregator = std::mem::take(&mut self.aggregator).expect("aggregator must exist");
|
||||||
let aggregator_task: JoinHandle<Result<()>> = tokio::spawn(async move {
|
let aggregator_task: JoinHandle<Result<()>> = tokio::spawn(async move {
|
||||||
@ -105,8 +105,7 @@ impl NavkaClientApp {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// fresh reader over the *same* broadcast channel
|
// fresh reader over the *same* broadcast channel
|
||||||
let mut rx = self.tx.subscribe();
|
let outbound = BroadcastStream::new(self.tx.subscribe()).filter_map(|r| r.ok());
|
||||||
let outbound = BroadcastStream::new(rx.clone()).filter_map(|r| async { r.ok() });
|
|
||||||
|
|
||||||
info!("🛫 spawning stream()");
|
info!("🛫 spawning stream()");
|
||||||
let response = match client.stream(Request::new(outbound)).await {
|
let response = match client.stream(Request::new(outbound)).await {
|
||||||
|
|||||||
@ -84,7 +84,7 @@ impl InputAggregator {
|
|||||||
info!("Grabbed mouse {:?}", dev.name().unwrap_or("UNKNOWN"));
|
info!("Grabbed mouse {:?}", dev.name().unwrap_or("UNKNOWN"));
|
||||||
|
|
||||||
// let mouse_agg = MouseAggregator::new(dev);np
|
// let mouse_agg = MouseAggregator::new(dev);np
|
||||||
let mouse_agg = MouseAggregator::new(dev, self.tx.clone());
|
let mouse_agg = MouseAggregator::new(dev, self.dev_mode, self.tx.clone());
|
||||||
self.mice.push(mouse_agg);
|
self.mice.push(mouse_agg);
|
||||||
found_any = true;
|
found_any = true;
|
||||||
continue;
|
continue;
|
||||||
|
|||||||
@ -5,8 +5,7 @@ use evdev::{Device, InputEvent, KeyCode, EventType};
|
|||||||
use tokio::sync::broadcast::Sender;
|
use tokio::sync::broadcast::Sender;
|
||||||
use tracing::{warn, error, info, debug};
|
use tracing::{warn, error, info, debug};
|
||||||
|
|
||||||
use navka_common::navka::HidReport;
|
use navka_common::navka::{HidReport, hid_report};
|
||||||
use navka_common::navka::hid_report::Kind;
|
|
||||||
|
|
||||||
use crate::input::keymap::{keycode_to_usage, is_modifier};
|
use crate::input::keymap::{keycode_to_usage, is_modifier};
|
||||||
|
|
||||||
@ -29,31 +28,44 @@ impl KeyboardAggregator {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn dev_log(&self, record: impl FnOnce()) {
|
||||||
|
if self.dev_mode {
|
||||||
|
record();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Called frequently (e.g. every ~10ms) to fetch + handle events
|
/// Called frequently (e.g. every ~10ms) to fetch + handle events
|
||||||
pub fn process_events(&mut self) {
|
pub fn process_events(&mut self) {
|
||||||
let events: Vec<InputEvent> = {
|
// Fetch once. Any borrow of `self.dev` ends right here.
|
||||||
match self.dev.fetch_events() {
|
let events: Vec<InputEvent> = match self.dev.fetch_events() {
|
||||||
Ok(it) => it.collect(),
|
Ok(iter) => iter.collect(),
|
||||||
Err(e) if e.kind() == std::io::ErrorKind::WouldBlock => return,
|
|
||||||
Err(e) => {
|
// Would-block → nothing new
|
||||||
|
Err(ref e) if e.kind() == std::io::ErrorKind::WouldBlock => return,
|
||||||
|
|
||||||
|
// Any other error → log without touching `self.dev`
|
||||||
|
Err(e) => {
|
||||||
|
if self.dev_mode {
|
||||||
error!("Keyboard device read error: {e}");
|
error!("Keyboard device read error: {e}");
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Safe: no mutable borrow is active now
|
||||||
if self.dev_mode && !events.is_empty() {
|
if self.dev_mode && !events.is_empty() {
|
||||||
info!(
|
self.dev_log(|| info!(
|
||||||
"Got {} events from dev: {}",
|
"Got {} events from dev: {}",
|
||||||
events.len(),
|
events.len(),
|
||||||
self.dev.name().unwrap_or("???")
|
self.dev.name().unwrap_or("???")
|
||||||
);
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
for ev in events {
|
for ev in events {
|
||||||
self.handle_event(ev);
|
self.handle_event(ev);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn handle_event(&mut self, ev: InputEvent) {
|
fn handle_event(&mut self, ev: InputEvent) {
|
||||||
if ev.event_type() == EventType::KEY {
|
if ev.event_type() == EventType::KEY {
|
||||||
@ -61,10 +73,10 @@ impl KeyboardAggregator {
|
|||||||
let val = ev.value(); // 1 = press, 0 = release, 2 = repeat
|
let val = ev.value(); // 1 = press, 0 = release, 2 = repeat
|
||||||
|
|
||||||
if self.dev_mode {
|
if self.dev_mode {
|
||||||
info!(
|
self.dev_log(|| info!(
|
||||||
"Keyboard event: code={:?}, value={}, name={:?}",
|
"Keyboard event: code={:?}, value={}, name={:?}",
|
||||||
code, val, self.dev.name()
|
code, val, self.dev.name()
|
||||||
);
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
match val {
|
match val {
|
||||||
@ -77,12 +89,10 @@ impl KeyboardAggregator {
|
|||||||
let report = self.build_report();
|
let report = self.build_report();
|
||||||
// TODO: send this somewhere (e.g. an mpsc::Sender)
|
// TODO: send this somewhere (e.g. an mpsc::Sender)
|
||||||
// For now, just log:
|
// For now, just log:
|
||||||
debug!(?report, "Keyboard HID report");
|
self.dev_log(|| debug!(?report, "Keyboard HID report"));
|
||||||
self.send_report(report);
|
self.send_report(report);
|
||||||
// optional: magic chord
|
|
||||||
if self.is_magic_chord() {
|
if self.is_magic_chord() {
|
||||||
warn!("Magic chord pressed => exit aggregator??");
|
self.dev_log(|| warn!("Magic chord pressed => AVADA KEDAVA!!!"));
|
||||||
// Or do something else
|
|
||||||
std::process::exit(0);
|
std::process::exit(0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -116,10 +126,10 @@ impl KeyboardAggregator {
|
|||||||
|
|
||||||
match self.tx.send(msg.clone()) {
|
match self.tx.send(msg.clone()) {
|
||||||
Ok(n) => {
|
Ok(n) => {
|
||||||
info!("📤 sent HID report → {n} subscriber(s)");
|
self.dev_log(|| info!("📤 sent HID report → {n} subscriber(s)"));
|
||||||
}
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
tracing::warn!("❌ send failed: {e}");
|
self.dev_log(|| warn!("❌ send failed: {e}"));
|
||||||
let _ = self.tx.send(msg);
|
let _ = self.tx.send(msg);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -5,7 +5,7 @@ use evdev::KeyCode;
|
|||||||
/// Return Some(usage) if we have a known mapping from evdev::KeyCode -> HID usage code
|
/// Return Some(usage) if we have a known mapping from evdev::KeyCode -> HID usage code
|
||||||
pub fn keycode_to_usage(key: KeyCode) -> Option<u8> {
|
pub fn keycode_to_usage(key: KeyCode) -> Option<u8> {
|
||||||
match key {
|
match key {
|
||||||
// Letters
|
// --- Letters ------------------------------------------------------
|
||||||
KeyCode::KEY_A => Some(0x04),
|
KeyCode::KEY_A => Some(0x04),
|
||||||
KeyCode::KEY_B => Some(0x05),
|
KeyCode::KEY_B => Some(0x05),
|
||||||
KeyCode::KEY_C => Some(0x06),
|
KeyCode::KEY_C => Some(0x06),
|
||||||
@ -33,7 +33,7 @@ pub fn keycode_to_usage(key: KeyCode) -> Option<u8> {
|
|||||||
KeyCode::KEY_Y => Some(0x1C),
|
KeyCode::KEY_Y => Some(0x1C),
|
||||||
KeyCode::KEY_Z => Some(0x1D),
|
KeyCode::KEY_Z => Some(0x1D),
|
||||||
|
|
||||||
// Number row
|
// --- Number row ---------------------------------------------------
|
||||||
KeyCode::KEY_1 => Some(0x1E),
|
KeyCode::KEY_1 => Some(0x1E),
|
||||||
KeyCode::KEY_2 => Some(0x1F),
|
KeyCode::KEY_2 => Some(0x1F),
|
||||||
KeyCode::KEY_3 => Some(0x20),
|
KeyCode::KEY_3 => Some(0x20),
|
||||||
@ -45,7 +45,7 @@ pub fn keycode_to_usage(key: KeyCode) -> Option<u8> {
|
|||||||
KeyCode::KEY_9 => Some(0x26),
|
KeyCode::KEY_9 => Some(0x26),
|
||||||
KeyCode::KEY_0 => Some(0x27),
|
KeyCode::KEY_0 => Some(0x27),
|
||||||
|
|
||||||
// Common punctuation
|
// --- Common punctuation -------------------------------------------
|
||||||
KeyCode::KEY_ENTER => Some(0x28),
|
KeyCode::KEY_ENTER => Some(0x28),
|
||||||
KeyCode::KEY_ESC => Some(0x29),
|
KeyCode::KEY_ESC => Some(0x29),
|
||||||
KeyCode::KEY_BACKSPACE => Some(0x2A),
|
KeyCode::KEY_BACKSPACE => Some(0x2A),
|
||||||
@ -63,7 +63,42 @@ pub fn keycode_to_usage(key: KeyCode) -> Option<u8> {
|
|||||||
KeyCode::KEY_DOT => Some(0x37),
|
KeyCode::KEY_DOT => Some(0x37),
|
||||||
KeyCode::KEY_SLASH => Some(0x38),
|
KeyCode::KEY_SLASH => Some(0x38),
|
||||||
|
|
||||||
// Function keys
|
// --- Navigation / editing cluster ---------------------------------
|
||||||
|
KeyCode::KEY_SYSRQ => Some(0x46), // Print‑Screen
|
||||||
|
KeyCode::KEY_SCROLLLOCK => Some(0x47),
|
||||||
|
KeyCode::KEY_PAUSE => Some(0x48),
|
||||||
|
KeyCode::KEY_INSERT => Some(0x49),
|
||||||
|
KeyCode::KEY_HOME => Some(0x4A),
|
||||||
|
KeyCode::KEY_PAGEUP => Some(0x4B),
|
||||||
|
KeyCode::KEY_DELETE => Some(0x4C),
|
||||||
|
KeyCode::KEY_END => Some(0x4D),
|
||||||
|
KeyCode::KEY_PAGEDOWN => Some(0x4E),
|
||||||
|
KeyCode::KEY_RIGHT => Some(0x4F),
|
||||||
|
KeyCode::KEY_LEFT => Some(0x50),
|
||||||
|
KeyCode::KEY_DOWN => Some(0x51),
|
||||||
|
KeyCode::KEY_UP => Some(0x52),
|
||||||
|
|
||||||
|
// --- Keypad / Num‑lock block --------------------------------------
|
||||||
|
KeyCode::KEY_NUMLOCK => Some(0x53),
|
||||||
|
KeyCode::KEY_KPSLASH => Some(0x54),
|
||||||
|
KeyCode::KEY_KPASTERISK => Some(0x55),
|
||||||
|
KeyCode::KEY_KPMINUS => Some(0x56),
|
||||||
|
KeyCode::KEY_KPPLUS => Some(0x57),
|
||||||
|
KeyCode::KEY_KPENTER => Some(0x58),
|
||||||
|
KeyCode::KEY_KP1 => Some(0x59),
|
||||||
|
KeyCode::KEY_KP2 => Some(0x5A),
|
||||||
|
KeyCode::KEY_KP3 => Some(0x5B),
|
||||||
|
KeyCode::KEY_KP4 => Some(0x5C),
|
||||||
|
KeyCode::KEY_KP5 => Some(0x5D),
|
||||||
|
KeyCode::KEY_KP6 => Some(0x5E),
|
||||||
|
KeyCode::KEY_KP7 => Some(0x5F),
|
||||||
|
KeyCode::KEY_KP8 => Some(0x60),
|
||||||
|
KeyCode::KEY_KP9 => Some(0x61),
|
||||||
|
KeyCode::KEY_KP0 => Some(0x62),
|
||||||
|
KeyCode::KEY_KPDOT => Some(0x63),
|
||||||
|
KeyCode::KEY_KPEQUAL => Some(0x67),
|
||||||
|
|
||||||
|
// --- Function keys ------------------------------------------------
|
||||||
KeyCode::KEY_CAPSLOCK => Some(0x39),
|
KeyCode::KEY_CAPSLOCK => Some(0x39),
|
||||||
KeyCode::KEY_F1 => Some(0x3A),
|
KeyCode::KEY_F1 => Some(0x3A),
|
||||||
KeyCode::KEY_F2 => Some(0x3B),
|
KeyCode::KEY_F2 => Some(0x3B),
|
||||||
@ -78,6 +113,10 @@ pub fn keycode_to_usage(key: KeyCode) -> Option<u8> {
|
|||||||
KeyCode::KEY_F11 => Some(0x44),
|
KeyCode::KEY_F11 => Some(0x44),
|
||||||
KeyCode::KEY_F12 => Some(0x45),
|
KeyCode::KEY_F12 => Some(0x45),
|
||||||
|
|
||||||
|
// --- Misc ---------------------------------------------------------
|
||||||
|
KeyCode::KEY_102ND => Some(0x64), // “<>” on ISO boards
|
||||||
|
KeyCode::KEY_MENU => Some(0x65), // Application / Compose
|
||||||
|
|
||||||
// We'll handle modifiers (ctrl, shift, alt, meta) in `is_modifier()`
|
// We'll handle modifiers (ctrl, shift, alt, meta) in `is_modifier()`
|
||||||
_ => None,
|
_ => None,
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,16 +1,18 @@
|
|||||||
// client/src/input/mouse.rs
|
// client/src/input/mouse.rs
|
||||||
|
|
||||||
|
use anyhow::Result;
|
||||||
use evdev::{Device, InputEvent, EventType, KeyCode, RelativeAxisCode};
|
use evdev::{Device, InputEvent, EventType, KeyCode, RelativeAxisCode};
|
||||||
use tokio::sync::broadcast::Sender;
|
use tokio::sync::broadcast::{self, Sender};
|
||||||
use tracing::{error, debug};
|
use tracing::{debug, error, warn};
|
||||||
|
|
||||||
use navka_common::navka::HidReport;
|
use navka_common::navka::{HidReport, hid_report};
|
||||||
use navka_common::navka::hid_report::Kind;
|
|
||||||
|
|
||||||
/// Aggregator for a single mouse device
|
/// Aggregator for a single mouse device
|
||||||
pub struct MouseAggregator {
|
pub struct MouseAggregator {
|
||||||
dev: Device,
|
dev: Device,
|
||||||
tx: Sender<HidReport>,
|
tx: Sender<HidReport>,
|
||||||
|
dev_mode: bool,
|
||||||
|
|
||||||
buttons: u8,
|
buttons: u8,
|
||||||
dx: i8,
|
dx: i8,
|
||||||
dy: i8,
|
dy: i8,
|
||||||
@ -18,10 +20,12 @@ pub struct MouseAggregator {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl MouseAggregator {
|
impl MouseAggregator {
|
||||||
pub fn new(dev: Device, tx: Sender<HidReport>) -> Self {
|
pub fn new(dev: Device, dev_mode: bool, tx: Sender<HidReport>) -> Self {
|
||||||
Self {
|
Self {
|
||||||
dev,
|
dev,
|
||||||
tx,
|
tx,
|
||||||
|
dev_mode,
|
||||||
|
|
||||||
buttons: 0,
|
buttons: 0,
|
||||||
dx: 0,
|
dx: 0,
|
||||||
dy: 0,
|
dy: 0,
|
||||||
@ -29,77 +33,93 @@ impl MouseAggregator {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// helper to set or clear a mouse button bit
|
#[inline]
|
||||||
fn set_btn(&mut self, idx: usize, pressed: bool) {
|
fn dev_log(&self, record: impl FnOnce()) {
|
||||||
if pressed {
|
if self.dev_mode {
|
||||||
self.buttons |= 1 << idx;
|
record();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn set_btn(&mut self, bit: u8, val: i32) {
|
||||||
|
if val != 0 {
|
||||||
|
self.buttons |= 1 << bit;
|
||||||
} else {
|
} else {
|
||||||
self.buttons &= !(1 << idx);
|
self.buttons &= !(1 << bit);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn process_events(&mut self) {
|
pub fn process_events(&mut self) {
|
||||||
let events_vec: Vec<InputEvent> = match self.dev.fetch_events() {
|
/* 1 ─ read a non‑blocking batch */
|
||||||
|
let events: Vec<InputEvent> = match self.dev.fetch_events() {
|
||||||
Ok(it) => it.collect(),
|
Ok(it) => it.collect(),
|
||||||
Err(e) if e.kind() == std::io::ErrorKind::WouldBlock => {
|
Err(ref e) if e.kind() == std::io::ErrorKind::WouldBlock => return,
|
||||||
return;
|
Err(e) => { if self.dev_mode { error!("🖱️ read error: {e}"); } return; }
|
||||||
}
|
|
||||||
Err(e) => {
|
|
||||||
error!("Mouse device read error: {e}");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
for ev in events_vec {
|
if self.dev_mode && !events.is_empty() {
|
||||||
self.handle_event(ev);
|
self.dev_log(|| debug!("🖱️ {} events from {}", events.len(),
|
||||||
|
self.dev.name().unwrap_or("UNKNOWN")));
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 2 ─ aggregate */
|
||||||
|
for ev in events {
|
||||||
|
match ev.event_type() {
|
||||||
|
/* ---------- buttons ---------- */
|
||||||
|
EventType::KEY => match ev.code() {
|
||||||
|
c if c == KeyCode::BTN_LEFT.0 => self.set_btn(0, ev.value()),
|
||||||
|
c if c == KeyCode::BTN_RIGHT.0 => self.set_btn(1, ev.value()),
|
||||||
|
c if c == KeyCode::BTN_MIDDLE.0 => self.set_btn(2, ev.value()),
|
||||||
|
_ => {}
|
||||||
|
},
|
||||||
|
|
||||||
|
/* ----------- axes ------------ */
|
||||||
|
EventType::RELATIVE => match ev.code() {
|
||||||
|
c if c == RelativeAxisCode::REL_X.0 => {
|
||||||
|
self.dx = self.dx.saturating_add(ev.value().clamp(-127, 127) as i8);
|
||||||
|
}
|
||||||
|
c if c == RelativeAxisCode::REL_Y.0 => {
|
||||||
|
self.dy = self.dy.saturating_add(ev.value().clamp(-127, 127) as i8);
|
||||||
|
}
|
||||||
|
c if c == RelativeAxisCode::REL_WHEEL.0 => {
|
||||||
|
self.wheel = self.wheel.saturating_add(ev.value().clamp(-1, 1) as i8);
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
},
|
||||||
|
|
||||||
|
/* ---- batch delimiter -------- */
|
||||||
|
EventType::SYNCHRONIZATION => {
|
||||||
|
// Any sync event is fine – we only care about boundaries
|
||||||
|
self.flush_report();
|
||||||
|
}
|
||||||
|
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn handle_event(&mut self, ev: InputEvent) {
|
/// Build & send HID packet, then clear deltas
|
||||||
match ev.event_type() {
|
fn flush_report(&mut self) {
|
||||||
EventType::RELATIVE => {
|
/* Nothing changed ⇒ nothing to send */
|
||||||
match RelativeAxisCode::new(ev.code()) {
|
if self.dx == 0 && self.dy == 0 && self.wheel == 0 { return; }
|
||||||
RelativeAxisCode::REL_X => self.dx = ev.value() as i8,
|
|
||||||
RelativeAxisCode::REL_Y => self.dy = ev.value() as i8,
|
|
||||||
RelativeAxisCode::REL_WHEEL => self.wheel = ev.value() as i8,
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
EventType::KEY => {
|
|
||||||
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),
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
|
|
||||||
// whenever we changed something, emit:
|
let report = [
|
||||||
let report = [self.buttons, self.dx as u8, self.dy as u8, self.wheel as u8];
|
self.buttons,
|
||||||
self.send_report(report);
|
self.dx.clamp(-127, 127) as u8,
|
||||||
|
self.dy.clamp(-127, 127) as u8,
|
||||||
|
self.wheel as u8,
|
||||||
|
];
|
||||||
|
|
||||||
// reset deltas so we send *relative* movement
|
/* broadcast — this is non‑blocking just like `try_send` on mpsc */
|
||||||
self.dx = 0;
|
if let Err(broadcast::error::SendError(_)) = self.tx.send(HidReport {
|
||||||
self.dy = 0;
|
|
||||||
self.wheel = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
fn send_report(&self, report: [u8; 4]) {
|
|
||||||
let msg = HidReport {
|
|
||||||
kind: Some(hid_report::Kind::MouseReport(report.to_vec())),
|
kind: Some(hid_report::Kind::MouseReport(report.to_vec())),
|
||||||
};
|
}) {
|
||||||
|
self.dev_log(|| warn!("❌ no HID receiver (mouse)"));
|
||||||
match self.tx.send(msg.clone()) {
|
} else {
|
||||||
Ok(n) => {
|
self.dev_log(|| debug!("📤 HID mouse {:?}", report));
|
||||||
tracing::trace!("queued → {} receiver(s)", n);
|
|
||||||
}
|
|
||||||
Err(e) => {
|
|
||||||
tracing::warn!("send dropped report ({e}); falling back to send()");
|
|
||||||
let _ = self.tx.send(msg);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* reset deltas for next frame */
|
||||||
|
self.dx = 0; self.dy = 0; self.wheel = 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -4,56 +4,65 @@
|
|||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use navka_client::NavkaClientApp;
|
use navka_client::NavkaClientApp;
|
||||||
use std::env;
|
use std::{env, fs::OpenOptions, path::Path};
|
||||||
use std::fs::OpenOptions;
|
use tracing_appender::non_blocking;
|
||||||
use tracing_subscriber::fmt;
|
use tracing_appender::non_blocking::WorkerGuard;
|
||||||
use tracing_subscriber::prelude::__tracing_subscriber_SubscriberExt;
|
use tracing_subscriber::{filter::EnvFilter, fmt, prelude::*};
|
||||||
use tracing_subscriber::{fmt, EnvFilter};
|
|
||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
async fn main() -> Result<()> {
|
async fn main() -> Result<()> {
|
||||||
tracing_subscriber::fmt()
|
/*------------- common filter & stderr layer ------------------------*/
|
||||||
.with_env_filter(
|
let env_filter = EnvFilter::try_from_default_env().unwrap_or_else(|_| {
|
||||||
// honour RUST_LOG but fall back to very chatty defaults
|
EnvFilter::new(
|
||||||
EnvFilter::try_from_default_env().unwrap_or_else(|_| {
|
"navka_client=trace,\
|
||||||
EnvFilter::new(
|
navka_server=trace,\
|
||||||
"navka_client=trace,\
|
tonic=debug,\
|
||||||
navka_server=trace,\
|
h2=debug,\
|
||||||
tonic=debug,\
|
tower=debug",
|
||||||
h2=debug,\
|
)
|
||||||
tower=debug",
|
});
|
||||||
)
|
|
||||||
}),
|
let stderr_layer = fmt::layer()
|
||||||
)
|
.with_target(true)
|
||||||
.with_target(true)
|
.with_thread_ids(true)
|
||||||
.with_thread_ids(true)
|
.with_file(true);
|
||||||
.with_file(true)
|
|
||||||
.init();
|
|
||||||
|
|
||||||
let dev_mode = env::var("NAVKA_DEV_MODE").is_ok();
|
let dev_mode = env::var("NAVKA_DEV_MODE").is_ok();
|
||||||
|
let mut _guard: Option<WorkerGuard> = None; // keep guard alive
|
||||||
|
|
||||||
|
/*------------- subscriber setup -----------------------------------*/
|
||||||
if dev_mode {
|
if dev_mode {
|
||||||
|
let log_path = Path::new("/tmp").join("navka-client.log");
|
||||||
|
|
||||||
|
// file → non‑blocking writer (+ guard)
|
||||||
let file = OpenOptions::new()
|
let file = OpenOptions::new()
|
||||||
.create(true)
|
.create(true)
|
||||||
.append(true)
|
.append(true)
|
||||||
.open("/tmp/navka-client.log")?;
|
.open(&log_path)?;
|
||||||
|
let (file_writer, guard) = non_blocking(file);
|
||||||
|
_guard = Some(guard);
|
||||||
|
|
||||||
let subscriber = tracing_subscriber::registry()
|
let file_layer = fmt::layer()
|
||||||
.with(fmt::layer())
|
.with_writer(file_writer)
|
||||||
.with(
|
.with_ansi(false)
|
||||||
fmt::layer()
|
.with_target(true)
|
||||||
.with_writer(file)
|
.with_level(true);
|
||||||
.with_ansi(false)
|
|
||||||
.with_target(true)
|
|
||||||
.with_level(true),
|
|
||||||
);
|
|
||||||
|
|
||||||
tracing::subscriber::set_global_default(subscriber)?;
|
tracing_subscriber::registry()
|
||||||
tracing::info!("navka-client starting in dev mode: logs -> /tmp/navka-client.log");
|
.with(env_filter)
|
||||||
|
.with(stderr_layer)
|
||||||
|
.with(file_layer)
|
||||||
|
.init();
|
||||||
|
|
||||||
|
tracing::info!("navka-client running in DEV mode → {}", log_path.display());
|
||||||
} else {
|
} else {
|
||||||
tracing_subscriber::fmt::init();
|
tracing_subscriber::registry()
|
||||||
|
.with(env_filter)
|
||||||
|
.with(stderr_layer)
|
||||||
|
.init();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*------------- run the actual application -------------------------*/
|
||||||
let mut app = NavkaClientApp::new()?;
|
let mut app = NavkaClientApp::new()?;
|
||||||
app.run().await
|
app.run().await
|
||||||
}
|
}
|
||||||
|
|||||||
@ -39,25 +39,42 @@ impl Relay for Handler {
|
|||||||
while let Some(msg) = in_stream.next().await.transpose()? {
|
while let Some(msg) = in_stream.next().await.transpose()? {
|
||||||
info!("📥 packet received");
|
info!("📥 packet received");
|
||||||
match msg.kind {
|
match msg.kind {
|
||||||
|
/* ───────────── KEYBOARD ───────────── */
|
||||||
Some(hid_report::Kind::KeyboardReport(ref v)) if v.len() == 8 => {
|
Some(hid_report::Kind::KeyboardReport(ref v)) if v.len() == 8 => {
|
||||||
kb.lock().await.write_all(v).await?;
|
let mut f = kb.lock().await;
|
||||||
trace!(" └─ wrote 8 B to /dev/hidg0");
|
match f.write_all(v).await {
|
||||||
|
Ok(()) => info!("⌨️ HID report forwarded → /dev/hidg0"),
|
||||||
|
Err(e) if e.kind() == std::io::ErrorKind::WouldBlock => {
|
||||||
|
trace!("⌨️ /dev/hidg0 busy ({e}); dropped packet");
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
error!("⌨️ write error to /dev/hidg0: {e}");
|
||||||
|
continue; // drop this packet, keep stream alive
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ─────────────── MOUSE ─────────────── */
|
||||||
Some(hid_report::Kind::MouseReport(ref v)) if v.len() == 4 => {
|
Some(hid_report::Kind::MouseReport(ref v)) if v.len() == 4 => {
|
||||||
ms.lock().await.write_all(v).await?;
|
let mut f = ms.lock().await;
|
||||||
trace!(" └─ wrote 4 B to /dev/hidg1");
|
match f.write_all(v).await {
|
||||||
|
Ok(()) => info!("🖱️ HID report forwarded → /dev/hidg1"),
|
||||||
|
Err(e) if e.kind() == std::io::ErrorKind::WouldBlock => {
|
||||||
|
trace!("🖱️ /dev/hidg1 busy ({e}); dropped packet");
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
error!("🖱️ write error to /dev/hidg1: {e}");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ────────────── BAD PACKET ──────────── */
|
||||||
_ => {
|
_ => {
|
||||||
error!(?msg.kind, "⚠️ malformed packet");
|
error!(?msg.kind, "⚠️ malformed packet");
|
||||||
let _bad_len = match &msg.kind {
|
|
||||||
Some(hid_report::Kind::KeyboardReport(v)) => v.len(),
|
|
||||||
Some(hid_report::Kind::MouseReport(v)) => v.len(),
|
|
||||||
_ => 0,
|
|
||||||
};
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
info!("HID report forwarded");
|
|
||||||
let _ = tx.send(Ok(msg)).await;
|
let _ = tx.send(Ok(msg)).await;
|
||||||
}
|
}
|
||||||
info!("🔚 client stream closed");
|
info!("🔚 client stream closed");
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user