pipewire/registry/
mod.rs

1// Copyright The pipewire-rs Contributors.
2// SPDX-License-Identifier: MIT
3
4use libc::{c_char, c_void};
5
6use std::{
7    ffi::{CStr, CString},
8    mem,
9    pin::Pin,
10    ptr,
11};
12
13use crate::{
14    permissions::PermissionFlags,
15    properties::PropertiesBox,
16    proxy::{Proxy, ProxyT},
17    types::ObjectType,
18    Error,
19};
20
21mod box_;
22pub use box_::*;
23mod rc;
24pub use rc::*;
25
26#[repr(transparent)]
27pub struct Registry(pw_sys::pw_registry);
28
29impl Registry {
30    pub fn as_raw(&self) -> &pw_sys::pw_registry {
31        &self.0
32    }
33
34    pub fn as_raw_ptr(&self) -> *mut pw_sys::pw_registry {
35        std::ptr::addr_of!(self.0).cast_mut()
36    }
37
38    // TODO: add non-local version when we'll bind pw_thread_loop_start()
39    #[must_use]
40    pub fn add_listener_local(&self) -> ListenerLocalBuilder<'_> {
41        ListenerLocalBuilder {
42            registry: self,
43            cbs: ListenerLocalCallbacks::default(),
44        }
45    }
46
47    pub fn bind<T: ProxyT, P: AsRef<spa::utils::dict::DictRef>>(
48        &self,
49        object: &GlobalObject<P>,
50    ) -> Result<T, Error> {
51        let proxy = unsafe {
52            let type_ = CString::new(object.type_.to_str()).unwrap();
53            let version = object.type_.client_version();
54
55            let proxy = spa::spa_interface_call_method!(
56                self.as_raw_ptr(),
57                pw_sys::pw_registry_methods,
58                bind,
59                object.id,
60                type_.as_ptr(),
61                version,
62                0
63            );
64
65            proxy
66        };
67
68        let proxy = ptr::NonNull::new(proxy.cast()).ok_or(Error::NoMemory)?;
69
70        Proxy::new(proxy).downcast().map_err(|(_, e)| e)
71    }
72
73    /// Attempt to destroy the global object with the specified id on the remote.
74    pub fn destroy_global(&self, global_id: u32) -> spa::utils::result::SpaResult {
75        let result = unsafe {
76            spa::spa_interface_call_method!(
77                self.as_raw_ptr(),
78                pw_sys::pw_registry_methods,
79                destroy,
80                global_id
81            )
82        };
83
84        spa::utils::result::SpaResult::from_c(result)
85    }
86}
87
88type GlobalCallback = dyn Fn(&GlobalObject<&spa::utils::dict::DictRef>);
89type GlobalRemoveCallback = dyn Fn(u32);
90
91#[derive(Default)]
92struct ListenerLocalCallbacks {
93    global: Option<Box<GlobalCallback>>,
94    global_remove: Option<Box<GlobalRemoveCallback>>,
95}
96
97pub struct ListenerLocalBuilder<'a> {
98    registry: &'a Registry,
99    cbs: ListenerLocalCallbacks,
100}
101
102pub struct Listener {
103    // Need to stay allocated while the listener is registered
104    #[allow(dead_code)]
105    events: Pin<Box<pw_sys::pw_registry_events>>,
106    listener: Pin<Box<spa_sys::spa_hook>>,
107    #[allow(dead_code)]
108    data: Box<ListenerLocalCallbacks>,
109}
110
111impl Drop for Listener {
112    fn drop(&mut self) {
113        spa::utils::hook::remove(*self.listener);
114    }
115}
116
117impl<'a> ListenerLocalBuilder<'a> {
118    #[must_use]
119    pub fn global<F>(mut self, global: F) -> Self
120    where
121        F: Fn(&GlobalObject<&spa::utils::dict::DictRef>) + 'static,
122    {
123        self.cbs.global = Some(Box::new(global));
124        self
125    }
126
127    #[must_use]
128    pub fn global_remove<F>(mut self, global_remove: F) -> Self
129    where
130        F: Fn(u32) + 'static,
131    {
132        self.cbs.global_remove = Some(Box::new(global_remove));
133        self
134    }
135
136    #[must_use]
137    pub fn register(self) -> Listener {
138        unsafe extern "C" fn registry_events_global(
139            data: *mut c_void,
140            id: u32,
141            permissions: u32,
142            type_: *const c_char,
143            version: u32,
144            props: *const spa_sys::spa_dict,
145        ) {
146            let type_ = CStr::from_ptr(type_).to_str().unwrap();
147            let obj = GlobalObject::new(id, permissions, type_, version, props);
148            let callbacks = (data as *mut ListenerLocalCallbacks).as_ref().unwrap();
149            callbacks.global.as_ref().unwrap()(&obj);
150        }
151
152        unsafe extern "C" fn registry_events_global_remove(data: *mut c_void, id: u32) {
153            let callbacks = (data as *mut ListenerLocalCallbacks).as_ref().unwrap();
154            callbacks.global_remove.as_ref().unwrap()(id);
155        }
156
157        let e = unsafe {
158            let mut e: Pin<Box<pw_sys::pw_registry_events>> = Box::pin(mem::zeroed());
159            e.version = pw_sys::PW_VERSION_REGISTRY_EVENTS;
160
161            if self.cbs.global.is_some() {
162                e.global = Some(registry_events_global);
163            }
164            if self.cbs.global_remove.is_some() {
165                e.global_remove = Some(registry_events_global_remove);
166            }
167
168            e
169        };
170
171        let (listener, data) = unsafe {
172            let ptr = self.registry.as_raw_ptr();
173            let data = Box::into_raw(Box::new(self.cbs));
174            let mut listener: Pin<Box<spa_sys::spa_hook>> = Box::pin(mem::zeroed());
175            let listener_ptr: *mut spa_sys::spa_hook = listener.as_mut().get_unchecked_mut();
176
177            spa::spa_interface_call_method!(
178                ptr,
179                pw_sys::pw_registry_methods,
180                add_listener,
181                listener_ptr.cast(),
182                e.as_ref().get_ref(),
183                data as *mut _
184            );
185
186            (listener, Box::from_raw(data))
187        };
188
189        Listener {
190            events: e,
191            listener,
192            data,
193        }
194    }
195}
196
197#[derive(Debug)]
198pub struct GlobalObject<P: AsRef<spa::utils::dict::DictRef>> {
199    pub id: u32,
200    pub permissions: PermissionFlags,
201    pub type_: ObjectType,
202    pub version: u32,
203    pub props: Option<P>,
204}
205
206impl GlobalObject<&spa::utils::dict::DictRef> {
207    unsafe fn new(
208        id: u32,
209        permissions: u32,
210        type_: &str,
211        version: u32,
212        props: *const spa_sys::spa_dict,
213    ) -> Self {
214        let type_ = ObjectType::from_str(type_);
215        let permissions = PermissionFlags::from_bits_retain(permissions);
216        let props = ptr::NonNull::new(props.cast_mut())
217            .map(|ptr| ptr.cast::<spa::utils::dict::DictRef>().as_ref());
218
219        Self {
220            id,
221            permissions,
222            type_,
223            version,
224            props,
225        }
226    }
227}
228
229impl<P: AsRef<spa::utils::dict::DictRef>> GlobalObject<P> {
230    pub fn to_owned(&self) -> GlobalObject<PropertiesBox> {
231        GlobalObject {
232            id: self.id,
233            permissions: self.permissions,
234            type_: self.type_.clone(),
235            version: self.version,
236            props: self
237                .props
238                .as_ref()
239                .map(|props| PropertiesBox::from_dict(props.as_ref())),
240        }
241    }
242}
243
244#[cfg(test)]
245mod tests {
246    use super::*;
247    #[test]
248    fn set_object_type() {
249        assert_eq!(
250            ObjectType::from_str("PipeWire:Interface:Client"),
251            ObjectType::Client
252        );
253        assert_eq!(ObjectType::Client.to_str(), "PipeWire:Interface:Client");
254        assert_eq!(ObjectType::Client.client_version(), 3);
255
256        let o = ObjectType::Other("PipeWire:Interface:Badger".to_string());
257        assert_eq!(ObjectType::from_str("PipeWire:Interface:Badger"), o);
258        assert_eq!(o.to_str(), "PipeWire:Interface:Badger");
259    }
260
261    #[test]
262    #[should_panic(expected = "Invalid object type")]
263    fn client_version_panic() {
264        let o = ObjectType::Other("PipeWire:Interface:Badger".to_string());
265        assert_eq!(o.client_version(), 0);
266    }
267}