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#[derive(Debug)]
20pub struct Metadata {
21    proxy: Proxy,
22}
23
24impl ProxyT for Metadata {
25    fn type_() -> ObjectType {
26        ObjectType::Metadata
27    }
28
29    fn upcast(self) -> Proxy {
30        self.proxy
31    }
32
33    fn upcast_ref(&self) -> &Proxy {
34        &self.proxy
35    }
36
37    unsafe fn from_proxy_unchecked(proxy: Proxy) -> Self
38    where
39        Self: Sized,
40    {
41        Self { proxy }
42    }
43}
44
45impl Metadata {
46    pub fn add_listener_local(&self) -> MetadataListenerLocalBuilder<'_> {
47        MetadataListenerLocalBuilder {
48            metadata: self,
49            cbs: ListenerLocalCallbacks::default(),
50        }
51    }
52
53    pub fn set_property(&self, subject: u32, key: &str, type_: Option<&str>, value: Option<&str>) {
54        // Keep CStrings allocated here in order for pointers to remain valid.
55        let key = CString::new(key).expect("Invalid byte in metadata key");
56        let type_ = type_.map(|t| CString::new(t).expect("Invalid byte in metadata type"));
57        let value = value.map(|v| CString::new(v).expect("Invalid byte in metadata value"));
58        let key_cstr = key.as_c_str();
59
60        Metadata::set_property_cstr(self, subject, key_cstr, type_.as_deref(), value.as_deref())
61    }
62
63    pub fn set_property_cstr(
64        &self,
65        subject: u32,
66        key: &CStr,
67        type_: Option<&CStr>,
68        value: Option<&CStr>,
69    ) {
70        unsafe {
71            spa::spa_interface_call_method!(
72                self.proxy.as_ptr(),
73                pw_sys::pw_metadata_methods,
74                set_property,
75                subject,
76                key.as_ptr() as *const _,
77                type_.map_or_else(ptr::null, CStr::as_ptr) as *const _,
78                value.map_or_else(ptr::null, CStr::as_ptr) as *const _
79            );
80        }
81    }
82
83    pub fn clear(&self) {
84        unsafe {
85            spa::spa_interface_call_method!(
86                self.proxy.as_ptr(),
87                pw_sys::pw_metadata_methods,
88                clear,
89            );
90        }
91    }
92}
93
94pub struct MetadataListener {
95    // Need to stay allocated while the listener is registered
96    #[allow(dead_code)]
97    events: Pin<Box<pw_sys::pw_metadata_events>>,
98    listener: Pin<Box<spa_sys::spa_hook>>,
99    #[allow(dead_code)]
100    data: Box<ListenerLocalCallbacks>,
101}
102
103impl Listener for MetadataListener {}
104
105impl Drop for MetadataListener {
106    fn drop(&mut self) {
107        spa::utils::hook::remove(*self.listener);
108    }
109}
110
111#[derive(Default)]
112struct ListenerLocalCallbacks {
113    #[allow(clippy::type_complexity)]
114    property: Option<Box<dyn Fn(u32, Option<&str>, Option<&str>, Option<&str>) -> i32>>,
115}
116
117#[must_use]
118pub struct MetadataListenerLocalBuilder<'meta> {
119    metadata: &'meta Metadata,
120    cbs: ListenerLocalCallbacks,
121}
122
123impl<'meta> MetadataListenerLocalBuilder<'meta> {
124    /// Add property changed callback.
125    ///
126    /// Callback parameters: subject, key, type, value.
127    ///
128    /// `None` for `value` means removal of property.
129    /// `None` for `key` means removal of all properties.
130    pub fn property<F>(mut self, property: F) -> Self
131    where
132        F: Fn(u32, Option<&str>, Option<&str>, Option<&str>) -> i32 + 'static,
133    {
134        self.cbs.property = Some(Box::new(property));
135        self
136    }
137
138    #[must_use]
139    pub fn register(self) -> MetadataListener {
140        unsafe extern "C" fn metadata_events_property(
141            data: *mut c_void,
142            subject: u32,
143            key: *const c_char,
144            type_: *const c_char,
145            value: *const c_char,
146        ) -> i32 {
147            let callbacks = (data as *mut ListenerLocalCallbacks).as_ref().unwrap();
148            let key = if !key.is_null() {
149                Some(CStr::from_ptr(key).to_string_lossy())
150            } else {
151                None
152            };
153            let type_ = if !type_.is_null() {
154                Some(CStr::from_ptr(type_).to_string_lossy())
155            } else {
156                None
157            };
158            let value = if !value.is_null() {
159                Some(CStr::from_ptr(value).to_string_lossy())
160            } else {
161                None
162            };
163            callbacks.property.as_ref().unwrap()(
164                subject,
165                key.as_deref(),
166                type_.as_deref(),
167                value.as_deref(),
168            )
169        }
170
171        let e = unsafe {
172            let mut e: Pin<Box<pw_sys::pw_metadata_events>> = Box::pin(mem::zeroed());
173            e.version = pw_sys::PW_VERSION_METADATA_EVENTS;
174
175            if self.cbs.property.is_some() {
176                e.property = Some(metadata_events_property);
177            }
178
179            e
180        };
181
182        let (listener, data) = unsafe {
183            let metadata = &self.metadata.proxy.as_ptr();
184
185            let data = Box::into_raw(Box::new(self.cbs));
186            let mut listener: Pin<Box<spa_sys::spa_hook>> = Box::pin(mem::zeroed());
187            let listener_ptr: *mut spa_sys::spa_hook = listener.as_mut().get_unchecked_mut();
188
189            spa_interface_call_method!(
190                metadata,
191                pw_sys::pw_metadata_methods,
192                add_listener,
193                listener_ptr.cast(),
194                e.as_ref().get_ref(),
195                data as *mut _
196            );
197
198            (listener, Box::from_raw(data))
199        };
200
201        MetadataListener {
202            events: e,
203            listener,
204            data,
205        }
206    }
207}