feat(host): emit desktop audio exclusion status events

This commit is contained in:
2026-08-21 16:31:11 -04:00
parent 98cde2c19b
commit 5a65f50c4b
4 changed files with 402 additions and 12 deletions
+33 -4
View File
@@ -15,6 +15,7 @@ use super::observer::Readiness;
use super::observer::adapter::RegistryObserverHandle;
use super::owned_thread::OwnedThread;
use super::taint::snapshot::{GlobalId, Serial};
use crate::common::output::AudioExclusionEvent;
use crate::repair::plan as repair_plan;
use anyhow::{Context, Result, bail};
use pipewire::proxy::ProxyT;
@@ -143,8 +144,10 @@ pub(super) struct AudioGraphOwner {
/// `Routing` remains a separate type in `host::audio`.
pub(super) struct BareCaptureSink {
// Drop order is load-bearing: close the fan-out observer connection (and
// therefore every non-lingering link) before the sink-owning connection.
// therefore every non-lingering link), then detach the status task, before
// the sink-owning connection.
fanout: Option<RegistryObserverHandle>,
status_forwarder: Option<tokio::task::JoinHandle<()>>,
graph_owner: Option<AudioGraphOwner>,
monitor_name: String,
}
@@ -156,21 +159,34 @@ impl BareCaptureSink {
.await
.context("failed to start the bare connection-owned capture sink")?;
let identity = graph_owner.identity().clone();
let fanout = RegistryObserverHandle::spawn_with_mutation_sink(
Box::new(FanoutController::new(
let (status_tx, status_rx) = mpsc::unbounded_channel();
let status_forwarder = tokio::spawn(forward_audio_exclusion_status(status_rx));
let fanout = match RegistryObserverHandle::spawn_with_mutation_sink(
Box::new(FanoutController::with_status_sender(
Serial(identity.serial),
AecConfig::Off,
status_tx,
)),
health,
)
.context("failed to start the desktop-excluding fan-out observer")?;
.context("failed to start the desktop-excluding fan-out observer")
{
Ok(fanout) => fanout,
Err(error) => {
let _ = status_forwarder.await;
graph_owner.shutdown().await;
return Err(error);
}
};
if let Err(error) = wait_for_fanout_ready(&fanout, &identity).await {
drop(fanout);
let _ = status_forwarder.await;
graph_owner.shutdown().await;
return Err(error).context("desktop-excluding fan-out did not become ready");
}
Ok(Self {
fanout: Some(fanout),
status_forwarder: Some(status_forwarder),
graph_owner: Some(graph_owner),
monitor_name: identity.monitor_name,
})
@@ -193,6 +209,11 @@ impl BareCaptureSink {
if let Some(fanout) = self.fanout.take() {
drop(fanout);
}
if let Some(status_forwarder) = self.status_forwarder.take()
&& let Err(error) = status_forwarder.await
{
tracing::warn!(%error, "audio fan-out: status forwarder task failed");
}
if let Some(graph_owner) = self.graph_owner.take()
&& !graph_owner.shutdown().await
{
@@ -201,6 +222,14 @@ impl BareCaptureSink {
}
}
async fn forward_audio_exclusion_status(
mut status_rx: mpsc::UnboundedReceiver<AudioExclusionEvent>,
) {
while let Some(event) = status_rx.recv().await {
event.emit();
}
}
async fn wait_for_fanout_ready(
observer: &RegistryObserverHandle,
identity: &SinkIdentity,