libpulse_binding/context/subscribe.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//! Daemon introspection event subscription subsystem.
15//!
16//! # Overview
17//!
18//! The application can be notified, asynchronously, whenever the internal layout of the server
19//! changes. The set of facilities and operations for which notifications are generated are
20//! enumerated in [`Facility`] and [`Operation`].
21//!
22//! The application sets the notification mask using [`Context::subscribe()`] and the callback
23//! function that will be called whenever a notification occurs using
24//! [`Context::set_subscribe_callback()`].
25//!
26//! The mask provided to [`Context::subscribe()`] can be created by binary ORing a set of values,
27//! either produced with [`Facility::to_interest_mask()`], or more simply with the provided
28//! constants in the [`subscription_masks`](mod@subscription_masks) submodule.
29//!
30//! The callback will be called with event type information representing the event that caused the
31//! callback, detailing *facility* and *operation*, where for instance [`Facility::Source`] with
32//! [`Operation::New`] indicates that a new source was added.
33//!
34//! # Example
35//!
36//! Subscribe (declare interest):
37//!
38//! ```rust,ignore
39//! use libpulse_binding::context::subscribe::subscription_masks;
40//!
41//! let interest = subscription_masks::SINK |
42//! subscription_masks::SOURCE;
43//!
44//! let op = my_context.subscribe(
45//! interest, // Our interest mask
46//! |_| {} // We won’t bother doing anything in the success callback in this example
47//! );
48//! ```
49
50use std::os::raw::c_void;
51use bitflags::bitflags;
52use num_derive::{FromPrimitive, ToPrimitive};
53use super::{ContextInternal, Context};
54use crate::operation;
55use crate::callbacks::{box_closure_get_capi_ptr, MultiUseCallback};
56
57pub use capi::context::subscribe::pa_subscription_event_type_t as EventType;
58
59bitflags! {
60 /// A set of facility masks, to be passed to [`Context::subscribe()`].
61 ///
62 /// Note that you can convert a [`Facility`] to a mask with [`Facility::to_interest_mask()`].
63 #[repr(transparent)]
64 pub struct InterestMaskSet: u32 {
65 /// No facility (null selection; zero).
66 const NULL = capi::PA_SUBSCRIPTION_MASK_NULL;
67 /// Sink facility.
68 const SINK = capi::PA_SUBSCRIPTION_MASK_SINK;
69 /// Source facility.
70 const SOURCE = capi::PA_SUBSCRIPTION_MASK_SOURCE;
71 /// Sink input facility.
72 const SINK_INPUT = capi::PA_SUBSCRIPTION_MASK_SINK_INPUT;
73 /// Source output facility.
74 const SOURCE_OUTPUT = capi::PA_SUBSCRIPTION_MASK_SOURCE_OUTPUT;
75 /// Module facility.
76 const MODULE = capi::PA_SUBSCRIPTION_MASK_MODULE;
77 /// Client facility.
78 const CLIENT = capi::PA_SUBSCRIPTION_MASK_CLIENT;
79 /// Sample cache facility.
80 const SAMPLE_CACHE = capi::PA_SUBSCRIPTION_MASK_SAMPLE_CACHE;
81 /// Server facility.
82 const SERVER = capi::PA_SUBSCRIPTION_MASK_SERVER;
83 /// Card facility.
84 const CARD = capi::PA_SUBSCRIPTION_MASK_CARD;
85 /// All facilities.
86 const ALL = capi::PA_SUBSCRIPTION_MASK_ALL;
87 }
88}
89
90/// Facility component of an event.
91#[repr(u32)]
92#[derive(Debug, Copy, Clone, PartialEq, Eq)]
93#[derive(FromPrimitive, ToPrimitive)]
94pub enum Facility {
95 /// Sink.
96 Sink = 0,
97 /// Source.
98 Source = 1,
99 /// Sink-input.
100 SinkInput = 2,
101 /// Source-output.
102 SourceOutput = 3,
103 /// Module.
104 Module = 4,
105 /// Client.
106 Client = 5,
107 /// Sample-cache.
108 SampleCache = 6,
109 /// Global server change, only occurring with [`Operation::Changed`].
110 Server = 7,
111 /* NOTE: value `8` previously assigned, obsoleted */
112 /// Card.
113 Card = 9,
114}
115
116/// Operation component of an event.
117#[repr(u32)]
118#[derive(Debug, Copy, Clone, PartialEq, Eq)]
119#[derive(FromPrimitive, ToPrimitive)]
120pub enum Operation {
121 /// A new object was created
122 New = 0,
123 /// A property of the object was modified
124 Changed = 0x10,
125 /// An object was removed
126 Removed = 0x20,
127}
128
129impl Facility {
130 /// Mask to extract facility value from the event type passed to the user callback.
131 pub const MASK: EventType = capi::PA_SUBSCRIPTION_EVENT_FACILITY_MASK;
132
133 fn from_event(value: EventType) -> Option<Facility> {
134 match value & Self::MASK {
135 0 => Some(Facility::Sink),
136 1 => Some(Facility::Source),
137 2 => Some(Facility::SinkInput),
138 3 => Some(Facility::SourceOutput),
139 4 => Some(Facility::Module),
140 5 => Some(Facility::Client),
141 6 => Some(Facility::SampleCache),
142 7 => Some(Facility::Server),
143 /* NOTE: value `8` previously assigned, obsoleted */
144 9 => Some(Facility::Card),
145 _ => None,
146 }
147 }
148
149 /// Converts to an interest mask.
150 #[inline(always)]
151 pub const fn to_interest_mask(self) -> InterestMaskSet {
152 InterestMaskSet::from_bits_truncate(1u32 << (self as u32))
153 }
154}
155
156impl Operation {
157 /// Mask to extract operation value from the event type passed to the user callback.
158 pub const MASK: EventType = capi::PA_SUBSCRIPTION_EVENT_TYPE_MASK;
159
160 fn from_event(value: EventType) -> Option<Operation> {
161 match value & Self::MASK {
162 0 => Some(Operation::New),
163 0x10 => Some(Operation::Changed),
164 0x20 => Some(Operation::Removed),
165 _ => None,
166 }
167 }
168}
169
170pub(super) type Callback = MultiUseCallback<dyn FnMut(Option<Facility>, Option<Operation>, u32),
171 extern "C" fn(*mut ContextInternal, EventType, u32, *mut c_void)>;
172
173impl Context {
174 /// Enables event notification.
175 ///
176 /// The `mask` parameter is used to specify which facilities you are interested in being
177 /// modified about. Use [`set_subscribe_callback()`] to set the actual callback that will be
178 /// called when an event occurs.
179 ///
180 /// The callback must accept a `bool`, which indicates success.
181 ///
182 /// Panics if the underlying C function returns a null pointer.
183 ///
184 /// [`set_subscribe_callback()`]: Self::set_subscribe_callback
185 pub fn subscribe<F>(&mut self, mask: InterestMaskSet, callback: F)
186 -> operation::Operation<dyn FnMut(bool)>
187 where F: FnMut(bool) + 'static
188 {
189 let cb_data = box_closure_get_capi_ptr::<dyn FnMut(bool)>(Box::new(callback));
190 let ptr = unsafe { capi::pa_context_subscribe(self.ptr, mask.bits(),
191 Some(super::success_cb_proxy), cb_data) };
192 operation::Operation::from_raw(ptr, cb_data as *mut Box<dyn FnMut(bool)>)
193 }
194
195 /// Sets the context specific call back function that is called whenever a subscribed-to event
196 /// occurs.
197 ///
198 /// Use [`subscribe()`] to set the facilities you are interested in receiving notifications for,
199 /// and thus to start receiving notifications with the callback set here.
200 ///
201 /// The callback must take three parameters. The first two are the facility and operation
202 /// components of the event type respectively (the underlying C API provides this information
203 /// combined into a single integer, here we extract the two component parts for you); these are
204 /// wrapped in `Option` wrappers should the given values ever not map to the enum variants, but
205 /// it’s probably safe to always just `unwrap()` them). The third parameter is an associated
206 /// index value.
207 ///
208 /// [`subscribe()`]: Self::subscribe
209 pub fn set_subscribe_callback(&mut self,
210 callback: Option<Box<dyn FnMut(Option<Facility>, Option<Operation>, u32) + 'static>>)
211 {
212 let saved = &mut self.cb_ptrs.subscribe;
213 *saved = Callback::new(callback);
214 let (cb_fn, cb_data) = saved.get_capi_params(cb_proxy);
215 unsafe { capi::pa_context_set_subscribe_callback(self.ptr, cb_fn, cb_data); }
216 }
217}
218
219/// Proxy for callbacks.
220///
221/// Warning: This is for multi-use cases! It does **not** destroy the actual closure callback, which
222/// must be accomplished separately to avoid a memory leak.
223extern "C"
224fn cb_proxy(_: *mut ContextInternal, et: EventType, index: u32, userdata: *mut c_void) {
225 let _ = std::panic::catch_unwind(|| {
226 let facility = Facility::from_event(et);
227 let operation = Operation::from_event(et);
228 let callback = Callback::get_callback(userdata);
229 (callback)(facility, operation, index);
230 });
231}