pipewire/
port.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::{fmt, mem};
8use std::{pin::Pin, ptr};
9
10use crate::{
11    proxy::{Listener, Proxy, ProxyT},
12    spa::utils::Direction,
13    types::ObjectType,
14};
15use spa::{pod::Pod, spa_interface_call_method};
16
17#[derive(Debug)]
18pub struct Port {
19    proxy: Proxy,
20}
21
22impl Port {
23    // TODO: add non-local version when we'll bind pw_thread_loop_start()
24    #[must_use]
25    pub fn add_listener_local(&self) -> PortListenerLocalBuilder<'_> {
26        PortListenerLocalBuilder {
27            port: 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_port_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
78impl ProxyT for Port {
79    fn type_() -> ObjectType {
80        ObjectType::Port
81    }
82
83    fn upcast(self) -> Proxy {
84        self.proxy
85    }
86
87    fn upcast_ref(&self) -> &Proxy {
88        &self.proxy
89    }
90
91    unsafe fn from_proxy_unchecked(proxy: Proxy) -> Self
92    where
93        Self: Sized,
94    {
95        Self { proxy }
96    }
97}
98
99#[derive(Default)]
100struct ListenerLocalCallbacks {
101    #[allow(clippy::type_complexity)]
102    info: Option<Box<dyn Fn(&PortInfoRef)>>,
103    #[allow(clippy::type_complexity)]
104    param: Option<Box<dyn Fn(i32, spa::param::ParamType, u32, u32, Option<&Pod>)>>,
105}
106
107pub struct PortListenerLocalBuilder<'a> {
108    port: &'a Port,
109    cbs: ListenerLocalCallbacks,
110}
111
112#[repr(transparent)]
113pub struct PortInfoRef(pw_sys::pw_port_info);
114
115impl PortInfoRef {
116    pub fn as_raw(&self) -> &pw_sys::pw_port_info {
117        &self.0
118    }
119
120    pub fn as_raw_ptr(&self) -> *mut pw_sys::pw_port_info {
121        std::ptr::addr_of!(self.0).cast_mut()
122    }
123
124    pub fn id(&self) -> u32 {
125        self.0.id
126    }
127
128    pub fn direction(&self) -> Direction {
129        Direction::from_raw(self.0.direction)
130    }
131
132    pub fn change_mask(&self) -> PortChangeMask {
133        PortChangeMask::from_bits_retain(self.0.change_mask)
134    }
135
136    pub fn props(&self) -> Option<&spa::utils::dict::DictRef> {
137        let props_ptr: *mut spa::utils::dict::DictRef = self.0.props.cast();
138        ptr::NonNull::new(props_ptr).map(|ptr| unsafe { ptr.as_ref() })
139    }
140
141    /// Get the param infos for the port.
142    pub fn params(&self) -> &[spa::param::ParamInfo] {
143        let params = self.0.params;
144        if params.is_null() {
145            &[]
146        } else {
147            unsafe {
148                std::slice::from_raw_parts(params as *const _, self.0.n_params.try_into().unwrap())
149            }
150        }
151    }
152}
153
154impl fmt::Debug for PortInfoRef {
155    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
156        f.debug_struct("PortInfoRef")
157            .field("id", &self.id())
158            .field("direction", &self.direction())
159            .field("change-mask", &self.change_mask())
160            .field("props", &self.props())
161            .field("params", &self.params())
162            .finish()
163    }
164}
165
166pub struct PortInfo {
167    ptr: ptr::NonNull<pw_sys::pw_port_info>,
168}
169
170impl PortInfo {
171    pub fn new(ptr: ptr::NonNull<pw_sys::pw_port_info>) -> Self {
172        Self { ptr }
173    }
174
175    pub fn from_raw(raw: *mut pw_sys::pw_port_info) -> Self {
176        Self {
177            ptr: ptr::NonNull::new(raw).expect("Provided pointer is null"),
178        }
179    }
180
181    pub fn into_raw(self) -> *mut pw_sys::pw_port_info {
182        std::mem::ManuallyDrop::new(self).ptr.as_ptr()
183    }
184}
185
186impl Drop for PortInfo {
187    fn drop(&mut self) {
188        unsafe { pw_sys::pw_port_info_free(self.ptr.as_ptr()) }
189    }
190}
191
192impl std::ops::Deref for PortInfo {
193    type Target = PortInfoRef;
194
195    fn deref(&self) -> &Self::Target {
196        unsafe { self.ptr.cast::<PortInfoRef>().as_ref() }
197    }
198}
199
200impl AsRef<PortInfoRef> for PortInfo {
201    fn as_ref(&self) -> &PortInfoRef {
202        self.deref()
203    }
204}
205
206bitflags! {
207    #[derive(Debug, PartialEq, Eq, Clone, Copy)]
208    pub struct PortChangeMask: u64 {
209        const PROPS = pw_sys::PW_PORT_CHANGE_MASK_PROPS as u64;
210        const PARAMS = pw_sys::PW_PORT_CHANGE_MASK_PARAMS as u64;
211    }
212}
213
214impl fmt::Debug for PortInfo {
215    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
216        f.debug_struct("PortInfo")
217            .field("id", &self.id())
218            .field("direction", &self.direction())
219            .field("change-mask", &self.change_mask())
220            .field("props", &self.props())
221            .field("params", &self.params())
222            .finish()
223    }
224}
225
226pub struct PortListener {
227    // Need to stay allocated while the listener is registered
228    #[allow(dead_code)]
229    events: Pin<Box<pw_sys::pw_port_events>>,
230    listener: Pin<Box<spa_sys::spa_hook>>,
231    #[allow(dead_code)]
232    data: Box<ListenerLocalCallbacks>,
233}
234
235impl Listener for PortListener {}
236
237impl Drop for PortListener {
238    fn drop(&mut self) {
239        spa::utils::hook::remove(*self.listener);
240    }
241}
242
243impl<'a> PortListenerLocalBuilder<'a> {
244    #[must_use]
245    pub fn info<F>(mut self, info: F) -> Self
246    where
247        F: Fn(&PortInfoRef) + 'static,
248    {
249        self.cbs.info = Some(Box::new(info));
250        self
251    }
252
253    #[must_use]
254    pub fn param<F>(mut self, param: F) -> Self
255    where
256        F: Fn(i32, spa::param::ParamType, u32, u32, Option<&Pod>) + 'static,
257    {
258        self.cbs.param = Some(Box::new(param));
259        self
260    }
261
262    #[must_use]
263    pub fn register(self) -> PortListener {
264        unsafe extern "C" fn port_events_info(
265            data: *mut c_void,
266            info: *const pw_sys::pw_port_info,
267        ) {
268            let callbacks = (data as *mut ListenerLocalCallbacks).as_ref().unwrap();
269            let info = ptr::NonNull::new(info as *mut pw_sys::pw_port_info).expect("info is NULL");
270            let info = info.cast::<PortInfoRef>().as_ref();
271            callbacks.info.as_ref().unwrap()(info);
272        }
273
274        unsafe extern "C" fn port_events_param(
275            data: *mut c_void,
276            seq: i32,
277            id: u32,
278            index: u32,
279            next: u32,
280            param: *const spa_sys::spa_pod,
281        ) {
282            let callbacks = (data as *mut ListenerLocalCallbacks).as_ref().unwrap();
283
284            let id = spa::param::ParamType::from_raw(id);
285            let param = if !param.is_null() {
286                unsafe { Some(Pod::from_raw(param)) }
287            } else {
288                None
289            };
290
291            callbacks.param.as_ref().unwrap()(seq, id, index, next, param);
292        }
293
294        let e = unsafe {
295            let mut e: Pin<Box<pw_sys::pw_port_events>> = Box::pin(mem::zeroed());
296            e.version = pw_sys::PW_VERSION_PORT_EVENTS;
297
298            if self.cbs.info.is_some() {
299                e.info = Some(port_events_info);
300            }
301            if self.cbs.param.is_some() {
302                e.param = Some(port_events_param);
303            }
304
305            e
306        };
307
308        let (listener, data) = unsafe {
309            let port = &self.port.proxy.as_ptr();
310
311            let data = Box::into_raw(Box::new(self.cbs));
312            let mut listener: Pin<Box<spa_sys::spa_hook>> = Box::pin(mem::zeroed());
313            let listener_ptr: *mut spa_sys::spa_hook = listener.as_mut().get_unchecked_mut();
314
315            spa_interface_call_method!(
316                port,
317                pw_sys::pw_port_methods,
318                add_listener,
319                listener_ptr.cast(),
320                e.as_ref().get_ref(),
321                data as *mut _
322            );
323
324            (listener, Box::from_raw(data))
325        };
326
327        PortListener {
328            events: e,
329            listener,
330            data,
331        }
332    }
333}