2025-06-15 20:19:27 -05:00
|
|
|
|
#![forbid(unsafe_code)]
|
2025-06-17 20:54:31 -05:00
|
|
|
|
|
2025-06-11 00:37:01 -05:00
|
|
|
|
use anyhow::Result;
|
|
|
|
|
|
use std::time::Duration;
|
2025-06-24 23:48:06 -05:00
|
|
|
|
use tokio::sync::broadcast;
|
2025-06-16 17:54:47 -05:00
|
|
|
|
use tokio_stream::{wrappers::BroadcastStream, StreamExt};
|
2025-06-26 17:26:28 -05:00
|
|
|
|
use tonic::{transport::Channel, Request};
|
|
|
|
|
|
use tracing::{error, info, warn};
|
2025-06-24 23:48:06 -05:00
|
|
|
|
use winit::{
|
2025-06-26 17:26:28 -05:00
|
|
|
|
event::Event,
|
2025-06-24 23:48:06 -05:00
|
|
|
|
event_loop::EventLoopBuilder,
|
2025-06-25 09:21:39 -05:00
|
|
|
|
platform::wayland::EventLoopBuilderExtWayland,
|
2025-06-24 23:48:06 -05:00
|
|
|
|
};
|
2025-06-15 20:19:27 -05:00
|
|
|
|
|
2025-06-26 17:26:28 -05:00
|
|
|
|
use lesavka_common::lesavka::{
|
|
|
|
|
|
relay_client::RelayClient, KeyboardReport, MonitorRequest, MouseReport, VideoPacket,
|
|
|
|
|
|
};
|
2025-06-17 08:17:23 -05:00
|
|
|
|
|
2025-06-26 17:26:28 -05:00
|
|
|
|
use crate::{input::inputs::InputAggregator, output::video::MonitorWindow};
|
2025-06-08 04:11:58 -05:00
|
|
|
|
|
2025-06-23 07:18:26 -05:00
|
|
|
|
pub struct LesavkaClientApp {
|
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
|
|
|
|
}
|
|
|
|
|
|
|
2025-06-23 07:18:26 -05:00
|
|
|
|
impl LesavkaClientApp {
|
2025-06-08 04:11:58 -05:00
|
|
|
|
pub fn new() -> Result<Self> {
|
2025-06-26 17:26:28 -05:00
|
|
|
|
let dev_mode = std::env::var("LESAVKA_DEV_MODE").is_ok();
|
2025-06-17 20:54:31 -05:00
|
|
|
|
let server_addr = std::env::args()
|
2025-06-08 04:11:58 -05:00
|
|
|
|
.nth(1)
|
2025-06-26 14:05:23 -05:00
|
|
|
|
.or_else(|| std::env::var("LESAVKA_SERVER_ADDR").ok())
|
2025-06-17 20:54:31 -05:00
|
|
|
|
.unwrap_or_else(|| "http://127.0.0.1:50051".into());
|
2025-06-15 20:19:27 -05:00
|
|
|
|
|
2025-06-26 17:26:28 -05:00
|
|
|
|
let (kbd_tx, _) = broadcast::channel(1024);
|
|
|
|
|
|
let (mou_tx, _) = broadcast::channel(4096);
|
2025-06-17 20:54:31 -05:00
|
|
|
|
|
2025-06-17 08:17:23 -05:00
|
|
|
|
let mut agg = InputAggregator::new(dev_mode, kbd_tx.clone(), mou_tx.clone());
|
2025-06-26 17:26:28 -05:00
|
|
|
|
agg.init()?; // grab devices immediately
|
2025-06-17 20:54:31 -05:00
|
|
|
|
|
2025-06-26 17:26:28 -05:00
|
|
|
|
Ok(Self { aggregator: Some(agg), server_addr, dev_mode, kbd_tx, mou_tx })
|
2025-06-08 04:11:58 -05:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
pub async fn run(&mut self) -> Result<()> {
|
2025-06-26 17:26:28 -05:00
|
|
|
|
/*────────── persistent gRPC channels ──────────*/
|
|
|
|
|
|
let hid_ep = Channel::from_shared(self.server_addr.clone())?
|
2025-06-26 14:05:23 -05:00
|
|
|
|
.tcp_nodelay(true)
|
|
|
|
|
|
.concurrency_limit(1)
|
|
|
|
|
|
.http2_keep_alive_interval(Duration::from_secs(15))
|
|
|
|
|
|
.connect_lazy();
|
|
|
|
|
|
|
2025-06-26 17:26:28 -05:00
|
|
|
|
let vid_ep = Channel::from_shared(self.server_addr.clone())?
|
2025-06-27 22:51:50 -05:00
|
|
|
|
.initial_connection_window_size(4<<20)
|
|
|
|
|
|
.initial_stream_window_size(4<<20)
|
2025-06-26 14:05:23 -05:00
|
|
|
|
.tcp_nodelay(true)
|
|
|
|
|
|
.connect_lazy();
|
|
|
|
|
|
|
2025-06-26 17:26:28 -05:00
|
|
|
|
/*────────── input aggregator task ─────────────*/
|
2025-06-17 20:54:31 -05:00
|
|
|
|
let aggregator = self.aggregator.take().expect("InputAggregator present");
|
2025-06-26 17:26:28 -05:00
|
|
|
|
let agg_task = tokio::spawn(async move {
|
|
|
|
|
|
let mut a = aggregator;
|
|
|
|
|
|
a.run().await
|
2025-06-17 22:02:33 -05:00
|
|
|
|
});
|
2025-06-08 04:11:58 -05:00
|
|
|
|
|
2025-06-26 17:26:28 -05:00
|
|
|
|
/*────────── HID streams (never return) ────────*/
|
2025-06-26 14:05:23 -05:00
|
|
|
|
let kbd_loop = self.stream_loop_keyboard(hid_ep.clone());
|
|
|
|
|
|
let mou_loop = self.stream_loop_mouse(hid_ep.clone());
|
2025-06-17 08:17:23 -05:00
|
|
|
|
|
2025-06-26 17:26:28 -05:00
|
|
|
|
/*────────── optional 30 s auto‑exit in dev mode */
|
2025-06-17 20:54:31 -05:00
|
|
|
|
let suicide = async {
|
2025-06-08 18:11:44 -05:00
|
|
|
|
if self.dev_mode {
|
2025-06-15 20:19:27 -05:00
|
|
|
|
tokio::time::sleep(Duration::from_secs(30)).await;
|
2025-06-26 17:26:28 -05:00
|
|
|
|
warn!("💀 dev‑mode timeout");
|
2025-06-17 20:54:31 -05:00
|
|
|
|
std::process::exit(0);
|
2025-06-26 17:26:28 -05:00
|
|
|
|
} else {
|
|
|
|
|
|
std::future::pending::<()>().await
|
|
|
|
|
|
}
|
2025-06-08 13:35:23 -05:00
|
|
|
|
};
|
2025-06-08 13:11:31 -05:00
|
|
|
|
|
2025-06-26 17:26:28 -05:00
|
|
|
|
/*────────── video rendering thread (winit) ────*/
|
2025-06-21 05:21:57 -05:00
|
|
|
|
let (video_tx, mut video_rx) = tokio::sync::mpsc::unbounded_channel::<VideoPacket>();
|
|
|
|
|
|
|
|
|
|
|
|
std::thread::spawn(move || {
|
2025-06-26 17:26:28 -05:00
|
|
|
|
let el = EventLoopBuilder::<()>::new().with_any_thread(true).build().unwrap();
|
2025-06-21 05:21:57 -05:00
|
|
|
|
let win0 = MonitorWindow::new(0, &el).expect("win0");
|
|
|
|
|
|
let win1 = MonitorWindow::new(1, &el).expect("win1");
|
2025-06-26 17:26:28 -05:00
|
|
|
|
|
2025-06-25 16:23:50 -05:00
|
|
|
|
let _ = el.run(move |_: Event<()>, _| {
|
2025-06-21 05:21:57 -05:00
|
|
|
|
while let Ok(pkt) = video_rx.try_recv() {
|
|
|
|
|
|
match pkt.id {
|
|
|
|
|
|
0 => win0.push_packet(pkt),
|
|
|
|
|
|
1 => win1.push_packet(pkt),
|
|
|
|
|
|
_ => {}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
});
|
|
|
|
|
|
|
2025-06-26 17:26:28 -05:00
|
|
|
|
/*────────── start video gRPC pullers ──────────*/
|
|
|
|
|
|
tokio::spawn(Self::video_loop(vid_ep.clone(), video_tx));
|
2025-06-21 05:21:57 -05:00
|
|
|
|
|
2025-06-26 17:26:28 -05:00
|
|
|
|
/*────────── central reactor ───────────────────*/
|
2025-06-08 13:35:23 -05:00
|
|
|
|
tokio::select! {
|
2025-06-26 17:26:28 -05:00
|
|
|
|
_ = kbd_loop => { warn!("⚠️⌨️ keyboard stream finished"); },
|
|
|
|
|
|
_ = mou_loop => { warn!("⚠️🖱️ mouse stream finished"); },
|
|
|
|
|
|
_ = suicide => { /* handled above */ },
|
|
|
|
|
|
r = agg_task => {
|
|
|
|
|
|
match r {
|
|
|
|
|
|
Ok(Ok(())) => warn!("input aggregator terminated cleanly"),
|
|
|
|
|
|
Ok(Err(e)) => error!("input aggregator error: {e:?}"),
|
|
|
|
|
|
Err(join_err) => error!("aggregator task panicked: {join_err:?}"),
|
|
|
|
|
|
}
|
|
|
|
|
|
std::process::exit(1);
|
2025-06-08 18:11:44 -05:00
|
|
|
|
}
|
2025-06-08 04:11:58 -05:00
|
|
|
|
}
|
2025-06-26 17:26:28 -05:00
|
|
|
|
|
|
|
|
|
|
// The branches above either loop forever or exit the process; this
|
|
|
|
|
|
// point is unreachable but satisfies the type checker.
|
|
|
|
|
|
#[allow(unreachable_code)]
|
|
|
|
|
|
Ok(())
|
2025-06-08 18:11:44 -05:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-06-17 20:54:31 -05:00
|
|
|
|
/*──────────────── keyboard stream ───────────────*/
|
2025-06-26 14:05:23 -05:00
|
|
|
|
async fn stream_loop_keyboard(&self, ep: Channel) {
|
2025-06-08 18:11:44 -05:00
|
|
|
|
loop {
|
2025-06-26 18:29:14 -05:00
|
|
|
|
info!("⌨️ dial {}", self.server_addr); // LESAVKA-client
|
2025-06-26 14:05:23 -05:00
|
|
|
|
let mut cli = RelayClient::new(ep.clone());
|
2025-06-26 18:29:14 -05:00
|
|
|
|
|
|
|
|
|
|
// ✅ use kbd_tx here – fixes E0271
|
|
|
|
|
|
let outbound = BroadcastStream::new(self.kbd_tx.subscribe())
|
|
|
|
|
|
.filter_map(|r| r.ok());
|
|
|
|
|
|
|
2025-06-26 15:12:23 -05:00
|
|
|
|
match cli.stream_keyboard(Request::new(outbound)).await {
|
|
|
|
|
|
Ok(mut resp) => {
|
2025-06-26 18:29:14 -05:00
|
|
|
|
while let Some(msg) = resp.get_mut().message().await.transpose() {
|
|
|
|
|
|
if let Err(e) = msg { warn!("⌨️ server err: {e}"); break; }
|
|
|
|
|
|
}
|
2025-06-26 15:12:23 -05:00
|
|
|
|
}
|
2025-06-26 18:29:14 -05:00
|
|
|
|
Err(e) => warn!("⌨️ connect failed: {e}"),
|
2025-06-26 15:12:23 -05:00
|
|
|
|
}
|
2025-06-26 18:29:14 -05:00
|
|
|
|
tokio::time::sleep(Duration::from_secs(1)).await; // retry
|
2025-06-17 08:17:23 -05:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-06-17 20:54:31 -05:00
|
|
|
|
/*──────────────── mouse stream ──────────────────*/
|
2025-06-26 14:05:23 -05:00
|
|
|
|
async fn stream_loop_mouse(&self, ep: Channel) {
|
2025-06-17 08:17:23 -05:00
|
|
|
|
loop {
|
2025-06-26 18:29:14 -05:00
|
|
|
|
info!("🖱️ dial {}", self.server_addr);
|
2025-06-26 14:05:23 -05:00
|
|
|
|
let mut cli = RelayClient::new(ep.clone());
|
2025-06-26 18:29:14 -05:00
|
|
|
|
|
|
|
|
|
|
let outbound = BroadcastStream::new(self.mou_tx.subscribe())
|
|
|
|
|
|
.filter_map(|r| r.ok());
|
|
|
|
|
|
|
2025-06-26 15:12:23 -05:00
|
|
|
|
match cli.stream_mouse(Request::new(outbound)).await {
|
|
|
|
|
|
Ok(mut resp) => {
|
2025-06-26 18:29:14 -05:00
|
|
|
|
while let Some(msg) = resp.get_mut().message().await.transpose() {
|
|
|
|
|
|
if let Err(e) = msg { warn!("🖱️ server err: {e}"); break; }
|
|
|
|
|
|
}
|
2025-06-26 15:12:23 -05:00
|
|
|
|
}
|
2025-06-26 18:29:14 -05:00
|
|
|
|
Err(e) => warn!("🖱️ connect failed: {e}"),
|
2025-06-26 15:12:23 -05:00
|
|
|
|
}
|
2025-06-26 18:29:14 -05:00
|
|
|
|
tokio::time::sleep(Duration::from_secs(1)).await;
|
2025-06-08 18:11:44 -05:00
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-06-17 20:54:31 -05:00
|
|
|
|
|
2025-06-21 05:21:57 -05:00
|
|
|
|
/*──────────────── monitor stream ────────────────*/
|
2025-06-26 16:17:31 -05:00
|
|
|
|
async fn video_loop(
|
|
|
|
|
|
ep: Channel,
|
|
|
|
|
|
tx: tokio::sync::mpsc::UnboundedSender<VideoPacket>,
|
|
|
|
|
|
) {
|
|
|
|
|
|
for monitor_id in 0..=1 {
|
2025-06-26 17:26:28 -05:00
|
|
|
|
let ep = ep.clone();
|
|
|
|
|
|
let tx = tx.clone();
|
2025-06-26 16:17:31 -05:00
|
|
|
|
tokio::spawn(async move {
|
|
|
|
|
|
loop {
|
|
|
|
|
|
let mut cli = RelayClient::new(ep.clone());
|
2025-06-26 14:05:23 -05:00
|
|
|
|
let req = MonitorRequest { id: monitor_id, max_bitrate: 6_000 };
|
2025-06-26 16:17:31 -05:00
|
|
|
|
match cli.capture_video(Request::new(req)).await {
|
|
|
|
|
|
Ok(mut stream) => {
|
|
|
|
|
|
while let Some(pkt) = stream.get_mut().message().await.transpose() {
|
|
|
|
|
|
match pkt {
|
2025-06-26 17:26:28 -05:00
|
|
|
|
Ok(p) => { let _ = tx.send(p); }
|
|
|
|
|
|
Err(e) => { error!("video {monitor_id}: {e}"); break; }
|
2025-06-26 16:17:31 -05:00
|
|
|
|
}
|
2025-06-21 05:21:57 -05:00
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-06-26 16:17:31 -05:00
|
|
|
|
Err(e) => error!("video {monitor_id}: {e}"),
|
2025-06-21 05:21:57 -05:00
|
|
|
|
}
|
2025-06-26 16:17:31 -05:00
|
|
|
|
tokio::time::sleep(Duration::from_secs(1)).await;
|
2025-06-21 05:21:57 -05:00
|
|
|
|
}
|
2025-06-26 16:17:31 -05:00
|
|
|
|
});
|
2025-06-21 05:21:57 -05:00
|
|
|
|
}
|
2025-06-26 14:05:23 -05:00
|
|
|
|
}
|
2025-06-08 04:11:58 -05:00
|
|
|
|
}
|