pipewire/
module.rs

1// Copyright The pipewire-rs Contributors.
2// SPDX-License-Identifier: MIT
3
4//! Modules implement functionality such as providing new objects or policies.
5//!
6//! This module contains wrappers for [`pw_module`](pw_sys::pw_module) and related items.
7
8use bitflags::bitflags;
9use libc::c_void;
10use std::ops::Deref;
11use std::pin::Pin;
12use std::{ffi::CStr, ptr};
13use std::{fmt, mem};
14
15use crate::{
16    proxy::{Listener, Proxy, ProxyT},
17    types::ObjectType,
18};
19use spa::spa_interface_call_method;
20
21/// A [proxy][Proxy] to a [module](self).
22#[derive(Debug)]
23pub struct Module {
24    proxy: Proxy,
25}
26
27impl ProxyT for Module {
28    fn type_() -> ObjectType {
29        ObjectType::Module
30    }
31
32    fn upcast(self) -> Proxy {
33        self.proxy
34    }
35
36    fn upcast_ref(&self) -> &Proxy {
37        &self.proxy
38    }
39
40    unsafe fn from_proxy_unchecked(proxy: Proxy) -> Self
41    where
42        Self: Sized,
43    {
44        Self { proxy }
45    }
46}
47
48impl Module {
49    // TODO: add non-local version when we'll bind pw_thread_loop_start()
50    #[must_use = "Use the builder to register event callbacks"]
51    pub fn add_listener_local(&self) -> ModuleListenerLocalBuilder<'_> {
52        ModuleListenerLocalBuilder {
53            module: self,
54            cbs: ListenerLocalCallbacks::default(),
55        }
56    }
57}
58
59#[derive(Default)]
60struct ListenerLocalCallbacks {
61    #[allow(clippy::type_complexity)]
62    info: Option<Box<dyn Fn(&ModuleInfoRef)>>,
63}
64
65/// A builder for registering module event callbacks.
66///
67/// Use [`Module::add_listener_local`] to create this and register callbacks that will be called when events of interest occur.
68/// After adding callbacks, use [`register`](Self::register) to get back a [`ModuleListener`].
69///
70/// # Examples
71/// ```
72/// # use pipewire::module::Module;
73/// # fn example(module: Module) {
74/// let module_listener = module.add_listener_local()
75///     .info(|info| println!("New module info: {info:?}"))
76///     .register();
77/// # }
78/// ```
79pub struct ModuleListenerLocalBuilder<'a> {
80    module: &'a Module,
81    cbs: ListenerLocalCallbacks,
82}
83
84#[repr(transparent)]
85pub struct ModuleInfoRef(pw_sys::pw_module_info);
86
87impl ModuleInfoRef {
88    pub fn as_raw(&self) -> &pw_sys::pw_module_info {
89        &self.0
90    }
91
92    pub fn as_raw_ptr(&self) -> *mut pw_sys::pw_module_info {
93        std::ptr::addr_of!(self.0).cast_mut()
94    }
95
96    pub fn id(&self) -> u32 {
97        self.0.id
98    }
99
100    pub fn name(&self) -> &str {
101        unsafe { CStr::from_ptr(self.0.name).to_str().unwrap() }
102    }
103
104    pub fn filename(&self) -> &str {
105        unsafe { CStr::from_ptr(self.0.name).to_str().unwrap() }
106    }
107
108    pub fn args(&self) -> Option<&str> {
109        let args = self.0.args;
110        if args.is_null() {
111            None
112        } else {
113            Some(unsafe { CStr::from_ptr(args).to_str().unwrap() })
114        }
115    }
116
117    pub fn change_mask(&self) -> ModuleChangeMask {
118        ModuleChangeMask::from_bits(self.0.change_mask).expect("invalid change_mask")
119    }
120
121    pub fn props(&self) -> Option<&spa::utils::dict::DictRef> {
122        let props_ptr: *mut spa::utils::dict::DictRef = self.0.props.cast();
123        ptr::NonNull::new(props_ptr).map(|ptr| unsafe { ptr.as_ref() })
124    }
125}
126
127impl fmt::Debug for ModuleInfoRef {
128    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
129        f.debug_struct("ModuleInfoRef")
130            .field("id", &self.id())
131            .field("filename", &self.filename())
132            .field("args", &self.args())
133            .field("change_mask", &self.change_mask())
134            .field("props", &self.props())
135            .finish()
136    }
137}
138
139pub struct ModuleInfo {
140    ptr: ptr::NonNull<pw_sys::pw_module_info>,
141}
142
143impl ModuleInfo {
144    pub fn new(ptr: ptr::NonNull<pw_sys::pw_module_info>) -> Self {
145        Self { ptr }
146    }
147
148    pub fn from_raw(raw: *mut pw_sys::pw_module_info) -> Self {
149        Self {
150            ptr: ptr::NonNull::new(raw).expect("Provided pointer is null"),
151        }
152    }
153
154    pub fn into_raw(self) -> *mut pw_sys::pw_module_info {
155        std::mem::ManuallyDrop::new(self).ptr.as_ptr()
156    }
157}
158
159impl Drop for ModuleInfo {
160    fn drop(&mut self) {
161        unsafe { pw_sys::pw_module_info_free(self.ptr.as_ptr()) }
162    }
163}
164
165impl std::ops::Deref for ModuleInfo {
166    type Target = ModuleInfoRef;
167
168    fn deref(&self) -> &Self::Target {
169        unsafe { self.ptr.cast::<ModuleInfoRef>().as_ref() }
170    }
171}
172
173impl AsRef<ModuleInfoRef> for ModuleInfo {
174    fn as_ref(&self) -> &ModuleInfoRef {
175        self.deref()
176    }
177}
178
179bitflags! {
180    #[derive(Debug, PartialEq, Eq, Clone, Copy)]
181    pub struct ModuleChangeMask: u64 {
182        const PROPS = pw_sys::PW_MODULE_CHANGE_MASK_PROPS as u64;
183    }
184}
185
186impl fmt::Debug for ModuleInfo {
187    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
188        f.debug_struct("ModuleInfo")
189            .field("id", &self.id())
190            .field("filename", &self.filename())
191            .field("args", &self.args())
192            .field("change_mask", &self.change_mask())
193            .field("props", &self.props())
194            .finish()
195    }
196}
197
198/// An owned listener for module events.
199///
200/// This is created by [`ModuleListenerLocalBuilder`] and will receive events as long as it is alive.
201/// When this gets dropped, the listener gets unregistered and no events will be received by it.
202#[must_use = "Listeners unregister themselves when dropped. Keep the listener alive in order to receive events."]
203pub struct ModuleListener {
204    // Need to stay allocated while the listener is registered
205    #[allow(dead_code)]
206    events: Pin<Box<pw_sys::pw_module_events>>,
207    listener: Pin<Box<spa_sys::spa_hook>>,
208    #[allow(dead_code)]
209    data: Box<ListenerLocalCallbacks>,
210}
211
212impl Listener for ModuleListener {}
213
214impl Drop for ModuleListener {
215    fn drop(&mut self) {
216        spa::utils::hook::remove(*self.listener);
217    }
218}
219
220impl<'a> ModuleListenerLocalBuilder<'a> {
221    /// Set the module `info` event callback of the listener.
222    ///
223    /// # Callback parameters
224    /// `info`: Info about the module
225    ///
226    /// # Examples
227    /// ```
228    /// # use pipewire::module::Module;
229    /// # fn example(module: Module) {
230    /// let module_listener = module.add_listener_local()
231    ///     .info(|info| println!("New module info: {info:?}"))
232    ///     .register();
233    /// # }
234    /// ```
235    #[must_use = "Call `.register()` to start receiving events"]
236    pub fn info<F>(mut self, info: F) -> Self
237    where
238        F: Fn(&ModuleInfoRef) + 'static,
239    {
240        self.cbs.info = Some(Box::new(info));
241        self
242    }
243
244    /// Subscribe to events and register any provided callbacks.
245    pub fn register(self) -> ModuleListener {
246        unsafe extern "C" fn module_events_info(
247            data: *mut c_void,
248            info: *const pw_sys::pw_module_info,
249        ) {
250            let callbacks = (data as *mut ListenerLocalCallbacks).as_ref().unwrap();
251            let info =
252                ptr::NonNull::new(info as *mut pw_sys::pw_module_info).expect("info is NULL");
253            let info = info.cast::<ModuleInfoRef>().as_ref();
254            callbacks.info.as_ref().unwrap()(info);
255        }
256
257        let e = unsafe {
258            let mut e: Pin<Box<pw_sys::pw_module_events>> = Box::pin(mem::zeroed());
259            e.version = pw_sys::PW_VERSION_MODULE_EVENTS;
260
261            if self.cbs.info.is_some() {
262                e.info = Some(module_events_info);
263            }
264
265            e
266        };
267
268        let (listener, data) = unsafe {
269            let module = &self.module.proxy.as_ptr();
270
271            let data = Box::into_raw(Box::new(self.cbs));
272            let mut listener: Pin<Box<spa_sys::spa_hook>> = Box::pin(mem::zeroed());
273            let listener_ptr: *mut spa_sys::spa_hook = listener.as_mut().get_unchecked_mut();
274
275            spa_interface_call_method!(
276                module,
277                pw_sys::pw_module_methods,
278                add_listener,
279                listener_ptr.cast(),
280                e.as_ref().get_ref(),
281                data as *mut _
282            );
283
284            (listener, Box::from_raw(data))
285        };
286
287        ModuleListener {
288            events: e,
289            listener,
290            data,
291        }
292    }
293}