lesavka/client/src/app.rs

86 lines
2.9 KiB
Rust
Raw Normal View History

2025-06-08 04:11:58 -05:00
use anyhow::{Context, Result};
use tokio::sync::mpsc;
2025-06-08 14:40:15 -05:00
use tokio::time::Duration;
2025-06-08 04:11:58 -05:00
use tokio_stream::wrappers::ReceiverStream;
use tonic::Request;
2025-06-08 13:11:31 -05:00
use tracing::info;
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<()> {
// 1) Connect to navka-server
let mut client = RelayClient::connect(self.server_addr.clone())
.await
.with_context(|| format!("failed to connect to {}", self.server_addr))?;
// 2) Create a bidirectional streaming stub
let (tx, rx) = mpsc::channel::<HidReport>(32);
let outbound = ReceiverStream::new(rx);
let response = client.stream(Request::new(outbound)).await?;
let mut inbound = response.into_inner();
// 3) Start reading from all keyboards in a background task
let mut aggregator = KeyboardAggregator::new(tx.clone());
aggregator.init_devices()?; // discover & grab
2025-06-08 14:40:15 -05:00
let kb_handle = tokio::spawn(async move {
2025-06-08 04:11:58 -05:00
if let Err(e) = aggregator.run().await {
tracing::error!("KeyboardAggregator failed: {e}");
}
});
2025-06-08 13:11:31 -05:00
// 4) Add 30 second suicide for dev mode
2025-06-08 14:40:15 -05:00
let suicide_fut = async {
2025-06-08 13:35:23 -05:00
if std::env::var_os("NAVKA_DEV_MODE").is_some() {
tracing::info!("DEV-mode: will exit in 30 s");
tokio::time::sleep(Duration::from_secs(30)).await;
Err::<(), _>(anyhow::anyhow!("dev-mode timer expired")) // cause select! branch to win
} 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
// 5) Inbound loop: we do something with reports from the server, e.g. logging:
2025-06-08 14:40:15 -05:00
let inbound_fut = async {
2025-06-08 13:35:23 -05:00
while let Some(report) = inbound.message().await? {
tracing::debug!(?report.data, "msg from server");
}
Err::<(), _>(anyhow::anyhow!("server closed stream"))
};
// 6) Race the futures
tokio::select! {
2025-06-08 14:40:15 -05:00
res = inbound_fut => {
2025-06-08 13:35:23 -05:00
tracing::warn!("Inbound stream ended: {res:?}");
},
2025-06-08 14:40:15 -05:00
res = kb_handle => {
2025-06-08 13:35:23 -05:00
tracing::warn!("Keyboard task finished: {res:?}");
},
2025-06-08 14:40:15 -05:00
res = suicide_fut => {
2025-06-08 13:35:23 -05:00
tracing::warn!("Dev-mode shutdown: {res:?}");
},
2025-06-08 04:11:58 -05:00
}
2025-06-08 13:35:23 -05:00
// 7) If inbound stream ends, stop the input task
2025-06-08 04:11:58 -05:00
Ok(())
}
}