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<'a> RolePriorityInfo<'a> {
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
52/// Stores information about one device in the device database that is maintained by
53/// module-device-manager.
54#[derive(Debug)]
55pub struct Info<'a> {
56    /// Identifier string of the device. A string like “sink:” or similar followed by the name of
57    /// the device.
58    pub name: Option<Cow<'a, str>>,
59    /// The description of the device when it was last seen, if applicable and saved.
60    pub description: Option<Cow<'a, str>>,
61    /// The icon given to the device.
62    pub icon: Option<Cow<'a, str>>,
63    /// The device index if it is currently available or `None` if invalid.
64    pub index: Option<u32>,
65    /// A set of role priority structures.
66    pub role_priorities: Vec<RolePriorityInfo<'a>>,
67}
68
69impl<'a> Info<'a> {
70    fn new_from_raw(p: *const InfoInternal) -> Self {
71        assert!(!p.is_null());
72        let src = unsafe { p.as_ref().unwrap() };
73
74        let mut rp_vec = Vec::with_capacity(src.n_role_priorities as usize);
75        assert!(src.n_role_priorities == 0 || !src.role_priorities.is_null());
76        for i in 0..src.n_role_priorities as isize {
77            let indexed_ptr = unsafe { src.role_priorities.offset(i) as *mut RolePriorityInfoInternal };
78            if !indexed_ptr.is_null() {
79                rp_vec.push(RolePriorityInfo::new_from_raw(indexed_ptr));
80            }
81        }
82
83        unsafe {
84            Info {
85                name: match src.name.is_null() {
86                    false => Some(CStr::from_ptr(src.name).to_string_lossy()),
87                    true => None,
88                },
89                description: match src.description.is_null() {
90                    false => Some(CStr::from_ptr(src.description).to_string_lossy()),
91                    true => None,
92                },
93                icon: match src.icon.is_null() {
94                    false => Some(CStr::from_ptr(src.icon).to_string_lossy()),
95                    true => None,
96                },
97                index: match src.index {
98                    def::INVALID_INDEX => None,
99                    i => Some(i),
100                },
101                role_priorities: rp_vec,
102            }
103        }
104    }
105}
106
107/// A wrapper object providing device manager routines to a context.
108///
109/// Note: Saves a copy of active multi-use closure callbacks, which it frees on drop.
110pub struct DeviceManager {
111    context: *mut ContextInternal,
112    /// Multi-use callback closure pointers
113    cb_ptrs: CallbackPointers,
114}
115
116unsafe impl Send for DeviceManager {}
117unsafe impl Sync for DeviceManager {}
118
119/// Holds copies of callback closure pointers, for those that are “multi-use” (may be fired multiple
120/// times), for freeing at the appropriate time.
121#[derive(Default)]
122struct CallbackPointers {
123    subscribe: super::ExtSubscribeCb,
124}
125
126impl Context {
127    /// Gets a device manager object linked to the current context, giving access to device manager
128    /// routines.
129    ///
130    /// See [`context::ext_device_manager`](mod@crate::context::ext_device_manager).
131    pub fn device_manager(&self) -> DeviceManager {
132        unsafe { capi::pa_context_ref(self.ptr) };
133        DeviceManager::from_raw(self.ptr)
134    }
135}
136
137impl DeviceManager {
138    /// Creates a new `DeviceManager` from an existing [`ContextInternal`] pointer.
139    fn from_raw(context: *mut ContextInternal) -> Self {
140        Self { context: context, cb_ptrs: Default::default() }
141    }
142
143    /// Tests if this extension module is available in the server.
144    ///
145    /// Panics if the underlying C function returns a null pointer.
146    pub fn test<F>(&mut self, callback: F) -> Operation<dyn FnMut(u32)>
147        where F: FnMut(u32) + 'static
148    {
149        let cb_data = box_closure_get_capi_ptr::<dyn FnMut(u32)>(Box::new(callback));
150        let ptr = unsafe { capi::pa_ext_device_manager_test(self.context,
151            Some(super::ext_test_cb_proxy), cb_data) };
152        Operation::from_raw(ptr, cb_data as *mut Box<dyn FnMut(u32)>)
153    }
154
155    /// Reads all entries from the device database.
156    ///
157    /// Panics if the underlying C function returns a null pointer.
158    pub fn read<F>(&mut self, callback: F) -> Operation<dyn FnMut(ListResult<&Info>)>
159        where F: FnMut(ListResult<&Info>) + 'static
160    {
161        let cb_data = box_closure_get_capi_ptr::<dyn FnMut(ListResult<&Info>)>(Box::new(callback));
162        let ptr = unsafe { capi::pa_ext_device_manager_read(self.context, Some(read_list_cb_proxy),
163            cb_data) };
164        Operation::from_raw(ptr, cb_data as *mut Box<dyn FnMut(ListResult<&Info>)>)
165    }
166
167    /// Sets the description for a device.
168    ///
169    /// The callback must accept a `bool`, which indicates success.
170    ///
171    /// Panics if the underlying C function returns a null pointer.
172    pub fn set_device_description<F>(&mut self, device: &str, description: &str, callback: F)
173        -> Operation<dyn FnMut(bool)>
174        where F: FnMut(bool) + 'static
175    {
176        // Warning: New CStrings will be immediately freed if not bound to a
177        // variable, leading to as_ptr() giving dangling pointers!
178        let c_dev = CString::new(device).unwrap();
179        let c_desc = CString::new(description).unwrap();
180
181        let cb_data = box_closure_get_capi_ptr::<dyn FnMut(bool)>(Box::new(callback));
182        let ptr = unsafe {
183            capi::pa_ext_device_manager_set_device_description(self.context, c_dev.as_ptr(),
184                c_desc.as_ptr(), Some(super::success_cb_proxy), cb_data)
185        };
186        Operation::from_raw(ptr, cb_data as *mut Box<dyn FnMut(bool)>)
187    }
188
189    /// Deletes entries from the device database.
190    ///
191    /// The callback must accept a `bool`, which indicates success.
192    ///
193    /// Panics if the underlying C function returns a null pointer.
194    pub fn delete<F>(&mut self, devices: &[&str], callback: F) -> Operation<dyn FnMut(bool)>
195        where F: FnMut(bool) + 'static
196    {
197        // Warning: New CStrings will be immediately freed if not bound to a variable, leading to
198        // as_ptr() giving dangling pointers!
199        let mut c_devs: Vec<CString> = Vec::with_capacity(devices.len());
200        for device in devices {
201            c_devs.push(CString::new(*device).unwrap());
202        }
203
204        // Capture array of pointers to the above CString values.
205        // We also add a `NULL` pointer entry on the end, as expected by the C function called here.
206        let mut c_dev_ptrs: Vec<*const c_char> = Vec::with_capacity(c_devs.len() + 1);
207        for c_dev in &c_devs {
208            c_dev_ptrs.push(c_dev.as_ptr());
209        }
210        c_dev_ptrs.push(null());
211
212        let cb_data = box_closure_get_capi_ptr::<dyn FnMut(bool)>(Box::new(callback));
213        let ptr = unsafe { capi::pa_ext_device_manager_delete(self.context, c_dev_ptrs.as_ptr(),
214            Some(super::success_cb_proxy), cb_data) };
215        Operation::from_raw(ptr, cb_data as *mut Box<dyn FnMut(bool)>)
216    }
217
218    /// Enables the role-based device-priority routing mode.
219    ///
220    /// The callback must accept a `bool`, which indicates success.
221    ///
222    /// Panics if the underlying C function returns a null pointer.
223    pub fn enable_role_device_priority_routing<F>(&mut self, enable: bool, callback: F)
224        -> Operation<dyn FnMut(bool)>
225        where F: FnMut(bool) + 'static
226    {
227        let cb_data = box_closure_get_capi_ptr::<dyn FnMut(bool)>(Box::new(callback));
228        let ptr = unsafe {
229            capi::pa_ext_device_manager_enable_role_device_priority_routing(self.context,
230                enable as i32, Some(super::success_cb_proxy), cb_data)
231        };
232        Operation::from_raw(ptr, cb_data as *mut Box<dyn FnMut(bool)>)
233    }
234
235    /// Reorders the position of a given device in the priority list to give preference to it.
236    ///
237    /// The callback must accept a `bool`, which indicates success.
238    ///
239    /// Panics if the underlying C function returns a null pointer.
240    pub fn reorder_devices_for_role<F>(&mut self, role: &str, devices: &[&str], callback: F)
241        -> Operation<dyn FnMut(bool)>
242        where F: FnMut(bool) + 'static
243    {
244        // Warning: New CStrings will be immediately freed if not bound to a variable, leading to
245        // as_ptr() giving dangling pointers!
246        let c_role = CString::new(role).unwrap();
247        let mut c_devs: Vec<CString> = Vec::with_capacity(devices.len());
248        for device in devices {
249            c_devs.push(CString::new(*device).unwrap());
250        }
251
252        // Capture array of pointers to the above CString values.
253        // We also add a `NULL` pointer entry on the end, as expected by the C function called here.
254        let mut c_dev_ptrs: Vec<*const c_char> = Vec::with_capacity(c_devs.len() + 1);
255        for c_dev in &c_devs {
256            c_dev_ptrs.push(c_dev.as_ptr());
257        }
258        c_dev_ptrs.push(null());
259
260        let cb_data = box_closure_get_capi_ptr::<dyn FnMut(bool)>(Box::new(callback));
261        let ptr = unsafe {
262            capi::pa_ext_device_manager_reorder_devices_for_role(self.context, c_role.as_ptr(),
263                c_dev_ptrs.as_ptr(), Some(super::success_cb_proxy), cb_data)
264        };
265        Operation::from_raw(ptr, cb_data as *mut Box<dyn FnMut(bool)>)
266    }
267
268    /// Subscribes to changes in the device database.
269    ///
270    /// The callback must accept a `bool`, which indicates success.
271    ///
272    /// Panics if the underlying C function returns a null pointer.
273    pub fn subscribe<F>(&mut self, enable: bool, callback: F) -> Operation<dyn FnMut(bool)>
274        where F: FnMut(bool) + 'static
275    {
276        let cb_data = box_closure_get_capi_ptr::<dyn FnMut(bool)>(Box::new(callback));
277        let ptr = unsafe { capi::pa_ext_device_manager_subscribe(self.context, enable as i32,
278            Some(super::success_cb_proxy), cb_data) };
279        Operation::from_raw(ptr, cb_data as *mut Box<dyn FnMut(bool)>)
280    }
281
282    /// Sets the subscription callback that is called when [`subscribe()`](Self::subscribe) was
283    /// called.
284    pub fn set_subscribe_cb<F>(&mut self, callback: F)
285        where F: FnMut() + 'static
286    {
287        let saved = &mut self.cb_ptrs.subscribe;
288        *saved = super::ExtSubscribeCb::new(Some(Box::new(callback)));
289        let (cb_fn, cb_data) = saved.get_capi_params(super::ext_subscribe_cb_proxy);
290        unsafe { capi::pa_ext_device_manager_set_subscribe_cb(self.context, cb_fn, cb_data) };
291    }
292}
293
294impl Drop for DeviceManager {
295    fn drop(&mut self) {
296        unsafe { capi::pa_context_unref(self.context) };
297        self.context = null_mut::<ContextInternal>();
298    }
299}
300
301/// Proxy for read list callbacks.
302///
303/// Warning: This is for list cases only! On EOL it destroys the actual closure callback.
304extern "C"
305fn read_list_cb_proxy(_: *mut ContextInternal, i: *const InfoInternal, eol: i32,
306    userdata: *mut c_void)
307{
308    let _ = std::panic::catch_unwind(|| {
309        callback_for_list_instance(i, eol, userdata, Info::new_from_raw);
310    });
311}