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}