Skip to main content

flexaudio_core/
capabilities.rs

1//! Capture support and optional side effects, independent of capture permissions.
2//!
3//! A supported capability describes an implemented backend, not permission to use a
4//! particular device or process. Snapshots do not open capture or render streams.
5
6use crate::{Error, SourceKind};
7
8/// Infallible capability snapshot for the current platform.
9#[non_exhaustive]
10#[derive(Debug, Clone, PartialEq, Eq)]
11pub struct Capabilities {
12    /// Running OS version, when it can be read.
13    pub os_version: Option<OsVersion>,
14    /// Capture without system exclusions.
15    pub sources: SourceCapabilities,
16    /// System capture with `exclude_self` or `exclude_pids`.
17    pub system_exclusions: SystemExclusionCapabilities,
18    /// PID matching semantics for process capture.
19    pub process: ProcessCapabilities,
20    /// Device notification support.
21    pub device_watcher: DeviceWatcherCapabilities,
22    /// Optional capture-related audio rendering.
23    pub side_effects: SideEffectCapabilities,
24}
25
26impl Capabilities {
27    /// Construct a platform snapshot from independently reported capabilities.
28    pub fn new(
29        os_version: Option<OsVersion>,
30        sources: SourceCapabilities,
31        system_exclusions: SystemExclusionCapabilities,
32        process: ProcessCapabilities,
33        device_watcher: DeviceWatcherCapabilities,
34        side_effects: SideEffectCapabilities,
35    ) -> Self {
36        Self {
37            os_version,
38            sources,
39            system_exclusions,
40            process,
41            device_watcher,
42            side_effects,
43        }
44    }
45}
46
47/// Numeric OS version; absent components were not reported by the OS.
48#[non_exhaustive]
49#[derive(Debug, Clone, PartialEq, Eq)]
50pub struct OsVersion {
51    /// Major version.
52    pub major: u32,
53    /// Minor version.
54    pub minor: u32,
55    /// Patch version, if known.
56    pub patch: Option<u32>,
57    /// Build number, if known.
58    pub build: Option<u32>,
59}
60
61impl OsVersion {
62    /// Construct a version without inventing unreported components.
63    pub fn new(major: u32, minor: u32, patch: Option<u32>, build: Option<u32>) -> Self {
64        Self {
65            major,
66            minor,
67            patch,
68            build,
69        }
70    }
71}
72
73/// Capture sources without system PID exclusions.
74#[non_exhaustive]
75#[derive(Debug, Clone, PartialEq, Eq)]
76pub struct SourceCapabilities {
77    /// Microphone capture.
78    pub mic: SourceCapability,
79    /// System output capture without exclusions.
80    pub system: SourceCapability,
81    /// Individual process capture.
82    pub process: SourceCapability,
83    /// Microphone and system mix without exclusions.
84    pub mix: SourceCapability,
85}
86
87impl SourceCapabilities {
88    /// Construct the four source descriptions.
89    pub fn new(
90        mic: SourceCapability,
91        system: SourceCapability,
92        process: SourceCapability,
93        mix: SourceCapability,
94    ) -> Self {
95        Self {
96            mic,
97            system,
98            process,
99            mix,
100        }
101    }
102}
103
104/// Backend and availability of one capture source.
105#[non_exhaustive]
106#[derive(Debug, Clone, PartialEq, Eq)]
107pub struct SourceCapability {
108    /// Implementing backend.
109    pub backend: BackendKind,
110    /// Current support or query failure.
111    pub availability: Availability,
112}
113
114impl SourceCapability {
115    /// Construct a source description.
116    pub fn new(backend: BackendKind, availability: Availability) -> Self {
117        Self {
118            backend,
119            availability,
120        }
121    }
122}
123
124/// Implementation used for a capability.
125#[non_exhaustive]
126#[derive(Debug, Clone, PartialEq, Eq)]
127pub enum BackendKind {
128    /// CPAL microphone capture.
129    Cpal,
130    /// PipeWire capture.
131    PipeWire,
132    /// WASAPI render-endpoint loopback.
133    WasapiLoopback,
134    /// WASAPI process-tree loopback.
135    WasapiProcessLoopback,
136    /// Core Audio process taps.
137    CoreAudioProcessTap,
138    /// Microphone and system capture composed by the facade.
139    Composite,
140    /// No implementation on this platform.
141    Unsupported,
142}
143
144/// Capability availability, distinct from device or recording permission.
145#[non_exhaustive]
146#[derive(Debug, Clone, PartialEq, Eq)]
147pub enum Availability {
148    /// Implemented and any platform prerequisite checked successfully.
149    Supported,
150    /// A known prerequisite is absent.
151    Unavailable {
152        /// Confirmed reason for lack of support.
153        reason: UnavailableReason,
154    },
155    /// The prerequisite could not be determined.
156    Unknown {
157        /// Typed query failure; diagnostic details remain opt-in.
158        error: Error,
159    },
160}
161
162/// Confirmed reason why a capability cannot be used.
163#[non_exhaustive]
164#[derive(Debug, Clone, PartialEq, Eq)]
165pub enum UnavailableReason {
166    /// No implementation for this OS.
167    UnsupportedOs,
168    /// The OS predates the backend's minimum version.
169    OsVersionTooOld {
170        /// Minimum required version.
171        required: OsVersion,
172        /// Running version.
173        actual: OsVersion,
174    },
175    /// A PipeWire server is confirmed unavailable.
176    PipeWireUnavailable,
177    /// This feature does not apply on the platform.
178    NotApplicable,
179}
180
181/// How a PID identifies captured or excluded audio.
182#[non_exhaustive]
183#[derive(Debug, Clone, PartialEq, Eq)]
184pub enum PidScope {
185    /// Only the specified PID.
186    ExactPid,
187    /// The root PID and its process tree.
188    ProcessTree,
189    /// No applicable PID operation.
190    NotApplicable,
191}
192
193/// Treatment of an explicit system output device during exclusion capture.
194#[non_exhaustive]
195#[derive(Debug, Clone, PartialEq, Eq)]
196pub enum DeviceSelectionBehavior {
197    /// Exclusions and device selection are applied together.
198    Honored,
199    /// Exclusion capture ignores the selected output device.
200    Ignored,
201    /// No applicable exclusion backend.
202    NotApplicable,
203}
204
205/// Support for system capture with `exclude_self` or `exclude_pids`.
206#[non_exhaustive]
207#[derive(Debug, Clone, PartialEq, Eq)]
208pub struct SystemExclusionCapabilities {
209    /// Availability of the exclusion backend.
210    pub availability: Availability,
211    /// Implementing backend.
212    pub backend: BackendKind,
213    /// Meaning of each excluded PID.
214    pub scope: PidScope,
215    /// Distinct PID limit, including implicit self; `None` means no count limit.
216    pub max_distinct_pids: Option<u32>,
217    /// Whether output-device selection is honored with exclusions.
218    pub device_selection: DeviceSelectionBehavior,
219}
220
221impl SystemExclusionCapabilities {
222    /// Construct a system exclusion description.
223    pub fn new(
224        availability: Availability,
225        backend: BackendKind,
226        scope: PidScope,
227        max_distinct_pids: Option<u32>,
228        device_selection: DeviceSelectionBehavior,
229    ) -> Self {
230        Self {
231            availability,
232            backend,
233            scope,
234            max_distinct_pids,
235            device_selection,
236        }
237    }
238}
239
240/// PID semantics for process-source include and exclude modes.
241#[non_exhaustive]
242#[derive(Debug, Clone, PartialEq, Eq)]
243pub struct ProcessCapabilities {
244    /// Include-mode PID semantics.
245    pub include_scope: PidScope,
246    /// Exclude-mode PID semantics.
247    pub exclude_scope: PidScope,
248}
249
250impl ProcessCapabilities {
251    /// Construct process-mode semantics.
252    pub fn new(include_scope: PidScope, exclude_scope: PidScope) -> Self {
253        Self {
254            include_scope,
255            exclude_scope,
256        }
257    }
258}
259
260/// Device watcher implementation.
261#[non_exhaustive]
262#[derive(Debug, Clone, PartialEq, Eq)]
263pub enum WatcherMode {
264    /// Native OS notifications.
265    Native,
266    /// Intentional watcher that emits no notifications.
267    Noop,
268}
269
270/// Device changes a watcher can report.
271#[non_exhaustive]
272#[derive(Debug, Clone, PartialEq, Eq)]
273pub enum WatchScope {
274    /// System output additions and removals.
275    SystemOutputs,
276    /// Default system output changes.
277    DefaultSystemOutput,
278}
279
280/// Device watcher support, independent of capture sources.
281#[non_exhaustive]
282#[derive(Debug, Clone, PartialEq, Eq)]
283pub struct DeviceWatcherCapabilities {
284    /// Availability of notifications.
285    pub availability: Availability,
286    /// Implementing watcher mode.
287    pub mode: WatcherMode,
288    /// Reported change domains; empty for a no-op watcher.
289    pub scopes: Vec<WatchScope>,
290}
291
292impl DeviceWatcherCapabilities {
293    /// Construct a watcher description.
294    pub fn new(availability: Availability, mode: WatcherMode, scopes: Vec<WatchScope>) -> Self {
295        Self {
296            availability,
297            mode,
298            scopes,
299        }
300    }
301}
302
303/// Condition under which an optional audio-rendering side effect occurs.
304#[non_exhaustive]
305#[derive(Debug, Clone, PartialEq, Eq)]
306pub enum SideEffectCondition {
307    /// Sustained captured silence while system audio is active.
308    SilentActiveSystemAudio,
309    /// Classic system loopback, including the Mix lane, without PID exclusions.
310    ClassicLoopbackWithoutExclusions,
311}
312
313/// One optional capture-related side effect.
314#[non_exhaustive]
315#[derive(Debug, Clone, PartialEq, Eq)]
316pub struct SideEffectCapability {
317    /// Platform availability of this side effect.
318    pub availability: Availability,
319    /// Whether default capture options enable it.
320    pub enabled_by_default: bool,
321    /// Capture sources that may trigger it.
322    pub sources: Vec<SourceKind>,
323    /// Required capture condition.
324    pub condition: SideEffectCondition,
325}
326
327impl SideEffectCapability {
328    /// Construct a side-effect description.
329    pub fn new(
330        availability: Availability,
331        enabled_by_default: bool,
332        sources: Vec<SourceKind>,
333        condition: SideEffectCondition,
334    ) -> Self {
335        Self {
336            availability,
337            enabled_by_default,
338            sources,
339            condition,
340        }
341    }
342}
343
344/// Optional audio rendering performed by capture backends.
345#[non_exhaustive]
346#[derive(Debug, Clone, PartialEq, Eq)]
347pub struct SideEffectCapabilities {
348    /// Silence diagnosis through a short active audio probe.
349    pub silence_probe: SideEffectCapability,
350    /// Silent rendering that keeps classic WASAPI loopback delivering idle packets.
351    pub classic_loopback_keepalive: SideEffectCapability,
352}
353
354impl SideEffectCapabilities {
355    /// Construct the optional side-effect descriptions.
356    pub fn new(
357        silence_probe: SideEffectCapability,
358        classic_loopback_keepalive: SideEffectCapability,
359    ) -> Self {
360        Self {
361            silence_probe,
362            classic_loopback_keepalive,
363        }
364    }
365}