cat / goq
clone

turns a linux box into a private steam streaming appliance: plug in, tap your key, play from another computer

feat(portal): force JPEG in debug hardware tests

Dami committed 8e8749f
commit
8e8749fe97c262665e0b603432e706a26c04424f
parent
76ec14f51f05fd48e3e01c656915c1a18f1dcd8e
author
Dami <dami.etoile@gmail.com>
7 files changed, +187 -13
Mportal/enrollment-ui.test.mjs7++
Mportal/index.html1+
Mportal/main.js6+
Mportal/video-capability.mjs10++-
Mportal/video-capability.test.mjs34++++++++-
Mscripts/verify-demo-build.sh16++++
Msrc-tauri/src/commands/state.rs126++++++++++++++++++++++++++++--
M portal/enrollment-ui.test.mjs +7 -0
M portal/index.html +1 -0
M portal/main.js +6 -0
M portal/video-capability.mjs +8 -2
M portal/video-capability.test.mjs +33 -1
M scripts/verify-demo-build.sh +16 -0
M src-tauri/src/commands/state.rs +116 -10
@@ -264,12 +264,21 @@ pub const fn development_direct_node_available() -> bool {
264264 cfg!(any(debug_assertions, feature = "demo-direct-node"))265265 }266266 267+pub const fn development_force_jpeg_available() -> bool {268+ cfg!(debug_assertions)269+}270+267271 #[derive(Clone, Debug, Default, PartialEq, Eq)]268272 pub struct LaunchOptions {269273 pub dev_connect_node_id: Option<iroh::PublicKey>,274+ pub dev_force_jpeg: bool,270275 }271276 272-pub fn parse_launch_options<I, S>(args: I, allow_dev_connect: bool) -> Result<LaunchOptions, String>277+pub fn parse_launch_options<I, S>(278+ args: I,279+ allow_dev_connect: bool,280+ allow_force_jpeg: bool,281+) -> Result<LaunchOptions, String>273282 where274283 I: IntoIterator<Item = S>,275284 S: Into<OsString>,
@@ -291,6 +300,19 @@ where
291300 let arg = arg292301 .into_string()293302 .map_err(|_| "Portal command-line arguments must be valid UTF-8".to_string())?;303+ if arg == "--dev-force-jpeg" {304+ if !allow_force_jpeg {305+ return Err(306+ "--dev-force-jpeg requires a debug build; ordinary release builds use the normal WebCodecs capability probe"307+ .to_string(),308+ );309+ }310+ if options.dev_force_jpeg {311+ return Err("--dev-force-jpeg may be specified only once".to_string());312+ }313+ options.dev_force_jpeg = true;314+ continue;315+ }294316 let node_id = if arg == "--dev-connect" {295317 Some(296318 args.next()
@@ -344,6 +366,7 @@ pub struct AppState {
344366 pub client_connection_active: Arc<AtomicBool>,345367 pub webcodecs: AtomicBool,346368 pub dev_connect_node_id: Option<iroh::PublicKey>,369+ pub dev_force_jpeg: bool,347370 }348371 349372 pub type MediaControlRequestSender =
@@ -445,7 +468,12 @@ impl AppState {
445468 I: IntoIterator<Item = S>,446469 S: Into<OsString>,447470 {448- parse_launch_options(args, development_direct_node_available()).map(Self::new)471+ parse_launch_options(472+ args,473+ development_direct_node_available(),474+ development_force_jpeg_available(),475+ )476+ .map(Self::new)449477 }450478 451479 fn new(options: LaunchOptions) -> Self {
@@ -466,6 +494,7 @@ impl AppState {
466494 client_connection_active: Arc::new(AtomicBool::new(false)),467495 webcodecs: AtomicBool::new(false),468496 dev_connect_node_id: options.dev_connect_node_id,497+ dev_force_jpeg: options.dev_force_jpeg,469498 }470499 }471500 }
@@ -477,6 +506,8 @@ pub struct DevelopmentConnectionMode {
477506 pub enabled: bool,478507 pub host_node_id: Option<String>,479508 pub warning: &'static str,509+ pub force_jpeg: bool,510+ pub jpeg_warning: &'static str,480511 }481512 482513 #[tauri::command]
@@ -485,6 +516,8 @@ pub fn development_connection_mode(state: tauri::State<'_, AppState>) -> Develop
485516 enabled: state.dev_connect_node_id.is_some(),486517 host_node_id: state.dev_connect_node_id.map(|node_id| node_id.to_string()),487518 warning: "Development direct-node routing skips passkey identity lookup; it is not client authorization.",519+ force_jpeg: state.dev_force_jpeg,520+ jpeg_warning: "Development JPEG compatibility mode is forced; WebCodecs remains probed but is not selected.",488521 }489522 }490523
@@ -569,9 +602,15 @@ pub fn set_client_cursor_grab(window: tauri::WebviewWindow, grab: bool) -> Resul
569602 .map(|()| !cfg!(target_os = "macos"))570603 }571604 605+const fn effective_webcodecs_available(available: bool, force_jpeg: bool) -> bool {606+ available && !force_jpeg607+}608+572609 #[tauri::command]573-pub fn set_webcodecs_available(state: tauri::State<'_, AppState>, available: bool) {574- state.webcodecs.store(available, Ordering::SeqCst);610+pub fn set_webcodecs_available(state: tauri::State<'_, AppState>, available: bool) -> bool {611+ let effective = effective_webcodecs_available(available, state.dev_force_jpeg);612+ state.webcodecs.store(effective, Ordering::SeqCst);613+ effective575614 }576615 577616 #[tauri::command]
@@ -693,14 +732,41 @@ mod tests {
693732 #[test]694733 fn parses_debug_direct_node_flag() {695734 let node_id = test_node_id();696- let options = parse_launch_options(["portal", "--dev-connect", &node_id], true).unwrap();735+ let options =736+ parse_launch_options(["portal", "--dev-connect", &node_id], true, true).unwrap();697737 698738 assert_eq!(options.dev_connect_node_id.unwrap().to_string(), node_id);699739 }700740 741+ #[test]742+ fn parses_debug_force_jpeg_flag() {743+ let options = parse_launch_options(["portal", "--dev-force-jpeg"], true, true).unwrap();744+745+ assert!(options.dev_force_jpeg);746+ }747+748+ #[test]749+ fn parses_combined_debug_hardware_proof_flags() {750+ let node_id = test_node_id();751+ let options = parse_launch_options(752+ [753+ "portal",754+ "--dev-connect",755+ node_id.as_str(),756+ "--dev-force-jpeg",757+ ],758+ true,759+ true,760+ )761+ .unwrap();762+763+ assert_eq!(options.dev_connect_node_id.unwrap().to_string(), node_id);764+ assert!(options.dev_force_jpeg);765+ }766+701767 #[test]702768 fn rejects_legacy_daemon_mode() {703- let error = parse_launch_options(["portal", "--daemon"], true).unwrap_err();769+ let error = parse_launch_options(["portal", "--daemon"], true, true).unwrap_err();704770 assert!(error.contains("separate sigil executable"));705771 }706772
@@ -708,7 +774,7 @@ mod tests {
708774 fn parses_equals_form() {709775 let node_id = test_node_id();710776 let flag = format!("--dev-connect={node_id}");711- let options = parse_launch_options(["portal", &flag], true).unwrap();777+ let options = parse_launch_options(["portal", &flag], true, true).unwrap();712778 713779 assert_eq!(options.dev_connect_node_id.unwrap().to_string(), node_id);714780 }
@@ -716,11 +782,19 @@ mod tests {
716782 #[test]717783 fn rejects_direct_node_when_debug_mode_is_disabled() {718784 let node_id = test_node_id();719- let error = parse_launch_options(["portal", "--dev-connect", &node_id], false).unwrap_err();785+ let error =786+ parse_launch_options(["portal", "--dev-connect", &node_id], false, true).unwrap_err();720787 721788 assert!(error.contains("debug build or the explicit demo-direct-node feature"));722789 }723790 791+ #[test]792+ fn rejects_force_jpeg_when_debug_mode_is_disabled() {793+ let error = parse_launch_options(["portal", "--dev-force-jpeg"], true, false).unwrap_err();794+795+ assert!(error.contains("requires a debug build"));796+ }797+724798 #[test]725799 fn ordinary_release_excludes_direct_node_bypass() {726800 if cfg!(not(debug_assertions)) && cfg!(not(feature = "demo-direct-node")) {
@@ -736,6 +810,28 @@ mod tests {
736810 assert_eq!(result.is_ok(), development_direct_node_available());737811 }738812 813+ #[test]814+ fn ordinary_release_excludes_force_jpeg_override() {815+ if cfg!(not(debug_assertions)) {816+ assert!(!development_force_jpeg_available());817+ }818+ }819+820+ #[test]821+ fn app_state_accepts_force_jpeg_only_in_debug_builds() {822+ let result = AppState::from_args(["portal", "--dev-force-jpeg"]);823+824+ assert_eq!(result.is_ok(), development_force_jpeg_available());825+ }826+827+ #[test]828+ fn forced_jpeg_publication_keeps_native_and_frontend_mode_aligned() {829+ assert!(effective_webcodecs_available(true, false));830+ assert!(!effective_webcodecs_available(true, true));831+ assert!(!effective_webcodecs_available(false, false));832+ assert!(!effective_webcodecs_available(false, true));833+ }834+739835 #[test]740836 fn app_state_starts_without_owned_transport_connections() {741837 let state = AppState::default();
@@ -750,12 +846,12 @@ mod tests {
750846 #[test]751847 fn rejects_missing_invalid_and_duplicate_node_ids() {752848 assert!(753- parse_launch_options(["portal", "--dev-connect"], true)849+ parse_launch_options(["portal", "--dev-connect"], true, true)754850 .unwrap_err()755851 .contains("requires an iroh node ID")756852 );757853 assert!(758- parse_launch_options(["portal", "--dev-connect", "not-a-node-id"], true)854+ parse_launch_options(["portal", "--dev-connect", "not-a-node-id"], true, true)759855 .unwrap_err()760856 .contains("Invalid iroh node ID")761857 );
@@ -771,6 +867,16 @@ mod tests {
771867 &node_id,772868 ],773869 true,870+ true,871+ )872+ .unwrap_err()873+ .contains("only once")874+ );875+ assert!(876+ parse_launch_options(877+ ["portal", "--dev-force-jpeg", "--dev-force-jpeg"],878+ true,879+ true,774880 )775881 .unwrap_err()776882 .contains("only once")