pipewire/
node.rs

1// Copyright The pipewire-rs Contributors.
2// SPDX-License-Identifier: MIT
3
4use bitflags::bitflags;
5use libc::c_void;
6use std::ops::Deref;
7use std::pin::Pin;
8use std::{ffi::CStr, ptr};
9use std::{fmt, mem};
10
11use crate::{
12    proxy::{Listener, Proxy, ProxyT},
13    types::ObjectType,
14};
15use spa::{pod::Pod, spa_interface_call_method};
16
17#[derive(Debug)]
18pub struct Node {
19    proxy: Proxy,
20}
21
22impl Node {
23    // TODO: add non-local version when we'll bind pw_thread_loop_start()
24    #[must_use]
25    pub fn add_listener_local(&self) -> NodeListenerLocalBuilder<'_> {
26        NodeListenerLocalBuilder {
27            node: self,
28            cbs: ListenerLocalCallbacks::default(),
29        }
30    }
31
32    /// Subscribe to parameter changes
33    ///
34    /// Automatically emit `param` events for the given ids when they are changed
35    // FIXME: Return result?
36    pub fn subscribe_params(&self, ids: &[spa::param::ParamType]) {
37        unsafe {
38            spa_interface_call_method!(
39                self.proxy.as_ptr(),
40                pw_sys::pw_node_methods,
41                subscribe_params,
42                ids.as_ptr() as *mut _,
43                ids.len().try_into().unwrap()
44            );
45        }
46    }
47
48    /// Enumerate node parameters
49    ///
50    /// Start enumeration of node parameters. For each param, a
51    /// param event will be emitted.
52    ///
53    /// # Parameters
54    /// `seq`: a sequence number to place in the reply \
55    /// `id`: the parameter id to enum, or [`None`] to allow any id \
56    /// `start`: the start index or 0 for the first param \
57    /// `num`: the maximum number of params to retrieve ([`u32::MAX`] may be used to retrieve all params)
58    // FIXME: Add filter parameter
59    // FIXME: Return result?
60    pub fn enum_params(&self, seq: i32, id: Option<spa::param::ParamType>, start: u32, num: u32) {
61        let id = id.map(|id| id.as_raw()).unwrap_or(crate::constants::ID_ANY);
62
63        unsafe {
64            spa_interface_call_method!(
65                self.proxy.as_ptr(),
66                pw_sys::pw_node_methods,
67                enum_params,
68                seq,
69                id,
70                start,
71                num,
72                std::ptr::null()
73            );
74        }
75    }
76
77    pub fn set_param(&self, id: spa::param::ParamType, flags: u32, param: &Pod) {
78        unsafe {
79            spa_interface_call_method!(
80                self.proxy.as_ptr(),
81                pw_sys::pw_node_methods,
82                set_param,
83                id.as_raw(),
84                flags,
85                param.as_raw_ptr()
86            );
87        }
88    }
89}
90
91impl ProxyT for Node {
92    fn type_() -> ObjectType {
93        ObjectType::Node
94    }
95
96    fn upcast(self) -> Proxy {
97        self.proxy
98    }
99
100    fn upcast_ref(&self) -> &Proxy {
101        &self.proxy
102    }
103
104    unsafe fn from_proxy_unchecked(proxy: Proxy) -> Self
105    where
106        Self: Sized,
107    {
108        Self { proxy }
109    }
110}
111
112#[derive(Default)]
113struct ListenerLocalCallbacks {
114    #[allow(clippy::type_complexity)]
115    info: Option<Box<dyn Fn(&NodeInfoRef)>>,
116    #[allow(clippy::type_complexity)]
117    param: Option<Box<dyn Fn(i32, spa::param::ParamType, u32, u32, Option<&Pod>)>>,
118}
119
120pub struct NodeListenerLocalBuilder<'a> {
121    node: &'a Node,
122    cbs: ListenerLocalCallbacks,
123}
124
125#[repr(transparent)]
126pub struct NodeInfoRef(pw_sys::pw_node_info);
127
128impl NodeInfoRef {
129    pub fn as_raw(&self) -> &pw_sys::pw_node_info {
130        &self.0
131    }
132
133    pub fn as_raw_ptr(&self) -> *mut pw_sys::pw_node_info {
134        std::ptr::addr_of!(self.0).cast_mut()
135    }
136
137    pub fn id(&self) -> u32 {
138        self.0.id
139    }
140
141    pub fn max_input_ports(&self) -> u32 {
142        self.0.max_input_ports
143    }
144
145    pub fn max_output_ports(&self) -> u32 {
146        self.0.max_output_ports
147    }
148
149    pub fn change_mask(&self) -> NodeChangeMask {
150        NodeChangeMask::from_bits_retain(self.0.change_mask)
151    }
152
153    pub fn n_input_ports(&self) -> u32 {
154        self.0.n_input_ports
155    }
156
157    pub fn n_output_ports(&self) -> u32 {
158        self.0.n_output_ports
159    }
160
161    pub fn state(&self) -> NodeState<'_> {
162        let raw_state = self.0.state;
163        match raw_state {
164            pw_sys::pw_node_state_PW_NODE_STATE_ERROR => {
165                let error = unsafe {
166                    let error = self.0.error;
167                    CStr::from_ptr(error).to_str().unwrap()
168                };
169                NodeState::Error(error)
170            }
171            pw_sys::pw_node_state_PW_NODE_STATE_CREATING => NodeState::Creating,
172            pw_sys::pw_node_state_PW_NODE_STATE_SUSPENDED => NodeState::Suspended,
173            pw_sys::pw_node_state_PW_NODE_STATE_IDLE => NodeState::Idle,
174            pw_sys::pw_node_state_PW_NODE_STATE_RUNNING => NodeState::Running,
175            _ => panic!("Invalid node state: {raw_state}"),
176        }
177    }
178
179    pub fn props(&self) -> Option<&spa::utils::dict::DictRef> {
180        let props_ptr: *mut spa::utils::dict::DictRef = self.0.props.cast();
181        ptr::NonNull::new(props_ptr).map(|ptr| unsafe { ptr.as_ref() })
182    }
183
184    /// Get the param infos for the node.
185    pub fn params(&self) -> &[spa::param::ParamInfo] {
186        unsafe {
187            let params_ptr = self.0.params;
188
189            if params_ptr.is_null() {
190                &[]
191            } else {
192                std::slice::from_raw_parts(
193                    params_ptr as *const _,
194                    self.0.n_params.try_into().unwrap(),
195                )
196            }
197        }
198    }
199}
200
201impl fmt::Debug for NodeInfoRef {
202    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
203        f.debug_struct("NodeInfoRef")
204            .field("id", &self.id())
205            .field("max-input-ports", &self.max_input_ports())
206            .field("max-output-ports", &self.max_output_ports())
207            .field("change-mask", &self.change_mask())
208            .field("n-input-ports", &self.n_input_ports())
209            .field("n-output-ports", &self.n_output_ports())
210            .field("state", &self.state())
211            .field("props", &self.props())
212            .field("params", &self.params())
213            .finish()
214    }
215}
216
217pub struct NodeInfo {
218    ptr: ptr::NonNull<pw_sys::pw_node_info>,
219}
220
221impl NodeInfo {
222    pub fn new(ptr: ptr::NonNull<pw_sys::pw_node_info>) -> Self {
223        Self { ptr }
224    }
225
226    /// Create a `NodeInfo` from a raw `pw_sys::pw_node_info`.
227    ///
228    /// # Safety
229    /// `ptr` must point to a valid, well aligned `pw_sys::pw_node_info`.
230    pub fn from_raw(raw: *mut pw_sys::pw_node_info) -> Self {
231        Self {
232            ptr: ptr::NonNull::new(raw).expect("Provided pointer is null"),
233        }
234    }
235
236    pub fn into_raw(self) -> *mut pw_sys::pw_node_info {
237        std::mem::ManuallyDrop::new(self).ptr.as_ptr()
238    }
239}
240
241impl Drop for NodeInfo {
242    fn drop(&mut self) {
243        unsafe { pw_sys::pw_node_info_free(self.ptr.as_ptr()) }
244    }
245}
246
247impl std::ops::Deref for NodeInfo {
248    type Target = NodeInfoRef;
249
250    fn deref(&self) -> &Self::Target {
251        unsafe { self.ptr.cast::<NodeInfoRef>().as_ref() }
252    }
253}
254
255impl AsRef<NodeInfoRef> for NodeInfo {
256    fn as_ref(&self) -> &NodeInfoRef {
257        self.deref()
258    }
259}
260
261bitflags! {
262    #[derive(Debug, PartialEq, Eq, Clone, Copy)]
263    pub struct NodeChangeMask: u64 {
264        const INPUT_PORTS = pw_sys::PW_NODE_CHANGE_MASK_INPUT_PORTS as u64;
265        const OUTPUT_PORTS = pw_sys::PW_NODE_CHANGE_MASK_OUTPUT_PORTS as u64;
266        const STATE = pw_sys::PW_NODE_CHANGE_MASK_STATE as u64;
267        const PROPS = pw_sys::PW_NODE_CHANGE_MASK_PROPS as u64;
268        const PARAMS = pw_sys::PW_NODE_CHANGE_MASK_PARAMS as u64;
269    }
270}
271
272#[derive(Debug)]
273pub enum NodeState<'a> {
274    Error(&'a str),
275    Creating,
276    Suspended,
277    Idle,
278    Running,
279}
280
281impl fmt::Debug for NodeInfo {
282    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
283        f.debug_struct("NodeInfo")
284            .field("id", &self.id())
285            .field("max-input-ports", &self.max_input_ports())
286            .field("max-output-ports", &self.max_output_ports())
287            .field("change-mask", &self.change_mask())
288            .field("n-input-ports", &self.n_input_ports())
289            .field("n-output-ports", &self.n_output_ports())
290            .field("state", &self.state())
291            .field("props", &self.props())
292            .field("params", &self.params())
293            .finish()
294    }
295}
296
297pub struct NodeListener {
298    // Need to stay allocated while the listener is registered
299    #[allow(dead_code)]
300    events: Pin<Box<pw_sys::pw_node_events>>,
301    listener: Pin<Box<spa_sys::spa_hook>>,
302    #[allow(dead_code)]
303    data: Box<ListenerLocalCallbacks>,
304}
305
306impl Listener for NodeListener {}
307
308impl Drop for NodeListener {
309    fn drop(&mut self) {
310        spa::utils::hook::remove(*self.listener);
311    }
312}
313
314impl<'a> NodeListenerLocalBuilder<'a> {
315    #[must_use]
316    pub fn info<F>(mut self, info: F) -> Self
317    where
318        F: Fn(&NodeInfoRef) + 'static,
319    {
320        self.cbs.info = Some(Box::new(info));
321        self
322    }
323
324    #[must_use]
325    pub fn param<F>(mut self, param: F) -> Self
326    where
327        F: Fn(i32, spa::param::ParamType, u32, u32, Option<&Pod>) + 'static,
328    {
329        self.cbs.param = Some(Box::new(param));
330        self
331    }
332
333    #[must_use]
334    pub fn register(self) -> NodeListener {
335        unsafe extern "C" fn node_events_info(
336            data: *mut c_void,
337            info: *const pw_sys::pw_node_info,
338        ) {
339            let callbacks = (data as *mut ListenerLocalCallbacks).as_ref().unwrap();
340            let info = ptr::NonNull::new(info as *mut pw_sys::pw_node_info).expect("info is NULL");
341            let info = info.cast::<NodeInfoRef>().as_ref();
342            callbacks.info.as_ref().unwrap()(info);
343        }
344
345        unsafe extern "C" fn node_events_param(
346            data: *mut c_void,
347            seq: i32,
348            id: u32,
349            index: u32,
350            next: u32,
351            param: *const spa_sys::spa_pod,
352        ) {
353            let callbacks = (data as *mut ListenerLocalCallbacks).as_ref().unwrap();
354
355            let id = spa::param::ParamType::from_raw(id);
356            let param = if !param.is_null() {
357                unsafe { Some(Pod::from_raw(param)) }
358            } else {
359                None
360            };
361
362            callbacks.param.as_ref().unwrap()(seq, id, index, next, param);
363        }
364
365        let e = unsafe {
366            let mut e: Pin<Box<pw_sys::pw_node_events>> = Box::pin(mem::zeroed());
367            e.version = pw_sys::PW_VERSION_NODE_EVENTS;
368
369            if self.cbs.info.is_some() {
370                e.info = Some(node_events_info);
371            }
372            if self.cbs.param.is_some() {
373                e.param = Some(node_events_param);
374            }
375
376            e
377        };
378
379        let (listener, data) = unsafe {
380            let node = &self.node.proxy.as_ptr();
381
382            let data = Box::into_raw(Box::new(self.cbs));
383            let mut listener: Pin<Box<spa_sys::spa_hook>> = Box::pin(mem::zeroed());
384            let listener_ptr: *mut spa_sys::spa_hook = listener.as_mut().get_unchecked_mut();
385
386            spa_interface_call_method!(
387                node,
388                pw_sys::pw_node_methods,
389                add_listener,
390                listener_ptr.cast(),
391                e.as_ref().get_ref(),
392                data as *mut _
393            );
394
395            (listener, Box::from_raw(data))
396        };
397
398        NodeListener {
399            events: e,
400            listener,
401            data,
402        }
403    }
404}