Compare commits
2
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
646f35d3eb | ||
|
|
ff7daee34e |
+14
@@ -6,6 +6,20 @@ description = "P2P screen sharing CLI over iroh"
|
|||||||
license = "MIT OR Apache-2.0"
|
license = "MIT OR Apache-2.0"
|
||||||
publish = false
|
publish = false
|
||||||
|
|
||||||
|
# Debian/Ubuntu packaging (cargo-deb). Headless default build (no `gui` feature) —
|
||||||
|
# that is exactly what peerspeak spawns as a child. Runtime shared-lib deps
|
||||||
|
# (libpipewire, libc, …) are resolved by dpkg-shlibdeps via `depends = "$auto"`.
|
||||||
|
# Build inside a Debian/Ubuntu distrobox, then `cargo deb --no-build`.
|
||||||
|
[package.metadata.deb]
|
||||||
|
maintainer = "mollusk <jitty+lc1iz0dc@protonmail.com>"
|
||||||
|
section = "net"
|
||||||
|
priority = "optional"
|
||||||
|
depends = "$auto"
|
||||||
|
extended-description = "Peer-to-peer screen sharing over iroh (QUIC). Companion to peerspeak: shares a window or screen directly to a peer with no central server, driven via the CLI and its JSON event stream."
|
||||||
|
assets = [
|
||||||
|
["target/release/pixelpass", "usr/bin/", "755"],
|
||||||
|
]
|
||||||
|
|
||||||
[[bin]]
|
[[bin]]
|
||||||
name = "pixelpass"
|
name = "pixelpass"
|
||||||
path = "src/main.rs"
|
path = "src/main.rs"
|
||||||
|
|||||||
@@ -72,7 +72,7 @@ pub enum CaptureState {
|
|||||||
Stopped,
|
Stopped,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Clone, Copy)]
|
#[derive(Serialize, Clone, Copy, Debug, PartialEq, Eq)]
|
||||||
#[serde(rename_all = "snake_case")]
|
#[serde(rename_all = "snake_case")]
|
||||||
pub enum AppAudioState {
|
pub enum AppAudioState {
|
||||||
Routed,
|
Routed,
|
||||||
|
|||||||
@@ -165,6 +165,16 @@ impl Routing {
|
|||||||
routing.event_task = Some(event_task);
|
routing.event_task = Some(event_task);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Strict per-app mode suppresses the default-sink loopback, so until the
|
||||||
|
// chosen app's first stream routes the viewer hears *silence*. Emit an
|
||||||
|
// initial `lost` at capture start (capture is lazy — this runs on the
|
||||||
|
// first viewer) so the front-end can warn from the outset rather than
|
||||||
|
// only after an app that *was* routed later stops (audit A23 P2/F1):
|
||||||
|
// `LastRoutedStreamGone`→`lost` never fires for an app that never routed.
|
||||||
|
if let Some(state) = initial_app_audio_state(opts) {
|
||||||
|
crate::common::output::emit(crate::common::output::Event::AppAudio { state });
|
||||||
|
}
|
||||||
|
|
||||||
Ok(routing)
|
Ok(routing)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -206,6 +216,16 @@ impl Drop for Routing {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The app-audio state to announce at capture start, if any. Only strict per-app
|
||||||
|
/// mode warrants one: there the loopback is suppressed, so the viewer hears
|
||||||
|
/// silence until the chosen app's first stream routes — surface that as an
|
||||||
|
/// initial `lost`. In every other mode (whole-desktop, or best-effort app
|
||||||
|
/// filtering) the loopback keeps audio flowing from the outset, so there is no
|
||||||
|
/// initial gap to report. Pure: no I/O, so the emit decision is unit-testable.
|
||||||
|
pub(super) fn initial_app_audio_state(opts: &HostOpts) -> Option<crate::common::output::AppAudioState> {
|
||||||
|
(opts.app.is_some() && opts.strict_audio).then_some(crate::common::output::AppAudioState::Lost)
|
||||||
|
}
|
||||||
|
|
||||||
// ──────────────────────────────────────────────────────────────────────
|
// ──────────────────────────────────────────────────────────────────────
|
||||||
// App enumeration (interactive picker source)
|
// App enumeration (interactive picker source)
|
||||||
// ──────────────────────────────────────────────────────────────────────
|
// ──────────────────────────────────────────────────────────────────────
|
||||||
|
|||||||
@@ -535,4 +535,20 @@ mod tests {
|
|||||||
);
|
);
|
||||||
assert_eq!(capture_summary(&opts(None, true)), "fullscreen + system-audio");
|
assert_eq!(capture_summary(&opts(None, true)), "fullscreen + system-audio");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn initial_app_audio_is_lost_only_in_strict_app_mode() {
|
||||||
|
use crate::common::output::AppAudioState;
|
||||||
|
use crate::host::audio::initial_app_audio_state;
|
||||||
|
// Strict + app: announce silence up front (loopback suppressed).
|
||||||
|
assert_eq!(
|
||||||
|
initial_app_audio_state(&opts(Some("Firefox"), true)),
|
||||||
|
Some(AppAudioState::Lost)
|
||||||
|
);
|
||||||
|
// Best-effort app (no strict): loopback covers the gap → no initial event.
|
||||||
|
assert_eq!(initial_app_audio_state(&opts(Some("Firefox"), false)), None);
|
||||||
|
// Whole-desktop (strict is ignored without --app): no per-app events.
|
||||||
|
assert_eq!(initial_app_audio_state(&opts(None, true)), None);
|
||||||
|
assert_eq!(initial_app_audio_state(&opts(None, false)), None);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user