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).
1314//! Routines for controlling module-device-manager.
1516use 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;
2627/// Role priority information.
28#[derive(Debug)]
29pub struct RolePriorityInfo<'a> {
30/// Role name
31pub role: Option<Cow<'a, str>>,
32/// Priority
33pub priority: u32,
34}
3536impl RolePriorityInfo<'_> {
37fn new_from_raw(p: *const RolePriorityInfoInternal) -> Self {
38assert!(!p.is_null());
39let src = unsafe { p.as_ref().unwrap() };
40unsafe {
41 RolePriorityInfo {
42 role: match src.role.is_null() {
43false => Some(CStr::from_ptr(src.role).to_string_lossy()),
44true => None,
45 },
46 priority: src.priority,
47 }
48 }
49 }
5051/// Creates a copy with owned data.
52pub 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}
5960/// 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.
66pub name: Option<Cow<'a, str>>,
67/// The description of the device when it was last seen, if applicable and saved.
68pub description: Option<Cow<'a, str>>,
69/// The icon given to the device.
70pub icon: Option<Cow<'a, str>>,
71/// The device index if it is currently available or `None` if invalid.
72pub index: Option<u32>,
73/// A set of role priority structures.
74pub role_priorities: Vec<RolePriorityInfo<'a>>,
75}
7677impl Info<'_> {
78fn new_from_raw(p: *const InfoInternal) -> Self {
79assert!(!p.is_null());
80let src = unsafe { p.as_ref().unwrap() };
8182let mut rp_vec = Vec::with_capacity(src.n_role_priorities as usize);
83assert!(src.n_role_priorities == 0 || !src.role_priorities.is_null());
84for i in 0..src.n_role_priorities as isize {
85let indexed_ptr = unsafe { src.role_priorities.offset(i) as *mut RolePriorityInfoInternal };
86if !indexed_ptr.is_null() {
87 rp_vec.push(RolePriorityInfo::new_from_raw(indexed_ptr));
88 }
89 }
9091unsafe {
92 Info {
93 name: match src.name.is_null() {
94false => Some(CStr::from_ptr(src.name).to_string_lossy()),
95true => None,
96 },
97 description: match src.description.is_null() {
98false => Some(CStr::from_ptr(src.description).to_string_lossy()),
99true => None,
100 },
101 icon: match src.icon.is_null() {
102false => Some(CStr::from_ptr(src.icon).to_string_lossy()),
103true => 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 }
113114/// Creates a copy with owned data.
115pub 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}
125126/// 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
132cb_ptrs: CallbackPointers,
133}
134135unsafe impl Send for DeviceManager {}
136unsafe impl Sync for DeviceManager {}
137138/// 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}
144145impl 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).
150pub fn device_manager(&self) -> DeviceManager {
151unsafe { capi::pa_context_ref(self.ptr) };
152 DeviceManager::from_raw(self.ptr)
153 }
154}
155156impl DeviceManager {
157/// Creates a new `DeviceManager` from an existing [`ContextInternal`] pointer.
158fn from_raw(context: *mut ContextInternal) -> Self {
159Self { context: context, cb_ptrs: Default::default() }
160 }
161162/// Tests if this extension module is available in the server.
163 ///
164 /// Panics if the underlying C function returns a null pointer.
165pub fn test<F>(&mut self, callback: F) -> Operation<dyn FnMut(u32)>
166where F: FnMut(u32) + 'static
167{
168let cb_data = box_closure_get_capi_ptr::<dyn FnMut(u32)>(Box::new(callback));
169let ptr = unsafe { capi::pa_ext_device_manager_test(self.context,
170Some(super::ext_test_cb_proxy), cb_data) };
171 Operation::from_raw(ptr, cb_data as *mut Box<dyn FnMut(u32)>)
172 }
173174/// Reads all entries from the device database.
175 ///
176 /// Panics if the underlying C function returns a null pointer.
177pub fn read<F>(&mut self, callback: F) -> Operation<dyn FnMut(ListResult<&Info>)>
178where F: FnMut(ListResult<&Info>) + 'static
179{
180let cb_data = box_closure_get_capi_ptr::<dyn FnMut(ListResult<&Info>)>(Box::new(callback));
181let 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 }
185186/// 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.
191pub fn set_device_description<F>(&mut self, device: &str, description: &str, callback: F)
192 -> Operation<dyn FnMut(bool)>
193where 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!
197let c_dev = CString::new(device).unwrap();
198let c_desc = CString::new(description).unwrap();
199200let cb_data = box_closure_get_capi_ptr::<dyn FnMut(bool)>(Box::new(callback));
201let 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 }
207208/// 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.
213pub fn delete<F>(&mut self, devices: &[&str], callback: F) -> Operation<dyn FnMut(bool)>
214where 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!
218let mut c_devs: Vec<CString> = Vec::with_capacity(devices.len());
219for device in devices {
220 c_devs.push(CString::new(*device).unwrap());
221 }
222223// 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.
225let mut c_dev_ptrs: Vec<*const c_char> = Vec::with_capacity(c_devs.len() + 1);
226for c_dev in &c_devs {
227 c_dev_ptrs.push(c_dev.as_ptr());
228 }
229 c_dev_ptrs.push(null());
230231let cb_data = box_closure_get_capi_ptr::<dyn FnMut(bool)>(Box::new(callback));
232let ptr = unsafe { capi::pa_ext_device_manager_delete(self.context, c_dev_ptrs.as_ptr(),
233Some(super::success_cb_proxy), cb_data) };
234 Operation::from_raw(ptr, cb_data as *mut Box<dyn FnMut(bool)>)
235 }
236237/// 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.
242pub fn enable_role_device_priority_routing<F>(&mut self, enable: bool, callback: F)
243 -> Operation<dyn FnMut(bool)>
244where F: FnMut(bool) + 'static
245{
246let cb_data = box_closure_get_capi_ptr::<dyn FnMut(bool)>(Box::new(callback));
247let 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 }
253254/// 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.
259pub fn reorder_devices_for_role<F>(&mut self, role: &str, devices: &[&str], callback: F)
260 -> Operation<dyn FnMut(bool)>
261where 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!
265let c_role = CString::new(role).unwrap();
266let mut c_devs: Vec<CString> = Vec::with_capacity(devices.len());
267for device in devices {
268 c_devs.push(CString::new(*device).unwrap());
269 }
270271// 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.
273let mut c_dev_ptrs: Vec<*const c_char> = Vec::with_capacity(c_devs.len() + 1);
274for c_dev in &c_devs {
275 c_dev_ptrs.push(c_dev.as_ptr());
276 }
277 c_dev_ptrs.push(null());
278279let cb_data = box_closure_get_capi_ptr::<dyn FnMut(bool)>(Box::new(callback));
280let 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 }
286287/// 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.
292pub fn subscribe<F>(&mut self, enable: bool, callback: F) -> Operation<dyn FnMut(bool)>
293where F: FnMut(bool) + 'static
294{
295let cb_data = box_closure_get_capi_ptr::<dyn FnMut(bool)>(Box::new(callback));
296let ptr = unsafe { capi::pa_ext_device_manager_subscribe(self.context, enable as i32,
297Some(super::success_cb_proxy), cb_data) };
298 Operation::from_raw(ptr, cb_data as *mut Box<dyn FnMut(bool)>)
299 }
300301/// Sets the subscription callback that is called when [`subscribe()`](Self::subscribe) was
302 /// called.
303pub fn set_subscribe_cb<F>(&mut self, callback: F)
304where F: FnMut() + 'static
305{
306let saved = &mut self.cb_ptrs.subscribe;
307*saved = super::ExtSubscribeCb::new(Some(Box::new(callback)));
308let (cb_fn, cb_data) = saved.get_capi_params(super::ext_subscribe_cb_proxy);
309unsafe { capi::pa_ext_device_manager_set_subscribe_cb(self.context, cb_fn, cb_data) };
310 }
311}
312313impl Drop for DeviceManager {
314fn drop(&mut self) {
315unsafe { capi::pa_context_unref(self.context) };
316self.context = null_mut::<ContextInternal>();
317 }
318}
319320/// 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{
327let _ = std::panic::catch_unwind(|| {
328 callback_for_list_instance(i, eol, userdata, Info::new_from_raw);
329 });
330}