From 6d81aa3a173950c78b0b9d527076e6ffaeeeee7a Mon Sep 17 00:00:00 2001 From: Brad Stein Date: Sun, 1 Jun 2025 16:04:00 -0500 Subject: [PATCH] update to edition 2024 --- client/Cargo.toml | 2 +- client/src/main.rs | 54 ++++++++++++++++++------------- scripts/install-server.sh | 12 ++++++- server/Cargo.toml | 8 +++-- server/src/main.rs | 68 ++++++++++++++++++++++++++++----------- 5 files changed, 98 insertions(+), 46 deletions(-) diff --git a/client/Cargo.toml b/client/Cargo.toml index 07ea9ad..d6b511f 100644 --- a/client/Cargo.toml +++ b/client/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "navka-client" version = "0.1.0" -edition = "2021" +edition = "2024" [dependencies] tokio = { version = "1.45", features = ["full"] } diff --git a/client/src/main.rs b/client/src/main.rs index 428796f..3fbd6da 100644 --- a/client/src/main.rs +++ b/client/src/main.rs @@ -1,37 +1,47 @@ +//! navka-client – forward keyboard/mouse HID reports to navka-server +#![forbid(unsafe_code)] + use anyhow::Result; -use navka_common::navka::HidReport; -use navka_common::navka::relay_client::RelayClient; -use tokio::sync::mpsc; +use navka_common::navka::{hid_report::*, relay_client::RelayClient, HidReport}; +use tokio::{sync::mpsc, time::sleep}; use tokio_stream::wrappers::ReceiverStream; -use tonic::{Request, transport::Channel}; +use tonic::{transport::Channel, Request}; #[tokio::main] async fn main() -> Result<()> { - // 1) connect to the navka-server gRPC endpoint - let channel = Channel::from_static("http://127.0.0.1:50051").connect().await?; - let mut client = RelayClient::new(channel); + // Connect to navka-server (adjust the address if server not local) + let channel = Channel::from_static("http://127.0.0.1:50051") + .connect() + .await?; - // 2) create an mpsc channel that we can push HID reports into - let (tx_request, rx_request) = mpsc::channel::(32); - let outbound = ReceiverStream::new(rx_request); + // mpsc channel -> ReceiverStream -> gRPC bidirectional stream + let (tx, rx) = mpsc::channel::(32); + let outbound = ReceiverStream::new(rx); - // 3) start the bi-directional stream (note the REQUIRED argument) - let response = client.stream(Request::new(outbound)).await?; - let mut inbound = response.into_inner(); // Streaming + // Kick off the RPC – note: in tonic 0.11 the request object is built + // by wrapping the outbound stream in `Request::new(...)`. + let mut inbound = RelayClient::new(channel) + .stream(Request::new(outbound)) + .await? + .into_inner(); - // 4) demo: send “press A”, wait 100 ms, send “release” + // Example task: press and release the 'a' key once. tokio::spawn(async move { - let press_a = HidReport { data: vec![0x00,0x00,0x04,0,0,0,0,0] }; - let release = HidReport { data: vec![0x00; 8] }; + // 8-byte boot-keyboard report: [mods, reserved, key1..6] + let press_a = HidReport { + data: vec![0x00, 0x00, 0x04, 0, 0, 0, 0, 0], + }; + let release = HidReport { data: vec![0; 8] }; - tx_request.send(press_a).await.ok(); - tokio::time::sleep(std::time::Duration::from_millis(100)).await; - tx_request.send(release).await.ok(); + tx.send(press_a).await.ok(); + sleep(std::time::Duration::from_millis(100)).await; + tx.send(release).await.ok(); }); - // 5) print anything the server echoes back - while let Some(msg) = inbound.message().await? { - println!("🔄 got report from server: {:?}", msg.data); + // Print whatever the server echoes back. + while let Some(report) = inbound.message().await? { + println!("🔄 received: {:?}", report.data); } + Ok(()) } diff --git a/scripts/install-server.sh b/scripts/install-server.sh index 5f5f49a..1c21ec9 100755 --- a/scripts/install-server.sh +++ b/scripts/install-server.sh @@ -2,10 +2,20 @@ set -euo pipefail # 1) Install system dependencies (Arch Linux) -sudo pacman -S --needed --noconfirm git rustup protobuf gcc ustreamer pulseaudio tailscale +sudo pacman -S --needed --noconfirm git rustup protobuf gcc pulseaudio tailscale snapd +# Enable and start snapd if not already: +sudo systemctl enable --now snapd.socket +# Create classic symlink for snap (one-time): +sudo ln -sf /var/lib/snapd/snap /snap +# Install µStreamer via Snap (if you actually need streaming) +if ! snap list | grep -q ustreamer; then + sudo snap install ustreamer +fi # 2) Ensure Rust toolchain is present rustup toolchain install stable +sudo install -Dm755 target/release/navka-server /usr/local/bin/navka-server +sudo install -Dm755 gadget/navka-gadget.sh /usr/local/bin/navka-gadget.sh # 3) Determine repo URL from the current directory REPO_DIR="$(cd "$(dirname "$0")/.." && pwd)" diff --git a/server/Cargo.toml b/server/Cargo.toml index b5e2907..b65fbff 100644 --- a/server/Cargo.toml +++ b/server/Cargo.toml @@ -4,6 +4,8 @@ version = "0.1.0" edition = "2021" [dependencies] -tokio = { version = "1", features = ["rt-multi-thread", "macros"] } -navka-common = { path = "../common" } -tokio-stream = "0.1" +tokio = { version = "1.45", features = ["full", "fs"] } # add "fs" +tokio-stream = "0.1" +tonic = { version = "0.11", features = ["transport"] } +anyhow = "1.0" +navka-common = { path = "../common" } diff --git a/server/src/main.rs b/server/src/main.rs index 831c715..9293fcc 100644 --- a/server/src/main.rs +++ b/server/src/main.rs @@ -1,43 +1,73 @@ -use navka_common::navka::{relay_server::{Relay, RelayServer}, HidReport}; -use tokio::{fs::OpenOptions, io::AsyncWriteExt, sync::mpsc}; -use tokio_stream::wrappers::ReceiverStream; +//! navka-server – bridge HID reports between client (gRPC) and /dev/hidg0 +#![forbid(unsafe_code)] + +use anyhow::Result; +use navka_common::navka::{ + hid_report::*, relay_server::Relay, HidReport, RelayServer, +}; +use std::{path::Path, sync::Arc}; +use tokio::{ + fs::OpenOptions, + io::AsyncWriteExt, + signal, + sync::{mpsc, Mutex}, +}; use tonic::{transport::Server, Request, Response, Status}; -const HID_DEV: &str = "/dev/hidg0"; - -#[derive(Default)] -struct RelaySvc; +/// Implementation of the gRPC service generated by tonic-build. +#[derive(Debug)] +pub struct RelaySvc { + /// Shared handle to /dev/hidg0 (protected by a mutex because several + /// gRPC streams may write concurrently). + hidg: Arc>, +} #[tonic::async_trait] impl Relay for RelaySvc { - type StreamStream = ReceiverStream>; + type StreamStream = tokio_stream::wrappers::ReceiverStream>; async fn stream( &self, request: Request>, ) -> Result, Status> { let mut inbound = request.into_inner(); - let (tx, rx) = mpsc::channel(8); - // writer task: client → hidg0 + // Channel we’ll use if one day we need to send something back. + let (tx, rx) = mpsc::channel(32); + + let hidg = self.hidg.clone(); tokio::spawn(async move { - let mut hid = OpenOptions::new().write(true).open(HID_DEV).await.unwrap(); - while let Some(Ok(report)) = inbound.message().await { - hid.write_all(&report.data).await.unwrap(); + while let Some(report) = inbound.message().await.transpose()? { + // Write raw 8-byte packet to the gadget device + hidg.lock().await.write_all(&report.data).await?; } + Ok::<(), anyhow::Error>(()) }); - // nothing to send back yet - Ok(Response::new(ReceiverStream::new(rx))) + Ok(Response::new(tokio_stream::wrappers::ReceiverStream::new(rx))) } } #[tokio::main] -async fn main() -> anyhow::Result<()> { - let addr = "0.0.0.0:4000".parse()?; +async fn main() -> Result<()> { + // Open /dev/hidg0 once and share it. + let hid_dev = if Path::new("/dev/hidg0").exists() { + OpenOptions::new().write(true).open("/dev/hidg0").await? + } else { + anyhow::bail!("/dev/hidg0 not found – is navka-gadget running?"); + }; + + let svc = RelayServer::new(RelaySvc { + hidg: Arc::new(Mutex::new(hid_dev)), + }); + + println!("🛰️ navka-server listening on 0.0.0.0:50051"); Server::builder() - .add_service(RelayServer::new(RelaySvc::default())) - .serve(addr) + .add_service(svc) + .serve_with_shutdown("0.0.0.0:50051".parse()?, async { + signal::ctrl_c().await.ok(); + }) .await?; + Ok(()) }