2025-06-08 22:24:14 -05:00
|
|
|
|
// client/src/app.rs
|
2025-06-15 20:19:27 -05:00
|
|
|
|
#![forbid(unsafe_code)]
|
2025-06-11 00:37:01 -05:00
|
|
|
|
use anyhow::Result;
|
|
|
|
|
|
use std::time::Duration;
|
2025-06-16 17:54:47 -05:00
|
|
|
|
use tokio::{sync::broadcast, task::JoinHandle};
|
|
|
|
|
|
use tokio_stream::{wrappers::BroadcastStream, StreamExt};
|
2025-06-08 04:11:58 -05:00
|
|
|
|
use tonic::Request;
|
2025-06-15 22:15:50 -05:00
|
|
|
|
use tracing::{info, warn, error, debug};
|
2025-06-15 20:19:27 -05:00
|
|
|
|
|
2025-06-17 08:17:23 -05:00
|
|
|
|
use navka_common::navka::{relay_client::RelayClient, KeyboardReport, MouseReport,};
|
|
|
|
|
|
|
|
|
|
|
|
use input::inputs::InputAggregator;
|
|
|
|
|
|
|
2025-06-08 04:11:58 -05:00
|
|
|
|
|
|
|
|
|
|
pub struct NavkaClientApp {
|
2025-06-11 00:37:01 -05:00
|
|
|
|
aggregator: Option<InputAggregator>,
|
2025-06-08 04:11:58 -05:00
|
|
|
|
server_addr: String,
|
2025-06-08 13:11:31 -05:00
|
|
|
|
dev_mode: bool,
|
2025-06-17 08:17:23 -05:00
|
|
|
|
kbd_tx: broadcast::Sender<KeyboardReport>,
|
|
|
|
|
|
mou_tx: broadcast::Sender<MouseReport>,
|
2025-06-08 04:11:58 -05:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
impl NavkaClientApp {
|
|
|
|
|
|
pub fn new() -> Result<Self> {
|
2025-06-16 17:54:47 -05:00
|
|
|
|
info!("Creating navka-client app!");
|
2025-06-17 08:17:23 -05:00
|
|
|
|
let dev_mode = std::env::var("NAVKA_DEV_MODE").is_ok();
|
|
|
|
|
|
let server = std::env::args()
|
2025-06-08 04:11:58 -05:00
|
|
|
|
.nth(1)
|
|
|
|
|
|
.or_else(|| std::env::var("NAVKA_SERVER_ADDR").ok())
|
|
|
|
|
|
.unwrap_or_else(|| "http://127.0.0.1:50051".to_owned());
|
2025-06-15 20:19:27 -05:00
|
|
|
|
|
2025-06-17 08:17:23 -05:00
|
|
|
|
let (kbd_tx, _) = broadcast::channel::<KeyboardReport>(1024);
|
|
|
|
|
|
let (mou_tx, _) = broadcast::channel::<MouseReport>(4096);
|
|
|
|
|
|
let mut agg = InputAggregator::new(dev_mode, kbd_tx.clone(), mou_tx.clone());
|
|
|
|
|
|
agg.init()?;
|
2025-06-11 00:37:01 -05:00
|
|
|
|
|
2025-06-08 13:11:31 -05:00
|
|
|
|
Ok(Self {
|
2025-06-17 08:17:23 -05:00
|
|
|
|
keyboard: Some(kbd_aggregator),
|
|
|
|
|
|
mouse: Some(mou_aggregator),
|
2025-06-08 13:11:31 -05:00
|
|
|
|
server_addr: addr,
|
|
|
|
|
|
dev_mode,
|
2025-06-15 20:19:27 -05:00
|
|
|
|
tx,
|
2025-06-08 13:11:31 -05:00
|
|
|
|
})
|
2025-06-08 04:11:58 -05:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
pub async fn run(&mut self) -> Result<()> {
|
2025-06-16 17:54:47 -05:00
|
|
|
|
info!("Running navka-client app!");
|
2025-06-17 08:17:23 -05:00
|
|
|
|
/* ─ spawn the input thread ─ */
|
|
|
|
|
|
let agg_task: JoinHandle<Result<()>> =
|
|
|
|
|
|
tokio::spawn(async move { self.aggregator.take().unwrap().run().await });
|
2025-06-08 04:11:58 -05:00
|
|
|
|
|
2025-06-17 08:17:23 -05:00
|
|
|
|
/* ─ stream loops ─ */
|
|
|
|
|
|
let kbd_loop = self.stream_loop_keyboard();
|
|
|
|
|
|
let mou_loop = self.stream_loop_mouse();
|
|
|
|
|
|
|
|
|
|
|
|
/* ─ suicide timer for DEV mode ─ */
|
2025-06-08 18:11:44 -05:00
|
|
|
|
let suicide_future = async {
|
|
|
|
|
|
if self.dev_mode {
|
2025-06-11 00:37:01 -05:00
|
|
|
|
info!("DEV-mode: will kill itself in 30s");
|
2025-06-15 20:19:27 -05:00
|
|
|
|
tokio::time::sleep(Duration::from_secs(30)).await;
|
2025-06-17 08:17:23 -05:00
|
|
|
|
Err::<(), _>(anyhow::anyhow!("dev-mode timer expired - 💀goodbye💀 cruel world..."))
|
2025-06-08 13:35:23 -05:00
|
|
|
|
} else {
|
|
|
|
|
|
futures::future::pending().await
|
2025-06-08 13:11:31 -05:00
|
|
|
|
}
|
2025-06-08 13:35:23 -05:00
|
|
|
|
};
|
2025-06-08 13:11:31 -05:00
|
|
|
|
|
2025-06-08 18:11:44 -05:00
|
|
|
|
// Combine aggregator + dev + reconnect logic
|
2025-06-08 13:35:23 -05:00
|
|
|
|
tokio::select! {
|
2025-06-11 00:37:01 -05:00
|
|
|
|
// 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);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-06-08 13:35:23 -05:00
|
|
|
|
},
|
2025-06-11 00:37:01 -05:00
|
|
|
|
// dev-mode
|
2025-06-08 18:11:44 -05:00
|
|
|
|
res = suicide_future => {
|
|
|
|
|
|
warn!("Dev-mode: {res:?}");
|
|
|
|
|
|
std::process::exit(0);
|
2025-06-08 13:35:23 -05:00
|
|
|
|
},
|
2025-06-11 00:37:01 -05:00
|
|
|
|
// reconnect loop
|
2025-06-08 18:11:44 -05:00
|
|
|
|
_ = self.reconnect_loop() => {
|
2025-06-15 20:19:27 -05:00
|
|
|
|
warn!("Reconnect loop ended??");
|
2025-06-08 18:11:44 -05:00
|
|
|
|
std::process::exit(0);
|
|
|
|
|
|
}
|
2025-06-08 04:11:58 -05:00
|
|
|
|
}
|
2025-06-08 18:11:44 -05:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-06-17 08:17:23 -05:00
|
|
|
|
#[inline(always)]
|
|
|
|
|
|
async fn delay() { tokio::time::sleep(Duration::from_secs(1)).await; }
|
|
|
|
|
|
|
|
|
|
|
|
/*──────────────────────────────── keyboard stream ─────────────────────────*/
|
|
|
|
|
|
async fn stream_loop_keyboard(&self) {
|
2025-06-08 18:11:44 -05:00
|
|
|
|
loop {
|
2025-06-17 08:17:23 -05:00
|
|
|
|
info!("⌨️ dial {}", self.server_addr);
|
|
|
|
|
|
let mut cli = match RelayClient::connect(self.server_addr.clone()).await {
|
|
|
|
|
|
Ok(c) => c, Err(e) => { error!("connect: {e}"); delay().await; continue }
|
2025-06-08 18:11:44 -05:00
|
|
|
|
};
|
2025-06-17 08:17:23 -05:00
|
|
|
|
let outbound = BroadcastStream::new(self.kbd_tx.subscribe())
|
|
|
|
|
|
.filter_map(|r| r.ok());
|
2025-06-08 18:11:44 -05:00
|
|
|
|
|
2025-06-17 08:17:23 -05:00
|
|
|
|
info!("⌨️ start stream_keyboard()");
|
|
|
|
|
|
let resp = match cli.stream_keyboard(Request::new(outbound)).await {
|
|
|
|
|
|
Ok(r) => r, Err(e) => { error!("stream_keyboard: {e}"); delay().await; continue }
|
|
|
|
|
|
};
|
2025-06-15 22:15:50 -05:00
|
|
|
|
|
2025-06-17 08:17:23 -05:00
|
|
|
|
let mut inbound = resp.into_inner();
|
|
|
|
|
|
while let Some(m) = inbound.message().await.transpose() {
|
|
|
|
|
|
match m {
|
|
|
|
|
|
Ok(r) => debug!("↩️ kbd echo {} B", r.data.len()),
|
|
|
|
|
|
Err(e) => { error!("kbd inbound: {e}"); break }
|
2025-06-08 18:11:44 -05:00
|
|
|
|
}
|
2025-06-17 08:17:23 -05:00
|
|
|
|
}
|
|
|
|
|
|
warn!("⌨️ disconnected, retry");
|
|
|
|
|
|
delay().await;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/*──────────────────────────────── mouse stream ────────────────────────────*/
|
|
|
|
|
|
async fn stream_loop_mouse(&self) {
|
|
|
|
|
|
loop {
|
|
|
|
|
|
info!("🖱️ dial {}", self.server_addr);
|
|
|
|
|
|
let mut cli = match RelayClient::connect(self.server_addr.clone()).await {
|
|
|
|
|
|
Ok(c) => c, Err(e) => { error!("connect: {e}"); delay().await; continue }
|
2025-06-08 18:11:44 -05:00
|
|
|
|
};
|
2025-06-17 08:17:23 -05:00
|
|
|
|
let outbound = BroadcastStream::new(self.mou_tx.subscribe())
|
|
|
|
|
|
.filter_map(|r| r.ok());
|
2025-06-08 18:11:44 -05:00
|
|
|
|
|
2025-06-17 08:17:23 -05:00
|
|
|
|
info!("🖱️ start stream_mouse()");
|
|
|
|
|
|
let resp = match cli.stream_mouse(Request::new(outbound)).await {
|
|
|
|
|
|
Ok(r) => r, Err(e) => { error!("stream_mouse: {e}"); delay().await; continue }
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
let mut inbound = resp.into_inner();
|
|
|
|
|
|
while let Some(m) = inbound.message().await.transpose() {
|
|
|
|
|
|
match m {
|
|
|
|
|
|
Ok(r) => debug!("↩️ mouse echo {} B", r.data.len()),
|
|
|
|
|
|
Err(e) => { error!("mouse inbound: {e}"); break }
|
2025-06-08 18:11:44 -05:00
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-06-17 08:17:23 -05:00
|
|
|
|
warn!("🖱️ disconnected, retry");
|
|
|
|
|
|
delay().await;
|
2025-06-08 18:11:44 -05:00
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-06-08 04:11:58 -05:00
|
|
|
|
}
|