pipewire/
metadata.rs

1// Copyright The pipewire-rs Contributors.
2// SPDX-License-Identifier: MIT
3
4use std::ffi::CString;
5use std::os::raw::c_char;
6use std::{
7    ffi::{c_void, CStr},
8    mem,
9    pin::Pin,
10    ptr,
11};
12
13use crate::{
14    proxy::{Listener, Proxy, ProxyT},
15    types::ObjectType,
16};
17use spa::spa_interface_call_method;
18
19/// A [proxy][Proxy] to a metadata.
20#[derive(Debug)]
21pub struct Metadata {
22    proxy: Proxy,
23}
24
25impl ProxyT for Metadata {
26    fn type_() -> ObjectType {
27        ObjectType::Metadata
28    }
29
30    fn upcast(self) -> Proxy {
31        self.proxy
32    }
33
34    fn upcast_ref(&self) -> &Proxy {
35        &self.proxy
36    }
37
38    unsafe fn from_proxy_unchecked(proxy: Proxy) -> Self
39    where
40        Self: Sized,
41    {
42        Self { proxy }
43    }
44}
45
46impl Metadata {
47    #[must_use = "Use the builder to register event callbacks"]
48    pub fn add_listener_local(&self) -> MetadataListenerLocalBuilder<'_> {
49        MetadataListenerLocalBuilder {
50            metadata: self,
51            cbs: ListenerLocalCallbacks::default(),
52        }
53    }
54
55    /// Set a metadata property.
56    ///
57    /// # Permissions
58    /// Requires [`W`](crate::permissions::PermissionFlags::W) and
59    /// [`X`](crate::permissions::PermissionFlags::X) on the metadata.
60    /// Also requires [`M`](crate::permissions::PermissionFlags::M) on the `subject` global.
61    pub fn set_property(&self, subject: u32, key: &str, type_: Option<&str>, value: Option<&str>) {
62        // Keep CStrings allocated here in order for pointers to remain valid.
63        let key = CString::new(key).expect("Invalid byte in metadata key");
64        let type_ = type_.map(|t| CString::new(t).expect("Invalid byte in metadata type"));
65        let value = value.map(|v| CString::new(v).expect("Invalid byte in metadata value"));
66        let key_cstr = key.as_c_str();
67
68        Metadata::set_property_cstr(self, subject, key_cstr, type_.as_deref(), value.as_deref())
69    }
70
71    /// Set a metadata property.
72    ///
73    /// # Permissions
74    /// Requires [`W`](crate::permissions::PermissionFlags::W) and
75    /// [`X`](crate::permissions::PermissionFlags::X) on the metadata.
76    /// Also requires [`M`](crate::permissions::PermissionFlags::M) on the `subject` global.
77    pub fn set_property_cstr(
78        &self,
79        subject: u32,
80        key: &CStr,
81        type_: Option<&CStr>,
82        value: Option<&CStr>,
83    ) {
84        unsafe {
85            spa::spa_interface_call_method!(
86                self.proxy.as_ptr(),
87                pw_sys::pw_metadata_methods,
88                set_property,
89                subject,
90                key.as_ptr() as *const _,
91                type_.map_or_else(ptr::null, CStr::as_ptr) as *const _,
92                value.map_or_else(ptr::null, CStr::as_ptr) as *const _
93            );
94        }
95    }
96
97    /// Clear all metadata.
98    ///
99    /// # Permissions
100    /// Requires [`W`](crate::permissions::PermissionFlags::W) and
101    /// [`X`](crate::permissions::PermissionFlags::X) on the metadata.
102    pub fn clear(&self) {
103        unsafe {
104            spa::spa_interface_call_method!(
105                self.proxy.as_ptr(),
106                pw_sys::pw_metadata_methods,
107                clear,
108            );
109        }
110    }
111}
112
113/// An owned listener for metadata events.
114///
115/// This is created by [`MetadataListenerLocalBuilder`] and will receive events as long as it is alive.
116/// When this gets dropped, the listener gets unregistered and no events will be received by it.
117///
118/// # Examples
119/// ```
120/// # use pipewire::metadata::Metadata;
121/// # fn example(metadata: Metadata) {
122/// let metadata_listener = metadata.add_listener_local()
123///     .property(|subject, key, type_, value| {
124///         println!("Metadata property update: subject {subject}, key {key:?}, type {type_:?}, value {value:?}");
125///         0
126///     })
127///     .register();
128/// # }
129/// ```
130#[must_use = "Listeners unregister themselves when dropped. Keep the listener alive in order to receive events."]
131pub struct MetadataListener {
132    // Need to stay allocated while the listener is registered
133    #[allow(dead_code)]
134    events: Pin<Box<pw_sys::pw_metadata_events>>,
135    listener: Pin<Box<spa_sys::spa_hook>>,
136    #[allow(dead_code)]
137    data: Box<ListenerLocalCallbacks>,
138}
139
140impl Listener for MetadataListener {}
141
142impl Drop for MetadataListener {
143    fn drop(&mut self) {
144        spa::utils::hook::remove(*self.listener);
145    }
146}
147
148#[derive(Default)]
149struct ListenerLocalCallbacks {
150    #[allow(clippy::type_complexity)]
151    property: Option<Box<dyn Fn(u32, Option<&str>, Option<&str>, Option<&str>) -> i32>>,
152}
153
154/// A builder for registering metadata event callbacks.
155///
156/// Use [`Metadata::add_listener_local`] to create this and register callbacks that will be called when events of interest occur.
157/// After adding callbacks, use [`register`](Self::register) to get back a [`MetadataListener`].
158/// # Examples
159/// ```
160/// # use pipewire::metadata::Metadata;
161/// # fn example(metadata: Metadata) {
162/// let metadata_listener = metadata.add_listener_local()
163///     .property(|subject, key, type_, value| {
164///         println!("Metadata property update: subject {subject}, key {key:?}, type {type_:?}, value {value:?}");
165///         0
166///     })
167///     .register();
168/// # }
169/// ```
170pub struct MetadataListenerLocalBuilder<'meta> {
171    metadata: &'meta Metadata,
172    cbs: ListenerLocalCallbacks,
173}
174
175impl<'meta> MetadataListenerLocalBuilder<'meta> {
176    /// Set the metadata `property` event callback of the listener.
177    ///
178    /// # Callback parameters
179    /// `subject`, `key`, `type`, `value`.
180    ///
181    /// [`None`] for `key` means removal of all properties.
182    /// [`None`] for `value` means removal of property with key `key`.  
183    ///
184    /// # Examples
185    /// ```
186    /// # use pipewire::metadata::Metadata;
187    /// # fn example(metadata: Metadata) {
188    /// let metadata_listener = metadata.add_listener_local()
189    ///     .property(|subject, key, type_, value| {
190    ///         println!("Metadata property update: subject {subject}, key {key:?}, type {type_:?}, value {value:?}");
191    ///         0
192    ///     })
193    ///     .register();
194    /// # }
195    /// ```
196    #[must_use = "Call `.register()` to start receiving events"]
197    pub fn property<F>(mut self, property: F) -> Self
198    where
199        F: Fn(u32, Option<&str>, Option<&str>, Option<&str>) -> i32 + 'static,
200    {
201        self.cbs.property = Some(Box::new(property));
202        self
203    }
204
205    /// Subscribe to events and register any provided callbacks.
206    pub fn register(self) -> MetadataListener {
207        unsafe extern "C" fn metadata_events_property(
208            data: *mut c_void,
209            subject: u32,
210            key: *const c_char,
211            type_: *const c_char,
212            value: *const c_char,
213        ) -> i32 {
214            let callbacks = (data as *mut ListenerLocalCallbacks).as_ref().unwrap();
215            let key = if !key.is_null() {
216                Some(CStr::from_ptr(key).to_string_lossy())
217            } else {
218                None
219            };
220            let type_ = if !type_.is_null() {
221                Some(CStr::from_ptr(type_).to_string_lossy())
222            } else {
223                None
224            };
225            let value = if !value.is_null() {
226                Some(CStr::from_ptr(value).to_string_lossy())
227            } else {
228                None
229            };
230            callbacks.property.as_ref().unwrap()(
231                subject,
232                key.as_deref(),
233                type_.as_deref(),
234                value.as_deref(),
235            )
236        }
237
238        let e = unsafe {
239            let mut e: Pin<Box<pw_sys::pw_metadata_events>> = Box::pin(mem::zeroed());
240            e.version = pw_sys::PW_VERSION_METADATA_EVENTS;
241
242            if self.cbs.property.is_some() {
243                e.property = Some(metadata_events_property);
244            }
245
246            e
247        };
248
249        let (listener, data) = unsafe {
250            let metadata = &self.metadata.proxy.as_ptr();
251
252            let data = Box::into_raw(Box::new(self.cbs));
253            let mut listener: Pin<Box<spa_sys::spa_hook>> = Box::pin(mem::zeroed());
254            let listener_ptr: *mut spa_sys::spa_hook = listener.as_mut().get_unchecked_mut();
255
256            spa_interface_call_method!(
257                metadata,
258                pw_sys::pw_metadata_methods,
259                add_listener,
260                listener_ptr.cast(),
261                e.as_ref().get_ref(),
262                data as *mut _
263            );
264
265            (listener, Box::from_raw(data))
266        };
267
268        MetadataListener {
269            events: e,
270            listener,
271            data,
272        }
273    }
274}