libpulse_binding/context/
ext_stream_restore.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-stream-restore.
15
16use std::os::raw::{c_char, c_void};
17use std::ffi::{CStr, CString};
18use std::borrow::Cow;
19use std::ptr::{null, null_mut};
20use std::mem;
21use capi::pa_ext_stream_restore_info as InfoInternal;
22use super::{ContextInternal, Context};
23use crate::{channelmap, proplist};
24use crate::callbacks::{ListResult, box_closure_get_capi_ptr, callback_for_list_instance};
25use crate::{operation::Operation, volume::ChannelVolumes};
26
27/// Stores information about one entry in the stream database that is maintained by
28/// module-stream-restore.
29#[derive(Debug)]
30pub struct Info<'a> {
31    /// Identifier string of the stream. A string like “sink-input-by-role:” or similar followed by
32    /// some arbitrary property value.
33    pub name: Option<Cow<'a, str>>,
34    /// The channel map for the volume field, if applicable.
35    pub channel_map: channelmap::Map,
36    /// The volume of the stream when it was seen last, if applicable and saved.
37    pub volume: ChannelVolumes,
38    /// The sink/source of the stream when it was last seen, if applicable and saved.
39    pub device: Option<Cow<'a, str>>,
40    /// The boolean mute state of the stream when it was last seen, if applicable and saved.
41    pub mute: bool,
42}
43
44impl<'a> Info<'a> {
45    fn new_from_raw(p: *const InfoInternal) -> Self {
46        assert!(!p.is_null());
47        let src = unsafe { p.as_ref().unwrap() };
48        unsafe {
49            Info {
50                name: match src.name.is_null() {
51                    false => Some(CStr::from_ptr(src.name).to_string_lossy()),
52                    true => None,
53                },
54                channel_map: src.channel_map.into(),
55                volume: src.volume.into(),
56                device: match src.device.is_null() {
57                    false => Some(CStr::from_ptr(src.device).to_string_lossy()),
58                    true => None,
59                },
60                mute: match src.mute {
61                    0 => false,
62                    _ => true,
63                },
64            }
65        }
66    }
67}
68
69/// A wrapper object providing stream restore routines to a context.
70///
71/// Note: Saves a copy of active multi-use closure callbacks, which it frees on drop.
72pub struct StreamRestore {
73    context: *mut ContextInternal,
74    /// Multi-use callback closure pointers
75    cb_ptrs: CallbackPointers,
76}
77
78unsafe impl Send for StreamRestore {}
79unsafe impl Sync for StreamRestore {}
80
81/// Holds copies of callback closure pointers, for those that are “multi-use” (may be fired multiple
82/// times), for freeing at the appropriate time.
83#[derive(Default)]
84struct CallbackPointers {
85    subscribe: super::ExtSubscribeCb,
86}
87
88impl Context {
89    /// Gets a stream restore object linked to the current context, giving access to stream restore
90    /// routines.
91    ///
92    /// See [`context::ext_stream_restore`](mod@crate::context::ext_stream_restore).
93    pub fn stream_restore(&self) -> StreamRestore {
94        unsafe { capi::pa_context_ref(self.ptr) };
95        StreamRestore::from_raw(self.ptr)
96    }
97}
98
99impl StreamRestore {
100    /// Creates a new `StreamRestore` from an existing [`ContextInternal`] pointer.
101    fn from_raw(context: *mut ContextInternal) -> Self {
102        Self { context: context, cb_ptrs: Default::default() }
103    }
104
105    /// Tests if this extension module is available in the server.
106    ///
107    /// Panics if the underlying C function returns a null pointer.
108    pub fn test<F>(&mut self, callback: F) -> Operation<dyn FnMut(u32)>
109        where F: FnMut(u32) + 'static
110    {
111        let cb_data = box_closure_get_capi_ptr::<dyn FnMut(u32)>(Box::new(callback));
112        let ptr = unsafe { capi::pa_ext_stream_restore_test(self.context,
113            Some(super::ext_test_cb_proxy), cb_data) };
114        Operation::from_raw(ptr, cb_data as *mut Box<dyn FnMut(u32)>)
115    }
116
117    /// Reads all entries from the stream database.
118    ///
119    /// Panics if the underlying C function returns a null pointer.
120    pub fn read<F>(&mut self, callback: F) -> Operation<dyn FnMut(ListResult<&Info>)>
121        where F: FnMut(ListResult<&Info>) + 'static
122    {
123        let cb_data = box_closure_get_capi_ptr::<dyn FnMut(ListResult<&Info>)>(Box::new(callback));
124        let ptr = unsafe { capi::pa_ext_stream_restore_read(self.context, Some(read_list_cb_proxy),
125            cb_data) };
126        Operation::from_raw(ptr, cb_data as *mut Box<dyn FnMut(ListResult<&Info>)>)
127    }
128
129    /// Stores entries in the stream database.
130    ///
131    /// The callback must accept a `bool`, which indicates success.
132    ///
133    /// Panics if the underlying C function returns a null pointer.
134    pub fn write<F>(&mut self, mode: proplist::UpdateMode, data: &[&Info],
135        apply_immediately: bool, callback: F) -> Operation<dyn FnMut(bool)>
136        where F: FnMut(bool) + 'static
137    {
138        let cb_data = box_closure_get_capi_ptr::<dyn FnMut(bool)>(Box::new(callback));
139        let ptr = unsafe {
140            capi::pa_ext_stream_restore_write(self.context, mode, mem::transmute(data.as_ptr()),
141                data.len() as u32, apply_immediately as i32, Some(super::success_cb_proxy),
142                cb_data)
143        };
144        Operation::from_raw(ptr, cb_data as *mut Box<dyn FnMut(bool)>)
145    }
146
147    /// Deletes entries from the stream database.
148    ///
149    /// The callback must accept a `bool`, which indicates success.
150    ///
151    /// Panics if the underlying C function returns a null pointer.
152    pub fn delete<F>(&mut self, streams: &[&str], callback: F) -> Operation<dyn FnMut(bool)>
153        where F: FnMut(bool) + 'static
154    {
155        // Warning: New CStrings will be immediately freed if not bound to a variable, leading to
156        // as_ptr() giving dangling pointers!
157        let mut c_streams: Vec<CString> = Vec::with_capacity(streams.len());
158        for stream in streams {
159            c_streams.push(CString::new(*stream).unwrap());
160        }
161
162        // Capture array of pointers to the above CString values.
163        // We also add a `NULL` pointer entry on the end, as expected by the C function called here.
164        let mut c_stream_ptrs: Vec<*const c_char> = Vec::with_capacity(c_streams.len() + 1);
165        for c_stream in &c_streams {
166            c_stream_ptrs.push(c_stream.as_ptr());
167        }
168        c_stream_ptrs.push(null());
169
170        let cb_data = box_closure_get_capi_ptr::<dyn FnMut(bool)>(Box::new(callback));
171        let ptr = unsafe { capi::pa_ext_stream_restore_delete(self.context, c_stream_ptrs.as_ptr(),
172            Some(super::success_cb_proxy), cb_data) };
173        Operation::from_raw(ptr, cb_data as *mut Box<dyn FnMut(bool)>)
174    }
175
176    /// Subscribes to changes in the stream database.
177    ///
178    /// The callback must accept a `bool`, which indicates success.
179    ///
180    /// Panics if the underlying C function returns a null pointer.
181    pub fn subscribe<F>(&mut self, enable: bool, callback: F) -> Operation<dyn FnMut(bool)>
182        where F: FnMut(bool) + 'static
183    {
184        let cb_data = box_closure_get_capi_ptr::<dyn FnMut(bool)>(Box::new(callback));
185        let ptr = unsafe { capi::pa_ext_stream_restore_subscribe(self.context, enable as i32,
186            Some(super::success_cb_proxy), cb_data) };
187        Operation::from_raw(ptr, cb_data as *mut Box<dyn FnMut(bool)>)
188    }
189
190    /// Sets the subscription callback that is called when [`subscribe()`](Self::subscribe) was
191    /// called.
192    pub fn set_subscribe_cb<F>(&mut self, callback: F)
193        where F: FnMut() + 'static
194    {
195        let saved = &mut self.cb_ptrs.subscribe;
196        *saved = super::ExtSubscribeCb::new(Some(Box::new(callback)));
197        let (cb_fn, cb_data) = saved.get_capi_params(super::ext_subscribe_cb_proxy);
198        unsafe { capi::pa_ext_stream_restore_set_subscribe_cb(self.context, cb_fn, cb_data); }
199    }
200}
201
202impl Drop for StreamRestore {
203    fn drop(&mut self) {
204        unsafe { capi::pa_context_unref(self.context) };
205        self.context = null_mut::<ContextInternal>();
206    }
207}
208
209/// Proxy for read list callbacks.
210///
211/// Warning: This is for list cases only! On EOL it destroys the actual closure callback.
212extern "C"
213fn read_list_cb_proxy(_: *mut ContextInternal, i: *const InfoInternal, eol: i32,
214    userdata: *mut c_void)
215{
216    let _ = std::panic::catch_unwind(|| {
217        callback_for_list_instance(i, eol, userdata, Info::new_from_raw);
218    });
219}