feat(stage-tamagotchi,electron-screen-capture,i18n): check for screen capture permissions
This commit is contained in:
@@ -0,0 +1,111 @@
|
||||
<script setup lang="ts">
|
||||
import type { SourcesOptions } from 'electron'
|
||||
|
||||
import { useElectronScreenCapture } from '@proj-airi/electron-screen-capture/vue'
|
||||
import { Button } from '@proj-airi/ui'
|
||||
import { useWindowFocus } from '@vueuse/core'
|
||||
import { DialogContent, DialogDescription, DialogOverlay, DialogPortal, DialogRoot, DialogTitle } from 'reka-ui'
|
||||
import { onMounted, ref, toRef, watch } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
const props = defineProps<{
|
||||
sourcesOptions: SourcesOptions
|
||||
}>()
|
||||
|
||||
const sourcesOptions = toRef(props, 'sourcesOptions')
|
||||
|
||||
const hasPermissions = ref(false)
|
||||
const showDialog = ref(false)
|
||||
|
||||
const { t } = useI18n()
|
||||
const {
|
||||
getSources,
|
||||
setSource,
|
||||
selectWithSource,
|
||||
checkMacOSPermission,
|
||||
requestMacOSPermission,
|
||||
} = useElectronScreenCapture(window.electron.ipcRenderer, sourcesOptions)
|
||||
const focused = useWindowFocus()
|
||||
|
||||
async function checkPermissions() {
|
||||
if (window.platform === 'darwin') {
|
||||
const status = await checkMacOSPermission()
|
||||
hasPermissions.value = status === 'granted'
|
||||
}
|
||||
else {
|
||||
hasPermissions.value = true
|
||||
}
|
||||
if (!hasPermissions.value) {
|
||||
showDialog.value = true
|
||||
}
|
||||
}
|
||||
|
||||
async function requestPermission() {
|
||||
if (window.platform === 'darwin') {
|
||||
await requestMacOSPermission()
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
await checkPermissions()
|
||||
})
|
||||
|
||||
watch(focused, async (isFocused) => {
|
||||
if (isFocused) {
|
||||
await checkPermissions()
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<slot
|
||||
v-bind="{
|
||||
getSources,
|
||||
setSource,
|
||||
selectWithSource,
|
||||
hasPermissions,
|
||||
checkPermissions,
|
||||
requestPermission,
|
||||
}"
|
||||
/>
|
||||
|
||||
<DialogRoot :open="showDialog">
|
||||
<DialogPortal>
|
||||
<DialogOverlay class="fixed inset-0 z-9999 bg-black/50 backdrop-blur-sm data-[state=closed]:animate-fadeOut data-[state=open]:animate-fadeIn" />
|
||||
<DialogContent flex="~ col items-start gap-4" class="fixed left-1/2 top-1/2 z-9999 max-h-full max-w-2xl w-[92dvw] transform overflow-y-scroll rounded-2xl bg-white p-6 shadow-xl outline-none backdrop-blur-md scrollbar-none -translate-x-1/2 -translate-y-1/2 data-[state=closed]:animate-contentHide data-[state=open]:animate-contentShow dark:bg-neutral-900">
|
||||
<DialogTitle class="m-0 text-lg font-semibold">
|
||||
{{ t('tamagotchi.settings.screen-capture.permissions-prompt.title') }}
|
||||
</DialogTitle>
|
||||
|
||||
<DialogDescription>
|
||||
{{ t('tamagotchi.settings.screen-capture.permissions-prompt.description') }}
|
||||
|
||||
<ol mt-4 list-decimal pl-5 text-sm>
|
||||
<li mb-1>
|
||||
{{ t('tamagotchi.settings.screen-capture.permissions-prompt.instructions.step-1') }}
|
||||
</li>
|
||||
<li>
|
||||
{{ t('tamagotchi.settings.screen-capture.permissions-prompt.instructions.step-2') }}
|
||||
<br>
|
||||
<span class="text-neutral-500">
|
||||
{{ t('tamagotchi.settings.screen-capture.permissions-prompt.instructions.step-2-note') }}
|
||||
</span>
|
||||
</li>
|
||||
</ol>
|
||||
</DialogDescription>
|
||||
|
||||
<div flex="~ row gap-2 mt-4 justify-end" w-full>
|
||||
<Button
|
||||
variant="secondary"
|
||||
@click="showDialog = false"
|
||||
>
|
||||
{{ t('tamagotchi.settings.screen-capture.permissions-prompt.dismiss') }}
|
||||
</Button>
|
||||
<Button @click="requestMacOSPermission()">
|
||||
{{ t('tamagotchi.settings.screen-capture.permissions-prompt.open-preferences') }}
|
||||
</Button>
|
||||
</div>
|
||||
</DialogContent>
|
||||
</DialogPortal>
|
||||
</DialogRoot>
|
||||
</template>
|
||||
@@ -1,29 +1,39 @@
|
||||
<script setup lang="ts">
|
||||
import type { SerializableDesktopCapturerSource } from '@proj-airi/electron-screen-capture'
|
||||
import type { SourcesOptions } from 'electron'
|
||||
|
||||
import { useElectronScreenCapture } from '@proj-airi/electron-screen-capture/vue'
|
||||
import { Button } from '@proj-airi/ui'
|
||||
import { onBeforeUnmount, onMounted, ref } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
const sources = ref<ScreenCaptureSource[]>([])
|
||||
const isRefetching = ref(false)
|
||||
const activeStreams = ref<MediaStream[]>([])
|
||||
import WithScreenCapture from '../../components/WithScreenCapture.vue'
|
||||
|
||||
interface ScreenCaptureSource extends SerializableDesktopCapturerSource {
|
||||
appIconURL?: string
|
||||
thumbnailURL?: string
|
||||
}
|
||||
|
||||
const { getSources, selectWithSource } = useElectronScreenCapture(window.electron.ipcRenderer, {
|
||||
const sources = ref<ScreenCaptureSource[]>([])
|
||||
const isRefetching = ref(false)
|
||||
const activeStreams = ref<MediaStream[]>([])
|
||||
|
||||
const sourcesOptions = ref<SourcesOptions>({
|
||||
types: ['screen', 'window'],
|
||||
fetchWindowIcons: true,
|
||||
})
|
||||
|
||||
const { t } = useI18n()
|
||||
const {
|
||||
getSources,
|
||||
selectWithSource,
|
||||
} = useElectronScreenCapture(window.electron.ipcRenderer, sourcesOptions)
|
||||
|
||||
function toLocalArrayBuffer(bytes: Uint8Array) {
|
||||
if (bytes.buffer instanceof SharedArrayBuffer) {
|
||||
if (typeof SharedArrayBuffer !== 'undefined' && bytes.buffer instanceof SharedArrayBuffer) {
|
||||
return bytes.slice().buffer
|
||||
}
|
||||
return bytes.buffer
|
||||
return bytes.buffer as ArrayBuffer
|
||||
}
|
||||
|
||||
function toObjectUrl(bytes: Uint8Array, mime: string) {
|
||||
@@ -80,6 +90,9 @@ async function refetchSources() {
|
||||
thumbnailURL: source.thumbnail && source.thumbnail.length > 0 ? toObjectUrl(source.thumbnail, 'image/jpeg') : undefined,
|
||||
}))
|
||||
}
|
||||
catch (err) {
|
||||
console.error('Error fetching sources:', err)
|
||||
}
|
||||
finally {
|
||||
isRefetching.value = false
|
||||
}
|
||||
@@ -100,133 +113,146 @@ onBeforeUnmount(() => {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
flex="~ col gap-4 items-start" w-full
|
||||
text="neutral-500 dark:neutral-400"
|
||||
>
|
||||
<div
|
||||
v-if="activeStreams.length > 0"
|
||||
bg="primary-300/10"
|
||||
b="2 solid primary-400/70"
|
||||
w-full overflow-hidden rounded-2xl p-3
|
||||
flex="~ col gap-2"
|
||||
>
|
||||
<div flex="~ row items-center gap-2">
|
||||
<div class="i-solar:videocamera-record-line-duotone" />
|
||||
<div>Capturing</div>
|
||||
</div>
|
||||
<WithScreenCapture :sources-options="sourcesOptions">
|
||||
<template #default="{ hasPermissions, requestPermission }">
|
||||
<div
|
||||
flex="~ row items-center gap-3"
|
||||
w-full overflow-x-auto
|
||||
v-if="hasPermissions"
|
||||
flex="~ col gap-4 items-start" w-full
|
||||
text="neutral-500 dark:neutral-400"
|
||||
>
|
||||
<div
|
||||
v-for="stream in activeStreams" :key="stream.id"
|
||||
relative overflow-hidden rounded-lg
|
||||
v-if="activeStreams.length > 0"
|
||||
bg="primary-300/10"
|
||||
b="2 solid primary-400/70"
|
||||
w-full overflow-hidden rounded-2xl p-3
|
||||
flex="~ col gap-2"
|
||||
>
|
||||
<div
|
||||
flex="~ col items-center justify-center gap-1"
|
||||
absolute right-0 top-0 z-10 h-full w-full cursor-pointer
|
||||
rounded-lg op-0 backdrop-blur-sm hover:op-100
|
||||
transition="all duration-200"
|
||||
text="light"
|
||||
bg="black/30"
|
||||
@click="stopStream(stream)"
|
||||
>
|
||||
<div class="i-solar:stop-line-duotone" />
|
||||
<div text-sm>
|
||||
Stop
|
||||
</div>
|
||||
</div>
|
||||
<video
|
||||
autoplay
|
||||
muted
|
||||
playsinline
|
||||
:srcObject="stream"
|
||||
h-140px
|
||||
w-auto
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
flex="~ col gap-3"
|
||||
w-full pb-6
|
||||
>
|
||||
<div flex="~ row items-center justify-between" w-full>
|
||||
<div>{{ sources.length }} source(s)</div>
|
||||
<Button
|
||||
:label="isRefetching ? 'Refetching...' : 'Refetch'"
|
||||
icon="i-solar:refresh-line-duotone"
|
||||
size="sm"
|
||||
:disabled="isRefetching"
|
||||
@click="refetchSources()"
|
||||
/>
|
||||
</div>
|
||||
<div grid="~ cols-1 sm:cols-2 md:cols-3 lg:cols-4 xl:cols-5 gap-3">
|
||||
<div
|
||||
v-for="source in sources"
|
||||
:key="source.id"
|
||||
flex="~ col justify-between gap-3"
|
||||
w-full cursor-pointer rounded-2xl p-3
|
||||
transition="all duration-200"
|
||||
border="2 solid neutral-200/60 dark:neutral-800/10 hover:primary-400/70"
|
||||
@click="startCapture(source)"
|
||||
>
|
||||
<div flex="~ col items-start w-full">
|
||||
<div flex="~ row items-center gap-1">
|
||||
<div class="h-16px w-16px">
|
||||
<img
|
||||
v-if="source.appIconURL"
|
||||
:src="source.appIconURL"
|
||||
:alt="source.id.startsWith('screen:') ? 'Screen Icon' : 'Window Icon'"
|
||||
class="h-full w-full shrink-0"
|
||||
>
|
||||
<div
|
||||
v-else-if="source.id.startsWith('screen:')"
|
||||
h-full w-full
|
||||
class="i-solar:screencast-2-line-duotone"
|
||||
/>
|
||||
<div
|
||||
v-else
|
||||
h-full w-full
|
||||
class="i-solar:window-frame-line-duotone"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div text-sm>
|
||||
{{ source.id.startsWith('screen:') ? 'Screen' : 'Window' }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div text-ellipsis break-all>
|
||||
{{ source.name }}
|
||||
</div>
|
||||
<div text-sm font-mono text="neutral-400 dark:neutral-600">
|
||||
{{ source.id }}
|
||||
</div>
|
||||
<div flex="~ row items-center gap-2">
|
||||
<div class="i-solar:videocamera-record-line-duotone" />
|
||||
<div>Capturing</div>
|
||||
</div>
|
||||
<div
|
||||
h-200px w-full overflow-hidden rounded-2xl bg-black
|
||||
flex="~ items-center justify-center shrink-0"
|
||||
flex="~ row items-center gap-3"
|
||||
w-full overflow-x-auto
|
||||
>
|
||||
<img
|
||||
v-if="source.thumbnailURL"
|
||||
:src="source.thumbnailURL"
|
||||
alt="Thumbnail"
|
||||
h-full
|
||||
w-full object-contain
|
||||
>
|
||||
<div
|
||||
v-else
|
||||
class="i-solar:forbidden-circle-line-duotone"
|
||||
h-10 w-10 bg-light
|
||||
v-for="stream in activeStreams" :key="stream.id"
|
||||
relative overflow-hidden rounded-lg
|
||||
>
|
||||
<div
|
||||
flex="~ col items-center justify-center gap-1"
|
||||
absolute right-0 top-0 z-10 h-full w-full cursor-pointer
|
||||
rounded-lg op-0 backdrop-blur-sm hover:op-100
|
||||
transition="all duration-200"
|
||||
text="light"
|
||||
bg="black/30"
|
||||
@click="stopStream(stream)"
|
||||
>
|
||||
<div class="i-solar:stop-line-duotone" />
|
||||
<div text-sm>
|
||||
Stop
|
||||
</div>
|
||||
</div>
|
||||
<video
|
||||
autoplay
|
||||
muted
|
||||
playsinline
|
||||
:srcObject="stream"
|
||||
h-140px
|
||||
w-auto
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
flex="~ col gap-3"
|
||||
w-full pb-6
|
||||
>
|
||||
<div flex="~ row items-center justify-between" w-full>
|
||||
<div>{{ sources.length }} source(s)</div>
|
||||
<Button
|
||||
:label="isRefetching ? 'Refetching...' : 'Refetch'"
|
||||
icon="i-solar:refresh-line-duotone"
|
||||
size="sm"
|
||||
:disabled="isRefetching"
|
||||
@click="refetchSources()"
|
||||
/>
|
||||
</div>
|
||||
<div grid="~ cols-1 sm:cols-2 md:cols-3 lg:cols-4 xl:cols-5 gap-3">
|
||||
<div
|
||||
v-for="source in sources"
|
||||
:key="source.id"
|
||||
flex="~ col justify-between gap-3"
|
||||
w-full cursor-pointer rounded-2xl p-3
|
||||
transition="all duration-200"
|
||||
border="2 solid neutral-200/60 dark:neutral-800/10 hover:primary-400/70"
|
||||
@click="startCapture(source)"
|
||||
>
|
||||
<div flex="~ col items-start w-full">
|
||||
<div flex="~ row items-center gap-1">
|
||||
<div class="h-16px w-16px">
|
||||
<img
|
||||
v-if="source.appIconURL"
|
||||
:src="source.appIconURL"
|
||||
:alt="source.id.startsWith('screen:') ? 'Screen Icon' : 'Window Icon'"
|
||||
class="h-full w-full shrink-0"
|
||||
>
|
||||
<div
|
||||
v-else-if="source.id.startsWith('screen:')"
|
||||
h-full w-full
|
||||
class="i-solar:screencast-2-line-duotone"
|
||||
/>
|
||||
<div
|
||||
v-else
|
||||
h-full w-full
|
||||
class="i-solar:window-frame-line-duotone"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div text-sm>
|
||||
{{ source.id.startsWith('screen:') ? 'Screen' : 'Window' }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div text-ellipsis break-all>
|
||||
{{ source.name }}
|
||||
</div>
|
||||
<div text-sm font-mono text="neutral-400 dark:neutral-600">
|
||||
{{ source.id }}
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
h-200px w-full overflow-hidden rounded-2xl bg-black
|
||||
flex="~ items-center justify-center shrink-0"
|
||||
>
|
||||
<img
|
||||
v-if="source.thumbnailURL"
|
||||
:src="source.thumbnailURL"
|
||||
alt="Thumbnail"
|
||||
h-full
|
||||
w-full object-contain
|
||||
>
|
||||
<div
|
||||
v-else
|
||||
class="i-solar:forbidden-circle-line-duotone"
|
||||
h-10 w-10 bg-light
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else flex="~ col gap-4 items-center justify-center" h-full p-6>
|
||||
<div>
|
||||
{{ t('tamagotchi.settings.screen-capture.permissions-prompt.description') }}
|
||||
</div>
|
||||
<Button @click="requestPermission()">
|
||||
{{ t('tamagotchi.settings.screen-capture.permissions-prompt.open-preferences') }}
|
||||
</Button>
|
||||
</div>
|
||||
</template>
|
||||
</WithScreenCapture>
|
||||
</template>
|
||||
|
||||
<route lang="yaml">
|
||||
|
||||
@@ -273,6 +273,7 @@ words:
|
||||
- sumimakito
|
||||
- supergroup
|
||||
- superjson
|
||||
- systempreferences
|
||||
- tamagotchi
|
||||
- tauri
|
||||
- taze
|
||||
|
||||
@@ -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<SerializableDesktopCapturerSource[], SourcesOptions>('eventa:invoke:electron:screen-capture:get-sources')
|
||||
export const screenCaptureSetSourceEx = defineInvokeEventa<any, ScreenCaptureSetSourceRequest>('eventa:invoke:electron:screen-capture:set-source')
|
||||
export const screenCaptureResetSource = defineInvokeEventa<any, string>('eventa:invoke:electron:screen-capture:reset-source')
|
||||
export const screenCaptureSetSourceEx = defineInvokeEventa<string, ScreenCaptureSetSourceRequest>('eventa:invoke:electron:screen-capture:set-source')
|
||||
export const screenCaptureResetSource = defineInvokeEventa<void, string>('eventa:invoke:electron:screen-capture:reset-source')
|
||||
|
||||
export const screenCaptureCheckMacOSPermission = defineInvokeEventa<ReturnType<typeof systemPreferences.getMediaAccessStatus>, never>('eventa:invoke:electron:screen-capture:check-macos-permission')
|
||||
export const screenCaptureRequestMacOSPermission = defineInvokeEventa<void, never>('eventa:invoke:electron:screen-capture:request-macos-permission')
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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<typeof systemPreferences.getMediaAccessStatus> {
|
||||
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')
|
||||
}
|
||||
|
||||
@@ -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<typeof createCont
|
||||
const setSource = defineInvoke(context, screenCaptureSetSourceEx)
|
||||
const resetSource = defineInvoke(context, screenCaptureResetSource)
|
||||
|
||||
const checkMacOSPermission = defineInvoke(context, screenCaptureCheckMacOSPermission)
|
||||
const requestMacOSPermission = defineInvoke(context, screenCaptureRequestMacOSPermission)
|
||||
|
||||
async function getSources(sourcesOptions: SourcesOptions) {
|
||||
return invokeGetSources(sourcesOptions)
|
||||
}
|
||||
@@ -45,5 +48,12 @@ export function setupElectronScreenCapture(context: ReturnType<typeof createCont
|
||||
}
|
||||
}
|
||||
|
||||
return { getSources, setSource, selectWithSource, resetSource }
|
||||
return {
|
||||
getSources,
|
||||
setSource,
|
||||
selectWithSource,
|
||||
resetSource,
|
||||
checkMacOSPermission,
|
||||
requestMacOSPermission,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,9 +6,9 @@ import type { ScreenCaptureSetSourceRequest, SerializableDesktopCapturerSource }
|
||||
|
||||
import { defineInvoke } from '@moeru/eventa'
|
||||
import { createContext } from '@moeru/eventa/adapters/electron/renderer'
|
||||
import { toValue } from 'vue'
|
||||
import { toRaw, toValue } from 'vue'
|
||||
|
||||
import { screenCaptureGetSources, screenCaptureResetSource, screenCaptureSetSourceEx } from '..'
|
||||
import { screenCaptureCheckMacOSPermission, screenCaptureGetSources, screenCaptureRequestMacOSPermission, screenCaptureResetSource, screenCaptureSetSourceEx } from '..'
|
||||
|
||||
export function useElectronScreenCapture(ipcRenderer: IpcRenderer, sourcesOptions: MaybeRefOrGetter<SourcesOptions>) {
|
||||
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<R>(
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
|
||||
Reference in New Issue
Block a user