Skip to main content

app_ui/
screen_ipc.rs

1//! JS-bridge bindings for the M-SCK.1 / .2 / .4 screen-capture
2//! commands (AUT-268 / AUT-269 / AUT-271).
3//!
4//! Mirror of [`crate::camera_ipc`] / [`crate::mic_ipc`] /
5//! [`crate::system_audio_ipc`] for the screen video path. Each
6//! function invokes a `__screen*` helper in `index.html` which
7//! wraps `window.__TAURI__.core.invoke(...)`.
8
9use serde::{Deserialize, Serialize};
10use wasm_bindgen::JsValue;
11use wasm_bindgen::prelude::*;
12
13/// Mirror of `crates/app/src/commands.rs::DisplaySourceView`.
14#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
15pub struct DisplaySourceView {
16    /// Stable id (`display-<displayID>`).
17    pub id: String,
18    /// Human-readable label (`"Display 1920×1080"`).
19    pub label: String,
20    /// Width in points.
21    pub width: u32,
22    /// Height in points.
23    pub height: u32,
24    /// `true` for the first display in the enumeration.
25    pub is_primary: bool,
26}
27
28/// Mirror of `crates/app/src/commands.rs::WindowSourceView`.
29#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
30pub struct WindowSourceView {
31    /// Stable id for the current session (`window-<windowID>`).
32    pub id: String,
33    /// Window title (or empty).
34    pub label: String,
35    /// Width in points.
36    pub width: u32,
37    /// Height in points.
38    pub height: u32,
39    /// Owning app bundle id.
40    pub bundle_id: String,
41    /// Owning app display name.
42    pub display_name: String,
43}
44
45/// Tagged result so the picker can render a TCC-denied error
46/// inline instead of swallowing it.
47#[derive(Debug)]
48pub enum ScreenSourcesResult<T> {
49    /// Successful enumeration.
50    Ok(Vec<T>),
51    /// SCK refused (often: TCC permission denied).
52    Err(String),
53}
54
55#[wasm_bindgen]
56extern "C" {
57    /// `__screenListScreenDisplays()` — `Promise<DisplaySourceView[]>`.
58    #[wasm_bindgen(js_name = __screenListScreenDisplays, catch)]
59    pub async fn list_screen_displays_js() -> Result<JsValue, JsValue>;
60
61    /// `__screenListScreenWindows()` — `Promise<WindowSourceView[]>`.
62    #[wasm_bindgen(js_name = __screenListScreenWindows, catch)]
63    pub async fn list_screen_windows_js() -> Result<JsValue, JsValue>;
64
65    /// `__screenStartScreenCapture(sourceId?: string)` —
66    /// `Promise<void>` (or string error). `sourceId` is
67    /// `"display-<id>"` / `"window-<id>"` / `null` for primary
68    /// display (M-SCK.0.1 / AUT-291).
69    #[wasm_bindgen(js_name = __screenStartScreenCapture, catch)]
70    pub async fn start_screen_capture_js(source_id: JsValue) -> Result<JsValue, JsValue>;
71
72    /// `__screenStopScreenCapture()` — `Promise<void>`.
73    #[wasm_bindgen(js_name = __screenStopScreenCapture, catch)]
74    pub async fn stop_screen_capture_js() -> Result<JsValue, JsValue>;
75
76    /// `__screenScreenCaptureStatus()` — `Promise<boolean>`.
77    #[wasm_bindgen(js_name = __screenScreenCaptureStatus, catch)]
78    pub async fn screen_capture_status_js() -> Result<JsValue, JsValue>;
79
80    /// `__screenScreenCaptureFrameCount()` — `Promise<number>`.
81    #[wasm_bindgen(js_name = __screenScreenCaptureFrameCount, catch)]
82    pub async fn screen_capture_frame_count_js() -> Result<JsValue, JsValue>;
83
84    /// `__screenRequestScreenRecordingPermission()` — triggers the
85    /// macOS Screen & System Audio Recording TCC request.
86    #[wasm_bindgen(js_name = __screenRequestScreenRecordingPermission, catch)]
87    pub async fn request_screen_recording_permission_js() -> Result<JsValue, JsValue>;
88
89    /// `__screenOpenSettingsScreenRecording()` — opens the OS privacy
90    /// pane where the user enables Screen & System Audio Recording.
91    #[wasm_bindgen(js_name = __screenOpenSettingsScreenRecording, catch)]
92    pub async fn open_settings_screen_recording_js() -> Result<JsValue, JsValue>;
93}
94
95/// Enumerate displays. Empty `Vec` outside Tauri.
96pub async fn list_screen_displays() -> ScreenSourcesResult<DisplaySourceView> {
97    match list_screen_displays_js().await {
98        Ok(value) => match serde_wasm_bindgen::from_value(value) {
99            Ok(v) => ScreenSourcesResult::Ok(v),
100            Err(err) => ScreenSourcesResult::Err(format!("decode failed: {err}")),
101        },
102        Err(err) => ScreenSourcesResult::Err(js_error_string(&err)),
103    }
104}
105
106/// Enumerate visible windows.
107pub async fn list_screen_windows() -> ScreenSourcesResult<WindowSourceView> {
108    match list_screen_windows_js().await {
109        Ok(value) => match serde_wasm_bindgen::from_value(value) {
110            Ok(v) => ScreenSourcesResult::Ok(v),
111            Err(err) => ScreenSourcesResult::Err(format!("decode failed: {err}")),
112        },
113        Err(err) => ScreenSourcesResult::Err(js_error_string(&err)),
114    }
115}
116
117/// Start screen capture. `source_id` of `None` captures the primary
118/// display (M-SCK.0 default); `Some("display-<id>")` / `Some("window-<id>")`
119/// pin to a specific source (M-SCK.0.1 / AUT-291). Returns
120/// `Ok(())` or `Err(message)`.
121pub async fn start_screen_capture(source_id: Option<String>) -> Result<(), String> {
122    let arg = match source_id {
123        Some(id) => JsValue::from_str(&id),
124        None => JsValue::NULL,
125    };
126    start_screen_capture_js(arg)
127        .await
128        .map(|_| ())
129        .map_err(|err| js_error_string(&err))
130}
131
132/// Stop screen capture.
133pub async fn stop_screen_capture() {
134    let _ = stop_screen_capture_js().await;
135}
136
137/// `true` when a screen-capture session is active.
138pub async fn screen_capture_status() -> bool {
139    match screen_capture_status_js().await {
140        Ok(value) => serde_wasm_bindgen::from_value(value).unwrap_or(false),
141        Err(_) => false,
142    }
143}
144
145/// Cumulative frame counter. Returns `0` outside Tauri.
146pub async fn screen_capture_frame_count() -> u64 {
147    match screen_capture_frame_count_js().await {
148        Ok(value) => serde_wasm_bindgen::from_value(value).unwrap_or(0),
149        Err(_) => 0,
150    }
151}
152
153/// Trigger the platform Screen Recording permission flow. On macOS this
154/// is the call that creates the row in System Settings after a TCC reset.
155pub async fn request_screen_recording_permission() {
156    let _ = request_screen_recording_permission_js().await;
157}
158
159/// Open System Settings → Privacy & Security → Screen & System Audio
160/// Recording. No-op outside Tauri.
161pub async fn open_settings_screen_recording() {
162    let _ = open_settings_screen_recording_js().await;
163}
164
165fn js_error_string(err: &JsValue) -> String {
166    err.as_string().unwrap_or_else(|| format!("{err:?}"))
167}