From be4e2d0c94db9fef4af2b6bbfe20cdde19e45232 Mon Sep 17 00:00:00 2001 From: auberginewly <3127221787@qq.com> Date: Sun, 10 May 2026 05:30:42 +0800 Subject: [PATCH] fix(electron/macOS): proactively check screen recording permission on startup MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Microphone permission is checked at startup via getMediaAccessStatus, and camera has a dedicated request-camera-access IPC handler, but screen recording relied entirely on desktopCapturer.getSources() to implicitly trigger the TCC prompt — causing the permission dialog to reappear on every launch (issue #558). Note: askForMediaAccess() only accepts "microphone" | "camera"; screen recording TCC is triggered via desktopCapturer.getSources() instead. Fix: - Import desktopCapturer in main.ts - Call getMediaAccessStatus("screen") in app.whenReady(); trigger the TCC prompt via getSources when status is "not-determined" - Add request-screen-access IPC handler symmetric to request-camera-access --- electron/ipc/handlers.ts | 26 ++++++++++++++++++++++++++ electron/main.ts | 12 +++++++++++- 2 files changed, 37 insertions(+), 1 deletion(-) diff --git a/electron/ipc/handlers.ts b/electron/ipc/handlers.ts index bf0bc97..b13dd75 100644 --- a/electron/ipc/handlers.ts +++ b/electron/ipc/handlers.ts @@ -718,6 +718,32 @@ export function registerIpcHandlers( } }); + ipcMain.handle("request-screen-access", async () => { + if (process.platform !== "darwin") { + return { success: true, granted: true, status: "granted" }; + } + + try { + const status = systemPreferences.getMediaAccessStatus("screen"); + if (status === "granted") { + return { success: true, granted: true, status }; + } + + // Screen recording has no askForMediaAccess equivalent — the TCC prompt + // is triggered by desktopCapturer.getSources(). Fire it and return so + // the renderer can re-check status after the user responds. + if (status === "not-determined") { + desktopCapturer.getSources({ types: ["screen"] }).catch(() => {}); + return { success: true, granted: false, status: "not-determined" }; + } + + return { success: true, granted: false, status }; + } catch (error) { + console.error("Failed to request screen access:", error); + return { success: false, granted: false, status: "unknown", error: String(error) }; + } + }); + // macOS Accessibility prompt for global click capture. First call shows the // system dialog; the user has to toggle the app in System Settings (no // programmatic grant exists for Accessibility). diff --git a/electron/main.ts b/electron/main.ts index cf20b5a..4e443a9 100644 --- a/electron/main.ts +++ b/electron/main.ts @@ -4,6 +4,7 @@ import { fileURLToPath } from "node:url"; import { app, BrowserWindow, + desktopCapturer, ipcMain, Menu, nativeImage, @@ -476,12 +477,21 @@ app.whenReady().then(async () => { callback(allowed.includes(permission)); }); - // Request microphone permission from macOS + // Request microphone and screen recording permissions from macOS if (process.platform === "darwin") { const micStatus = systemPreferences.getMediaAccessStatus("microphone"); if (micStatus !== "granted") { await systemPreferences.askForMediaAccess("microphone"); } + + // Screen recording has no askForMediaAccess equivalent — the TCC prompt is + // triggered by the first desktopCapturer.getSources() call. Firing it here + // at startup settles the permission state early and prevents repeated prompts + // driven by later getSources() calls (fixes repeated permission dialog). + const screenStatus = systemPreferences.getMediaAccessStatus("screen"); + if (screenStatus === "not-determined") { + desktopCapturer.getSources({ types: ["screen"] }).catch(() => {}); + } } // Listen for HUD overlay quit event (macOS only)