libpulse_binding/context/
ext_device_manager.rs

1// Copyright 2017 Lyndon Brown
2//
3// This file is part of the PulseAudio Rust language binding.
4//
5// Licensed under the MIT license or the Apache license (version 2.0), at your option. You may not
6// copy, modify, or distribute this file except in compliance with said license. You can find copies
7// of these licenses either in the LICENSE-MIT and LICENSE-APACHE files, or alternatively at
8// <http://opensource.org/licenses/MIT> and <http://www.apache.org/licenses/LICENSE-2.0>
9// respectively.
10//
11// Portions of documentation are copied from the LGPL 2.1+ licensed PulseAudio C headers on a
12// fair-use basis, as discussed in the overall project readme (available in the git repository).
13
14//! Routines for controlling module-device-manager.
15
16use std::ffi::{CStr, CString};
17use std::borrow::Cow;
18use std::os::raw::{c_char, c_void};
19use std::ptr::{null, null_mut};
20use capi::pa_ext_device_manager_info as InfoInternal;
21use capi::pa_ext_device_manager_role_priority_info as RolePriorityInfoInternal;
22use super::{ContextInternal, Context};
23use crate::def;
24use crate::callbacks::{ListResult, box_closure_get_capi_ptr, callback_for_list_instance};
25use crate::operation::Operation;
26
27/// Role priority information.
28#[derive(Debug)]
29pub struct RolePriorityInfo<'a> {
30    /// Role name
31    pub role: Option<Cow<'a, str>>,
32    /// Priority
33    pub priority: u32,
34}
35
36impl RolePriorityInfo<'_> {
37    fn new_from_raw(p: *const RolePriorityInfoInternal) -> Self {
38        assert!(!p.is_null());
39        let src = unsafe { p.as_ref().unwrap() };
40        unsafe {
41            RolePriorityInfo {
42                role: match src.role.is_null() {
43                    false => Some(CStr::from_ptr(src.role).to_string_lossy()),
44                    true => None,
45                },
46                priority: src.priority,
47            }
48        }
49    }
50
51    /// Creates a copy with owned data.
52    pub fn to_owned(&self) -> RolePriorityInfo<'static> {
53        RolePriorityInfo {
54            role: self.role.clone().map(|o| Cow::Owned(o.into_owned())),
55            ..*self
56        }
57    }
58}
59
60/// Stores information about one device in the device database that is maintained by
61/// module-device-manager.
62#[derive(Debug)]
63pub struct Info<'a> {
64    /// Identifier string of the device. A string like “sink:” or similar followed by the name of
65    /// the device.
66    pub name: Option<Cow<'a, str>>,
67    /// The description of the device when it was last seen, if applicable and saved.
68    pub description: Option<Cow<'a, str>>,
69    /// The icon given to the device.
70    pub icon: Option<Cow<'a, str>>,
71    /// The device index if it is currently available or `None` if invalid.
72    pub index: Option<u32>,
73    /// A set of role priority structures.
74    pub role_priorities: Vec<RolePriorityInfo<'a>>,
75}
76
77impl Info<'_> {
78    fn new_from_raw(p: *const InfoInternal) -> Self {
79        assert!(!p.is_null());
80        let src = unsafe { p.as_ref().unwrap() };
81
82        let mut rp_vec = Vec::with_capacity(src.n_role_priorities as usize);
83        assert!(src.n_role_priorities == 0 || !src.role_priorities.is_null());
84        for i in 0..src.n_role_priorities as isize {
85            let indexed_ptr = unsafe { src.role_priorities.offset(i) as *mut RolePriorityInfoInternal };
86            if !indexed_ptr.is_null() {
87                rp_vec.push(RolePriorityInfo::new_from_raw(indexed_ptr));
88            }
89        }
90
91        unsafe {
92            Info {
93                name: match src.name.is_null() {
94                    false => Some(CStr::from_ptr(src.name).to_string_lossy()),
95                    true => None,
96                },
97                description: match src.description.is_null() {
98                    false => Some(CStr::from_ptr(src.description).to_string_lossy()),
99                    true => None,
100                },
101                icon: match src.icon.is_null() {
102                    false => Some(CStr::from_ptr(src.icon).to_string_lossy()),
103                    true => None,
104                },
105                index: match src.index {
106                    def::INVALID_INDEX => None,
107                    i => Some(i),
108                },
109                role_priorities: rp_vec,
110            }
111        }
112    }
113
114    /// Creates a copy with owned data.
115    pub fn to_owned(&self) -> Info<'static> {
116        Info {
117            name: self.name.clone().map(|o| Cow::Owned(o.into_owned())),
118            description: self.description.clone().map(|o| Cow::Owned(o.into_owned())),
119            icon: self.icon.clone().map(|o| Cow::Owned(o.into_owned())),
120            role_priorities: self.role_priorities.iter().map(RolePriorityInfo::to_owned).collect(),
121            ..*self
122        }
123    }
124}
125
126/// A wrapper object providing device manager routines to a context.
127///
128/// Note: Saves a copy of active multi-use closure callbacks, which it frees on drop.
129pub struct DeviceManager {
130    context: *mut ContextInternal,
131    /// Multi-use callback closure pointers
132    cb_ptrs: CallbackPointers,
133}
134
135unsafe impl Send for DeviceManager {}
136unsafe impl Sync for DeviceManager {}
137
138/// Holds copies of callback closure pointers, for those that are “multi-use” (may be fired multiple
139/// times), for freeing at the appropriate time.
140#[derive(Default)]
141struct CallbackPointers {
142    subscribe: super::ExtSubscribeCb,
143}
144
145impl Context {
146    /// Gets a device manager object linked to the current context, giving access to device manager
147    /// routines.
148    ///
149    /// See [`context::ext_device_manager`](mod@crate::context::ext_device_manager).
150    pub fn device_manager(&self) -> DeviceManager {
151        unsafe { capi::pa_context_ref(self.ptr) };
152        DeviceManager::from_raw(self.ptr)
153    }
154}
155
156impl DeviceManager {
157    /// Creates a new `DeviceManager` from an existing [`ContextInternal`] pointer.
158    fn from_raw(context: *mut ContextInternal) -> Self {
159        Self { context: context, cb_ptrs: Default::default() }
160    }
161
162    /// Tests if this extension module is available in the server.
163    ///
164    /// Panics if the underlying C function returns a null pointer.
165    pub fn test<F>(&mut self, callback: F) -> Operation<dyn FnMut(u32)>
166        where F: FnMut(u32) + 'static
167    {
168        let cb_data = box_closure_get_capi_ptr::<dyn FnMut(u32)>(Box::new(callback));
169        let ptr = unsafe { capi::pa_ext_device_manager_test(self.context,
170            Some(super::ext_test_cb_proxy), cb_data) };
171        Operation::from_raw(ptr, cb_data as *mut Box<dyn FnMut(u32)>)
172    }
173
174    /// Reads all entries from the device database.
175    ///
176    /// Panics if the underlying C function returns a null pointer.
177    pub fn read<F>(&mut self, callback: F) -> Operation<dyn FnMut(ListResult<&Info>)>
178        where F: FnMut(ListResult<&Info>) + 'static
179    {
180        let cb_data = box_closure_get_capi_ptr::<dyn FnMut(ListResult<&Info>)>(Box::new(callback));
181        let ptr = unsafe { capi::pa_ext_device_manager_read(self.context, Some(read_list_cb_proxy),
182            cb_data) };
183        Operation::from_raw(ptr, cb_data as *mut Box<dyn FnMut(ListResult<&Info>)>)
184    }
185
186    /// Sets the description for a device.
187    ///
188    /// The callback must accept a `bool`, which indicates success.
189    ///
190    /// Panics if the underlying C function returns a null pointer.
191    pub fn set_device_description<F>(&mut self, device: &str, description: &str, callback: F)
192        -> Operation<dyn FnMut(bool)>
193        where F: FnMut(bool) + 'static
194    {
195        // Warning: New CStrings will be immediately freed if not bound to a
196        // variable, leading to as_ptr() giving dangling pointers!
197        let c_dev = CString::new(device).unwrap();
198        let c_desc = CString::new(description).unwrap();
199
200        let cb_data = box_closure_get_capi_ptr::<dyn FnMut(bool)>(Box::new(callback));
201        let ptr = unsafe {
202            capi::pa_ext_device_manager_set_device_description(self.context, c_dev.as_ptr(),
203                c_desc.as_ptr(), Some(super::success_cb_proxy), cb_data)
204        };
205        Operation::from_raw(ptr, cb_data as *mut Box<dyn FnMut(bool)>)
206    }
207
208    /// Deletes entries from the device database.
209    ///
210    /// The callback must accept a `bool`, which indicates success.
211    ///
212    /// Panics if the underlying C function returns a null pointer.
213    pub fn delete<F>(&mut self, devices: &[&str], callback: F) -> Operation<dyn FnMut(bool)>
214        where F: FnMut(bool) + 'static
215    {
216        // Warning: New CStrings will be immediately freed if not bound to a variable, leading to
217        // as_ptr() giving dangling pointers!
218        let mut c_devs: Vec<CString> = Vec::with_capacity(devices.len());
219        for device in devices {
220            c_devs.push(CString::new(*device).unwrap());
221        }
222
223        // Capture array of pointers to the above CString values.
224        // We also add a `NULL` pointer entry on the end, as expected by the C function called here.
225        let mut c_dev_ptrs: Vec<*const c_char> = Vec::with_capacity(c_devs.len() + 1);
226        for c_dev in &c_devs {
227            c_dev_ptrs.push(c_dev.as_ptr());
228        }
229        c_dev_ptrs.push(null());
230
231        let cb_data = box_closure_get_capi_ptr::<dyn FnMut(bool)>(Box::new(callback));
232        let ptr = unsafe { capi::pa_ext_device_manager_delete(self.context, c_dev_ptrs.as_ptr(),
233            Some(super::success_cb_proxy), cb_data) };
234        Operation::from_raw(ptr, cb_data as *mut Box<dyn FnMut(bool)>)
235    }
236
237    /// Enables the role-based device-priority routing mode.
238    ///
239    /// The callback must accept a `bool`, which indicates success.
240    ///
241    /// Panics if the underlying C function returns a null pointer.
242    pub fn enable_role_device_priority_routing<F>(&mut self, enable: bool, callback: F)
243        -> Operation<dyn FnMut(bool)>
244        where F: FnMut(bool) + 'static
245    {
246        let cb_data = box_closure_get_capi_ptr::<dyn FnMut(bool)>(Box::new(callback));
247        let ptr = unsafe {
248            capi::pa_ext_device_manager_enable_role_device_priority_routing(self.context,
249                enable as i32, Some(super::success_cb_proxy), cb_data)
250        };
251        Operation::from_raw(ptr, cb_data as *mut Box<dyn FnMut(bool)>)
252    }
253
254    /// Reorders the position of a given device in the priority list to give preference to it.
255    ///
256    /// The callback must accept a `bool`, which indicates success.
257    ///
258    /// Panics if the underlying C function returns a null pointer.
259    pub fn reorder_devices_for_role<F>(&mut self, role: &str, devices: &[&str], callback: F)
260        -> Operation<dyn FnMut(bool)>
261        where F: FnMut(bool) + 'static
262    {
263        // Warning: New CStrings will be immediately freed if not bound to a variable, leading to
264        // as_ptr() giving dangling pointers!
265        let c_role = CString::new(role).unwrap();
266        let mut c_devs: Vec<CString> = Vec::with_capacity(devices.len());
267        for device in devices {
268            c_devs.push(CString::new(*device).unwrap());
269        }
270
271        // Capture array of pointers to the above CString values.
272        // We also add a `NULL` pointer entry on the end, as expected by the C function called here.
273        let mut c_dev_ptrs: Vec<*const c_char> = Vec::with_capacity(c_devs.len() + 1);
274        for c_dev in &c_devs {
275            c_dev_ptrs.push(c_dev.as_ptr());
276        }
277        c_dev_ptrs.push(null());
278
279        let cb_data = box_closure_get_capi_ptr::<dyn FnMut(bool)>(Box::new(callback));
280        let ptr = unsafe {
281            capi::pa_ext_device_manager_reorder_devices_for_role(self.context, c_role.as_ptr(),
282                c_dev_ptrs.as_ptr(), Some(super::success_cb_proxy), cb_data)
283        };
284        Operation::from_raw(ptr, cb_data as *mut Box<dyn FnMut(bool)>)
285    }
286
287    /// Subscribes to changes in the device database.
288    ///
289    /// The callback must accept a `bool`, which indicates success.
290    ///
291    /// Panics if the underlying C function returns a null pointer.
292    pub fn subscribe<F>(&mut self, enable: bool, callback: F) -> Operation<dyn FnMut(bool)>
293        where F: FnMut(bool) + 'static
294    {
295        let cb_data = box_closure_get_capi_ptr::<dyn FnMut(bool)>(Box::new(callback));
296        let ptr = unsafe { capi::pa_ext_device_manager_subscribe(self.context, enable as i32,
297            Some(super::success_cb_proxy), cb_data) };
298        Operation::from_raw(ptr, cb_data as *mut Box<dyn FnMut(bool)>)
299    }
300
301    /// Sets the subscription callback that is called when [`subscribe()`](Self::subscribe) was
302    /// called.
303    pub fn set_subscribe_cb<F>(&mut self, callback: F)
304        where F: FnMut() + 'static
305    {
306        let saved = &mut self.cb_ptrs.subscribe;
307        *saved = super::ExtSubscribeCb::new(Some(Box::new(callback)));
308        let (cb_fn, cb_data) = saved.get_capi_params(super::ext_subscribe_cb_proxy);
309        unsafe { capi::pa_ext_device_manager_set_subscribe_cb(self.context, cb_fn, cb_data) };
310    }
311}
312
313impl Drop for DeviceManager {
314    fn drop(&mut self) {
315        unsafe { capi::pa_context_unref(self.context) };
316        self.context = null_mut::<ContextInternal>();
317    }
318}
319
320/// Proxy for read list callbacks.
321///
322/// Warning: This is for list cases only! On EOL it destroys the actual closure callback.
323extern "C"
324fn read_list_cb_proxy(_: *mut ContextInternal, i: *const InfoInternal, eol: i32,
325    userdata: *mut c_void)
326{
327    let _ = std::panic::catch_unwind(|| {
328        callback_for_list_instance(i, eol, userdata, Info::new_from_raw);
329    });
330}