ci(lesavka): clear hygiene gate regressions

This commit is contained in:
Brad Stein 2026-05-18 17:07:26 -03:00
parent 5b55c85263
commit 277442ef94
13 changed files with 1013 additions and 974 deletions

View File

@ -4,6 +4,8 @@ include!("preview/feed_state.rs");
include!("preview/feed_runtime.rs");
include!("preview/status_pipeline.rs");
include!("preview/frame_telemetry.rs");
include!("preview/feed_worker.rs");
include!("preview/launcher_preview_impl.rs");
#[cfg(test)]
#[path = "tests/preview.rs"]

View File

@ -224,315 +224,3 @@ impl PreviewFrame {
}
}
}
#[cfg(not(coverage))]
#[allow(clippy::too_many_arguments)]
fn run_preview_feed(
server_addr: Arc<Mutex<String>>,
monitor_id: u32,
profile: PreviewProfile,
session_active: Arc<AtomicBool>,
active_bindings: Arc<AtomicUsize>,
running: Arc<AtomicBool>,
shared: Arc<Mutex<SharedPreviewState>>,
log_sink: Arc<Mutex<Option<std::sync::mpsc::Sender<String>>>>,
) -> Result<()> {
let mut startup_error = None;
let mut selected = None;
for decoder_name in preview_decoder_candidates() {
match build_preview_pipeline(profile, &decoder_name) {
Ok((pipeline, appsrc, appsink, decoder_label)) => {
match pipeline
.set_state(gst::State::Playing)
.context("starting launcher preview pipeline")
{
Ok(_) => {
selected = Some((pipeline, appsrc, appsink, decoder_label));
break;
}
Err(err) => {
let _ = pipeline.set_state(gst::State::Null);
startup_error = Some(err);
}
}
}
Err(err) => {
startup_error = Some(err);
}
}
}
let (pipeline, appsrc, appsink, decoder_name) = selected.ok_or_else(|| {
startup_error.unwrap_or_else(|| anyhow::anyhow!("no usable H.264 decoder"))
})?;
let parser = pipeline.by_name("preview_parse");
let decoder = pipeline.by_name("decoder");
if let Ok(mut slot) = shared.lock() {
slot.telemetry.note_decoder(&decoder_name);
}
{
let shared = Arc::clone(&shared);
pipeline.connect_deep_element_added(move |_, _, element| {
if let Some(decoder_label) = preview_decoder_label(element)
&& let Ok(mut slot) = shared.lock()
{
slot.telemetry.note_decoder(&decoder_label);
}
});
}
let sample_worker = {
let shared = Arc::clone(&shared);
let appsink = appsink.clone();
let parser = parser.clone();
let decoder = decoder.clone();
let running = Arc::clone(&running);
std::thread::spawn(move || {
loop {
if !running.load(Ordering::Relaxed) {
break;
}
if let Some(sample) = appsink.try_pull_sample(gst::ClockTime::from_mseconds(250)) {
if let Some(parser) = parser.as_ref() {
record_preview_caps(&shared, parser, "src", PreviewCapsKind::Stream);
}
if let Some(decoder) = decoder.as_ref() {
record_preview_caps(&shared, decoder, "src", PreviewCapsKind::Decoded);
}
if let Some(caps) = sample.caps() {
let caps_label = preview_caps_summary(&caps);
if !caps_label.is_empty()
&& let Ok(mut slot) = shared.lock()
{
slot.telemetry.note_rendered_caps(&caps_label);
}
}
if let Some(frame) = sample_to_frame(&sample)
&& let Ok(mut slot) = shared.lock() {
slot.push_frame(frame);
}
}
}
})
};
let rt = tokio::runtime::Builder::new_current_thread()
.enable_all()
.build()
.context("building preview tokio runtime")?;
let running_for_loop = Arc::clone(&running);
let _ = rt.block_on(async move {
let mut was_active = false;
let mut retry_delay = Duration::from_millis(750);
loop {
if !running_for_loop.load(Ordering::Relaxed) {
break;
}
let active_now = session_active.load(Ordering::Relaxed)
&& active_bindings.load(Ordering::Relaxed) > 0;
if !active_now {
was_active = false;
retry_delay = Duration::from_millis(750);
set_shared_status(&shared, &log_sink, monitor_id, PREVIEW_IDLE_STATUS, true);
tokio::time::sleep(Duration::from_millis(150)).await;
continue;
}
if !was_active {
was_active = true;
set_shared_status(
&shared,
&log_sink,
monitor_id,
"Waking relay preview...",
true,
);
tokio::time::sleep(Duration::from_millis(350)).await;
}
set_shared_status(
&shared,
&log_sink,
monitor_id,
"Connecting relay preview...",
true,
);
let current_addr = match server_addr.lock() {
Ok(value) => value.clone(),
Err(_) => {
set_shared_status(
&shared,
&log_sink,
monitor_id,
"Preview address is unavailable.",
true,
);
tokio::time::sleep(Duration::from_millis(750)).await;
continue;
}
};
let channel = match crate::relay_transport::endpoint(&current_addr) {
Ok(endpoint) => match endpoint.tcp_nodelay(true).connect().await {
Ok(channel) => channel,
Err(err) => {
warn!(monitor_id, ?err, "launcher preview connect failed");
log_preview_issue(
&shared,
&log_sink,
monitor_id,
&format!("Preview host is unavailable: {err}"),
);
set_shared_status(
&shared,
&log_sink,
monitor_id,
"Preview host is unavailable.",
true,
);
tokio::time::sleep(retry_delay).await;
continue;
}
},
Err(err) => {
warn!(monitor_id, ?err, "launcher preview endpoint invalid");
log_preview_issue(
&shared,
&log_sink,
monitor_id,
&format!("Preview address is invalid: {err}"),
);
set_shared_status(
&shared,
&log_sink,
monitor_id,
"Preview address is invalid.",
true,
);
tokio::time::sleep(retry_delay).await;
continue;
}
};
let mut cli = RelayClient::new(channel);
let req = MonitorRequest {
id: monitor_id,
max_bitrate: profile.max_bitrate_kbit,
requested_width: profile.requested_width.max(0) as u32,
requested_height: profile.requested_height.max(0) as u32,
requested_fps: profile.requested_fps,
source_id: Some(profile.source_monitor_id),
};
match cli.capture_video(Request::new(req)).await {
Ok(mut stream) => {
retry_delay = Duration::from_millis(750);
debug!(monitor_id, "launcher preview connected");
set_shared_status(
&shared,
&log_sink,
monitor_id,
"Waiting for stream...",
true,
);
loop {
if !session_active.load(Ordering::Relaxed)
|| !running_for_loop.load(Ordering::Relaxed)
|| active_bindings.load(Ordering::Relaxed) == 0
{
break;
}
match tokio::time::timeout(
Duration::from_millis(300),
stream.get_mut().message(),
)
.await
{
Ok(Ok(Some(pkt))) => {
record_preview_packet(&shared, &pkt);
push_preview_packet(&appsrc, pkt);
}
Ok(Ok(None)) => {
set_shared_status(
&shared,
&log_sink,
monitor_id,
"Preview stream ended.",
true,
);
retry_delay = Duration::from_millis(1_500);
break;
}
Ok(Err(err)) => {
warn!(monitor_id, ?err, "launcher preview stream error");
log_preview_issue(
&shared,
&log_sink,
monitor_id,
&format!("Preview stream error: {err}"),
);
set_shared_status(
&shared,
&log_sink,
monitor_id,
"Preview stream error. See session log.",
true,
);
retry_delay =
preview_retry_delay(retry_delay, Some(&err.to_string()));
break;
}
Err(_) => continue,
}
}
}
Err(err) => {
if preview_startup_condition(&err) {
debug!(
monitor_id,
?err,
"launcher preview waiting for capture pipeline"
);
log_preview_issue(
&shared,
&log_sink,
monitor_id,
&format!("Waiting for capture pipeline: {err}"),
);
set_shared_status(
&shared,
&log_sink,
monitor_id,
"Waiting for capture pipeline...",
true,
);
retry_delay = preview_retry_delay(retry_delay, Some(err.message()));
} else {
warn!(monitor_id, ?err, "launcher preview rpc failed");
log_preview_issue(
&shared,
&log_sink,
monitor_id,
&format!("Preview RPC failed: {err}"),
);
set_shared_status(
&shared,
&log_sink,
monitor_id,
"Preview RPC failed. See session log.",
true,
);
retry_delay = preview_retry_delay(retry_delay, Some(err.message()));
}
}
}
tokio::time::sleep(retry_delay).await;
}
#[allow(unreachable_code)]
Ok::<(), anyhow::Error>(())
});
let _ = pipeline.set_state(gst::State::Null);
running.store(false, Ordering::Relaxed);
let _ = sample_worker.join();
Ok(())
}

