app_ui/
system_audio_ipc.rs1use serde::{Deserialize, Serialize};
10use wasm_bindgen::JsCast;
11use wasm_bindgen::JsValue;
12use wasm_bindgen::prelude::*;
13
14#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
16pub struct AudioAppView {
17 pub pid: u32,
20 pub bundle_id: String,
22 pub display_name: String,
24 pub icon_png_bytes: Vec<u8>,
27}
28
29#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
33pub enum AudioAppFilterView {
34 AllAudio,
36 OnlyApps(Vec<String>),
38 ExcludeApps(Vec<String>),
40}
41
42#[wasm_bindgen]
43extern "C" {
44 #[wasm_bindgen(js_name = __screenListAudioApps, catch)]
47 pub async fn list_audio_apps_js() -> Result<JsValue, JsValue>;
48
49 #[wasm_bindgen(js_name = __screenStartSystemAudio, catch)]
52 pub async fn start_system_audio_js() -> Result<JsValue, JsValue>;
53
54 #[wasm_bindgen(js_name = __screenStopSystemAudio, catch)]
56 pub async fn stop_system_audio_js() -> Result<JsValue, JsValue>;
57
58 #[wasm_bindgen(js_name = __screenSetSystemAudioFilter, catch)]
61 pub async fn set_system_audio_filter_js(filter: JsValue) -> Result<JsValue, JsValue>;
62
63 #[wasm_bindgen(js_name = __screenSystemAudioStatus, catch)]
65 pub async fn system_audio_status_js() -> Result<JsValue, JsValue>;
66
67 #[wasm_bindgen(js_name = __screenOpenSettingsScreenRecording, catch)]
71 pub async fn open_settings_screen_recording_js() -> Result<JsValue, JsValue>;
72
73 #[wasm_bindgen(js_name = __screenRequestScreenRecordingPermission, catch)]
76 pub async fn request_screen_recording_permission_js() -> Result<JsValue, JsValue>;
77}
78
79#[derive(Debug)]
82pub enum ListAudioAppsResult {
83 Ok(Vec<AudioAppView>),
85 Err(String),
87}
88
89pub async fn list_audio_apps() -> ListAudioAppsResult {
91 match list_audio_apps_js().await {
92 Ok(value) => match serde_wasm_bindgen::from_value(value) {
93 Ok(apps) => ListAudioAppsResult::Ok(apps),
94 Err(err) => ListAudioAppsResult::Err(format!("decode failed: {err}")),
95 },
96 Err(err) => ListAudioAppsResult::Err(js_error_string(&err)),
97 }
98}
99
100pub async fn start_system_audio_capture() -> Result<(), String> {
103 start_system_audio_js()
104 .await
105 .map(|_| ())
106 .map_err(|err| js_error_string(&err))
107}
108
109pub async fn stop_system_audio_capture() {
111 let _ = stop_system_audio_js().await;
112}
113
114pub async fn set_system_audio_filter(filter: AudioAppFilterView) -> Result<(), String> {
116 let value = serde_wasm_bindgen::to_value(&filter)
117 .map_err(|err| format!("serialise filter failed: {err}"))?;
118 set_system_audio_filter_js(value)
119 .await
120 .map(|_| ())
121 .map_err(|err| js_error_string(&err))
122}
123
124pub async fn system_audio_status() -> bool {
126 match system_audio_status_js().await {
127 Ok(value) => serde_wasm_bindgen::from_value(value).unwrap_or(false),
128 Err(_) => false,
129 }
130}
131
132pub async fn open_settings_screen_recording() {
137 let _ = open_settings_screen_recording_js().await;
138}
139
140pub async fn request_screen_recording_permission() {
144 let _ = request_screen_recording_permission_js().await;
145}
146
147pub fn subscribe_system_audio_level(handler: impl Fn(f32) + 'static) {
153 use wasm_bindgen::closure::Closure;
154 let Some(window) = web_sys::window() else {
155 return;
156 };
157 let Ok(tauri) = js_sys::Reflect::get(&window, &"__TAURI__".into()) else {
158 return;
159 };
160 let Ok(event) = js_sys::Reflect::get(&tauri, &"event".into()) else {
161 return;
162 };
163 let Ok(listen) = js_sys::Reflect::get(&event, &"listen".into()) else {
164 return;
165 };
166 let Ok(listen_fn) = listen.dyn_into::<js_sys::Function>() else {
167 return;
168 };
169 let cb = Closure::wrap(Box::new(move |payload: JsValue| {
170 #[allow(
171 clippy::cast_possible_truncation,
172 reason = "RMS in [0, 1] easily fits f32"
173 )]
174 let level = js_sys::Reflect::get(&payload, &"payload".into())
175 .ok()
176 .and_then(|v| v.as_f64())
177 .map_or(0.0_f32, |v| v as f32);
178 handler(level);
179 }) as Box<dyn Fn(JsValue)>);
180 let _ = listen_fn.call2(
181 &event,
182 &"system-audio-level".into(),
183 cb.as_ref().unchecked_ref(),
184 );
185 cb.forget();
186}
187
188fn js_error_string(err: &JsValue) -> String {
191 err.as_string().unwrap_or_else(|| format!("{err:?}"))
192}