use super::super::{ clipboard::send_clipboard_text_to_remote, power::{recover_uac_soft, recover_usb_soft, recover_uvc_soft}, }; use futures::stream; use lesavka_common::lesavka::{ AudioPacket, CalibrationRequest, CalibrationState, CapturePowerState, Empty, KeyboardReport, MonitorRequest, MouseReport, OutputDelayProbeReply, OutputDelayProbeRequest, PasteReply, PasteRequest, ResetUsbReply, SetCapturePowerRequest, UpstreamMediaBundle, UpstreamSyncState, VideoPacket, relay_server::{Relay, RelayServer}, }; use serial_test::serial; use std::{ pin::Pin, sync::{Arc, Mutex}, time::Duration, }; use tonic::{Request, Response, Status}; type KeyboardStream = Pin> + Send>>; type MouseStream = Pin> + Send>>; type VideoStream = Pin> + Send>>; type AudioStream = Pin> + Send>>; type EmptyStream = Pin> + Send>>; type OutputDelayProbeStream = Pin> + Send>>; #[derive(Clone)] struct UtilityRelay { paste_count: Arc>, reset_count: Arc>, reset_ok: bool, } impl UtilityRelay { fn new(reset_ok: bool) -> Self { Self { paste_count: Arc::new(Mutex::new(0)), reset_count: Arc::new(Mutex::new(0)), reset_ok, } } } #[tonic::async_trait] impl Relay for UtilityRelay { type StreamKeyboardStream = KeyboardStream; type StreamMouseStream = MouseStream; type CaptureVideoStream = VideoStream; type CaptureAudioStream = AudioStream; type StreamMicrophoneStream = EmptyStream; type StreamCameraStream = EmptyStream; type StreamWebcamMediaStream = EmptyStream; type RunOutputDelayProbeStream = OutputDelayProbeStream; async fn stream_keyboard( &self, _request: Request>, ) -> Result, Status> { Ok(Response::new(Box::pin(stream::empty()))) } async fn stream_mouse( &self, _request: Request>, ) -> Result, Status> { Ok(Response::new(Box::pin(stream::empty()))) } async fn capture_video( &self, _request: Request, ) -> Result, Status> { Ok(Response::new(Box::pin(stream::empty()))) } async fn capture_audio( &self, _request: Request, ) -> Result, Status> { Ok(Response::new(Box::pin(stream::empty()))) } async fn stream_microphone( &self, _request: Request>, ) -> Result, Status> { Ok(Response::new(Box::pin(stream::empty()))) } async fn stream_camera( &self, _request: Request>, ) -> Result, Status> { Ok(Response::new(Box::pin(stream::empty()))) } async fn stream_webcam_media( &self, _request: Request>, ) -> Result, Status> { Ok(Response::new(Box::pin(stream::empty()))) } async fn run_output_delay_probe( &self, _request: Request, ) -> Result, Status> { Ok(Response::new(Box::pin(stream::empty()))) } async fn paste_text( &self, _request: Request, ) -> Result, Status> { *self.paste_count.lock().expect("paste count") += 1; Ok(Response::new(PasteReply { ok: true, error: String::new(), })) } async fn reset_usb(&self, _request: Request) -> Result, Status> { *self.reset_count.lock().expect("reset count") += 1; Ok(Response::new(ResetUsbReply { ok: self.reset_ok })) } async fn recover_usb( &self, _request: Request, ) -> Result, Status> { *self.reset_count.lock().expect("reset count") += 1; Ok(Response::new(ResetUsbReply { ok: self.reset_ok })) } async fn recover_uac( &self, _request: Request, ) -> Result, Status> { *self.reset_count.lock().expect("reset count") += 1; Ok(Response::new(ResetUsbReply { ok: self.reset_ok })) } async fn recover_uvc( &self, _request: Request, ) -> Result, Status> { *self.reset_count.lock().expect("reset count") += 1; Ok(Response::new(ResetUsbReply { ok: self.reset_ok })) } async fn get_capture_power( &self, _request: Request, ) -> Result, Status> { Ok(Response::new(CapturePowerState::default())) } async fn set_capture_power( &self, _request: Request, ) -> Result, Status> { Ok(Response::new(CapturePowerState::default())) } async fn get_calibration( &self, _request: Request, ) -> Result, Status> { Ok(Response::new(CalibrationState::default())) } async fn calibrate( &self, _request: Request, ) -> Result, Status> { Ok(Response::new(CalibrationState::default())) } async fn get_upstream_sync( &self, _request: Request, ) -> Result, Status> { Ok(Response::new(UpstreamSyncState::default())) } } fn serve(relay: UtilityRelay) -> (tokio::runtime::Runtime, String) { let rt = tokio::runtime::Runtime::new().expect("runtime"); let addr = rt.block_on(async { let listener = std::net::TcpListener::bind("127.0.0.1:0").expect("bind relay"); let addr = listener.local_addr().expect("relay addr"); drop(listener); tokio::spawn(async move { let _ = tonic::transport::Server::builder() .add_service(RelayServer::new(relay)) .serve(addr) .await; }); addr }); std::thread::sleep(Duration::from_millis(50)); (rt, format!("http://{addr}")) } #[test] #[serial] fn clipboard_action_delivers_text_over_rpc_when_key_is_available() { temp_env::with_vars( [ ( "LESAVKA_PASTE_KEY", Some("000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f"), ), ("LESAVKA_PASTE_KEY_FILE", None::<&str>), ("LESAVKA_CLIPBOARD_TIMEOUT_MS", Some("1500")), ], || { let relay = UtilityRelay::new(true); let paste_count = Arc::clone(&relay.paste_count); let (_rt, addr) = serve(relay); let result = send_clipboard_text_to_remote(&addr, "hello from launcher").expect("clipboard RPC"); assert_eq!(result, "Clipboard delivered to remote"); assert_eq!(*paste_count.lock().expect("paste count"), 1); }, ); } #[test] #[serial] fn recover_usb_action_reports_relay_success_and_failure() { let relay = UtilityRelay::new(true); let reset_count = Arc::clone(&relay.reset_count); let (_rt, addr) = serve(relay); recover_usb_soft(&addr).expect("successful soft USB recovery"); assert_eq!(*reset_count.lock().expect("reset count"), 1); let relay = UtilityRelay::new(false); let reset_count = Arc::clone(&relay.reset_count); let (_rt, addr) = serve(relay); let err = recover_usb_soft(&addr).expect_err("soft USB recovery failure"); assert!(format!("{err:#}").contains("relay reported soft USB recovery failure")); assert_eq!(*reset_count.lock().expect("reset count"), 1); } #[test] #[serial] fn soft_recovery_actions_report_relay_success() { let relay = UtilityRelay::new(true); let reset_count = Arc::clone(&relay.reset_count); let (_rt, addr) = serve(relay); recover_usb_soft(&addr).expect("usb soft recover"); recover_uac_soft(&addr).expect("uac soft recover"); recover_uvc_soft(&addr).expect("uvc soft recover"); assert_eq!(*reset_count.lock().expect("reset count"), 3); }