impl Handler { async fn new(gadget: UsbGadget) -> anyhow::Result { #[cfg(not(coverage))] if runtime_support::allow_gadget_cycle() { info!("🛠️ Initial USB recovery…"); match UsbGadget::current_controller_state() { Ok((ctrl, state)) if !UsbGadget::host_enumerated_state(&state) => { warn!("⚠️ UDC {ctrl} is {state}; forcing gadget recovery before opening HID"); if let Err(error) = gadget.recover_enumeration() { warn!("⚠️ initial USB recovery did not enumerate the host: {error:#}"); } } Ok(_) => { let _ = gadget.cycle(); // ignore failure - may boot without host } Err(error) => { warn!("⚠️ UDC state unavailable during startup: {error:#}"); let _ = gadget.cycle(); // preserve the old best-effort startup path } } } #[cfg(not(coverage))] { if !runtime_support::allow_gadget_cycle() { if runtime_support::external_uvc_helper_owns_gadget() { info!( "🔒 gadget cycle disabled at startup because external UVC helper owns the gadget" ); } else { info!( "🔒 gadget cycle disabled at startup (set LESAVKA_ALLOW_GADGET_CYCLE=1 to enable)" ); } } info!("🛠️ opening HID endpoints …"); } let kb_path = hid_endpoint(0); let ms_path = hid_endpoint(1); let kb = runtime_support::open_hid_if_ready(&kb_path).await?; let ms = runtime_support::open_hid_if_ready(&ms_path).await?; #[cfg(not(coverage))] if kb.is_some() && ms.is_some() { info!("✅ HID endpoints ready"); } else { warn!("⌛ HID endpoints are not ready; relay will keep running and open them lazily"); } let upstream_media_rt = Arc::new(UpstreamMediaRuntime::new()); let calibration = Arc::new(CalibrationStore::load(upstream_media_rt.clone())); #[cfg(not(coverage))] lesavka_server::blind_healer::spawn_blind_healer( upstream_media_rt.clone(), calibration.clone(), ); Ok(Self { kb: Arc::new(Mutex::new(kb)), ms: Arc::new(Mutex::new(ms)), gadget, did_cycle: Arc::new(AtomicBool::new(false)), camera_rt: Arc::new(CameraRuntime::new()), upstream_media_rt, calibration, capture_power: CapturePowerManager::new(), eye_hubs: Arc::new(Mutex::new(HashMap::new())), }) } async fn reopen_hid(&self) -> anyhow::Result<()> { let kb_new = runtime_support::open_hid_if_ready(&hid_endpoint(0)).await?; let ms_new = runtime_support::open_hid_if_ready(&hid_endpoint(1)).await?; *self.kb.lock().await = kb_new; *self.ms.lock().await = ms_new; Ok(()) } fn detected_capture_devices_from_symlinks() -> u32 { ["/dev/lesavka_l_eye", "/dev/lesavka_r_eye"] .into_iter() .filter(|path| { std::fs::metadata(path) .ok() .is_some_and(|metadata| metadata.file_type().is_char_device()) }) .count() as u32 } #[cfg(not(coverage))] fn detected_capture_devices_from_udev() -> u32 { let Ok(mut enumerator) = udev::Enumerator::new() else { return 0; }; let _ = enumerator.match_subsystem("video4linux"); let Ok(devices) = enumerator.scan_devices() else { return 0; }; devices .filter(|device| { device .attribute_value("index") .and_then(|value| value.to_str()) == Some("0") && device .property_value("ID_VENDOR_ID") .and_then(|value| value.to_str()) == Some("07ca") && device .property_value("ID_MODEL_ID") .and_then(|value| value.to_str()) == Some("3311") }) .count() .min(2) as u32 } #[cfg(coverage)] fn detected_capture_devices_from_udev() -> u32 { std::env::var("LESAVKA_TEST_UDEV_CAPTURE_DEVICES") .ok() .and_then(|value| value.parse::().ok()) .unwrap_or(0) .min(2) } async fn active_eye_source_count(&self) -> u32 { self.eye_hubs .lock() .await .iter() .filter_map(|(key, hub)| hub.running.load(Ordering::Relaxed).then_some(key.source_id)) .collect::>() .len() .min(2) as u32 } async fn with_detected_capture_devices( &self, mut state: CapturePowerState, ) -> CapturePowerState { state.detected_devices = Self::detected_capture_devices_from_udev() .max(Self::detected_capture_devices_from_symlinks()) .max(self.active_eye_source_count().await); state } }