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