pipewire/
device.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    types::ObjectType,
13};
14use spa::{pod::Pod, spa_interface_call_method};
15
16#[derive(Debug)]
17pub struct Device {
18    proxy: Proxy,
19}
20
21impl Device {
22    // TODO: add non-local version when we'll bind pw_thread_loop_start()
23    #[must_use]
24    pub fn add_listener_local(&self) -> DeviceListenerLocalBuilder<'_> {
25        DeviceListenerLocalBuilder {
26            device: self,
27            cbs: ListenerLocalCallbacks::default(),
28        }
29    }
30
31    /// Subscribe to parameter changes
32    ///
33    /// Automatically emit `param` events for the given ids when they are changed
34    // FIXME: Return result?
35    pub fn subscribe_params(&self, ids: &[spa::param::ParamType]) {
36        unsafe {
37            spa_interface_call_method!(
38                self.proxy.as_ptr(),
39                pw_sys::pw_device_methods,
40                subscribe_params,
41                ids.as_ptr() as *mut _,
42                ids.len().try_into().unwrap()
43            );
44        }
45    }
46
47    /// Enumerate device parameters
48    ///
49    /// Start enumeration of device parameters. For each param, a
50    /// param event will be emitted.
51    ///
52    /// # Parameters
53    /// `seq`: a sequence number to place in the reply \
54    /// `id`: the parameter id to enum, or [`None`] to allow any id \
55    /// `start`: the start index or 0 for the first param \
56    /// `num`: the maximum number of params to retrieve ([`u32::MAX`] may be used to retrieve all params)
57    // FIXME: Add filter parameter
58    // FIXME: Return result?
59    pub fn enum_params(&self, seq: i32, id: Option<spa::param::ParamType>, start: u32, num: u32) {
60        let id = id.map(|id| id.as_raw()).unwrap_or(crate::constants::ID_ANY);
61
62        unsafe {
63            spa_interface_call_method!(
64                self.proxy.as_ptr(),
65                pw_sys::pw_device_methods,
66                enum_params,
67                seq,
68                id,
69                start,
70                num,
71                std::ptr::null()
72            );
73        }
74    }
75
76    pub fn set_param(&self, id: spa::param::ParamType, flags: u32, param: &Pod) {
77        unsafe {
78            spa_interface_call_method!(
79                self.proxy.as_ptr(),
80                pw_sys::pw_device_methods,
81                set_param,
82                id.as_raw(),
83                flags,
84                param.as_raw_ptr()
85            );
86        }
87    }
88}
89
90impl ProxyT for Device {
91    fn type_() -> ObjectType {
92        ObjectType::Device
93    }
94
95    fn upcast(self) -> Proxy {
96        self.proxy
97    }
98
99    fn upcast_ref(&self) -> &Proxy {
100        &self.proxy
101    }
102
103    unsafe fn from_proxy_unchecked(proxy: Proxy) -> Self
104    where
105        Self: Sized,
106    {
107        Self { proxy }
108    }
109}
110
111#[derive(Default)]
112struct ListenerLocalCallbacks {
113    #[allow(clippy::type_complexity)]
114    info: Option<Box<dyn Fn(&DeviceInfoRef)>>,
115    #[allow(clippy::type_complexity)]
116    param: Option<Box<dyn Fn(i32, spa::param::ParamType, u32, u32, Option<&Pod>)>>,
117}
118
119pub struct DeviceListenerLocalBuilder<'a> {
120    device: &'a Device,
121    cbs: ListenerLocalCallbacks,
122}
123
124#[repr(transparent)]
125pub struct DeviceInfoRef(pw_sys::pw_device_info);
126
127impl DeviceInfoRef {
128    pub fn as_raw(&self) -> &pw_sys::pw_device_info {
129        &self.0
130    }
131
132    pub fn as_raw_ptr(&self) -> *mut pw_sys::pw_device_info {
133        std::ptr::addr_of!(self.0).cast_mut()
134    }
135
136    pub fn id(&self) -> u32 {
137        self.0.id
138    }
139
140    pub fn change_mask(&self) -> DeviceChangeMask {
141        DeviceChangeMask::from_bits(self.0.change_mask).expect("invalid change_mask")
142    }
143    pub fn props(&self) -> Option<&spa::utils::dict::DictRef> {
144        let props_ptr: *mut spa::utils::dict::DictRef = self.0.props.cast();
145        ptr::NonNull::new(props_ptr).map(|ptr| unsafe { ptr.as_ref() })
146    }
147
148    /// Get the param infos for the device.
149    pub fn params(&self) -> &[spa::param::ParamInfo] {
150        let params = self.0.params;
151        if params.is_null() {
152            &[]
153        } else {
154            unsafe {
155                std::slice::from_raw_parts(params as *const _, self.0.n_params.try_into().unwrap())
156            }
157        }
158    }
159}
160
161impl fmt::Debug for DeviceInfoRef {
162    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
163        f.debug_struct("DeviceInfoRef")
164            .field("id", &self.id())
165            .field("change-mask", &self.change_mask())
166            .field("props", &self.props())
167            .field("params", &self.params())
168            .finish()
169    }
170}
171
172pub struct DeviceInfo {
173    ptr: ptr::NonNull<pw_sys::pw_device_info>,
174}
175
176impl DeviceInfo {
177    pub fn new(ptr: ptr::NonNull<pw_sys::pw_device_info>) -> Self {
178        Self { ptr }
179    }
180
181    pub fn from_raw(raw: *mut pw_sys::pw_device_info) -> Self {
182        Self {
183            ptr: ptr::NonNull::new(raw).expect("Provided pointer is null"),
184        }
185    }
186
187    pub fn into_raw(self) -> *mut pw_sys::pw_device_info {
188        std::mem::ManuallyDrop::new(self).ptr.as_ptr()
189    }
190}
191
192impl Drop for DeviceInfo {
193    fn drop(&mut self) {
194        unsafe { pw_sys::pw_device_info_free(self.ptr.as_ptr()) }
195    }
196}
197
198impl std::ops::Deref for DeviceInfo {
199    type Target = DeviceInfoRef;
200
201    fn deref(&self) -> &Self::Target {
202        unsafe { self.ptr.cast::<DeviceInfoRef>().as_ref() }
203    }
204}
205
206impl AsRef<DeviceInfoRef> for DeviceInfo {
207    fn as_ref(&self) -> &DeviceInfoRef {
208        self.deref()
209    }
210}
211
212bitflags! {
213    #[derive(Debug, PartialEq, Eq, Clone, Copy)]
214    pub struct DeviceChangeMask: u64 {
215        const PROPS = pw_sys::PW_DEVICE_CHANGE_MASK_PROPS as u64;
216        const PARAMS = pw_sys::PW_DEVICE_CHANGE_MASK_PARAMS as u64;
217    }
218}
219
220impl fmt::Debug for DeviceInfo {
221    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
222        f.debug_struct("DeviceInfo")
223            .field("id", &self.id())
224            .field("change-mask", &self.change_mask())
225            .field("props", &self.props())
226            .field("params", &self.params())
227            .finish()
228    }
229}
230
231pub struct DeviceListener {
232    // Need to stay allocated while the listener is registered
233    #[allow(dead_code)]
234    events: Pin<Box<pw_sys::pw_device_events>>,
235    listener: Pin<Box<spa_sys::spa_hook>>,
236    #[allow(dead_code)]
237    data: Box<ListenerLocalCallbacks>,
238}
239
240impl Listener for DeviceListener {}
241
242impl Drop for DeviceListener {
243    fn drop(&mut self) {
244        spa::utils::hook::remove(*self.listener);
245    }
246}
247
248impl<'a> DeviceListenerLocalBuilder<'a> {
249    #[must_use]
250    pub fn info<F>(mut self, info: F) -> Self
251    where
252        F: Fn(&DeviceInfoRef) + 'static,
253    {
254        self.cbs.info = Some(Box::new(info));
255        self
256    }
257
258    #[must_use]
259    pub fn param<F>(mut self, param: F) -> Self
260    where
261        F: Fn(i32, spa::param::ParamType, u32, u32, Option<&Pod>) + 'static,
262    {
263        self.cbs.param = Some(Box::new(param));
264        self
265    }
266
267    #[must_use]
268    pub fn register(self) -> DeviceListener {
269        unsafe extern "C" fn device_events_info(
270            data: *mut c_void,
271            info: *const pw_sys::pw_device_info,
272        ) {
273            let callbacks = (data as *mut ListenerLocalCallbacks).as_ref().unwrap();
274            let info =
275                ptr::NonNull::new(info as *mut pw_sys::pw_device_info).expect("info is NULL");
276            let info = info.cast::<DeviceInfoRef>().as_ref();
277            callbacks.info.as_ref().unwrap()(info);
278        }
279
280        unsafe extern "C" fn device_events_param(
281            data: *mut c_void,
282            seq: i32,
283            id: u32,
284            index: u32,
285            next: u32,
286            param: *const spa_sys::spa_pod,
287        ) {
288            let callbacks = (data as *mut ListenerLocalCallbacks).as_ref().unwrap();
289
290            let id = spa::param::ParamType::from_raw(id);
291            let param = if !param.is_null() {
292                unsafe { Some(Pod::from_raw(param)) }
293            } else {
294                None
295            };
296
297            callbacks.param.as_ref().unwrap()(seq, id, index, next, param);
298        }
299
300        let e = unsafe {
301            let mut e: Pin<Box<pw_sys::pw_device_events>> = Box::pin(mem::zeroed());
302            e.version = pw_sys::PW_VERSION_DEVICE_EVENTS;
303
304            if self.cbs.info.is_some() {
305                e.info = Some(device_events_info);
306            }
307            if self.cbs.param.is_some() {
308                e.param = Some(device_events_param);
309            }
310
311            e
312        };
313
314        let (listener, data) = unsafe {
315            let device = &self.device.proxy.as_ptr();
316
317            let data = Box::into_raw(Box::new(self.cbs));
318            let mut listener: Pin<Box<spa_sys::spa_hook>> = Box::pin(mem::zeroed());
319            let listener_ptr: *mut spa_sys::spa_hook = listener.as_mut().get_unchecked_mut();
320
321            spa_interface_call_method!(
322                device,
323                pw_sys::pw_device_methods,
324                add_listener,
325                listener_ptr.cast(),
326                e.as_ref().get_ref(),
327                data as *mut _
328            );
329
330            (listener, Box::from_raw(data))
331        };
332
333        DeviceListener {
334            events: e,
335            listener,
336            data,
337        }
338    }
339}