ux: make safe desktop audio the default
This commit is contained in:
+72
-33
@@ -1150,9 +1150,9 @@ pub struct AppState {
|
|||||||
/// Apps currently producing audio, shown in the share picker. Populated from
|
/// Apps currently producing audio, shown in the share picker. Populated from
|
||||||
/// `UiEvent::AudioAppsListed` after the picker requests an enumeration.
|
/// `UiEvent::AudioAppsListed` after the picker requests an enumeration.
|
||||||
share_audio_apps: Vec<String>,
|
share_audio_apps: Vec<String>,
|
||||||
/// The picker's current typed selection. Desktop-shared remains the legacy
|
/// The picker's current typed selection. Desktop-excluding is the normal
|
||||||
/// fallback; desktop-excluding is shown only when the resolved PixelPass
|
/// whole-desktop choice; desktop-shared remains an internal compatibility
|
||||||
/// advertises it.
|
/// fallback for an older PixelPass that does not advertise exclusion.
|
||||||
share_audio_selection: ShareAudioSelection,
|
share_audio_selection: ShareAudioSelection,
|
||||||
/// Session-only quality override for the next screen-share start.
|
/// Session-only quality override for the next screen-share start.
|
||||||
share_quality_selection: ShareQuality,
|
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
|
// Open the audio picker instead of sharing immediately, so the
|
||||||
// user chooses which app's audio to capture rather than the whole
|
// user chooses which app's audio to capture rather than the whole
|
||||||
// desktop (which echoes the call back to viewers, A23). Default
|
// 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.
|
// enumeration so the list reflects what's playing right now.
|
||||||
// Suppressed while a start is already in flight (`share_starting`)
|
// Suppressed while a start is already in flight (`share_starting`)
|
||||||
// so the picker can't be reopened during the startup window.
|
// so the picker can't be reopened during the startup window.
|
||||||
state.share_picker_open = true;
|
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
|
// NB: do NOT reset `share_quality_selection` here. It is the
|
||||||
// per-call override set by the inline quality dropdown next to
|
// per-call override set by the inline quality dropdown next to
|
||||||
// the Share button, and the picker has no quality control of its
|
// 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
|
// Only meaningful while the picker is open; if the user
|
||||||
// already cancelled, drop it.
|
// already cancelled, drop it.
|
||||||
if state.share_picker_open {
|
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_app_audio_supported = app_audio_supported;
|
||||||
state.share_desktop_audio_exclusion_supported =
|
state.share_desktop_audio_exclusion_supported =
|
||||||
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
|
&state.share_audio_selection
|
||||||
&& !apps.iter().any(|a| a == sel)
|
&& !apps.iter().any(|a| a == sel)
|
||||||
{
|
{
|
||||||
state.share_audio_selection = ShareAudioSelection::DesktopShared;
|
state.share_audio_selection = desktop_fallback.clone();
|
||||||
}
|
}
|
||||||
state.share_audio_apps = apps;
|
state.share_audio_apps = apps;
|
||||||
} else {
|
} else {
|
||||||
@@ -2556,16 +2563,21 @@ fn update(state: &mut AppState, message: AppMessage) -> Task<AppMessage> {
|
|||||||
state.share_audio_selection,
|
state.share_audio_selection,
|
||||||
ShareAudioSelection::Application(_)
|
ShareAudioSelection::Application(_)
|
||||||
) {
|
) {
|
||||||
state.share_audio_selection = ShareAudioSelection::DesktopShared;
|
state.share_audio_selection = desktop_fallback.clone();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if !desktop_audio_exclusion_supported
|
match state.share_audio_selection {
|
||||||
&& matches!(
|
ShareAudioSelection::DesktopShared
|
||||||
state.share_audio_selection,
|
if desktop_audio_exclusion_supported =>
|
||||||
ShareAudioSelection::DesktopExcluding
|
{
|
||||||
)
|
state.share_audio_selection = ShareAudioSelection::DesktopExcluding;
|
||||||
{
|
}
|
||||||
state.share_audio_selection = ShareAudioSelection::DesktopShared;
|
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
|
/// 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
|
/// capture all safe system audio or one specific app. With a capable PixelPass,
|
||||||
/// whole-desktop capture contains our own call playout, so a viewer would
|
/// "All system audio" uses desktop exclusion so PeerSpeak playback is never
|
||||||
/// otherwise hear themselves echoed back. "All system audio" keeps the legacy
|
/// included. An older PixelPass gets the same row backed by legacy capture and
|
||||||
/// behavior (with a warning); picking an app passes `--app=<name>` to pixelpass.
|
/// an explicit echo warning. Picking an app passes `--app=<name>` to pixelpass.
|
||||||
fn with_share_picker<'a>(
|
fn with_share_picker<'a>(
|
||||||
base: Element<'a, AppMessage>,
|
base: Element<'a, AppMessage>,
|
||||||
state: &'a AppState,
|
state: &'a AppState,
|
||||||
@@ -8926,29 +8938,37 @@ fn with_share_picker<'a>(
|
|||||||
})
|
})
|
||||||
};
|
};
|
||||||
|
|
||||||
// Keep the legacy whole-desktop choice visible with its echo warning. The
|
// Present one whole-desktop row. A current PixelPass backs it with safe
|
||||||
// new exclusion row exists only when this exact PixelPass advertised it.
|
// exclusion; only an older binary sees the legacy implementation/warning.
|
||||||
let mut options = column![opt_row(
|
// Treat the optimistic pre-probe DesktopExcluding selection as the safe row
|
||||||
matches!(
|
// 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,
|
state.share_audio_selection,
|
||||||
ShareAudioSelection::DesktopShared
|
ShareAudioSelection::DesktopExcluding
|
||||||
),
|
);
|
||||||
"All system audio".to_string(),
|
let mut options = if safe_desktop {
|
||||||
Some("⚠ may echo the call back to viewers"),
|
column![opt_row(
|
||||||
AppMessage::SelectShareAudio(ShareAudioSelection::DesktopShared),
|
|
||||||
)]
|
|
||||||
.spacing(4);
|
|
||||||
if state.share_desktop_audio_exclusion_supported {
|
|
||||||
options = options.push(opt_row(
|
|
||||||
matches!(
|
matches!(
|
||||||
state.share_audio_selection,
|
state.share_audio_selection,
|
||||||
ShareAudioSelection::DesktopExcluding
|
ShareAudioSelection::DesktopExcluding
|
||||||
),
|
),
|
||||||
"System audio except PeerSpeak".to_string(),
|
"All system audio".to_string(),
|
||||||
Some("Excludes call and watched-share playback"),
|
Some("Excludes call and watched-share playback"),
|
||||||
AppMessage::SelectShareAudio(ShareAudioSelection::DesktopExcluding),
|
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 {
|
for app in &state.share_audio_apps {
|
||||||
let selected = matches!(
|
let selected = matches!(
|
||||||
&state.share_audio_selection,
|
&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]
|
#[test]
|
||||||
fn share_start_failure_clears_in_flight_flag() {
|
fn share_start_failure_clears_in_flight_flag() {
|
||||||
// A failed spawn surfaces as UiEvent::Error (not ScreenShareStopped); the
|
// 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_app_audio_supported);
|
||||||
assert!(state.share_desktop_audio_exclusion_supported);
|
assert!(state.share_desktop_audio_exclusion_supported);
|
||||||
assert_eq!(state.share_audio_apps.len(), 2);
|
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]
|
#[test]
|
||||||
|
|||||||
Reference in New Issue
Block a user