diff --git a/client/Cargo.toml b/client/Cargo.toml index ccb19da..2aa382d 100644 --- a/client/Cargo.toml +++ b/client/Cargo.toml @@ -4,7 +4,7 @@ path = "src/main.rs" [package] name = "lesavka_client" -version = "0.11.13" +version = "0.11.14" edition = "2024" [dependencies] diff --git a/client/src/launcher/diagnostics.rs b/client/src/launcher/diagnostics.rs index 9b579e9..1726f9c 100644 --- a/client/src/launcher/diagnostics.rs +++ b/client/src/launcher/diagnostics.rs @@ -151,8 +151,12 @@ pub struct SnapshotReport { impl SnapshotReport { pub fn from_state(state: &LauncherState, log: &DiagnosticsLog, probe_command: String) -> Self { - let left_capture = state.capture_size_choice(0); - let right_capture = state.capture_size_choice(1); + let left_capture = state + .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 right_breakout = state.breakout_size_choice(1); let latest = log.latest(); @@ -741,7 +745,9 @@ fn recommendations_for(state: &LauncherState, log: &DiagnosticsLog) -> Vec PerformanceSample { PerformanceSample { @@ -895,6 +901,23 @@ mod tests { 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] fn quality_probe_command_mentions_both_gates() { let cmd = quality_probe_command(); diff --git a/client/src/launcher/ui.rs b/client/src/launcher/ui.rs index bfb3f05..8adf7b2 100644 --- a/client/src/launcher/ui.rs +++ b/client/src/launcher/ui.rs @@ -213,23 +213,33 @@ fn refresh_eye_feed_controls( state.feed_source_preset(monitor_id), ); if state.feed_source_preset(monitor_id) != FeedSourcePreset::Off { - let choice = state.capture_size_choice(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), - ); + let choice = state + .display_capture_size_choice(monitor_id) + .unwrap_or_else(|| state.capture_size_choice(monitor_id)); + if state.feed_source_preset(monitor_id) == FeedSourcePreset::ThisEye { + 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( &widgets.display_panes[monitor_id].capture_fps_combo, state.capture_fps_options(), - state.capture_fps(monitor_id), + choice.fps, true, choice.fps, ); super::ui_components::sync_capture_bitrate_combo( &widgets.display_panes[monitor_id].capture_bitrate_combo, state.capture_bitrate_options(), - state.capture_bitrate_kbit(monitor_id), + choice.max_bitrate_kbit, true, choice.max_bitrate_kbit, ); @@ -516,7 +526,9 @@ fn apply_preview_profiles(preview: &super::preview::LauncherPreview, state: &Lau for monitor_id in 0..2 { let enabled = state.feed_source_preset(monitor_id) != FeedSourcePreset::Off; 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 .resolved_feed_monitor_id(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 { return; }; - if state.borrow().feed_source_preset(monitor_id) == FeedSourcePreset::Off { + if state.borrow().feed_source_preset(monitor_id) != FeedSourcePreset::ThisEye { return; } 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); } 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 .borrow() .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::() else { return; }; - if state.borrow().feed_source_preset(monitor_id) == FeedSourcePreset::Off { + if state.borrow().feed_source_preset(monitor_id) != FeedSourcePreset::ThisEye { return; } 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); } 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 .borrow() .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::() else { return; }; - if state.borrow().feed_source_preset(monitor_id) == FeedSourcePreset::Off { + if state.borrow().feed_source_preset(monitor_id) != FeedSourcePreset::ThisEye { return; } 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); } 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 .borrow() .resolved_feed_monitor_id(monitor_id) @@ -2084,6 +2105,31 @@ mod tests { 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] 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(); diff --git a/client/src/launcher/ui_components.rs b/client/src/launcher/ui_components.rs index dee4555..c987aa0 100644 --- a/client/src/launcher/ui_components.rs +++ b/client/src/launcher/ui_components.rs @@ -566,23 +566,33 @@ pub fn build_launcher_view( state.feed_source_preset(1), ); if state.feed_source_preset(0) != FeedSourcePreset::Off { - let choice = state.capture_size_choice(0); - sync_capture_resolution_combo( - &left_pane.capture_resolution_combo, - state.capture_size_options(), - state.capture_size_preset(0), - ); + let choice = state + .display_capture_size_choice(0) + .unwrap_or_else(|| state.capture_size_choice(0)); + if state.feed_source_preset(0) == FeedSourcePreset::ThisEye { + 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( &left_pane.capture_fps_combo, state.capture_fps_options(), - state.capture_fps(0), + choice.fps, true, choice.fps, ); sync_capture_bitrate_combo( &left_pane.capture_bitrate_combo, state.capture_bitrate_options(), - state.capture_bitrate_kbit(0), + choice.max_bitrate_kbit, true, choice.max_bitrate_kbit, ); @@ -592,23 +602,33 @@ pub fn build_launcher_view( sync_capture_bitrate_disabled(&left_pane.capture_bitrate_combo); } if state.feed_source_preset(1) != FeedSourcePreset::Off { - let choice = state.capture_size_choice(1); - sync_capture_resolution_combo( - &right_pane.capture_resolution_combo, - state.capture_size_options(), - state.capture_size_preset(1), - ); + let choice = state + .display_capture_size_choice(1) + .unwrap_or_else(|| state.capture_size_choice(1)); + if state.feed_source_preset(1) == FeedSourcePreset::ThisEye { + 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( &right_pane.capture_fps_combo, state.capture_fps_options(), - state.capture_fps(1), + choice.fps, true, choice.fps, ); sync_capture_bitrate_combo( &right_pane.capture_bitrate_combo, state.capture_bitrate_options(), - state.capture_bitrate_kbit(1), + choice.max_bitrate_kbit, true, choice.max_bitrate_kbit, ); @@ -921,6 +941,15 @@ pub fn sync_capture_resolution_combo( combo.set_sensitive(option_count > 1); } +pub fn sync_capture_resolution_locked( + combo: >k::ComboBoxText, + options: Vec, + selected: CaptureSizePreset, +) { + sync_capture_resolution_combo(combo, options, selected); + combo.set_sensitive(false); +} + pub fn sync_capture_resolution_disabled(combo: >k::ComboBoxText) { combo.remove_all(); combo.append(Some("off"), "Feed disabled"); diff --git a/common/Cargo.toml b/common/Cargo.toml index f4ee62a..06c2151 100644 --- a/common/Cargo.toml +++ b/common/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "lesavka_common" -version = "0.11.13" +version = "0.11.14" edition = "2024" build = "build.rs" diff --git a/common/src/cli.rs b/common/src/cli.rs index a8155d1..81ee032 100644 --- a/common/src/cli.rs +++ b/common/src/cli.rs @@ -17,6 +17,6 @@ mod tests { #[test] 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)"); } } diff --git a/server/Cargo.toml b/server/Cargo.toml index 33dc615..b28b6b3 100644 --- a/server/Cargo.toml +++ b/server/Cargo.toml @@ -10,7 +10,7 @@ bench = false [package] name = "lesavka_server" -version = "0.11.13" +version = "0.11.14" edition = "2024" autobins = false