146 lines
5.3 KiB
Rust
146 lines
5.3 KiB
Rust
impl Handler {
|
|
async fn new(gadget: UsbGadget) -> anyhow::Result<Self> {
|
|
#[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::<u32>().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::<HashSet<_>>()
|
|
.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
|
|
}
|
|
}
|