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}