View File

@ -0,0 +1,312 @@
#[cfg(not(coverage))]
#[allow(clippy::too_many_arguments)]
fn run_preview_feed(
server_addr: Arc<Mutex<String>>,
monitor_id: u32,
profile: PreviewProfile,
session_active: Arc<AtomicBool>,
active_bindings: Arc<AtomicUsize>,
running: Arc<AtomicBool>,
shared: Arc<Mutex<SharedPreviewState>>,
log_sink: Arc<Mutex<Option<std::sync::mpsc::Sender<String>>>>,
) -> Result<()> {
let mut startup_error = None;
let mut selected = None;
for decoder_name in preview_decoder_candidates() {
match build_preview_pipeline(profile, &decoder_name) {
Ok((pipeline, appsrc, appsink, decoder_label)) => {
match pipeline
.set_state(gst::State::Playing)
.context("starting launcher preview pipeline")
{
Ok(_) => {
selected = Some((pipeline, appsrc, appsink, decoder_label));
break;
}
Err(err) => {
let _ = pipeline.set_state(gst::State::Null);
startup_error = Some(err);
}
}
}
Err(err) => {
startup_error = Some(err);
}
}
}
let (pipeline, appsrc, appsink, decoder_name) = selected.ok_or_else(|| {
startup_error.unwrap_or_else(|| anyhow::anyhow!("no usable H.264 decoder"))
})?;
let parser = pipeline.by_name("preview_parse");
let decoder = pipeline.by_name("decoder");
if let Ok(mut slot) = shared.lock() {
slot.telemetry.note_decoder(&decoder_name);
}
{
let shared = Arc::clone(&shared);
pipeline.connect_deep_element_added(move |_, _, element| {
if let Some(decoder_label) = preview_decoder_label(element)
&& let Ok(mut slot) = shared.lock()
{
slot.telemetry.note_decoder(&decoder_label);
}
});
}
let sample_worker = {
let shared = Arc::clone(&shared);
let appsink = appsink.clone();
let parser = parser.clone();
let decoder = decoder.clone();
let running = Arc::clone(&running);
std::thread::spawn(move || {
loop {
if !running.load(Ordering::Relaxed) {
break;
}
if let Some(sample) = appsink.try_pull_sample(gst::ClockTime::from_mseconds(250)) {
if let Some(parser) = parser.as_ref() {
record_preview_caps(&shared, parser, "src", PreviewCapsKind::Stream);
}
if let Some(decoder) = decoder.as_ref() {
record_preview_caps(&shared, decoder, "src", PreviewCapsKind::Decoded);
}
if let Some(caps) = sample.caps() {
let caps_label = preview_caps_summary(&caps);
if !caps_label.is_empty()
&& let Ok(mut slot) = shared.lock()
{
slot.telemetry.note_rendered_caps(&caps_label);
}
}
if let Some(frame) = sample_to_frame(&sample)
&& let Ok(mut slot) = shared.lock()
{
slot.push_frame(frame);
}
}
}
})
};
let rt = tokio::runtime::Builder::new_current_thread()
.enable_all()
.build()
.context("building preview tokio runtime")?;
let running_for_loop = Arc::clone(&running);
let _ = rt.block_on(async move {
let mut was_active = false;
let mut retry_delay = Duration::from_millis(750);
loop {
if !running_for_loop.load(Ordering::Relaxed) {
break;
}
let active_now =
session_active.load(Ordering::Relaxed) && active_bindings.load(Ordering::Relaxed) > 0;
if !active_now {
was_active = false;
retry_delay = Duration::from_millis(750);
set_shared_status(&shared, &log_sink, monitor_id, PREVIEW_IDLE_STATUS, true);
tokio::time::sleep(Duration::from_millis(150)).await;
continue;
}
if !was_active {
was_active = true;
set_shared_status(
&shared,
&log_sink,
monitor_id,
"Waking relay preview...",
true,
);
tokio::time::sleep(Duration::from_millis(350)).await;
}
set_shared_status(
&shared,
&log_sink,
monitor_id,
"Connecting relay preview...",
true,
);
let current_addr = match server_addr.lock() {
Ok(value) => value.clone(),
Err(_) => {
set_shared_status(
&shared,
&log_sink,
monitor_id,
"Preview address is unavailable.",
true,
);
tokio::time::sleep(Duration::from_millis(750)).await;
continue;
}
};
let channel = match crate::relay_transport::endpoint(&current_addr) {
Ok(endpoint) => match endpoint.tcp_nodelay(true).connect().await {
Ok(channel) => channel,
Err(err) => {
warn!(monitor_id, ?err, "launcher preview connect failed");
log_preview_issue(
&shared,
&log_sink,
monitor_id,
&format!("Preview host is unavailable: {err}"),
);
set_shared_status(
&shared,
&log_sink,
monitor_id,
"Preview host is unavailable.",
true,
);
tokio::time::sleep(retry_delay).await;
continue;
}
},
Err(err) => {
warn!(monitor_id, ?err, "launcher preview endpoint invalid");
log_preview_issue(
&shared,
&log_sink,
monitor_id,
&format!("Preview address is invalid: {err}"),
);
set_shared_status(
&shared,
&log_sink,
monitor_id,
"Preview address is invalid.",
true,
);
tokio::time::sleep(retry_delay).await;
continue;
}
};
let mut cli = RelayClient::new(channel);
let req = MonitorRequest {
id: monitor_id,
max_bitrate: profile.max_bitrate_kbit,
requested_width: profile.requested_width.max(0) as u32,
requested_height: profile.requested_height.max(0) as u32,
requested_fps: profile.requested_fps,
source_id: Some(profile.source_monitor_id),
};
match cli.capture_video(Request::new(req)).await {
Ok(mut stream) => {
retry_delay = Duration::from_millis(750);
debug!(monitor_id, "launcher preview connected");
set_shared_status(
&shared,
&log_sink,
monitor_id,
"Waiting for stream...",
true,
);
loop {
if !session_active.load(Ordering::Relaxed)
|| !running_for_loop.load(Ordering::Relaxed)
|| active_bindings.load(Ordering::Relaxed) == 0
{
break;
}
match tokio::time::timeout(
Duration::from_millis(300),
stream.get_mut().message(),
)
.await
{
Ok(Ok(Some(pkt))) => {
record_preview_packet(&shared, &pkt);
push_preview_packet(&appsrc, pkt);
}
Ok(Ok(None)) => {
set_shared_status(
&shared,
&log_sink,
monitor_id,
"Preview stream ended.",
true,
);
retry_delay = Duration::from_millis(1_500);
break;
}
Ok(Err(err)) => {
warn!(monitor_id, ?err, "launcher preview stream error");
log_preview_issue(
&shared,
&log_sink,
monitor_id,
&format!("Preview stream error: {err}"),
);
set_shared_status(
&shared,
&log_sink,
monitor_id,
"Preview stream error. See session log.",
true,
);
retry_delay =
preview_retry_delay(retry_delay, Some(&err.to_string()));
break;
}
Err(_) => continue,
}
}
}
Err(err) => {
if preview_startup_condition(&err) {
debug!(
monitor_id,
?err,
"launcher preview waiting for capture pipeline"
);
log_preview_issue(
&shared,
&log_sink,
monitor_id,
&format!("Waiting for capture pipeline: {err}"),
);
set_shared_status(
&shared,
&log_sink,
monitor_id,
"Waiting for capture pipeline...",
true,
);
retry_delay = preview_retry_delay(retry_delay, Some(err.message()));
} else {
warn!(monitor_id, ?err, "launcher preview rpc failed");
log_preview_issue(
&shared,
&log_sink,
monitor_id,
&format!("Preview RPC failed: {err}"),
);
set_shared_status(
&shared,
&log_sink,
monitor_id,
"Preview RPC failed. See session log.",
true,
);
retry_delay = preview_retry_delay(retry_delay, Some(err.message()));
}
}
}
tokio::time::sleep(retry_delay).await;
}
#[allow(unreachable_code)]
Ok::<(), anyhow::Error>(())
});
let _ = pipeline.set_state(gst::State::Null);
running.store(false, Ordering::Relaxed);
let _ = sample_worker.join();
Ok(())
}

