From a5d5c780c2ad3d6695978dfb292b755c423d02ca Mon Sep 17 00:00:00 2001 From: Roshan Warrier Date: Thu, 16 Apr 2026 14:28:29 +0530 Subject: [PATCH] fix: avoid ScreenCaptureKit launch crash on macOS 11 (#424) Co-authored-by: txhno <198242577+txhno@users.noreply.github.com> --- app/src/lib/hooks/useSystemAudioCapture.ts | 20 ++++++++++++-- app/src/platform/types.ts | 2 +- tauri/src-tauri/build.rs | 4 +++ tauri/src-tauri/src/audio_capture/macos.rs | 32 ++++++++++++++-------- tauri/src/platform/audio.ts | 5 ++-- web/src/platform/audio.ts | 2 +- 6 files changed, 47 insertions(+), 18 deletions(-) diff --git a/app/src/lib/hooks/useSystemAudioCapture.ts b/app/src/lib/hooks/useSystemAudioCapture.ts index 6136de67..9e85384f 100644 --- a/app/src/lib/hooks/useSystemAudioCapture.ts +++ b/app/src/lib/hooks/useSystemAudioCapture.ts @@ -26,8 +26,24 @@ export function useSystemAudioCapture({ // Check if system audio capture is supported useEffect(() => { - const supported = platform.audio.isSystemAudioSupported(); - setIsSupported(supported); + let isActive = true; + + void platform.audio + .isSystemAudioSupported() + .then((supported) => { + if (isActive) { + setIsSupported(supported); + } + }) + .catch(() => { + if (isActive) { + setIsSupported(false); + } + }); + + return () => { + isActive = false; + }; }, [platform]); const startRecording = useCallback(async () => { diff --git a/app/src/platform/types.ts b/app/src/platform/types.ts index 28c3c071..c6b06f44 100644 --- a/app/src/platform/types.ts +++ b/app/src/platform/types.ts @@ -42,7 +42,7 @@ export interface AudioDevice { } export interface PlatformAudio { - isSystemAudioSupported(): boolean; + isSystemAudioSupported(): Promise; startSystemAudioCapture(maxDurationSecs: number): Promise; stopSystemAudioCapture(): Promise; listOutputDevices(): Promise; diff --git a/tauri/src-tauri/build.rs b/tauri/src-tauri/build.rs index 37f93ff4..317d74d3 100644 --- a/tauri/src-tauri/build.rs +++ b/tauri/src-tauri/build.rs @@ -5,6 +5,10 @@ fn main() { // Link Swift runtime libraries for screencapturekit crate #[cfg(target_os = "macos")] { + // ScreenCaptureKit does not exist on macOS 11, so weak-link it to + // allow the app to launch and gate usage at runtime instead. + println!("cargo:rustc-link-arg=-Wl,-weak_framework,ScreenCaptureKit"); + // Add Swift runtime library paths to RPATH println!("cargo:rustc-link-arg=-Wl,-rpath,/usr/lib/swift"); println!("cargo:rustc-link-arg=-L/usr/lib/swift"); diff --git a/tauri/src-tauri/src/audio_capture/macos.rs b/tauri/src-tauri/src/audio_capture/macos.rs index ef5451d0..996de8cb 100644 --- a/tauri/src-tauri/src/audio_capture/macos.rs +++ b/tauri/src-tauri/src/audio_capture/macos.rs @@ -13,6 +13,7 @@ use screencapturekit::{ }, }; use std::io::Cursor; +use std::process::Command; use std::sync::{Arc, Mutex}; use tokio::sync::mpsc; @@ -20,6 +21,10 @@ pub async fn start_capture( state: &AudioCaptureState, max_duration_secs: u32, ) -> Result<(), String> { + if !is_supported() { + return Err("System audio capture requires macOS 12.3 or newer.".to_string()); + } + // Reset previous samples state.reset(); @@ -144,17 +149,22 @@ pub async fn stop_capture(state: &AudioCaptureState) -> Result { } pub fn is_supported() -> bool { - // ScreenCaptureKit requires macOS 12.3+ - // Check if we're on a supported version - #[cfg(target_os = "macos")] - { - // Basic check - ScreenCaptureKit should be available on macOS 12.3+ - true - } - #[cfg(not(target_os = "macos"))] - { - false - } + macos_version_at_least(12, 3) +} + +fn macos_version_at_least(required_major: u64, required_minor: u64) -> bool { + let output = match Command::new("sw_vers").arg("-productVersion").output() { + Ok(output) if output.status.success() => output, + _ => return false, + }; + + let version = String::from_utf8_lossy(&output.stdout); + let mut parts = version.trim().split('.'); + + let major = parts.next().and_then(|part| part.parse::().ok()).unwrap_or(0); + let minor = parts.next().and_then(|part| part.parse::().ok()).unwrap_or(0); + + major > required_major || (major == required_major && minor >= required_minor) } fn extract_audio_samples(sample_buffer: CMSampleBuffer) -> Result, String> { diff --git a/tauri/src/platform/audio.ts b/tauri/src/platform/audio.ts index bdcf0b27..06c3e45b 100644 --- a/tauri/src/platform/audio.ts +++ b/tauri/src/platform/audio.ts @@ -2,9 +2,8 @@ import { invoke } from '@tauri-apps/api/core'; import type { PlatformAudio, AudioDevice } from '@/platform/types'; export const tauriAudio: PlatformAudio = { - isSystemAudioSupported(): boolean { - // This will be checked dynamically via invoke - return true; // Tauri supports it, but actual support depends on platform + async isSystemAudioSupported(): Promise { + return await invoke('is_system_audio_supported'); }, async startSystemAudioCapture(maxDurationSecs: number): Promise { diff --git a/web/src/platform/audio.ts b/web/src/platform/audio.ts index 220a5ade..1abd4ecc 100644 --- a/web/src/platform/audio.ts +++ b/web/src/platform/audio.ts @@ -1,7 +1,7 @@ import type { PlatformAudio, AudioDevice } from '@/platform/types'; export const webAudio: PlatformAudio = { - isSystemAudioSupported(): boolean { + async isSystemAudioSupported(): Promise { return false; // System audio capture not supported in web },