lesavka: refresh live eye detection

This commit is contained in:
Brad Stein 2026-04-20 01:41:57 -03:00
parent 64f6ab3336
commit fc38925f0d
7 changed files with 76 additions and 16 deletions

View File

@ -4,7 +4,7 @@ path = "src/main.rs"
[package]
name = "lesavka_client"
version = "0.11.20"
version = "0.11.21"
edition = "2024"
[dependencies]

View File

@ -81,6 +81,7 @@ fn print_state(state: lesavka_common::lesavka::CapturePowerState) {
println!("available={}", state.available);
println!("enabled={}", state.enabled);
println!("mode={}", state.mode);
println!("detected_devices={}", state.detected_devices);
println!("active_leases={}", state.active_leases);
println!("unit={}", state.unit);
println!("detail={}", state.detail);

View File

@ -738,6 +738,8 @@ pub fn run_gui_launcher(server_addr: String) -> Result<()> {
let caps_request_in_flight = Rc::new(Cell::new(false));
let diagnostics_network = Rc::new(RefCell::new(NetworkTelemetry::default()));
let diagnostics_process = Rc::new(RefCell::new(ProcessCpuSampler::new()));
let next_power_probe =
Rc::new(Cell::new(Instant::now() + Duration::from_millis(500)));
let next_diagnostics_probe =
Rc::new(Cell::new(Instant::now() + Duration::from_millis(250)));
let next_diagnostics_sample =
@ -1891,6 +1893,21 @@ pub fn run_gui_launcher(server_addr: String) -> Result<()> {
}
let now = Instant::now();
let child_running = child_proc.borrow().is_some();
if now >= next_power_probe.get()
&& !power_request_in_flight.get()
&& (child_running
|| state.borrow().capture_power.enabled
|| state.borrow().remote_active)
{
power_request_in_flight.set(true);
let server_addr =
selected_server_addr(&server_entry, server_addr_fallback.as_ref());
request_capture_power_refresh(power_tx.clone(), server_addr, Duration::ZERO);
next_power_probe.set(now + Duration::from_secs(2));
}
if now >= next_diagnostics_probe.get() && !caps_request_in_flight.get() {
caps_request_in_flight.set(true);
let server_addr =
@ -1915,7 +1932,6 @@ pub fn run_gui_launcher(server_addr: String) -> Result<()> {
next_diagnostics_sample.set(now + Duration::from_secs(1));
}
let child_running = child_proc.borrow().is_some();
refresh_launcher_ui(&widgets, &state.borrow(), child_running);
refresh_test_buttons(&widgets, &mut tests.borrow_mut());
glib::ControlFlow::Continue

View File

@ -1,6 +1,6 @@
[package]
name = "lesavka_common"
version = "0.11.20"
version = "0.11.21"
edition = "2024"
build = "build.rs"

View File

@ -17,6 +17,6 @@ mod tests {
#[test]
fn banner_includes_version() {
assert_eq!(banner("0.11.20"), "lesavka-common CLI (v0.11.20)");
assert_eq!(banner("0.11.21"), "lesavka-common CLI (v0.11.21)");
}
}

View File

@ -10,7 +10,7 @@ bench = false
[package]
name = "lesavka_server"
version = "0.11.20"
version = "0.11.21"
edition = "2024"
autobins = false

View File

@ -3,6 +3,7 @@
#[allow(clippy::useless_attribute)]
#[forbid(unsafe_code)]
use futures_util::{Stream, StreamExt};
use std::collections::HashSet;
use std::collections::HashMap;
use std::path::Path;
use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
@ -112,15 +113,59 @@ impl Handler {
Ok(())
}
fn detected_capture_devices() -> u32 {
fn detected_capture_devices_from_symlinks() -> u32 {
["/dev/lesavka_l_eye", "/dev/lesavka_r_eye"]
.into_iter()
.filter(|path| Path::new(path).exists())
.count() as u32
}
fn with_detected_capture_devices(mut state: CapturePowerState) -> CapturePowerState {
state.detected_devices = Self::detected_capture_devices();
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
}
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
}
@ -295,12 +340,12 @@ impl Handler {
}
async fn get_capture_power_reply(&self) -> Result<Response<CapturePowerState>, Status> {
self.capture_power
let state = self
.capture_power
.snapshot()
.await
.map(Self::with_detected_capture_devices)
.map(Response::new)
.map_err(|e| Status::internal(format!("{e:#}")))
.map_err(|e| Status::internal(format!("{e:#}")))?;
Ok(Response::new(self.with_detected_capture_devices(state).await))
}
async fn set_capture_power_reply(
@ -316,10 +361,8 @@ impl Handler {
CapturePowerCommand::ForceOff => self.capture_power.set_manual(false).await,
CapturePowerCommand::Unspecified => self.capture_power.set_manual(req.enabled).await,
};
result
.map(Self::with_detected_capture_devices)
.map(Response::new)
.map_err(|e| Status::internal(format!("{e:#}")))
let state = result.map_err(|e| Status::internal(format!("{e:#}")))?;
Ok(Response::new(self.with_detected_capture_devices(state).await))
}
}