View File

@ -0,0 +1,334 @@
#[cfg(not(coverage))]
impl LauncherPreview {
pub fn new(server_addr: String) -> Result<Self> {
gst::init().context("initialising preview gstreamer")?;
let server_addr = Arc::new(Mutex::new(server_addr));
let log_sink = Arc::new(Mutex::new(None));
let inline_feeds = Arc::new(Mutex::new([
PreviewFeed::spawn(
Arc::clone(&server_addr),
0,
PreviewSurface::Inline.profile(),
Arc::clone(&log_sink),
)?,
PreviewFeed::spawn(
Arc::clone(&server_addr),
1,
PreviewSurface::Inline.profile(),
Arc::clone(&log_sink),
)?,
]));
let window_feeds = Arc::new(Mutex::new([
PreviewFeed::spawn(
Arc::clone(&server_addr),
0,
PreviewSurface::Window.profile(),
Arc::clone(&log_sink),
)?,
PreviewFeed::spawn(
Arc::clone(&server_addr),
1,
PreviewSurface::Window.profile(),
Arc::clone(&log_sink),
)?,
]));
Ok(Self {
server_addr: Arc::clone(&server_addr),
log_sink: Arc::clone(&log_sink),
inline_feeds,
window_feeds,
})
}
pub fn set_log_sink(&self, tx: std::sync::mpsc::Sender<String>) {
if let Ok(mut slot) = self.log_sink.lock() {
*slot = Some(tx);
}
}
pub fn set_server_addr(&self, server_addr: String) {
if let Ok(mut slot) = self.server_addr.lock() {
*slot = server_addr;
}
}
pub fn set_session_active(&self, active: bool) {
if let Ok(feeds) = self.inline_feeds.lock() {
for feed in feeds.iter() {
feed.set_active(active);
}
}
if let Ok(feeds) = self.window_feeds.lock() {
for feed in feeds.iter() {
feed.set_active(active);
}
}
}
pub fn shutdown_all(&self) {
if let Ok(feeds) = self.inline_feeds.lock() {
for feed in feeds.iter() {
feed.shutdown();
}
}
if let Ok(feeds) = self.window_feeds.lock() {
for feed in feeds.iter() {
feed.shutdown();
}
}
}
pub fn install_on_picture(
&self,
monitor_id: usize,
surface: PreviewSurface,
picture: &gtk::Picture,
status_label: &gtk::Label,
) -> Option<PreviewBinding> {
match surface {
PreviewSurface::Inline => self
.inline_feeds
.lock()
.ok()
.and_then(|feeds| feeds.get(monitor_id).cloned())
.map(|feed| feed.install_on_picture(picture, status_label)),
PreviewSurface::Window => self
.window_feeds
.lock()
.ok()
.and_then(|feeds| feeds.get(monitor_id).cloned())
.map(|feed| feed.install_on_picture(picture, status_label)),
}
}
pub fn snapshot_metrics(
&self,
monitor_id: usize,
surface: PreviewSurface,
) -> Option<PreviewMetricsSnapshot> {
match surface {
PreviewSurface::Inline => self
.inline_feeds
.lock()
.ok()
.and_then(|feeds| feeds.get(monitor_id).cloned())
.map(|feed| feed.snapshot_metrics()),
PreviewSurface::Window => self
.window_feeds
.lock()
.ok()
.and_then(|feeds| feeds.get(monitor_id).cloned())
.map(|feed| feed.snapshot_metrics()),
}
}
pub fn start_recording_tap(
&self,
monitor_id: usize,
surface: PreviewSurface,
) -> Option<PreviewRecordingTap> {
match surface {
PreviewSurface::Inline => self
.inline_feeds
.lock()
.ok()
.and_then(|feeds| feeds.get(monitor_id).cloned())
.and_then(|feed| feed.start_recording_tap()),
PreviewSurface::Window => self
.window_feeds
.lock()
.ok()
.and_then(|feeds| feeds.get(monitor_id).cloned())
.and_then(|feed| feed.start_recording_tap()),
}
}
pub fn set_capture_profile(
&self,
monitor_id: usize,
source_monitor_id: usize,
requested_width: i32,
requested_height: i32,
requested_fps: u32,
max_bitrate_kbit: u32,
) {
let (
inline_requested_width,
inline_requested_height,
inline_requested_fps,
inline_max_bitrate_kbit,
) = sanitize_preview_request(
requested_width,
requested_height,
requested_fps,
max_bitrate_kbit,
);
self.rebuild_feed(
&self.inline_feeds,
monitor_id,
Some((
source_monitor_id,
inline_requested_width,
inline_requested_height,
inline_requested_fps,
inline_max_bitrate_kbit,
)),
None,
);
self.rebuild_feed(
&self.window_feeds,
monitor_id,
Some((
source_monitor_id,
requested_width,
requested_height,
requested_fps,
max_bitrate_kbit,
)),
None,
);
}
pub fn set_breakout_profile(&self, monitor_id: usize, width: i32, height: i32) {
self.rebuild_feed(&self.window_feeds, monitor_id, None, Some((width, height)));
}
#[cfg(test)]
pub(crate) fn profile_for_test(
&self,
monitor_id: usize,
surface: PreviewSurface,
) -> Option<(u32, i32, i32, i32, i32, u32, u32)> {
let feed = match surface {
PreviewSurface::Inline => self.inline_feeds.lock().ok()?.get(monitor_id).cloned(),
PreviewSurface::Window => self.window_feeds.lock().ok()?.get(monitor_id).cloned(),
}?;
let profile = feed.profile();
Some((
profile.source_monitor_id,
profile.display_width,
profile.display_height,
profile.requested_width,
profile.requested_height,
profile.requested_fps,
profile.max_bitrate_kbit,
))
}
#[cfg(test)]
pub(crate) fn feed_disabled_for_test(
&self,
monitor_id: usize,
surface: PreviewSurface,
) -> Option<bool> {
let feed = match surface {
PreviewSurface::Inline => self.inline_feeds.lock().ok()?.get(monitor_id).cloned(),
PreviewSurface::Window => self.window_feeds.lock().ok()?.get(monitor_id).cloned(),
}?;
Some(feed.is_disabled())
}
fn rebuild_feed(
&self,
feeds: &Arc<Mutex<[PreviewFeed; 2]>>,
monitor_id: usize,
requested: Option<(usize, i32, i32, u32, u32)>,
display: Option<(i32, i32)>,
) {
let Ok(mut feeds) = feeds.lock() else {
return;
};
let Some(existing) = feeds.get(monitor_id).cloned() else {
return;
};
let was_active = existing.is_active();
let keep_disabled = existing.is_disabled();
let mut profile = existing.profile();
if let Some((
source_monitor_id,
requested_width,
requested_height,
requested_fps,
max_bitrate_kbit,
)) = requested
{
profile.source_monitor_id = source_monitor_id as u32;
profile.requested_width = requested_width.max(2);
profile.requested_height = requested_height.max(2);
profile.requested_fps = requested_fps.max(1);
profile.max_bitrate_kbit = max_bitrate_kbit.max(800);
}
if let Some((display_width, display_height)) = display {
profile.display_width = display_width.max(2);
profile.display_height = display_height.max(2);
}
let next_feed = if keep_disabled {
Some(PreviewFeed::spawn_disabled(profile))
} else {
match PreviewFeed::spawn(
Arc::clone(&self.server_addr),
monitor_id as u32,
profile,
Arc::clone(&self.log_sink),
) {
Ok(feed) => Some(feed),
Err(err) => {
warn!(monitor_id, ?err, "could not rebuild preview feed");
None
}
}
};
if let Some(feed) = next_feed {
if was_active {
feed.set_active(true);
}
existing.shutdown();
feeds[monitor_id] = feed;
}
}
pub fn set_monitor_enabled(&self, monitor_id: usize, enabled: bool) {
self.set_feed_enabled(&self.inline_feeds, monitor_id, enabled);
self.set_feed_enabled(&self.window_feeds, monitor_id, enabled);
}
fn set_feed_enabled(
&self,
feeds: &Arc<Mutex<[PreviewFeed; 2]>>,
monitor_id: usize,
enabled: bool,
) {
let Ok(mut feeds) = feeds.lock() else {
return;
};
let Some(existing) = feeds.get(monitor_id).cloned() else {
return;
};
if existing.is_disabled() != enabled {
return;
}
let was_active = existing.is_active();
let profile = existing.profile();
let replacement = if enabled {
match PreviewFeed::spawn(
Arc::clone(&self.server_addr),
monitor_id as u32,
profile,
Arc::clone(&self.log_sink),
) {
Ok(feed) => feed,
Err(err) => {
warn!(monitor_id, ?err, "could not enable preview feed");
return;
}
}
} else {
PreviewFeed::spawn_disabled(profile)
};
if was_active {
replacement.set_active(true);
}
existing.shutdown();
feeds[monitor_id] = replacement;
}
}

