ux: make safe desktop audio the default

This commit is contained in:
2026-08-22 05:44:08 -04:00
parent ff2533c95d
commit 0823f6173f
+72 -33
View File
@@ -1150,9 +1150,9 @@ pub struct AppState {
/// Apps currently producing audio, shown in the share picker. Populated from
/// `UiEvent::AudioAppsListed` after the picker requests an enumeration.
share_audio_apps: Vec<String>,
/// The picker's current typed selection. Desktop-shared remains the legacy
/// fallback; desktop-excluding is shown only when the resolved PixelPass
/// advertises it.
/// The picker's current typed selection. Desktop-excluding is the normal
/// whole-desktop choice; desktop-shared remains an internal compatibility
/// fallback for an older PixelPass that does not advertise exclusion.
share_audio_selection: ShareAudioSelection,
/// Session-only quality override for the next screen-share start.
share_quality_selection: ShareQuality,
@@ -2188,12 +2188,14 @@ fn update(state: &mut AppState, message: AppMessage) -> Task<AppMessage> {
// Open the audio picker instead of sharing immediately, so the
// user chooses which app's audio to capture rather than the whole
// desktop (which echoes the call back to viewers, A23). Default
// selection is legacy "All system audio". Kick off a fresh
// to the fail-closed desktop-exclusion contract while the fresh
// capability probe runs; an old PixelPass response replaces it
// with the visibly warned legacy fallback. Kick off a fresh
// enumeration so the list reflects what's playing right now.
// Suppressed while a start is already in flight (`share_starting`)
// so the picker can't be reopened during the startup window.
state.share_picker_open = true;
state.share_audio_selection = ShareAudioSelection::DesktopShared;
state.share_audio_selection = ShareAudioSelection::DesktopExcluding;
// NB: do NOT reset `share_quality_selection` here. It is the
// per-call override set by the inline quality dropdown next to
// the Share button, and the picker has no quality control of its
@@ -2535,6 +2537,11 @@ fn update(state: &mut AppState, message: AppMessage) -> Task<AppMessage> {
// Only meaningful while the picker is open; if the user
// already cancelled, drop it.
if state.share_picker_open {
let desktop_fallback = if desktop_audio_exclusion_supported {
ShareAudioSelection::DesktopExcluding
} else {
ShareAudioSelection::DesktopShared
};
state.share_app_audio_supported = app_audio_supported;
state.share_desktop_audio_exclusion_supported =
desktop_audio_exclusion_supported;
@@ -2545,7 +2552,7 @@ fn update(state: &mut AppState, message: AppMessage) -> Task<AppMessage> {
&state.share_audio_selection
&& !apps.iter().any(|a| a == sel)
{
state.share_audio_selection = ShareAudioSelection::DesktopShared;
state.share_audio_selection = desktop_fallback.clone();
}
state.share_audio_apps = apps;
} else {
@@ -2556,16 +2563,21 @@ fn update(state: &mut AppState, message: AppMessage) -> Task<AppMessage> {
state.share_audio_selection,
ShareAudioSelection::Application(_)
) {
state.share_audio_selection = ShareAudioSelection::DesktopShared;
state.share_audio_selection = desktop_fallback.clone();
}
}
if !desktop_audio_exclusion_supported
&& matches!(
state.share_audio_selection,
ShareAudioSelection::DesktopExcluding
)
{
state.share_audio_selection = ShareAudioSelection::DesktopShared;
match state.share_audio_selection {
ShareAudioSelection::DesktopShared
if desktop_audio_exclusion_supported =>
{
state.share_audio_selection = ShareAudioSelection::DesktopExcluding;
}
ShareAudioSelection::DesktopExcluding
if !desktop_audio_exclusion_supported =>
{
state.share_audio_selection = ShareAudioSelection::DesktopShared;
}
_ => {}
}
}
}
@@ -8856,10 +8868,10 @@ fn audio_exclusion_status_message(status: &AudioExclusionStatus) -> Option<Strin
}
/// Overlay the screen-share **audio picker** when open (A23). Lets the user
/// capture a single app's audio instead of the whole desktop sink — the default
/// whole-desktop capture contains our own call playout, so a viewer would
/// otherwise hear themselves echoed back. "All system audio" keeps the legacy
/// behavior (with a warning); picking an app passes `--app=<name>` to pixelpass.
/// capture all safe system audio or one specific app. With a capable PixelPass,
/// "All system audio" uses desktop exclusion so PeerSpeak playback is never
/// included. An older PixelPass gets the same row backed by legacy capture and
/// an explicit echo warning. Picking an app passes `--app=<name>` to pixelpass.
fn with_share_picker<'a>(
base: Element<'a, AppMessage>,
state: &'a AppState,
@@ -8926,29 +8938,37 @@ fn with_share_picker<'a>(
})
};
// Keep the legacy whole-desktop choice visible with its echo warning. The
// new exclusion row exists only when this exact PixelPass advertised it.
let mut options = column![opt_row(
matches!(
// Present one whole-desktop row. A current PixelPass backs it with safe
// exclusion; only an older binary sees the legacy implementation/warning.
// Treat the optimistic pre-probe DesktopExcluding selection as the safe row
// too; the core re-probes the exact binary before constructing its argv.
let safe_desktop = state.share_desktop_audio_exclusion_supported
|| matches!(
state.share_audio_selection,
ShareAudioSelection::DesktopShared
),
"All system audio".to_string(),
Some("⚠ may echo the call back to viewers"),
AppMessage::SelectShareAudio(ShareAudioSelection::DesktopShared),
)]
.spacing(4);
if state.share_desktop_audio_exclusion_supported {
options = options.push(opt_row(
ShareAudioSelection::DesktopExcluding
);
let mut options = if safe_desktop {
column![opt_row(
matches!(
state.share_audio_selection,
ShareAudioSelection::DesktopExcluding
),
"System audio except PeerSpeak".to_string(),
"All system audio".to_string(),
Some("Excludes call and watched-share playback"),
AppMessage::SelectShareAudio(ShareAudioSelection::DesktopExcluding),
));
)]
} else {
column![opt_row(
matches!(
state.share_audio_selection,
ShareAudioSelection::DesktopShared
),
"All system audio".to_string(),
Some("⚠ may echo the call back to viewers"),
AppMessage::SelectShareAudio(ShareAudioSelection::DesktopShared),
)]
}
.spacing(4);
for app in &state.share_audio_apps {
let selected = matches!(
&state.share_audio_selection,
@@ -10330,6 +10350,20 @@ mod tests {
);
}
#[test]
fn share_picker_defaults_whole_desktop_to_safe_exclusion() {
let mut state = AppState::default();
let _ = update(&mut state, AppMessage::ToggleScreenShare);
assert!(state.share_picker_open);
assert_eq!(
state.share_audio_selection,
ShareAudioSelection::DesktopExcluding,
"the picker must not expose legacy echoing capture as its normal desktop default"
);
}
#[test]
fn share_start_failure_clears_in_flight_flag() {
// A failed spawn surfaces as UiEvent::Error (not ScreenShareStopped); the
@@ -10577,6 +10611,11 @@ mod tests {
assert!(state.share_app_audio_supported);
assert!(state.share_desktop_audio_exclusion_supported);
assert_eq!(state.share_audio_apps.len(), 2);
assert_eq!(
state.share_audio_selection,
ShareAudioSelection::DesktopExcluding,
"a capable PixelPass must make the single desktop row use exclusion"
);
}
#[test]