2025-06-23 07:18:26 -05:00
|
|
|
|
//! lesavka-server — receive HidReport and write to /dev/hidg0
|
2025-06-16 21:47:01 -05:00
|
|
|
|
// sever/src/main.rs
|
2025-06-01 16:04:00 -05:00
|
|
|
|
#![forbid(unsafe_code)]
|
2025-06-01 13:31:22 -05:00
|
|
|
|
|
2025-06-25 15:13:49 -05:00
|
|
|
|
use std::sync::atomic::{AtomicBool, Ordering};
|
2025-06-25 16:23:50 -05:00
|
|
|
|
use std::{pin::Pin, sync::Arc, time::Duration};
|
|
|
|
|
|
use futures_util::{Stream, StreamExt};
|
2025-06-23 21:22:02 -05:00
|
|
|
|
use tokio::{fs::{OpenOptions}, io::AsyncWriteExt, sync::Mutex};
|
2025-06-21 05:21:57 -05:00
|
|
|
|
use tokio_stream::{wrappers::ReceiverStream};
|
2025-06-25 18:23:38 -05:00
|
|
|
|
use tonic::{transport::Server, Request, Response, Status};
|
|
|
|
|
|
use anyhow::Context as _;
|
2025-06-25 19:04:11 -05:00
|
|
|
|
use tracing::{info, trace, warn, error};
|
2025-06-25 16:23:50 -05:00
|
|
|
|
use tracing_subscriber::{filter::EnvFilter, fmt, prelude::*};
|
|
|
|
|
|
use tracing_appender::non_blocking;
|
2025-06-25 16:52:26 -05:00
|
|
|
|
use tracing_appender::non_blocking::WorkerGuard;
|
2025-06-23 21:22:02 -05:00
|
|
|
|
use udev::{MonitorBuilder};
|
2025-06-21 05:21:57 -05:00
|
|
|
|
|
2025-06-25 15:13:49 -05:00
|
|
|
|
use lesavka_server::{usb_gadget::UsbGadget, video};
|
2025-06-01 21:26:57 -05:00
|
|
|
|
|
2025-06-23 07:18:26 -05:00
|
|
|
|
use lesavka_common::lesavka::{
|
2025-06-01 21:26:57 -05:00
|
|
|
|
relay_server::{Relay, RelayServer},
|
2025-06-17 20:54:31 -05:00
|
|
|
|
KeyboardReport, MouseReport,
|
2025-06-21 05:21:57 -05:00
|
|
|
|
MonitorRequest, VideoPacket,
|
2025-06-01 16:04:00 -05:00
|
|
|
|
};
|
2025-06-02 20:24:00 -05:00
|
|
|
|
|
2025-06-25 16:23:50 -05:00
|
|
|
|
// ───────────────── helper ─────────────────────
|
2025-06-25 16:52:26 -05:00
|
|
|
|
fn init_tracing() -> anyhow::Result<WorkerGuard> {
|
|
|
|
|
|
// 1. create file writer + guard
|
2025-06-25 16:23:50 -05:00
|
|
|
|
let file = std::fs::OpenOptions::new()
|
|
|
|
|
|
.create(true).write(true).truncate(true)
|
|
|
|
|
|
.open("/tmp/lesavka-server.log")?;
|
2025-06-25 16:52:26 -05:00
|
|
|
|
let (file_writer, guard) = non_blocking(file);
|
2025-06-25 16:23:50 -05:00
|
|
|
|
|
2025-06-25 16:52:26 -05:00
|
|
|
|
// 2. build subscriber once
|
|
|
|
|
|
let env = EnvFilter::try_from_default_env()
|
|
|
|
|
|
.unwrap_or_else(|_| EnvFilter::new("lesavka_server=info"));
|
2025-06-25 16:23:50 -05:00
|
|
|
|
let console_layer = fmt::layer()
|
|
|
|
|
|
.with_target(true)
|
|
|
|
|
|
.with_thread_ids(true);
|
2025-06-25 16:52:26 -05:00
|
|
|
|
let file_layer = fmt::layer()
|
2025-06-25 16:23:50 -05:00
|
|
|
|
.with_writer(file_writer)
|
|
|
|
|
|
.with_ansi(false);
|
|
|
|
|
|
|
|
|
|
|
|
tracing_subscriber::registry()
|
2025-06-25 16:52:26 -05:00
|
|
|
|
.with(env)
|
2025-06-25 16:23:50 -05:00
|
|
|
|
.with(console_layer)
|
|
|
|
|
|
.with(file_layer)
|
|
|
|
|
|
.init();
|
|
|
|
|
|
|
2025-06-25 16:52:26 -05:00
|
|
|
|
Ok(guard)
|
2025-06-25 16:23:50 -05:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-06-25 19:04:11 -05:00
|
|
|
|
async fn open_with_retry(
|
|
|
|
|
|
path: &str,
|
|
|
|
|
|
retries: usize,
|
|
|
|
|
|
delay_ms: u64,
|
|
|
|
|
|
) -> anyhow::Result<tokio::fs::File> {
|
|
|
|
|
|
for attempt in 0..=retries {
|
|
|
|
|
|
match OpenOptions::new()
|
|
|
|
|
|
.write(true)
|
|
|
|
|
|
.custom_flags(libc::O_NONBLOCK)
|
|
|
|
|
|
.open(path)
|
|
|
|
|
|
.await
|
|
|
|
|
|
{
|
|
|
|
|
|
Ok(f) => {
|
|
|
|
|
|
info!("✅ opened {path} on attempt #{attempt}");
|
|
|
|
|
|
return Ok(f);
|
|
|
|
|
|
}
|
|
|
|
|
|
Err(e) if attempt < retries => {
|
|
|
|
|
|
warn!("🕒 {path} not ready ({e}); retrying… #{attempt}"); // will retry
|
|
|
|
|
|
tokio::time::sleep(Duration::from_millis(delay_ms)).await;
|
|
|
|
|
|
}
|
|
|
|
|
|
Err(e) => return Err(e).with_context(|| format!("giving up on {path}")),
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
unreachable!()
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-06-21 05:21:57 -05:00
|
|
|
|
/*─────────────────── tonic service ─────────────────────*/
|
2025-06-02 20:41:36 -05:00
|
|
|
|
struct Handler {
|
2025-06-17 20:54:31 -05:00
|
|
|
|
kb: Arc<Mutex<tokio::fs::File>>,
|
|
|
|
|
|
ms: Arc<Mutex<tokio::fs::File>>,
|
2025-06-25 07:46:50 -05:00
|
|
|
|
gadget: UsbGadget,
|
2025-06-25 15:13:49 -05:00
|
|
|
|
did_cycle: AtomicBool,
|
2025-06-24 23:48:06 -05:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
impl Handler {
|
2025-06-25 07:46:50 -05:00
|
|
|
|
async fn make(gadget: UsbGadget) -> anyhow::Result<Self> {
|
2025-06-25 16:23:50 -05:00
|
|
|
|
gadget.cycle()?;
|
|
|
|
|
|
tokio::time::sleep(Duration::from_secs(1)).await;
|
|
|
|
|
|
|
2025-06-25 19:04:11 -05:00
|
|
|
|
let kb = open_with_retry("/dev/hidg0", 10, 100).await?;
|
|
|
|
|
|
let ms = open_with_retry("/dev/hidg1", 10, 100).await?;
|
2025-06-25 16:23:50 -05:00
|
|
|
|
|
2025-06-24 23:48:06 -05:00
|
|
|
|
Ok(Self { kb: Arc::new(Mutex::new(kb)),
|
|
|
|
|
|
ms: Arc::new(Mutex::new(ms)),
|
2025-06-25 15:13:49 -05:00
|
|
|
|
gadget,
|
2025-06-25 16:23:50 -05:00
|
|
|
|
did_cycle: AtomicBool::new(true),
|
2025-06-25 15:13:49 -05:00
|
|
|
|
})
|
2025-06-24 23:48:06 -05:00
|
|
|
|
}
|
2025-06-01 16:04:00 -05:00
|
|
|
|
}
|
2025-06-01 13:31:22 -05:00
|
|
|
|
|
2025-06-02 20:41:36 -05:00
|
|
|
|
#[tonic::async_trait]
|
2025-06-16 22:14:52 -05:00
|
|
|
|
impl Relay for Handler {
|
2025-06-17 08:17:23 -05:00
|
|
|
|
type StreamKeyboardStream = ReceiverStream<Result<KeyboardReport, Status>>;
|
|
|
|
|
|
type StreamMouseStream = ReceiverStream<Result<MouseReport, Status>>;
|
2025-06-21 05:21:57 -05:00
|
|
|
|
type CaptureVideoStream = Pin<Box<dyn Stream<Item = Result<VideoPacket, Status>> + Send + Sync + 'static>>;
|
2025-06-01 13:31:22 -05:00
|
|
|
|
|
2025-06-17 08:17:23 -05:00
|
|
|
|
async fn stream_keyboard(
|
2025-06-01 13:31:22 -05:00
|
|
|
|
&self,
|
2025-06-17 08:17:23 -05:00
|
|
|
|
req: Request<tonic::Streaming<KeyboardReport>>,
|
|
|
|
|
|
) -> Result<Response<Self::StreamKeyboardStream>, Status> {
|
2025-06-25 12:31:48 -05:00
|
|
|
|
// self.gadget.cycle().map_err(|e| Status::internal(e.to_string()))?;
|
2025-06-25 15:13:49 -05:00
|
|
|
|
if !self.did_cycle.swap(true, Ordering::SeqCst) {
|
|
|
|
|
|
self.gadget
|
|
|
|
|
|
.cycle()
|
|
|
|
|
|
.map_err(|e| Status::internal(e.to_string()))?;
|
|
|
|
|
|
tokio::time::sleep(Duration::from_millis(500)).await;
|
|
|
|
|
|
}
|
2025-06-17 08:17:23 -05:00
|
|
|
|
let (tx, rx) = tokio::sync::mpsc::channel(32);
|
2025-06-12 01:57:08 -05:00
|
|
|
|
let kb = self.kb.clone();
|
2025-06-02 20:41:36 -05:00
|
|
|
|
|
|
|
|
|
|
tokio::spawn(async move {
|
2025-06-17 08:17:23 -05:00
|
|
|
|
let mut s = req.into_inner();
|
2025-06-17 20:54:31 -05:00
|
|
|
|
while let Some(pkt) = s.next().await.transpose()? {
|
2025-06-25 15:13:49 -05:00
|
|
|
|
// kb.lock().await.write_all(&pkt.data).await?;
|
2025-06-25 16:23:50 -05:00
|
|
|
|
loop {
|
2025-06-25 15:13:49 -05:00
|
|
|
|
match kb.lock().await.write_all(&pkt.data).await {
|
|
|
|
|
|
Ok(()) => {
|
|
|
|
|
|
trace!("⌨️ wrote {}", pkt.data.iter()
|
|
|
|
|
|
.map(|b| format!("{b:02X}")).collect::<Vec<_>>().join(" "));
|
|
|
|
|
|
break;
|
|
|
|
|
|
},
|
2025-06-25 16:23:50 -05:00
|
|
|
|
Err(e) if matches!(e.raw_os_error(), Some(libc::EBUSY) | Some(libc::EAGAIN)) => {
|
|
|
|
|
|
tokio::time::sleep(Duration::from_millis(10)).await;
|
|
|
|
|
|
continue;
|
2025-06-25 15:13:49 -05:00
|
|
|
|
}
|
|
|
|
|
|
Err(e) => return Err(Status::internal(e.to_string())),
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-06-17 20:54:31 -05:00
|
|
|
|
tx.send(Ok(pkt)).await;//.ok(); // best-effort echo
|
2025-06-17 08:17:23 -05:00
|
|
|
|
}
|
|
|
|
|
|
Ok::<(), Status>(())
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
Ok(Response::new(ReceiverStream::new(rx)))
|
|
|
|
|
|
}
|
2025-06-16 19:19:14 -05:00
|
|
|
|
|
2025-06-17 08:17:23 -05:00
|
|
|
|
async fn stream_mouse(
|
|
|
|
|
|
&self,
|
|
|
|
|
|
req: Request<tonic::Streaming<MouseReport>>,
|
|
|
|
|
|
) -> Result<Response<Self::StreamMouseStream>, Status> {
|
2025-06-17 22:36:23 -05:00
|
|
|
|
let (tx, rx) = tokio::sync::mpsc::channel(4096); // higher burst
|
2025-06-17 08:17:23 -05:00
|
|
|
|
let ms = self.ms.clone();
|
2025-06-16 19:19:14 -05:00
|
|
|
|
|
2025-06-17 08:17:23 -05:00
|
|
|
|
tokio::spawn(async move {
|
|
|
|
|
|
let mut s = req.into_inner();
|
2025-06-18 02:06:11 -05:00
|
|
|
|
let mut boot_mode = true;
|
2025-06-17 20:54:31 -05:00
|
|
|
|
while let Some(pkt) = s.next().await.transpose()? {
|
2025-06-17 23:13:13 -05:00
|
|
|
|
loop {
|
|
|
|
|
|
match ms.lock().await.write_all(&pkt.data).await {
|
2025-06-18 02:06:11 -05:00
|
|
|
|
Ok(()) => {
|
|
|
|
|
|
trace!("🖱️ wrote {}", pkt.data.iter()
|
|
|
|
|
|
.map(|b| format!("{b:02X}")).collect::<Vec<_>>().join(" "));
|
|
|
|
|
|
break;
|
|
|
|
|
|
}
|
2025-06-25 16:23:50 -05:00
|
|
|
|
Err(e) if matches!(e.raw_os_error(), Some(libc::EBUSY) | Some(libc::EAGAIN)) => {
|
|
|
|
|
|
tokio::time::sleep(Duration::from_millis(10)).await;
|
|
|
|
|
|
continue;
|
2025-06-17 23:13:13 -05:00
|
|
|
|
}
|
2025-06-25 16:23:50 -05:00
|
|
|
|
Err(e) => return Err(Status::internal(e.to_string())),
|
2025-06-17 23:13:13 -05:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
let _ = tx.send(Ok(pkt)).await;
|
2025-06-01 13:31:22 -05:00
|
|
|
|
}
|
2025-06-17 08:17:23 -05:00
|
|
|
|
Ok::<(), Status>(())
|
2025-06-01 13:31:22 -05:00
|
|
|
|
});
|
|
|
|
|
|
|
2025-06-17 08:17:23 -05:00
|
|
|
|
Ok(Response::new(ReceiverStream::new(rx)))
|
2025-06-01 13:31:22 -05:00
|
|
|
|
}
|
2025-06-21 05:21:57 -05:00
|
|
|
|
|
|
|
|
|
|
async fn capture_video(
|
|
|
|
|
|
&self,
|
|
|
|
|
|
req: Request<MonitorRequest>,
|
|
|
|
|
|
) -> Result<Response<Self::CaptureVideoStream>, Status> {
|
|
|
|
|
|
let r = req.into_inner();
|
|
|
|
|
|
|
2025-06-25 12:31:48 -05:00
|
|
|
|
// let devs = loop {
|
|
|
|
|
|
// let list = list_gc311_devices()
|
|
|
|
|
|
// .map_err(|e| Status::internal(format!("enum v4l2: {e}")))?;
|
|
|
|
|
|
// if !list.is_empty() { break list; }
|
|
|
|
|
|
// tokio::time::sleep(Duration::from_secs(1)).await;
|
|
|
|
|
|
// };
|
|
|
|
|
|
|
|
|
|
|
|
// let dev = devs
|
|
|
|
|
|
// .get(r.id as usize)
|
|
|
|
|
|
// .ok_or_else(|| Status::invalid_argument(format!("monitor id {} absent", r.id)))?
|
|
|
|
|
|
// .to_owned();
|
|
|
|
|
|
|
|
|
|
|
|
let dev = match r.id {
|
|
|
|
|
|
0 => "/dev/lesavka_l_eye",
|
|
|
|
|
|
1 => "/dev/lesavka_r_eye",
|
|
|
|
|
|
_ => return Err(Status::invalid_argument("monitor id must be 0 or 1")),
|
|
|
|
|
|
}
|
|
|
|
|
|
.to_string();
|
2025-06-21 05:21:57 -05:00
|
|
|
|
|
|
|
|
|
|
info!("🎥 streaming {dev} at ≤{} kb/s", r.max_bitrate);
|
|
|
|
|
|
|
|
|
|
|
|
let s = video::spawn_camera(&dev, r.id, r.max_bitrate)
|
|
|
|
|
|
.await
|
|
|
|
|
|
.map_err(|e| Status::internal(format!("{e:#?}")))?;
|
|
|
|
|
|
|
|
|
|
|
|
Ok(Response::new(Box::pin(s) as _))
|
|
|
|
|
|
}
|
2025-06-01 13:31:22 -05:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-06-21 05:21:57 -05:00
|
|
|
|
/*─────────────────── main ──────────────────────────────*/
|
|
|
|
|
|
#[tokio::main(flavor = "multi_thread", worker_threads = 4)]
|
2025-06-02 20:41:36 -05:00
|
|
|
|
async fn main() -> anyhow::Result<()> {
|
2025-06-21 05:21:57 -05:00
|
|
|
|
/* logging */
|
2025-06-25 18:23:38 -05:00
|
|
|
|
let _log_guard: WorkerGuard = init_tracing()?;
|
2025-06-05 22:44:27 -05:00
|
|
|
|
|
2025-06-21 05:21:57 -05:00
|
|
|
|
/* auto‑cycle task */
|
2025-06-23 21:52:57 -05:00
|
|
|
|
// tokio::spawn(async { monitor_gc311_disconnect().await.ok(); });
|
2025-06-21 05:21:57 -05:00
|
|
|
|
|
2025-06-25 07:46:50 -05:00
|
|
|
|
let gadget = UsbGadget::new("lesavka");
|
|
|
|
|
|
let handler = Handler::make(gadget.clone()).await?;
|
2025-06-12 01:57:08 -05:00
|
|
|
|
|
2025-06-25 09:21:39 -05:00
|
|
|
|
// tokio::spawn({
|
|
|
|
|
|
// let gadget = gadget.clone();
|
|
|
|
|
|
// async move {
|
|
|
|
|
|
// loop {
|
|
|
|
|
|
// tokio::time::sleep(Duration::from_secs(4)).await;
|
|
|
|
|
|
// if LAST_HID_WRITE.elapsed().as_secs() > 3 {
|
|
|
|
|
|
// warn!("no HID traffic in 3 s – cycling UDC");
|
|
|
|
|
|
// let _ = gadget.cycle();
|
|
|
|
|
|
// }
|
|
|
|
|
|
// }
|
|
|
|
|
|
// }
|
|
|
|
|
|
// });
|
2025-06-01 16:04:00 -05:00
|
|
|
|
|
2025-06-25 19:04:11 -05:00
|
|
|
|
info!("🌐 lesavka‑server listening on 0.0.0.0:50051");
|
|
|
|
|
|
|
|
|
|
|
|
if let Err(e) = Server::builder()
|
|
|
|
|
|
.add_service(RelayServer::new(handler))
|
|
|
|
|
|
.serve(([0, 0, 0, 0], 50051).into())
|
|
|
|
|
|
.await
|
|
|
|
|
|
{
|
|
|
|
|
|
error!("💥 gRPC server exited: {e:#}");
|
|
|
|
|
|
std::process::exit(1);
|
|
|
|
|
|
}
|
2025-06-01 13:31:22 -05:00
|
|
|
|
Ok(())
|
|
|
|
|
|
}
|