1use 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#[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 #[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 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 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
135pub struct ListenerLocalBuilder<'a> {
152 registry: &'a Registry,
153 cbs: ListenerLocalCallbacks,
154}
155
156#[must_use = "Listeners unregister themselves when dropped. Keep the listener alive in order to receive events."]
161pub struct Listener {
162 #[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 #[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 #[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 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}