pipewire/registry/
mod.rs

1// Copyright The pipewire-rs Contributors.
2// SPDX-License-Identifier: MIT
3
4//! The registry is a singleton object that keeps track of global objects on the PipeWire instance.
5//!
6//! Global objects typically represent an actual object in PipeWire (for example, a module or node) or they are singleton objects such as the core.
7//!
8//! When a client creates a registry object, the registry object will emit a global event for each global currently in the registry.
9//! Globals come and go as a result of device hotplugs or reconfiguration or other events, and the registry will send
10//! out [`global`](self::ListenerLocalBuilder::global) and [`global_remove`](self::ListenerLocalBuilder::global_remove) events to keep the client up to date with the changes. To mark the end of the initial
11//! burst of events, the client can use the [`Core::sync`](crate::core::Core::sync) method immediately after getting the registry from the core.
12//!
13//! A client can bind to a global object by using [`bind`](Registry::bind). This creates a client-side proxy that lets the
14//! object emit events to the client and lets the client invoke methods on the object. See
15//! [`proxy`](crate::proxy).
16//!
17//! Clients can also change the permissions of the global objects that they can see. This is interesting when you want to
18//! configure a pipewire session before handing it to another application. You can, for example, hide certain existing
19//! or new objects or limit the access permissions on an object.
20//!
21//! This module contains wrappers for [`pw_registry`](pw_sys::pw_registry) and related items.
22
23use libc::{c_char, c_void};
24
25use std::{
26    ffi::{CStr, CString},
27    mem,
28    pin::Pin,
29    ptr,
30};
31
32use crate::{
33    permissions::PermissionFlags,
34    properties::PropertiesBox,
35    proxy::{Proxy, ProxyT},
36    types::ObjectType,
37    Error,
38};
39
40mod box_;
41pub use box_::*;
42mod rc;
43pub use rc::*;
44
45/// Transparent wrapper around a [registry](self).
46///
47/// This does not own the underlying object and is usually seen behind a `&` reference.
48///
49/// For owning wrappers, see [`RegistryBox`] and [`RegistryRc`].
50///
51/// For an explanation of these, see [Smart pointers to PipeWire
52/// objects](crate#smart-pointers-to-pipewire-objects).
53#[repr(transparent)]
54pub struct Registry(pw_sys::pw_registry);
55
56impl Registry {
57    pub fn as_raw(&self) -> &pw_sys::pw_registry {
58        &self.0
59    }
60
61    pub fn as_raw_ptr(&self) -> *mut pw_sys::pw_registry {
62        std::ptr::addr_of!(self.0).cast_mut()
63    }
64
65    // TODO: add non-local version when we'll bind pw_thread_loop_start()
66    #[must_use = "Use the builder to register event callbacks"]
67    pub fn add_listener_local(&self) -> ListenerLocalBuilder<'_> {
68        ListenerLocalBuilder {
69            registry: self,
70            cbs: ListenerLocalCallbacks::default(),
71        }
72    }
73
74    /// Bind to a global object.
75    ///
76    /// Bind to the global object and get a proxy to the object. After this call, methods can be sent to the remote global object and events can be received.
77    ///
78    /// Usually this is called in callbacks for the [`global`](ListenerLocalBuilder::global) event.
79    ///
80    /// # Errors
81    /// If `T` does not match the type of the global object, [`Error::WrongProxyType`] is returned.
82    pub fn bind<T: ProxyT, P: AsRef<spa::utils::dict::DictRef>>(
83        &self,
84        object: &GlobalObject<P>,
85    ) -> Result<T, Error> {
86        let proxy = unsafe {
87            let type_ = CString::new(object.type_.to_str()).unwrap();
88            let version = object.type_.client_version();
89
90            let proxy = spa::spa_interface_call_method!(
91                self.as_raw_ptr(),
92                pw_sys::pw_registry_methods,
93                bind,
94                object.id,
95                type_.as_ptr(),
96                version,
97                0
98            );
99
100            proxy
101        };
102
103        let proxy = ptr::NonNull::new(proxy.cast()).ok_or(Error::NoMemory)?;
104
105        Proxy::new(proxy).downcast().map_err(|(_, e)| e)
106    }
107
108    /// Attempt to destroy the global object with the specified id on the remote.
109    ///
110    /// # Permissions
111    /// Requires [`X`](crate::permissions::PermissionFlags::X) permissions on the global.
112    pub fn destroy_global(&self, global_id: u32) -> spa::utils::result::SpaResult {
113        let result = unsafe {
114            spa::spa_interface_call_method!(
115                self.as_raw_ptr(),
116                pw_sys::pw_registry_methods,
117                destroy,
118                global_id
119            )
120        };
121
122        spa::utils::result::SpaResult::from_c(result)
123    }
124}
125
126type GlobalCallback = dyn Fn(&GlobalObject<&spa::utils::dict::DictRef>);
127type GlobalRemoveCallback = dyn Fn(u32);
128
129#[derive(Default)]
130struct ListenerLocalCallbacks {
131    global: Option<Box<GlobalCallback>>,
132    global_remove: Option<Box<GlobalRemoveCallback>>,
133}
134
135/// A builder for registering registry event callbacks.
136///
137/// Use [`Registry::add_listener_local`] to create this and register callbacks that will be called when events of interest occur.
138/// After adding callbacks, use [`register`](Self::register) to get back a
139/// [`registry::Listener`](Listener).
140///
141/// # Examples
142/// ```
143/// # use pipewire::registry::Registry;
144/// # fn example(registry: Registry) {
145/// let registry_listener = registry.add_listener_local()
146///     .global(|global| println!("New global: {global:?}"))
147///     .global_remove(|id| println!("Global with id {id} was removed"))
148///     .register();
149/// # }
150/// ```
151pub struct ListenerLocalBuilder<'a> {
152    registry: &'a Registry,
153    cbs: ListenerLocalCallbacks,
154}
155
156/// An owned listener for registry events.
157///
158/// This is created by [`registry::ListenerLocalBuilder`](ListenerLocalBuilder) and will receive events as long as it is alive.
159/// When this gets dropped, the listener gets unregistered and no events will be received by it.
160#[must_use = "Listeners unregister themselves when dropped. Keep the listener alive in order to receive events."]
161pub struct Listener {
162    // Need to stay allocated while the listener is registered
163    #[allow(dead_code)]
164    events: Pin<Box<pw_sys::pw_registry_events>>,
165    listener: Pin<Box<spa_sys::spa_hook>>,
166    #[allow(dead_code)]
167    data: Box<ListenerLocalCallbacks>,
168}
169
170impl Drop for Listener {
171    fn drop(&mut self) {
172        spa::utils::hook::remove(*self.listener);
173    }
174}
175
176impl<'a> ListenerLocalBuilder<'a> {
177    /// Set the registry `global` event callback of the listener.
178    ///
179    /// This event is emitted when a new global object is available.
180    ///
181    /// # Callback parameters
182    /// `global`: The new global object
183    ///
184    /// # Examples
185    /// ```
186    /// # use pipewire::registry::Registry;
187    /// # fn example(registry: Registry) {
188    /// let registry_listener = registry.add_listener_local()
189    ///     .global(|global| println!("New global: {global:?}"))
190    ///     .register();
191    /// # }
192    /// ```
193    #[must_use = "Call `.register()` to start receiving events"]
194    pub fn global<F>(mut self, global: F) -> Self
195    where
196        F: Fn(&GlobalObject<&spa::utils::dict::DictRef>) + 'static,
197    {
198        self.cbs.global = Some(Box::new(global));
199        self
200    }
201
202    /// Set the registry `global_remove` event callback of the listener.
203    ///
204    /// This event is emitted when a global object was removed from the registry. If the client has any bindings to the global, it should destroy those.
205    ///
206    /// # Callback parameters
207    /// `id`: The id of the global that was removed
208    ///
209    /// # Examples
210    /// ```
211    /// # use pipewire::registry::Registry;
212    /// # fn example(registry: Registry) {
213    /// let registry_listener = registry.add_listener_local()
214    ///     .global_remove(|id| println!("Global with id {id} was removed"))
215    ///     .register();
216    /// # }
217    /// ```
218    #[must_use = "Call `.register()` to start receiving events"]
219    pub fn global_remove<F>(mut self, global_remove: F) -> Self
220    where
221        F: Fn(u32) + 'static,
222    {
223        self.cbs.global_remove = Some(Box::new(global_remove));
224        self
225    }
226
227    /// Subscribe to events and register any provided callbacks.
228    pub fn register(self) -> Listener {
229        unsafe extern "C" fn registry_events_global(
230            data: *mut c_void,
231            id: u32,
232            permissions: u32,
233            type_: *const c_char,
234            version: u32,
235            props: *const spa_sys::spa_dict,
236        ) {
237            let type_ = CStr::from_ptr(type_).to_str().unwrap();
238            let obj = GlobalObject::new(id, permissions, type_, version, props);
239            let callbacks = (data as *mut ListenerLocalCallbacks).as_ref().unwrap();
240            callbacks.global.as_ref().unwrap()(&obj);
241        }
242
243        unsafe extern "C" fn registry_events_global_remove(data: *mut c_void, id: u32) {
244            let callbacks = (data as *mut ListenerLocalCallbacks).as_ref().unwrap();
245            callbacks.global_remove.as_ref().unwrap()(id);
246        }
247
248        let e = unsafe {
249            let mut e: Pin<Box<pw_sys::pw_registry_events>> = Box::pin(mem::zeroed());
250            e.version = pw_sys::PW_VERSION_REGISTRY_EVENTS;
251
252            if self.cbs.global.is_some() {
253                e.global = Some(registry_events_global);
254            }
255            if self.cbs.global_remove.is_some() {
256                e.global_remove = Some(registry_events_global_remove);
257            }
258
259            e
260        };
261
262        let (listener, data) = unsafe {
263            let ptr = self.registry.as_raw_ptr();
264            let data = Box::into_raw(Box::new(self.cbs));
265            let mut listener: Pin<Box<spa_sys::spa_hook>> = Box::pin(mem::zeroed());
266            let listener_ptr: *mut spa_sys::spa_hook = listener.as_mut().get_unchecked_mut();
267
268            spa::spa_interface_call_method!(
269                ptr,
270                pw_sys::pw_registry_methods,
271                add_listener,
272                listener_ptr.cast(),
273                e.as_ref().get_ref(),
274                data as *mut _
275            );
276
277            (listener, Box::from_raw(data))
278        };
279
280        Listener {
281            events: e,
282            listener,
283            data,
284        }
285    }
286}
287
288#[derive(Debug)]
289pub struct GlobalObject<P: AsRef<spa::utils::dict::DictRef>> {
290    pub id: u32,
291    pub permissions: PermissionFlags,
292    pub type_: ObjectType,
293    pub version: u32,
294    pub props: Option<P>,
295}
296
297impl GlobalObject<&spa::utils::dict::DictRef> {
298    unsafe fn new(
299        id: u32,
300        permissions: u32,
301        type_: &str,
302        version: u32,
303        props: *const spa_sys::spa_dict,
304    ) -> Self {
305        let type_ = ObjectType::from_str(type_);
306        let permissions = PermissionFlags::from_bits_retain(permissions);
307        let props = ptr::NonNull::new(props.cast_mut())
308            .map(|ptr| ptr.cast::<spa::utils::dict::DictRef>().as_ref());
309
310        Self {
311            id,
312            permissions,
313            type_,
314            version,
315            props,
316        }
317    }
318}
319
320impl<P: AsRef<spa::utils::dict::DictRef>> GlobalObject<P> {
321    pub fn to_owned(&self) -> GlobalObject<PropertiesBox> {
322        GlobalObject {
323            id: self.id,
324            permissions: self.permissions,
325            type_: self.type_.clone(),
326            version: self.version,
327            props: self
328                .props
329                .as_ref()
330                .map(|props| PropertiesBox::from_dict(props.as_ref())),
331        }
332    }
333}
334
335#[cfg(test)]
336mod tests {
337    use super::*;
338    #[test]
339    fn set_object_type() {
340        assert_eq!(
341            ObjectType::from_str("PipeWire:Interface:Client"),
342            ObjectType::Client
343        );
344        assert_eq!(ObjectType::Client.to_str(), "PipeWire:Interface:Client");
345        assert_eq!(ObjectType::Client.client_version(), 3);
346
347        let o = ObjectType::Other("PipeWire:Interface:Badger".to_string());
348        assert_eq!(ObjectType::from_str("PipeWire:Interface:Badger"), o);
349        assert_eq!(o.to_str(), "PipeWire:Interface:Badger");
350    }
351
352    #[test]
353    #[should_panic(expected = "Invalid object type")]
354    fn client_version_panic() {
355        let o = ObjectType::Other("PipeWire:Interface:Badger".to_string());
356        assert_eq!(o.client_version(), 0);
357    }
358}