2026-04-12 21:10:15 -03:00
|
|
|
// lesavka-server - gadget cycle guarded by env
|
2025-06-27 06:56:08 -05:00
|
|
|
// server/src/main.rs
|
2026-04-12 21:10:15 -03:00
|
|
|
#[allow(clippy::useless_attribute)] #[forbid(unsafe_code)]
|
2025-06-25 18:23:38 -05:00
|
|
|
use anyhow::Context as _;
|
2025-06-27 06:56:08 -05:00
|
|
|
use futures_util::{Stream, StreamExt};
|
2026-04-10 14:05:04 -03:00
|
|
|
use std::sync::atomic::AtomicBool;
|
2026-04-08 22:23:40 -03:00
|
|
|
use std::time::Duration;
|
2026-01-06 11:48:36 -03:00
|
|
|
use std::{backtrace::Backtrace, panic, pin::Pin, sync::Arc};
|
2026-04-10 14:05:04 -03:00
|
|
|
use tokio::sync::Mutex;
|
2025-06-27 06:56:08 -05:00
|
|
|
use tokio_stream::wrappers::ReceiverStream;
|
|
|
|
|
use tonic::transport::Server;
|
2025-11-30 16:16:03 -03:00
|
|
|
use tonic::{Request, Response, Status};
|
|
|
|
|
use tonic_reflection::server::Builder as ReflBuilder;
|
2026-04-10 14:05:04 -03:00
|
|
|
use tracing::{debug, error, info, warn};
|
2025-06-21 05:21:57 -05:00
|
|
|
|
2025-06-23 07:18:26 -05:00
|
|
|
use lesavka_common::lesavka::{
|
2026-04-08 20:00:14 -03:00
|
|
|
AudioPacket, Empty, KeyboardReport, MonitorRequest, MouseReport, PasteReply, PasteRequest,
|
|
|
|
|
ResetUsbReply, VideoPacket,
|
2025-06-01 21:26:57 -05:00
|
|
|
relay_server::{Relay, RelayServer},
|
2025-06-01 16:04:00 -05:00
|
|
|
};
|
2025-06-02 20:24:00 -05:00
|
|
|
|
2026-04-10 14:05:04 -03:00
|
|
|
use lesavka_server::{
|
|
|
|
|
audio, camera, camera_runtime::CameraRuntime, gadget::UsbGadget, handshake::HandshakeSvc,
|
|
|
|
|
paste, runtime_support, runtime_support::init_tracing, uvc_runtime, video,
|
|
|
|
|
};
|
2025-06-27 06:56:08 -05:00
|
|
|
|
|
|
|
|
/*──────────────── constants ────────────────*/
|
2025-07-04 10:17:01 -05:00
|
|
|
const VERSION: &str = env!("CARGO_PKG_VERSION");
|
|
|
|
|
const PKG_NAME: &str = env!("CARGO_PKG_NAME");
|
2026-01-06 11:48:36 -03:00
|
|
|
|
2025-06-27 06:56:08 -05:00
|
|
|
/*──────────────── Handler ───────────────────*/
|
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,
|
2026-01-06 04:38:41 -03:00
|
|
|
did_cycle: Arc<AtomicBool>,
|
2026-04-08 22:23:40 -03:00
|
|
|
camera_rt: Arc<CameraRuntime>,
|
|
|
|
|
}
|
|
|
|
|
|
2025-06-24 23:48:06 -05:00
|
|
|
impl Handler {
|
2025-06-27 06:56:08 -05:00
|
|
|
async fn new(gadget: UsbGadget) -> anyhow::Result<Self> {
|
2026-04-10 14:05:04 -03:00
|
|
|
if runtime_support::allow_gadget_cycle() {
|
2025-06-27 06:56:08 -05:00
|
|
|
info!("🛠️ Initial USB reset…");
|
2025-11-30 16:16:03 -03:00
|
|
|
let _ = gadget.cycle(); // ignore failure - may boot without host
|
2025-06-26 21:49:29 -05:00
|
|
|
} else {
|
2026-01-28 17:52:00 -03:00
|
|
|
info!(
|
|
|
|
|
"🔒 gadget cycle disabled at startup (set LESAVKA_ALLOW_GADGET_CYCLE=1 to enable)"
|
|
|
|
|
);
|
2025-06-26 21:49:29 -05:00
|
|
|
}
|
2025-06-25 16:23:50 -05:00
|
|
|
|
2025-06-27 06:56:08 -05:00
|
|
|
info!("🛠️ opening HID endpoints …");
|
2026-04-10 14:05:04 -03:00
|
|
|
let kb = runtime_support::open_with_retry("/dev/hidg0").await?;
|
|
|
|
|
let ms = runtime_support::open_with_retry("/dev/hidg1").await?;
|
2025-06-25 20:00:34 -05:00
|
|
|
info!("✅ HID endpoints ready");
|
2025-06-26 21:49:29 -05:00
|
|
|
|
|
|
|
|
Ok(Self {
|
|
|
|
|
kb: Arc::new(Mutex::new(kb)),
|
|
|
|
|
ms: Arc::new(Mutex::new(ms)),
|
|
|
|
|
gadget,
|
2026-01-06 04:38:41 -03:00
|
|
|
did_cycle: Arc::new(AtomicBool::new(false)),
|
2026-04-08 22:23:40 -03:00
|
|
|
camera_rt: Arc::new(CameraRuntime::new()),
|
2025-06-26 21:49:29 -05:00
|
|
|
})
|
|
|
|
|
}
|
2025-07-05 12:45:08 -05:00
|
|
|
|
|
|
|
|
async fn reopen_hid(&self) -> anyhow::Result<()> {
|
2026-04-10 14:05:04 -03:00
|
|
|
let kb_new = runtime_support::open_with_retry("/dev/hidg0").await?;
|
|
|
|
|
let ms_new = runtime_support::open_with_retry("/dev/hidg1").await?;
|
2025-07-05 12:45:08 -05:00
|
|
|
*self.kb.lock().await = kb_new;
|
|
|
|
|
*self.ms.lock().await = ms_new;
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|
2025-06-01 16:04:00 -05:00
|
|
|
}
|
2025-06-01 13:31:22 -05:00
|
|
|
|
2025-06-27 06:56:08 -05:00
|
|
|
/*──────────────── gRPC service ─────────────*/
|
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-28 15:45:11 -05:00
|
|
|
/* existing streams ─ unchanged, except: no more auto-reset */
|
2025-11-30 16:16:03 -03:00
|
|
|
type StreamKeyboardStream = ReceiverStream<Result<KeyboardReport, Status>>;
|
|
|
|
|
type StreamMouseStream = ReceiverStream<Result<MouseReport, Status>>;
|
|
|
|
|
type CaptureVideoStream = Pin<Box<dyn Stream<Item = Result<VideoPacket, Status>> + Send>>;
|
|
|
|
|
type CaptureAudioStream = Pin<Box<dyn Stream<Item = Result<AudioPacket, Status>> + Send>>;
|
2025-06-30 19:35:38 -05:00
|
|
|
type StreamMicrophoneStream = ReceiverStream<Result<Empty, Status>>;
|
2025-07-03 08:19:59 -05:00
|
|
|
type StreamCameraStream = ReceiverStream<Result<Empty, Status>>;
|
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> {
|
2026-04-10 14:05:04 -03:00
|
|
|
let rpc_id = runtime_support::next_stream_id();
|
2026-04-08 23:01:31 -03:00
|
|
|
info!(rpc_id, "⌨️ stream_keyboard opened");
|
2025-06-27 06:56:08 -05:00
|
|
|
let (tx, rx) = tokio::sync::mpsc::channel(32);
|
2025-06-12 01:57:08 -05:00
|
|
|
let kb = self.kb.clone();
|
2026-01-06 04:38:41 -03:00
|
|
|
let ms = self.ms.clone();
|
|
|
|
|
let gadget = self.gadget.clone();
|
|
|
|
|
let did_cycle = self.did_cycle.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()? {
|
2026-04-10 14:05:04 -03:00
|
|
|
if let Err(e) = runtime_support::write_hid_report(&kb, &pkt.data).await {
|
2026-04-08 23:01:31 -03:00
|
|
|
if e.raw_os_error() == Some(libc::EAGAIN) {
|
|
|
|
|
debug!(rpc_id, "⌨️ write would block (dropped)");
|
|
|
|
|
} else {
|
|
|
|
|
warn!(rpc_id, "⌨️ write failed: {e} (dropped)");
|
2026-04-10 14:05:04 -03:00
|
|
|
runtime_support::recover_hid_if_needed(
|
2026-04-08 23:01:31 -03:00
|
|
|
&e,
|
|
|
|
|
gadget.clone(),
|
|
|
|
|
kb.clone(),
|
|
|
|
|
ms.clone(),
|
|
|
|
|
did_cycle.clone(),
|
|
|
|
|
)
|
|
|
|
|
.await;
|
|
|
|
|
}
|
2025-06-25 15:13:49 -05:00
|
|
|
}
|
2025-06-27 06:56:08 -05:00
|
|
|
tx.send(Ok(pkt)).await.ok();
|
2025-06-17 08:17:23 -05:00
|
|
|
}
|
2026-04-08 23:01:31 -03:00
|
|
|
info!(rpc_id, "⌨️ stream_keyboard closed");
|
2025-06-17 08:17:23 -05:00
|
|
|
Ok::<(), Status>(())
|
|
|
|
|
});
|
2025-06-27 06:56:08 -05:00
|
|
|
|
2025-06-26 15:12:23 -05:00
|
|
|
Ok(Response::new(ReceiverStream::new(rx)))
|
2025-06-17 08:17:23 -05:00
|
|
|
}
|
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> {
|
2026-04-10 14:05:04 -03:00
|
|
|
let rpc_id = runtime_support::next_stream_id();
|
2026-04-08 23:01:31 -03:00
|
|
|
info!(rpc_id, "🖱️ stream_mouse opened");
|
2025-06-28 15:45:11 -05:00
|
|
|
let (tx, rx) = tokio::sync::mpsc::channel(1024);
|
2025-06-17 08:17:23 -05:00
|
|
|
let ms = self.ms.clone();
|
2026-01-06 04:38:41 -03:00
|
|
|
let kb = self.kb.clone();
|
|
|
|
|
let gadget = self.gadget.clone();
|
|
|
|
|
let did_cycle = self.did_cycle.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-17 20:54:31 -05:00
|
|
|
while let Some(pkt) = s.next().await.transpose()? {
|
2026-04-10 14:05:04 -03:00
|
|
|
if let Err(e) = runtime_support::write_hid_report(&ms, &pkt.data).await {
|
2026-04-08 23:01:31 -03:00
|
|
|
if e.raw_os_error() == Some(libc::EAGAIN) {
|
|
|
|
|
debug!(rpc_id, "🖱️ write would block (dropped)");
|
|
|
|
|
} else {
|
|
|
|
|
warn!(rpc_id, "🖱️ write failed: {e} (dropped)");
|
2026-04-10 14:05:04 -03:00
|
|
|
runtime_support::recover_hid_if_needed(
|
2026-04-08 23:01:31 -03:00
|
|
|
&e,
|
|
|
|
|
gadget.clone(),
|
|
|
|
|
kb.clone(),
|
|
|
|
|
ms.clone(),
|
|
|
|
|
did_cycle.clone(),
|
|
|
|
|
)
|
|
|
|
|
.await;
|
|
|
|
|
}
|
2025-06-26 20:38:55 -05:00
|
|
|
}
|
2025-06-27 06:56:08 -05:00
|
|
|
tx.send(Ok(pkt)).await.ok();
|
2025-06-01 13:31:22 -05:00
|
|
|
}
|
2026-04-08 23:01:31 -03:00
|
|
|
info!(rpc_id, "🖱️ stream_mouse closed");
|
2025-06-17 08:17:23 -05:00
|
|
|
Ok::<(), Status>(())
|
2025-06-01 13:31:22 -05:00
|
|
|
});
|
2025-06-27 06:56:08 -05:00
|
|
|
|
2025-06-26 15:12: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
|
|
|
|
2025-06-30 19:35:38 -05:00
|
|
|
async fn stream_microphone(
|
|
|
|
|
&self,
|
|
|
|
|
req: Request<tonic::Streaming<AudioPacket>>,
|
|
|
|
|
) -> Result<Response<Self::StreamMicrophoneStream>, Status> {
|
2026-04-10 14:05:04 -03:00
|
|
|
let rpc_id = runtime_support::next_stream_id();
|
2026-04-08 23:01:31 -03:00
|
|
|
info!(rpc_id, "🎤 stream_microphone opened");
|
2025-07-01 17:30:34 -05:00
|
|
|
// 1 ─ build once, early
|
2025-12-01 03:34:01 -03:00
|
|
|
let uac_dev = std::env::var("LESAVKA_UAC_DEV").unwrap_or_else(|_| "hw:UAC2Gadget,0".into());
|
|
|
|
|
info!(%uac_dev, "🎤 stream_microphone using UAC sink");
|
2026-04-10 14:05:04 -03:00
|
|
|
let mut sink = runtime_support::open_voice_with_retry(&uac_dev)
|
2025-11-30 16:16:03 -03:00
|
|
|
.await
|
2025-06-30 19:35:38 -05:00
|
|
|
.map_err(|e| Status::internal(format!("{e:#}")))?;
|
2025-07-01 17:30:34 -05:00
|
|
|
|
|
|
|
|
// 2 ─ dummy outbound stream (same trick as before)
|
2025-06-30 19:35:38 -05:00
|
|
|
let (tx, rx) = tokio::sync::mpsc::channel(1);
|
2025-07-01 12:11:52 -05:00
|
|
|
|
2025-07-01 17:30:34 -05:00
|
|
|
// 3 ─ drive the sink in a background task
|
2025-06-30 19:35:38 -05:00
|
|
|
tokio::spawn(async move {
|
|
|
|
|
let mut inbound = req.into_inner();
|
2025-11-30 16:16:03 -03:00
|
|
|
static CNT: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
|
2025-07-01 17:30:34 -05:00
|
|
|
|
2025-06-30 19:35:38 -05:00
|
|
|
while let Some(pkt) = inbound.next().await.transpose()? {
|
2025-07-01 10:23:51 -05:00
|
|
|
let n = CNT.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
|
|
|
|
|
if n < 10 || n % 300 == 0 {
|
2026-04-08 23:01:31 -03:00
|
|
|
tracing::trace!(rpc_id, "🎤⬇ srv pkt#{n} {} bytes", pkt.data.len());
|
2025-06-30 19:35:38 -05:00
|
|
|
}
|
2025-07-01 17:30:34 -05:00
|
|
|
sink.push(&pkt);
|
2025-06-30 19:35:38 -05:00
|
|
|
}
|
2025-11-30 16:16:03 -03:00
|
|
|
sink.finish(); // flush on EOS
|
2025-06-30 19:35:38 -05:00
|
|
|
let _ = tx.send(Ok(Empty {})).await;
|
2026-04-08 23:01:31 -03:00
|
|
|
info!(rpc_id, "🎤 stream_microphone closed");
|
2025-07-01 17:30:34 -05:00
|
|
|
Ok::<(), Status>(())
|
2025-06-30 19:35:38 -05:00
|
|
|
});
|
2025-07-01 17:30:34 -05:00
|
|
|
|
2025-06-30 19:35:38 -05:00
|
|
|
Ok(Response::new(ReceiverStream::new(rx)))
|
|
|
|
|
}
|
|
|
|
|
|
2025-07-03 08:19:59 -05:00
|
|
|
async fn stream_camera(
|
|
|
|
|
&self,
|
2025-07-03 09:24:57 -05:00
|
|
|
req: Request<tonic::Streaming<VideoPacket>>,
|
2025-07-03 08:19:59 -05:00
|
|
|
) -> Result<Response<Self::StreamCameraStream>, Status> {
|
2026-04-10 14:05:04 -03:00
|
|
|
let rpc_id = runtime_support::next_stream_id();
|
2026-01-28 17:52:00 -03:00
|
|
|
let cfg = camera::current_camera_config();
|
|
|
|
|
info!(
|
2026-04-08 23:01:31 -03:00
|
|
|
rpc_id,
|
2026-01-28 17:52:00 -03:00
|
|
|
output = cfg.output.as_str(),
|
|
|
|
|
codec = cfg.codec.as_str(),
|
|
|
|
|
width = cfg.width,
|
|
|
|
|
height = cfg.height,
|
|
|
|
|
fps = cfg.fps,
|
|
|
|
|
hdmi = cfg.hdmi.as_ref().map(|h| h.name.as_str()).unwrap_or("none"),
|
|
|
|
|
"🎥 stream_camera output selected"
|
|
|
|
|
);
|
|
|
|
|
|
2026-04-08 22:23:40 -03:00
|
|
|
let (session_id, relay) = self.camera_rt.activate(&cfg).await?;
|
|
|
|
|
let camera_rt = self.camera_rt.clone();
|
2026-04-08 23:01:31 -03:00
|
|
|
info!(rpc_id, session_id, "🎥 stream_camera opened");
|
2025-11-30 16:16:03 -03:00
|
|
|
|
2025-07-03 09:24:57 -05:00
|
|
|
// dummy outbound (same pattern as other streams)
|
2025-07-03 08:19:59 -05:00
|
|
|
let (tx, rx) = tokio::sync::mpsc::channel(1);
|
2025-11-30 16:16:03 -03:00
|
|
|
|
2025-07-03 09:24:57 -05:00
|
|
|
tokio::spawn(async move {
|
|
|
|
|
let mut s = req.into_inner();
|
|
|
|
|
while let Some(pkt) = s.next().await.transpose()? {
|
2026-04-08 22:23:40 -03:00
|
|
|
if !camera_rt.is_active(session_id) {
|
2026-04-08 23:01:31 -03:00
|
|
|
info!(rpc_id, session_id, "🎥 stream_camera session superseded");
|
2026-04-08 22:23:40 -03:00
|
|
|
break;
|
|
|
|
|
}
|
2025-11-30 16:16:03 -03:00
|
|
|
relay.feed(pkt); // ← all logging inside video.rs
|
2025-07-03 09:24:57 -05:00
|
|
|
}
|
|
|
|
|
tx.send(Ok(Empty {})).await.ok();
|
2026-04-08 23:01:31 -03:00
|
|
|
info!(rpc_id, session_id, "🎥 stream_camera closed");
|
2025-07-03 09:24:57 -05:00
|
|
|
Ok::<(), Status>(())
|
|
|
|
|
});
|
2025-11-30 16:16:03 -03:00
|
|
|
|
2025-07-03 08:19:59 -05:00
|
|
|
Ok(Response::new(ReceiverStream::new(rx)))
|
|
|
|
|
}
|
|
|
|
|
|
2025-06-21 05:21:57 -05:00
|
|
|
async fn capture_video(
|
|
|
|
|
&self,
|
|
|
|
|
req: Request<MonitorRequest>,
|
|
|
|
|
) -> Result<Response<Self::CaptureVideoStream>, Status> {
|
2026-04-10 14:05:04 -03:00
|
|
|
let rpc_id = runtime_support::next_stream_id();
|
2026-04-08 22:23:40 -03:00
|
|
|
let req = req.into_inner();
|
|
|
|
|
let id = req.id;
|
2025-06-27 06:56:08 -05:00
|
|
|
let dev = match id {
|
2025-06-25 12:31:48 -05:00
|
|
|
0 => "/dev/lesavka_l_eye",
|
|
|
|
|
1 => "/dev/lesavka_r_eye",
|
|
|
|
|
_ => return Err(Status::invalid_argument("monitor id must be 0 or 1")),
|
2025-06-27 06:56:08 -05:00
|
|
|
};
|
2026-04-08 23:01:31 -03:00
|
|
|
info!(
|
|
|
|
|
rpc_id,
|
|
|
|
|
id,
|
|
|
|
|
max_bitrate = req.max_bitrate,
|
|
|
|
|
"🎥 capture_video opened"
|
|
|
|
|
);
|
|
|
|
|
debug!(rpc_id, "🎥 streaming {dev}");
|
2026-04-08 22:23:40 -03:00
|
|
|
let s = video::eye_ball(dev, id, req.max_bitrate)
|
2025-06-21 05:21:57 -05:00
|
|
|
.await
|
2025-06-27 06:56:08 -05:00
|
|
|
.map_err(|e| Status::internal(format!("{e:#}")))?;
|
|
|
|
|
Ok(Response::new(Box::pin(s)))
|
|
|
|
|
}
|
2025-06-21 05:21:57 -05:00
|
|
|
|
2025-06-29 03:46:34 -05:00
|
|
|
async fn capture_audio(
|
|
|
|
|
&self,
|
|
|
|
|
req: Request<MonitorRequest>,
|
|
|
|
|
) -> Result<Response<Self::CaptureAudioStream>, Status> {
|
2026-04-10 14:05:04 -03:00
|
|
|
let rpc_id = runtime_support::next_stream_id();
|
2025-06-29 22:57:54 -05:00
|
|
|
// Only one speaker stream for now; both 0/1 → same ALSA dev.
|
2025-11-30 16:16:03 -03:00
|
|
|
let _id = req.into_inner().id;
|
2025-06-29 22:57:54 -05:00
|
|
|
// Allow override (`LESAVKA_ALSA_DEV=hw:2,0` for debugging).
|
2025-11-30 16:16:03 -03:00
|
|
|
let dev = std::env::var("LESAVKA_ALSA_DEV").unwrap_or_else(|_| "hw:UAC2Gadget,0".into());
|
2026-04-08 23:01:31 -03:00
|
|
|
info!(rpc_id, %dev, "🔊 capture_audio opened");
|
2025-06-29 22:57:54 -05:00
|
|
|
|
2025-06-30 02:42:20 -05:00
|
|
|
let s = audio::ear(&dev, 0)
|
2025-06-29 22:57:54 -05:00
|
|
|
.await
|
|
|
|
|
.map_err(|e| Status::internal(format!("{e:#}")))?;
|
|
|
|
|
|
|
|
|
|
Ok(Response::new(Box::pin(s)))
|
2025-06-29 03:46:34 -05:00
|
|
|
}
|
|
|
|
|
|
2026-04-08 20:00:14 -03:00
|
|
|
async fn paste_text(&self, req: Request<PasteRequest>) -> Result<Response<PasteReply>, Status> {
|
|
|
|
|
let req = req.into_inner();
|
|
|
|
|
let text = paste::decrypt(&req).map_err(|e| Status::unauthenticated(format!("{e}")))?;
|
|
|
|
|
if let Err(e) = paste::type_text(self.kb.as_ref(), &text).await {
|
|
|
|
|
return Ok(Response::new(PasteReply {
|
|
|
|
|
ok: false,
|
|
|
|
|
error: format!("{e}"),
|
|
|
|
|
}));
|
|
|
|
|
}
|
|
|
|
|
Ok(Response::new(PasteReply {
|
|
|
|
|
ok: true,
|
|
|
|
|
error: String::new(),
|
|
|
|
|
}))
|
|
|
|
|
}
|
|
|
|
|
|
2025-07-01 12:11:52 -05:00
|
|
|
/*────────────── USB-reset RPC ────────────*/
|
2025-11-30 16:16:03 -03:00
|
|
|
async fn reset_usb(&self, _req: Request<Empty>) -> Result<Response<ResetUsbReply>, Status> {
|
2025-06-27 06:56:08 -05:00
|
|
|
info!("🔴 explicit ResetUsb() called");
|
|
|
|
|
match self.gadget.cycle() {
|
2025-07-05 12:45:08 -05:00
|
|
|
Ok(_) => {
|
|
|
|
|
if let Err(e) = self.reopen_hid().await {
|
|
|
|
|
error!("💥 reopen HID failed: {e:#}");
|
|
|
|
|
return Err(Status::internal(e.to_string()));
|
|
|
|
|
}
|
|
|
|
|
Ok(Response::new(ResetUsbReply { ok: true }))
|
|
|
|
|
}
|
2025-06-27 06:56:08 -05:00
|
|
|
Err(e) => {
|
|
|
|
|
error!("💥 cycle failed: {e:#}");
|
|
|
|
|
Err(Status::internal(e.to_string()))
|
|
|
|
|
}
|
|
|
|
|
}
|
2025-06-21 05:21:57 -05:00
|
|
|
}
|
2025-06-01 13:31:22 -05:00
|
|
|
}
|
|
|
|
|
|
2025-06-27 06:56:08 -05:00
|
|
|
/*──────────────── main ───────────────────────*/
|
|
|
|
|
#[tokio::main(worker_threads = 4)]
|
2025-06-02 20:41:36 -05:00
|
|
|
async fn main() -> anyhow::Result<()> {
|
2025-06-27 06:56:08 -05:00
|
|
|
let _guard = init_tracing()?;
|
2025-07-04 10:17:01 -05:00
|
|
|
info!("🚀 {} v{} starting up", PKG_NAME, VERSION);
|
2025-06-05 22:44:27 -05:00
|
|
|
|
2025-06-25 20:00:34 -05:00
|
|
|
panic::set_hook(Box::new(|p| {
|
|
|
|
|
let bt = Backtrace::force_capture();
|
|
|
|
|
error!("💥 panic: {p}\n{bt}");
|
|
|
|
|
}));
|
|
|
|
|
|
2025-11-30 16:16:03 -03:00
|
|
|
let gadget = UsbGadget::new("lesavka");
|
2026-01-06 11:48:36 -03:00
|
|
|
if std::env::var("LESAVKA_DISABLE_UVC").is_err() {
|
2026-01-08 00:59:14 -03:00
|
|
|
if std::env::var("LESAVKA_UVC_EXTERNAL").is_ok() {
|
|
|
|
|
info!("📷 UVC control helper external; not spawning");
|
|
|
|
|
} else {
|
2026-04-10 14:05:04 -03:00
|
|
|
let bin = uvc_runtime::uvc_ctrl_bin();
|
|
|
|
|
tokio::spawn(uvc_runtime::supervise_uvc_control(bin));
|
2026-01-08 00:59:14 -03:00
|
|
|
}
|
2026-01-06 04:38:41 -03:00
|
|
|
} else {
|
|
|
|
|
info!("📷 UVC disabled (LESAVKA_DISABLE_UVC set)");
|
2026-01-06 11:48:36 -03:00
|
|
|
}
|
2025-06-27 06:56:08 -05:00
|
|
|
let handler = Handler::new(gadget.clone()).await?;
|
2025-06-12 01:57:08 -05:00
|
|
|
|
2025-06-28 15:45:11 -05:00
|
|
|
info!("🌐 lesavka-server listening on 0.0.0.0:50051");
|
2025-06-27 06:56:08 -05:00
|
|
|
Server::builder()
|
|
|
|
|
.tcp_nodelay(true)
|
2025-11-30 16:16:03 -03:00
|
|
|
.max_frame_size(Some(2 * 1024 * 1024))
|
2025-06-27 06:56:08 -05:00
|
|
|
.add_service(RelayServer::new(handler))
|
2025-07-04 03:41:39 -05:00
|
|
|
.add_service(HandshakeSvc::server())
|
2025-06-27 19:31:46 -05:00
|
|
|
.add_service(ReflBuilder::configure().build_v1().unwrap())
|
2025-11-30 16:16:03 -03:00
|
|
|
.serve(([0, 0, 0, 0], 50051).into())
|
2025-06-27 06:56:08 -05:00
|
|
|
.await?;
|
2025-06-01 13:31:22 -05:00
|
|
|
Ok(())
|
|
|
|
|
}
|