From ec1bbb7bcc2a93816ae5cef47fe34eff8deb0720 Mon Sep 17 00:00:00 2001 From: Roderick van Domburg Date: Mon, 18 May 2026 22:12:31 +0200 Subject: [PATCH 1/2] feat(pipewire): make auto-connect configurable --- src/host/jack/mod.rs | 7 +++---- src/host/pipewire/device.rs | 10 ++++++++-- src/host/pipewire/mod.rs | 33 +++++++++++++++++++++++++++++++-- src/host/pipewire/stream.rs | 13 +++++++++++-- src/platform/mod.rs | 12 ++++++++++++ 5 files changed, 65 insertions(+), 10 deletions(-) diff --git a/src/host/jack/mod.rs b/src/host/jack/mod.rs index d4d668fc8..105e778b5 100644 --- a/src/host/jack/mod.rs +++ b/src/host/jack/mod.rs @@ -23,8 +23,7 @@ pub type Devices = std::vec::IntoIter; /// /// # JACK-Specific Configuration /// -/// Unlike other backends, JACK provides configuration options to control connection and server -/// behavior: +/// JACK provides configuration options to control connection and server behavior: /// - Port auto-connection via [`set_connect_automatically`](Host::set_connect_automatically) /// - Server auto-start via [`set_start_server_automatically`](Host::set_start_server_automatically) #[derive(Debug)] @@ -57,8 +56,8 @@ impl Host { /// ports. /// /// When enabled (default), output streams connect to system playback ports and input streams - /// connect to system capture ports automatically. When disabled, applications must manually - /// connect ports using JACK tools or APIs. + /// connect to system capture ports automatically. When disabled, users must manually connect + /// ports using JACK tools or APIs. /// /// Default: `true` pub fn set_connect_automatically(&mut self, do_connect: bool) { diff --git a/src/host/pipewire/device.rs b/src/host/pipewire/device.rs index f40e1af5d..44c9785c0 100644 --- a/src/host/pipewire/device.rs +++ b/src/host/pipewire/device.rs @@ -4,7 +4,7 @@ use std::{ hash::{Hash, Hasher}, rc::Rc, sync::{ - atomic::{AtomicU64, Ordering}, + atomic::{AtomicBool, AtomicU64, Ordering}, mpsc, Arc, }, thread, @@ -82,12 +82,14 @@ pub struct Device { interface_type: InterfaceType, address: Option, driver: Option, + connect_automatically: Arc, } impl Device { pub(crate) fn class(&self) -> Class { self.class } + fn sink_default() -> Self { Self { node_name: "sink_default".to_owned(), @@ -371,6 +373,7 @@ impl DeviceTrait for Device { sample_format, last_quantum: last_quantum_clone, start, + connect_automatically: device.connect_automatically.load(Ordering::Relaxed), }, data_callback, error_callback, @@ -537,6 +540,7 @@ impl DeviceTrait for Device { sample_format, last_quantum: last_quantum_clone, start, + connect_automatically: device.connect_automatically.load(Ordering::Relaxed), }, data_callback, error_callback, @@ -713,7 +717,7 @@ fn remote_props() -> Option { Some(props) } -pub fn init_devices() -> Option> { +pub fn init_devices(connect_automatically: Arc) -> Option> { let _pw = PwInitGuard::new(); let mainloop = pw::main_loop::MainLoopRc::new(None).ok()?; let context = pw::context::ContextRc::new(&mainloop, None).ok()?; @@ -1029,6 +1033,7 @@ pub fn init_devices() -> Option> { device.quantum = settings.quantum; device.min_quantum = settings.min_quantum; device.max_quantum = settings.max_quantum; + device.connect_automatically = connect_automatically.clone(); } // Resolve each discovered hardware node: global settings apply unless the node @@ -1043,6 +1048,7 @@ pub fn init_devices() -> Option> { device.quantum = overrides.quantum.unwrap_or(settings.quantum); device.min_quantum = settings.min_quantum; device.max_quantum = settings.max_quantum; + device.connect_automatically = connect_automatically.clone(); device }), ); diff --git a/src/host/pipewire/mod.rs b/src/host/pipewire/mod.rs index d223e6733..36698a2be 100644 --- a/src/host/pipewire/mod.rs +++ b/src/host/pipewire/mod.rs @@ -1,3 +1,8 @@ +use std::sync::{ + atomic::{AtomicBool, Ordering}, + Arc, +}; + use device::{init_devices, Class, Device, Devices}; use stream::PwInitGuard; @@ -7,20 +12,44 @@ mod device; mod stream; mod utils; +/// The PipeWire host, providing access to PipeWire audio devices. +/// +/// # PipeWire-Specific Configuration +/// +/// PipeWire provides a configuration option to control graph connection behavior: +/// - Port auto-connection via [`set_connect_automatically`](Host::set_connect_automatically) pub struct Host { // Keeps PipeWire initialized for the lifetime of the host, preventing // pw_deinit() from running between device enumeration and stream creation. _pw: PwInitGuard, devices: Vec, + connect_automatically: Arc, } impl Host { pub fn new() -> Result { let _pw = PwInitGuard::new(); - let devices = init_devices().ok_or_else(|| { + let connect_automatically = Arc::new(AtomicBool::new(true)); + let devices = init_devices(connect_automatically.clone()).ok_or_else(|| { Error::with_message(ErrorKind::HostUnavailable, "PipeWire is not available") })?; - Ok(Self { _pw, devices }) + Ok(Self { + _pw, + devices, + connect_automatically, + }) + } + + /// Configures whether created streams should automatically connect to system playback/capture + /// nodes via the session manager. + /// + /// When enabled (default), PipeWire's session manager links the stream to the appropriate sink + /// or source automatically. When disabled, the stream node is registered in the graph but left + /// unlinked; users must then manually connect ports using JACK tools or APIs. + /// + /// Default: `true` + pub fn set_connect_automatically(&mut self, connect: bool) { + self.connect_automatically.store(connect, Ordering::Relaxed); } } diff --git a/src/host/pipewire/stream.rs b/src/host/pipewire/stream.rs index d4d4f4445..32907f7dd 100644 --- a/src/host/pipewire/stream.rs +++ b/src/host/pipewire/stream.rs @@ -497,6 +497,7 @@ pub struct ConnectParams { pub sample_format: SampleFormat, pub last_quantum: Arc, pub start: Instant, + pub connect_automatically: bool, } pub fn connect_output( @@ -514,6 +515,7 @@ where sample_format, last_quantum, start, + connect_automatically, } = params; let mainloop = pw::main_loop::MainLoopRc::new(None)?; @@ -718,7 +720,10 @@ where // RT_PROCESS is intentionally absent: with add_local_listener the process callback always // runs on this mainloop thread, not the separate data-loop thread RT_PROCESS creates. // The worker thread is promoted to RT after signalling the main thread (see device.rs). - let flags = pw::stream::StreamFlags::AUTOCONNECT | pw::stream::StreamFlags::MAP_BUFFERS; + let mut flags = pw::stream::StreamFlags::MAP_BUFFERS; + if connect_automatically { + flags |= pw::stream::StreamFlags::AUTOCONNECT; + } stream.connect(pw::spa::utils::Direction::Output, None, flags, &mut params)?; @@ -751,6 +756,7 @@ where sample_format, last_quantum, start, + connect_automatically, } = params; let mainloop = pw::main_loop::MainLoopRc::new(None)?; @@ -938,7 +944,10 @@ where // RT_PROCESS is intentionally absent: with add_local_listener the process callback always // runs on this mainloop thread, not the separate data-loop thread RT_PROCESS creates. // The worker thread is promoted to RT after signalling the main thread (see device.rs). - let flags = pw::stream::StreamFlags::AUTOCONNECT | pw::stream::StreamFlags::MAP_BUFFERS; + let mut flags = pw::stream::StreamFlags::MAP_BUFFERS; + if connect_automatically { + flags |= pw::stream::StreamFlags::AUTOCONNECT; + } stream.connect(pw::spa::utils::Direction::Input, None, flags, &mut params)?; diff --git a/src/platform/mod.rs b/src/platform/mod.rs index f1be6be0d..83f2b32d1 100644 --- a/src/platform/mod.rs +++ b/src/platform/mod.rs @@ -20,6 +20,18 @@ pub use self::platform_impl::*; #[cfg_attr(docsrs, doc(cfg(feature = "jack")))] pub use crate::host::jack::Host as JackHost; +#[cfg(all( + any( + target_os = "linux", + target_os = "dragonfly", + target_os = "freebsd", + target_os = "netbsd", + ), + feature = "pipewire", +))] +#[cfg_attr(docsrs, doc(cfg(feature = "pipewire")))] +pub use crate::host::pipewire::Host as PipeWireHost; + #[cfg(feature = "custom")] pub use crate::host::custom::{Device as CustomDevice, Host as CustomHost, Stream as CustomStream}; From bd419bd059d6bb20cdf3d481ee58e8df7b7cb571 Mon Sep 17 00:00:00 2001 From: Roderick van Domburg Date: Mon, 18 May 2026 22:23:07 +0200 Subject: [PATCH 2/2] doc(pipewire): fix comment --- src/host/pipewire/mod.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/host/pipewire/mod.rs b/src/host/pipewire/mod.rs index 36698a2be..583948514 100644 --- a/src/host/pipewire/mod.rs +++ b/src/host/pipewire/mod.rs @@ -45,7 +45,8 @@ impl Host { /// /// When enabled (default), PipeWire's session manager links the stream to the appropriate sink /// or source automatically. When disabled, the stream node is registered in the graph but left - /// unlinked; users must then manually connect ports using JACK tools or APIs. + /// unlinked; users must then manually connect ports using PipeWire tools or session manager + /// APIs. /// /// Default: `true` pub fn set_connect_automatically(&mut self, connect: bool) {