pipewire/
device.rs

1// Copyright The pipewire-rs Contributors.
2// SPDX-License-Identifier: MIT
3
4//! Devices model physical hardware or software devices in the system and can create other objects such as nodes or other devices.
5//!
6//! This module contains wrappers for [`pw_device`](pw_sys::pw_device) and related items.
7
8use bitflags::bitflags;
9use libc::c_void;
10use std::ops::Deref;
11use std::{fmt, mem};
12use std::{pin::Pin, ptr};
13
14use crate::{
15    proxy::{Listener, Proxy, ProxyT},
16    types::ObjectType,
17};
18use spa::{pod::Pod, spa_interface_call_method};
19
20/// A [proxy][Proxy] to a [device](self).
21#[derive(Debug)]
22pub struct Device {
23    proxy: Proxy,
24}
25
26impl Device {
27    // TODO: add non-local version when we'll bind pw_thread_loop_start()
28    #[must_use = "Use the builder to register event callbacks"]
29    pub fn add_listener_local(&self) -> DeviceListenerLocalBuilder<'_> {
30        DeviceListenerLocalBuilder {
31            device: self,
32            cbs: ListenerLocalCallbacks::default(),
33        }
34    }
35
36    /// Subscribe to parameter changes.
37    ///
38    /// Automatically emit [`param`](DeviceListenerLocalBuilder::param) events for the `ids` when they are changed.
39    ///
40    /// # Permissions
41    /// Requires [`X`](crate::permissions::PermissionFlags::X) permissions on the device.
42    // FIXME: Return result?
43    pub fn subscribe_params(&self, ids: &[spa::param::ParamType]) {
44        unsafe {
45            spa_interface_call_method!(
46                self.proxy.as_ptr(),
47                pw_sys::pw_device_methods,
48                subscribe_params,
49                ids.as_ptr() as *mut _,
50                ids.len().try_into().unwrap()
51            );
52        }
53    }
54
55    /// Enumerate device parameters.
56    ///
57    /// Start enumeration of device parameters. For each param, a
58    /// [`param`](DeviceListenerLocalBuilder::param) event will be emitted.
59    ///
60    /// # Permissions
61    /// Requires [`X`](crate::permissions::PermissionFlags::X) permissions on the device.
62    ///
63    /// # Parameters
64    /// `seq`: A sequence number to place in the reply \
65    /// `id`: The parameter id to enum, or [`None`] to allow any id \
66    /// `start`: The start index or 0 for the first param \
67    /// `num`: The maximum number of params to retrieve ([`u32::MAX`] may be used to retrieve all params)
68    // FIXME: Add filter parameter
69    // FIXME: Return result?
70    pub fn enum_params(&self, seq: i32, id: Option<spa::param::ParamType>, start: u32, num: u32) {
71        let id = id.map(|id| id.as_raw()).unwrap_or(crate::constants::ID_ANY);
72
73        unsafe {
74            spa_interface_call_method!(
75                self.proxy.as_ptr(),
76                pw_sys::pw_device_methods,
77                enum_params,
78                seq,
79                id,
80                start,
81                num,
82                std::ptr::null()
83            );
84        }
85    }
86
87    /// Set a parameter on the device.
88    ///
89    /// # Permissions
90    /// Requires [`W`](crate::permissions::PermissionFlags::W) and
91    /// [`X`](crate::permissions::PermissionFlags::X) permissions on the client.
92    pub fn set_param(&self, id: spa::param::ParamType, flags: u32, param: &Pod) {
93        unsafe {
94            spa_interface_call_method!(
95                self.proxy.as_ptr(),
96                pw_sys::pw_device_methods,
97                set_param,
98                id.as_raw(),
99                flags,
100                param.as_raw_ptr()
101            );
102        }
103    }
104}
105
106impl ProxyT for Device {
107    fn type_() -> ObjectType {
108        ObjectType::Device
109    }
110
111    fn upcast(self) -> Proxy {
112        self.proxy
113    }
114
115    fn upcast_ref(&self) -> &Proxy {
116        &self.proxy
117    }
118
119    unsafe fn from_proxy_unchecked(proxy: Proxy) -> Self
120    where
121        Self: Sized,
122    {
123        Self { proxy }
124    }
125}
126
127#[derive(Default)]
128struct ListenerLocalCallbacks {
129    #[allow(clippy::type_complexity)]
130    info: Option<Box<dyn Fn(&DeviceInfoRef)>>,
131    #[allow(clippy::type_complexity)]
132    param: Option<Box<dyn Fn(i32, spa::param::ParamType, u32, u32, Option<&Pod>)>>,
133}
134
135/// A builder for registering device event callbacks.
136///
137/// Use [`Device::add_listener_local`] to create this and register callbacks that will be called when events of interest occur.
138/// After adding callbacks, use [`register`](Self::register) to get back a [`DeviceListener`].
139///
140/// # Examples
141/// ```
142/// # use pipewire::device::Device;
143/// # use pipewire::spa::pod::Pod;
144/// # fn example(device: Device) {
145/// let device_listener = device.add_listener_local()
146///     .info(|info| println!("New device info: {info:?}"))
147///     .param(|seq, param_type, index, next, param| {
148///         println!("New device param: seq {seq}, param type {param_type:?}, index {index}, next {next}, param {:?}",
149///             param.map(Pod::as_bytes));
150///     })
151///     .register();
152/// # }
153/// ```
154pub struct DeviceListenerLocalBuilder<'a> {
155    device: &'a Device,
156    cbs: ListenerLocalCallbacks,
157}
158
159#[repr(transparent)]
160pub struct DeviceInfoRef(pw_sys::pw_device_info);
161
162impl DeviceInfoRef {
163    pub fn as_raw(&self) -> &pw_sys::pw_device_info {
164        &self.0
165    }
166
167    pub fn as_raw_ptr(&self) -> *mut pw_sys::pw_device_info {
168        std::ptr::addr_of!(self.0).cast_mut()
169    }
170
171    pub fn id(&self) -> u32 {
172        self.0.id
173    }
174
175    pub fn change_mask(&self) -> DeviceChangeMask {
176        DeviceChangeMask::from_bits(self.0.change_mask).expect("invalid change_mask")
177    }
178    pub fn props(&self) -> Option<&spa::utils::dict::DictRef> {
179        let props_ptr: *mut spa::utils::dict::DictRef = self.0.props.cast();
180        ptr::NonNull::new(props_ptr).map(|ptr| unsafe { ptr.as_ref() })
181    }
182
183    /// Get the param infos for the device.
184    pub fn params(&self) -> &[spa::param::ParamInfo] {
185        let params = self.0.params;
186        if params.is_null() {
187            &[]
188        } else {
189            unsafe {
190                std::slice::from_raw_parts(params as *const _, self.0.n_params.try_into().unwrap())
191            }
192        }
193    }
194}
195
196impl fmt::Debug for DeviceInfoRef {
197    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
198        f.debug_struct("DeviceInfoRef")
199            .field("id", &self.id())
200            .field("change-mask", &self.change_mask())
201            .field("props", &self.props())
202            .field("params", &self.params())
203            .finish()
204    }
205}
206
207pub struct DeviceInfo {
208    ptr: ptr::NonNull<pw_sys::pw_device_info>,
209}
210
211impl DeviceInfo {
212    pub fn new(ptr: ptr::NonNull<pw_sys::pw_device_info>) -> Self {
213        Self { ptr }
214    }
215
216    pub fn from_raw(raw: *mut pw_sys::pw_device_info) -> Self {
217        Self {
218            ptr: ptr::NonNull::new(raw).expect("Provided pointer is null"),
219        }
220    }
221
222    pub fn into_raw(self) -> *mut pw_sys::pw_device_info {
223        std::mem::ManuallyDrop::new(self).ptr.as_ptr()
224    }
225}
226
227impl Drop for DeviceInfo {
228    fn drop(&mut self) {
229        unsafe { pw_sys::pw_device_info_free(self.ptr.as_ptr()) }
230    }
231}
232
233impl std::ops::Deref for DeviceInfo {
234    type Target = DeviceInfoRef;
235
236    fn deref(&self) -> &Self::Target {
237        unsafe { self.ptr.cast::<DeviceInfoRef>().as_ref() }
238    }
239}
240
241impl AsRef<DeviceInfoRef> for DeviceInfo {
242    fn as_ref(&self) -> &DeviceInfoRef {
243        self.deref()
244    }
245}
246
247bitflags! {
248    #[derive(Debug, PartialEq, Eq, Clone, Copy)]
249    pub struct DeviceChangeMask: u64 {
250        const PROPS = pw_sys::PW_DEVICE_CHANGE_MASK_PROPS as u64;
251        const PARAMS = pw_sys::PW_DEVICE_CHANGE_MASK_PARAMS as u64;
252    }
253}
254
255impl fmt::Debug for DeviceInfo {
256    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
257        f.debug_struct("DeviceInfo")
258            .field("id", &self.id())
259            .field("change-mask", &self.change_mask())
260            .field("props", &self.props())
261            .field("params", &self.params())
262            .finish()
263    }
264}
265
266/// An owned listener for device events.
267///
268/// This is created by [`DeviceListenerLocalBuilder`] and will receive events as long as it is alive.
269/// When this gets dropped, the listener gets unregistered and no events will be received by it.
270#[must_use = "Listeners unregister themselves when dropped. Keep the listener alive in order to receive events."]
271pub struct DeviceListener {
272    // Need to stay allocated while the listener is registered
273    #[allow(dead_code)]
274    events: Pin<Box<pw_sys::pw_device_events>>,
275    listener: Pin<Box<spa_sys::spa_hook>>,
276    #[allow(dead_code)]
277    data: Box<ListenerLocalCallbacks>,
278}
279
280impl Listener for DeviceListener {}
281
282impl Drop for DeviceListener {
283    fn drop(&mut self) {
284        spa::utils::hook::remove(*self.listener);
285    }
286}
287
288impl<'a> DeviceListenerLocalBuilder<'a> {
289    /// Set the device `info` event callback of the listener.
290    ///
291    /// # Callback parameters
292    /// `info`: Info about the device
293    /// # Examples
294    /// ```
295    /// # use pipewire::device::Device;
296    /// # use pipewire::spa::pod::Pod;
297    /// # fn example(device: Device) {
298    /// let device_listener = device.add_listener_local()
299    ///     .info(|info| println!("New device info: {info:?}"))
300    ///     .register();
301    /// # }
302    /// ```
303    #[must_use = "Call `.register()` to start receiving events"]
304    pub fn info<F>(mut self, info: F) -> Self
305    where
306        F: Fn(&DeviceInfoRef) + 'static,
307    {
308        self.cbs.info = Some(Box::new(info));
309        self
310    }
311
312    /// Set the device `param` event callback of the listener.
313    ///
314    /// This event is emitted as a result of [`enum_params`](Device::enum_params) or
315    /// [`subscribe_params`](Device::subscribe_params).
316    ///
317    /// # Callback parameters
318    /// `seq`: The sequence number of the request  
319    /// `param_type`: The param type  
320    /// `index`: The param index  
321    /// `next`: The param index of the next param  
322    /// `param`: The param  
323    ///
324    /// # Examples
325    /// ```
326    /// # use pipewire::device::Device;
327    /// # use pipewire::spa::pod::Pod;
328    /// # fn example(device: Device) {
329    /// let device_listener = device.add_listener_local()
330    ///     .param(|seq, param_type, index, next, param| {
331    ///         println!("New device param: seq {seq}, param type {param_type:?}, index {index}, next {next}, param {:?}",
332    ///             param.map(Pod::as_bytes));
333    ///     })
334    ///     .register();
335    /// # }
336    /// ```
337    #[must_use = "Call `.register()` to start receiving events"]
338    pub fn param<F>(mut self, param: F) -> Self
339    where
340        F: Fn(i32, spa::param::ParamType, u32, u32, Option<&Pod>) + 'static,
341    {
342        self.cbs.param = Some(Box::new(param));
343        self
344    }
345
346    /// Subscribe to events and register any provided callbacks.
347    pub fn register(self) -> DeviceListener {
348        unsafe extern "C" fn device_events_info(
349            data: *mut c_void,
350            info: *const pw_sys::pw_device_info,
351        ) {
352            let callbacks = (data as *mut ListenerLocalCallbacks).as_ref().unwrap();
353            let info =
354                ptr::NonNull::new(info as *mut pw_sys::pw_device_info).expect("info is NULL");
355            let info = info.cast::<DeviceInfoRef>().as_ref();
356            callbacks.info.as_ref().unwrap()(info);
357        }
358
359        unsafe extern "C" fn device_events_param(
360            data: *mut c_void,
361            seq: i32,
362            id: u32,
363            index: u32,
364            next: u32,
365            param: *const spa_sys::spa_pod,
366        ) {
367            let callbacks = (data as *mut ListenerLocalCallbacks).as_ref().unwrap();
368
369            let id = spa::param::ParamType::from_raw(id);
370            let param = if !param.is_null() {
371                unsafe { Some(Pod::from_raw(param)) }
372            } else {
373                None
374            };
375
376            callbacks.param.as_ref().unwrap()(seq, id, index, next, param);
377        }
378
379        let e = unsafe {
380            let mut e: Pin<Box<pw_sys::pw_device_events>> = Box::pin(mem::zeroed());
381            e.version = pw_sys::PW_VERSION_DEVICE_EVENTS;
382
383            if self.cbs.info.is_some() {
384                e.info = Some(device_events_info);
385            }
386            if self.cbs.param.is_some() {
387                e.param = Some(device_events_param);
388            }
389
390            e
391        };
392
393        let (listener, data) = unsafe {
394            let device = &self.device.proxy.as_ptr();
395
396            let data = Box::into_raw(Box::new(self.cbs));
397            let mut listener: Pin<Box<spa_sys::spa_hook>> = Box::pin(mem::zeroed());
398            let listener_ptr: *mut spa_sys::spa_hook = listener.as_mut().get_unchecked_mut();
399
400            spa_interface_call_method!(
401                device,
402                pw_sys::pw_device_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        DeviceListener {
413            events: e,
414            listener,
415            data,
416        }
417    }
418}