Run the formatter over the tree
`just lint` has always run `cargo fmt --all --check`, but nothing enforced it: the old pipeline only built the RBE image buck2's executor pulled. CI is `nix flake check` now, which runs the formatter, so the drift that accumulated in thirteen files has to come out first. Formatting only — no behaviour changes. It does cost something real: av.rs, av_media.rs, v4l2cam.rs and android_camera.rs are kept close to sleek's copies so a fix can be moved between the two by eye, and they have moved a little further away. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
3e8c6f0 parent: d682bfa modified
.gitignore +4 -0 | @@ -18,3 +18,7 @@ | ||
| 18 | 18 | # writes for an editor to find. |
| 19 | 19 | **/.jolt/cpcache/ |
| 20 | 20 | **/.nrepl-port |
| 21 | + | |
| 22 | +# nix build output symlinks | |
| 23 | +result | |
| 24 | +result-* | |
| @@ -18,3 +18,7 @@ | |||
| 18 | # writes for an editor to find. | 18 | # writes for an editor to find. |
| 19 | **/.jolt/cpcache/ | 19 | **/.jolt/cpcache/ |
| 20 | **/.nrepl-port | 20 | **/.nrepl-port |
| 21 | + | ||
| 22 | +# nix build output symlinks | ||
| 23 | +result | ||
| 24 | +result-* | ||
modified
crates/jolt-abi/src/lib.rs +3 -1 | @@ -131,7 +131,9 @@ mod tests { | ||
| 131 | 131 | use std::ffi::CStr; |
| 132 | 132 | |
| 133 | 133 | fn read(ptr: *const c_char) -> String { |
| 134 | - unsafe { CStr::from_ptr(ptr) }.to_string_lossy().into_owned() | |
| 134 | + unsafe { CStr::from_ptr(ptr) } | |
| 135 | + .to_string_lossy() | |
| 136 | + .into_owned() | |
| 135 | 137 | } |
| 136 | 138 | |
| 137 | 139 | #[test] |
| @@ -131,7 +131,9 @@ mod tests { | |||
| 131 | use std::ffi::CStr; | 131 | use std::ffi::CStr; |
| 132 | 132 | ||
| 133 | fn read(ptr: *const c_char) -> String { | 133 | fn read(ptr: *const c_char) -> String { |
| 134 | - unsafe { CStr::from_ptr(ptr) }.to_string_lossy().into_owned() | 134 | + unsafe { CStr::from_ptr(ptr) } |
| 135 | + .to_string_lossy() | ||
| 136 | + .into_owned() | ||
| 135 | } | 137 | } |
| 136 | 138 | ||
| 137 | #[test] | 139 | #[test] |
modified
crates/jolt-moq/src/android_camera.rs +18 -17 | @@ -330,26 +330,25 @@ pub extern "system" fn Java_uk_nandi_frq_CameraCapture_onNv12Frame<'local>( | ||
| 330 | 330 | }; |
| 331 | 331 | // Apply sensor/display orientation so published + preview frames |
| 332 | 332 | // are upright (Camera2 ImageReader buffers are sensor-oriented). |
| 333 | - let (y_bytes, uv_bytes, w, h, y_str, uv_str) = | |
| 334 | - match crate::nv12_orient::orient_nv12( | |
| 335 | - &y_bytes, | |
| 336 | - &uv_bytes, | |
| 333 | + let (y_bytes, uv_bytes, w, h, y_str, uv_str) = match crate::nv12_orient::orient_nv12( | |
| 334 | + &y_bytes, | |
| 335 | + &uv_bytes, | |
| 336 | + src_w, | |
| 337 | + src_h, | |
| 338 | + y_stride as u32, | |
| 339 | + uv_stride as u32, | |
| 340 | + rot, | |
| 341 | + ) { | |
| 342 | + Some((y, uv, w, h)) => (y, uv, w, h, w, w), | |
| 343 | + None => ( | |
| 344 | + y_bytes, | |
| 345 | + uv_bytes, | |
| 337 | 346 | src_w, |
| 338 | 347 | src_h, |
| 339 | 348 | y_stride as u32, |
| 340 | 349 | uv_stride as u32, |
| 341 | - rot, | |
| 342 | - ) { | |
| 343 | - Some((y, uv, w, h)) => (y, uv, w, h, w, w), | |
| 344 | - None => ( | |
| 345 | - y_bytes, | |
| 346 | - uv_bytes, | |
| 347 | - src_w, | |
| 348 | - src_h, | |
| 349 | - y_stride as u32, | |
| 350 | - uv_stride as u32, | |
| 351 | - ), | |
| 352 | - }; | |
| 350 | + ), | |
| 351 | + }; | |
| 353 | 352 | let frame = VideoFrame::new_nv12( |
| 354 | 353 | Nv12Planes { |
| 355 | 354 | y_data: y_bytes, |
| @@ -375,7 +374,9 @@ pub extern "system" fn Java_uk_nandi_frq_CameraCapture_onNv12Frame<'local>( | ||
| 375 | 374 | } |
| 376 | 375 | let n = s.frames_pushed.fetch_add(1, Ordering::Relaxed); |
| 377 | 376 | if n == 0 { |
| 378 | - log::info!("android camera: first NV12 frame {w}x{h} (src {src_w}x{src_h} rot={rot})"); | |
| 377 | + log::info!( | |
| 378 | + "android camera: first NV12 frame {w}x{h} (src {src_w}x{src_h} rot={rot})" | |
| 379 | + ); | |
| 379 | 380 | } |
| 380 | 381 | Ok(()) |
| 381 | 382 | }) |
| @@ -330,26 +330,25 @@ pub extern "system" fn Java_uk_nandi_frq_CameraCapture_onNv12Frame<'local>( | |||
| 330 | }; | 330 | }; |
| 331 | // Apply sensor/display orientation so published + preview frames | 331 | // Apply sensor/display orientation so published + preview frames |
| 332 | // are upright (Camera2 ImageReader buffers are sensor-oriented). | 332 | // are upright (Camera2 ImageReader buffers are sensor-oriented). |
| 333 | - let (y_bytes, uv_bytes, w, h, y_str, uv_str) = | 333 | + let (y_bytes, uv_bytes, w, h, y_str, uv_str) = match crate::nv12_orient::orient_nv12( |
| 334 | - match crate::nv12_orient::orient_nv12( | 334 | + &y_bytes, |
| 335 | - &y_bytes, | 335 | + &uv_bytes, |
| 336 | - &uv_bytes, | 336 | + src_w, |
| 337 | + src_h, | ||
| 338 | + y_stride as u32, | ||
| 339 | + uv_stride as u32, | ||
| 340 | + rot, | ||
| 341 | + ) { | ||
| 342 | + Some((y, uv, w, h)) => (y, uv, w, h, w, w), | ||
| 343 | + None => ( | ||
| 344 | + y_bytes, | ||
| 345 | + uv_bytes, | ||
| 337 | src_w, | 346 | src_w, |
| 338 | src_h, | 347 | src_h, |
| 339 | y_stride as u32, | 348 | y_stride as u32, |
| 340 | uv_stride as u32, | 349 | uv_stride as u32, |
| 341 | - rot, | 350 | + ), |
| 342 | - ) { | 351 | + }; |
| 343 | - Some((y, uv, w, h)) => (y, uv, w, h, w, w), | ||
| 344 | - None => ( | ||
| 345 | - y_bytes, | ||
| 346 | - uv_bytes, | ||
| 347 | - src_w, | ||
| 348 | - src_h, | ||
| 349 | - y_stride as u32, | ||
| 350 | - uv_stride as u32, | ||
| 351 | - ), | ||
| 352 | - }; | ||
| 353 | let frame = VideoFrame::new_nv12( | 352 | let frame = VideoFrame::new_nv12( |
| 354 | Nv12Planes { | 353 | Nv12Planes { |
| 355 | y_data: y_bytes, | 354 | y_data: y_bytes, |
| @@ -375,7 +374,9 @@ pub extern "system" fn Java_uk_nandi_frq_CameraCapture_onNv12Frame<'local>( | |||
| 375 | } | 374 | } |
| 376 | let n = s.frames_pushed.fetch_add(1, Ordering::Relaxed); | 375 | let n = s.frames_pushed.fetch_add(1, Ordering::Relaxed); |
| 377 | if n == 0 { | 376 | if n == 0 { |
| 378 | - log::info!("android camera: first NV12 frame {w}x{h} (src {src_w}x{src_h} rot={rot})"); | 377 | + log::info!( |
| 378 | + "android camera: first NV12 frame {w}x{h} (src {src_w}x{src_h} rot={rot})" | ||
| 379 | + ); | ||
| 379 | } | 380 | } |
| 380 | Ok(()) | 381 | Ok(()) |
| 381 | }) | 382 | }) |
modified
crates/jolt-moq/src/av.rs +21 -16 | @@ -14,7 +14,6 @@ use std::fmt; | ||
| 14 | 14 | use std::sync::atomic::{AtomicU32, AtomicU64, Ordering}; |
| 15 | 15 | use std::sync::{Arc, Mutex}; |
| 16 | 16 | |
| 17 | - | |
| 18 | 17 | /// Whether to claim camera hardware when dialing the media plane. |
| 19 | 18 | /// |
| 20 | 19 | /// `permission_granted` is the Android runtime CAMERA check (pass `true` on |
| @@ -61,9 +60,7 @@ pub struct VideoFrameStore { | ||
| 61 | 60 | impl fmt::Debug for VideoFrameStore { |
| 62 | 61 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
| 63 | 62 | let n = self.frames.lock().map(|g| g.len()).unwrap_or(0); |
| 64 | - f.debug_struct("VideoFrameStore") | |
| 65 | - .field("len", &n) | |
| 66 | - .finish() | |
| 63 | + f.debug_struct("VideoFrameStore").field("len", &n).finish() | |
| 67 | 64 | } |
| 68 | 65 | } |
| 69 | 66 | |
| @@ -76,14 +73,19 @@ impl VideoFrameStore { | ||
| 76 | 73 | if width == 0 || height == 0 { |
| 77 | 74 | return; |
| 78 | 75 | } |
| 79 | - let expected = (width as usize).saturating_mul(height as usize).saturating_mul(4); | |
| 76 | + let expected = (width as usize) | |
| 77 | + .saturating_mul(height as usize) | |
| 78 | + .saturating_mul(4); | |
| 80 | 79 | if rgba.len() != expected { |
| 81 | 80 | return; |
| 82 | 81 | } |
| 83 | 82 | // Camera / decoder paths sometimes leave alpha at 0 (OBS virtual cam, |
| 84 | 83 | // some MJPEG converters). egui then draws a fully transparent tile. |
| 85 | 84 | let rgba = force_opaque_rgba(rgba); |
| 86 | - let gen = self.next_gen.fetch_add(1, Ordering::Relaxed).wrapping_add(1); | |
| 85 | + let gen = self | |
| 86 | + .next_gen | |
| 87 | + .fetch_add(1, Ordering::Relaxed) | |
| 88 | + .wrapping_add(1); | |
| 87 | 89 | if let Ok(mut g) = self.frames.lock() { |
| 88 | 90 | g.insert( |
| 89 | 91 | nick.into(), |
| @@ -467,10 +469,7 @@ mod tests { | ||
| 467 | 469 | |
| 468 | 470 | #[test] |
| 469 | 471 | fn path_key_keeps_instance_path_nick_strips() { |
| 470 | - assert_eq!( | |
| 471 | - path_key("01SESSION/alice~phone"), | |
| 472 | - "alice~phone" | |
| 473 | - ); | |
| 472 | + assert_eq!(path_key("01SESSION/alice~phone"), "alice~phone"); | |
| 474 | 473 | assert_eq!(path_nick("01SESSION/alice~phone"), "alice"); |
| 475 | 474 | assert_eq!(path_key("01SESSION/bob"), "bob"); |
| 476 | 475 | assert_eq!(path_nick("bob~desk"), "bob"); |
| @@ -525,15 +524,15 @@ mod tests { | ||
| 525 | 524 | #[test] |
| 526 | 525 | fn video_frame_store_seed_missing_from_copies_only_absent_keys() { |
| 527 | 526 | let old = VideoFrameStore::new(); |
| 528 | - let rgba: Arc<[u8]> = Arc::from([10u8, 20, 30, 255, 40, 50, 60, 255, 70, 80, 90, 255, 1, 2, 3, 255]); | |
| 527 | + let rgba: Arc<[u8]> = Arc::from([ | |
| 528 | + 10u8, 20, 30, 255, 40, 50, 60, 255, 70, 80, 90, 255, 1, 2, 3, 255, | |
| 529 | + ]); | |
| 529 | 530 | old.set("eve", 2, 2, rgba.clone()); |
| 530 | 531 | old.set(LOCAL_PREVIEW_KEY, 2, 2, rgba); |
| 531 | 532 | |
| 532 | 533 | let new = VideoFrameStore::new(); |
| 533 | 534 | // Live key already present — must not be overwritten by seed. |
| 534 | - let live: Arc<[u8]> = Arc::from([ | |
| 535 | - 9u8, 9, 9, 255, 9, 9, 9, 255, 9, 9, 9, 255, 9, 9, 9, 255, | |
| 536 | - ]); | |
| 535 | + let live: Arc<[u8]> = Arc::from([9u8, 9, 9, 255, 9, 9, 9, 255, 9, 9, 9, 255, 9, 9, 9, 255]); | |
| 537 | 536 | new.set("eve", 2, 2, live.clone()); |
| 538 | 537 | |
| 539 | 538 | new.seed_missing_from(&old); |
| @@ -600,7 +599,10 @@ mod tests { | ||
| 600 | 599 | let r_vals: Vec<u8> = frame.rgba.iter().step_by(4).copied().collect(); |
| 601 | 600 | let min = *r_vals.iter().min().unwrap(); |
| 602 | 601 | let max = *r_vals.iter().max().unwrap(); |
| 603 | - assert!(max > min, "real camera-like content must have pixel variance"); | |
| 602 | + assert!( | |
| 603 | + max > min, | |
| 604 | + "real camera-like content must have pixel variance" | |
| 605 | + ); | |
| 604 | 606 | for a in frame.rgba.iter().skip(3).step_by(4) { |
| 605 | 607 | assert_eq!(*a, 255); |
| 606 | 608 | } |
| @@ -683,7 +685,10 @@ mod tests { | ||
| 683 | 685 | |
| 684 | 686 | #[test] |
| 685 | 687 | fn can_dial_sfu_with_jwt_always_allowed() { |
| 686 | - assert!(can_dial_sfu("wss://chat.example.com", Some("eyJhbGciOiJIUzI1NiJ9.e30.x"))); | |
| 688 | + assert!(can_dial_sfu( | |
| 689 | + "wss://chat.example.com", | |
| 690 | + Some("eyJhbGciOiJIUzI1NiJ9.e30.x") | |
| 691 | + )); | |
| 687 | 692 | assert!(can_dial_sfu("https://remote.example", Some("tok"))); |
| 688 | 693 | assert!(can_dial_sfu("ws://localhost:4443", Some("tok"))); |
| 689 | 694 | assert!(can_dial_sfu("127.0.0.1", Some("tok"))); |
| @@ -14,7 +14,6 @@ use std::fmt; | |||
| 14 | use std::sync::atomic::{AtomicU32, AtomicU64, Ordering}; | 14 | use std::sync::atomic::{AtomicU32, AtomicU64, Ordering}; |
| 15 | use std::sync::{Arc, Mutex}; | 15 | use std::sync::{Arc, Mutex}; |
| 16 | 16 | ||
| 17 | - | ||
| 18 | /// Whether to claim camera hardware when dialing the media plane. | 17 | /// Whether to claim camera hardware when dialing the media plane. |
| 19 | /// | 18 | /// |
| 20 | /// `permission_granted` is the Android runtime CAMERA check (pass `true` on | 19 | /// `permission_granted` is the Android runtime CAMERA check (pass `true` on |
| @@ -61,9 +60,7 @@ pub struct VideoFrameStore { | |||
| 61 | impl fmt::Debug for VideoFrameStore { | 60 | impl fmt::Debug for VideoFrameStore { |
| 62 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { | 61 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
| 63 | let n = self.frames.lock().map(|g| g.len()).unwrap_or(0); | 62 | let n = self.frames.lock().map(|g| g.len()).unwrap_or(0); |
| 64 | - f.debug_struct("VideoFrameStore") | 63 | + f.debug_struct("VideoFrameStore").field("len", &n).finish() |
| 65 | - .field("len", &n) | ||
| 66 | - .finish() | ||
| 67 | } | 64 | } |
| 68 | } | 65 | } |
| 69 | 66 | ||
| @@ -76,14 +73,19 @@ impl VideoFrameStore { | |||
| 76 | if width == 0 || height == 0 { | 73 | if width == 0 || height == 0 { |
| 77 | return; | 74 | return; |
| 78 | } | 75 | } |
| 79 | - let expected = (width as usize).saturating_mul(height as usize).saturating_mul(4); | 76 | + let expected = (width as usize) |
| 77 | + .saturating_mul(height as usize) | ||
| 78 | + .saturating_mul(4); | ||
| 80 | if rgba.len() != expected { | 79 | if rgba.len() != expected { |
| 81 | return; | 80 | return; |
| 82 | } | 81 | } |
| 83 | // Camera / decoder paths sometimes leave alpha at 0 (OBS virtual cam, | 82 | // Camera / decoder paths sometimes leave alpha at 0 (OBS virtual cam, |
| 84 | // some MJPEG converters). egui then draws a fully transparent tile. | 83 | // some MJPEG converters). egui then draws a fully transparent tile. |
| 85 | let rgba = force_opaque_rgba(rgba); | 84 | let rgba = force_opaque_rgba(rgba); |
| 86 | - let gen = self.next_gen.fetch_add(1, Ordering::Relaxed).wrapping_add(1); | 85 | + let gen = self |
| 86 | + .next_gen | ||
| 87 | + .fetch_add(1, Ordering::Relaxed) | ||
| 88 | + .wrapping_add(1); | ||
| 87 | if let Ok(mut g) = self.frames.lock() { | 89 | if let Ok(mut g) = self.frames.lock() { |
| 88 | g.insert( | 90 | g.insert( |
| 89 | nick.into(), | 91 | nick.into(), |
| @@ -467,10 +469,7 @@ mod tests { | |||
| 467 | 469 | ||
| 468 | #[test] | 470 | #[test] |
| 469 | fn path_key_keeps_instance_path_nick_strips() { | 471 | fn path_key_keeps_instance_path_nick_strips() { |
| 470 | - assert_eq!( | 472 | + assert_eq!(path_key("01SESSION/alice~phone"), "alice~phone"); |
| 471 | - path_key("01SESSION/alice~phone"), | ||
| 472 | - "alice~phone" | ||
| 473 | - ); | ||
| 474 | assert_eq!(path_nick("01SESSION/alice~phone"), "alice"); | 473 | assert_eq!(path_nick("01SESSION/alice~phone"), "alice"); |
| 475 | assert_eq!(path_key("01SESSION/bob"), "bob"); | 474 | assert_eq!(path_key("01SESSION/bob"), "bob"); |
| 476 | assert_eq!(path_nick("bob~desk"), "bob"); | 475 | assert_eq!(path_nick("bob~desk"), "bob"); |
| @@ -525,15 +524,15 @@ mod tests { | |||
| 525 | #[test] | 524 | #[test] |
| 526 | fn video_frame_store_seed_missing_from_copies_only_absent_keys() { | 525 | fn video_frame_store_seed_missing_from_copies_only_absent_keys() { |
| 527 | let old = VideoFrameStore::new(); | 526 | let old = VideoFrameStore::new(); |
| 528 | - let rgba: Arc<[u8]> = Arc::from([10u8, 20, 30, 255, 40, 50, 60, 255, 70, 80, 90, 255, 1, 2, 3, 255]); | 527 | + let rgba: Arc<[u8]> = Arc::from([ |
| 528 | + 10u8, 20, 30, 255, 40, 50, 60, 255, 70, 80, 90, 255, 1, 2, 3, 255, | ||
| 529 | + ]); | ||
| 529 | old.set("eve", 2, 2, rgba.clone()); | 530 | old.set("eve", 2, 2, rgba.clone()); |
| 530 | old.set(LOCAL_PREVIEW_KEY, 2, 2, rgba); | 531 | old.set(LOCAL_PREVIEW_KEY, 2, 2, rgba); |
| 531 | 532 | ||
| 532 | let new = VideoFrameStore::new(); | 533 | let new = VideoFrameStore::new(); |
| 533 | // Live key already present — must not be overwritten by seed. | 534 | // Live key already present — must not be overwritten by seed. |
| 534 | - let live: Arc<[u8]> = Arc::from([ | 535 | + let live: Arc<[u8]> = Arc::from([9u8, 9, 9, 255, 9, 9, 9, 255, 9, 9, 9, 255, 9, 9, 9, 255]); |
| 535 | - 9u8, 9, 9, 255, 9, 9, 9, 255, 9, 9, 9, 255, 9, 9, 9, 255, | ||
| 536 | - ]); | ||
| 537 | new.set("eve", 2, 2, live.clone()); | 536 | new.set("eve", 2, 2, live.clone()); |
| 538 | 537 | ||
| 539 | new.seed_missing_from(&old); | 538 | new.seed_missing_from(&old); |
| @@ -600,7 +599,10 @@ mod tests { | |||
| 600 | let r_vals: Vec<u8> = frame.rgba.iter().step_by(4).copied().collect(); | 599 | let r_vals: Vec<u8> = frame.rgba.iter().step_by(4).copied().collect(); |
| 601 | let min = *r_vals.iter().min().unwrap(); | 600 | let min = *r_vals.iter().min().unwrap(); |
| 602 | let max = *r_vals.iter().max().unwrap(); | 601 | let max = *r_vals.iter().max().unwrap(); |
| 603 | - assert!(max > min, "real camera-like content must have pixel variance"); | 602 | + assert!( |
| 603 | + max > min, | ||
| 604 | + "real camera-like content must have pixel variance" | ||
| 605 | + ); | ||
| 604 | for a in frame.rgba.iter().skip(3).step_by(4) { | 606 | for a in frame.rgba.iter().skip(3).step_by(4) { |
| 605 | assert_eq!(*a, 255); | 607 | assert_eq!(*a, 255); |
| 606 | } | 608 | } |
| @@ -683,7 +685,10 @@ mod tests { | |||
| 683 | 685 | ||
| 684 | #[test] | 686 | #[test] |
| 685 | fn can_dial_sfu_with_jwt_always_allowed() { | 687 | fn can_dial_sfu_with_jwt_always_allowed() { |
| 686 | - assert!(can_dial_sfu("wss://chat.example.com", Some("eyJhbGciOiJIUzI1NiJ9.e30.x"))); | 688 | + assert!(can_dial_sfu( |
| 689 | + "wss://chat.example.com", | ||
| 690 | + Some("eyJhbGciOiJIUzI1NiJ9.e30.x") | ||
| 691 | + )); | ||
| 687 | assert!(can_dial_sfu("https://remote.example", Some("tok"))); | 692 | assert!(can_dial_sfu("https://remote.example", Some("tok"))); |
| 688 | assert!(can_dial_sfu("ws://localhost:4443", Some("tok"))); | 693 | assert!(can_dial_sfu("ws://localhost:4443", Some("tok"))); |
| 689 | assert!(can_dial_sfu("127.0.0.1", Some("tok"))); | 694 | assert!(can_dial_sfu("127.0.0.1", Some("tok"))); |
modified
crates/jolt-moq/src/av_media.rs +37 -87 | @@ -168,10 +168,7 @@ pub fn sanitize_audio_device_pref(id: Option<String>) -> Option<String> { | ||
| 168 | 168 | #[cfg(not(target_os = "android"))] |
| 169 | 169 | fn preferred_audio_host() -> Option<String> { |
| 170 | 170 | let hosts = AudioBackend::available_hosts(); |
| 171 | - if hosts | |
| 172 | - .iter() | |
| 173 | - .any(|h| h.eq_ignore_ascii_case("pipewire")) | |
| 174 | - { | |
| 171 | + if hosts.iter().any(|h| h.eq_ignore_ascii_case("pipewire")) { | |
| 175 | 172 | Some("PipeWire".into()) |
| 176 | 173 | } else { |
| 177 | 174 | None |
| @@ -536,10 +533,7 @@ async fn run_media( | ||
| 536 | 533 | let _ = rustls::crypto::aws_lc_rs::default_provider().install_default(); |
| 537 | 534 | |
| 538 | 535 | let our_broadcast = broadcast_path(&config.session_id, &config.nick, &config.instance); |
| 539 | - log::info!( | |
| 540 | - "av-media: dialing {} as {our_broadcast}", | |
| 541 | - config.sfu_url | |
| 542 | - ); | |
| 536 | + log::info!("av-media: dialing {} as {our_broadcast}", config.sfu_url); | |
| 543 | 537 | |
| 544 | 538 | let mut client_config = moq_native::ClientConfig::default(); |
| 545 | 539 | client_config.tls.disable_verify = Some(true); |
| @@ -627,9 +621,7 @@ async fn run_media( | ||
| 627 | 621 | // Warm-up: wait until the capture ring actually has energy so we |
| 628 | 622 | // don't advertise "mic open" while still InputNotReady→silence. |
| 629 | 623 | let peak = warm_up_mic(&mut *mic, std::time::Duration::from_millis(800)); |
| 630 | - log::info!( | |
| 631 | - "av-media: mic warm-up peak={peak:.4} (0 = capture silent / not ready)" | |
| 632 | - ); | |
| 624 | + log::info!("av-media: mic warm-up peak={peak:.4} (0 = capture silent / not ready)"); | |
| 633 | 625 | if peak < 1e-5 { |
| 634 | 626 | log::warn!( |
| 635 | 627 | "av-media: microphone opened but capture is silent so far — \ |
| @@ -669,9 +661,7 @@ async fn run_media( | ||
| 669 | 661 | } |
| 670 | 662 | } |
| 671 | 663 | Err(e) => { |
| 672 | - log::warn!( | |
| 673 | - "av-media: no microphone ({e}); publishing silence (listen-only)" | |
| 674 | - ); | |
| 664 | + log::warn!("av-media: no microphone ({e}); publishing silence (listen-only)"); | |
| 675 | 665 | muted.store(true, Ordering::Relaxed); |
| 676 | 666 | if let Err(e2) = broadcast.audio().set( |
| 677 | 667 | MuteableSource::silence(muted.clone(), mic_level.clone()), |
| @@ -1201,19 +1191,14 @@ async fn tap_remote( | ||
| 1201 | 1191 | // Match freeq-sdk-ffi: tighter latency than the 150ms streaming default. |
| 1202 | 1192 | let policy = iroh_live::media::playout::PlaybackPolicy::default() |
| 1203 | 1193 | .with_max_latency(std::time::Duration::from_millis(60)); |
| 1204 | - let remote = match RemoteBroadcast::with_playback_policy( | |
| 1205 | - &path, | |
| 1206 | - broadcast_consumer, | |
| 1207 | - policy, | |
| 1208 | - ) | |
| 1209 | - .await | |
| 1210 | - { | |
| 1211 | - Ok(r) => r, | |
| 1212 | - Err(e) => { | |
| 1213 | - log::warn!("av-media: catalog {path}: {e}"); | |
| 1214 | - return; | |
| 1215 | - } | |
| 1216 | - }; | |
| 1194 | + let remote = | |
| 1195 | + match RemoteBroadcast::with_playback_policy(&path, broadcast_consumer, policy).await { | |
| 1196 | + Ok(r) => r, | |
| 1197 | + Err(e) => { | |
| 1198 | + log::warn!("av-media: catalog {path}: {e}"); | |
| 1199 | + return; | |
| 1200 | + } | |
| 1201 | + }; | |
| 1217 | 1202 | |
| 1218 | 1203 | let audio_task = { |
| 1219 | 1204 | let remote = remote.clone(); |
| @@ -1254,9 +1239,7 @@ async fn tap_remote( | ||
| 1254 | 1239 | // the independent video pipeline (tap_remote waits on |
| 1255 | 1240 | // `remote.closed()`, not this task exiting). |
| 1256 | 1241 | if consecutive_errs <= 3 || consecutive_errs % 10 == 0 { |
| 1257 | - log::warn!( | |
| 1258 | - "av-media: audio sub {ps}: {e} (retry {consecutive_errs})" | |
| 1259 | - ); | |
| 1242 | + log::warn!("av-media: audio sub {ps}: {e} (retry {consecutive_errs})"); | |
| 1260 | 1243 | } |
| 1261 | 1244 | let backoff_ms = (500u64 * u64::from(consecutive_errs.min(8))).min(4_000); |
| 1262 | 1245 | tokio::time::sleep(std::time::Duration::from_millis(backoff_ms)).await; |
| @@ -1395,10 +1378,7 @@ impl iroh_live::media::traits::AudioSource for SilenceSource { | ||
| 1395 | 1378 | self.format |
| 1396 | 1379 | } |
| 1397 | 1380 | |
| 1398 | - fn pop_samples( | |
| 1399 | - &mut self, | |
| 1400 | - buf: &mut [f32], | |
| 1401 | - ) -> anyhow::Result<Option<usize>> { | |
| 1381 | + fn pop_samples(&mut self, buf: &mut [f32]) -> anyhow::Result<Option<usize>> { | |
| 1402 | 1382 | for s in buf.iter_mut() { |
| 1403 | 1383 | *s = 0.0; |
| 1404 | 1384 | } |
| @@ -1585,11 +1565,7 @@ fn open_camera_with_fallback( | ||
| 1585 | 1565 | |
| 1586 | 1566 | // Then non-virtual hardware (skip virtuals for auto-pick). |
| 1587 | 1567 | let mut cams = listed.clone(); |
| 1588 | - cams.sort_by(|a, b| { | |
| 1589 | - a.name | |
| 1590 | - .to_lowercase() | |
| 1591 | - .cmp(&b.name.to_lowercase()) | |
| 1592 | - }); | |
| 1568 | + cams.sort_by(|a, b| a.name.to_lowercase().cmp(&b.name.to_lowercase())); | |
| 1593 | 1569 | for c in cams { |
| 1594 | 1570 | if is_virtual_camera(&c.name, &c.id) { |
| 1595 | 1571 | continue; |
| @@ -1597,9 +1573,10 @@ fn open_camera_with_fallback( | ||
| 1597 | 1573 | if c.supported_formats.is_empty() { |
| 1598 | 1574 | continue; |
| 1599 | 1575 | } |
| 1600 | - if candidates.iter().any(|x| { | |
| 1601 | - x.as_deref() == Some(c.id.as_str()) || x.as_deref() == Some(c.name.as_str()) | |
| 1602 | - }) { | |
| 1576 | + if candidates | |
| 1577 | + .iter() | |
| 1578 | + .any(|x| x.as_deref() == Some(c.id.as_str()) || x.as_deref() == Some(c.name.as_str())) | |
| 1579 | + { | |
| 1603 | 1580 | continue; |
| 1604 | 1581 | } |
| 1605 | 1582 | candidates.push(Some(c.id)); |
| @@ -1618,9 +1595,7 @@ fn open_camera_with_fallback( | ||
| 1618 | 1595 | // prefer virtual. |
| 1619 | 1596 | let user_wants_virtual = preferred.is_some_and(|p| is_virt(p)); |
| 1620 | 1597 | if is_virtual_camera(&name, label) && !user_wants_virtual { |
| 1621 | - log::warn!( | |
| 1622 | - "av-media: rejecting auto-opened virtual camera {name} ({label})" | |
| 1623 | - ); | |
| 1598 | + log::warn!("av-media: rejecting auto-opened virtual camera {name} ({label})"); | |
| 1624 | 1599 | errors.push(format!("{label}: rejected virtual {name}")); |
| 1625 | 1600 | continue; |
| 1626 | 1601 | } |
| @@ -1702,7 +1677,9 @@ fn open_camera_with_busy_retry( | ||
| 1702 | 1677 | let busy = e.to_string().to_ascii_lowercase().contains("busy"); |
| 1703 | 1678 | last = Some(e); |
| 1704 | 1679 | if busy && attempt + 1 < ATTEMPTS { |
| 1705 | - std::thread::sleep(std::time::Duration::from_millis(150 * (attempt + 1) as u64)); | |
| 1680 | + std::thread::sleep(std::time::Duration::from_millis( | |
| 1681 | + 150 * (attempt + 1) as u64, | |
| 1682 | + )); | |
| 1706 | 1683 | continue; |
| 1707 | 1684 | } |
| 1708 | 1685 | break; |
| @@ -1784,9 +1761,7 @@ fn spawn_local_preview_pump( | ||
| 1784 | 1761 | } |
| 1785 | 1762 | } |
| 1786 | 1763 | Err(_) => { |
| 1787 | - log::warn!( | |
| 1788 | - "av-media: local preview pump rgba_image panicked {w}x{h}" | |
| 1789 | - ); | |
| 1764 | + log::warn!("av-media: local preview pump rgba_image panicked {w}x{h}"); | |
| 1790 | 1765 | } |
| 1791 | 1766 | } |
| 1792 | 1767 | } else if track.is_closed() { |
| @@ -1831,8 +1806,7 @@ fn open_android_camera( | ||
| 1831 | 1806 | local_frame_count: Arc<AtomicU64>, |
| 1832 | 1807 | want_publish: bool, |
| 1833 | 1808 | ) -> Result<crate::android_camera::CameraCaptureGuard> { |
| 1834 | - crate::android_camera::start_capture(camera_id) | |
| 1835 | - .context("CameraCapture.start")?; | |
| 1809 | + crate::android_camera::start_capture(camera_id).context("CameraCapture.start")?; | |
| 1836 | 1810 | // Camera2 open + session configure is async on a Java handler thread. |
| 1837 | 1811 | if let Err(e) = crate::android_camera::wait_until_opened(std::time::Duration::from_secs(5)) { |
| 1838 | 1812 | crate::android_camera::stop_capture(); |
| @@ -1917,9 +1891,7 @@ impl VideoSource for GatedCameraSource { | ||
| 1917 | 1891 | self.inner.stop() |
| 1918 | 1892 | } |
| 1919 | 1893 | |
| 1920 | - fn pop_frame( | |
| 1921 | - &mut self, | |
| 1922 | - ) -> anyhow::Result<Option<iroh_live::media::format::VideoFrame>> { | |
| 1894 | + fn pop_frame(&mut self) -> anyhow::Result<Option<iroh_live::media::format::VideoFrame>> { | |
| 1923 | 1895 | // Check publish flag *before* capturing. The previous order (dqbuf then |
| 1924 | 1896 | // discard) left V4L2 STREAMON / the privacy LED on for the whole mute. |
| 1925 | 1897 | if !self.enabled.load(Ordering::Relaxed) { |
| @@ -2075,10 +2047,7 @@ impl iroh_live::media::traits::AudioSource for ToneSource { | ||
| 2075 | 2047 | self.format |
| 2076 | 2048 | } |
| 2077 | 2049 | |
| 2078 | - fn pop_samples( | |
| 2079 | - &mut self, | |
| 2080 | - buf: &mut [f32], | |
| 2081 | - ) -> anyhow::Result<Option<usize>> { | |
| 2050 | + fn pop_samples(&mut self, buf: &mut [f32]) -> anyhow::Result<Option<usize>> { | |
| 2082 | 2051 | let channels = self.format.channel_count.max(1) as usize; |
| 2083 | 2052 | let frames = buf.len() / channels; |
| 2084 | 2053 | let phase_inc = self.frequency / self.format.sample_rate as f32; |
| @@ -2170,10 +2139,7 @@ impl iroh_live::media::traits::AudioSource for MuteableSource { | ||
| 2170 | 2139 | self.inner.format() |
| 2171 | 2140 | } |
| 2172 | 2141 | |
| 2173 | - fn pop_samples( | |
| 2174 | - &mut self, | |
| 2175 | - buf: &mut [f32], | |
| 2176 | - ) -> anyhow::Result<Option<usize>> { | |
| 2142 | + fn pop_samples(&mut self, buf: &mut [f32]) -> anyhow::Result<Option<usize>> { | |
| 2177 | 2143 | let mut pre_peak = 0.0f32; |
| 2178 | 2144 | let n = match self.inner.pop_samples(buf)? { |
| 2179 | 2145 | Some(n) if n > 0 => { |
| @@ -2238,9 +2204,7 @@ mod tests { | ||
| 2238 | 2204 | use iroh_live::media::playout::PlaybackPolicy; |
| 2239 | 2205 | use iroh_live::media::publish::LocalBroadcast; |
| 2240 | 2206 | use iroh_live::media::subscribe::RemoteBroadcast; |
| 2241 | - use iroh_live::media::traits::{ | |
| 2242 | - AudioSink, AudioSinkHandle, AudioSource, AudioStreamFactory, | |
| 2243 | - }; | |
| 2207 | + use iroh_live::media::traits::{AudioSink, AudioSinkHandle, AudioSource, AudioStreamFactory}; | |
| 2244 | 2208 | use n0_future::boxed::BoxFuture; |
| 2245 | 2209 | use std::time::Duration; |
| 2246 | 2210 | |
| @@ -2278,9 +2242,7 @@ mod tests { | ||
| 2278 | 2242 | } |
| 2279 | 2243 | let _ = cam.stop(); |
| 2280 | 2244 | let Some(f) = frame else { |
| 2281 | - eprintln!( | |
| 2282 | - "SKIP real_camera id={id:?}: no frames (OBS Virtual Camera not started?)" | |
| 2283 | - ); | |
| 2245 | + eprintln!("SKIP real_camera id={id:?}: no frames (OBS Virtual Camera not started?)"); | |
| 2284 | 2246 | return; |
| 2285 | 2247 | }; |
| 2286 | 2248 | let (w, h) = (f.width(), f.height()); |
| @@ -2291,9 +2253,7 @@ mod tests { | ||
| 2291 | 2253 | .get(0..4) |
| 2292 | 2254 | .map(|p| (p[0], p[1], p[2], p[3])) |
| 2293 | 2255 | .unwrap_or((0, 0, 0, 0)); |
| 2294 | - eprintln!( | |
| 2295 | - "camera id={id:?} {w}x{h} rgba0=({r0},{g0},{b0},{a0}) luma range {min}..{max}" | |
| 2296 | - ); | |
| 2256 | + eprintln!("camera id={id:?} {w}x{h} rgba0=({r0},{g0},{b0},{a0}) luma range {min}..{max}"); | |
| 2297 | 2257 | assert!( |
| 2298 | 2258 | max > min, |
| 2299 | 2259 | "real camera frame must be non-uniform (luma range {min}..{max}); \ |
| @@ -2394,10 +2354,7 @@ mod tests { | ||
| 2394 | 2354 | assert_eq!(n, Some(buf.len())); |
| 2395 | 2355 | assert_eq!(pcm_rms(&buf), 0.0, "muted must be silence"); |
| 2396 | 2356 | // Level still saw pre-mute energy from the tone. |
| 2397 | - assert!( | |
| 2398 | - src.level.get() > 0.01, | |
| 2399 | - "mic meter should move while muted" | |
| 2400 | - ); | |
| 2357 | + assert!(src.level.get() > 0.01, "mic meter should move while muted"); | |
| 2401 | 2358 | } |
| 2402 | 2359 | |
| 2403 | 2360 | #[test] |
| @@ -2561,13 +2518,10 @@ mod tests { | ||
| 2561 | 2518 | // Keep producer alive for the duration of the test. |
| 2562 | 2519 | let _keepalive = broadcast; |
| 2563 | 2520 | |
| 2564 | - let remote = RemoteBroadcast::with_playback_policy( | |
| 2565 | - &path, | |
| 2566 | - consumer, | |
| 2567 | - PlaybackPolicy::unmanaged(), | |
| 2568 | - ) | |
| 2569 | - .await | |
| 2570 | - .expect("catalog from LocalBroadcast"); | |
| 2521 | + let remote = | |
| 2522 | + RemoteBroadcast::with_playback_policy(&path, consumer, PlaybackPolicy::unmanaged()) | |
| 2523 | + .await | |
| 2524 | + .expect("catalog from LocalBroadcast"); | |
| 2571 | 2525 | |
| 2572 | 2526 | let (tx, rx) = std::sync::mpsc::sync_channel::<Vec<f32>>(64); |
| 2573 | 2527 | let backend = TapBackend { tx }; |
| @@ -2796,12 +2750,8 @@ mod tests { | ||
| 2796 | 2750 | "video.clear must drop the source so privacy LED can go dark" |
| 2797 | 2751 | ); |
| 2798 | 2752 | assert!( |
| 2799 | - !store | |
| 2800 | - .snapshot() | |
| 2801 | - .iter() | |
| 2802 | - .any(|(k, _)| k == LOCAL_PREVIEW_KEY), | |
| 2753 | + !store.snapshot().iter().any(|(k, _)| k == LOCAL_PREVIEW_KEY), | |
| 2803 | 2754 | "local preview frame must be cleared on release" |
| 2804 | 2755 | ); |
| 2805 | 2756 | } |
| 2806 | - | |
| 2807 | 2757 | } |
| @@ -168,10 +168,7 @@ pub fn sanitize_audio_device_pref(id: Option<String>) -> Option<String> { | |||
| 168 | #[cfg(not(target_os = "android"))] | 168 | #[cfg(not(target_os = "android"))] |
| 169 | fn preferred_audio_host() -> Option<String> { | 169 | fn preferred_audio_host() -> Option<String> { |
| 170 | let hosts = AudioBackend::available_hosts(); | 170 | let hosts = AudioBackend::available_hosts(); |
| 171 | - if hosts | 171 | + if hosts.iter().any(|h| h.eq_ignore_ascii_case("pipewire")) { |
| 172 | - .iter() | ||
| 173 | - .any(|h| h.eq_ignore_ascii_case("pipewire")) | ||
| 174 | - { | ||
| 175 | Some("PipeWire".into()) | 172 | Some("PipeWire".into()) |
| 176 | } else { | 173 | } else { |
| 177 | None | 174 | None |
| @@ -536,10 +533,7 @@ async fn run_media( | |||
| 536 | let _ = rustls::crypto::aws_lc_rs::default_provider().install_default(); | 533 | let _ = rustls::crypto::aws_lc_rs::default_provider().install_default(); |
| 537 | 534 | ||
| 538 | let our_broadcast = broadcast_path(&config.session_id, &config.nick, &config.instance); | 535 | let our_broadcast = broadcast_path(&config.session_id, &config.nick, &config.instance); |
| 539 | - log::info!( | 536 | + log::info!("av-media: dialing {} as {our_broadcast}", config.sfu_url); |
| 540 | - "av-media: dialing {} as {our_broadcast}", | ||
| 541 | - config.sfu_url | ||
| 542 | - ); | ||
| 543 | 537 | ||
| 544 | let mut client_config = moq_native::ClientConfig::default(); | 538 | let mut client_config = moq_native::ClientConfig::default(); |
| 545 | client_config.tls.disable_verify = Some(true); | 539 | client_config.tls.disable_verify = Some(true); |
| @@ -627,9 +621,7 @@ async fn run_media( | |||
| 627 | // Warm-up: wait until the capture ring actually has energy so we | 621 | // Warm-up: wait until the capture ring actually has energy so we |
| 628 | // don't advertise "mic open" while still InputNotReady→silence. | 622 | // don't advertise "mic open" while still InputNotReady→silence. |
| 629 | let peak = warm_up_mic(&mut *mic, std::time::Duration::from_millis(800)); | 623 | let peak = warm_up_mic(&mut *mic, std::time::Duration::from_millis(800)); |
| 630 | - log::info!( | 624 | + log::info!("av-media: mic warm-up peak={peak:.4} (0 = capture silent / not ready)"); |
| 631 | - "av-media: mic warm-up peak={peak:.4} (0 = capture silent / not ready)" | ||
| 632 | - ); | ||
| 633 | if peak < 1e-5 { | 625 | if peak < 1e-5 { |
| 634 | log::warn!( | 626 | log::warn!( |
| 635 | "av-media: microphone opened but capture is silent so far — \ | 627 | "av-media: microphone opened but capture is silent so far — \ |
| @@ -669,9 +661,7 @@ async fn run_media( | |||
| 669 | } | 661 | } |
| 670 | } | 662 | } |
| 671 | Err(e) => { | 663 | Err(e) => { |
| 672 | - log::warn!( | 664 | + log::warn!("av-media: no microphone ({e}); publishing silence (listen-only)"); |
| 673 | - "av-media: no microphone ({e}); publishing silence (listen-only)" | ||
| 674 | - ); | ||
| 675 | muted.store(true, Ordering::Relaxed); | 665 | muted.store(true, Ordering::Relaxed); |
| 676 | if let Err(e2) = broadcast.audio().set( | 666 | if let Err(e2) = broadcast.audio().set( |
| 677 | MuteableSource::silence(muted.clone(), mic_level.clone()), | 667 | MuteableSource::silence(muted.clone(), mic_level.clone()), |
| @@ -1201,19 +1191,14 @@ async fn tap_remote( | |||
| 1201 | // Match freeq-sdk-ffi: tighter latency than the 150ms streaming default. | 1191 | // Match freeq-sdk-ffi: tighter latency than the 150ms streaming default. |
| 1202 | let policy = iroh_live::media::playout::PlaybackPolicy::default() | 1192 | let policy = iroh_live::media::playout::PlaybackPolicy::default() |
| 1203 | .with_max_latency(std::time::Duration::from_millis(60)); | 1193 | .with_max_latency(std::time::Duration::from_millis(60)); |
| 1204 | - let remote = match RemoteBroadcast::with_playback_policy( | 1194 | + let remote = |
| 1205 | - &path, | 1195 | + match RemoteBroadcast::with_playback_policy(&path, broadcast_consumer, policy).await { |
| 1206 | - broadcast_consumer, | 1196 | + Ok(r) => r, |
| 1207 | - policy, | 1197 | + Err(e) => { |
| 1208 | - ) | 1198 | + log::warn!("av-media: catalog {path}: {e}"); |
| 1209 | - .await | 1199 | + return; |
| 1210 | - { | 1200 | + } |
| 1211 | - Ok(r) => r, | 1201 | + }; |
| 1212 | - Err(e) => { | ||
| 1213 | - log::warn!("av-media: catalog {path}: {e}"); | ||
| 1214 | - return; | ||
| 1215 | - } | ||
| 1216 | - }; | ||
| 1217 | 1202 | ||
| 1218 | let audio_task = { | 1203 | let audio_task = { |
| 1219 | let remote = remote.clone(); | 1204 | let remote = remote.clone(); |
| @@ -1254,9 +1239,7 @@ async fn tap_remote( | |||
| 1254 | // the independent video pipeline (tap_remote waits on | 1239 | // the independent video pipeline (tap_remote waits on |
| 1255 | // `remote.closed()`, not this task exiting). | 1240 | // `remote.closed()`, not this task exiting). |
| 1256 | if consecutive_errs <= 3 || consecutive_errs % 10 == 0 { | 1241 | if consecutive_errs <= 3 || consecutive_errs % 10 == 0 { |
| 1257 | - log::warn!( | 1242 | + log::warn!("av-media: audio sub {ps}: {e} (retry {consecutive_errs})"); |
| 1258 | - "av-media: audio sub {ps}: {e} (retry {consecutive_errs})" | ||
| 1259 | - ); | ||
| 1260 | } | 1243 | } |
| 1261 | let backoff_ms = (500u64 * u64::from(consecutive_errs.min(8))).min(4_000); | 1244 | let backoff_ms = (500u64 * u64::from(consecutive_errs.min(8))).min(4_000); |
| 1262 | tokio::time::sleep(std::time::Duration::from_millis(backoff_ms)).await; | 1245 | tokio::time::sleep(std::time::Duration::from_millis(backoff_ms)).await; |
| @@ -1395,10 +1378,7 @@ impl iroh_live::media::traits::AudioSource for SilenceSource { | |||
| 1395 | self.format | 1378 | self.format |
| 1396 | } | 1379 | } |
| 1397 | 1380 | ||
| 1398 | - fn pop_samples( | 1381 | + fn pop_samples(&mut self, buf: &mut [f32]) -> anyhow::Result<Option<usize>> { |
| 1399 | - &mut self, | ||
| 1400 | - buf: &mut [f32], | ||
| 1401 | - ) -> anyhow::Result<Option<usize>> { | ||
| 1402 | for s in buf.iter_mut() { | 1382 | for s in buf.iter_mut() { |
| 1403 | *s = 0.0; | 1383 | *s = 0.0; |
| 1404 | } | 1384 | } |
| @@ -1585,11 +1565,7 @@ fn open_camera_with_fallback( | |||
| 1585 | 1565 | ||
| 1586 | // Then non-virtual hardware (skip virtuals for auto-pick). | 1566 | // Then non-virtual hardware (skip virtuals for auto-pick). |
| 1587 | let mut cams = listed.clone(); | 1567 | let mut cams = listed.clone(); |
| 1588 | - cams.sort_by(|a, b| { | 1568 | + cams.sort_by(|a, b| a.name.to_lowercase().cmp(&b.name.to_lowercase())); |
| 1589 | - a.name | ||
| 1590 | - .to_lowercase() | ||
| 1591 | - .cmp(&b.name.to_lowercase()) | ||
| 1592 | - }); | ||
| 1593 | for c in cams { | 1569 | for c in cams { |
| 1594 | if is_virtual_camera(&c.name, &c.id) { | 1570 | if is_virtual_camera(&c.name, &c.id) { |
| 1595 | continue; | 1571 | continue; |
| @@ -1597,9 +1573,10 @@ fn open_camera_with_fallback( | |||
| 1597 | if c.supported_formats.is_empty() { | 1573 | if c.supported_formats.is_empty() { |
| 1598 | continue; | 1574 | continue; |
| 1599 | } | 1575 | } |
| 1600 | - if candidates.iter().any(|x| { | 1576 | + if candidates |
| 1601 | - x.as_deref() == Some(c.id.as_str()) || x.as_deref() == Some(c.name.as_str()) | 1577 | + .iter() |
| 1602 | - }) { | 1578 | + .any(|x| x.as_deref() == Some(c.id.as_str()) || x.as_deref() == Some(c.name.as_str())) |
| 1579 | + { | ||
| 1603 | continue; | 1580 | continue; |
| 1604 | } | 1581 | } |
| 1605 | candidates.push(Some(c.id)); | 1582 | candidates.push(Some(c.id)); |
| @@ -1618,9 +1595,7 @@ fn open_camera_with_fallback( | |||
| 1618 | // prefer virtual. | 1595 | // prefer virtual. |
| 1619 | let user_wants_virtual = preferred.is_some_and(|p| is_virt(p)); | 1596 | let user_wants_virtual = preferred.is_some_and(|p| is_virt(p)); |
| 1620 | if is_virtual_camera(&name, label) && !user_wants_virtual { | 1597 | if is_virtual_camera(&name, label) && !user_wants_virtual { |
| 1621 | - log::warn!( | 1598 | + log::warn!("av-media: rejecting auto-opened virtual camera {name} ({label})"); |
| 1622 | - "av-media: rejecting auto-opened virtual camera {name} ({label})" | ||
| 1623 | - ); | ||
| 1624 | errors.push(format!("{label}: rejected virtual {name}")); | 1599 | errors.push(format!("{label}: rejected virtual {name}")); |
| 1625 | continue; | 1600 | continue; |
| 1626 | } | 1601 | } |
| @@ -1702,7 +1677,9 @@ fn open_camera_with_busy_retry( | |||
| 1702 | let busy = e.to_string().to_ascii_lowercase().contains("busy"); | 1677 | let busy = e.to_string().to_ascii_lowercase().contains("busy"); |
| 1703 | last = Some(e); | 1678 | last = Some(e); |
| 1704 | if busy && attempt + 1 < ATTEMPTS { | 1679 | if busy && attempt + 1 < ATTEMPTS { |
| 1705 | - std::thread::sleep(std::time::Duration::from_millis(150 * (attempt + 1) as u64)); | 1680 | + std::thread::sleep(std::time::Duration::from_millis( |
| 1681 | + 150 * (attempt + 1) as u64, | ||
| 1682 | + )); | ||
| 1706 | continue; | 1683 | continue; |
| 1707 | } | 1684 | } |
| 1708 | break; | 1685 | break; |
| @@ -1784,9 +1761,7 @@ fn spawn_local_preview_pump( | |||
| 1784 | } | 1761 | } |
| 1785 | } | 1762 | } |
| 1786 | Err(_) => { | 1763 | Err(_) => { |
| 1787 | - log::warn!( | 1764 | + log::warn!("av-media: local preview pump rgba_image panicked {w}x{h}"); |
| 1788 | - "av-media: local preview pump rgba_image panicked {w}x{h}" | ||
| 1789 | - ); | ||
| 1790 | } | 1765 | } |
| 1791 | } | 1766 | } |
| 1792 | } else if track.is_closed() { | 1767 | } else if track.is_closed() { |
| @@ -1831,8 +1806,7 @@ fn open_android_camera( | |||
| 1831 | local_frame_count: Arc<AtomicU64>, | 1806 | local_frame_count: Arc<AtomicU64>, |
| 1832 | want_publish: bool, | 1807 | want_publish: bool, |
| 1833 | ) -> Result<crate::android_camera::CameraCaptureGuard> { | 1808 | ) -> Result<crate::android_camera::CameraCaptureGuard> { |
| 1834 | - crate::android_camera::start_capture(camera_id) | 1809 | + crate::android_camera::start_capture(camera_id).context("CameraCapture.start")?; |
| 1835 | - .context("CameraCapture.start")?; | ||
| 1836 | // Camera2 open + session configure is async on a Java handler thread. | 1810 | // Camera2 open + session configure is async on a Java handler thread. |
| 1837 | if let Err(e) = crate::android_camera::wait_until_opened(std::time::Duration::from_secs(5)) { | 1811 | if let Err(e) = crate::android_camera::wait_until_opened(std::time::Duration::from_secs(5)) { |
| 1838 | crate::android_camera::stop_capture(); | 1812 | crate::android_camera::stop_capture(); |
| @@ -1917,9 +1891,7 @@ impl VideoSource for GatedCameraSource { | |||
| 1917 | self.inner.stop() | 1891 | self.inner.stop() |
| 1918 | } | 1892 | } |
| 1919 | 1893 | ||
| 1920 | - fn pop_frame( | 1894 | + fn pop_frame(&mut self) -> anyhow::Result<Option<iroh_live::media::format::VideoFrame>> { |
| 1921 | - &mut self, | ||
| 1922 | - ) -> anyhow::Result<Option<iroh_live::media::format::VideoFrame>> { | ||
| 1923 | // Check publish flag *before* capturing. The previous order (dqbuf then | 1895 | // Check publish flag *before* capturing. The previous order (dqbuf then |
| 1924 | // discard) left V4L2 STREAMON / the privacy LED on for the whole mute. | 1896 | // discard) left V4L2 STREAMON / the privacy LED on for the whole mute. |
| 1925 | if !self.enabled.load(Ordering::Relaxed) { | 1897 | if !self.enabled.load(Ordering::Relaxed) { |
| @@ -2075,10 +2047,7 @@ impl iroh_live::media::traits::AudioSource for ToneSource { | |||
| 2075 | self.format | 2047 | self.format |
| 2076 | } | 2048 | } |
| 2077 | 2049 | ||
| 2078 | - fn pop_samples( | 2050 | + fn pop_samples(&mut self, buf: &mut [f32]) -> anyhow::Result<Option<usize>> { |
| 2079 | - &mut self, | ||
| 2080 | - buf: &mut [f32], | ||
| 2081 | - ) -> anyhow::Result<Option<usize>> { | ||
| 2082 | let channels = self.format.channel_count.max(1) as usize; | 2051 | let channels = self.format.channel_count.max(1) as usize; |
| 2083 | let frames = buf.len() / channels; | 2052 | let frames = buf.len() / channels; |
| 2084 | let phase_inc = self.frequency / self.format.sample_rate as f32; | 2053 | let phase_inc = self.frequency / self.format.sample_rate as f32; |
| @@ -2170,10 +2139,7 @@ impl iroh_live::media::traits::AudioSource for MuteableSource { | |||
| 2170 | self.inner.format() | 2139 | self.inner.format() |
| 2171 | } | 2140 | } |
| 2172 | 2141 | ||
| 2173 | - fn pop_samples( | 2142 | + fn pop_samples(&mut self, buf: &mut [f32]) -> anyhow::Result<Option<usize>> { |
| 2174 | - &mut self, | ||
| 2175 | - buf: &mut [f32], | ||
| 2176 | - ) -> anyhow::Result<Option<usize>> { | ||
| 2177 | let mut pre_peak = 0.0f32; | 2143 | let mut pre_peak = 0.0f32; |
| 2178 | let n = match self.inner.pop_samples(buf)? { | 2144 | let n = match self.inner.pop_samples(buf)? { |
| 2179 | Some(n) if n > 0 => { | 2145 | Some(n) if n > 0 => { |
| @@ -2238,9 +2204,7 @@ mod tests { | |||
| 2238 | use iroh_live::media::playout::PlaybackPolicy; | 2204 | use iroh_live::media::playout::PlaybackPolicy; |
| 2239 | use iroh_live::media::publish::LocalBroadcast; | 2205 | use iroh_live::media::publish::LocalBroadcast; |
| 2240 | use iroh_live::media::subscribe::RemoteBroadcast; | 2206 | use iroh_live::media::subscribe::RemoteBroadcast; |
| 2241 | - use iroh_live::media::traits::{ | 2207 | + use iroh_live::media::traits::{AudioSink, AudioSinkHandle, AudioSource, AudioStreamFactory}; |
| 2242 | - AudioSink, AudioSinkHandle, AudioSource, AudioStreamFactory, | ||
| 2243 | - }; | ||
| 2244 | use n0_future::boxed::BoxFuture; | 2208 | use n0_future::boxed::BoxFuture; |
| 2245 | use std::time::Duration; | 2209 | use std::time::Duration; |
| 2246 | 2210 | ||
| @@ -2278,9 +2242,7 @@ mod tests { | |||
| 2278 | } | 2242 | } |
| 2279 | let _ = cam.stop(); | 2243 | let _ = cam.stop(); |
| 2280 | let Some(f) = frame else { | 2244 | let Some(f) = frame else { |
| 2281 | - eprintln!( | 2245 | + eprintln!("SKIP real_camera id={id:?}: no frames (OBS Virtual Camera not started?)"); |
| 2282 | - "SKIP real_camera id={id:?}: no frames (OBS Virtual Camera not started?)" | ||
| 2283 | - ); | ||
| 2284 | return; | 2246 | return; |
| 2285 | }; | 2247 | }; |
| 2286 | let (w, h) = (f.width(), f.height()); | 2248 | let (w, h) = (f.width(), f.height()); |
| @@ -2291,9 +2253,7 @@ mod tests { | |||
| 2291 | .get(0..4) | 2253 | .get(0..4) |
| 2292 | .map(|p| (p[0], p[1], p[2], p[3])) | 2254 | .map(|p| (p[0], p[1], p[2], p[3])) |
| 2293 | .unwrap_or((0, 0, 0, 0)); | 2255 | .unwrap_or((0, 0, 0, 0)); |
| 2294 | - eprintln!( | 2256 | + eprintln!("camera id={id:?} {w}x{h} rgba0=({r0},{g0},{b0},{a0}) luma range {min}..{max}"); |
| 2295 | - "camera id={id:?} {w}x{h} rgba0=({r0},{g0},{b0},{a0}) luma range {min}..{max}" | ||
| 2296 | - ); | ||
| 2297 | assert!( | 2257 | assert!( |
| 2298 | max > min, | 2258 | max > min, |
| 2299 | "real camera frame must be non-uniform (luma range {min}..{max}); \ | 2259 | "real camera frame must be non-uniform (luma range {min}..{max}); \ |
| @@ -2394,10 +2354,7 @@ mod tests { | |||
| 2394 | assert_eq!(n, Some(buf.len())); | 2354 | assert_eq!(n, Some(buf.len())); |
| 2395 | assert_eq!(pcm_rms(&buf), 0.0, "muted must be silence"); | 2355 | assert_eq!(pcm_rms(&buf), 0.0, "muted must be silence"); |
| 2396 | // Level still saw pre-mute energy from the tone. | 2356 | // Level still saw pre-mute energy from the tone. |
| 2397 | - assert!( | 2357 | + assert!(src.level.get() > 0.01, "mic meter should move while muted"); |
| 2398 | - src.level.get() > 0.01, | ||
| 2399 | - "mic meter should move while muted" | ||
| 2400 | - ); | ||
| 2401 | } | 2358 | } |
| 2402 | 2359 | ||
| 2403 | #[test] | 2360 | #[test] |
| @@ -2561,13 +2518,10 @@ mod tests { | |||
| 2561 | // Keep producer alive for the duration of the test. | 2518 | // Keep producer alive for the duration of the test. |
| 2562 | let _keepalive = broadcast; | 2519 | let _keepalive = broadcast; |
| 2563 | 2520 | ||
| 2564 | - let remote = RemoteBroadcast::with_playback_policy( | 2521 | + let remote = |
| 2565 | - &path, | 2522 | + RemoteBroadcast::with_playback_policy(&path, consumer, PlaybackPolicy::unmanaged()) |
| 2566 | - consumer, | 2523 | + .await |
| 2567 | - PlaybackPolicy::unmanaged(), | 2524 | + .expect("catalog from LocalBroadcast"); |
| 2568 | - ) | ||
| 2569 | - .await | ||
| 2570 | - .expect("catalog from LocalBroadcast"); | ||
| 2571 | 2525 | ||
| 2572 | let (tx, rx) = std::sync::mpsc::sync_channel::<Vec<f32>>(64); | 2526 | let (tx, rx) = std::sync::mpsc::sync_channel::<Vec<f32>>(64); |
| 2573 | let backend = TapBackend { tx }; | 2527 | let backend = TapBackend { tx }; |
| @@ -2796,12 +2750,8 @@ mod tests { | |||
| 2796 | "video.clear must drop the source so privacy LED can go dark" | 2750 | "video.clear must drop the source so privacy LED can go dark" |
| 2797 | ); | 2751 | ); |
| 2798 | assert!( | 2752 | assert!( |
| 2799 | - !store | 2753 | + !store.snapshot().iter().any(|(k, _)| k == LOCAL_PREVIEW_KEY), |
| 2800 | - .snapshot() | ||
| 2801 | - .iter() | ||
| 2802 | - .any(|(k, _)| k == LOCAL_PREVIEW_KEY), | ||
| 2803 | "local preview frame must be cleared on release" | 2754 | "local preview frame must be cleared on release" |
| 2804 | ); | 2755 | ); |
| 2805 | } | 2756 | } |
| 2806 | - | ||
| 2807 | } | 2757 | } |
modified
crates/jolt-moq/src/lib.rs +11 -7 | @@ -43,12 +43,12 @@ pub mod av_media; | ||
| 43 | 43 | mod nv12_orient; |
| 44 | 44 | // V4L2 is Linux's camera interface and the phone does not offer it; there the |
| 45 | 45 | // camera is Java, reached through the two modules below. |
| 46 | -#[cfg(not(target_os = "android"))] | |
| 47 | -mod v4l2cam; | |
| 48 | 46 | #[cfg(target_os = "android")] |
| 49 | 47 | mod android_camera; |
| 50 | 48 | #[cfg(target_os = "android")] |
| 51 | 49 | mod android_jni; |
| 50 | +#[cfg(not(target_os = "android"))] | |
| 51 | +mod v4l2cam; | |
| 52 | 52 | |
| 53 | 53 | use std::collections::HashMap; |
| 54 | 54 | use std::ffi::{c_char, c_int}; |
| @@ -768,7 +768,9 @@ mod tests { | ||
| 768 | 768 | use std::ffi::{CStr, CString}; |
| 769 | 769 | |
| 770 | 770 | fn read(ptr: *const c_char) -> String { |
| 771 | - unsafe { CStr::from_ptr(ptr) }.to_string_lossy().into_owned() | |
| 771 | + unsafe { CStr::from_ptr(ptr) } | |
| 772 | + .to_string_lossy() | |
| 773 | + .into_owned() | |
| 772 | 774 | } |
| 773 | 775 | |
| 774 | 776 | /// There is one session in this library, so there is one in its tests, and |
| @@ -831,9 +833,8 @@ mod tests { | ||
| 831 | 833 | #[test] |
| 832 | 834 | fn a_server_that_is_not_a_url_answers_the_empty_string() { |
| 833 | 835 | let server = CString::new(" ").unwrap(); |
| 834 | - let url = read(unsafe { | |
| 835 | - joltmoq_sfu_url(server.as_ptr(), std::ptr::null(), std::ptr::null()) | |
| 836 | - }); | |
| 836 | + let url = | |
| 837 | + read(unsafe { joltmoq_sfu_url(server.as_ptr(), std::ptr::null(), std::ptr::null()) }); | |
| 837 | 838 | assert_eq!(url, ""); |
| 838 | 839 | } |
| 839 | 840 | |
| @@ -846,7 +847,10 @@ mod tests { | ||
| 846 | 847 | unsafe { joltmoq_can_dial(remote.as_ptr(), std::ptr::null()) }, |
| 847 | 848 | 0 |
| 848 | 849 | ); |
| 849 | - assert_eq!(unsafe { joltmoq_can_dial(remote.as_ptr(), jwt.as_ptr()) }, 1); | |
| 850 | + assert_eq!( | |
| 851 | + unsafe { joltmoq_can_dial(remote.as_ptr(), jwt.as_ptr()) }, | |
| 852 | + 1 | |
| 853 | + ); | |
| 850 | 854 | assert_eq!( |
| 851 | 855 | unsafe { joltmoq_can_dial(local.as_ptr(), std::ptr::null()) }, |
| 852 | 856 | 1 |
| @@ -43,12 +43,12 @@ pub mod av_media; | |||
| 43 | mod nv12_orient; | 43 | mod nv12_orient; |
| 44 | // V4L2 is Linux's camera interface and the phone does not offer it; there the | 44 | // V4L2 is Linux's camera interface and the phone does not offer it; there the |
| 45 | // camera is Java, reached through the two modules below. | 45 | // camera is Java, reached through the two modules below. |
| 46 | -#[cfg(not(target_os = "android"))] | ||
| 47 | -mod v4l2cam; | ||
| 48 | #[cfg(target_os = "android")] | 46 | #[cfg(target_os = "android")] |
| 49 | mod android_camera; | 47 | mod android_camera; |
| 50 | #[cfg(target_os = "android")] | 48 | #[cfg(target_os = "android")] |
| 51 | mod android_jni; | 49 | mod android_jni; |
| 50 | +#[cfg(not(target_os = "android"))] | ||
| 51 | +mod v4l2cam; | ||
| 52 | 52 | ||
| 53 | use std::collections::HashMap; | 53 | use std::collections::HashMap; |
| 54 | use std::ffi::{c_char, c_int}; | 54 | use std::ffi::{c_char, c_int}; |
| @@ -768,7 +768,9 @@ mod tests { | |||
| 768 | use std::ffi::{CStr, CString}; | 768 | use std::ffi::{CStr, CString}; |
| 769 | 769 | ||
| 770 | fn read(ptr: *const c_char) -> String { | 770 | fn read(ptr: *const c_char) -> String { |
| 771 | - unsafe { CStr::from_ptr(ptr) }.to_string_lossy().into_owned() | 771 | + unsafe { CStr::from_ptr(ptr) } |
| 772 | + .to_string_lossy() | ||
| 773 | + .into_owned() | ||
| 772 | } | 774 | } |
| 773 | 775 | ||
| 774 | /// There is one session in this library, so there is one in its tests, and | 776 | /// There is one session in this library, so there is one in its tests, and |
| @@ -831,9 +833,8 @@ mod tests { | |||
| 831 | #[test] | 833 | #[test] |
| 832 | fn a_server_that_is_not_a_url_answers_the_empty_string() { | 834 | fn a_server_that_is_not_a_url_answers_the_empty_string() { |
| 833 | let server = CString::new(" ").unwrap(); | 835 | let server = CString::new(" ").unwrap(); |
| 834 | - let url = read(unsafe { | 836 | + let url = |
| 835 | - joltmoq_sfu_url(server.as_ptr(), std::ptr::null(), std::ptr::null()) | 837 | + read(unsafe { joltmoq_sfu_url(server.as_ptr(), std::ptr::null(), std::ptr::null()) }); |
| 836 | - }); | ||
| 837 | assert_eq!(url, ""); | 838 | assert_eq!(url, ""); |
| 838 | } | 839 | } |
| 839 | 840 | ||
| @@ -846,7 +847,10 @@ mod tests { | |||
| 846 | unsafe { joltmoq_can_dial(remote.as_ptr(), std::ptr::null()) }, | 847 | unsafe { joltmoq_can_dial(remote.as_ptr(), std::ptr::null()) }, |
| 847 | 0 | 848 | 0 |
| 848 | ); | 849 | ); |
| 849 | - assert_eq!(unsafe { joltmoq_can_dial(remote.as_ptr(), jwt.as_ptr()) }, 1); | 850 | + assert_eq!( |
| 851 | + unsafe { joltmoq_can_dial(remote.as_ptr(), jwt.as_ptr()) }, | ||
| 852 | + 1 | ||
| 853 | + ); | ||
| 850 | assert_eq!( | 854 | assert_eq!( |
| 851 | unsafe { joltmoq_can_dial(local.as_ptr(), std::ptr::null()) }, | 855 | unsafe { joltmoq_can_dial(local.as_ptr(), std::ptr::null()) }, |
| 852 | 1 | 856 | 1 |
modified
crates/jolt-moq/src/v4l2cam.rs +3 -6 | @@ -96,11 +96,7 @@ impl V4l2MmapCapture { | ||
| 96 | 96 | // Try requested size with YUYV first (loopback native), then MJPG, |
| 97 | 97 | // then let the driver pick (0x0 keeps current). |
| 98 | 98 | let mut actual: Option<Format> = None; |
| 99 | - for (req_w, req_h, fourcc) in [ | |
| 100 | - (w, h, *b"YUYV"), | |
| 101 | - (w, h, *b"MJPG"), | |
| 102 | - (0, 0, *b"YUYV"), | |
| 103 | - ] { | |
| 99 | + for (req_w, req_h, fourcc) in [(w, h, *b"YUYV"), (w, h, *b"MJPG"), (0, 0, *b"YUYV")] { | |
| 104 | 100 | let desired = Format { |
| 105 | 101 | width: req_w, |
| 106 | 102 | height: req_h, |
| @@ -421,7 +417,8 @@ mod tests { | ||
| 421 | 417 | let mut min = 255u8; |
| 422 | 418 | let mut max = 0u8; |
| 423 | 419 | for px in bytes.chunks_exact(4) { |
| 424 | - let l = ((77u32 * px[0] as u32 + 150u32 * px[1] as u32 + 29u32 * px[2] as u32) >> 8) as u8; | |
| 420 | + let l = | |
| 421 | + ((77u32 * px[0] as u32 + 150u32 * px[1] as u32 + 29u32 * px[2] as u32) >> 8) as u8; | |
| 425 | 422 | min = min.min(l); |
| 426 | 423 | max = max.max(l); |
| 427 | 424 | } |
| @@ -96,11 +96,7 @@ impl V4l2MmapCapture { | |||
| 96 | // Try requested size with YUYV first (loopback native), then MJPG, | 96 | // Try requested size with YUYV first (loopback native), then MJPG, |
| 97 | // then let the driver pick (0x0 keeps current). | 97 | // then let the driver pick (0x0 keeps current). |
| 98 | let mut actual: Option<Format> = None; | 98 | let mut actual: Option<Format> = None; |
| 99 | - for (req_w, req_h, fourcc) in [ | 99 | + for (req_w, req_h, fourcc) in [(w, h, *b"YUYV"), (w, h, *b"MJPG"), (0, 0, *b"YUYV")] { |
| 100 | - (w, h, *b"YUYV"), | ||
| 101 | - (w, h, *b"MJPG"), | ||
| 102 | - (0, 0, *b"YUYV"), | ||
| 103 | - ] { | ||
| 104 | let desired = Format { | 100 | let desired = Format { |
| 105 | width: req_w, | 101 | width: req_w, |
| 106 | height: req_h, | 102 | height: req_h, |
| @@ -421,7 +417,8 @@ mod tests { | |||
| 421 | let mut min = 255u8; | 417 | let mut min = 255u8; |
| 422 | let mut max = 0u8; | 418 | let mut max = 0u8; |
| 423 | for px in bytes.chunks_exact(4) { | 419 | for px in bytes.chunks_exact(4) { |
| 424 | - let l = ((77u32 * px[0] as u32 + 150u32 * px[1] as u32 + 29u32 * px[2] as u32) >> 8) as u8; | 420 | + let l = |
| 421 | + ((77u32 * px[0] as u32 + 150u32 * px[1] as u32 + 29u32 * px[2] as u32) >> 8) as u8; | ||
| 425 | min = min.min(l); | 422 | min = min.min(l); |
| 426 | max = max.max(l); | 423 | max = max.max(l); |
| 427 | } | 424 | } |
modified
crates/jolt-tui/src/graphics.rs +18 -8 | @@ -50,9 +50,8 @@ pub fn png_size(bytes: &[u8]) -> Option<(u32, u32)> { | ||
| 50 | 50 | if bytes.len() < 24 || bytes[..8] != SIGNATURE || &bytes[12..16] != b"IHDR" { |
| 51 | 51 | return None; |
| 52 | 52 | } |
| 53 | - let read = |at: usize| { | |
| 54 | - u32::from_be_bytes([bytes[at], bytes[at + 1], bytes[at + 2], bytes[at + 3]]) | |
| 55 | - }; | |
| 53 | + let read = | |
| 54 | + |at: usize| u32::from_be_bytes([bytes[at], bytes[at + 1], bytes[at + 2], bytes[at + 3]]); | |
| 56 | 55 | let (w, h) = (read(16), read(20)); |
| 57 | 56 | (w > 0 && h > 0).then_some((w, h)) |
| 58 | 57 | } |
| @@ -206,7 +205,12 @@ impl Graphics { | ||
| 206 | 205 | if still { |
| 207 | 206 | self.placed.insert(node, was); |
| 208 | 207 | } else { |
| 209 | - write!(out, "\x1b_Ga=d,d=i,i={},p={},q=2\x1b\\", self.id(&was.path), node)?; | |
| 208 | + write!( | |
| 209 | + out, | |
| 210 | + "\x1b_Ga=d,d=i,i={},p={},q=2\x1b\\", | |
| 211 | + self.id(&was.path), | |
| 212 | + node | |
| 213 | + )?; | |
| 210 | 214 | } |
| 211 | 215 | } |
| 212 | 216 | for placement in now { |
| @@ -300,11 +304,14 @@ impl Graphics { | ||
| 300 | 304 | |
| 301 | 305 | /// Standard base64, which is what the protocol's payload is written in. |
| 302 | 306 | fn base64(bytes: &[u8]) -> String { |
| 303 | - const ALPHABET: &[u8; 64] = | |
| 304 | - b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; | |
| 307 | + const ALPHABET: &[u8; 64] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; | |
| 305 | 308 | let mut out = String::with_capacity(bytes.len().div_ceil(3) * 4); |
| 306 | 309 | for group in bytes.chunks(3) { |
| 307 | - let b = [group[0], *group.get(1).unwrap_or(&0), *group.get(2).unwrap_or(&0)]; | |
| 310 | + let b = [ | |
| 311 | + group[0], | |
| 312 | + *group.get(1).unwrap_or(&0), | |
| 313 | + *group.get(2).unwrap_or(&0), | |
| 314 | + ]; | |
| 308 | 315 | let n = u32::from_be_bytes([0, b[0], b[1], b[2]]); |
| 309 | 316 | let mut quad = [0u8; 4]; |
| 310 | 317 | for (i, slot) in quad.iter_mut().enumerate() { |
| @@ -330,7 +337,10 @@ mod tests { | ||
| 330 | 337 | assert_eq!(base64(b"fo"), "Zm8="); |
| 331 | 338 | assert_eq!(base64(b"foo"), "Zm9v"); |
| 332 | 339 | assert_eq!(base64(b"foob"), "Zm9vYg=="); |
| 333 | - assert_eq!(base64(b"any carnal pleasure."), "YW55IGNhcm5hbCBwbGVhc3VyZS4="); | |
| 340 | + assert_eq!( | |
| 341 | + base64(b"any carnal pleasure."), | |
| 342 | + "YW55IGNhcm5hbCBwbGVhc3VyZS4=" | |
| 343 | + ); | |
| 334 | 344 | } |
| 335 | 345 | |
| 336 | 346 | #[test] |
| @@ -50,9 +50,8 @@ pub fn png_size(bytes: &[u8]) -> Option<(u32, u32)> { | |||
| 50 | if bytes.len() < 24 || bytes[..8] != SIGNATURE || &bytes[12..16] != b"IHDR" { | 50 | if bytes.len() < 24 || bytes[..8] != SIGNATURE || &bytes[12..16] != b"IHDR" { |
| 51 | return None; | 51 | return None; |
| 52 | } | 52 | } |
| 53 | - let read = |at: usize| { | 53 | + let read = |
| 54 | - u32::from_be_bytes([bytes[at], bytes[at + 1], bytes[at + 2], bytes[at + 3]]) | 54 | + |at: usize| u32::from_be_bytes([bytes[at], bytes[at + 1], bytes[at + 2], bytes[at + 3]]); |
| 55 | - }; | ||
| 56 | let (w, h) = (read(16), read(20)); | 55 | let (w, h) = (read(16), read(20)); |
| 57 | (w > 0 && h > 0).then_some((w, h)) | 56 | (w > 0 && h > 0).then_some((w, h)) |
| 58 | } | 57 | } |
| @@ -206,7 +205,12 @@ impl Graphics { | |||
| 206 | if still { | 205 | if still { |
| 207 | self.placed.insert(node, was); | 206 | self.placed.insert(node, was); |
| 208 | } else { | 207 | } else { |
| 209 | - write!(out, "\x1b_Ga=d,d=i,i={},p={},q=2\x1b\\", self.id(&was.path), node)?; | 208 | + write!( |
| 209 | + out, | ||
| 210 | + "\x1b_Ga=d,d=i,i={},p={},q=2\x1b\\", | ||
| 211 | + self.id(&was.path), | ||
| 212 | + node | ||
| 213 | + )?; | ||
| 210 | } | 214 | } |
| 211 | } | 215 | } |
| 212 | for placement in now { | 216 | for placement in now { |
| @@ -300,11 +304,14 @@ impl Graphics { | |||
| 300 | 304 | ||
| 301 | /// Standard base64, which is what the protocol's payload is written in. | 305 | /// Standard base64, which is what the protocol's payload is written in. |
| 302 | fn base64(bytes: &[u8]) -> String { | 306 | fn base64(bytes: &[u8]) -> String { |
| 303 | - const ALPHABET: &[u8; 64] = | 307 | + const ALPHABET: &[u8; 64] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; |
| 304 | - b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; | ||
| 305 | let mut out = String::with_capacity(bytes.len().div_ceil(3) * 4); | 308 | let mut out = String::with_capacity(bytes.len().div_ceil(3) * 4); |
| 306 | for group in bytes.chunks(3) { | 309 | for group in bytes.chunks(3) { |
| 307 | - let b = [group[0], *group.get(1).unwrap_or(&0), *group.get(2).unwrap_or(&0)]; | 310 | + let b = [ |
| 311 | + group[0], | ||
| 312 | + *group.get(1).unwrap_or(&0), | ||
| 313 | + *group.get(2).unwrap_or(&0), | ||
| 314 | + ]; | ||
| 308 | let n = u32::from_be_bytes([0, b[0], b[1], b[2]]); | 315 | let n = u32::from_be_bytes([0, b[0], b[1], b[2]]); |
| 309 | let mut quad = [0u8; 4]; | 316 | let mut quad = [0u8; 4]; |
| 310 | for (i, slot) in quad.iter_mut().enumerate() { | 317 | for (i, slot) in quad.iter_mut().enumerate() { |
| @@ -330,7 +337,10 @@ mod tests { | |||
| 330 | assert_eq!(base64(b"fo"), "Zm8="); | 337 | assert_eq!(base64(b"fo"), "Zm8="); |
| 331 | assert_eq!(base64(b"foo"), "Zm9v"); | 338 | assert_eq!(base64(b"foo"), "Zm9v"); |
| 332 | assert_eq!(base64(b"foob"), "Zm9vYg=="); | 339 | assert_eq!(base64(b"foob"), "Zm9vYg=="); |
| 333 | - assert_eq!(base64(b"any carnal pleasure."), "YW55IGNhcm5hbCBwbGVhc3VyZS4="); | 340 | + assert_eq!( |
| 341 | + base64(b"any carnal pleasure."), | ||
| 342 | + "YW55IGNhcm5hbCBwbGVhc3VyZS4=" | ||
| 343 | + ); | ||
| 334 | } | 344 | } |
| 335 | 345 | ||
| 336 | #[test] | 346 | #[test] |
modified
crates/jolt-tui/src/paint.rs +9 -7 | @@ -9,8 +9,8 @@ | ||
| 9 | 9 | //! over the whole screen, so it is painted after everything else at the size it |
| 10 | 10 | //! asked for, in the middle. |
| 11 | 11 | |
| 12 | -use crate::layout::{self, wrap, Align}; | |
| 13 | 12 | use crate::graphics; |
| 13 | +use crate::layout::{self, wrap, Align}; | |
| 14 | 14 | use crate::screen::{self, attr, Color, Rect, Screen, Style}; |
| 15 | 15 | use crate::tree::{Props, Tag, Tree}; |
| 16 | 16 | |
| @@ -283,8 +283,13 @@ impl Painter<'_> { | ||
| 283 | 283 | return; |
| 284 | 284 | } |
| 285 | 285 | if !graphics::supported() { |
| 286 | - self.screen | |
| 287 | - .text(area.x, area.y, area.w, layout::PICTURE, style.with(attr::DIM)); | |
| 286 | + self.screen.text( | |
| 287 | + area.x, | |
| 288 | + area.y, | |
| 289 | + area.w, | |
| 290 | + layout::PICTURE, | |
| 291 | + style.with(attr::DIM), | |
| 292 | + ); | |
| 288 | 293 | return; |
| 289 | 294 | } |
| 290 | 295 | // The column hands a child its whole width; a picture takes only what |
| @@ -384,10 +389,7 @@ impl Painter<'_> { | ||
| 384 | 389 | let col = if showing_placeholder { |
| 385 | 390 | 0 |
| 386 | 391 | } else { |
| 387 | - let typed: String = visible | |
| 388 | - .chars() | |
| 389 | - .take(caret.saturating_sub(from)) | |
| 390 | - .collect(); | |
| 392 | + let typed: String = visible.chars().take(caret.saturating_sub(from)).collect(); | |
| 391 | 393 | (screen::text_cols(&typed)).min(area.w.saturating_sub(1)) |
| 392 | 394 | }; |
| 393 | 395 | self.out.cursor = Some((area.x.saturating_add(col), area.y + i as u16)); |
| @@ -9,8 +9,8 @@ | |||
| 9 | //! over the whole screen, so it is painted after everything else at the size it | 9 | //! over the whole screen, so it is painted after everything else at the size it |
| 10 | //! asked for, in the middle. | 10 | //! asked for, in the middle. |
| 11 | 11 | ||
| 12 | -use crate::layout::{self, wrap, Align}; | ||
| 13 | use crate::graphics; | 12 | use crate::graphics; |
| 13 | +use crate::layout::{self, wrap, Align}; | ||
| 14 | use crate::screen::{self, attr, Color, Rect, Screen, Style}; | 14 | use crate::screen::{self, attr, Color, Rect, Screen, Style}; |
| 15 | use crate::tree::{Props, Tag, Tree}; | 15 | use crate::tree::{Props, Tag, Tree}; |
| 16 | 16 | ||
| @@ -283,8 +283,13 @@ impl Painter<'_> { | |||
| 283 | return; | 283 | return; |
| 284 | } | 284 | } |
| 285 | if !graphics::supported() { | 285 | if !graphics::supported() { |
| 286 | - self.screen | 286 | + self.screen.text( |
| 287 | - .text(area.x, area.y, area.w, layout::PICTURE, style.with(attr::DIM)); | 287 | + area.x, |
| 288 | + area.y, | ||
| 289 | + area.w, | ||
| 290 | + layout::PICTURE, | ||
| 291 | + style.with(attr::DIM), | ||
| 292 | + ); | ||
| 288 | return; | 293 | return; |
| 289 | } | 294 | } |
| 290 | // The column hands a child its whole width; a picture takes only what | 295 | // The column hands a child its whole width; a picture takes only what |
| @@ -384,10 +389,7 @@ impl Painter<'_> { | |||
| 384 | let col = if showing_placeholder { | 389 | let col = if showing_placeholder { |
| 385 | 0 | 390 | 0 |
| 386 | } else { | 391 | } else { |
| 387 | - let typed: String = visible | 392 | + let typed: String = visible.chars().take(caret.saturating_sub(from)).collect(); |
| 388 | - .chars() | ||
| 389 | - .take(caret.saturating_sub(from)) | ||
| 390 | - .collect(); | ||
| 391 | (screen::text_cols(&typed)).min(area.w.saturating_sub(1)) | 393 | (screen::text_cols(&typed)).min(area.w.saturating_sub(1)) |
| 392 | }; | 394 | }; |
| 393 | self.out.cursor = Some((area.x.saturating_add(col), area.y + i as u16)); | 395 | self.out.cursor = Some((area.x.saturating_add(col), area.y + i as u16)); |
modified
crates/jolt-tui/src/term.rs +1 -1 | @@ -16,8 +16,8 @@ use crossterm::terminal::{ | ||
| 16 | 16 | }; |
| 17 | 17 | use crossterm::{cursor, execute, queue, style}; |
| 18 | 18 | |
| 19 | -use crate::keys; | |
| 20 | 19 | use crate::graphics::{self, Graphics, Placement}; |
| 20 | +use crate::keys; | |
| 21 | 21 | use crate::screen::{self, attr, Color, Screen, Style}; |
| 22 | 22 | |
| 23 | 23 | /// How far one notch of the wheel moves a list, in rows. |
| @@ -16,8 +16,8 @@ use crossterm::terminal::{ | |||
| 16 | }; | 16 | }; |
| 17 | use crossterm::{cursor, execute, queue, style}; | 17 | use crossterm::{cursor, execute, queue, style}; |
| 18 | 18 | ||
| 19 | -use crate::keys; | ||
| 20 | use crate::graphics::{self, Graphics, Placement}; | 19 | use crate::graphics::{self, Graphics, Placement}; |
| 20 | +use crate::keys; | ||
| 21 | use crate::screen::{self, attr, Color, Screen, Style}; | 21 | use crate::screen::{self, attr, Color, Screen, Style}; |
| 22 | 22 | ||
| 23 | /// How far one notch of the wheel moves a list, in rows. | 23 | /// How far one notch of the wheel moves a list, in rows. |
modified
crates/jolt-tui/src/tests.rs +14 -4 | @@ -316,12 +316,22 @@ fn the_wheel_scrolls_the_list_under_the_pointer_not_the_first_one_painted() { | ||
| 316 | 316 | let sidebar = node(&mut ui, row, "scroll", &[]); |
| 317 | 317 | ui.tree.set(sidebar, "width-request", Value::Num(10.0)); |
| 318 | 318 | for i in 0..6 { |
| 319 | - node(&mut ui, sidebar, "label", &[("label", &format!("chat {i}"))]); | |
| 319 | + node( | |
| 320 | + &mut ui, | |
| 321 | + sidebar, | |
| 322 | + "label", | |
| 323 | + &[("label", &format!("chat {i}"))], | |
| 324 | + ); | |
| 320 | 325 | } |
| 321 | 326 | let backlog = node(&mut ui, row, "scroll", &[]); |
| 322 | 327 | ui.tree.set(backlog, "width-request", Value::Num(10.0)); |
| 323 | 328 | for i in 0..6 { |
| 324 | - node(&mut ui, backlog, "label", &[("label", &format!("line {i}"))]); | |
| 329 | + node( | |
| 330 | + &mut ui, | |
| 331 | + backlog, | |
| 332 | + "label", | |
| 333 | + &[("label", &format!("line {i}"))], | |
| 334 | + ); | |
| 325 | 335 | } |
| 326 | 336 | ui.frame(); |
| 327 | 337 | assert_eq!(ui.screen.line(0), "chat 0 line 0"); |
| @@ -680,9 +690,9 @@ fn a_sticky_viewport_opens_at_the_bottom_and_stays_there() { | ||
| 680 | 690 | ui.frame(); |
| 681 | 691 | assert_eq!(ui.screen.line(0), "line 1"); |
| 682 | 692 | ui.tree.clear_props(scroll); |
| 683 | - ui.tree.set(scroll, "scroll-key", Value::Str("backlog".into())); | |
| 684 | 693 | ui.tree |
| 685 | - .set(scroll, "stick-to-bottom", Value::Bool(true)); | |
| 694 | + .set(scroll, "scroll-key", Value::Str("backlog".into())); | |
| 695 | + ui.tree.set(scroll, "stick-to-bottom", Value::Bool(true)); | |
| 686 | 696 | ui.frame(); |
| 687 | 697 | assert_eq!(ui.screen.line(0), "line 1"); |
| 688 | 698 | |
| @@ -316,12 +316,22 @@ fn the_wheel_scrolls_the_list_under_the_pointer_not_the_first_one_painted() { | |||
| 316 | let sidebar = node(&mut ui, row, "scroll", &[]); | 316 | let sidebar = node(&mut ui, row, "scroll", &[]); |
| 317 | ui.tree.set(sidebar, "width-request", Value::Num(10.0)); | 317 | ui.tree.set(sidebar, "width-request", Value::Num(10.0)); |
| 318 | for i in 0..6 { | 318 | for i in 0..6 { |
| 319 | - node(&mut ui, sidebar, "label", &[("label", &format!("chat {i}"))]); | 319 | + node( |
| 320 | + &mut ui, | ||
| 321 | + sidebar, | ||
| 322 | + "label", | ||
| 323 | + &[("label", &format!("chat {i}"))], | ||
| 324 | + ); | ||
| 320 | } | 325 | } |
| 321 | let backlog = node(&mut ui, row, "scroll", &[]); | 326 | let backlog = node(&mut ui, row, "scroll", &[]); |
| 322 | ui.tree.set(backlog, "width-request", Value::Num(10.0)); | 327 | ui.tree.set(backlog, "width-request", Value::Num(10.0)); |
| 323 | for i in 0..6 { | 328 | for i in 0..6 { |
| 324 | - node(&mut ui, backlog, "label", &[("label", &format!("line {i}"))]); | 329 | + node( |
| 330 | + &mut ui, | ||
| 331 | + backlog, | ||
| 332 | + "label", | ||
| 333 | + &[("label", &format!("line {i}"))], | ||
| 334 | + ); | ||
| 325 | } | 335 | } |
| 326 | ui.frame(); | 336 | ui.frame(); |
| 327 | assert_eq!(ui.screen.line(0), "chat 0 line 0"); | 337 | assert_eq!(ui.screen.line(0), "chat 0 line 0"); |
| @@ -680,9 +690,9 @@ fn a_sticky_viewport_opens_at_the_bottom_and_stays_there() { | |||
| 680 | ui.frame(); | 690 | ui.frame(); |
| 681 | assert_eq!(ui.screen.line(0), "line 1"); | 691 | assert_eq!(ui.screen.line(0), "line 1"); |
| 682 | ui.tree.clear_props(scroll); | 692 | ui.tree.clear_props(scroll); |
| 683 | - ui.tree.set(scroll, "scroll-key", Value::Str("backlog".into())); | ||
| 684 | ui.tree | 693 | ui.tree |
| 685 | - .set(scroll, "stick-to-bottom", Value::Bool(true)); | 694 | + .set(scroll, "scroll-key", Value::Str("backlog".into())); |
| 695 | + ui.tree.set(scroll, "stick-to-bottom", Value::Bool(true)); | ||
| 686 | ui.frame(); | 696 | ui.frame(); |
| 687 | assert_eq!(ui.screen.line(0), "line 1"); | 697 | assert_eq!(ui.screen.line(0), "line 1"); |
| 688 | 698 | ||
modified
crates/jolt-vidya/src/lib.rs +3 -1 | @@ -273,7 +273,9 @@ pub unsafe extern "C" fn vidya_checkbox(label: *const c_char, checked: *mut c_in | ||
| 273 | 273 | pub unsafe extern "C" fn vidya_checkbox_value(label: *const c_char, checked: c_int) -> c_int { |
| 274 | 274 | let label = borrowed_str(label); |
| 275 | 275 | let current = checked != 0; |
| 276 | - with_ui(current, |ui, theme| ui::checkbox(ui, theme, current, &label).0) as c_int | |
| 276 | + with_ui(current, |ui, theme| { | |
| 277 | + ui::checkbox(ui, theme, current, &label).0 | |
| 278 | + }) as c_int | |
| 277 | 279 | } |
| 278 | 280 | |
| 279 | 281 | /// # Safety |
| @@ -273,7 +273,9 @@ pub unsafe extern "C" fn vidya_checkbox(label: *const c_char, checked: *mut c_in | |||
| 273 | pub unsafe extern "C" fn vidya_checkbox_value(label: *const c_char, checked: c_int) -> c_int { | 273 | pub unsafe extern "C" fn vidya_checkbox_value(label: *const c_char, checked: c_int) -> c_int { |
| 274 | let label = borrowed_str(label); | 274 | let label = borrowed_str(label); |
| 275 | let current = checked != 0; | 275 | let current = checked != 0; |
| 276 | - with_ui(current, |ui, theme| ui::checkbox(ui, theme, current, &label).0) as c_int | 276 | + with_ui(current, |ui, theme| { |
| 277 | + ui::checkbox(ui, theme, current, &label).0 | ||
| 278 | + }) as c_int | ||
| 277 | } | 279 | } |
| 278 | 280 | ||
| 279 | /// # Safety | 281 | /// # Safety |
modified
crates/jolt-vidya/src/tree.rs +31 -23 | @@ -322,10 +322,8 @@ impl Tree { | ||
| 322 | 322 | if rgba.len() != expected { |
| 323 | 323 | return false; |
| 324 | 324 | } |
| 325 | - let image = egui::ColorImage::from_rgba_unmultiplied( | |
| 326 | - [width as usize, height as usize], | |
| 327 | - rgba, | |
| 328 | - ); | |
| 325 | + let image = | |
| 326 | + egui::ColorImage::from_rgba_unmultiplied([width as usize, height as usize], rgba); | |
| 329 | 327 | // Overwrites whatever had not been painted yet: the newest frame is |
| 330 | 328 | // the only one worth showing, and a backlog of stale ones is latency. |
| 331 | 329 | self.feeds.entry(key.to_owned()).or_default().pending = Some(image); |
| @@ -1069,7 +1067,6 @@ impl Tree { | ||
| 1069 | 1067 | // whatever it is given, and MAX minus anything is still |
| 1070 | 1068 | // MAX — an offset the content can never reach, which left |
| 1071 | 1069 | // the area painting nothing at all. |
| 1072 | - | |
| 1073 | 1070 | }); |
| 1074 | 1071 | |
| 1075 | 1072 | // Say when the view leaves the end and when it comes back, so |
| @@ -1091,7 +1088,11 @@ impl Tree { | ||
| 1091 | 1088 | let end_key = key.with("at_end"); |
| 1092 | 1089 | let away_key = key.with("away_frames"); |
| 1093 | 1090 | let away_frames = ui.ctx().data(|d| d.get_temp::<u32>(away_key)).unwrap_or(0); |
| 1094 | - let away_frames = if at_end { 0 } else { away_frames.saturating_add(1) }; | |
| 1091 | + let away_frames = if at_end { | |
| 1092 | + 0 | |
| 1093 | + } else { | |
| 1094 | + away_frames.saturating_add(1) | |
| 1095 | + }; | |
| 1095 | 1096 | ui.ctx().data_mut(|d| d.insert_temp(away_key, away_frames)); |
| 1096 | 1097 | |
| 1097 | 1098 | let settled = if at_end { |
| @@ -1223,10 +1224,7 @@ impl Tree { | ||
| 1223 | 1224 | .. |
| 1224 | 1225 | } if modifiers.command |
| 1225 | 1226 | ) |
| 1226 | - }) && !i | |
| 1227 | - .events | |
| 1228 | - .iter() | |
| 1229 | - .any(|e| matches!(e, egui::Event::Paste(_))) | |
| 1227 | + }) && !i.events.iter().any(|e| matches!(e, egui::Event::Paste(_))) | |
| 1230 | 1228 | }); |
| 1231 | 1229 | if paste_without_text { |
| 1232 | 1230 | self.emit(id, "paste-empty", String::new(), 0.0); |
| @@ -1283,8 +1281,11 @@ impl Tree { | ||
| 1283 | 1281 | .next() |
| 1284 | 1282 | .map(|c| c.to_uppercase().to_string()) |
| 1285 | 1283 | .unwrap_or_else(|| "?".to_owned()); |
| 1286 | - ui.painter() | |
| 1287 | - .circle_filled(rect.center(), size * 0.5, name_colour(&label, theme)); | |
| 1284 | + ui.painter().circle_filled( | |
| 1285 | + rect.center(), | |
| 1286 | + size * 0.5, | |
| 1287 | + name_colour(&label, theme), | |
| 1288 | + ); | |
| 1288 | 1289 | ui.painter().text( |
| 1289 | 1290 | rect.center(), |
| 1290 | 1291 | Align2::CENTER_CENTER, |
| @@ -1416,10 +1417,8 @@ impl Tree { | ||
| 1416 | 1417 | return; |
| 1417 | 1418 | } |
| 1418 | 1419 | let scale = (space.x / size.x).min(space.y / size.y); |
| 1419 | - let (rect, response) = | |
| 1420 | - ui.allocate_exact_size(space, egui::Sense::click()); | |
| 1421 | - let painted = | |
| 1422 | - egui::Rect::from_center_size(rect.center(), size * scale); | |
| 1420 | + let (rect, response) = ui.allocate_exact_size(space, egui::Sense::click()); | |
| 1421 | + let painted = egui::Rect::from_center_size(rect.center(), size * scale); | |
| 1423 | 1422 | egui::Image::new(egui::load::SizedTexture::new(texture.id(), size * scale)) |
| 1424 | 1423 | .paint_at(ui, painted); |
| 1425 | 1424 | if response.clicked() { |
| @@ -1483,9 +1482,7 @@ impl Tree { | ||
| 1483 | 1482 | // otherwise for one of them. A row that sits at the bottom of a screen |
| 1484 | 1483 | // wants its space above it, not under it, and that is not a thing a |
| 1485 | 1484 | // single number can express. |
| 1486 | - let side = |key: &str| { | |
| 1487 | - props.num(key, props.num("margin", 0.0)).clamp(0.0, 127.0) as i8 | |
| 1488 | - }; | |
| 1485 | + let side = |key: &str| props.num(key, props.num("margin", 0.0)).clamp(0.0, 127.0) as i8; | |
| 1489 | 1486 | let margin = Margin { |
| 1490 | 1487 | left: side("margin-left"), |
| 1491 | 1488 | right: side("margin-right"), |
| @@ -1571,7 +1568,9 @@ mod tests { | ||
| 1571 | 1568 | let ctx = egui::Context::default(); |
| 1572 | 1569 | |
| 1573 | 1570 | let mut input = egui::RawInput::default(); |
| 1574 | - input.events.push(egui::Event::PointerMoved(egui::pos2(20.0, 20.0))); | |
| 1571 | + input | |
| 1572 | + .events | |
| 1573 | + .push(egui::Event::PointerMoved(egui::pos2(20.0, 20.0))); | |
| 1575 | 1574 | let _ = ctx.run(input.clone(), |ctx| { |
| 1576 | 1575 | egui::CentralPanel::default().show(ctx, |ui| tree.paint(ui, &theme)); |
| 1577 | 1576 | }); |
| @@ -1591,7 +1590,9 @@ mod tests { | ||
| 1591 | 1590 | let card = tree.new_node("window"); |
| 1592 | 1591 | tree.append(pill, card); |
| 1593 | 1592 | let mut input = egui::RawInput::default(); |
| 1594 | - input.events.push(egui::Event::PointerMoved(egui::pos2(20.0, 20.0))); | |
| 1593 | + input | |
| 1594 | + .events | |
| 1595 | + .push(egui::Event::PointerMoved(egui::pos2(20.0, 20.0))); | |
| 1595 | 1596 | let _ = ctx.run(input, |ctx| { |
| 1596 | 1597 | egui::CentralPanel::default().show(ctx, |ui| tree.paint(ui, &theme)); |
| 1597 | 1598 | }); |
| @@ -1741,7 +1742,10 @@ mod tests { | ||
| 1741 | 1742 | let mut tree = Tree::default(); |
| 1742 | 1743 | let id = tree.new_node("carousel"); |
| 1743 | 1744 | assert!(tree.exists(id)); |
| 1744 | - assert_eq!(tree.slot(id).unwrap().tag, Tag::Unknown("carousel".to_owned())); | |
| 1745 | + assert_eq!( | |
| 1746 | + tree.slot(id).unwrap().tag, | |
| 1747 | + Tag::Unknown("carousel".to_owned()) | |
| 1748 | + ); | |
| 1745 | 1749 | } |
| 1746 | 1750 | #[test] |
| 1747 | 1751 | fn dump_is_hiccup_of_what_the_tree_holds() { |
| @@ -1765,7 +1769,11 @@ mod tests { | ||
| 1765 | 1769 | fn dump_keeps_an_unknown_tag_and_escapes_a_string() { |
| 1766 | 1770 | let mut tree = Tree::default(); |
| 1767 | 1771 | let id = tree.new_node("carousel"); |
| 1768 | - tree.set(id, "label", Value::Str("a \"quote\"\nand a line".to_owned())); | |
| 1772 | + tree.set( | |
| 1773 | + id, | |
| 1774 | + "label", | |
| 1775 | + Value::Str("a \"quote\"\nand a line".to_owned()), | |
| 1776 | + ); | |
| 1769 | 1777 | assert_eq!( |
| 1770 | 1778 | tree.dump(id), |
| 1771 | 1779 | "[:carousel {:label \"a \\\"quote\\\"\\nand a line\"}]" |
| @@ -322,10 +322,8 @@ impl Tree { | |||
| 322 | if rgba.len() != expected { | 322 | if rgba.len() != expected { |
| 323 | return false; | 323 | return false; |
| 324 | } | 324 | } |
| 325 | - let image = egui::ColorImage::from_rgba_unmultiplied( | 325 | + let image = |
| 326 | - [width as usize, height as usize], | 326 | + egui::ColorImage::from_rgba_unmultiplied([width as usize, height as usize], rgba); |
| 327 | - rgba, | ||
| 328 | - ); | ||
| 329 | // Overwrites whatever had not been painted yet: the newest frame is | 327 | // Overwrites whatever had not been painted yet: the newest frame is |
| 330 | // the only one worth showing, and a backlog of stale ones is latency. | 328 | // the only one worth showing, and a backlog of stale ones is latency. |
| 331 | self.feeds.entry(key.to_owned()).or_default().pending = Some(image); | 329 | self.feeds.entry(key.to_owned()).or_default().pending = Some(image); |
| @@ -1069,7 +1067,6 @@ impl Tree { | |||
| 1069 | // whatever it is given, and MAX minus anything is still | 1067 | // whatever it is given, and MAX minus anything is still |
| 1070 | // MAX — an offset the content can never reach, which left | 1068 | // MAX — an offset the content can never reach, which left |
| 1071 | // the area painting nothing at all. | 1069 | // the area painting nothing at all. |
| 1072 | - | ||
| 1073 | }); | 1070 | }); |
| 1074 | 1071 | ||
| 1075 | // Say when the view leaves the end and when it comes back, so | 1072 | // Say when the view leaves the end and when it comes back, so |
| @@ -1091,7 +1088,11 @@ impl Tree { | |||
| 1091 | let end_key = key.with("at_end"); | 1088 | let end_key = key.with("at_end"); |
| 1092 | let away_key = key.with("away_frames"); | 1089 | let away_key = key.with("away_frames"); |
| 1093 | let away_frames = ui.ctx().data(|d| d.get_temp::<u32>(away_key)).unwrap_or(0); | 1090 | let away_frames = ui.ctx().data(|d| d.get_temp::<u32>(away_key)).unwrap_or(0); |
| 1094 | - let away_frames = if at_end { 0 } else { away_frames.saturating_add(1) }; | 1091 | + let away_frames = if at_end { |
| 1092 | + 0 | ||
| 1093 | + } else { | ||
| 1094 | + away_frames.saturating_add(1) | ||
| 1095 | + }; | ||
| 1095 | ui.ctx().data_mut(|d| d.insert_temp(away_key, away_frames)); | 1096 | ui.ctx().data_mut(|d| d.insert_temp(away_key, away_frames)); |
| 1096 | 1097 | ||
| 1097 | let settled = if at_end { | 1098 | let settled = if at_end { |
| @@ -1223,10 +1224,7 @@ impl Tree { | |||
| 1223 | .. | 1224 | .. |
| 1224 | } if modifiers.command | 1225 | } if modifiers.command |
| 1225 | ) | 1226 | ) |
| 1226 | - }) && !i | 1227 | + }) && !i.events.iter().any(|e| matches!(e, egui::Event::Paste(_))) |
| 1227 | - .events | ||
| 1228 | - .iter() | ||
| 1229 | - .any(|e| matches!(e, egui::Event::Paste(_))) | ||
| 1230 | }); | 1228 | }); |
| 1231 | if paste_without_text { | 1229 | if paste_without_text { |
| 1232 | self.emit(id, "paste-empty", String::new(), 0.0); | 1230 | self.emit(id, "paste-empty", String::new(), 0.0); |
| @@ -1283,8 +1281,11 @@ impl Tree { | |||
| 1283 | .next() | 1281 | .next() |
| 1284 | .map(|c| c.to_uppercase().to_string()) | 1282 | .map(|c| c.to_uppercase().to_string()) |
| 1285 | .unwrap_or_else(|| "?".to_owned()); | 1283 | .unwrap_or_else(|| "?".to_owned()); |
| 1286 | - ui.painter() | 1284 | + ui.painter().circle_filled( |
| 1287 | - .circle_filled(rect.center(), size * 0.5, name_colour(&label, theme)); | 1285 | + rect.center(), |
| 1286 | + size * 0.5, | ||
| 1287 | + name_colour(&label, theme), | ||
| 1288 | + ); | ||
| 1288 | ui.painter().text( | 1289 | ui.painter().text( |
| 1289 | rect.center(), | 1290 | rect.center(), |
| 1290 | Align2::CENTER_CENTER, | 1291 | Align2::CENTER_CENTER, |
| @@ -1416,10 +1417,8 @@ impl Tree { | |||
| 1416 | return; | 1417 | return; |
| 1417 | } | 1418 | } |
| 1418 | let scale = (space.x / size.x).min(space.y / size.y); | 1419 | let scale = (space.x / size.x).min(space.y / size.y); |
| 1419 | - let (rect, response) = | 1420 | + let (rect, response) = ui.allocate_exact_size(space, egui::Sense::click()); |
| 1420 | - ui.allocate_exact_size(space, egui::Sense::click()); | 1421 | + let painted = egui::Rect::from_center_size(rect.center(), size * scale); |
| 1421 | - let painted = | ||
| 1422 | - egui::Rect::from_center_size(rect.center(), size * scale); | ||
| 1423 | egui::Image::new(egui::load::SizedTexture::new(texture.id(), size * scale)) | 1422 | egui::Image::new(egui::load::SizedTexture::new(texture.id(), size * scale)) |
| 1424 | .paint_at(ui, painted); | 1423 | .paint_at(ui, painted); |
| 1425 | if response.clicked() { | 1424 | if response.clicked() { |
| @@ -1483,9 +1482,7 @@ impl Tree { | |||
| 1483 | // otherwise for one of them. A row that sits at the bottom of a screen | 1482 | // otherwise for one of them. A row that sits at the bottom of a screen |
| 1484 | // wants its space above it, not under it, and that is not a thing a | 1483 | // wants its space above it, not under it, and that is not a thing a |
| 1485 | // single number can express. | 1484 | // single number can express. |
| 1486 | - let side = |key: &str| { | 1485 | + let side = |key: &str| props.num(key, props.num("margin", 0.0)).clamp(0.0, 127.0) as i8; |
| 1487 | - props.num(key, props.num("margin", 0.0)).clamp(0.0, 127.0) as i8 | ||
| 1488 | - }; | ||
| 1489 | let margin = Margin { | 1486 | let margin = Margin { |
| 1490 | left: side("margin-left"), | 1487 | left: side("margin-left"), |
| 1491 | right: side("margin-right"), | 1488 | right: side("margin-right"), |
| @@ -1571,7 +1568,9 @@ mod tests { | |||
| 1571 | let ctx = egui::Context::default(); | 1568 | let ctx = egui::Context::default(); |
| 1572 | 1569 | ||
| 1573 | let mut input = egui::RawInput::default(); | 1570 | let mut input = egui::RawInput::default(); |
| 1574 | - input.events.push(egui::Event::PointerMoved(egui::pos2(20.0, 20.0))); | 1571 | + input |
| 1572 | + .events | ||
| 1573 | + .push(egui::Event::PointerMoved(egui::pos2(20.0, 20.0))); | ||
| 1575 | let _ = ctx.run(input.clone(), |ctx| { | 1574 | let _ = ctx.run(input.clone(), |ctx| { |
| 1576 | egui::CentralPanel::default().show(ctx, |ui| tree.paint(ui, &theme)); | 1575 | egui::CentralPanel::default().show(ctx, |ui| tree.paint(ui, &theme)); |
| 1577 | }); | 1576 | }); |
| @@ -1591,7 +1590,9 @@ mod tests { | |||
| 1591 | let card = tree.new_node("window"); | 1590 | let card = tree.new_node("window"); |
| 1592 | tree.append(pill, card); | 1591 | tree.append(pill, card); |
| 1593 | let mut input = egui::RawInput::default(); | 1592 | let mut input = egui::RawInput::default(); |
| 1594 | - input.events.push(egui::Event::PointerMoved(egui::pos2(20.0, 20.0))); | 1593 | + input |
| 1594 | + .events | ||
| 1595 | + .push(egui::Event::PointerMoved(egui::pos2(20.0, 20.0))); | ||
| 1595 | let _ = ctx.run(input, |ctx| { | 1596 | let _ = ctx.run(input, |ctx| { |
| 1596 | egui::CentralPanel::default().show(ctx, |ui| tree.paint(ui, &theme)); | 1597 | egui::CentralPanel::default().show(ctx, |ui| tree.paint(ui, &theme)); |
| 1597 | }); | 1598 | }); |
| @@ -1741,7 +1742,10 @@ mod tests { | |||
| 1741 | let mut tree = Tree::default(); | 1742 | let mut tree = Tree::default(); |
| 1742 | let id = tree.new_node("carousel"); | 1743 | let id = tree.new_node("carousel"); |
| 1743 | assert!(tree.exists(id)); | 1744 | assert!(tree.exists(id)); |
| 1744 | - assert_eq!(tree.slot(id).unwrap().tag, Tag::Unknown("carousel".to_owned())); | 1745 | + assert_eq!( |
| 1746 | + tree.slot(id).unwrap().tag, | ||
| 1747 | + Tag::Unknown("carousel".to_owned()) | ||
| 1748 | + ); | ||
| 1745 | } | 1749 | } |
| 1746 | #[test] | 1750 | #[test] |
| 1747 | fn dump_is_hiccup_of_what_the_tree_holds() { | 1751 | fn dump_is_hiccup_of_what_the_tree_holds() { |
| @@ -1765,7 +1769,11 @@ mod tests { | |||
| 1765 | fn dump_keeps_an_unknown_tag_and_escapes_a_string() { | 1769 | fn dump_keeps_an_unknown_tag_and_escapes_a_string() { |
| 1766 | let mut tree = Tree::default(); | 1770 | let mut tree = Tree::default(); |
| 1767 | let id = tree.new_node("carousel"); | 1771 | let id = tree.new_node("carousel"); |
| 1768 | - tree.set(id, "label", Value::Str("a \"quote\"\nand a line".to_owned())); | 1772 | + tree.set( |
| 1773 | + id, | ||
| 1774 | + "label", | ||
| 1775 | + Value::Str("a \"quote\"\nand a line".to_owned()), | ||
| 1776 | + ); | ||
| 1769 | assert_eq!( | 1777 | assert_eq!( |
| 1770 | tree.dump(id), | 1778 | tree.dump(id), |
| 1771 | "[:carousel {:label \"a \\\"quote\\\"\\nand a line\"}]" | 1779 | "[:carousel {:label \"a \\\"quote\\\"\\nand a line\"}]" |
modified
crates/vidya-core/src/theme.rs +22 -30 | @@ -568,45 +568,37 @@ pub fn status_dot(ui: &mut Ui, theme: &Theme, live: bool) -> Response { | ||
| 568 | 568 | /// squeezed it — a status reading "joine/d", an unread count split down two |
| 569 | 569 | /// lines — for want of the width it asked for. |
| 570 | 570 | pub fn title(ui: &mut Ui, theme: &Theme, text: &str) { |
| 571 | - ui.add( | |
| 572 | - Label::new( | |
| 573 | - RichText::new(text) | |
| 574 | - .size(theme.type_scale.title) | |
| 575 | - .strong() | |
| 576 | - .color(theme.palette.text), | |
| 577 | - ), | |
| 578 | - ); | |
| 571 | + ui.add(Label::new( | |
| 572 | + RichText::new(text) | |
| 573 | + .size(theme.type_scale.title) | |
| 574 | + .strong() | |
| 575 | + .color(theme.palette.text), | |
| 576 | + )); | |
| 579 | 577 | } |
| 580 | 578 | |
| 581 | 579 | pub fn title_2(ui: &mut Ui, theme: &Theme, text: &str) { |
| 582 | - ui.add( | |
| 583 | - Label::new( | |
| 584 | - RichText::new(text) | |
| 585 | - .size(theme.type_scale.title_2) | |
| 586 | - .strong() | |
| 587 | - .color(theme.palette.text), | |
| 588 | - ), | |
| 589 | - ); | |
| 580 | + ui.add(Label::new( | |
| 581 | + RichText::new(text) | |
| 582 | + .size(theme.type_scale.title_2) | |
| 583 | + .strong() | |
| 584 | + .color(theme.palette.text), | |
| 585 | + )); | |
| 590 | 586 | } |
| 591 | 587 | |
| 592 | 588 | pub fn body(ui: &mut Ui, theme: &Theme, text: &str) { |
| 593 | - ui.add( | |
| 594 | - Label::new( | |
| 595 | - RichText::new(text) | |
| 596 | - .size(theme.type_scale.body) | |
| 597 | - .color(theme.palette.text), | |
| 598 | - ), | |
| 599 | - ); | |
| 589 | + ui.add(Label::new( | |
| 590 | + RichText::new(text) | |
| 591 | + .size(theme.type_scale.body) | |
| 592 | + .color(theme.palette.text), | |
| 593 | + )); | |
| 600 | 594 | } |
| 601 | 595 | |
| 602 | 596 | pub fn dim_label(ui: &mut Ui, theme: &Theme, text: &str) { |
| 603 | - ui.add( | |
| 604 | - Label::new( | |
| 605 | - RichText::new(text) | |
| 606 | - .size(theme.type_scale.caption) | |
| 607 | - .color(theme.palette.text_secondary), | |
| 608 | - ), | |
| 609 | - ); | |
| 597 | + ui.add(Label::new( | |
| 598 | + RichText::new(text) | |
| 599 | + .size(theme.type_scale.caption) | |
| 600 | + .color(theme.palette.text_secondary), | |
| 601 | + )); | |
| 610 | 602 | } |
| 611 | 603 | |
| 612 | 604 | /// Single-line text field with theme field padding. |
| @@ -568,45 +568,37 @@ pub fn status_dot(ui: &mut Ui, theme: &Theme, live: bool) -> Response { | |||
| 568 | /// squeezed it — a status reading "joine/d", an unread count split down two | 568 | /// squeezed it — a status reading "joine/d", an unread count split down two |
| 569 | /// lines — for want of the width it asked for. | 569 | /// lines — for want of the width it asked for. |
| 570 | pub fn title(ui: &mut Ui, theme: &Theme, text: &str) { | 570 | pub fn title(ui: &mut Ui, theme: &Theme, text: &str) { |
| 571 | - ui.add( | 571 | + ui.add(Label::new( |
| 572 | - Label::new( | 572 | + RichText::new(text) |
| 573 | - RichText::new(text) | 573 | + .size(theme.type_scale.title) |
| 574 | - .size(theme.type_scale.title) | 574 | + .strong() |
| 575 | - .strong() | 575 | + .color(theme.palette.text), |
| 576 | - .color(theme.palette.text), | 576 | + )); |
| 577 | - ), | ||
| 578 | - ); | ||
| 579 | } | 577 | } |
| 580 | 578 | ||
| 581 | pub fn title_2(ui: &mut Ui, theme: &Theme, text: &str) { | 579 | pub fn title_2(ui: &mut Ui, theme: &Theme, text: &str) { |
| 582 | - ui.add( | 580 | + ui.add(Label::new( |
| 583 | - Label::new( | 581 | + RichText::new(text) |
| 584 | - RichText::new(text) | 582 | + .size(theme.type_scale.title_2) |
| 585 | - .size(theme.type_scale.title_2) | 583 | + .strong() |
| 586 | - .strong() | 584 | + .color(theme.palette.text), |
| 587 | - .color(theme.palette.text), | 585 | + )); |
| 588 | - ), | ||
| 589 | - ); | ||
| 590 | } | 586 | } |
| 591 | 587 | ||
| 592 | pub fn body(ui: &mut Ui, theme: &Theme, text: &str) { | 588 | pub fn body(ui: &mut Ui, theme: &Theme, text: &str) { |
| 593 | - ui.add( | 589 | + ui.add(Label::new( |
| 594 | - Label::new( | 590 | + RichText::new(text) |
| 595 | - RichText::new(text) | 591 | + .size(theme.type_scale.body) |
| 596 | - .size(theme.type_scale.body) | 592 | + .color(theme.palette.text), |
| 597 | - .color(theme.palette.text), | 593 | + )); |
| 598 | - ), | ||
| 599 | - ); | ||
| 600 | } | 594 | } |
| 601 | 595 | ||
| 602 | pub fn dim_label(ui: &mut Ui, theme: &Theme, text: &str) { | 596 | pub fn dim_label(ui: &mut Ui, theme: &Theme, text: &str) { |
| 603 | - ui.add( | 597 | + ui.add(Label::new( |
| 604 | - Label::new( | 598 | + RichText::new(text) |
| 605 | - RichText::new(text) | 599 | + .size(theme.type_scale.caption) |
| 606 | - .size(theme.type_scale.caption) | 600 | + .color(theme.palette.text_secondary), |
| 607 | - .color(theme.palette.text_secondary), | 601 | + )); |
| 608 | - ), | ||
| 609 | - ); | ||
| 610 | } | 602 | } |
| 611 | 603 | ||
| 612 | /// Single-line text field with theme field padding. | 604 | /// Single-line text field with theme field padding. |
modified
flake.nix +19 -2 | @@ -125,7 +125,12 @@ | ||
| 125 | 125 | # skew the patch exists to prevent, rather than the empty file it is. |
| 126 | 126 | dummySrc = craneLib.mkDummySrc { |
| 127 | 127 | inherit src; |
| 128 | + # The stub tree already has a third-party/ of its own, so this has to | |
| 129 | + # replace that directory rather than copy into it — `cp -r a b` where | |
| 130 | + # b exists means b/a, and the real cpal lands at third-party/third-party | |
| 131 | + # while the path cargo reads keeps the stub. | |
| 128 | 132 | extraDummyScript = '' |
| 133 | + rm -rf $out/third-party | |
| 129 | 134 | cp -r --no-preserve=mode,ownership ${src}/third-party $out/third-party |
| 130 | 135 | ''; |
| 131 | 136 | }; |
| @@ -141,7 +146,12 @@ | ||
| 141 | 146 | # cargo does not install a cdylib, so crane's default install phase — |
| 142 | 147 | # `cargo install`, which only knows about binaries — has nothing to do. |
| 143 | 148 | # Take the objects out of the target directory instead. |
| 144 | - soPackage = { pname, package, soname }: | |
| 149 | + # `dir` is named separately because a crate's directory and its cargo | |
| 150 | + # package name are not the same thing here: vidya-ffi lives in | |
| 151 | + # crates/jolt-vidya. Nothing is silenced — a header that stops being | |
| 152 | + # there should fail the build rather than ship an object with no ABI | |
| 153 | + # beside it. | |
| 154 | + soPackage = { pname, package, dir, soname }: | |
| 145 | 155 | craneLib.buildPackage (commonArgs // { |
| 146 | 156 | inherit pname cargoArtifacts; |
| 147 | 157 | version = "0.1.0"; |
| @@ -150,23 +160,26 @@ | ||
| 150 | 160 | installPhaseCommand = '' |
| 151 | 161 | mkdir -p $out/lib $out/include |
| 152 | 162 | cp target/release/${soname} $out/lib/ |
| 153 | - cp -r crates/${package}/include/. $out/include/ 2>/dev/null || true | |
| 163 | + cp -r crates/${dir}/include/. $out/include/ | |
| 154 | 164 | ''; |
| 155 | 165 | }); |
| 156 | 166 | |
| 157 | 167 | libvidya = soPackage { |
| 158 | 168 | pname = "libvidya"; |
| 159 | 169 | package = "vidya-ffi"; |
| 170 | + dir = "jolt-vidya"; | |
| 160 | 171 | soname = "libvidya.so"; |
| 161 | 172 | }; |
| 162 | 173 | libjolttui = soPackage { |
| 163 | 174 | pname = "libjolttui"; |
| 164 | 175 | package = "jolt-tui"; |
| 176 | + dir = "jolt-tui"; | |
| 165 | 177 | soname = "libjolttui.so"; |
| 166 | 178 | }; |
| 167 | 179 | libjoltmoq = soPackage { |
| 168 | 180 | pname = "libjoltmoq"; |
| 169 | 181 | package = "jolt-moq"; |
| 182 | + dir = "jolt-moq"; | |
| 170 | 183 | soname = "libjoltmoq.so"; |
| 171 | 184 | }; |
| 172 | 185 | |
| @@ -222,7 +235,11 @@ | ||
| 222 | 235 | cargoExtraArgs = "--locked -p vidya-ffi -p jolt-moq"; |
| 223 | 236 | } // androidEnv; |
| 224 | 237 | |
| 238 | + # Same stubbed-source caveat as the host deps above: the vendored cpal | |
| 239 | + # has to survive into the dummy tree, or moq-media builds against an | |
| 240 | + # empty crate here too. | |
| 225 | 241 | androidArtifacts = craneLib.buildDepsOnly (androidArgs // { |
| 242 | + inherit dummySrc; | |
| 226 | 243 | pname = "jolt-native-android-deps"; |
| 227 | 244 | version = "0.1.0"; |
| 228 | 245 | }); |
| @@ -125,7 +125,12 @@ | |||
| 125 | # skew the patch exists to prevent, rather than the empty file it is. | 125 | # skew the patch exists to prevent, rather than the empty file it is. |
| 126 | dummySrc = craneLib.mkDummySrc { | 126 | dummySrc = craneLib.mkDummySrc { |
| 127 | inherit src; | 127 | inherit src; |
| 128 | + # The stub tree already has a third-party/ of its own, so this has to | ||
| 129 | + # replace that directory rather than copy into it — `cp -r a b` where | ||
| 130 | + # b exists means b/a, and the real cpal lands at third-party/third-party | ||
| 131 | + # while the path cargo reads keeps the stub. | ||
| 128 | extraDummyScript = '' | 132 | extraDummyScript = '' |
| 133 | + rm -rf $out/third-party | ||
| 129 | cp -r --no-preserve=mode,ownership ${src}/third-party $out/third-party | 134 | cp -r --no-preserve=mode,ownership ${src}/third-party $out/third-party |
| 130 | ''; | 135 | ''; |
| 131 | }; | 136 | }; |
| @@ -141,7 +146,12 @@ | |||
| 141 | # cargo does not install a cdylib, so crane's default install phase — | 146 | # cargo does not install a cdylib, so crane's default install phase — |
| 142 | # `cargo install`, which only knows about binaries — has nothing to do. | 147 | # `cargo install`, which only knows about binaries — has nothing to do. |
| 143 | # Take the objects out of the target directory instead. | 148 | # Take the objects out of the target directory instead. |
| 144 | - soPackage = { pname, package, soname }: | 149 | + # `dir` is named separately because a crate's directory and its cargo |
| 150 | + # package name are not the same thing here: vidya-ffi lives in | ||
| 151 | + # crates/jolt-vidya. Nothing is silenced — a header that stops being | ||
| 152 | + # there should fail the build rather than ship an object with no ABI | ||
| 153 | + # beside it. | ||
| 154 | + soPackage = { pname, package, dir, soname }: | ||
| 145 | craneLib.buildPackage (commonArgs // { | 155 | craneLib.buildPackage (commonArgs // { |
| 146 | inherit pname cargoArtifacts; | 156 | inherit pname cargoArtifacts; |
| 147 | version = "0.1.0"; | 157 | version = "0.1.0"; |
| @@ -150,23 +160,26 @@ | |||
| 150 | installPhaseCommand = '' | 160 | installPhaseCommand = '' |
| 151 | mkdir -p $out/lib $out/include | 161 | mkdir -p $out/lib $out/include |
| 152 | cp target/release/${soname} $out/lib/ | 162 | cp target/release/${soname} $out/lib/ |
| 153 | - cp -r crates/${package}/include/. $out/include/ 2>/dev/null || true | 163 | + cp -r crates/${dir}/include/. $out/include/ |
| 154 | ''; | 164 | ''; |
| 155 | }); | 165 | }); |
| 156 | 166 | ||
| 157 | libvidya = soPackage { | 167 | libvidya = soPackage { |
| 158 | pname = "libvidya"; | 168 | pname = "libvidya"; |
| 159 | package = "vidya-ffi"; | 169 | package = "vidya-ffi"; |
| 170 | + dir = "jolt-vidya"; | ||
| 160 | soname = "libvidya.so"; | 171 | soname = "libvidya.so"; |
| 161 | }; | 172 | }; |
| 162 | libjolttui = soPackage { | 173 | libjolttui = soPackage { |
| 163 | pname = "libjolttui"; | 174 | pname = "libjolttui"; |
| 164 | package = "jolt-tui"; | 175 | package = "jolt-tui"; |
| 176 | + dir = "jolt-tui"; | ||
| 165 | soname = "libjolttui.so"; | 177 | soname = "libjolttui.so"; |
| 166 | }; | 178 | }; |
| 167 | libjoltmoq = soPackage { | 179 | libjoltmoq = soPackage { |
| 168 | pname = "libjoltmoq"; | 180 | pname = "libjoltmoq"; |
| 169 | package = "jolt-moq"; | 181 | package = "jolt-moq"; |
| 182 | + dir = "jolt-moq"; | ||
| 170 | soname = "libjoltmoq.so"; | 183 | soname = "libjoltmoq.so"; |
| 171 | }; | 184 | }; |
| 172 | 185 | ||
| @@ -222,7 +235,11 @@ | |||
| 222 | cargoExtraArgs = "--locked -p vidya-ffi -p jolt-moq"; | 235 | cargoExtraArgs = "--locked -p vidya-ffi -p jolt-moq"; |
| 223 | } // androidEnv; | 236 | } // androidEnv; |
| 224 | 237 | ||
| 238 | + # Same stubbed-source caveat as the host deps above: the vendored cpal | ||
| 239 | + # has to survive into the dummy tree, or moq-media builds against an | ||
| 240 | + # empty crate here too. | ||
| 225 | androidArtifacts = craneLib.buildDepsOnly (androidArgs // { | 241 | androidArtifacts = craneLib.buildDepsOnly (androidArgs // { |
| 242 | + inherit dummySrc; | ||
| 226 | pname = "jolt-native-android-deps"; | 243 | pname = "jolt-native-android-deps"; |
| 227 | version = "0.1.0"; | 244 | version = "0.1.0"; |
| 228 | }); | 245 | }); |