lesavka: inherit mirrored source modes

This commit is contained in:
Brad Stein 2026-04-19 11:42:41 -03:00
parent fa2233f59e
commit ddbbf6b036
7 changed files with 136 additions and 38 deletions

View File

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

View File

@ -151,8 +151,12 @@ pub struct SnapshotReport {
impl SnapshotReport { impl SnapshotReport {
pub fn from_state(state: &LauncherState, log: &DiagnosticsLog, probe_command: String) -> Self { pub fn from_state(state: &LauncherState, log: &DiagnosticsLog, probe_command: String) -> Self {
let left_capture = state.capture_size_choice(0); let left_capture = state
let right_capture = state.capture_size_choice(1); .display_capture_size_choice(0)
.unwrap_or_else(|| state.capture_size_choice(0));
let right_capture = state
.display_capture_size_choice(1)
.unwrap_or_else(|| state.capture_size_choice(1));
let left_breakout = state.breakout_size_choice(0); let left_breakout = state.breakout_size_choice(0);
let right_breakout = state.breakout_size_choice(1); let right_breakout = state.breakout_size_choice(1);
let latest = log.latest(); let latest = log.latest();
@ -741,7 +745,9 @@ fn recommendations_for(state: &LauncherState, log: &DiagnosticsLog) -> Vec<Strin
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
use crate::launcher::state::{CaptureSizePreset, DeviceSelection, LauncherState}; use crate::launcher::state::{
CaptureSizePreset, DeviceSelection, FeedSourcePreset, LauncherState,
};
fn sample(n: u64) -> PerformanceSample { fn sample(n: u64) -> PerformanceSample {
PerformanceSample { PerformanceSample {
@ -895,6 +901,23 @@ mod tests {
assert!(text.contains("recommendations")); assert!(text.contains("recommendations"));
} }
#[test]
fn snapshot_report_uses_effective_mirrored_capture_profile() {
let mut state = LauncherState::new();
state.set_feed_source_preset(0, FeedSourcePreset::OtherEye);
state.set_capture_size_preset(1, CaptureSizePreset::P720);
let report = SnapshotReport::from_state(
&state,
&DiagnosticsLog::new(1),
quality_probe_command().to_string(),
);
assert_eq!(report.left_feed_source, "Right Eye (mirrored)");
assert!(report.left_capture_profile.contains("720p"));
assert!(report.left_capture_profile.contains("1280x720"));
}
#[test] #[test]
fn quality_probe_command_mentions_both_gates() { fn quality_probe_command_mentions_both_gates() {
let cmd = quality_probe_command(); let cmd = quality_probe_command();

View File

@ -213,23 +213,33 @@ fn refresh_eye_feed_controls(
state.feed_source_preset(monitor_id), state.feed_source_preset(monitor_id),
); );
if state.feed_source_preset(monitor_id) != FeedSourcePreset::Off { if state.feed_source_preset(monitor_id) != FeedSourcePreset::Off {
let choice = state.capture_size_choice(monitor_id); let choice = state
super::ui_components::sync_capture_resolution_combo( .display_capture_size_choice(monitor_id)
&widgets.display_panes[monitor_id].capture_resolution_combo, .unwrap_or_else(|| state.capture_size_choice(monitor_id));
state.capture_size_options(), if state.feed_source_preset(monitor_id) == FeedSourcePreset::ThisEye {
state.capture_size_preset(monitor_id), super::ui_components::sync_capture_resolution_combo(
); &widgets.display_panes[monitor_id].capture_resolution_combo,
state.capture_size_options(),
state.capture_size_preset(monitor_id),
);
} else {
super::ui_components::sync_capture_resolution_locked(
&widgets.display_panes[monitor_id].capture_resolution_combo,
state.capture_size_options(),
choice.preset,
);
}
super::ui_components::sync_capture_fps_combo( super::ui_components::sync_capture_fps_combo(
&widgets.display_panes[monitor_id].capture_fps_combo, &widgets.display_panes[monitor_id].capture_fps_combo,
state.capture_fps_options(), state.capture_fps_options(),
state.capture_fps(monitor_id), choice.fps,
true, true,
choice.fps, choice.fps,
); );
super::ui_components::sync_capture_bitrate_combo( super::ui_components::sync_capture_bitrate_combo(
&widgets.display_panes[monitor_id].capture_bitrate_combo, &widgets.display_panes[monitor_id].capture_bitrate_combo,
state.capture_bitrate_options(), state.capture_bitrate_options(),
state.capture_bitrate_kbit(monitor_id), choice.max_bitrate_kbit,
true, true,
choice.max_bitrate_kbit, choice.max_bitrate_kbit,
); );
@ -516,7 +526,9 @@ fn apply_preview_profiles(preview: &super::preview::LauncherPreview, state: &Lau
for monitor_id in 0..2 { for monitor_id in 0..2 {
let enabled = state.feed_source_preset(monitor_id) != FeedSourcePreset::Off; let enabled = state.feed_source_preset(monitor_id) != FeedSourcePreset::Off;
preview.set_monitor_enabled(monitor_id, enabled); preview.set_monitor_enabled(monitor_id, enabled);
let capture = state.capture_size_choice(monitor_id); let capture = state
.display_capture_size_choice(monitor_id)
.unwrap_or_else(|| state.capture_size_choice(monitor_id));
let source_monitor_id = state let source_monitor_id = state
.resolved_feed_monitor_id(monitor_id) .resolved_feed_monitor_id(monitor_id)
.unwrap_or(monitor_id); .unwrap_or(monitor_id);
@ -897,7 +909,7 @@ pub fn run_gui_launcher(server_addr: String) -> Result<()> {
let Some(preset) = CaptureSizePreset::from_id(active_id.as_str()) else { let Some(preset) = CaptureSizePreset::from_id(active_id.as_str()) else {
return; return;
}; };
if state.borrow().feed_source_preset(monitor_id) == FeedSourcePreset::Off { if state.borrow().feed_source_preset(monitor_id) != FeedSourcePreset::ThisEye {
return; return;
} }
if state.borrow().capture_size_preset(monitor_id) == preset { if state.borrow().capture_size_preset(monitor_id) == preset {
@ -908,7 +920,10 @@ pub fn run_gui_launcher(server_addr: String) -> Result<()> {
state.set_capture_size_preset(monitor_id, preset); state.set_capture_size_preset(monitor_id, preset);
} }
if let Some(preview) = preview.as_ref() { if let Some(preview) = preview.as_ref() {
let choice = state.borrow().capture_size_choice(monitor_id); let choice = state
.borrow()
.display_capture_size_choice(monitor_id)
.unwrap_or_else(|| state.borrow().capture_size_choice(monitor_id));
let source_monitor_id = state let source_monitor_id = state
.borrow() .borrow()
.resolved_feed_monitor_id(monitor_id) .resolved_feed_monitor_id(monitor_id)
@ -944,7 +959,7 @@ pub fn run_gui_launcher(server_addr: String) -> Result<()> {
let Ok(fps) = active_id.as_str().parse::<u32>() else { let Ok(fps) = active_id.as_str().parse::<u32>() else {
return; return;
}; };
if state.borrow().feed_source_preset(monitor_id) == FeedSourcePreset::Off { if state.borrow().feed_source_preset(monitor_id) != FeedSourcePreset::ThisEye {
return; return;
} }
if state.borrow().capture_fps(monitor_id) == fps { if state.borrow().capture_fps(monitor_id) == fps {
@ -955,7 +970,10 @@ pub fn run_gui_launcher(server_addr: String) -> Result<()> {
state.set_capture_fps(monitor_id, fps); state.set_capture_fps(monitor_id, fps);
} }
if let Some(preview) = preview.as_ref() { if let Some(preview) = preview.as_ref() {
let choice = state.borrow().capture_size_choice(monitor_id); let choice = state
.borrow()
.display_capture_size_choice(monitor_id)
.unwrap_or_else(|| state.borrow().capture_size_choice(monitor_id));
let source_monitor_id = state let source_monitor_id = state
.borrow() .borrow()
.resolved_feed_monitor_id(monitor_id) .resolved_feed_monitor_id(monitor_id)
@ -992,7 +1010,7 @@ pub fn run_gui_launcher(server_addr: String) -> Result<()> {
let Ok(max_bitrate_kbit) = active_id.as_str().parse::<u32>() else { let Ok(max_bitrate_kbit) = active_id.as_str().parse::<u32>() else {
return; return;
}; };
if state.borrow().feed_source_preset(monitor_id) == FeedSourcePreset::Off { if state.borrow().feed_source_preset(monitor_id) != FeedSourcePreset::ThisEye {
return; return;
} }
if state.borrow().capture_bitrate_kbit(monitor_id) == max_bitrate_kbit { if state.borrow().capture_bitrate_kbit(monitor_id) == max_bitrate_kbit {
@ -1003,7 +1021,10 @@ pub fn run_gui_launcher(server_addr: String) -> Result<()> {
state.set_capture_bitrate_kbit(monitor_id, max_bitrate_kbit); state.set_capture_bitrate_kbit(monitor_id, max_bitrate_kbit);
} }
if let Some(preview) = preview.as_ref() { if let Some(preview) = preview.as_ref() {
let choice = state.borrow().capture_size_choice(monitor_id); let choice = state
.borrow()
.display_capture_size_choice(monitor_id)
.unwrap_or_else(|| state.borrow().capture_size_choice(monitor_id));
let source_monitor_id = state let source_monitor_id = state
.borrow() .borrow()
.resolved_feed_monitor_id(monitor_id) .resolved_feed_monitor_id(monitor_id)
@ -2084,6 +2105,31 @@ mod tests {
preview.shutdown_all(); preview.shutdown_all();
} }
#[test]
fn mirrored_preview_profile_inherits_the_source_eye_mode() {
let preview = LauncherPreview::new("http://127.0.0.1:1".to_string()).unwrap();
let mut state = LauncherState::default();
state.set_feed_source_preset(0, FeedSourcePreset::OtherEye);
state.set_capture_size_preset(1, CaptureSizePreset::P720);
apply_preview_profiles(&preview, &state);
let inline = preview.profile_for_test(0, PreviewSurface::Inline).unwrap();
let window = preview.profile_for_test(0, PreviewSurface::Window).unwrap();
assert_eq!(inline.0, 1);
assert_eq!(window.0, 1);
assert_eq!(inline.3, 1280);
assert_eq!(inline.4, 720);
assert_eq!(inline.5, 60);
assert_eq!(inline.6, 12_000);
assert_eq!(window.3, 1280);
assert_eq!(window.4, 720);
assert_eq!(window.5, 60);
assert_eq!(window.6, 12_000);
preview.shutdown_all();
}
#[test] #[test]
fn off_preview_profile_disables_both_surfaces_instead_of_leaving_idle_feeds_running() { fn off_preview_profile_disables_both_surfaces_instead_of_leaving_idle_feeds_running() {
let preview = LauncherPreview::new("http://127.0.0.1:1".to_string()).unwrap(); let preview = LauncherPreview::new("http://127.0.0.1:1".to_string()).unwrap();

View File

@ -566,23 +566,33 @@ pub fn build_launcher_view(
state.feed_source_preset(1), state.feed_source_preset(1),
); );
if state.feed_source_preset(0) != FeedSourcePreset::Off { if state.feed_source_preset(0) != FeedSourcePreset::Off {
let choice = state.capture_size_choice(0); let choice = state
sync_capture_resolution_combo( .display_capture_size_choice(0)
&left_pane.capture_resolution_combo, .unwrap_or_else(|| state.capture_size_choice(0));
state.capture_size_options(), if state.feed_source_preset(0) == FeedSourcePreset::ThisEye {
state.capture_size_preset(0), sync_capture_resolution_combo(
); &left_pane.capture_resolution_combo,
state.capture_size_options(),
state.capture_size_preset(0),
);
} else {
sync_capture_resolution_locked(
&left_pane.capture_resolution_combo,
state.capture_size_options(),
choice.preset,
);
}
sync_capture_fps_combo( sync_capture_fps_combo(
&left_pane.capture_fps_combo, &left_pane.capture_fps_combo,
state.capture_fps_options(), state.capture_fps_options(),
state.capture_fps(0), choice.fps,
true, true,
choice.fps, choice.fps,
); );
sync_capture_bitrate_combo( sync_capture_bitrate_combo(
&left_pane.capture_bitrate_combo, &left_pane.capture_bitrate_combo,
state.capture_bitrate_options(), state.capture_bitrate_options(),
state.capture_bitrate_kbit(0), choice.max_bitrate_kbit,
true, true,
choice.max_bitrate_kbit, choice.max_bitrate_kbit,
); );
@ -592,23 +602,33 @@ pub fn build_launcher_view(
sync_capture_bitrate_disabled(&left_pane.capture_bitrate_combo); sync_capture_bitrate_disabled(&left_pane.capture_bitrate_combo);
} }
if state.feed_source_preset(1) != FeedSourcePreset::Off { if state.feed_source_preset(1) != FeedSourcePreset::Off {
let choice = state.capture_size_choice(1); let choice = state
sync_capture_resolution_combo( .display_capture_size_choice(1)
&right_pane.capture_resolution_combo, .unwrap_or_else(|| state.capture_size_choice(1));
state.capture_size_options(), if state.feed_source_preset(1) == FeedSourcePreset::ThisEye {
state.capture_size_preset(1), sync_capture_resolution_combo(
); &right_pane.capture_resolution_combo,
state.capture_size_options(),
state.capture_size_preset(1),
);
} else {
sync_capture_resolution_locked(
&right_pane.capture_resolution_combo,
state.capture_size_options(),
choice.preset,
);
}
sync_capture_fps_combo( sync_capture_fps_combo(
&right_pane.capture_fps_combo, &right_pane.capture_fps_combo,
state.capture_fps_options(), state.capture_fps_options(),
state.capture_fps(1), choice.fps,
true, true,
choice.fps, choice.fps,
); );
sync_capture_bitrate_combo( sync_capture_bitrate_combo(
&right_pane.capture_bitrate_combo, &right_pane.capture_bitrate_combo,
state.capture_bitrate_options(), state.capture_bitrate_options(),
state.capture_bitrate_kbit(1), choice.max_bitrate_kbit,
true, true,
choice.max_bitrate_kbit, choice.max_bitrate_kbit,
); );
@ -921,6 +941,15 @@ pub fn sync_capture_resolution_combo(
combo.set_sensitive(option_count > 1); combo.set_sensitive(option_count > 1);
} }
pub fn sync_capture_resolution_locked(
combo: &gtk::ComboBoxText,
options: Vec<CaptureSizeChoice>,
selected: CaptureSizePreset,
) {
sync_capture_resolution_combo(combo, options, selected);
combo.set_sensitive(false);
}
pub fn sync_capture_resolution_disabled(combo: &gtk::ComboBoxText) { pub fn sync_capture_resolution_disabled(combo: &gtk::ComboBoxText) {
combo.remove_all(); combo.remove_all();
combo.append(Some("off"), "Feed disabled"); combo.append(Some("off"), "Feed disabled");

View File

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

View File

@ -17,6 +17,6 @@ mod tests {
#[test] #[test]
fn banner_includes_version() { fn banner_includes_version() {
assert_eq!(banner("0.11.13"), "lesavka-common CLI (v0.11.13)"); assert_eq!(banner("0.11.14"), "lesavka-common CLI (v0.11.14)");
} }
} }

View File

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