2025-06-08 04:11:58 -05:00
|
|
|
use anyhow::{Context, Result};
|
2025-06-08 18:11:44 -05:00
|
|
|
use tokio::{sync::mpsc, time::Duration, task::JoinHandle};
|
2025-06-08 04:11:58 -05:00
|
|
|
use tokio_stream::wrappers::ReceiverStream;
|
|
|
|
|
use tonic::Request;
|
2025-06-08 18:11:44 -05:00
|
|
|
use tracing::{info, warn, error};
|
2025-06-08 13:11:31 -05:00
|
|
|
use navka_common::navka::{relay_client::RelayClient, HidReport};
|
2025-06-08 04:11:58 -05:00
|
|
|
use crate::input::keyboard::KeyboardAggregator;
|
|
|
|
|
|
|
|
|
|
pub struct NavkaClientApp {
|
|
|
|
|
server_addr: String,
|
2025-06-08 13:11:31 -05:00
|
|
|
dev_mode: bool,
|
2025-06-08 04:11:58 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl NavkaClientApp {
|
|
|
|
|
pub fn new() -> Result<Self> {
|
2025-06-08 13:11:31 -05:00
|
|
|
let dev_mode = std::env::var("NAVKA_DEV_MODE").is_ok();
|
2025-06-08 04:11:58 -05:00
|
|
|
let addr = std::env::args()
|
|
|
|
|
.nth(1)
|
|
|
|
|
.or_else(|| std::env::var("NAVKA_SERVER_ADDR").ok())
|
|
|
|
|
.unwrap_or_else(|| "http://127.0.0.1:50051".to_owned());
|
|
|
|
|
|
2025-06-08 13:11:31 -05:00
|
|
|
Ok(Self {
|
|
|
|
|
server_addr: addr,
|
|
|
|
|
dev_mode,
|
|
|
|
|
})
|
2025-06-08 04:11:58 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub async fn run(&mut self) -> Result<()> {
|
2025-06-08 18:11:44 -05:00
|
|
|
// aggregator
|
|
|
|
|
let mut aggregator = KeyboardAggregator::new(self.make_channel()?);
|
|
|
|
|
aggregator.init_devices()?; // discover & grab
|
2025-06-08 04:11:58 -05:00
|
|
|
|
2025-06-08 18:11:44 -05:00
|
|
|
// spawn aggregator
|
|
|
|
|
let aggregator_task: JoinHandle<Result<()>> = tokio::spawn(async move {
|
|
|
|
|
aggregator.run().await
|
2025-06-08 04:11:58 -05:00
|
|
|
});
|
|
|
|
|
|
2025-06-08 18:11:44 -05:00
|
|
|
// dev-mode kill future
|
|
|
|
|
let suicide_future = async {
|
|
|
|
|
if self.dev_mode {
|
|
|
|
|
info!("DEV-mode: will kill itself in 30 s");
|
2025-06-08 13:35:23 -05:00
|
|
|
tokio::time::sleep(Duration::from_secs(30)).await;
|
2025-06-08 18:11:44 -05:00
|
|
|
Err::<(), _>(anyhow::anyhow!("dev-mode timer expired\ngoodbye 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-08 18:11:44 -05:00
|
|
|
res = aggregator_task => {
|
|
|
|
|
error!("Aggregator ended: {res:?}");
|
|
|
|
|
std::process::exit(1);
|
2025-06-08 13:35:23 -05:00
|
|
|
},
|
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-08 18:11:44 -05:00
|
|
|
_ = self.reconnect_loop() => {
|
|
|
|
|
// if that loop ends, e.g. user or server
|
|
|
|
|
warn!("Reconnect loop ended?? We exit");
|
|
|
|
|
std::process::exit(0);
|
|
|
|
|
}
|
2025-06-08 04:11:58 -05:00
|
|
|
}
|
2025-06-08 18:11:44 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// The loop that dials the server, sets up gRPC streaming,
|
|
|
|
|
/// and waits for inbound to end. Then tries again, unless aggregator ended.
|
|
|
|
|
async fn reconnect_loop(&self) {
|
|
|
|
|
loop {
|
|
|
|
|
// dial the server
|
|
|
|
|
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;
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// fresh channel for aggregator => we do this with new (tx, rx)
|
|
|
|
|
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;
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
let mut inbound = response.into_inner();
|
|
|
|
|
// read inbound
|
|
|
|
|
while let Some(res) = inbound.message().await.transpose() {
|
|
|
|
|
match res {
|
|
|
|
|
Ok(report) => {
|
|
|
|
|
tracing::debug!(?report.data, "server inbound");
|
|
|
|
|
},
|
|
|
|
|
Err(e) => {
|
|
|
|
|
error!("Inbound error: {e}");
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
warn!("Inbound ended. Will try to reconnect in 5s");
|
|
|
|
|
tokio::time::sleep(Duration::from_secs(5)).await;
|
|
|
|
|
}
|
|
|
|
|
}
|
2025-06-08 04:11:58 -05:00
|
|
|
|
2025-06-08 18:11:44 -05:00
|
|
|
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)
|
2025-06-08 04:11:58 -05:00
|
|
|
}
|
|
|
|
|
}
|