View File

@ -198,338 +198,3 @@ impl PreviewSurface {
}
}
}
#[cfg(not(coverage))]
impl LauncherPreview {
pub fn new(server_addr: String) -> Result<Self> {
gst::init().context("initialising preview gstreamer")?;
let server_addr = Arc::new(Mutex::new(server_addr));
let log_sink = Arc::new(Mutex::new(None));
let inline_feeds = Arc::new(Mutex::new([
PreviewFeed::spawn(
Arc::clone(&server_addr),
0,
PreviewSurface::Inline.profile(),
Arc::clone(&log_sink),
)?,
PreviewFeed::spawn(
Arc::clone(&server_addr),
1,
PreviewSurface::Inline.profile(),
Arc::clone(&log_sink),
)?,
]));
let window_feeds = Arc::new(Mutex::new([
PreviewFeed::spawn(
Arc::clone(&server_addr),
0,
PreviewSurface::Window.profile(),
Arc::clone(&log_sink),
)?,
PreviewFeed::spawn(
Arc::clone(&server_addr),
1,
PreviewSurface::Window.profile(),
Arc::clone(&log_sink),
)?,
]));
Ok(Self {
server_addr: Arc::clone(&server_addr),
log_sink: Arc::clone(&log_sink),
inline_feeds,
window_feeds,
})
}
pub fn set_log_sink(&self, tx: std::sync::mpsc::Sender<String>) {
if let Ok(mut slot) = self.log_sink.lock() {
*slot = Some(tx);
}
}
pub fn set_server_addr(&self, server_addr: String) {
if let Ok(mut slot) = self.server_addr.lock() {
*slot = server_addr;
}
}
pub fn set_session_active(&self, active: bool) {
if let Ok(feeds) = self.inline_feeds.lock() {
for feed in feeds.iter() {
feed.set_active(active);
}
}
if let Ok(feeds) = self.window_feeds.lock() {
for feed in feeds.iter() {
feed.set_active(active);
}
}
}
pub fn shutdown_all(&self) {
if let Ok(feeds) = self.inline_feeds.lock() {
for feed in feeds.iter() {
feed.shutdown();
}
}
if let Ok(feeds) = self.window_feeds.lock() {
for feed in feeds.iter() {
feed.shutdown();
}
}
}
pub fn install_on_picture(
&self,
monitor_id: usize,
surface: PreviewSurface,
picture: &gtk::Picture,
status_label: &gtk::Label,
) -> Option<PreviewBinding> {
match surface {
PreviewSurface::Inline => self
.inline_feeds
.lock()
.ok()
.and_then(|feeds| feeds.get(monitor_id).cloned())
.map(|feed| feed.install_on_picture(picture, status_label)),
PreviewSurface::Window => self
.window_feeds
.lock()
.ok()
.and_then(|feeds| feeds.get(monitor_id).cloned())
.map(|feed| feed.install_on_picture(picture, status_label)),
}
}
pub fn snapshot_metrics(
&self,
monitor_id: usize,
surface: PreviewSurface,
) -> Option<PreviewMetricsSnapshot> {
match surface {
PreviewSurface::Inline => self
.inline_feeds
.lock()
.ok()
.and_then(|feeds| feeds.get(monitor_id).cloned())
.map(|feed| feed.snapshot_metrics()),
PreviewSurface::Window => self
.window_feeds
.lock()
.ok()
.and_then(|feeds| feeds.get(monitor_id).cloned())
.map(|feed| feed.snapshot_metrics()),
}
}
pub fn start_recording_tap(
&self,
monitor_id: usize,
surface: PreviewSurface,
) -> Option<PreviewRecordingTap> {
match surface {
PreviewSurface::Inline => self
.inline_feeds
.lock()
.ok()
.and_then(|feeds| feeds.get(monitor_id).cloned())
.and_then(|feed| feed.start_recording_tap()),
PreviewSurface::Window => self
.window_feeds
.lock()
.ok()
.and_then(|feeds| feeds.get(monitor_id).cloned())
.and_then(|feed| feed.start_recording_tap()),
}
}
pub fn set_capture_profile(
&self,
monitor_id: usize,
source_monitor_id: usize,
requested_width: i32,
requested_height: i32,
requested_fps: u32,
max_bitrate_kbit: u32,
) {
let (
inline_requested_width,
inline_requested_height,
inline_requested_fps,
inline_max_bitrate_kbit,
) = sanitize_preview_request(
requested_width,
requested_height,
requested_fps,
max_bitrate_kbit,
);
self.rebuild_feed(
&self.inline_feeds,
monitor_id,
Some((
source_monitor_id,
inline_requested_width,
inline_requested_height,
inline_requested_fps,
inline_max_bitrate_kbit,
)),
None,
);
self.rebuild_feed(
&self.window_feeds,
monitor_id,
Some((
source_monitor_id,
requested_width,
requested_height,
requested_fps,
max_bitrate_kbit,
)),
None,
);
}
pub fn set_breakout_profile(&self, monitor_id: usize, width: i32, height: i32) {
self.rebuild_feed(&self.window_feeds, monitor_id, None, Some((width, height)));
}
#[cfg(test)]
pub(crate) fn profile_for_test(
&self,
monitor_id: usize,
surface: PreviewSurface,
) -> Option<(u32, i32, i32, i32, i32, u32, u32)> {
let feed = match surface {
PreviewSurface::Inline => self.inline_feeds.lock().ok()?.get(monitor_id).cloned(),
PreviewSurface::Window => self.window_feeds.lock().ok()?.get(monitor_id).cloned(),
}?;
let profile = feed.profile();
Some((
profile.source_monitor_id,
profile.display_width,
profile.display_height,
profile.requested_width,
profile.requested_height,
profile.requested_fps,
profile.max_bitrate_kbit,
))
}
#[cfg(test)]
pub(crate) fn feed_disabled_for_test(
&self,
monitor_id: usize,
surface: PreviewSurface,
) -> Option<bool> {
let feed = match surface {
PreviewSurface::Inline => self.inline_feeds.lock().ok()?.get(monitor_id).cloned(),
PreviewSurface::Window => self.window_feeds.lock().ok()?.get(monitor_id).cloned(),
}?;
Some(feed.is_disabled())
}
fn rebuild_feed(
&self,
feeds: &Arc<Mutex<[PreviewFeed; 2]>>,
monitor_id: usize,
requested: Option<(usize, i32, i32, u32, u32)>,
display: Option<(i32, i32)>,
) {
let Ok(mut feeds) = feeds.lock() else {
return;
};
let Some(existing) = feeds.get(monitor_id).cloned() else {
return;
};
let was_active = existing.is_active();
let keep_disabled = existing.is_disabled();
let mut profile = existing.profile();
if let Some((
source_monitor_id,
requested_width,
requested_height,
requested_fps,
max_bitrate_kbit,
)) = requested
{
profile.source_monitor_id = source_monitor_id as u32;
profile.requested_width = requested_width.max(2);
profile.requested_height = requested_height.max(2);
profile.requested_fps = requested_fps.max(1);
profile.max_bitrate_kbit = max_bitrate_kbit.max(800);
}
if let Some((display_width, display_height)) = display {
profile.display_width = display_width.max(2);
profile.display_height = display_height.max(2);
}
let next_feed = if keep_disabled {
Some(PreviewFeed::spawn_disabled(profile))
} else {
match PreviewFeed::spawn(
Arc::clone(&self.server_addr),
monitor_id as u32,
profile,
Arc::clone(&self.log_sink),
) {
Ok(feed) => Some(feed),
Err(err) => {
warn!(monitor_id, ?err, "could not rebuild preview feed");
None
}
}
};
if let Some(feed) = next_feed {
if was_active {
feed.set_active(true);
}
existing.shutdown();
feeds[monitor_id] = feed;
}
}
pub fn set_monitor_enabled(&self, monitor_id: usize, enabled: bool) {
self.set_feed_enabled(&self.inline_feeds, monitor_id, enabled);
self.set_feed_enabled(&self.window_feeds, monitor_id, enabled);
}
fn set_feed_enabled(
&self,
feeds: &Arc<Mutex<[PreviewFeed; 2]>>,
monitor_id: usize,
enabled: bool,
) {
let Ok(mut feeds) = feeds.lock() else {
return;
};
let Some(existing) = feeds.get(monitor_id).cloned() else {
return;
};
if existing.is_disabled() != enabled {
return;
}
let was_active = existing.is_active();
let profile = existing.profile();
let replacement = if enabled {
match PreviewFeed::spawn(
Arc::clone(&self.server_addr),
monitor_id as u32,
profile,
Arc::clone(&self.log_sink),
) {
Ok(feed) => feed,
Err(err) => {
warn!(monitor_id, ?err, "could not enable preview feed");
return;
}
}
} else {
PreviewFeed::spawn_disabled(profile)
};
if was_active {
replacement.set_active(true);
}
existing.shutdown();
feeds[monitor_id] = replacement;
}
}

