From 46eefc63f3c5d424b01614ae8c3f67d20d4145f2 Mon Sep 17 00:00:00 2001 From: Brendan Allan Date: Tue, 4 Nov 2025 12:12:58 +0800 Subject: [PATCH 1/5] support macos 12.7 --- Cargo.toml | 2 +- crates/audio/src/bin/macos-audio-capture.rs | 11 ++++++++--- crates/camera-avfoundation/src/lib.rs | 11 +++++++---- crates/scap-screencapturekit/src/capture.rs | 4 ++-- crates/scap-screencapturekit/src/config.rs | 9 +++++++-- 5 files changed, 25 insertions(+), 12 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index f18ad9c2be..0b35f66d45 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -42,7 +42,7 @@ tracing = "0.1.41" futures = "0.3.31" cidre = { git = "https://github.com/CapSoftware/cidre", rev = "bf84b67079a8", features = [ - "macos_13_0", + "macos_12_7", "cv", "cf", "core_audio", diff --git a/crates/audio/src/bin/macos-audio-capture.rs b/crates/audio/src/bin/macos-audio-capture.rs index 2ba94cc29a..72e60ce82b 100644 --- a/crates/audio/src/bin/macos-audio-capture.rs +++ b/crates/audio/src/bin/macos-audio-capture.rs @@ -12,7 +12,7 @@ mod macos { use std::{sync::mpsc::Sender, time::Duration}; use cidre::{ - cm, define_obj_type, ns, objc, + api, cm, define_obj_type, ns, objc, sc::{ self, stream::{Output, OutputImpl}, @@ -63,8 +63,13 @@ mod macos { pub async fn main() { let mut cfg = sc::StreamCfg::new(); - cfg.set_captures_audio(true); - cfg.set_excludes_current_process_audio(false); + + if api::macos_available("13.0") { + unsafe { + cfg.set_captures_audio(true); + cfg.set_excludes_current_process_audio(false); + } + } let content = sc::ShareableContent::current().await.expect("content"); let display = &content.displays().get(0).unwrap(); diff --git a/crates/camera-avfoundation/src/lib.rs b/crates/camera-avfoundation/src/lib.rs index 1f59ea6264..f4cebcc862 100644 --- a/crates/camera-avfoundation/src/lib.rs +++ b/crates/camera-avfoundation/src/lib.rs @@ -12,10 +12,13 @@ use std::{ use tracing::warn; pub fn list_video_devices() -> arc::R> { - let mut device_types = vec![ - av::CaptureDeviceType::built_in_wide_angle_camera(), - av::CaptureDeviceType::desk_view_camera(), - ]; + let mut device_types = vec![av::CaptureDeviceType::built_in_wide_angle_camera()]; + + if api::macos_available("13.0") + && let Some(typ) = unsafe { av::CaptureDeviceType::desk_view_camera() } + { + device_types.push(typ); + } if api::macos_available("14.0") { if let Some(typ) = unsafe { av::CaptureDeviceType::external() } { diff --git a/crates/scap-screencapturekit/src/capture.rs b/crates/scap-screencapturekit/src/capture.rs index 18a1a0bb62..4098648d82 100644 --- a/crates/scap-screencapturekit/src/capture.rs +++ b/crates/scap-screencapturekit/src/capture.rs @@ -1,5 +1,5 @@ use cidre::{ - arc, cm, cv, define_obj_type, dispatch, ns, objc, + api, arc, cm, cv, define_obj_type, dispatch, ns, objc, sc::{self, StreamDelegate, StreamDelegateImpl, StreamOutput, StreamOutputImpl}, }; @@ -193,7 +193,7 @@ impl CapturerBuilder { let queue = dispatch::Queue::serial_with_ar_pool(); - if self.config.captures_audio() { + if api::macos_available("13.0") && unsafe { self.config.captures_audio() } { stream .add_stream_output(callbacks.as_ref(), sc::OutputType::Audio, Some(&queue)) .map_err(|e| e.retained())?; diff --git a/crates/scap-screencapturekit/src/config.rs b/crates/scap-screencapturekit/src/config.rs index 5787959c70..eabd8a36b0 100644 --- a/crates/scap-screencapturekit/src/config.rs +++ b/crates/scap-screencapturekit/src/config.rs @@ -1,4 +1,4 @@ -use cidre::{arc, cg, cm, sc}; +use cidre::{api, arc, cg, cm, sc}; pub struct StreamCfgBuilder(arc::R); @@ -31,8 +31,13 @@ impl StreamCfgBuilder { }); } + // Only supported on macOS 13.0+ pub fn set_captures_audio(&mut self, captures_audio: bool) { - self.0.set_captures_audio(captures_audio); + if api::macos_available("13.0") { + unsafe { + self.0.set_captures_audio(captures_audio); + } + } } /// Logical width of the capture area From 8293c6728e41405dd4a6449a96be47f99c358d47 Mon Sep 17 00:00:00 2001 From: Brendan Allan Date: Tue, 4 Nov 2025 13:59:55 +0800 Subject: [PATCH 2/5] Disable system audio toggle when not available --- apps/desktop/src-tauri/src/lib.rs | 1 + apps/desktop/src-tauri/src/platform/mod.rs | 20 +++++++++++++ .../src/routes/(window-chrome)/(main).tsx | 23 +++++++++++++-- .../(window-chrome)/new-main/SystemAudio.tsx | 29 ++++++++++++++++--- apps/desktop/src/utils/queries.ts | 6 ++++ apps/desktop/src/utils/tauri.ts | 19 +++++++----- crates/scap-screencapturekit/src/capture.rs | 4 +-- crates/scap-screencapturekit/src/config.rs | 4 +-- crates/scap-screencapturekit/src/lib.rs | 6 ++++ 9 files changed, 94 insertions(+), 18 deletions(-) diff --git a/apps/desktop/src-tauri/src/lib.rs b/apps/desktop/src-tauri/src/lib.rs index d6ae97bfc3..115008412b 100644 --- a/apps/desktop/src-tauri/src/lib.rs +++ b/apps/desktop/src-tauri/src/lib.rs @@ -1873,6 +1873,7 @@ pub async fn run(recording_logging_handle: LoggingHandle, logs_dir: PathBuf) { show_window, write_clipboard_string, platform::perform_haptic_feedback, + platform::is_system_audio_capture_supported, list_fails, set_fail, update_auth_plan, diff --git a/apps/desktop/src-tauri/src/platform/mod.rs b/apps/desktop/src-tauri/src/platform/mod.rs index 267ec61b9c..b409f600d7 100644 --- a/apps/desktop/src-tauri/src/platform/mod.rs +++ b/apps/desktop/src-tauri/src/platform/mod.rs @@ -52,3 +52,23 @@ pub fn perform_haptic_feedback( #[cfg(not(target_os = "macos"))] Err("Haptics are only supported on macOS.".into()) } + +/// Check if system audio capture is supported on the current platform and OS version. +/// On macOS, system audio capture requires macOS 13.0 or later. +/// On Windows/Linux, this may have different requirements. +#[tauri::command] +#[specta::specta] +#[instrument] +pub fn is_system_audio_capture_supported() -> bool { + #[cfg(target_os = "macos")] + { + scap_screencapturekit::is_system_audio_supported() + } + + #[cfg(not(target_os = "macos"))] + { + // On Windows/Linux, we assume system audio capture is available + // This can be refined later based on platform-specific requirements + true + } +} diff --git a/apps/desktop/src/routes/(window-chrome)/(main).tsx b/apps/desktop/src/routes/(window-chrome)/(main).tsx index 5a98956cc4..9aaacff060 100644 --- a/apps/desktop/src/routes/(window-chrome)/(main).tsx +++ b/apps/desktop/src/routes/(window-chrome)/(main).tsx @@ -30,6 +30,7 @@ import { createLicenseQuery, createVideoDevicesQuery, getPermissions, + isSystemAudioSupported, listAudioDevices, listScreens, listWindows, @@ -952,15 +953,25 @@ function MicrophoneSelect(props: { function SystemAudio() { const { rawOptions, setOptions } = useRecordingOptions(); const currentRecording = createCurrentRecordingQuery(); + const systemAudioSupported = createQuery(() => isSystemAudioSupported); - return ( + const isDisabled = () => + !!currentRecording.data || systemAudioSupported.data === false; + const tooltipMessage = () => { + if (systemAudioSupported.data === false) { + return "System audio capture requires macOS 13.0 or later"; + } + return undefined; + }; + + const button = ( ); + + return tooltipMessage() ? ( + {button} + ) : ( + button + ); } function TargetSelect(props: { diff --git a/apps/desktop/src/routes/(window-chrome)/new-main/SystemAudio.tsx b/apps/desktop/src/routes/(window-chrome)/new-main/SystemAudio.tsx index c07c7bce92..a4300e95e9 100644 --- a/apps/desktop/src/routes/(window-chrome)/new-main/SystemAudio.tsx +++ b/apps/desktop/src/routes/(window-chrome)/new-main/SystemAudio.tsx @@ -1,18 +1,33 @@ -import { createCurrentRecordingQuery } from "~/utils/queries"; +import { createQuery } from "@tanstack/solid-query"; +import Tooltip from "~/components/Tooltip"; +import { + createCurrentRecordingQuery, + isSystemAudioSupported, +} from "~/utils/queries"; import { useRecordingOptions } from "../OptionsContext"; import InfoPill from "./InfoPill"; export default function SystemAudio() { const { rawOptions, setOptions } = useRecordingOptions(); const currentRecording = createCurrentRecordingQuery(); + const systemAudioSupported = createQuery(() => isSystemAudioSupported); - return ( + const isDisabled = () => + !!currentRecording.data || systemAudioSupported.data === false; + const tooltipMessage = () => { + if (systemAudioSupported.data === false) { + return "System audio capture requires macOS 13.0 or later"; + } + return undefined; + }; + + const button = ( ); + + return tooltipMessage() ? ( + {button} + ) : ( + button + ); } diff --git a/apps/desktop/src/utils/queries.ts b/apps/desktop/src/utils/queries.ts index c9462c5c98..a70c79a2bd 100644 --- a/apps/desktop/src/utils/queries.ts +++ b/apps/desktop/src/utils/queries.ts @@ -112,6 +112,12 @@ export const getPermissions = queryOptions({ refetchInterval: 1000, }); +export const isSystemAudioSupported = queryOptions({ + queryKey: ["systemAudioSupported"] as const, + queryFn: () => commands.isSystemAudioCaptureSupported(), + staleTime: Number.POSITIVE_INFINITY, // This won't change during runtime +}); + export function createOptionsQuery() { const PERSIST_KEY = "recording-options-query-2"; const [_state, _setState] = createStore<{ diff --git a/apps/desktop/src/utils/tauri.ts b/apps/desktop/src/utils/tauri.ts index 927aea45ac..bcf2824adc 100644 --- a/apps/desktop/src/utils/tauri.ts +++ b/apps/desktop/src/utils/tauri.ts @@ -182,6 +182,14 @@ async writeClipboardString(text: string) : Promise { async performHapticFeedback(pattern: HapticPattern | null, time: HapticPerformanceTime | null) : Promise { return await TAURI_INVOKE("perform_haptic_feedback", { pattern, time }); }, +/** + * Check if system audio capture is supported on the current platform and OS version. + * On macOS, system audio capture requires macOS 13.0 or later. + * On Windows/Linux, this may have different requirements. + */ +async isSystemAudioCaptureSupported() : Promise { + return await TAURI_INVOKE("is_system_audio_capture_supported"); +}, async listFails() : Promise<{ [key in string]: boolean }> { return await TAURI_INVOKE("list_fails"); }, @@ -301,7 +309,6 @@ recordingOptionsChanged: RecordingOptionsChanged, recordingStarted: RecordingStarted, recordingStopped: RecordingStopped, renderFrameEvent: RenderFrameEvent, -requestNewScreenshot: RequestNewScreenshot, requestOpenRecordingPicker: RequestOpenRecordingPicker, requestOpenSettings: RequestOpenSettings, requestScreenCapturePrewarm: RequestScreenCapturePrewarm, @@ -323,7 +330,6 @@ recordingOptionsChanged: "recording-options-changed", recordingStarted: "recording-started", recordingStopped: "recording-stopped", renderFrameEvent: "render-frame-event", -requestNewScreenshot: "request-new-screenshot", requestOpenRecordingPicker: "request-open-recording-picker", requestOpenSettings: "request-open-settings", requestScreenCapturePrewarm: "request-screen-capture-prewarm", @@ -343,7 +349,7 @@ export type AspectRatio = "wide" | "vertical" | "square" | "classic" | "tall" export type Audio = { duration: number; sample_rate: number; channels: number; start_time: number } export type AudioConfiguration = { mute: boolean; improve: boolean; micVolumeDb?: number; micStereoMode?: StereoMode; systemVolumeDb?: number } export type AudioInputLevelChange = number -export type AudioMeta = { path: string; +export type AudioMeta = { path: string; /** * unix time of the first frame */ @@ -395,11 +401,11 @@ export type Flags = { captions: boolean } export type FramesRendered = { renderedCount: number; totalFrames: number; type: "FramesRendered" } export type GeneralSettingsStore = { instanceId?: string; uploadIndividualFiles?: boolean; hideDockIcon?: boolean; hapticsEnabled?: boolean; autoCreateShareableLink?: boolean; enableNotifications?: boolean; disableAutoOpenLinks?: boolean; hasCompletedStartup?: boolean; theme?: AppTheme; commercialLicense?: CommercialLicense | null; lastVersion?: string | null; windowTransparency?: boolean; postStudioRecordingBehaviour?: PostStudioRecordingBehaviour; mainWindowRecordingStartBehaviour?: MainWindowRecordingStartBehaviour; custom_cursor_capture2?: boolean; serverUrl?: string; recordingCountdown?: number | null; enableNativeCameraPreview: boolean; autoZoomOnClicks?: boolean; enableNewRecordingFlow: boolean; postDeletionBehaviour?: PostDeletionBehaviour; excludedWindows?: WindowExclusion[]; deleteInstantRecordingsAfterUpload?: boolean; instantModeMaxResolution?: number } export type GifExportSettings = { fps: number; resolution_base: XY; quality: GifQuality | null } -export type GifQuality = { +export type GifQuality = { /** * Encoding quality from 1-100 (default: 90) */ -quality: number | null; +quality: number | null; /** * Whether to prioritize speed over quality (default: false) */ @@ -449,7 +455,6 @@ export type RecordingStarted = null export type RecordingStopped = null export type RecordingTargetMode = "display" | "window" | "area" export type RenderFrameEvent = { frame_number: number; fps: number; resolution_base: XY } -export type RequestNewScreenshot = null export type RequestOpenRecordingPicker = { target_mode: RecordingTargetMode | null } export type RequestOpenSettings = { page: string } export type RequestScreenCapturePrewarm = { force?: boolean } @@ -477,7 +482,7 @@ export type UploadProgress = { progress: number } export type UploadProgressEvent = { video_id: string; uploaded: string; total: string } export type UploadResult = { Success: string } | "NotAuthenticated" | "PlanCheckFailed" | "UpgradeRequired" export type Video = { duration: number; width: number; height: number; fps: number; start_time: number } -export type VideoMeta = { path: string; fps?: number; +export type VideoMeta = { path: string; fps?: number; /** * unix time of the first frame */ diff --git a/crates/scap-screencapturekit/src/capture.rs b/crates/scap-screencapturekit/src/capture.rs index 4098648d82..5eb35c1812 100644 --- a/crates/scap-screencapturekit/src/capture.rs +++ b/crates/scap-screencapturekit/src/capture.rs @@ -1,5 +1,5 @@ use cidre::{ - api, arc, cm, cv, define_obj_type, dispatch, ns, objc, + arc, cm, cv, define_obj_type, dispatch, ns, objc, sc::{self, StreamDelegate, StreamDelegateImpl, StreamOutput, StreamOutputImpl}, }; @@ -193,7 +193,7 @@ impl CapturerBuilder { let queue = dispatch::Queue::serial_with_ar_pool(); - if api::macos_available("13.0") && unsafe { self.config.captures_audio() } { + if crate::is_system_audio_supported() && unsafe { self.config.captures_audio() } { stream .add_stream_output(callbacks.as_ref(), sc::OutputType::Audio, Some(&queue)) .map_err(|e| e.retained())?; diff --git a/crates/scap-screencapturekit/src/config.rs b/crates/scap-screencapturekit/src/config.rs index eabd8a36b0..3d49fdede8 100644 --- a/crates/scap-screencapturekit/src/config.rs +++ b/crates/scap-screencapturekit/src/config.rs @@ -1,4 +1,4 @@ -use cidre::{api, arc, cg, cm, sc}; +use cidre::{arc, cg, cm, sc}; pub struct StreamCfgBuilder(arc::R); @@ -33,7 +33,7 @@ impl StreamCfgBuilder { // Only supported on macOS 13.0+ pub fn set_captures_audio(&mut self, captures_audio: bool) { - if api::macos_available("13.0") { + if crate::is_system_audio_supported() { unsafe { self.0.set_captures_audio(captures_audio); } diff --git a/crates/scap-screencapturekit/src/lib.rs b/crates/scap-screencapturekit/src/lib.rs index f0071fef60..7a45162451 100644 --- a/crates/scap-screencapturekit/src/lib.rs +++ b/crates/scap-screencapturekit/src/lib.rs @@ -7,3 +7,9 @@ mod permission; pub use capture::{AudioFrame, Capturer, CapturerBuilder, Frame, VideoFrame}; pub use config::StreamCfgBuilder; pub use permission::{has_permission, request_permission}; + +/// Check if system audio capture is supported on the current macOS version. +/// System audio capture via ScreenCaptureKit requires macOS 13.0 or later. +pub fn is_system_audio_supported() -> bool { + cidre::api::macos_available("13.0") +} From c400723c495d1973ff860c168810613bd117f1e0 Mon Sep 17 00:00:00 2001 From: Brendan Allan Date: Tue, 4 Nov 2025 16:03:52 +0800 Subject: [PATCH 3/5] unify system audio toggle --- .../src/routes/(window-chrome)/(main).tsx | 54 +++++-------------- .../(window-chrome)/new-main/SystemAudio.tsx | 49 ++++++++++++----- 2 files changed, 48 insertions(+), 55 deletions(-) diff --git a/apps/desktop/src/routes/(window-chrome)/(main).tsx b/apps/desktop/src/routes/(window-chrome)/(main).tsx index 9aaacff060..2ae9f36808 100644 --- a/apps/desktop/src/routes/(window-chrome)/(main).tsx +++ b/apps/desktop/src/routes/(window-chrome)/(main).tsx @@ -30,7 +30,6 @@ import { createLicenseQuery, createVideoDevicesQuery, getPermissions, - isSystemAudioSupported, listAudioDevices, listScreens, listWindows, @@ -173,10 +172,11 @@ function Page() { cameraID: () => cameras.find((c) => { const { cameraID } = rawOptions; - if (!cameraID) return; + if (!cameraID) return null; if ("ModelID" in cameraID && c.model_id === cameraID.ModelID) return c; if ("DeviceID" in cameraID && c.device_id === cameraID.DeviceID) return c; + return null; }), micName: () => mics.data?.find((name: any) => name === rawOptions.micName), }; @@ -600,6 +600,7 @@ import { RecordingOptionsProvider, useRecordingOptions, } from "./OptionsContext"; +import { SystemAudioToggleRoot } from "./new-main/SystemAudio"; let hasChecked = false; function createUpdateCheck() { @@ -951,47 +952,16 @@ function MicrophoneSelect(props: { } function SystemAudio() { - const { rawOptions, setOptions } = useRecordingOptions(); - const currentRecording = createCurrentRecordingQuery(); - const systemAudioSupported = createQuery(() => isSystemAudioSupported); - - const isDisabled = () => - !!currentRecording.data || systemAudioSupported.data === false; - const tooltipMessage = () => { - if (systemAudioSupported.data === false) { - return "System audio capture requires macOS 13.0 or later"; - } - return undefined; - }; - - const button = ( - - ); - - return tooltipMessage() ? ( - {button} - ) : ( - button + PillComponent={InfoPill} + icon={ +
+ +
+ } + /> ); } diff --git a/apps/desktop/src/routes/(window-chrome)/new-main/SystemAudio.tsx b/apps/desktop/src/routes/(window-chrome)/new-main/SystemAudio.tsx index a4300e95e9..d6f32c8fb3 100644 --- a/apps/desktop/src/routes/(window-chrome)/new-main/SystemAudio.tsx +++ b/apps/desktop/src/routes/(window-chrome)/new-main/SystemAudio.tsx @@ -1,5 +1,8 @@ import { createQuery } from "@tanstack/solid-query"; -import Tooltip from "~/components/Tooltip"; +import type { ComponentProps, JSX } from "solid-js"; +import type { Component } from "solid-js"; +import { Dynamic } from "solid-js/web"; + import { createCurrentRecordingQuery, isSystemAudioSupported, @@ -8,6 +11,27 @@ import { useRecordingOptions } from "../OptionsContext"; import InfoPill from "./InfoPill"; export default function SystemAudio() { + return ( + } + /> + ); +} + +export function SystemAudioToggleRoot( + props: Omit< + ComponentProps<"button">, + "onClick" | "disabled" | "title" | "type" | "children" + > & { + PillComponent: Component<{ + variant: "blue" | "red"; + children: JSX.Element; + }>; + icon: JSX.Element; + }, +) { const { rawOptions, setOptions } = useRecordingOptions(); const currentRecording = createCurrentRecordingQuery(); const systemAudioSupported = createQuery(() => isSystemAudioSupported); @@ -15,36 +39,35 @@ export default function SystemAudio() { const isDisabled = () => !!currentRecording.data || systemAudioSupported.data === false; const tooltipMessage = () => { - if (systemAudioSupported.data === false) { + if (systemAudioSupported.data !== false) { return "System audio capture requires macOS 13.0 or later"; } return undefined; }; - const button = ( + return ( ); - - return tooltipMessage() ? ( - {button} - ) : ( - button - ); } From 06a37964603e778c28ede4353968e9f59b0dc7ae Mon Sep 17 00:00:00 2001 From: Brendan Allan Date: Tue, 4 Nov 2025 16:45:26 +0800 Subject: [PATCH 4/5] unify camera/mic/system audio selects --- .../src/routes/(window-chrome)/(main).tsx | 250 ++---------------- .../(window-chrome)/new-main/CameraSelect.tsx | 36 ++- .../new-main/MicrophoneSelect.tsx | 60 ++++- .../(window-chrome)/new-main/SystemAudio.tsx | 5 +- .../new-main/TargetSelectInfoPill.tsx | 11 +- 5 files changed, 110 insertions(+), 252 deletions(-) diff --git a/apps/desktop/src/routes/(window-chrome)/(main).tsx b/apps/desktop/src/routes/(window-chrome)/(main).tsx index 2ae9f36808..dcfbe8a86e 100644 --- a/apps/desktop/src/routes/(window-chrome)/(main).tsx +++ b/apps/desktop/src/routes/(window-chrome)/(main).tsx @@ -1,10 +1,6 @@ import { Button } from "@cap/ui-solid"; import { useNavigate } from "@solidjs/router"; -import { - createMutation, - createQuery, - useQueryClient, -} from "@tanstack/solid-query"; +import { createMutation, createQuery } from "@tanstack/solid-query"; import { getVersion } from "@tauri-apps/api/app"; import { getCurrentWindow, LogicalSize } from "@tauri-apps/api/window"; import { cx } from "cva"; @@ -12,7 +8,6 @@ import { type ComponentProps, createEffect, createResource, - createSignal, ErrorBoundary, onCleanup, onMount, @@ -29,7 +24,6 @@ import { createCurrentRecordingQuery, createLicenseQuery, createVideoDevicesQuery, - getPermissions, listAudioDevices, listScreens, listWindows, @@ -39,7 +33,6 @@ import { type CaptureDisplay, type CaptureWindow, commands, - events, type RecordingMode, type ScreenCaptureTarget, } from "~/utils/tauri"; @@ -559,29 +552,9 @@ function Page() { ); } -function useRequestPermission() { - const queryClient = useQueryClient(); - - async function requestPermission(type: "camera" | "microphone") { - try { - if (type === "camera") { - await commands.resetCameraPermissions(); - } else if (type === "microphone") { - await commands.resetMicrophonePermissions(); - } - await commands.requestPermission(type); - await queryClient.refetchQueries(getPermissions); - } catch (error) { - console.error(`Failed to get ${type} permission:`, error); - } - } - - return requestPermission; -} - import { createEventListener } from "@solid-primitives/event-listener"; import { makePersisted } from "@solid-primitives/storage"; -import { CheckMenuItem, Menu, PredefinedMenuItem } from "@tauri-apps/api/menu"; +import { CheckMenuItem, Menu } from "@tauri-apps/api/menu"; import { getCurrentWebviewWindow, WebviewWindow, @@ -592,15 +565,16 @@ import * as updater from "@tauri-apps/plugin-updater"; import { Transition } from "solid-transition-group"; import { SignInButton } from "~/components/SignInButton"; import { authStore, generalSettingsStore } from "~/store"; -import { createTauriEventListener } from "~/utils/createEventListener"; import { handleRecordingResult } from "~/utils/recording"; import { apiClient } from "~/utils/web-api"; import { WindowChromeHeader } from "./Context"; +import { CameraSelectBase } from "./new-main/CameraSelect"; +import { MicrophoneSelectBase } from "./new-main/MicrophoneSelect"; +import { SystemAudioToggleRoot } from "./new-main/SystemAudio"; import { RecordingOptionsProvider, useRecordingOptions, } from "./OptionsContext"; -import { SystemAudioToggleRoot } from "./new-main/SystemAudio"; let hasChecked = false; function createUpdateCheck() { @@ -763,191 +737,36 @@ function AreaSelectButton(props: { ); } -const NO_CAMERA = "No Camera"; - function CameraSelect(props: { disabled?: boolean; options: CameraInfo[]; value: CameraInfo | null; onChange: (cameraInfo: CameraInfo | null) => void; }) { - const currentRecording = createCurrentRecordingQuery(); - const permissions = createQuery(() => getPermissions); - const requestPermission = useRequestPermission(); - - const permissionGranted = () => - permissions?.data?.camera === "granted" || - permissions?.data?.camera === "notNeeded"; - - const onChange = (cameraInfo: CameraInfo | null) => { - if (!cameraInfo && !permissionGranted()) return requestPermission("camera"); - - props.onChange(cameraInfo); - - trackEvent("camera_selected", { - camera_name: cameraInfo, - enabled: !!cameraInfo, - }); - }; - return ( -
- -
+ ); } -const NO_MICROPHONE = "No Microphone"; - function MicrophoneSelect(props: { disabled?: boolean; options: string[]; value: string | null; onChange: (micName: string | null) => void; }) { - const DB_SCALE = 40; - - const permissions = createQuery(() => getPermissions); - const currentRecording = createCurrentRecordingQuery(); - - const [dbs, setDbs] = createSignal(); - - const requestPermission = useRequestPermission(); - - const permissionGranted = () => - permissions?.data?.microphone === "granted" || - permissions?.data?.microphone === "notNeeded"; - - type Option = { name: string }; - - const handleMicrophoneChange = async (item: Option | null) => { - if (!props.options) return; - - props.onChange(item ? item.name : null); - if (!item) setDbs(); - - trackEvent("microphone_selected", { - microphone_name: item?.name ?? null, - enabled: !!item, - }); - }; - - createTauriEventListener(events.audioInputLevelChange, (dbs) => { - if (!props.value) setDbs(); - else setDbs(dbs); - }); - - // visual audio level from 0 -> 1 - const audioLevel = () => - (1 - Math.max((dbs() ?? 0) + DB_SCALE, 0) / DB_SCALE) ** 0.5; - return ( -
- -
+ ); } @@ -1025,39 +844,6 @@ function TargetSelect(props: { ); } -function TargetSelectInfoPill(props: { - value: T | null; - permissionGranted: boolean; - requestPermission: () => void; - onClick: (e: MouseEvent) => void; -}) { - return ( - { - if (!props.permissionGranted || props.value === null) return; - - e.stopPropagation(); - }} - onClick={(e) => { - if (!props.permissionGranted) { - props.requestPermission(); - e.stopPropagation(); - return; - } - - props.onClick(e); - }} - > - {!props.permissionGranted - ? "Request Permission" - : props.value !== null - ? "On" - : "Off"} - - ); -} - function InfoPill( props: ComponentProps<"button"> & { variant: "blue" | "red" }, ) { diff --git a/apps/desktop/src/routes/(window-chrome)/new-main/CameraSelect.tsx b/apps/desktop/src/routes/(window-chrome)/new-main/CameraSelect.tsx index 83a28b7fa8..ab95e90f45 100644 --- a/apps/desktop/src/routes/(window-chrome)/new-main/CameraSelect.tsx +++ b/apps/desktop/src/routes/(window-chrome)/new-main/CameraSelect.tsx @@ -1,8 +1,10 @@ import { createQuery } from "@tanstack/solid-query"; import { CheckMenuItem, Menu, PredefinedMenuItem } from "@tauri-apps/api/menu"; +import type { Component, ComponentProps } from "solid-js"; import { trackEvent } from "~/utils/analytics"; import { createCurrentRecordingQuery, getPermissions } from "~/utils/queries"; import type { CameraInfo } from "~/utils/tauri"; +import InfoPill from "./InfoPill"; import TargetSelectInfoPill from "./TargetSelectInfoPill"; import useRequestPermission from "./useRequestPermission"; @@ -13,6 +15,27 @@ export default function CameraSelect(props: { options: CameraInfo[]; value: CameraInfo | null; onChange: (camera: CameraInfo | null) => void; +}) { + return ( + + ); +} + +export function CameraSelectBase(props: { + disabled?: boolean; + options: CameraInfo[]; + value: CameraInfo | null; + onChange: (camera: CameraInfo | null) => void; + PillComponent: Component< + ComponentProps<"button"> & { variant: "blue" | "red" } + >; + class: string; + iconClass: string; }) { const currentRecording = createCurrentRecordingQuery(); const permissions = createQuery(() => getPermissions); @@ -23,7 +46,7 @@ export default function CameraSelect(props: { permissions?.data?.camera === "notNeeded"; const onChange = (cameraLabel: CameraInfo | null) => { - if (!cameraLabel && permissions?.data?.camera !== "granted") + if (!cameraLabel && !permissionGranted()) return requestPermission("camera"); props.onChange(cameraLabel); @@ -37,9 +60,14 @@ export default function CameraSelect(props: { return (