From 969d53f8a1ceaa87300373aa943ea49824c40363 Mon Sep 17 00:00:00 2001 From: Makito Date: Tue, 13 Jan 2026 01:13:02 +0900 Subject: [PATCH] feat(stage-tamagotchi,electron-screen-capture,i18n): check for screen capture permissions --- .../renderer/components/WithScreenCapture.vue | 111 ++++++++ .../pages/devtools/screen-capture.vue | 268 ++++++++++-------- cspell.config.yaml | 1 + packages/electron-screen-capture/src/index.ts | 9 +- .../electron-screen-capture/src/main/index.ts | 7 +- .../electron-screen-capture/src/main/utils.ts | 20 ++ .../electron-screen-capture/src/renderer.ts | 14 +- .../src/vue/use-electron-screen-capture.ts | 20 +- .../src/locales/en/tamagotchi/settings.yaml | 11 + 9 files changed, 328 insertions(+), 133 deletions(-) create mode 100644 apps/stage-tamagotchi/src/renderer/components/WithScreenCapture.vue diff --git a/apps/stage-tamagotchi/src/renderer/components/WithScreenCapture.vue b/apps/stage-tamagotchi/src/renderer/components/WithScreenCapture.vue new file mode 100644 index 000000000..5655123f2 --- /dev/null +++ b/apps/stage-tamagotchi/src/renderer/components/WithScreenCapture.vue @@ -0,0 +1,111 @@ + + + diff --git a/apps/stage-tamagotchi/src/renderer/pages/devtools/screen-capture.vue b/apps/stage-tamagotchi/src/renderer/pages/devtools/screen-capture.vue index e770123c0..46d34feaf 100644 --- a/apps/stage-tamagotchi/src/renderer/pages/devtools/screen-capture.vue +++ b/apps/stage-tamagotchi/src/renderer/pages/devtools/screen-capture.vue @@ -1,29 +1,39 @@ diff --git a/cspell.config.yaml b/cspell.config.yaml index ac2125c10..5dccb6edb 100644 --- a/cspell.config.yaml +++ b/cspell.config.yaml @@ -273,6 +273,7 @@ words: - sumimakito - supergroup - superjson + - systempreferences - tamagotchi - tauri - taze diff --git a/packages/electron-screen-capture/src/index.ts b/packages/electron-screen-capture/src/index.ts index c811a1fc2..a5e1a6d7b 100644 --- a/packages/electron-screen-capture/src/index.ts +++ b/packages/electron-screen-capture/src/index.ts @@ -1,4 +1,4 @@ -import type { DesktopCapturerSource, SourcesOptions } from 'electron' +import type { DesktopCapturerSource, SourcesOptions, systemPreferences } from 'electron' import { defineInvokeEventa } from '@moeru/eventa' @@ -19,5 +19,8 @@ export interface ScreenCaptureSetSourceRequest { } export const screenCaptureGetSources = defineInvokeEventa('eventa:invoke:electron:screen-capture:get-sources') -export const screenCaptureSetSourceEx = defineInvokeEventa('eventa:invoke:electron:screen-capture:set-source') -export const screenCaptureResetSource = defineInvokeEventa('eventa:invoke:electron:screen-capture:reset-source') +export const screenCaptureSetSourceEx = defineInvokeEventa('eventa:invoke:electron:screen-capture:set-source') +export const screenCaptureResetSource = defineInvokeEventa('eventa:invoke:electron:screen-capture:reset-source') + +export const screenCaptureCheckMacOSPermission = defineInvokeEventa, never>('eventa:invoke:electron:screen-capture:check-macos-permission') +export const screenCaptureRequestMacOSPermission = defineInvokeEventa('eventa:invoke:electron:screen-capture:request-macos-permission') diff --git a/packages/electron-screen-capture/src/main/index.ts b/packages/electron-screen-capture/src/main/index.ts index cf5b4be88..c72517a09 100644 --- a/packages/electron-screen-capture/src/main/index.ts +++ b/packages/electron-screen-capture/src/main/index.ts @@ -11,8 +11,8 @@ import { Mutex, withTimeout } from 'async-mutex' import { app, desktopCapturer, ipcMain, session as sessionModule } from 'electron' import { nanoid } from 'nanoid' -import { screenCaptureGetSources, screenCaptureResetSource, screenCaptureSetSourceEx } from '..' -import { toSerializableDesktopCapturerSource } from './utils' +import { screenCaptureCheckMacOSPermission, screenCaptureGetSources, screenCaptureRequestMacOSPermission, screenCaptureResetSource, screenCaptureSetSourceEx } from '..' +import { checkMacOSScreenCapturePermission, requestMacOSScreenCapturePermission, toSerializableDesktopCapturerSource } from './utils' export const defaultSourcesOptions: SourcesOptions = { types: ['screen'] } @@ -144,6 +144,9 @@ export function initScreenCaptureForWindow(window: BrowserWindow, options?: Init const { context } = createContext(ipcMain, window, { onlySameWindow: true }) const session = sessionModule.defaultSession + defineInvokeHandler(context, screenCaptureCheckMacOSPermission, async () => checkMacOSScreenCapturePermission()) + defineInvokeHandler(context, screenCaptureRequestMacOSPermission, async () => requestMacOSScreenCapturePermission()) + defineInvokeHandler(context, screenCaptureGetSources, async (sourcesOptions) => { const sources = await desktopCapturer.getSources(sourcesOptions) return sources.map(source => toSerializableDesktopCapturerSource(source)) diff --git a/packages/electron-screen-capture/src/main/utils.ts b/packages/electron-screen-capture/src/main/utils.ts index 0b368a076..37f4591fb 100644 --- a/packages/electron-screen-capture/src/main/utils.ts +++ b/packages/electron-screen-capture/src/main/utils.ts @@ -2,6 +2,10 @@ import type { DesktopCapturerSource } from 'electron' import type { SerializableDesktopCapturerSource } from '..' +import process from 'node:process' + +import { shell, systemPreferences } from 'electron' + /** * Serializes a DesktopCapturerSource to a format that can be sent over IPC. * @@ -24,3 +28,19 @@ export function toSerializableDesktopCapturerSource(source: DesktopCapturerSourc thumbnail: source.thumbnail != null ? new Uint8Array(source.thumbnail.toJPEG(90).buffer) : undefined, } } + +export function checkMacOSScreenCapturePermission(): ReturnType { + if (process.platform !== 'darwin') { + throw new Error('checkMacOSScreenCapturePermission is only available on macOS (darwin)') + } + + return systemPreferences.getMediaAccessStatus('screen') +} + +export function requestMacOSScreenCapturePermission(): void { + if (process.platform !== 'darwin') { + throw new Error('requestMacOSScreenCapturePermission is only available on macOS (darwin)') + } + + shell.openExternal('x-apple.systempreferences:com.apple.preference.security?Privacy_ScreenCapture') +} diff --git a/packages/electron-screen-capture/src/renderer.ts b/packages/electron-screen-capture/src/renderer.ts index 311d3cf67..d04abc3b0 100644 --- a/packages/electron-screen-capture/src/renderer.ts +++ b/packages/electron-screen-capture/src/renderer.ts @@ -5,7 +5,7 @@ import type { ScreenCaptureSetSourceRequest, SerializableDesktopCapturerSource } import { defineInvoke } from '@moeru/eventa' -import { screenCaptureGetSources, screenCaptureResetSource, screenCaptureSetSourceEx } from '.' +import { screenCaptureCheckMacOSPermission, screenCaptureGetSources, screenCaptureRequestMacOSPermission, screenCaptureResetSource, screenCaptureSetSourceEx } from '.' export interface SourceOptionsWithRequest { sourcesOptions?: SourcesOptions @@ -17,6 +17,9 @@ export function setupElectronScreenCapture(context: ReturnType) { const context = createContext(ipcRenderer).context @@ -17,8 +17,11 @@ export function useElectronScreenCapture(ipcRenderer: IpcRenderer, sourcesOption const setSource = defineInvoke(context, screenCaptureSetSourceEx) const resetSource = defineInvoke(context, screenCaptureResetSource) + const checkMacOSPermission = defineInvoke(context, screenCaptureCheckMacOSPermission) + const requestMacOSPermission = defineInvoke(context, screenCaptureRequestMacOSPermission) + async function getSources() { - return invokeGetSources(toValue(sourcesOptions)) + return invokeGetSources(toRaw(toValue(sourcesOptions))) } async function selectWithSource( @@ -32,7 +35,7 @@ export function useElectronScreenCapture(ipcRenderer: IpcRenderer, sourcesOption let handle: string | undefined try { handle = await setSource({ - options: toValue(sourcesOptions), + options: toRaw(toValue(sourcesOptions)), sourceId, timeout: request?.timeout, }) @@ -45,5 +48,12 @@ export function useElectronScreenCapture(ipcRenderer: IpcRenderer, sourcesOption } } - return { getSources, setSource, resetSource, selectWithSource } + return { + getSources, + setSource, + resetSource, + selectWithSource, + checkMacOSPermission, + requestMacOSPermission, + } } diff --git a/packages/i18n/src/locales/en/tamagotchi/settings.yaml b/packages/i18n/src/locales/en/tamagotchi/settings.yaml index 0b74ec541..66338e09f 100644 --- a/packages/i18n/src/locales/en/tamagotchi/settings.yaml +++ b/packages/i18n/src/locales/en/tamagotchi/settings.yaml @@ -42,3 +42,14 @@ devtools: description: Stress markdown parsing and rendering with heavy chat payloads widgets-calling: title: Widget Calling + +screen-capture: + permissions-prompt: + title: Screen Capture + description: To capture screens or windows, screen and system audio recording permissions are required. + open-preferences: Open System Preferences + dismiss: Dismiss + instructions: + step-1: Open "System Preferences", select "Privacy & Security" from the sidebar. + step-2: In the "Screen & System Audio Recording" and "System Audio Recording Only" sections, turn on the switches for AIRI. + step-2-note: If AIRI is not listed, please add it manually by clicking the "+" button and selecting AIRI from the Applications folder.