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}