pipewire_native/lib.rs
1// SPDX-License-Identifier: MIT
2// SPDX-FileCopyrightText: Copyright (c) 2025 Asymptotic Inc.
3// SPDX-FileCopyrightText: Copyright (c) 2025 Arun Raghavan
4
5#![warn(missing_docs)]
6
7//! This library provides a Rust-native API to the [PipeWire](https://pipewire.org) audio server.
8//! This includes a native implementation of the protocol, and FFI wrappers around the lower-level
9//! SPA libraries used by PipeWire itself (which we try not to expose in the public API).
10//!
11//! <div class="warning">
12//! <p>
13//! The API is expected to change. Notably, performing audio/video processing is not yet
14//! implemented, and the mechanism for setting up proxy events and defining closures can definitely
15//! be improved.
16//! </p>
17//!
18//! <p>
19//! We will follow semantic versioning in order to avoid breaking existing API users.
20//! </p>
21//! </div>
22//!
23//! # Usage
24//!
25//! A typical client would use the following steps:
26//!
27//! * Create a [MainLoop](main_loop::MainLoop), and run it
28//! * Configure and create a [Context](context::Context), optionally specifying client-specific
29//! configuration
30//! * [Connect](context::Context::connect()) to the server, which provides a [Core](core::Core)
31//! * Request a [Registry](proxy::registry::Registry) via the core
32//! * Listen for [global events](proxy::registry::RegistryEvents)
33//! * [Bind](proxy::registry::Registry::bind()) to the global objects you wish to interact with
34//! * For each object you bind to, you will get a proxy object which has methods you can call on
35//! the object, as well as events you can subscribe to with an `add_listener()` call.
36//!
37//! # Examples
38//!
39//! Example usage of the library can be found in the source repository. The simple client test in
40//! `tests/lib.rs` is a good starting point. The `pw-browse` utility in `tools/browse` can also
41//! serve as a guide for writing clients.
42
43use std::sync::OnceLock;
44
45use pipewire_native_spa as spa;
46
47use properties::Properties;
48use support::Support;
49
50/// The [Context](context::Context) holds local client configuration and support libraries. It is
51/// the entry point to all other API.
52pub mod context;
53/// The [Core](core::Core) object is the top-level singleton representing a connection to the
54/// PipeWire server. The [Core](core::Core) can be used to query objects known to the PipeWire
55/// server via the [Registry](proxy::registry::Registry). It can also be used to create objects on
56/// the server on behalf of this client (this functionality has not yet been implemented).
57pub mod core;
58/// Contains a number of well-known keys used in properties (such as application name, language,
59/// process ID, etc.).
60pub mod keys;
61/// Provides an event loop for the library to communicate with the PipeWire server, as well as
62/// primitives to managed mutually exclusive access to shared data structures.
63pub mod main_loop;
64/// Sructures for representing permissions.
65pub mod permission;
66/// Properties represent a key-value structure for various object properties. While the internal
67/// representation is [String] pairs, helper methods are provided for interpreting values as
68/// various primitives.
69pub mod properties;
70/// Proxy objects that represent objects exposed by the PipeWire server. This is the primary means
71/// by which clients can interact with server-side objects.
72pub mod proxy;
73/// Provides an event loop that runs in a separate thread.
74pub mod thread_loop;
75/// List of well-known types and interfaces shared by PipeWire server and clients.
76pub mod types;
77
78mod conf;
79mod id_map;
80/// Logggng utilities for using the PipeWire logging system. Controlled via the `PIPEWIRE_DEBUG` and
81/// `PIPEWIRE_LOG*` environment variables, as well as [Context](context::Context) properties.
82mod log;
83mod protocol;
84mod refcounted;
85mod support;
86mod utils;
87
88#[doc(inline)]
89pub use refcounted::*;
90
91// pub use so users of closure! don't need to import paste themselves
92#[doc(hidden)]
93pub use paste::paste;
94
95/// Represents an object identifier
96pub type Id = u32;
97
98/// Represents an identifier for hooks added with objects' add_listener() method
99pub type HookId = spa::hook::HookId;
100
101#[allow(unused)]
102pub(crate) const INVALID_ID: Id = Id::MAX;
103pub(crate) const ANY_ID: Id = 0xFFFF_FFFF;
104
105pub(crate) static GLOBAL_SUPPORT: OnceLock<Support> = OnceLock::new();
106
107/// Must be called before using any other API from this crate. Initialises global support libraries
108/// and sets up logging.
109pub fn init() {
110 GLOBAL_SUPPORT.get_or_init(|| {
111 let mut support = Support::new();
112
113 let levels = log::parse_levels(std::env::var("PIPEWIRE_DEBUG").ok().as_deref());
114 log::topic::init(&levels);
115
116 // First, initialise logging
117 let mut log_info = Properties::new();
118 log_info.set(
119 spa::interface::log::LEVEL,
120 if support.no_color {
121 "false".to_string()
122 } else {
123 utils::read_env_string("PIPEWIRE_LOG_COLOR", "true")
124 },
125 );
126 log_info.set(
127 spa::interface::log::TIMESTAMP,
128 utils::read_env_string("PIPEWIRE_LOG_TIMESTAMP", "true"),
129 );
130 log_info.set(
131 spa::interface::log::LINE,
132 utils::read_env_string("PIPEWIRE_LOG_LINE", "true"),
133 );
134 let _ = std::env::var("PIPEWIRE_LOG").map(|v| {
135 log_info.set(spa::interface::log::FILE, v);
136 });
137
138 // Initialise to the global default as parsed (if not specified, parse_levels() always
139 // provides a default
140 log_info.set(
141 spa::interface::log::LEVEL,
142 format!(
143 "{}",
144 levels.iter().find(|v| v.0.is_empty()).unwrap().1 as u32
145 ),
146 );
147
148 // TODO: Check for/load the systemd logger if PIPEWIRE_SYSTEMD is set
149 support
150 .load_interfaces(
151 spa::interface::plugin::LOG_FACTORY,
152 &[spa::interface::LOG],
153 Some(&log_info),
154 )
155 .expect("failed to load log interface");
156
157 // Next, load CPU support
158 let mut cpu_info = Properties::new();
159 let _ = std::env::var("PIPEWIRE_CPU").map(|v| {
160 cpu_info.set(spa::interface::cpu::FORCE, v);
161 });
162 let _ = std::env::var("PIPEWIRE_VM").map(|v| {
163 cpu_info.set(spa::interface::cpu::VM, v);
164 });
165
166 support
167 .load_interfaces(
168 spa::interface::plugin::CPU_FACTORY,
169 &[spa::interface::CPU],
170 Some(&cpu_info),
171 )
172 .expect("failed to load CPU interface");
173
174 support.init_log();
175
176 // TODO: Load i18n interface
177
178 support
179 .load_interfaces(
180 spa::interface::plugin::SYSTEM_FACTORY,
181 &[spa::interface::SYSTEM],
182 None,
183 )
184 .expect("failed to load system interface");
185 support.init_system();
186
187 support
188 });
189}
190
191/// Utility macro to reduce closure-related boilerplate.
192///
193/// This closure allows creating a closure that captures [Refcounted] and [Clone] values without
194/// having to manually manage the `downgrade()`/`upgrade()` cycle and `clone()` calls respectively.
195/// The syntax is a little strange at first, but it adds a great deal of convenience. This is
196/// expected to improve in the future, with less arcane syntax.
197///
198/// Example:
199/// ```ignore
200/// // We assume `AppContext` is `Clone` (maybe internally has an `Arc`)
201/// fn setup_registry(app: AppContext, core: &Core) -> Registry {
202/// let registry = core.registry();
203///
204/// // some_closure!() is the same as closure!(), but wrapped in a Some()
205/// registry.add_listener(RegistryEvents {
206/// // captured via Clone ----.
207/// // | .---- callback
208/// // captured via Refcounted \ | | arguments
209/// // v v v
210/// // |--------|------| |-----------------|
211/// global: some_closure!([registry ^(app)] id, type_, version, {
212/// // registry is made available here through a weak reference
213/// let object = registry.bind(...);
214/// app.add_object(id, object);
215/// }),
216/// global_remove: some_closure!([^(app)] id, {
217/// // Without the macro, we would have to create and move two cloned copies of `app`
218/// app.remove_object(id);
219/// }),
220/// });
221/// }
222/// ```
223///
224/// In addition to the `^` marker to capture via [Clone], there is also a `^mut` marker to capture
225/// via [Clone] and make the capture value available as `mut`.
226#[macro_export]
227macro_rules! closure {
228 ([$($names:ident <- $objects:ident),* $(^($($clones:ident),+))? $(^mut($($mut_clones:ident),+))?] $($($args:ident),* ,)? $body:block) => {
229 $crate::paste! {
230 {
231 $(let [<_weak $names>] = $objects.downgrade();)*
232 $($(let [<_cloned $clones>] = $clones.clone();)+)?
233 $($(let mut [< _cloned $mut_clones >] = $mut_clones.clone();)+)?
234 Box::new(move |$($($args),*)?| {
235 $(let $names = [<_weak $names>].upgrade().unwrap();)*
236 $($(let $clones = &[< _cloned $clones >];)+)?
237 $($(let $mut_clones = &mut [< _cloned $mut_clones >];)+)?
238 $body
239 })
240 }
241 }
242 };
243 ([$($objects:ident),* $(^($($clones:ident),+))? $(^mut($($mut_clones:ident),+))?] $($($args:ident),* ,)? $body:block) => {
244 $crate::closure!([$($objects <- $objects),* $(^($($clones),+))? $(^mut($($mut_clones),+))?] $($($args),*,)? $body)
245 };
246}
247
248/// Similar to [closure!], but returns a `Some(...)` for use with event callbacks.
249#[macro_export]
250macro_rules! some_closure {
251 ($($args:tt)*) => { Some($crate::closure!($($args)*)) };
252}