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