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}