From c8a4e0680cb553b5547cfccfb859316688078cd6 Mon Sep 17 00:00:00 2001 From: Lukas <103962359+L4XB@users.noreply.github.com> Date: Sat, 24 Oct 2026 08:56:09 +0200 Subject: [PATCH] fix(linux): capture the default output's monitor for remote audio (#16681) * fix(linux): capture the default output's monitor for remote audio With no audio input chosen, the _pa service recorded the first source whose name contains "monitor". Sources are listed in creation order, so on a host with more than one output, such as onboard audio plus HDMI, a USB headset or a Bluetooth speaker, that monitor can belong to an output the desktop does not play to, and the controlling side hears silence. Ask the server for the default sink and record its monitor, keeping the first monitor as the fallback. * fix(linux): log the lookups that fall back to the first monitor * fix(linux): fall back to the first monitor when the default sink has no monitor name * fix(linux): throttle the default monitor lookup warnings --- src/platform/linux.rs | 46 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/src/platform/linux.rs b/src/platform/linux.rs index beed657d3..b739e1b82 100644 --- a/src/platform/linux.rs +++ b/src/platform/linux.rs @@ -1550,6 +1550,9 @@ where } pub fn get_pa_monitor() -> String { + if let Some(monitor) = get_default_pa_monitor() { + return monitor; + } get_pa_sources() .drain(..) .map(|x| x.0) @@ -1558,6 +1561,49 @@ pub fn get_pa_monitor() -> String { .unwrap_or("".to_owned()) } +// The first monitor in the source list can belong to an output the desktop does not play to. +fn get_default_pa_monitor() -> Option { + use pulsectl::controllers::*; + // The audio service retries about every second while PulseAudio is unavailable. + const LOG_INTERVAL: std::time::Duration = std::time::Duration::from_secs(60); + let mut handler = SinkController::create() + .map_err(|err| { + hbb_common::throttled_log!( + LOG_INTERVAL, + warn, + "Failed to create SinkController: {:?}", + err + ) + }) + .ok()?; + let sink = handler + .get_server_info() + .map_err(|err| { + hbb_common::throttled_log!( + LOG_INTERVAL, + warn, + "Failed to get the PulseAudio server info: {:?}", + err + ) + }) + .ok()? + .default_sink_name?; + handler + .get_device_by_name(&sink) + .map_err(|err| { + hbb_common::throttled_log!( + LOG_INTERVAL, + warn, + "Failed to get the default sink {}: {:?}", + sink, + err + ) + }) + .ok()? + .monitor_name + .filter(|name| !name.is_empty()) +} + pub fn get_pa_source_name(desc: &str) -> String { get_pa_sources() .drain(..)