pipewire/core/
mod.rs

1// Copyright The pipewire-rs Contributors.
2// SPDX-License-Identifier: MIT
3
4//! The core singleton object used in communication with a PipeWire instance.
5//!
6//! This module contains wrappers for [`pw_core`](pw_sys::pw_core) and related items.
7
8use bitflags::bitflags;
9use libc::{c_char, c_void};
10use std::{
11    ffi::{CStr, CString},
12    fmt, mem,
13    pin::Pin,
14    ptr,
15};
16
17use crate::{
18    proxy::{Proxy, ProxyT},
19    registry::RegistryBox,
20    Error,
21};
22use spa::{
23    spa_interface_call_method,
24    utils::result::{AsyncSeq, SpaResult},
25};
26
27mod box_;
28pub use box_::*;
29mod rc;
30pub use rc::*;
31
32pub const PW_ID_CORE: u32 = pw_sys::PW_ID_CORE;
33
34/// Transparent wrapper around a [core](self).
35///
36/// This does not own the underlying object and is usually seen behind a `&` reference.
37///
38/// For owning wrappers, see [`CoreBox`] and [`CoreRc`].
39///
40/// For an explanation of these, see [Smart pointers to PipeWire
41/// objects](crate#smart-pointers-to-pipewire-objects).
42#[repr(transparent)]
43pub struct Core(pw_sys::pw_core);
44
45impl Core {
46    pub fn as_raw(&self) -> &pw_sys::pw_core {
47        &self.0
48    }
49
50    pub fn as_raw_ptr(&self) -> *mut pw_sys::pw_core {
51        std::ptr::addr_of!(self.0).cast_mut()
52    }
53
54    // TODO: add non-local version when we'll bind pw_thread_loop_start()
55    #[must_use = "Use the builder to register event callbacks"]
56    pub fn add_listener_local(&self) -> ListenerLocalBuilder<'_> {
57        ListenerLocalBuilder {
58            core: self,
59            cbs: ListenerLocalCallbacks::default(),
60        }
61    }
62
63    pub fn get_registry(&self) -> Result<RegistryBox<'_>, Error> {
64        unsafe {
65            let registry = spa_interface_call_method!(
66                self.as_raw_ptr(),
67                pw_sys::pw_core_methods,
68                get_registry,
69                pw_sys::PW_VERSION_REGISTRY,
70                0
71            );
72            let registry = ptr::NonNull::new(registry).ok_or(Error::CreationFailed)?;
73            Ok(RegistryBox::from_raw(registry))
74        }
75    }
76
77    /// Do server roundtrip.
78    ///
79    /// Ask the server to emit the [`done`](ListenerLocalBuilder::done) event with `seq`.
80    ///
81    /// Since methods are handled in-order and events are delivered in-order, this can be used as a barrier
82    /// to ensure all previous methods and the resulting events have been handled.
83    pub fn sync(&self, seq: i32) -> Result<AsyncSeq, Error> {
84        let res = unsafe {
85            spa_interface_call_method!(
86                self.as_raw_ptr(),
87                pw_sys::pw_core_methods,
88                sync,
89                PW_ID_CORE,
90                seq
91            )
92        };
93
94        let res = SpaResult::from_c(res).into_async_result()?;
95        Ok(res)
96    }
97
98    /// Create a new object on the PipeWire server from a factory.
99    ///
100    /// You will need specify what type you are expecting to be constructed by either using type inference or the
101    /// turbofish syntax.
102    ///
103    /// # Parameters
104    /// - `factory_name` the name of the factory to use
105    /// - `properties` extra properties that the new object will have
106    ///
107    /// # Panics
108    /// If `factory_name` contains a null byte.
109    ///
110    /// # Returns
111    /// One of:
112    /// - `Ok(P)` on success, where `P` is the newly created object
113    /// - `Err(Error::CreationFailed)` if the object could not be created
114    /// - `Err(Error::WrongProxyType)` if the created type does not match the type `P` that the user is trying to create
115    ///
116    /// # Examples
117    /// Creating a new link:
118    // Doctest ignored, as the factory name is hardcoded, but may be different on different systems.
119    /// ```ignore
120    /// use pipewire as pw;
121    ///
122    /// pw::init();
123    ///
124    /// let mainloop = pw::MainLoop::new().expect("Failed to create Pipewire Mainloop");
125    /// let context = pw::Context::new(&mainloop).expect("Failed to create Pipewire Context");
126    /// let core = context
127    ///     .connect(None)
128    ///     .expect("Failed to connect to Pipewire Core");
129    ///
130    /// // This call uses turbofish syntax to specify that we want a link.
131    /// let link = core.create_object::<pw::link::Link>(
132    ///     // The actual name for a link factory might be different for your system,
133    ///     // you should probably obtain a factory from the registry.
134    ///     "link-factory",
135    ///     &pw::properties! {
136    ///         "link.output.port" => "1",
137    ///         "link.input.port" => "2",
138    ///         "link.output.node" => "3",
139    ///         "link.input.node" => "4"
140    ///     },
141    /// )
142    /// .expect("Failed to create object");
143    /// ```
144    ///
145    /// See `pipewire/examples/create-delete-remote-objects.rs` in the crates repository for a more detailed example.
146    pub fn create_object<P: ProxyT>(
147        &self,
148        factory_name: &str,
149        properties: &impl AsRef<spa::utils::dict::DictRef>,
150    ) -> Result<P, Error> {
151        let factory_name = CString::new(factory_name).expect("Null byte in factory_name parameter");
152        let factory_name_cstr = factory_name.as_c_str();
153        self.create_object_cstr(factory_name_cstr, properties)
154    }
155
156    pub fn create_object_cstr<P: ProxyT>(
157        &self,
158        factory_name: &CStr,
159        properties: &impl AsRef<spa::utils::dict::DictRef>,
160    ) -> Result<P, Error> {
161        let type_ = P::type_();
162        let type_str = CString::new(type_.to_string())
163            .expect("Null byte in string representation of type_ parameter");
164
165        let res = unsafe {
166            spa_interface_call_method!(
167                self.as_raw_ptr(),
168                pw_sys::pw_core_methods,
169                create_object,
170                factory_name.as_ptr(),
171                type_str.as_ptr(),
172                type_.client_version(),
173                properties.as_ref().as_raw_ptr(),
174                0
175            )
176        };
177
178        let ptr = ptr::NonNull::new(res.cast()).ok_or(Error::CreationFailed)?;
179
180        Proxy::new(ptr).downcast().map_err(|(_, e)| e)
181    }
182
183    /// Destroy the object on the remote server represented by the provided proxy.
184    ///
185    /// The proxy will be destroyed alongside the server side resource, as it is no longer needed.
186    pub fn destroy_object<P: ProxyT>(&self, proxy: P) -> Result<AsyncSeq, Error> {
187        let res = unsafe {
188            spa_interface_call_method!(
189                self.as_raw_ptr(),
190                pw_sys::pw_core_methods,
191                destroy,
192                proxy.upcast_ref().as_ptr() as *mut c_void
193            )
194        };
195
196        let res = SpaResult::from_c(res).into_async_result()?;
197        Ok(res)
198    }
199}
200
201#[derive(Default)]
202struct ListenerLocalCallbacks {
203    #[allow(clippy::type_complexity)]
204    info: Option<Box<dyn Fn(&Info)>>,
205    done: Option<Box<dyn Fn(u32, AsyncSeq)>>,
206    #[allow(clippy::type_complexity)]
207    error: Option<Box<dyn Fn(u32, i32, i32, &str)>>, // TODO: return a proper Error enum?
208                                                     // TODO: ping, remove_id, bound_id, add_mem, remove_mem
209}
210
211/// A builder for registering core event callbacks.
212///
213/// Use [`Core::add_listener_local`] to create this and register callbacks that will be called when events of interest occur.
214/// After adding callbacks, use [`register`](Self::register) to get back a
215/// [`core::Listener`](Listener).
216///
217/// # Examples
218/// ```
219/// # use pipewire::core::Core;
220/// # fn example(core: Core) {
221/// let core_listener = core.add_listener_local()
222///     .info(|info| println!("New core info: {info:?}"))
223///     .done(|id, seq| println!("Object {id} received done with seq {seq:?}"))
224///     .error(|id, seq, res, message| {
225///         println!("Object {id} received error with seq {seq:?}, error code {res} and message {message}")
226///     })
227///     .register();
228/// # }
229/// ```
230pub struct ListenerLocalBuilder<'a> {
231    core: &'a Core,
232    cbs: ListenerLocalCallbacks,
233}
234
235/// An owned listener for core events.
236///
237/// This is created by [`core::ListenerLocalBuilder`][ListenerLocalBuilder] and will receive events as long as it is alive.
238/// When this gets dropped, the listener gets unregistered and no events will be received by it.
239#[must_use = "Listeners unregister themselves when dropped. Keep the listener alive in order to receive events."]
240pub struct Listener {
241    // Need to stay allocated while the listener is registered
242    #[allow(dead_code)]
243    events: Pin<Box<pw_sys::pw_core_events>>,
244    listener: Pin<Box<spa_sys::spa_hook>>,
245    #[allow(dead_code)]
246    data: Box<ListenerLocalCallbacks>,
247}
248
249impl Listener {
250    pub fn unregister(self) {
251        // Consuming the listener will call drop()
252    }
253}
254
255impl Drop for Listener {
256    fn drop(&mut self) {
257        spa::utils::hook::remove(*self.listener);
258    }
259}
260
261impl<'a> ListenerLocalBuilder<'a> {
262    /// Set the core `info` event callback of the listener.
263    ///
264    /// This event is emitted when first bound to the core or when the hello method is called.
265    ///
266    /// # Callback parameters
267    /// `info`: New core info
268    ///
269    /// # Examples
270    /// ```
271    /// # use pipewire::core::Core;
272    /// # fn example(core: Core) {
273    /// let core_listener = core.add_listener_local()
274    ///     .info(|info| println!("New core info: {info:?}"))
275    ///     .register();
276    /// # }
277    /// ```
278    #[must_use = "Call `.register()` to start receiving events"]
279    pub fn info<F>(mut self, info: F) -> Self
280    where
281        F: Fn(&Info) + 'static,
282    {
283        self.cbs.info = Some(Box::new(info));
284        self
285    }
286
287    /// Set the core `done` event callback of the listener.
288    ///
289    /// This event is emitted as a result of a [`sync`](Core::sync) call with the same seq number.
290    ///
291    /// # Callback parameters
292    /// `id`: Object where the done event occurred  
293    /// `seq`: The seq number passed to the [`sync`](Core::sync) call that emitted this.
294    ///
295    /// # Examples
296    /// ```
297    /// # use pipewire::core::Core;
298    /// # fn example(core: Core) {
299    /// let core_listener = core.add_listener_local()
300    ///     .done(|id, seq| println!("Object {id} received done with seq {seq:?}"))
301    ///     .register();
302    /// # }
303    /// ```
304    #[must_use = "Call `.register()` to start receiving events"]
305    pub fn done<F>(mut self, done: F) -> Self
306    where
307        F: Fn(u32, AsyncSeq) + 'static,
308    {
309        self.cbs.done = Some(Box::new(done));
310        self
311    }
312
313    /// Set the core `error` event callback of the listener.
314    ///
315    /// This event is emitted when a fatal (non-recoverable) error has occurred. The `id` argument is the proxy object
316    /// where the error occurred, most often in response to a request to that object. The message is a brief description of the error, for (debugging) convenience.
317    ///
318    /// This event is usually also emitted on the proxy object with id `id`.
319    ///
320    /// # Callback parameters
321    /// `id`: Object where the error occurred  
322    /// `seq`: Sequence number that generated the error  
323    /// `res`: Error code  
324    /// `message`: Error description
325    ///
326    /// # Examples
327    /// ```
328    /// # use pipewire::core::Core;
329    /// # fn example(core: Core) {
330    /// let core_listener = core.add_listener_local()
331    ///     .error(|id, seq, res, message| {
332    ///         println!("Object {id} received error with seq {seq:?}, error code {res} and message {message}")
333    ///     })
334    ///     .register();
335    /// # }
336    /// ```
337    #[must_use = "Call `.register()` to start receiving events"]
338    pub fn error<F>(mut self, error: F) -> Self
339    where
340        F: Fn(u32, i32, i32, &str) + 'static,
341    {
342        self.cbs.error = Some(Box::new(error));
343        self
344    }
345
346    /// Subscribe to events and register any provided callbacks.
347    pub fn register(self) -> Listener {
348        unsafe extern "C" fn core_events_info(
349            data: *mut c_void,
350            info: *const pw_sys::pw_core_info,
351        ) {
352            let callbacks = (data as *mut ListenerLocalCallbacks).as_ref().unwrap();
353            let info = Info::new(ptr::NonNull::new(info as *mut _).expect("info is NULL"));
354            callbacks.info.as_ref().unwrap()(&info);
355        }
356
357        unsafe extern "C" fn core_events_done(data: *mut c_void, id: u32, seq: i32) {
358            let callbacks = (data as *mut ListenerLocalCallbacks).as_ref().unwrap();
359            callbacks.done.as_ref().unwrap()(id, AsyncSeq::from_raw(seq));
360        }
361
362        unsafe extern "C" fn core_events_error(
363            data: *mut c_void,
364            id: u32,
365            seq: i32,
366            res: i32,
367            message: *const c_char,
368        ) {
369            let callbacks = (data as *mut ListenerLocalCallbacks).as_ref().unwrap();
370            let message = CStr::from_ptr(message).to_str().unwrap();
371            callbacks.error.as_ref().unwrap()(id, seq, res, message);
372        }
373
374        let e = unsafe {
375            let mut e: Pin<Box<pw_sys::pw_core_events>> = Box::pin(mem::zeroed());
376            e.version = pw_sys::PW_VERSION_CORE_EVENTS;
377
378            if self.cbs.info.is_some() {
379                e.info = Some(core_events_info);
380            }
381            if self.cbs.done.is_some() {
382                e.done = Some(core_events_done);
383            }
384            if self.cbs.error.is_some() {
385                e.error = Some(core_events_error);
386            }
387
388            e
389        };
390
391        let (listener, data) = unsafe {
392            let ptr = self.core.as_raw_ptr();
393            let data = Box::into_raw(Box::new(self.cbs));
394            let mut listener: Pin<Box<spa_sys::spa_hook>> = Box::pin(mem::zeroed());
395            // Have to cast from pw-sys namespaced type to the equivalent spa-sys type
396            // as bindgen does not allow us to generate bindings dependings of another
397            // sys crate, see https://github.com/rust-lang/rust-bindgen/issues/1929
398            let listener_ptr: *mut spa_sys::spa_hook = listener.as_mut().get_unchecked_mut();
399
400            spa_interface_call_method!(
401                ptr,
402                pw_sys::pw_core_methods,
403                add_listener,
404                listener_ptr.cast(),
405                e.as_ref().get_ref(),
406                data as *mut _
407            );
408
409            (listener, Box::from_raw(data))
410        };
411
412        Listener {
413            events: e,
414            listener,
415            data,
416        }
417    }
418}
419
420pub struct Info {
421    ptr: ptr::NonNull<pw_sys::pw_core_info>,
422}
423
424impl Info {
425    fn new(info: ptr::NonNull<pw_sys::pw_core_info>) -> Self {
426        Self { ptr: info }
427    }
428
429    pub fn id(&self) -> u32 {
430        unsafe { self.ptr.as_ref().id }
431    }
432
433    pub fn cookie(&self) -> u32 {
434        unsafe { self.ptr.as_ref().cookie }
435    }
436
437    pub fn user_name(&self) -> &str {
438        unsafe {
439            CStr::from_ptr(self.ptr.as_ref().user_name)
440                .to_str()
441                .unwrap()
442        }
443    }
444
445    pub fn host_name(&self) -> &str {
446        unsafe {
447            CStr::from_ptr(self.ptr.as_ref().host_name)
448                .to_str()
449                .unwrap()
450        }
451    }
452
453    pub fn version(&self) -> &str {
454        unsafe { CStr::from_ptr(self.ptr.as_ref().version).to_str().unwrap() }
455    }
456
457    pub fn name(&self) -> &str {
458        unsafe { CStr::from_ptr(self.ptr.as_ref().name).to_str().unwrap() }
459    }
460
461    pub fn change_mask(&self) -> ChangeMask {
462        let mask = unsafe { self.ptr.as_ref().change_mask };
463        ChangeMask::from_bits_retain(mask)
464    }
465
466    pub fn props(&self) -> Option<&spa::utils::dict::DictRef> {
467        let props_ptr: *mut spa::utils::dict::DictRef = unsafe { self.ptr.as_ref().props.cast() };
468
469        ptr::NonNull::new(props_ptr).map(|ptr| unsafe { ptr.as_ref() })
470    }
471}
472
473impl fmt::Debug for Info {
474    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
475        f.debug_struct("CoreInfo")
476            .field("id", &self.id())
477            .field("cookie", &self.cookie())
478            .field("user-name", &self.user_name())
479            .field("host-name", &self.host_name())
480            .field("version", &self.version())
481            .field("name", &self.name())
482            .field("change-mask", &self.change_mask())
483            .field("props", &self.props())
484            .finish()
485    }
486}
487
488bitflags! {
489    #[derive(Debug, PartialEq, Eq, Clone, Copy)]
490    pub struct ChangeMask: u64 {
491        const PROPS = pw_sys::PW_CORE_CHANGE_MASK_PROPS as u64;
492    }
493}