feat(launcher): guide staging flow and capture modes
This commit is contained in:
parent
e15afc2ebd
commit
e7dcfd2fd5
@ -21,6 +21,7 @@ pub enum DeviceTestKind {
|
||||
Speaker,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct DeviceTestController {
|
||||
camera: Option<LocalCameraPreview>,
|
||||
selected_camera: Option<String>,
|
||||
@ -28,17 +29,6 @@ pub struct DeviceTestController {
|
||||
speaker: Option<Child>,
|
||||
}
|
||||
|
||||
impl Default for DeviceTestController {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
camera: None,
|
||||
selected_camera: None,
|
||||
microphone: None,
|
||||
speaker: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl DeviceTestController {
|
||||
pub fn new() -> Self {
|
||||
Self::default()
|
||||
@ -227,7 +217,9 @@ impl LocalCameraPreview {
|
||||
}
|
||||
|
||||
self.set_status(match self.selected_device.as_deref() {
|
||||
Some(camera) => format!("Selected {camera}. Click Start Preview to verify it here."),
|
||||
Some(camera) => format!(
|
||||
"Selected {camera}. Start Preview to confirm framing here before you launch the relay."
|
||||
),
|
||||
None => CAMERA_PREVIEW_IDLE.to_string(),
|
||||
});
|
||||
Ok(())
|
||||
@ -255,7 +247,7 @@ impl LocalCameraPreview {
|
||||
let running = Arc::clone(&self.running);
|
||||
let token = generation.fetch_add(1, Ordering::AcqRel) + 1;
|
||||
running.store(true, Ordering::Release);
|
||||
self.set_status(format!("Starting preview for {selected}..."));
|
||||
self.set_status(format!("Starting local preview for {selected}..."));
|
||||
|
||||
std::thread::spawn(move || {
|
||||
if let Err(err) = run_camera_preview_feed(
|
||||
@ -266,12 +258,11 @@ impl LocalCameraPreview {
|
||||
status_text.clone(),
|
||||
generation.clone(),
|
||||
running.clone(),
|
||||
) {
|
||||
if generation.load(Ordering::Acquire) == token {
|
||||
running.store(false, Ordering::Release);
|
||||
if let Ok(mut status) = status_text.lock() {
|
||||
*status = format!("Camera preview failed: {err}");
|
||||
}
|
||||
) && generation.load(Ordering::Acquire) == token
|
||||
{
|
||||
running.store(false, Ordering::Release);
|
||||
if let Ok(mut status) = status_text.lock() {
|
||||
*status = format!("Camera preview failed: {err}");
|
||||
}
|
||||
}
|
||||
});
|
||||
@ -285,7 +276,9 @@ impl LocalCameraPreview {
|
||||
*latest = None;
|
||||
}
|
||||
self.set_status(match self.selected_device.as_deref() {
|
||||
Some(camera) => format!("Preview stopped. {camera} is still selected."),
|
||||
Some(camera) => {
|
||||
format!("Local preview stopped. {camera} stays selected for the next relay launch.")
|
||||
}
|
||||
None => CAMERA_PREVIEW_IDLE.to_string(),
|
||||
});
|
||||
}
|
||||
@ -327,16 +320,15 @@ fn run_camera_preview_feed(
|
||||
.context("starting in-launcher camera preview pipeline")?;
|
||||
|
||||
if let Ok(mut status) = status_text.lock() {
|
||||
*status = format!("Previewing {selected} locally.");
|
||||
*status = format!("Local preview live for {selected}.");
|
||||
}
|
||||
|
||||
while running.load(Ordering::Acquire) && generation.load(Ordering::Acquire) == token {
|
||||
if let Some(sample) = appsink.try_pull_sample(gst::ClockTime::from_mseconds(250)) {
|
||||
if let Some(frame) = sample_to_frame(&sample) {
|
||||
if let Ok(mut slot) = latest.lock() {
|
||||
*slot = Some(frame);
|
||||
}
|
||||
}
|
||||
if let Some(sample) = appsink.try_pull_sample(gst::ClockTime::from_mseconds(250))
|
||||
&& let Some(frame) = sample_to_frame(&sample)
|
||||
&& let Ok(mut slot) = latest.lock()
|
||||
{
|
||||
*slot = Some(frame);
|
||||
}
|
||||
}
|
||||
|
||||
@ -363,9 +355,9 @@ fn build_camera_preview_pipeline(device: &str) -> Result<(gst::Pipeline, gst_app
|
||||
.expect("camera preview appsink");
|
||||
appsink.set_caps(Some(
|
||||
&gst::Caps::builder("video/x-raw")
|
||||
.field("format", &"RGBA")
|
||||
.field("width", &CAMERA_PREVIEW_WIDTH)
|
||||
.field("height", &CAMERA_PREVIEW_HEIGHT)
|
||||
.field("format", "RGBA")
|
||||
.field("width", CAMERA_PREVIEW_WIDTH)
|
||||
.field("height", CAMERA_PREVIEW_HEIGHT)
|
||||
.build(),
|
||||
));
|
||||
Ok((pipeline, appsink))
|
||||
|
||||
@ -3,7 +3,7 @@ use anyhow::Result;
|
||||
#[cfg(not(coverage))]
|
||||
use {
|
||||
super::clipboard::send_clipboard_to_remote,
|
||||
super::device_test::DeviceTestController,
|
||||
super::device_test::{DeviceTestController, DeviceTestKind},
|
||||
super::devices::DeviceCatalog,
|
||||
super::diagnostics::quality_probe_command,
|
||||
super::launcher_focus_signal_path,
|
||||
@ -121,11 +121,18 @@ pub fn run_gui_launcher(server_addr: String) -> Result<()> {
|
||||
let camera_combo_read = camera_combo.clone();
|
||||
camera_combo.connect_changed(move |_| {
|
||||
let selected = selected_combo_value(&camera_combo_read);
|
||||
let preview_was_running =
|
||||
tests.borrow_mut().is_running(DeviceTestKind::Camera);
|
||||
state.borrow_mut().select_camera(selected.clone());
|
||||
if let Err(err) = tests.borrow_mut().set_camera_selection(selected.as_deref()) {
|
||||
widgets
|
||||
.status_label
|
||||
.set_text(&format!("Camera preview update failed: {err}"));
|
||||
} else if preview_was_running {
|
||||
widgets.status_label.set_text(&format!(
|
||||
"Local camera preview switched to {}.",
|
||||
selected.as_deref().unwrap_or("auto")
|
||||
));
|
||||
}
|
||||
refresh_launcher_ui(&widgets, &state.borrow(), child_proc.borrow().is_some());
|
||||
refresh_test_buttons(&widgets, &mut tests.borrow_mut());
|
||||
@ -136,13 +143,20 @@ pub fn run_gui_launcher(server_addr: String) -> Result<()> {
|
||||
let state = Rc::clone(&state);
|
||||
let widgets = widgets.clone();
|
||||
let child_proc = Rc::clone(&child_proc);
|
||||
let tests = Rc::clone(&tests);
|
||||
let microphone_combo = microphone_combo.clone();
|
||||
let microphone_combo_read = microphone_combo.clone();
|
||||
microphone_combo.connect_changed(move |_| {
|
||||
state
|
||||
.borrow_mut()
|
||||
.select_microphone(selected_combo_value(µphone_combo_read));
|
||||
if tests.borrow_mut().is_running(DeviceTestKind::Microphone) {
|
||||
widgets.status_label.set_text(
|
||||
"Microphone selection changed. Restart Monitor Mic to audition the new input.",
|
||||
);
|
||||
}
|
||||
refresh_launcher_ui(&widgets, &state.borrow(), child_proc.borrow().is_some());
|
||||
refresh_test_buttons(&widgets, &mut tests.borrow_mut());
|
||||
});
|
||||
}
|
||||
|
||||
@ -150,13 +164,23 @@ pub fn run_gui_launcher(server_addr: String) -> Result<()> {
|
||||
let state = Rc::clone(&state);
|
||||
let widgets = widgets.clone();
|
||||
let child_proc = Rc::clone(&child_proc);
|
||||
let tests = Rc::clone(&tests);
|
||||
let speaker_combo = speaker_combo.clone();
|
||||
let speaker_combo_read = speaker_combo.clone();
|
||||
speaker_combo.connect_changed(move |_| {
|
||||
state
|
||||
.borrow_mut()
|
||||
.select_speaker(selected_combo_value(&speaker_combo_read));
|
||||
let speaker_running = tests.borrow_mut().is_running(DeviceTestKind::Speaker);
|
||||
let microphone_running =
|
||||
tests.borrow_mut().is_running(DeviceTestKind::Microphone);
|
||||
if speaker_running || microphone_running {
|
||||
widgets.status_label.set_text(
|
||||
"Speaker selection changed. Restart the local audio tests to hear the new output.",
|
||||
);
|
||||
}
|
||||
refresh_launcher_ui(&widgets, &state.borrow(), child_proc.borrow().is_some());
|
||||
refresh_test_buttons(&widgets, &mut tests.borrow_mut());
|
||||
});
|
||||
}
|
||||
|
||||
@ -203,10 +227,23 @@ pub fn run_gui_launcher(server_addr: String) -> Result<()> {
|
||||
Ok(child) => {
|
||||
*child_proc.borrow_mut() = Some(child);
|
||||
let _ = state.borrow_mut().start_remote();
|
||||
widgets_handle.status_label.set_text(&format!(
|
||||
"Relay started. Inputs are routed to {}.",
|
||||
routing_name(state.borrow().routing)
|
||||
));
|
||||
let routing = routing_name(state.borrow().routing);
|
||||
let power_mode = state.borrow().capture_power.mode.clone();
|
||||
let message = match power_mode.as_str() {
|
||||
"forced-off" => format!(
|
||||
"Relay started with inputs routed to {}, but capture is forced off. Return capture to Auto or Force On when you want remote video.",
|
||||
routing
|
||||
),
|
||||
"forced-on" => format!(
|
||||
"Relay started with inputs routed to {}. Capture is being held awake for staging.",
|
||||
routing
|
||||
),
|
||||
_ => format!(
|
||||
"Relay started with inputs routed to {}. Capture will wake automatically for previews and live session demand.",
|
||||
routing
|
||||
),
|
||||
};
|
||||
widgets_handle.status_label.set_text(&message);
|
||||
}
|
||||
Err(err) => {
|
||||
widgets_handle
|
||||
@ -231,7 +268,9 @@ pub fn run_gui_launcher(server_addr: String) -> Result<()> {
|
||||
stop_button.connect_clicked(move |_| {
|
||||
stop_child_process(&child_proc);
|
||||
let _ = state.borrow_mut().stop_remote();
|
||||
widgets_handle.status_label.set_text("Relay stopped.");
|
||||
widgets_handle
|
||||
.status_label
|
||||
.set_text("Relay stopped. Local staging remains available.");
|
||||
refresh_launcher_ui(&widgets_handle, &state.borrow(), false);
|
||||
});
|
||||
}
|
||||
@ -351,8 +390,8 @@ pub fn run_gui_launcher(server_addr: String) -> Result<()> {
|
||||
&widgets_handle,
|
||||
&mut tests.borrow_mut(),
|
||||
result,
|
||||
"Camera preview started inside the launcher.",
|
||||
"Camera preview stopped.",
|
||||
"Camera preview started inside the launcher. This stays local until you start the relay.",
|
||||
"Camera preview stopped. The selected camera stays staged for the next launch.",
|
||||
);
|
||||
});
|
||||
}
|
||||
@ -374,7 +413,7 @@ pub fn run_gui_launcher(server_addr: String) -> Result<()> {
|
||||
&widgets_handle,
|
||||
&mut tests.borrow_mut(),
|
||||
result,
|
||||
"Microphone monitor started.",
|
||||
"Microphone monitor started locally through the selected speaker.",
|
||||
"Microphone monitor stopped.",
|
||||
);
|
||||
});
|
||||
@ -394,7 +433,7 @@ pub fn run_gui_launcher(server_addr: String) -> Result<()> {
|
||||
&widgets_handle,
|
||||
&mut tests.borrow_mut(),
|
||||
result,
|
||||
"Speaker test tone started.",
|
||||
"Speaker tone started locally.",
|
||||
"Speaker test tone stopped.",
|
||||
);
|
||||
});
|
||||
@ -547,7 +586,9 @@ pub fn run_gui_launcher(server_addr: String) -> Result<()> {
|
||||
let child_running = reap_exited_child(&child_proc);
|
||||
if !child_running && state.borrow().remote_active {
|
||||
let _ = state.borrow_mut().stop_remote();
|
||||
widgets.status_label.set_text("Relay ended.");
|
||||
widgets
|
||||
.status_label
|
||||
.set_text("Relay ended. Local staging remains available.");
|
||||
}
|
||||
|
||||
let next_state_marker = path_marker(input_state_path.as_path());
|
||||
@ -593,9 +634,9 @@ pub fn run_gui_launcher(server_addr: String) -> Result<()> {
|
||||
let mode = power.mode.clone();
|
||||
state.borrow_mut().set_capture_power(power);
|
||||
widgets.status_label.set_text(match mode.as_str() {
|
||||
"forced-on" => "Capture feeds forced on.",
|
||||
"forced-off" => "Capture feeds forced off.",
|
||||
_ => "Capture feeds returned to automatic mode.",
|
||||
"forced-on" => "Capture feeds forced on. Remote eyes stay awake even if previews or the relay stop.",
|
||||
"forced-off" => "Capture feeds forced off. Remote eye previews and session video stay dark until you switch back.",
|
||||
_ => "Capture feeds returned to automatic mode. Live previews and relay demand will wake them as needed.",
|
||||
});
|
||||
}
|
||||
PowerMessage::Command(Err(err)) => {
|
||||
|
||||
@ -39,6 +39,10 @@ pub struct LauncherWidgets {
|
||||
pub status_label: gtk::Label,
|
||||
pub summary: SummaryWidgets,
|
||||
pub power_detail: gtk::Label,
|
||||
pub launch_plan_title: gtk::Label,
|
||||
pub launch_plan_summary: gtk::Label,
|
||||
pub launch_plan_detail: gtk::Label,
|
||||
pub local_test_detail: gtk::Label,
|
||||
pub display_panes: [DisplayPaneWidgets; 2],
|
||||
pub start_button: gtk::Button,
|
||||
pub stop_button: gtk::Button,
|
||||
@ -151,8 +155,14 @@ pub fn build_launcher_view(
|
||||
));
|
||||
let start_button = gtk::Button::with_label("Start Relay");
|
||||
start_button.add_css_class("suggested-action");
|
||||
start_button.set_tooltip_text(Some(
|
||||
"Launch the relay using the staged devices and current input routing.",
|
||||
));
|
||||
let stop_button = gtk::Button::with_label("Stop Relay");
|
||||
stop_button.add_css_class("destructive-action");
|
||||
stop_button.set_tooltip_text(Some(
|
||||
"Stop the live relay session. Local staging and previews stay available.",
|
||||
));
|
||||
server_row.append(&server_entry);
|
||||
server_row.append(&start_button);
|
||||
server_row.append(&stop_button);
|
||||
@ -225,7 +235,7 @@ pub fn build_launcher_view(
|
||||
|
||||
let (devices_panel, devices_body) = build_panel("Device Staging");
|
||||
let devices_intro = gtk::Label::new(Some(
|
||||
"Lock in the exact local camera, microphone, and speaker you want before you launch the relay.",
|
||||
"Choose the exact local camera, microphone, and speaker the next relay launch should inherit.",
|
||||
));
|
||||
devices_intro.add_css_class("dim-label");
|
||||
devices_intro.set_wrap(true);
|
||||
@ -320,6 +330,37 @@ pub fn build_launcher_view(
|
||||
devices_body.append(&preview_shell);
|
||||
sidebar.append(&devices_panel);
|
||||
|
||||
let (plan_panel, plan_body) = build_panel("Launch Plan");
|
||||
let launch_plan_title = gtk::Label::new(Some("Stage locally, then start the relay."));
|
||||
launch_plan_title.add_css_class("title-4");
|
||||
launch_plan_title.set_halign(gtk::Align::Start);
|
||||
launch_plan_title.set_wrap(true);
|
||||
let launch_plan_summary =
|
||||
gtk::Label::new(Some("Camera: auto\nMicrophone: auto\nSpeaker: auto"));
|
||||
launch_plan_summary.add_css_class("launch-plan-summary");
|
||||
launch_plan_summary.set_halign(gtk::Align::Start);
|
||||
launch_plan_summary.set_xalign(0.0);
|
||||
launch_plan_summary.set_wrap(true);
|
||||
let local_test_detail = gtk::Label::new(Some(
|
||||
"Local checks are idle. Use Start Preview, Monitor Mic, or Play Tone before you launch.",
|
||||
));
|
||||
local_test_detail.add_css_class("dim-label");
|
||||
local_test_detail.set_halign(gtk::Align::Start);
|
||||
local_test_detail.set_xalign(0.0);
|
||||
local_test_detail.set_wrap(true);
|
||||
let launch_plan_detail = gtk::Label::new(Some(
|
||||
"Automatic capture mode will wake the remote feeds when previews or the live relay ask for them.",
|
||||
));
|
||||
launch_plan_detail.add_css_class("dim-label");
|
||||
launch_plan_detail.set_halign(gtk::Align::Start);
|
||||
launch_plan_detail.set_xalign(0.0);
|
||||
launch_plan_detail.set_wrap(true);
|
||||
plan_body.append(&launch_plan_title);
|
||||
plan_body.append(&launch_plan_summary);
|
||||
plan_body.append(&local_test_detail);
|
||||
plan_body.append(&launch_plan_detail);
|
||||
sidebar.append(&plan_panel);
|
||||
|
||||
let (actions_panel, actions_body) = build_panel("Remote Actions");
|
||||
let actions_row = gtk::Box::new(gtk::Orientation::Horizontal, 8);
|
||||
let clipboard_button = gtk::Button::with_label("Send Clipboard");
|
||||
@ -336,11 +377,18 @@ pub fn build_launcher_view(
|
||||
sidebar.append(&actions_panel);
|
||||
|
||||
let stage_header = gtk::Box::new(gtk::Orientation::Horizontal, 8);
|
||||
let stage_title = gtk::Label::new(Some("Remote Eyes"));
|
||||
let stage_title = gtk::Label::new(Some("Remote Eye Feeds"));
|
||||
stage_title.add_css_class("title-4");
|
||||
stage_title.set_halign(gtk::Align::Start);
|
||||
stage_header.append(&stage_title);
|
||||
let stage_note = gtk::Label::new(Some(
|
||||
"These are the live server-side eye feeds. In Auto mode, open eye previews and active relay sessions count as capture demand.",
|
||||
));
|
||||
stage_note.add_css_class("dim-label");
|
||||
stage_note.set_wrap(true);
|
||||
stage_note.set_xalign(0.0);
|
||||
stage.append(&stage_header);
|
||||
stage.append(&stage_note);
|
||||
|
||||
let display_row = gtk::Box::new(gtk::Orientation::Horizontal, 16);
|
||||
display_row.set_hexpand(true);
|
||||
@ -387,6 +435,10 @@ pub fn build_launcher_view(
|
||||
shortcut_value,
|
||||
},
|
||||
power_detail,
|
||||
launch_plan_title,
|
||||
launch_plan_summary,
|
||||
launch_plan_detail,
|
||||
local_test_detail,
|
||||
display_panes: [left_pane.clone(), right_pane.clone()],
|
||||
start_button: start_button.clone(),
|
||||
stop_button: stop_button.clone(),
|
||||
@ -482,6 +534,13 @@ pub fn install_css(window: >k::ApplicationWindow) {
|
||||
label.status-line {
|
||||
opacity: 0.88;
|
||||
}
|
||||
label.launch-plan-summary {
|
||||
font-family: monospace;
|
||||
background: rgba(255, 255, 255, 0.03);
|
||||
border: 1px solid rgba(255, 255, 255, 0.08);
|
||||
border-radius: 14px;
|
||||
padding: 12px;
|
||||
}
|
||||
entry.server-entry {
|
||||
min-height: 38px;
|
||||
}
|
||||
@ -489,6 +548,11 @@ pub fn install_css(window: >k::ApplicationWindow) {
|
||||
min-height: 36px;
|
||||
padding: 0 14px;
|
||||
}
|
||||
button.pill-toggle-active {
|
||||
background: rgba(91, 179, 162, 0.2);
|
||||
border-color: rgba(91, 179, 162, 0.45);
|
||||
font-weight: 700;
|
||||
}
|
||||
"#,
|
||||
);
|
||||
if let Some(display) = gtk::gdk::Display::default() {
|
||||
|
||||
@ -52,7 +52,21 @@ pub fn refresh_launcher_ui(widgets: &LauncherWidgets, state: &LauncherState, chi
|
||||
widgets
|
||||
.power_detail
|
||||
.set_text(&capture_power_detail(&state.capture_power));
|
||||
widgets
|
||||
.launch_plan_title
|
||||
.set_text(&launch_plan_title(state, child_running));
|
||||
widgets
|
||||
.launch_plan_summary
|
||||
.set_text(&launch_plan_summary(state));
|
||||
widgets
|
||||
.launch_plan_detail
|
||||
.set_text(&launch_plan_detail(state, child_running));
|
||||
|
||||
widgets.start_button.set_label(if child_running {
|
||||
"Relay Running"
|
||||
} else {
|
||||
"Start Relay"
|
||||
});
|
||||
widgets.start_button.set_sensitive(!child_running);
|
||||
widgets.stop_button.set_sensitive(child_running);
|
||||
widgets.clipboard_button.set_sensitive(child_running);
|
||||
@ -71,6 +85,7 @@ pub fn refresh_launcher_ui(widgets: &LauncherWidgets, state: &LauncherState, chi
|
||||
widgets.power_off_button.set_sensitive(
|
||||
power_available && !matches!(state.capture_power.mode.as_str(), "forced-off"),
|
||||
);
|
||||
sync_power_mode_button_styles(widgets, state.capture_power.mode.as_str());
|
||||
|
||||
for monitor_id in 0..2 {
|
||||
refresh_display_pane(
|
||||
@ -81,27 +96,32 @@ pub fn refresh_launcher_ui(widgets: &LauncherWidgets, state: &LauncherState, chi
|
||||
}
|
||||
|
||||
pub fn refresh_test_buttons(widgets: &LauncherWidgets, tests: &mut DeviceTestController) {
|
||||
widgets
|
||||
.camera_test_button
|
||||
.set_label(if tests.is_running(DeviceTestKind::Camera) {
|
||||
"Stop Preview"
|
||||
} else {
|
||||
"Start Preview"
|
||||
});
|
||||
let camera_running = tests.is_running(DeviceTestKind::Camera);
|
||||
let microphone_running = tests.is_running(DeviceTestKind::Microphone);
|
||||
let speaker_running = tests.is_running(DeviceTestKind::Speaker);
|
||||
|
||||
widgets.camera_test_button.set_label(if camera_running {
|
||||
"Stop Preview"
|
||||
} else {
|
||||
"Start Preview"
|
||||
});
|
||||
widgets
|
||||
.microphone_test_button
|
||||
.set_label(if tests.is_running(DeviceTestKind::Microphone) {
|
||||
.set_label(if microphone_running {
|
||||
"Stop Monitor"
|
||||
} else {
|
||||
"Monitor Mic"
|
||||
});
|
||||
widgets
|
||||
.speaker_test_button
|
||||
.set_label(if tests.is_running(DeviceTestKind::Speaker) {
|
||||
"Stop Tone"
|
||||
} else {
|
||||
"Play Tone"
|
||||
});
|
||||
widgets.speaker_test_button.set_label(if speaker_running {
|
||||
"Stop Tone"
|
||||
} else {
|
||||
"Play Tone"
|
||||
});
|
||||
widgets.local_test_detail.set_text(&local_test_detail(
|
||||
camera_running,
|
||||
microphone_running,
|
||||
speaker_running,
|
||||
));
|
||||
}
|
||||
|
||||
pub fn update_test_action_result(
|
||||
@ -140,7 +160,7 @@ pub fn open_popout_window(
|
||||
|
||||
let window = gtk::ApplicationWindow::builder()
|
||||
.application(app)
|
||||
.title(&format!(
|
||||
.title(format!(
|
||||
"Lesavka {}",
|
||||
widgets.display_panes[monitor_id].title
|
||||
))
|
||||
@ -310,6 +330,137 @@ pub fn capture_power_detail(power: &CapturePowerStatus) -> String {
|
||||
}
|
||||
}
|
||||
|
||||
/// Highlights the currently active capture mode so it reads like a segmented control.
|
||||
fn sync_power_mode_button_styles(widgets: &LauncherWidgets, mode: &str) {
|
||||
for button in [
|
||||
&widgets.power_auto_button,
|
||||
&widgets.power_on_button,
|
||||
&widgets.power_off_button,
|
||||
] {
|
||||
button.remove_css_class("pill-toggle-active");
|
||||
}
|
||||
match mode {
|
||||
"forced-on" => widgets.power_on_button.add_css_class("pill-toggle-active"),
|
||||
"forced-off" => widgets.power_off_button.add_css_class("pill-toggle-active"),
|
||||
_ => widgets
|
||||
.power_auto_button
|
||||
.add_css_class("pill-toggle-active"),
|
||||
}
|
||||
}
|
||||
|
||||
/// Summarizes the current staging state as a short operator-facing heading.
|
||||
fn launch_plan_title(state: &LauncherState, child_running: bool) -> String {
|
||||
if child_running || state.remote_active {
|
||||
return match state.capture_power.mode.as_str() {
|
||||
"forced-off" => "Relay live, but capture is intentionally dark.".to_string(),
|
||||
"forced-on" => "Relay live with capture held awake.".to_string(),
|
||||
_ => "Relay live with automatic capture management.".to_string(),
|
||||
};
|
||||
}
|
||||
|
||||
match state.capture_power.mode.as_str() {
|
||||
"forced-off" => "Staging mode is holding capture off.".to_string(),
|
||||
"forced-on" => "Capture is pre-warmed for staging.".to_string(),
|
||||
_ => "Stage locally, then start the relay.".to_string(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Shows the exact devices the next relay launch will inherit.
|
||||
fn launch_plan_summary(state: &LauncherState) -> String {
|
||||
format!(
|
||||
"Camera: {}\nMicrophone: {}\nSpeaker: {}",
|
||||
selected_device_label(state.devices.camera.as_deref()),
|
||||
selected_device_label(state.devices.microphone.as_deref()),
|
||||
selected_device_label(state.devices.speaker.as_deref())
|
||||
)
|
||||
}
|
||||
|
||||
/// Explains the consequence of the current capture and session state.
|
||||
fn launch_plan_detail(state: &LauncherState, child_running: bool) -> String {
|
||||
if child_running || state.remote_active {
|
||||
return match state.capture_power.mode.as_str() {
|
||||
"forced-off" => format!(
|
||||
"Inputs are routed to {}. Return capture to Auto or Force On when you want the remote eyes and session video to wake up.",
|
||||
capitalize(routing_name(state.routing))
|
||||
),
|
||||
"forced-on" => format!(
|
||||
"Inputs are routed to {}. The relay host is keeping the capture feeds up even without preview demand.",
|
||||
capitalize(routing_name(state.routing))
|
||||
),
|
||||
_ => format!(
|
||||
"Inputs are routed to {}. Live eye previews and session demand will wake capture automatically as needed.",
|
||||
capitalize(routing_name(state.routing))
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
if !state.capture_power.available {
|
||||
return format!(
|
||||
"Capture power status from {} is unavailable right now. You can still stage devices locally before you try the relay host again.",
|
||||
state.capture_power.unit
|
||||
);
|
||||
}
|
||||
|
||||
match state.capture_power.mode.as_str() {
|
||||
"forced-off" => {
|
||||
"Remote eye previews and the next relay session will stay dark until you return to Auto or Force On."
|
||||
.to_string()
|
||||
}
|
||||
"forced-on" => {
|
||||
"The relay host is already holding capture awake, which is useful for preflight framing checks before the session starts."
|
||||
.to_string()
|
||||
}
|
||||
_ => {
|
||||
"Automatic capture mode wakes the remote feeds only while eye previews or the live relay session actually need them."
|
||||
.to_string()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Reports which local staging checks are active right now.
|
||||
fn local_test_detail(
|
||||
camera_running: bool,
|
||||
microphone_running: bool,
|
||||
speaker_running: bool,
|
||||
) -> String {
|
||||
let mut active = Vec::new();
|
||||
if camera_running {
|
||||
active.push("camera preview");
|
||||
}
|
||||
if microphone_running {
|
||||
active.push("mic monitor");
|
||||
}
|
||||
if speaker_running {
|
||||
active.push("speaker tone");
|
||||
}
|
||||
|
||||
if active.is_empty() {
|
||||
"Local checks are idle. Use Start Preview, Monitor Mic, or Play Tone before you launch."
|
||||
.to_string()
|
||||
} else {
|
||||
format!(
|
||||
"Local checks running: {}. Stop them whenever staging is complete.",
|
||||
active.join(", ")
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/// Formats a selected device for the launch-plan summary.
|
||||
fn selected_device_label(value: Option<&str>) -> String {
|
||||
value
|
||||
.map(compact_device_name)
|
||||
.unwrap_or_else(|| "auto".to_string())
|
||||
}
|
||||
|
||||
/// Prefer the basename for `/dev/...` entries while keeping Pulse names intact.
|
||||
fn compact_device_name(value: &str) -> String {
|
||||
let trimmed = value.trim();
|
||||
if trimmed.is_empty() {
|
||||
return "auto".to_string();
|
||||
}
|
||||
trimmed.rsplit('/').next().unwrap_or(trimmed).to_string()
|
||||
}
|
||||
|
||||
pub fn capitalize(value: &str) -> String {
|
||||
let mut chars = value.chars();
|
||||
match chars.next() {
|
||||
@ -455,3 +606,55 @@ pub fn next_input_routing(routing: InputRouting) -> InputRouting {
|
||||
InputRouting::Local => InputRouting::Remote,
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn launch_plan_summary_compacts_selected_devices() {
|
||||
let mut state = LauncherState::new();
|
||||
state.select_camera(Some(
|
||||
"/dev/v4l/by-id/usb-Logitech_C920-video-index0".to_string(),
|
||||
));
|
||||
state.select_microphone(Some("alsa_input.usb-focusrite".to_string()));
|
||||
state.select_speaker(Some("alsa_output.studio".to_string()));
|
||||
|
||||
let summary = launch_plan_summary(&state);
|
||||
assert!(summary.contains("usb-Logitech_C920-video-index0"));
|
||||
assert!(summary.contains("alsa_input.usb-focusrite"));
|
||||
assert!(summary.contains("alsa_output.studio"));
|
||||
assert!(!summary.contains("/dev/v4l/by-id/"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn launch_plan_detail_calls_out_forced_off_sessions() {
|
||||
let mut state = LauncherState::new();
|
||||
state.set_capture_power(CapturePowerStatus {
|
||||
available: true,
|
||||
enabled: false,
|
||||
unit: "relay.service".to_string(),
|
||||
detail: "inactive/dead".to_string(),
|
||||
active_leases: 0,
|
||||
mode: "forced-off".to_string(),
|
||||
});
|
||||
state.start_remote();
|
||||
|
||||
let detail = launch_plan_detail(&state, true);
|
||||
assert!(detail.contains("Return capture to Auto or Force On"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn local_test_detail_mentions_idle_and_running_modes() {
|
||||
assert!(local_test_detail(false, false, false).contains("idle"));
|
||||
let running = local_test_detail(true, true, false);
|
||||
assert!(running.contains("camera preview"));
|
||||
assert!(running.contains("mic monitor"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn compact_device_name_prefers_basename_when_available() {
|
||||
assert_eq!(compact_device_name("/dev/video0"), "video0");
|
||||
assert_eq!(compact_device_name("alsa_input.usb"), "alsa_input.usb");
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,273 +1,272 @@
|
||||
{
|
||||
"files": {
|
||||
"client/src/app.rs": {
|
||||
"loc": 546,
|
||||
"clippy_warnings": 42,
|
||||
"doc_debt": 10
|
||||
"doc_debt": 10,
|
||||
"loc": 546
|
||||
},
|
||||
"client/src/app_support.rs": {
|
||||
"loc": 128,
|
||||
"clippy_warnings": 0,
|
||||
"doc_debt": 3,
|
||||
"clippy_warnings": 0
|
||||
"loc": 128
|
||||
},
|
||||
"client/src/handshake.rs": {
|
||||
"loc": 194,
|
||||
"clippy_warnings": 0,
|
||||
"doc_debt": 3,
|
||||
"clippy_warnings": 0
|
||||
"loc": 194
|
||||
},
|
||||
"client/src/input/camera.rs": {
|
||||
"loc": 372,
|
||||
"clippy_warnings": 38,
|
||||
"doc_debt": 7
|
||||
"doc_debt": 7,
|
||||
"loc": 372
|
||||
},
|
||||
"client/src/input/inputs.rs": {
|
||||
"loc": 673,
|
||||
"clippy_warnings": 42,
|
||||
"doc_debt": 16
|
||||
"doc_debt": 16,
|
||||
"loc": 673
|
||||
},
|
||||
"client/src/input/keyboard.rs": {
|
||||
"loc": 580,
|
||||
"clippy_warnings": 24,
|
||||
"doc_debt": 18
|
||||
"doc_debt": 18,
|
||||
"loc": 580
|
||||
},
|
||||
"client/src/input/keymap.rs": {
|
||||
"loc": 196,
|
||||
"clippy_warnings": 8,
|
||||
"doc_debt": 0
|
||||
"doc_debt": 0,
|
||||
"loc": 196
|
||||
},
|
||||
"client/src/input/microphone.rs": {
|
||||
"loc": 166,
|
||||
"clippy_warnings": 17,
|
||||
"doc_debt": 2
|
||||
"doc_debt": 2,
|
||||
"loc": 166
|
||||
},
|
||||
"client/src/input/mod.rs": {
|
||||
"loc": 8,
|
||||
"clippy_warnings": 0,
|
||||
"doc_debt": 0,
|
||||
"clippy_warnings": 0
|
||||
"loc": 8
|
||||
},
|
||||
"client/src/input/mouse.rs": {
|
||||
"loc": 317,
|
||||
"clippy_warnings": 40,
|
||||
"doc_debt": 8
|
||||
"doc_debt": 8,
|
||||
"loc": 317
|
||||
},
|
||||
"client/src/launcher/clipboard.rs": {
|
||||
"loc": 177,
|
||||
"clippy_warnings": 2,
|
||||
"doc_debt": 1
|
||||
"doc_debt": 1,
|
||||
"loc": 177
|
||||
},
|
||||
"client/src/launcher/device_test.rs": {
|
||||
"loc": 454,
|
||||
"clippy_warnings": 36,
|
||||
"doc_debt": 20
|
||||
"clippy_warnings": 22,
|
||||
"doc_debt": 20,
|
||||
"loc": 446
|
||||
},
|
||||
"client/src/launcher/devices.rs": {
|
||||
"loc": 158,
|
||||
"clippy_warnings": 6,
|
||||
"doc_debt": 3
|
||||
"doc_debt": 3,
|
||||
"loc": 158
|
||||
},
|
||||
"client/src/launcher/diagnostics.rs": {
|
||||
"loc": 175,
|
||||
"clippy_warnings": 17,
|
||||
"doc_debt": 3
|
||||
"doc_debt": 3,
|
||||
"loc": 175
|
||||
},
|
||||
"client/src/launcher/mod.rs": {
|
||||
"loc": 195,
|
||||
"clippy_warnings": 6,
|
||||
"doc_debt": 4
|
||||
"doc_debt": 4,
|
||||
"loc": 195
|
||||
},
|
||||
"client/src/launcher/power.rs": {
|
||||
"loc": 69,
|
||||
"clippy_warnings": 0,
|
||||
"doc_debt": 1,
|
||||
"clippy_warnings": 0
|
||||
"loc": 69
|
||||
},
|
||||
"client/src/launcher/preview.rs": {
|
||||
"loc": 293,
|
||||
"clippy_warnings": 20,
|
||||
"doc_debt": 6
|
||||
"doc_debt": 6,
|
||||
"loc": 293
|
||||
},
|
||||
"client/src/launcher/state.rs": {
|
||||
"loc": 360,
|
||||
"clippy_warnings": 14,
|
||||
"doc_debt": 15
|
||||
"doc_debt": 15,
|
||||
"loc": 360
|
||||
},
|
||||
"client/src/launcher/ui.rs": {
|
||||
"loc": 654,
|
||||
"clippy_warnings": 4,
|
||||
"doc_debt": 1
|
||||
"clippy_warnings": 10,
|
||||
"doc_debt": 1,
|
||||
"loc": 695
|
||||
},
|
||||
"client/src/launcher/ui_components.rs": {
|
||||
"loc": 615,
|
||||
"clippy_warnings": 8,
|
||||
"doc_debt": 4
|
||||
"doc_debt": 4,
|
||||
"loc": 679
|
||||
},
|
||||
"client/src/launcher/ui_runtime.rs": {
|
||||
"loc": 457,
|
||||
"clippy_warnings": 10,
|
||||
"doc_debt": 18
|
||||
"doc_debt": 20,
|
||||
"loc": 660
|
||||
},
|
||||
"client/src/layout.rs": {
|
||||
"loc": 78,
|
||||
"clippy_warnings": 6,
|
||||
"doc_debt": 0
|
||||
"doc_debt": 0,
|
||||
"loc": 78
|
||||
},
|
||||
"client/src/lib.rs": {
|
||||
"loc": 14,
|
||||
"clippy_warnings": 0,
|
||||
"doc_debt": 0,
|
||||
"clippy_warnings": 0
|
||||
"loc": 14
|
||||
},
|
||||
"client/src/main.rs": {
|
||||
"loc": 96,
|
||||
"clippy_warnings": 2,
|
||||
"doc_debt": 2
|
||||
"doc_debt": 2,
|
||||
"loc": 96
|
||||
},
|
||||
"client/src/output/audio.rs": {
|
||||
"loc": 195,
|
||||
"clippy_warnings": 37,
|
||||
"doc_debt": 5
|
||||
"doc_debt": 5,
|
||||
"loc": 195
|
||||
},
|
||||
"client/src/output/display.rs": {
|
||||
"loc": 81,
|
||||
"clippy_warnings": 0,
|
||||
"doc_debt": 0,
|
||||
"clippy_warnings": 0
|
||||
"loc": 81
|
||||
},
|
||||
"client/src/output/layout.rs": {
|
||||
"loc": 155,
|
||||
"clippy_warnings": 4,
|
||||
"doc_debt": 2
|
||||
"doc_debt": 2,
|
||||
"loc": 155
|
||||
},
|
||||
"client/src/output/mod.rs": {
|
||||
"loc": 6,
|
||||
"clippy_warnings": 0,
|
||||
"doc_debt": 0,
|
||||
"clippy_warnings": 0
|
||||
"loc": 6
|
||||
},
|
||||
"client/src/output/video.rs": {
|
||||
"loc": 547,
|
||||
"clippy_warnings": 36,
|
||||
"doc_debt": 4
|
||||
"doc_debt": 4,
|
||||
"loc": 547
|
||||
},
|
||||
"client/src/paste.rs": {
|
||||
"loc": 46,
|
||||
"clippy_warnings": 2,
|
||||
"doc_debt": 1
|
||||
"doc_debt": 1,
|
||||
"loc": 46
|
||||
},
|
||||
"common/src/bin/cli.rs": {
|
||||
"loc": 3,
|
||||
"clippy_warnings": 0,
|
||||
"doc_debt": 0,
|
||||
"clippy_warnings": 0
|
||||
"loc": 3
|
||||
},
|
||||
"common/src/cli.rs": {
|
||||
"loc": 22,
|
||||
"clippy_warnings": 0,
|
||||
"doc_debt": 0,
|
||||
"clippy_warnings": 0
|
||||
"loc": 22
|
||||
},
|
||||
"common/src/hid.rs": {
|
||||
"loc": 134,
|
||||
"clippy_warnings": 0,
|
||||
"doc_debt": 2,
|
||||
"clippy_warnings": 0
|
||||
"loc": 134
|
||||
},
|
||||
"common/src/lib.rs": {
|
||||
"loc": 22,
|
||||
"clippy_warnings": 0,
|
||||
"doc_debt": 0,
|
||||
"clippy_warnings": 0
|
||||
"loc": 22
|
||||
},
|
||||
"common/src/paste.rs": {
|
||||
"loc": 95,
|
||||
"clippy_warnings": 0,
|
||||
"doc_debt": 2,
|
||||
"clippy_warnings": 0
|
||||
"loc": 95
|
||||
},
|
||||
"server/src/audio.rs": {
|
||||
"loc": 386,
|
||||
"clippy_warnings": 37,
|
||||
"doc_debt": 7
|
||||
"doc_debt": 7,
|
||||
"loc": 386
|
||||
},
|
||||
"server/src/bin/lesavka-uvc.real.inc": {
|
||||
"clippy_warnings": 31,
|
||||
"doc_debt": 0
|
||||
"clippy_warnings": 31
|
||||
},
|
||||
"server/src/bin/lesavka-uvc.rs": {
|
||||
"loc": 710,
|
||||
"clippy_warnings": 0,
|
||||
"doc_debt": 17,
|
||||
"clippy_warnings": 0
|
||||
"loc": 710
|
||||
},
|
||||
"server/src/camera.rs": {
|
||||
"loc": 392,
|
||||
"clippy_warnings": 12,
|
||||
"doc_debt": 11
|
||||
"doc_debt": 11,
|
||||
"loc": 392
|
||||
},
|
||||
"server/src/camera_runtime.rs": {
|
||||
"loc": 200,
|
||||
"clippy_warnings": 10,
|
||||
"doc_debt": 5
|
||||
"doc_debt": 5,
|
||||
"loc": 200
|
||||
},
|
||||
"server/src/capture_power.rs": {
|
||||
"loc": 338,
|
||||
"clippy_warnings": 10,
|
||||
"doc_debt": 7
|
||||
"doc_debt": 7,
|
||||
"loc": 338
|
||||
},
|
||||
"server/src/gadget.rs": {
|
||||
"loc": 327,
|
||||
"clippy_warnings": 30,
|
||||
"doc_debt": 7
|
||||
"doc_debt": 7,
|
||||
"loc": 327
|
||||
},
|
||||
"server/src/handshake.rs": {
|
||||
"loc": 40,
|
||||
"clippy_warnings": 2,
|
||||
"doc_debt": 1
|
||||
"doc_debt": 1,
|
||||
"loc": 40
|
||||
},
|
||||
"server/src/lib.rs": {
|
||||
"loc": 14,
|
||||
"clippy_warnings": 0,
|
||||
"doc_debt": 0,
|
||||
"clippy_warnings": 0
|
||||
"loc": 14
|
||||
},
|
||||
"server/src/main.rs": {
|
||||
"loc": 572,
|
||||
"clippy_warnings": 10,
|
||||
"doc_debt": 13
|
||||
"doc_debt": 13,
|
||||
"loc": 572
|
||||
},
|
||||
"server/src/paste.rs": {
|
||||
"loc": 207,
|
||||
"clippy_warnings": 6,
|
||||
"doc_debt": 3
|
||||
"doc_debt": 3,
|
||||
"loc": 207
|
||||
},
|
||||
"server/src/runtime_support.rs": {
|
||||
"loc": 387,
|
||||
"clippy_warnings": 14,
|
||||
"doc_debt": 8
|
||||
"doc_debt": 8,
|
||||
"loc": 387
|
||||
},
|
||||
"server/src/uvc_control/model.rs": {
|
||||
"loc": 510,
|
||||
"clippy_warnings": 0,
|
||||
"doc_debt": 11,
|
||||
"clippy_warnings": 0
|
||||
"loc": 510
|
||||
},
|
||||
"server/src/uvc_control/protocol.rs": {
|
||||
"loc": 403,
|
||||
"clippy_warnings": 0,
|
||||
"doc_debt": 11,
|
||||
"clippy_warnings": 0
|
||||
"loc": 403
|
||||
},
|
||||
"server/src/uvc_runtime.rs": {
|
||||
"loc": 241,
|
||||
"clippy_warnings": 4,
|
||||
"doc_debt": 5
|
||||
"doc_debt": 5,
|
||||
"loc": 241
|
||||
},
|
||||
"server/src/video.rs": {
|
||||
"loc": 344,
|
||||
"clippy_warnings": 25,
|
||||
"doc_debt": 2
|
||||
"doc_debt": 2,
|
||||
"loc": 343
|
||||
},
|
||||
"server/src/video_sinks.rs": {
|
||||
"loc": 559,
|
||||
"clippy_warnings": 78,
|
||||
"doc_debt": 11
|
||||
"doc_debt": 11,
|
||||
"loc": 559
|
||||
},
|
||||
"server/src/video_support.rs": {
|
||||
"loc": 236,
|
||||
"clippy_warnings": 8,
|
||||
"doc_debt": 6
|
||||
"doc_debt": 6,
|
||||
"loc": 236
|
||||
},
|
||||
"testing/src/lib.rs": {
|
||||
"loc": 10,
|
||||
"clippy_warnings": 0,
|
||||
"doc_debt": 0,
|
||||
"clippy_warnings": 0
|
||||
"loc": 10
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,168 +1,168 @@
|
||||
{
|
||||
"files": {
|
||||
"client/src/app.rs": {
|
||||
"loc": 546,
|
||||
"line_percent": 95.1219512195122
|
||||
"line_percent": 95.1219512195122,
|
||||
"loc": 546
|
||||
},
|
||||
"client/src/app_support.rs": {
|
||||
"loc": 128,
|
||||
"line_percent": 100.0
|
||||
"line_percent": 100.0,
|
||||
"loc": 128
|
||||
},
|
||||
"client/src/handshake.rs": {
|
||||
"loc": 194,
|
||||
"line_percent": 96.15384615384616
|
||||
"line_percent": 96.15384615384616,
|
||||
"loc": 194
|
||||
},
|
||||
"client/src/input/camera.rs": {
|
||||
"loc": 372,
|
||||
"line_percent": 98.42931937172776
|
||||
"line_percent": 98.42931937172776,
|
||||
"loc": 372
|
||||
},
|
||||
"client/src/input/inputs.rs": {
|
||||
"loc": 673,
|
||||
"line_percent": 97.55102040816327
|
||||
"line_percent": 97.55102040816327,
|
||||
"loc": 673
|
||||
},
|
||||
"client/src/input/keyboard.rs": {
|
||||
"loc": 580,
|
||||
"line_percent": 95.9409594095941
|
||||
"line_percent": 95.9409594095941,
|
||||
"loc": 580
|
||||
},
|
||||
"client/src/input/keymap.rs": {
|
||||
"loc": 196,
|
||||
"line_percent": 100.0
|
||||
"line_percent": 100.0,
|
||||
"loc": 196
|
||||
},
|
||||
"client/src/input/microphone.rs": {
|
||||
"loc": 166,
|
||||
"line_percent": 95.94594594594594
|
||||
"line_percent": 95.94594594594594,
|
||||
"loc": 166
|
||||
},
|
||||
"client/src/input/mouse.rs": {
|
||||
"loc": 317,
|
||||
"line_percent": 97.32142857142857
|
||||
"line_percent": 97.32142857142857,
|
||||
"loc": 317
|
||||
},
|
||||
"client/src/launcher/clipboard.rs": {
|
||||
"loc": 177,
|
||||
"line_percent": 98.0
|
||||
"line_percent": 98.0,
|
||||
"loc": 177
|
||||
},
|
||||
"client/src/launcher/devices.rs": {
|
||||
"loc": 158,
|
||||
"line_percent": 98.13084112149532
|
||||
"line_percent": 98.13084112149532,
|
||||
"loc": 158
|
||||
},
|
||||
"client/src/launcher/diagnostics.rs": {
|
||||
"loc": 175,
|
||||
"line_percent": 97.14285714285714
|
||||
"line_percent": 97.14285714285714,
|
||||
"loc": 175
|
||||
},
|
||||
"client/src/launcher/mod.rs": {
|
||||
"loc": 195,
|
||||
"line_percent": 95.23809523809523
|
||||
"line_percent": 95.23809523809523,
|
||||
"loc": 195
|
||||
},
|
||||
"client/src/launcher/state.rs": {
|
||||
"loc": 360,
|
||||
"line_percent": 98.29059829059828
|
||||
"line_percent": 98.29059829059828,
|
||||
"loc": 360
|
||||
},
|
||||
"client/src/launcher/ui.rs": {
|
||||
"loc": 654,
|
||||
"line_percent": 100.0
|
||||
"line_percent": 100.0,
|
||||
"loc": 695
|
||||
},
|
||||
"client/src/layout.rs": {
|
||||
"loc": 78,
|
||||
"line_percent": 97.72727272727273
|
||||
"line_percent": 97.72727272727273,
|
||||
"loc": 78
|
||||
},
|
||||
"client/src/main.rs": {
|
||||
"loc": 96,
|
||||
"line_percent": 97.0873786407767
|
||||
"line_percent": 97.0873786407767,
|
||||
"loc": 96
|
||||
},
|
||||
"client/src/output/audio.rs": {
|
||||
"loc": 195,
|
||||
"line_percent": 98.78048780487805
|
||||
"line_percent": 98.78048780487805,
|
||||
"loc": 195
|
||||
},
|
||||
"client/src/output/display.rs": {
|
||||
"loc": 81,
|
||||
"line_percent": 97.61904761904762
|
||||
"line_percent": 97.61904761904762,
|
||||
"loc": 81
|
||||
},
|
||||
"client/src/output/layout.rs": {
|
||||
"loc": 155,
|
||||
"line_percent": 98.9795918367347
|
||||
"line_percent": 98.9795918367347,
|
||||
"loc": 155
|
||||
},
|
||||
"client/src/output/video.rs": {
|
||||
"loc": 547,
|
||||
"line_percent": 96.22641509433963
|
||||
"line_percent": 96.22641509433963,
|
||||
"loc": 547
|
||||
},
|
||||
"client/src/paste.rs": {
|
||||
"loc": 46,
|
||||
"line_percent": 96.29629629629629
|
||||
"line_percent": 96.29629629629629,
|
||||
"loc": 46
|
||||
},
|
||||
"common/src/bin/cli.rs": {
|
||||
"loc": 3,
|
||||
"line_percent": 100.0
|
||||
"line_percent": 100.0,
|
||||
"loc": 3
|
||||
},
|
||||
"common/src/cli.rs": {
|
||||
"loc": 22,
|
||||
"line_percent": 100.0
|
||||
"line_percent": 100.0,
|
||||
"loc": 22
|
||||
},
|
||||
"common/src/hid.rs": {
|
||||
"loc": 134,
|
||||
"line_percent": 100.0
|
||||
"line_percent": 100.0,
|
||||
"loc": 134
|
||||
},
|
||||
"common/src/lib.rs": {
|
||||
"loc": 22,
|
||||
"line_percent": 100.0
|
||||
"line_percent": 100.0,
|
||||
"loc": 22
|
||||
},
|
||||
"common/src/paste.rs": {
|
||||
"loc": 95,
|
||||
"line_percent": 100.0
|
||||
"line_percent": 100.0,
|
||||
"loc": 95
|
||||
},
|
||||
"server/src/audio.rs": {
|
||||
"loc": 386,
|
||||
"line_percent": 98.9010989010989
|
||||
"line_percent": 100.0,
|
||||
"loc": 386
|
||||
},
|
||||
"server/src/bin/lesavka-uvc.rs": {
|
||||
"loc": 710,
|
||||
"line_percent": 96.35535307517085
|
||||
"line_percent": 96.35535307517085,
|
||||
"loc": 710
|
||||
},
|
||||
"server/src/camera.rs": {
|
||||
"loc": 392,
|
||||
"line_percent": 99.09909909909909
|
||||
"line_percent": 99.09909909909909,
|
||||
"loc": 392
|
||||
},
|
||||
"server/src/camera_runtime.rs": {
|
||||
"loc": 200,
|
||||
"line_percent": 100.0
|
||||
"line_percent": 100.0,
|
||||
"loc": 200
|
||||
},
|
||||
"server/src/capture_power.rs": {
|
||||
"loc": 338,
|
||||
"line_percent": 100.0
|
||||
"line_percent": 100.0,
|
||||
"loc": 338
|
||||
},
|
||||
"server/src/gadget.rs": {
|
||||
"loc": 327,
|
||||
"line_percent": 96.875
|
||||
"line_percent": 96.875,
|
||||
"loc": 327
|
||||
},
|
||||
"server/src/handshake.rs": {
|
||||
"loc": 40,
|
||||
"line_percent": 100.0
|
||||
"line_percent": 100.0,
|
||||
"loc": 40
|
||||
},
|
||||
"server/src/main.rs": {
|
||||
"loc": 572,
|
||||
"line_percent": 95.33333333333334
|
||||
"line_percent": 95.33333333333334,
|
||||
"loc": 572
|
||||
},
|
||||
"server/src/paste.rs": {
|
||||
"loc": 207,
|
||||
"line_percent": 97.12230215827337
|
||||
"line_percent": 97.12230215827337,
|
||||
"loc": 207
|
||||
},
|
||||
"server/src/runtime_support.rs": {
|
||||
"loc": 387,
|
||||
"line_percent": 96.42857142857143
|
||||
"line_percent": 96.42857142857143,
|
||||
"loc": 387
|
||||
},
|
||||
"server/src/uvc_runtime.rs": {
|
||||
"loc": 241,
|
||||
"line_percent": 97.14285714285714
|
||||
"line_percent": 97.14285714285714,
|
||||
"loc": 241
|
||||
},
|
||||
"server/src/video.rs": {
|
||||
"loc": 344,
|
||||
"line_percent": 100.0
|
||||
"line_percent": 100.0,
|
||||
"loc": 343
|
||||
},
|
||||
"server/src/video_sinks.rs": {
|
||||
"loc": 559,
|
||||
"line_percent": 100.0
|
||||
"line_percent": 100.0,
|
||||
"loc": 559
|
||||
},
|
||||
"server/src/video_support.rs": {
|
||||
"loc": 236,
|
||||
"line_percent": 96.03174603174604
|
||||
"line_percent": 96.03174603174604,
|
||||
"loc": 236
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user