diff --git a/Cargo.lock b/Cargo.lock index ac97750..a7bfc43 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1658,7 +1658,7 @@ checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" [[package]] name = "lesavka_client" -version = "0.27.0" +version = "0.27.1" dependencies = [ "anyhow", "async-stream", @@ -1692,7 +1692,7 @@ dependencies = [ [[package]] name = "lesavka_common" -version = "0.27.0" +version = "0.27.1" dependencies = [ "anyhow", "base64", @@ -1704,7 +1704,7 @@ dependencies = [ [[package]] name = "lesavka_server" -version = "0.27.0" +version = "0.27.1" dependencies = [ "anyhow", "base64", diff --git a/client/Cargo.toml b/client/Cargo.toml index 6f25dbf..0315c87 100644 --- a/client/Cargo.toml +++ b/client/Cargo.toml @@ -4,7 +4,7 @@ path = "src/main.rs" [package] name = "lesavka_client" -version = "0.27.0" +version = "0.27.1" edition = "2024" [dependencies] diff --git a/common/Cargo.toml b/common/Cargo.toml index e5f83bb..1010d6f 100644 --- a/common/Cargo.toml +++ b/common/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "lesavka_common" -version = "0.27.0" +version = "0.27.1" edition = "2024" build = "build.rs" diff --git a/scripts/install/server.sh b/scripts/install/server.sh index 30d55b4..f1c7ef9 100755 --- a/scripts/install/server.sh +++ b/scripts/install/server.sh @@ -1621,7 +1621,7 @@ SERVER_ENV_TMP=$(mktemp) printf 'LESAVKA_HDMI_CONNECTOR=%s\n' "$HDMI_CONNECTOR" fi printf 'LESAVKA_CAM_OUTPUT=%s\n' "${LESAVKA_INSTALL_CAM_OUTPUT:-uvc}" - printf 'LESAVKA_CAM_HDMI_MIRROR=%s\n' "${LESAVKA_INSTALL_CAM_HDMI_MIRROR:-${LESAVKA_CAM_HDMI_MIRROR:-0}}" + printf 'LESAVKA_CAM_HDMI_MIRROR=%s\n' "${LESAVKA_INSTALL_CAM_HDMI_MIRROR:-${LESAVKA_CAM_HDMI_MIRROR:-auto}}" printf 'LESAVKA_CAM_CODEC=%s\n' "${INSTALL_CAM_CODEC}" printf 'LESAVKA_UPLINK_CAMERA_CODEC=%s\n' "${INSTALL_CAM_CODEC}" if [[ -n "$INSTALL_HEVC_DECODER" ]]; then diff --git a/server/Cargo.toml b/server/Cargo.toml index 471211c..d1819a2 100644 --- a/server/Cargo.toml +++ b/server/Cargo.toml @@ -16,7 +16,7 @@ bench = false [package] name = "lesavka_server" -version = "0.27.0" +version = "0.27.1" edition = "2024" autobins = false diff --git a/server/src/camera_runtime.rs b/server/src/camera_runtime.rs index dda6eac..dca07f0 100644 --- a/server/src/camera_runtime.rs +++ b/server/src/camera_runtime.rs @@ -249,8 +249,16 @@ pub fn camera_cfg_eq(a: &camera::CameraConfig, b: &camera::CameraConfig) -> bool } fn camera_relay_profile(cfg: &camera::CameraConfig) -> CameraRelayProfile { - let hdmi_mirror = (cfg.output == camera::CameraOutput::Uvc && hdmi_mirror_enabled()) - .then(|| camera::hdmi_mirror_config(cfg)); + let hdmi_mirror = (cfg.output == camera::CameraOutput::Uvc) + .then(|| match hdmi_mirror_setting() { + HdmiMirrorSetting::Enabled => Some(camera::hdmi_mirror_config(cfg)), + HdmiMirrorSetting::Auto if hdmi_mirror_connector_is_configured() => { + let mirror = camera::hdmi_mirror_config(cfg); + mirror.hdmi.is_some().then_some(mirror) + } + HdmiMirrorSetting::Auto | HdmiMirrorSetting::Disabled => None, + }) + .flatten(); CameraRelayProfile { primary: cfg.clone(), hdmi_mirror, @@ -266,22 +274,33 @@ fn camera_relay_profile_eq(a: &CameraRelayProfile, b: &CameraRelayProfile) -> bo } } -fn hdmi_mirror_enabled() -> bool { +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +enum HdmiMirrorSetting { + Disabled, + Enabled, + Auto, +} + +fn hdmi_mirror_setting() -> HdmiMirrorSetting { std::env::var("LESAVKA_CAM_HDMI_MIRROR") .ok() - .map(|value| { - let trimmed = value.trim(); - trimmed.eq_ignore_ascii_case("1") - || trimmed.eq_ignore_ascii_case("true") - || trimmed.eq_ignore_ascii_case("yes") - || trimmed.eq_ignore_ascii_case("on") + .map(|value| match value.trim().to_ascii_lowercase().as_str() { + "1" | "true" | "yes" | "on" => HdmiMirrorSetting::Enabled, + "auto" => HdmiMirrorSetting::Auto, + _ => HdmiMirrorSetting::Disabled, }) - .unwrap_or(false) + .unwrap_or(HdmiMirrorSetting::Disabled) +} + +fn hdmi_mirror_connector_is_configured() -> bool { + std::env::var("LESAVKA_HDMI_CONNECTOR") + .ok() + .is_some_and(|value| !value.trim().is_empty()) } #[cfg(test)] mod tests { - use super::{camera_cfg_eq, camera_relay_profile, camera_relay_profile_eq}; + use super::{HdmiMirrorSetting, camera_cfg_eq, camera_relay_profile, camera_relay_profile_eq}; use crate::camera::{CameraCodec, CameraConfig, CameraOutput, HdmiConnector}; use serial_test::serial; @@ -342,6 +361,46 @@ mod tests { }); } + #[test] + #[serial] + fn camera_relay_profile_auto_mirrors_only_when_hdmi_is_detected() { + let base = CameraConfig { + output: CameraOutput::Uvc, + codec: CameraCodec::Mjpeg, + width: 1280, + height: 720, + fps: 20, + hdmi: None, + }; + + temp_env::with_vars( + [ + ("LESAVKA_CAM_HDMI_MIRROR", Some("auto")), + ("LESAVKA_HDMI_CONNECTOR", None::<&str>), + ], + || { + assert_eq!(super::hdmi_mirror_setting(), HdmiMirrorSetting::Auto); + assert!(camera_relay_profile(&base).hdmi_mirror.is_none()); + }, + ); + + temp_env::with_vars( + [ + ("LESAVKA_CAM_HDMI_MIRROR", Some("auto")), + ("LESAVKA_HDMI_CONNECTOR", Some("card1-HDMI-A-1")), + ], + || { + let profile = camera_relay_profile(&base); + let mirror = profile + .hdmi_mirror + .as_ref() + .expect("auto HDMI mirror config"); + assert_eq!(mirror.output, CameraOutput::Hdmi); + assert_eq!(mirror.hdmi.as_ref().unwrap().name, "card1-HDMI-A-1"); + }, + ); + } + #[test] #[serial] fn camera_relay_profile_reuse_requires_same_mirror_connector() { diff --git a/tests/contract/scripts/install/server_install_script_contract.rs b/tests/contract/scripts/install/server_install_script_contract.rs index fe7c68e..1784a3f 100644 --- a/tests/contract/scripts/install/server_install_script_contract.rs +++ b/tests/contract/scripts/install/server_install_script_contract.rs @@ -86,7 +86,7 @@ fn server_install_pins_hdmi_camera_and_display_defaults() { assert!(SERVER_INSTALL.contains("${LESAVKA_INSTALL_CAM_OUTPUT:-uvc}")); assert!( SERVER_INSTALL - .contains("${LESAVKA_INSTALL_CAM_HDMI_MIRROR:-${LESAVKA_CAM_HDMI_MIRROR:-0}}") + .contains("${LESAVKA_INSTALL_CAM_HDMI_MIRROR:-${LESAVKA_CAM_HDMI_MIRROR:-auto}}") ); assert!(SERVER_INSTALL.contains("normalize_cam_codec()")); assert!(