cranpose_services/
microphone_permission.rs1use std::sync::{
21 Arc, Mutex, OnceLock,
22 atomic::{AtomicU64, Ordering},
23};
24
25use cranpose_core::{State, rememberEventStream};
26
27use crate::registry::ServiceRegistry;
28
29#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
31pub enum MicrophonePermission {
32 NotAsked,
34 Granted,
35 Denied,
37 Blocked,
39}
40
41pub trait MicrophoneAccess: Send + Sync {
43 fn permission(&self) -> MicrophonePermission;
45 fn request(&self);
48 fn open_settings(&self);
50 fn prepare_recording(&self) -> Result<(), String> {
54 Ok(())
55 }
56}
57
58static PLATFORM: ServiceRegistry<dyn MicrophoneAccess> = ServiceRegistry::new();
59
60pub fn set_platform_microphone_access(access: Option<Arc<dyn MicrophoneAccess>>) {
63 match access {
64 Some(access) => PLATFORM.set(access),
65 None => PLATFORM.clear(),
66 }
67 publish_microphone_permission();
68}
69
70pub fn microphone_permission() -> MicrophonePermission {
72 PLATFORM
73 .get()
74 .map_or(MicrophonePermission::Granted, |access| access.permission())
75}
76
77pub fn request_microphone_permission() {
80 if let Some(access) = PLATFORM.get()
81 && matches!(
82 access.permission(),
83 MicrophonePermission::NotAsked | MicrophonePermission::Denied
84 )
85 {
86 access.request();
87 }
88}
89
90pub fn open_microphone_settings() {
93 if let Some(access) = PLATFORM.get() {
94 access.open_settings();
95 }
96}
97
98pub fn prepare_microphone() -> Result<(), String> {
102 PLATFORM
103 .get()
104 .map_or(Ok(()), |access| access.prepare_recording())
105}
106
107type Observer = Arc<dyn Fn(MicrophonePermission) + Send + Sync>;
108
109static NEXT_OBSERVER: AtomicU64 = AtomicU64::new(1);
110
111fn observers() -> &'static Mutex<Vec<(u64, Observer)>> {
112 static OBSERVERS: OnceLock<Mutex<Vec<(u64, Observer)>>> = OnceLock::new();
113 OBSERVERS.get_or_init(|| Mutex::new(Vec::new()))
114}
115
116pub fn publish_microphone_permission() {
119 let permission = microphone_permission();
120 let listeners: Vec<Observer> = observers()
121 .lock()
122 .map(|observers| observers.iter().map(|(_, o)| Arc::clone(o)).collect())
123 .unwrap_or_default();
124 for observer in listeners {
125 observer(permission);
126 }
127}
128
129pub struct MicrophonePermissionObserver {
131 id: u64,
132}
133
134impl Drop for MicrophonePermissionObserver {
135 fn drop(&mut self) {
136 if let Ok(mut observers) = observers().lock() {
137 observers.retain(|(id, _)| *id != self.id);
138 }
139 }
140}
141
142pub fn observe_microphone_permission(
145 observer: impl Fn(MicrophonePermission) + Send + Sync + 'static,
146) -> MicrophonePermissionObserver {
147 let id = NEXT_OBSERVER.fetch_add(1, Ordering::Relaxed);
148 let observer: Observer = Arc::new(observer);
149 if let Ok(mut observers) = observers().lock() {
150 observers.push((id, Arc::clone(&observer)));
151 }
152 observer(microphone_permission());
153 MicrophonePermissionObserver { id }
154}
155
156#[expect(non_snake_case)]
160#[track_caller]
161pub fn rememberMicrophonePermission() -> State<MicrophonePermission> {
162 let lifecycle = crate::rememberLifecycleState().get();
163 let updates = rememberEventStream(lifecycle, move |sender| {
164 observe_microphone_permission(move |permission| sender.send(permission))
165 });
166 cranpose_core::collectAsState(updates, lifecycle, microphone_permission())
167}
168
169#[cfg(test)]
170#[path = "tests/microphone_permission_tests.rs"]
171mod tests;