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}