Skip to content

Commit 5d95f24

Browse files
committed
Handle excluded windows in macOS screen capture
1 parent b61958c commit 5d95f24

File tree

1 file changed

+112
-17
lines changed

1 file changed

+112
-17
lines changed

apps/desktop/src-tauri/src/recording.rs

Lines changed: 112 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ use std::{
3131
any::Any,
3232
collections::{HashMap, VecDeque},
3333
error::Error as StdError,
34+
mem,
3435
panic::AssertUnwindSafe,
3536
path::{Path, PathBuf},
3637
str::FromStr,
@@ -64,6 +65,8 @@ use crate::{
6465
web_api::ManagerExt,
6566
windows::{CapWindowId, ShowCapWindow},
6667
};
68+
#[cfg(target_os = "macos")]
69+
use scap_targets::Window;
6770

6871
#[derive(Clone)]
6972
pub struct InProgressRecordingCommon {
@@ -107,6 +110,7 @@ unsafe impl Sync for SendableShareableContent {}
107110
#[cfg(target_os = "macos")]
108111
async fn acquire_shareable_content_for_target(
109112
capture_target: &ScreenCaptureTarget,
113+
excluded_windows: &[scap_targets::WindowId],
110114
) -> anyhow::Result<SendableShareableContent> {
111115
let mut refreshed = false;
112116

@@ -118,13 +122,20 @@ async fn acquire_shareable_content_for_target(
118122
.ok_or_else(|| anyhow!("GetShareableContent/NotAvailable"))?,
119123
);
120124

121-
if !shareable_content_missing_target_display(capture_target, shareable_content.retained())
122-
.await
123-
{
125+
let sc_content = shareable_content.retained();
126+
let missing_display =
127+
shareable_content_missing_target_display(capture_target, sc_content.clone()).await;
128+
let missing_windows =
129+
shareable_content_missing_windows(excluded_windows, sc_content.clone()).await;
130+
131+
if !missing_display && (!missing_windows || refreshed) {
132+
if missing_windows && refreshed {
133+
debug!("Excluded windows missing from refreshed ScreenCaptureKit content");
134+
}
124135
return Ok(shareable_content);
125136
}
126137

127-
if refreshed {
138+
if refreshed && missing_display {
128139
return Err(anyhow!("GetShareableContent/DisplayMissing"));
129140
}
130141

@@ -150,6 +161,74 @@ async fn shareable_content_missing_target_display(
150161
}
151162
}
152163

164+
#[cfg(target_os = "macos")]
165+
async fn shareable_content_missing_windows(
166+
excluded_windows: &[scap_targets::WindowId],
167+
shareable_content: cidre::arc::R<cidre::sc::ShareableContent>,
168+
) -> bool {
169+
if excluded_windows.is_empty() {
170+
return false;
171+
}
172+
173+
for window_id in excluded_windows {
174+
let Some(window) = Window::from_id(window_id) else {
175+
continue;
176+
};
177+
178+
if window
179+
.raw_handle()
180+
.as_sc(shareable_content.clone())
181+
.await
182+
.is_none()
183+
{
184+
return true;
185+
}
186+
}
187+
188+
false
189+
}
190+
191+
#[cfg(target_os = "macos")]
192+
async fn prune_excluded_windows_without_shareable_content(
193+
excluded_windows: &mut Vec<scap_targets::WindowId>,
194+
shareable_content: cidre::arc::R<cidre::sc::ShareableContent>,
195+
) {
196+
if excluded_windows.is_empty() {
197+
return;
198+
}
199+
200+
let mut removed = 0usize;
201+
let mut pruned = Vec::with_capacity(excluded_windows.len());
202+
let current = mem::take(excluded_windows);
203+
204+
for window_id in current {
205+
let Some(window) = Window::from_id(&window_id) else {
206+
removed += 1;
207+
continue;
208+
};
209+
210+
if window
211+
.raw_handle()
212+
.as_sc(shareable_content.clone())
213+
.await
214+
.is_some()
215+
{
216+
pruned.push(window_id);
217+
} else {
218+
removed += 1;
219+
}
220+
}
221+
222+
if removed > 0 {
223+
debug!(
224+
removed,
225+
"Dropping excluded windows missing from ScreenCaptureKit content"
226+
);
227+
}
228+
229+
*excluded_windows = pruned;
230+
}
231+
153232
#[cfg(target_os = "macos")]
154233
fn is_shareable_content_error(err: &anyhow::Error) -> bool {
155234
err.chain().any(|cause| {
@@ -613,17 +692,7 @@ pub async fn start_recording(
613692
state.camera_in_use = camera_feed.is_some();
614693

615694
#[cfg(target_os = "macos")]
616-
let mut shareable_content =
617-
acquire_shareable_content_for_target(&inputs.capture_target).await?;
618-
619-
let common = InProgressRecordingCommon {
620-
target_name,
621-
inputs: inputs.clone(),
622-
recording_dir: recording_dir.clone(),
623-
};
624-
625-
#[cfg(target_os = "macos")]
626-
let excluded_windows = {
695+
let mut excluded_windows = {
627696
let window_exclusions = general_settings
628697
.as_ref()
629698
.map_or_else(general_settings::default_excluded_windows, |settings| {
@@ -633,6 +702,24 @@ pub async fn start_recording(
633702
crate::window_exclusion::resolve_window_ids(&window_exclusions)
634703
};
635704

705+
#[cfg(target_os = "macos")]
706+
let mut shareable_content =
707+
acquire_shareable_content_for_target(&inputs.capture_target, &excluded_windows)
708+
.await?;
709+
710+
#[cfg(target_os = "macos")]
711+
prune_excluded_windows_without_shareable_content(
712+
&mut excluded_windows,
713+
shareable_content.retained(),
714+
)
715+
.await;
716+
717+
let common = InProgressRecordingCommon {
718+
target_name,
719+
inputs: inputs.clone(),
720+
recording_dir: recording_dir.clone(),
721+
};
722+
636723
let mut mic_restart_attempts = 0;
637724

638725
let done_fut = loop {
@@ -751,8 +838,16 @@ pub async fn start_recording(
751838
}
752839
#[cfg(target_os = "macos")]
753840
Err(err) if is_shareable_content_error(&err) => {
754-
shareable_content =
755-
acquire_shareable_content_for_target(&inputs.capture_target).await?;
841+
shareable_content = acquire_shareable_content_for_target(
842+
&inputs.capture_target,
843+
&excluded_windows,
844+
)
845+
.await?;
846+
prune_excluded_windows_without_shareable_content(
847+
&mut excluded_windows,
848+
shareable_content.retained(),
849+
)
850+
.await;
756851
continue;
757852
}
758853
Err(err) if mic_restart_attempts == 0 && mic_actor_not_running(&err) => {

0 commit comments

Comments
 (0)