pipewire/core/
mod.rs

1// Copyright The pipewire-rs Contributors.
2// SPDX-License-Identifier: MIT
3
4use bitflags::bitflags;
5use libc::{c_char, c_void};
6use std::{
7    ffi::{CStr, CString},
8    fmt, mem,
9    pin::Pin,
10    ptr,
11};
12
13use crate::{
14    proxy::{Proxy, ProxyT},
15    registry::RegistryBox,
16    Error,
17};
18use spa::{
19    spa_interface_call_method,
20    utils::result::{AsyncSeq, SpaResult},
21};
22
23mod box_;
24pub use box_::*;
25mod rc;
26pub use rc::*;
27
28pub const PW_ID_CORE: u32 = pw_sys::PW_ID_CORE;
29
30#[repr(transparent)]
31pub struct Core(pw_sys::pw_core);
32
33impl Core {
34    pub fn as_raw(&self) -> &pw_sys::pw_core {
35        &self.0
36    }
37
38    pub fn as_raw_ptr(&self) -> *mut pw_sys::pw_core {
39        std::ptr::addr_of!(self.0).cast_mut()
40    }
41
42    // TODO: add non-local version when we'll bind pw_thread_loop_start()
43    #[must_use]
44    pub fn add_listener_local(&self) -> ListenerLocalBuilder<'_> {
45        ListenerLocalBuilder {
46            core: self,
47            cbs: ListenerLocalCallbacks::default(),
48        }
49    }
50
51    pub fn get_registry(&self) -> Result<RegistryBox<'_>, Error> {
52        unsafe {
53            let registry = spa_interface_call_method!(
54                self.as_raw_ptr(),
55                pw_sys::pw_core_methods,
56                get_registry,
57                pw_sys::PW_VERSION_REGISTRY,
58                0
59            );
60            let registry = ptr::NonNull::new(registry).ok_or(Error::CreationFailed)?;
61            Ok(RegistryBox::from_raw(registry))
62        }
63    }
64
65    pub fn sync(&self, seq: i32) -> Result<AsyncSeq, Error> {
66        let res = unsafe {
67            spa_interface_call_method!(
68                self.as_raw_ptr(),
69                pw_sys::pw_core_methods,
70                sync,
71                PW_ID_CORE,
72                seq
73            )
74        };
75
76        let res = SpaResult::from_c(res).into_async_result()?;
77        Ok(res)
78    }
79
80    /// Create a new object on the PipeWire server from a factory.
81    ///
82    /// You will need specify what type you are expecting to be constructed by either using type inference or the
83    /// turbofish syntax.
84    ///
85    /// # Parameters
86    /// - `factory_name` the name of the factory to use
87    /// - `properties` extra properties that the new object will have
88    ///
89    /// # Panics
90    /// If `factory_name` contains a null byte.
91    ///
92    /// # Returns
93    /// One of:
94    /// - `Ok(P)` on success, where `P` is the newly created object
95    /// - `Err(Error::CreationFailed)` if the object could not be created
96    /// - `Err(Error::WrongProxyType)` if the created type does not match the type `P` that the user is trying to create
97    ///
98    /// # Examples
99    /// Creating a new link:
100    // Doctest ignored, as the factory name is hardcoded, but may be different on different systems.
101    /// ```ignore
102    /// use pipewire as pw;
103    ///
104    /// pw::init();
105    ///
106    /// let mainloop = pw::MainLoop::new().expect("Failed to create Pipewire Mainloop");
107    /// let context = pw::Context::new(&mainloop).expect("Failed to create Pipewire Context");
108    /// let core = context
109    ///     .connect(None)
110    ///     .expect("Failed to connect to Pipewire Core");
111    ///
112    /// // This call uses turbofish syntax to specify that we want a link.
113    /// let link = core.create_object::<pw::link::Link>(
114    ///     // The actual name for a link factory might be different for your system,
115    ///     // you should probably obtain a factory from the registry.
116    ///     "link-factory",
117    ///     &pw::properties! {
118    ///         "link.output.port" => "1",
119    ///         "link.input.port" => "2",
120    ///         "link.output.node" => "3",
121    ///         "link.input.node" => "4"
122    ///     },
123    /// )
124    /// .expect("Failed to create object");
125    /// ```
126    ///
127    /// See `pipewire/examples/create-delete-remote-objects.rs` in the crates repository for a more detailed example.
128    pub fn create_object<P: ProxyT>(
129        &self,
130        factory_name: &str,
131        properties: &impl AsRef<spa::utils::dict::DictRef>,
132    ) -> Result<P, Error> {
133        let factory_name = CString::new(factory_name).expect("Null byte in factory_name parameter");
134        let factory_name_cstr = factory_name.as_c_str();
135        self.create_object_cstr(factory_name_cstr, properties)
136    }
137
138    pub fn create_object_cstr<P: ProxyT>(
139        &self,
140        factory_name: &CStr,
141        properties: &impl AsRef<spa::utils::dict::DictRef>,
142    ) -> Result<P, Error> {
143        let type_ = P::type_();
144        let type_str = CString::new(type_.to_string())
145            .expect("Null byte in string representation of type_ parameter");
146
147        let res = unsafe {
148            spa_interface_call_method!(
149                self.as_raw_ptr(),
150                pw_sys::pw_core_methods,
151                create_object,
152                factory_name.as_ptr(),
153                type_str.as_ptr(),
154                type_.client_version(),
155                properties.as_ref().as_raw_ptr(),
156                0
157            )
158        };
159
160        let ptr = ptr::NonNull::new(res.cast()).ok_or(Error::CreationFailed)?;
161
162        Proxy::new(ptr).downcast().map_err(|(_, e)| e)
163    }
164
165    /// Destroy the object on the remote server represented by the provided proxy.
166    ///
167    /// The proxy will be destroyed alongside the server side resource, as it is no longer needed.
168    pub fn destroy_object<P: ProxyT>(&self, proxy: P) -> Result<AsyncSeq, Error> {
169        let res = unsafe {
170            spa_interface_call_method!(
171                self.as_raw_ptr(),
172                pw_sys::pw_core_methods,
173                destroy,
174                proxy.upcast_ref().as_ptr() as *mut c_void
175            )
176        };
177
178        let res = SpaResult::from_c(res).into_async_result()?;
179        Ok(res)
180    }
181}
182
183#[derive(Default)]
184struct ListenerLocalCallbacks {
185    #[allow(clippy::type_complexity)]
186    info: Option<Box<dyn Fn(&Info)>>,
187    done: Option<Box<dyn Fn(u32, AsyncSeq)>>,
188    #[allow(clippy::type_complexity)]
189    error: Option<Box<dyn Fn(u32, i32, i32, &str)>>, // TODO: return a proper Error enum?
190                                                     // TODO: ping, remove_id, bound_id, add_mem, remove_mem
191}
192
193pub struct ListenerLocalBuilder<'a> {
194    core: &'a Core,
195    cbs: ListenerLocalCallbacks,
196}
197
198pub struct Listener {
199    // Need to stay allocated while the listener is registered
200    #[allow(dead_code)]
201    events: Pin<Box<pw_sys::pw_core_events>>,
202    listener: Pin<Box<spa_sys::spa_hook>>,
203    #[allow(dead_code)]
204    data: Box<ListenerLocalCallbacks>,
205}
206
207impl Listener {
208    pub fn unregister(self) {
209        // Consuming the listener will call drop()
210    }
211}
212
213impl Drop for Listener {
214    fn drop(&mut self) {
215        spa::utils::hook::remove(*self.listener);
216    }
217}
218
219impl<'a> ListenerLocalBuilder<'a> {
220    #[must_use]
221    pub fn info<F>(mut self, info: F) -> Self
222    where
223        F: Fn(&Info) + 'static,
224    {
225        self.cbs.info = Some(Box::new(info));
226        self
227    }
228
229    #[must_use]
230    pub fn done<F>(mut self, done: F) -> Self
231    where
232        F: Fn(u32, AsyncSeq) + 'static,
233    {
234        self.cbs.done = Some(Box::new(done));
235        self
236    }
237
238    #[must_use]
239    pub fn error<F>(mut self, error: F) -> Self
240    where
241        F: Fn(u32, i32, i32, &str) + 'static,
242    {
243        self.cbs.error = Some(Box::new(error));
244        self
245    }
246
247    #[must_use]
248    pub fn register(self) -> Listener {
249        unsafe extern "C" fn core_events_info(
250            data: *mut c_void,
251            info: *const pw_sys::pw_core_info,
252        ) {
253            let callbacks = (data as *mut ListenerLocalCallbacks).as_ref().unwrap();
254            let info = Info::new(ptr::NonNull::new(info as *mut _).expect("info is NULL"));
255            callbacks.info.as_ref().unwrap()(&info);
256        }
257
258        unsafe extern "C" fn core_events_done(data: *mut c_void, id: u32, seq: i32) {
259            let callbacks = (data as *mut ListenerLocalCallbacks).as_ref().unwrap();
260            callbacks.done.as_ref().unwrap()(id, AsyncSeq::from_raw(seq));
261        }
262
263        unsafe extern "C" fn core_events_error(
264            data: *mut c_void,
265            id: u32,
266            seq: i32,
267            res: i32,
268            message: *const c_char,
269        ) {
270            let callbacks = (data as *mut ListenerLocalCallbacks).as_ref().unwrap();
271            let message = CStr::from_ptr(message).to_str().unwrap();
272            callbacks.error.as_ref().unwrap()(id, seq, res, message);
273        }
274
275        let e = unsafe {
276            let mut e: Pin<Box<pw_sys::pw_core_events>> = Box::pin(mem::zeroed());
277            e.version = pw_sys::PW_VERSION_CORE_EVENTS;
278
279            if self.cbs.info.is_some() {
280                e.info = Some(core_events_info);
281            }
282            if self.cbs.done.is_some() {
283                e.done = Some(core_events_done);
284            }
285            if self.cbs.error.is_some() {
286                e.error = Some(core_events_error);
287            }
288
289            e
290        };
291
292        let (listener, data) = unsafe {
293            let ptr = self.core.as_raw_ptr();
294            let data = Box::into_raw(Box::new(self.cbs));
295            let mut listener: Pin<Box<spa_sys::spa_hook>> = Box::pin(mem::zeroed());
296            // Have to cast from pw-sys namespaced type to the equivalent spa-sys type
297            // as bindgen does not allow us to generate bindings dependings of another
298            // sys crate, see https://github.com/rust-lang/rust-bindgen/issues/1929
299            let listener_ptr: *mut spa_sys::spa_hook = listener.as_mut().get_unchecked_mut();
300
301            spa_interface_call_method!(
302                ptr,
303                pw_sys::pw_core_methods,
304                add_listener,
305                listener_ptr.cast(),
306                e.as_ref().get_ref(),
307                data as *mut _
308            );
309
310            (listener, Box::from_raw(data))
311        };
312
313        Listener {
314            events: e,
315            listener,
316            data,
317        }
318    }
319}
320
321pub struct Info {
322    ptr: ptr::NonNull<pw_sys::pw_core_info>,
323}
324
325impl Info {
326    fn new(info: ptr::NonNull<pw_sys::pw_core_info>) -> Self {
327        Self { ptr: info }
328    }
329
330    pub fn id(&self) -> u32 {
331        unsafe { self.ptr.as_ref().id }
332    }
333
334    pub fn cookie(&self) -> u32 {
335        unsafe { self.ptr.as_ref().cookie }
336    }
337
338    pub fn user_name(&self) -> &str {
339        unsafe {
340            CStr::from_ptr(self.ptr.as_ref().user_name)
341                .to_str()
342                .unwrap()
343        }
344    }
345
346    pub fn host_name(&self) -> &str {
347        unsafe {
348            CStr::from_ptr(self.ptr.as_ref().host_name)
349                .to_str()
350                .unwrap()
351        }
352    }
353
354    pub fn version(&self) -> &str {
355        unsafe { CStr::from_ptr(self.ptr.as_ref().version).to_str().unwrap() }
356    }
357
358    pub fn name(&self) -> &str {
359        unsafe { CStr::from_ptr(self.ptr.as_ref().name).to_str().unwrap() }
360    }
361
362    pub fn change_mask(&self) -> ChangeMask {
363        let mask = unsafe { self.ptr.as_ref().change_mask };
364        ChangeMask::from_bits_retain(mask)
365    }
366
367    pub fn props(&self) -> Option<&spa::utils::dict::DictRef> {
368        let props_ptr: *mut spa::utils::dict::DictRef = unsafe { self.ptr.as_ref().props.cast() };
369
370        ptr::NonNull::new(props_ptr).map(|ptr| unsafe { ptr.as_ref() })
371    }
372}
373
374impl fmt::Debug for Info {
375    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
376        f.debug_struct("CoreInfo")
377            .field("id", &self.id())
378            .field("cookie", &self.cookie())
379            .field("user-name", &self.user_name())
380            .field("host-name", &self.host_name())
381            .field("version", &self.version())
382            .field("name", &self.name())
383            .field("change-mask", &self.change_mask())
384            .field("props", &self.props())
385            .finish()
386    }
387}
388
389bitflags! {
390    #[derive(Debug, PartialEq, Eq, Clone, Copy)]
391    pub struct ChangeMask: u64 {
392        const PROPS = pw_sys::PW_CORE_CHANGE_MASK_PROPS as u64;
393    }
394}