View File

@ -69,6 +69,10 @@ include!("ui/activation_context.rs");
include!("ui/startup_window_guard.rs");
#[cfg(not(coverage))]
include!("ui/eye_capture_bindings/recording_support.rs");
#[cfg(not(coverage))]
include!("ui/eye_capture_bindings/recording_worker.rs");
#[cfg(not(coverage))]
include!("ui/utility_button_bindings/pki_support.rs");
#[cfg(coverage)]
include!("ui/session_preview_coverage.rs");

View File

@ -1,228 +1,4 @@
{
fn spawn_raw_video_encoder(
width: i32,
height: i32,
output_path: &Path,
encode_fps: u32,
encode_bitrate_kbit: u32,
) -> Result<std::process::Child, String> {
let bitrate_arg = format!("{}k", encode_bitrate_kbit.max(800));
let fps_arg = encode_fps.max(1).to_string();
let video_size = format!("{}x{}", width.max(1), height.max(1));
let output_arg = output_path.to_string_lossy().into_owned();
Command::new("ffmpeg")
.args([
"-hide_banner",
"-loglevel",
"error",
"-y",
"-f",
"rawvideo",
"-pix_fmt",
"rgba",
"-video_size",
&video_size,
"-framerate",
&fps_arg,
"-i",
"-",
"-c:v",
"libx264",
"-pix_fmt",
"yuv420p",
"-r",
&fps_arg,
"-b:v",
&bitrate_arg,
])
.arg(&output_arg)
.stdin(std::process::Stdio::piped())
.stdout(std::process::Stdio::null())
.stderr(std::process::Stdio::null())
.spawn()
.map_err(|err| format!("ffmpeg video encoder is unavailable: {err}"))
}
fn normalize_recording_frame(frame: PreviewFrameSnapshot) -> Result<(i32, i32, Vec<u8>), String> {
let width = frame.width.max(0) as usize;
let height = frame.height.max(0) as usize;
if width == 0 || height == 0 {
return Err("decoded preview frame had zero size".to_string());
}
let row_bytes = width.saturating_mul(4);
let needed = row_bytes.saturating_mul(height);
if frame.rgba.len() < needed && frame.stride == row_bytes {
return Err("decoded preview frame was shorter than its declared size".to_string());
}
if frame.stride == row_bytes && frame.rgba.len() >= needed {
return Ok((frame.width, frame.height, frame.rgba[..needed].to_vec()));
}
if frame.stride < row_bytes || frame.rgba.len() < frame.stride.saturating_mul(height) {
return Err("decoded preview frame stride was inconsistent".to_string());
}
let mut rgba = Vec::with_capacity(needed);
for row in 0..height {
let start = row.saturating_mul(frame.stride);
rgba.extend_from_slice(&frame.rgba[start..start + row_bytes]);
}
Ok((frame.width, frame.height, rgba))
}
fn finish_raw_video_encoder(
child: &mut std::process::Child,
frame_dir: &Path,
output_path: &Path,
) -> Result<(), String> {
let _ = child.stdin.take();
let status = child
.wait()
.map_err(|err| format!("ffmpeg video encoder wait failed: {err}"))?;
if !status.success() {
return Err(format!(
"ffmpeg failed while encoding {}; temporary data is still in {}",
output_path.display(),
frame_dir.display()
));
}
Ok(())
}
fn mux_recording_audio(
video_path: &Path,
output_path: &Path,
audio_mode: EyeRecordAudioMode,
audio_paths: &[PathBuf],
) -> Result<(), String> {
let usable_audio_paths = validated_audio_paths(audio_mode, audio_paths)?;
if usable_audio_paths.is_empty() {
return Ok(());
}
let video_arg = video_path.to_string_lossy().into_owned();
let output_arg = output_path.to_string_lossy().into_owned();
let mut command = Command::new("ffmpeg");
command.args([
"-hide_banner",
"-loglevel",
"error",
"-y",
"-i",
&video_arg,
]);
for audio_path in &usable_audio_paths {
command.arg("-i").arg(audio_path);
}
if usable_audio_paths.len() > 1 {
command.args([
"-filter_complex",
"[1:a][2:a]amix=inputs=2:duration=shortest:normalize=0[a]",
"-map",
"0:v:0",
"-map",
"[a]",
]);
} else {
command.args(["-map", "0:v:0", "-map", "1:a:0"]);
}
command.args(["-c:v", "copy", "-c:a", "aac", "-b:a", "160k", "-shortest"]);
let mux = command
.arg(&output_arg)
.status()
.map_err(|err| format!("ffmpeg is unavailable: {err}"))?;
if !mux.success() {
return Err(format!(
"ffmpeg failed while adding audio to {}",
output_path.display()
));
}
Ok(())
}
fn run_recording_worker(
frame_tap: PreviewRecordingTap,
control_rx: std::sync::mpsc::Receiver<RecordFrameTask>,
frame_dir: PathBuf,
output_path: PathBuf,
encode_fps: u32,
encode_bitrate_kbit: u32,
audio_mode: EyeRecordAudioMode,
audio_paths: Vec<PathBuf>,
) -> Result<PathBuf, String> {
let needs_audio_mux = audio_mode != EyeRecordAudioMode::NoAudio;
let video_output_path = if needs_audio_mux {
frame_dir.join("recording-video.mp4")
} else {
output_path.clone()
};
let mut encoder: Option<std::process::Child> = None;
let mut frame_size: Option<(i32, i32)> = None;
let mut captured_frames = 0_u32;
loop {
match control_rx.try_recv() {
Ok(RecordFrameTask::Finish) | Err(std::sync::mpsc::TryRecvError::Disconnected) => {
break;
}
Err(std::sync::mpsc::TryRecvError::Empty) => {}
}
match frame_tap.recv_timeout(Duration::from_millis(50)) {
Ok(frame) => {
let (width, height, rgba) = normalize_recording_frame(frame)?;
if let Some((expected_width, expected_height)) = frame_size {
if width != expected_width || height != expected_height {
continue;
}
} else {
frame_size = Some((width, height));
encoder = Some(spawn_raw_video_encoder(
width,
height,
&video_output_path,
encode_fps,
encode_bitrate_kbit,
)?);
}
let encoder = encoder
.as_mut()
.ok_or_else(|| "recording encoder did not start".to_string())?;
let stdin = encoder
.stdin
.as_mut()
.ok_or_else(|| "recording encoder stdin is closed".to_string())?;
std::io::Write::write_all(stdin, &rgba)
.map_err(|err| format!("recording encoder write failed: {err}"))?;
captured_frames = captured_frames.saturating_add(1);
}
Err(std::sync::mpsc::RecvTimeoutError::Timeout) => {}
Err(std::sync::mpsc::RecvTimeoutError::Disconnected) => break,
}
}
if captured_frames < 2 {
if let Some(mut child) = encoder {
let _ = child.kill();
let _ = child.wait();
}
let _ = std::fs::remove_dir_all(&frame_dir);
return Err("need at least two captured frames to build a recording".to_string());
}
if let Some(mut child) = encoder {
finish_raw_video_encoder(&mut child, &frame_dir, &video_output_path)?;
}
mux_recording_audio(
&video_output_path,
&output_path,
audio_mode,
&audio_paths,
)?;
if needs_audio_mux {
let _ = std::fs::remove_file(&video_output_path);
}
let _ = std::fs::remove_dir_all(&frame_dir);
Ok(output_path)
}
for monitor_id in 0..2 {
let pane = widgets.display_panes[monitor_id].clone();
let widgets_for_ui = widgets.clone();
@ -477,15 +253,17 @@
let frame_dir_worker = frame_dir.clone();
let output_path_worker = output_path.clone();
std::thread::spawn(move || {
let result = run_recording_worker(
let result = run_recording_worker(
frame_tap,
control_rx,
frame_dir_worker,
output_path_worker,
record_fps,
record_bitrate_kbit,
audio_mode,
audio_paths,
EyeRecordingWorkerConfig {
frame_dir: frame_dir_worker,
output_path: output_path_worker,
encode_fps: record_fps,
encode_bitrate_kbit: record_bitrate_kbit,
audio_mode,
audio_paths,
},
);
let _ = result_tx.send(result);
});

View File

@ -0,0 +1,235 @@
fn spawn_raw_video_encoder(
width: i32,
height: i32,
output_path: &Path,
encode_fps: u32,
encode_bitrate_kbit: u32,
) -> Result<std::process::Child, String> {
let bitrate_arg = format!("{}k", encode_bitrate_kbit.max(800));
let fps_arg = encode_fps.max(1).to_string();
let video_size = format!("{}x{}", width.max(1), height.max(1));
let output_arg = output_path.to_string_lossy().into_owned();
Command::new("ffmpeg")
.args([
"-hide_banner",
"-loglevel",
"error",
"-y",
"-f",
"rawvideo",
"-pix_fmt",
"rgba",
"-video_size",
&video_size,
"-framerate",
&fps_arg,
"-i",
"-",
"-c:v",
"libx264",
"-pix_fmt",
"yuv420p",
"-r",
&fps_arg,
"-b:v",
&bitrate_arg,
])
.arg(&output_arg)
.stdin(std::process::Stdio::piped())
.stdout(std::process::Stdio::null())
.stderr(std::process::Stdio::null())
.spawn()
.map_err(|err| format!("ffmpeg video encoder is unavailable: {err}"))
}
fn normalize_recording_frame(frame: PreviewFrameSnapshot) -> Result<(i32, i32, Vec<u8>), String> {
let width = frame.width.max(0) as usize;
let height = frame.height.max(0) as usize;
if width == 0 || height == 0 {
return Err("decoded preview frame had zero size".to_string());
}
let row_bytes = width.saturating_mul(4);
let needed = row_bytes.saturating_mul(height);
if frame.rgba.len() < needed && frame.stride == row_bytes {
return Err("decoded preview frame was shorter than its declared size".to_string());
}
if frame.stride == row_bytes && frame.rgba.len() >= needed {
return Ok((frame.width, frame.height, frame.rgba[..needed].to_vec()));
}
if frame.stride < row_bytes || frame.rgba.len() < frame.stride.saturating_mul(height) {
return Err("decoded preview frame stride was inconsistent".to_string());
}
let mut rgba = Vec::with_capacity(needed);
for row in 0..height {
let start = row.saturating_mul(frame.stride);
rgba.extend_from_slice(&frame.rgba[start..start + row_bytes]);
}
Ok((frame.width, frame.height, rgba))
}
fn finish_raw_video_encoder(
child: &mut std::process::Child,
frame_dir: &Path,
output_path: &Path,
) -> Result<(), String> {
let _ = child.stdin.take();
let status = child
.wait()
.map_err(|err| format!("ffmpeg video encoder wait failed: {err}"))?;
if !status.success() {
return Err(format!(
"ffmpeg failed while encoding {}; temporary data is still in {}",
output_path.display(),
frame_dir.display()
));
}
Ok(())
}
fn mux_recording_audio(
video_path: &Path,
output_path: &Path,
audio_mode: EyeRecordAudioMode,
audio_paths: &[PathBuf],
) -> Result<(), String> {
let usable_audio_paths = validated_audio_paths(audio_mode, audio_paths)?;
if usable_audio_paths.is_empty() {
return Ok(());
}
let video_arg = video_path.to_string_lossy().into_owned();
let output_arg = output_path.to_string_lossy().into_owned();
let mut command = Command::new("ffmpeg");
command.args([
"-hide_banner",
"-loglevel",
"error",
"-y",
"-i",
&video_arg,
]);
for audio_path in &usable_audio_paths {
command.arg("-i").arg(audio_path);
}
if usable_audio_paths.len() > 1 {
command.args([
"-filter_complex",
"[1:a][2:a]amix=inputs=2:duration=shortest:normalize=0[a]",
"-map",
"0:v:0",
"-map",
"[a]",
]);
} else {
command.args(["-map", "0:v:0", "-map", "1:a:0"]);
}
command.args(["-c:v", "copy", "-c:a", "aac", "-b:a", "160k", "-shortest"]);
let mux = command
.arg(&output_arg)
.status()
.map_err(|err| format!("ffmpeg is unavailable: {err}"))?;
if !mux.success() {
return Err(format!(
"ffmpeg failed while adding audio to {}",
output_path.display()
));
}
Ok(())
}
struct EyeRecordingWorkerConfig {
frame_dir: PathBuf,
output_path: PathBuf,
encode_fps: u32,
encode_bitrate_kbit: u32,
audio_mode: EyeRecordAudioMode,
audio_paths: Vec<PathBuf>,
}
fn run_recording_worker(
frame_tap: PreviewRecordingTap,
control_rx: std::sync::mpsc::Receiver<RecordFrameTask>,
config: EyeRecordingWorkerConfig,
) -> Result<PathBuf, String> {
let EyeRecordingWorkerConfig {
frame_dir,
output_path,
encode_fps,
encode_bitrate_kbit,
audio_mode,
audio_paths,
} = config;
let needs_audio_mux = audio_mode != EyeRecordAudioMode::NoAudio;
let video_output_path = if needs_audio_mux {
frame_dir.join("recording-video.mp4")
} else {
output_path.clone()
};
let mut encoder: Option<std::process::Child> = None;
let mut frame_size: Option<(i32, i32)> = None;
let mut captured_frames = 0_u32;
loop {
match control_rx.try_recv() {
Ok(RecordFrameTask::Finish) | Err(std::sync::mpsc::TryRecvError::Disconnected) => {
break;
}
Err(std::sync::mpsc::TryRecvError::Empty) => {}
}
match frame_tap.recv_timeout(Duration::from_millis(50)) {
Ok(frame) => {
let (width, height, rgba) = normalize_recording_frame(frame)?;
if let Some((expected_width, expected_height)) = frame_size {
if width != expected_width || height != expected_height {
continue;
}
} else {
frame_size = Some((width, height));
encoder = Some(spawn_raw_video_encoder(
width,
height,
&video_output_path,
encode_fps,
encode_bitrate_kbit,
)?);
}
let encoder = encoder
.as_mut()
.ok_or_else(|| "recording encoder did not start".to_string())?;
let stdin = encoder
.stdin
.as_mut()
.ok_or_else(|| "recording encoder stdin is closed".to_string())?;
std::io::Write::write_all(stdin, &rgba)
.map_err(|err| format!("recording encoder write failed: {err}"))?;
captured_frames = captured_frames.saturating_add(1);
}
Err(std::sync::mpsc::RecvTimeoutError::Timeout) => {}
Err(std::sync::mpsc::RecvTimeoutError::Disconnected) => break,
}
}
if captured_frames < 2 {
if let Some(mut child) = encoder {
let _ = child.kill();
let _ = child.wait();
}
let _ = std::fs::remove_dir_all(&frame_dir);
return Err("need at least two captured frames to build a recording".to_string());
}
if let Some(mut child) = encoder {
finish_raw_video_encoder(&mut child, &frame_dir, &video_output_path)?;
}
mux_recording_audio(
&video_output_path,
&output_path,
audio_mode,
&audio_paths,
)?;
if needs_audio_mux {
let _ = std::fs::remove_file(&video_output_path);
}
let _ = std::fs::remove_dir_all(&frame_dir);
Ok(output_path)
}

View File

@ -1,64 +1,4 @@
{
fn default_client_pki_dir() -> PathBuf {
if let Some(home) = std::env::var_os("HOME") {
return PathBuf::from(home).join(".config/lesavka/pki");
}
std::env::current_dir()
.unwrap_or_else(|_| PathBuf::from("."))
.join(".config/lesavka/pki")
}
fn install_client_pki_bundle(bundle: &Path) -> Result<PathBuf, String> {
let target = default_client_pki_dir();
let scratch = std::env::temp_dir().join(format!(
"lesavka-client-pki-{}",
SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap_or_default()
.as_millis()
));
std::fs::create_dir_all(&scratch)
.map_err(|err| format!("could not create extraction folder: {err}"))?;
let extract = Command::new("tar")
.arg("-xzf")
.arg(bundle)
.arg("-C")
.arg(&scratch)
.status()
.map_err(|err| format!("tar is unavailable: {err}"))?;
if !extract.success() {
let _ = std::fs::remove_dir_all(&scratch);
return Err(format!("could not extract {}", bundle.display()));
}
for item in ["ca.crt", "client.crt", "client.key"] {
if !scratch.join(item).is_file() {
let _ = std::fs::remove_dir_all(&scratch);
return Err(format!("bundle is missing {item}"));
}
}
std::fs::create_dir_all(&target)
.map_err(|err| format!("could not create {}: {err}", target.display()))?;
for item in ["ca.crt", "client.crt", "client.key"] {
std::fs::copy(scratch.join(item), target.join(item))
.map_err(|err| format!("could not install {item}: {err}"))?;
}
tighten_client_key_permissions(&target.join("client.key"));
let _ = std::fs::remove_dir_all(&scratch);
Ok(target)
}
#[cfg(unix)]
fn tighten_client_key_permissions(path: &Path) {
use std::os::unix::fs::PermissionsExt;
if let Ok(mut permissions) = std::fs::metadata(path).map(|metadata| metadata.permissions()) {
permissions.set_mode(0o600);
let _ = std::fs::set_permissions(path, permissions);
}
}
#[cfg(not(unix))]
fn tighten_client_key_permissions(_path: &Path) {}
{
let widgets = widgets.clone();
let window = window.clone();

View File

@ -0,0 +1,59 @@
fn default_client_pki_dir() -> PathBuf {
if let Some(home) = std::env::var_os("HOME") {
return PathBuf::from(home).join(".config/lesavka/pki");
}
std::env::current_dir()
.unwrap_or_else(|_| PathBuf::from("."))
.join(".config/lesavka/pki")
}
fn install_client_pki_bundle(bundle: &Path) -> Result<PathBuf, String> {
let target = default_client_pki_dir();
let scratch = std::env::temp_dir().join(format!(
"lesavka-client-pki-{}",
SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap_or_default()
.as_millis()
));
std::fs::create_dir_all(&scratch)
.map_err(|err| format!("could not create extraction folder: {err}"))?;
let extract = Command::new("tar")
.arg("-xzf")
.arg(bundle)
.arg("-C")
.arg(&scratch)
.status()
.map_err(|err| format!("tar is unavailable: {err}"))?;
if !extract.success() {
let _ = std::fs::remove_dir_all(&scratch);
return Err(format!("could not extract {}", bundle.display()));
}
for item in ["ca.crt", "client.crt", "client.key"] {
if !scratch.join(item).is_file() {
let _ = std::fs::remove_dir_all(&scratch);
return Err(format!("bundle is missing {item}"));
}
}
std::fs::create_dir_all(&target)
.map_err(|err| format!("could not create {}: {err}", target.display()))?;
for item in ["ca.crt", "client.crt", "client.key"] {
std::fs::copy(scratch.join(item), target.join(item))
.map_err(|err| format!("could not install {item}: {err}"))?;
}
tighten_client_key_permissions(&target.join("client.key"));
let _ = std::fs::remove_dir_all(&scratch);
Ok(target)
}
#[cfg(unix)]
fn tighten_client_key_permissions(path: &Path) {
use std::os::unix::fs::PermissionsExt;
if let Ok(mut permissions) = std::fs::metadata(path).map(|metadata| metadata.permissions()) {
permissions.set_mode(0o600);
let _ = std::fs::set_permissions(path, permissions);
}
}
#[cfg(not(unix))]
fn tighten_client_key_permissions(_path: &Path) {}

View File

@ -183,6 +183,7 @@ from `LESAVKA_CLIENT_PKI_SSH_SOURCE` over SSH. Runtime clients require the insta
| `LESAVKA_LAUNCHER_PARENT_PID` | launcher UI/runtime override |
| `LESAVKA_LAUNCHER_PARENT_START_TICKS` | launcher UI/runtime override |
| `LESAVKA_LAUNCHER_TOGGLE_KEY_CONTROL` | launcher UI/runtime override |
| `LESAVKA_LAUNCHER_WAKE_CONTROL` | launcher idle-wake control file override; stores staged relay idle-nudge requests from the UI |
| `LESAVKA_LAUNCHER_WINDOW_TITLE` | launcher UI/runtime override |
| `LESAVKA_LAB_GATE_PUSHGATEWAY_JOB` | CI metrics destination override for the opt-in bare-metal lab gate profile |
| `LESAVKA_LIVE_KEYBOARD_REPORT_DELAY_MS` | input routing/clipboard override |
@ -355,6 +356,7 @@ from `LESAVKA_CLIENT_PKI_SSH_SOURCE` over SSH. Runtime clients require the insta
| `LESAVKA_UVC_MJPEG` | server hardware/device override |
| `LESAVKA_UVC_MJPEG_BUDGET_BYTES_PER_SEC` | UVC helper MJPEG budget guard; derives a per-frame byte cap from target FPS when `LESAVKA_UVC_FRAME_MAX_BYTES` is unset; non-bulk UVC is additionally capped by `LESAVKA_UVC_ISOCHRONOUS_LIMIT_PCT` |
| `LESAVKA_UVC_QUEUE_PACING` | UVC helper queue pacing override; defaults to `0` because the RCT host already paces UVC consumption, and delaying returned buffer requeueing can starve isochronous gadget transfers |
| `LESAVKA_UVC_RESTART_DELAY_MS` | UVC control helper supervisor restart delay after helper exit or failure; defaults to `1000` |
| `LESAVKA_UVC_SKIP_UDEV` | server hardware/device override |
| `LESAVKA_UVC_STATS_INTERVAL_MS` | UVC helper telemetry interval for queued/reloaded/rejected MJPEG frame counters; defaults to `5000`, `0` disables |
| `LESAVKA_UVC_STATS_PATH` | UVC helper JSON stats snapshot path for queued/reloaded/rejected MJPEG frame counters; defaults to `/run/lesavka-uvc-video-stats.json`, set `0` or empty to disable file snapshots |

View File

@ -168,12 +168,12 @@
"client/src/input/inputs.rs": {
"clippy_warnings": 0,
"doc_debt": 0,
"loc": 87
"loc": 94
},
"client/src/input/inputs/construction_and_scan.rs": {
"clippy_warnings": 0,
"doc_debt": 4,
"loc": 275
"loc": 292
},
"client/src/input/inputs/device_classification.rs": {
"clippy_warnings": 0,
@ -182,18 +182,18 @@
},
"client/src/input/inputs/routing_state.rs": {
"clippy_warnings": 0,
"doc_debt": 11,
"loc": 293
"doc_debt": 14,
"loc": 379
},
"client/src/input/inputs/run_loop.rs": {
"clippy_warnings": 0,
"doc_debt": 2,
"loc": 143
"loc": 149
},
"client/src/input/inputs/runtime_controls.rs": {
"clippy_warnings": 0,
"doc_debt": 4,
"loc": 127
"doc_debt": 5,
"loc": 145
},
"client/src/input/inputs/toggle_keys.rs": {
"clippy_warnings": 0,
@ -238,7 +238,7 @@
"client/src/input/mouse.rs": {
"clippy_warnings": 0,
"doc_debt": 13,
"loc": 411
"loc": 416
},
"client/src/input/mouse_event_contract_tests.rs": {
"clippy_warnings": 0,
@ -318,27 +318,37 @@
"client/src/launcher/preview.rs": {
"clippy_warnings": 0,
"doc_debt": 0,
"loc": 10
"loc": 12
},
"client/src/launcher/preview/feed_runtime.rs": {
"clippy_warnings": 0,
"doc_debt": 7,
"loc": 500
"loc": 226
},
"client/src/launcher/preview/feed_state.rs": {
"clippy_warnings": 0,
"doc_debt": 12,
"loc": 304
"doc_debt": 13,
"loc": 315
},
"client/src/launcher/preview/feed_worker.rs": {
"clippy_warnings": 0,
"doc_debt": 1,
"loc": 312
},
"client/src/launcher/preview/frame_telemetry.rs": {
"clippy_warnings": 0,
"doc_debt": 9,
"loc": 179
},
"client/src/launcher/preview/launcher_preview_impl.rs": {
"clippy_warnings": 0,
"doc_debt": 13,
"loc": 334
},
"client/src/launcher/preview/preview_core.rs": {
"clippy_warnings": 0,
"doc_debt": 14,
"loc": 500
"doc_debt": 1,
"loc": 200
},
"client/src/launcher/preview/status_pipeline.rs": {
"clippy_warnings": 0,
@ -378,7 +388,7 @@
"client/src/launcher/ui.rs": {
"clippy_warnings": 0,
"doc_debt": 1,
"loc": 204
"loc": 213
},
"client/src/launcher/ui/activation_context.rs": {
"clippy_warnings": 0,
@ -407,13 +417,18 @@
},
"client/src/launcher/ui/eye_capture_bindings.rs": {
"clippy_warnings": 0,
"doc_debt": 2,
"loc": 435
"doc_debt": 0,
"loc": 294
},
"client/src/launcher/ui/eye_capture_bindings/recording_support.rs": {
"clippy_warnings": 0,
"doc_debt": 24,
"loc": 449
"doc_debt": 23,
"loc": 421
},
"client/src/launcher/ui/eye_capture_bindings/recording_worker.rs": {
"clippy_warnings": 0,
"doc_debt": 5,
"loc": 235
},
"client/src/launcher/ui/eye_display_bindings.rs": {
"clippy_warnings": 0,
@ -476,24 +491,29 @@
"loc": 53
},
"client/src/launcher/ui/utility_button_bindings.rs": {
"clippy_warnings": 0,
"doc_debt": 0,
"loc": 481
},
"client/src/launcher/ui/utility_button_bindings/pki_support.rs": {
"clippy_warnings": 0,
"doc_debt": 3,
"loc": 489
"loc": 59
},
"client/src/launcher/ui_components.rs": {
"clippy_warnings": 0,
"doc_debt": 1,
"loc": 136
"loc": 137
},
"client/src/launcher/ui_components/assemble_view.rs": {
"clippy_warnings": 0,
"doc_debt": 0,
"loc": 216
"loc": 217
},
"client/src/launcher/ui_components/build_contexts.rs": {
"clippy_warnings": 0,
"doc_debt": 0,
"loc": 96
"loc": 97
},
"client/src/launcher/ui_components/build_device_controls.rs": {
"clippy_warnings": 0,
@ -503,7 +523,7 @@
"client/src/launcher/ui_components/build_operations_rail.rs": {
"clippy_warnings": 0,
"doc_debt": 0,
"loc": 332
"loc": 352
},
"client/src/launcher/ui_components/build_shell.rs": {
"clippy_warnings": 0,
@ -523,7 +543,7 @@
"client/src/launcher/ui_components/display_pane.rs": {
"clippy_warnings": 0,
"doc_debt": 2,
"loc": 247
"loc": 249
},
"client/src/launcher/ui_components/panel_chips.rs": {
"clippy_warnings": 0,
@ -543,7 +563,7 @@
"client/src/launcher/ui_components/types.rs": {
"clippy_warnings": 0,
"doc_debt": 0,
"loc": 237
"loc": 238
},
"client/src/launcher/ui_runtime.rs": {
"clippy_warnings": 0,
@ -553,12 +573,12 @@
"client/src/launcher/ui_runtime/control_paths.rs": {
"clippy_warnings": 0,
"doc_debt": 0,
"loc": 302
"loc": 316
},
"client/src/launcher/ui_runtime/display_popouts.rs": {
"clippy_warnings": 0,
"doc_debt": 5,
"loc": 273
"loc": 277
},
"client/src/launcher/ui_runtime/log_filtering.rs": {
"clippy_warnings": 0,
@ -568,7 +588,7 @@
"client/src/launcher/ui_runtime/process_logs.rs": {
"clippy_warnings": 0,
"doc_debt": 7,
"loc": 277
"loc": 279
},
"client/src/launcher/ui_runtime/report_popouts.rs": {
"clippy_warnings": 0,
@ -588,7 +608,7 @@
"client/src/launcher/ui_runtime/status_refresh.rs": {
"clippy_warnings": 0,
"doc_debt": 3,
"loc": 444
"loc": 446
},
"client/src/layout.rs": {
"clippy_warnings": 0,
@ -918,7 +938,7 @@
"server/src/audio/ear_capture.rs": {
"clippy_warnings": 0,
"doc_debt": 9,
"loc": 450
"loc": 451
},
"server/src/audio/ear_capture/source_watchdog.rs": {
"clippy_warnings": 0,
@ -1253,7 +1273,7 @@
"server/src/uvc_runtime.rs": {
"clippy_warnings": 0,
"doc_debt": 4,
"loc": 255
"loc": 264
},
"server/src/video.rs": {
"clippy_warnings": 0,

View File

@ -19,10 +19,10 @@ use tokio::runtime::Runtime;
async fn wait_for_marker(marker: &Path, is_ready: impl Fn(&str) -> bool) -> String {
let deadline = tokio::time::Instant::now() + Duration::from_secs(2);
loop {
if let Ok(contents) = fs::read_to_string(marker) {
if is_ready(&contents) {
return contents;
}
if let Ok(contents) = fs::read_to_string(marker)
&& is_ready(&contents)
{
return contents;
}
assert!(
tokio::time::Instant::now() < deadline,