From ce5e393f4bc5d29d9ca31aa51b94073d53b02f00 Mon Sep 17 00:00:00 2001 From: "Grigorii K. Shartsev" Date: Fri, 5 Apr 2024 19:04:10 +0200 Subject: [PATCH] [wip] feat(screensharing): use setDisplayMediaRequestHandler Signed-off-by: Grigorii K. Shartsev --- src/main.js | 32 ++++++++++++++++++- src/preload.js | 3 ++ .../renderer/AppGetDesktopMediaSource.vue | 12 ++++++- .../components/DesktopMediaSourceDialog.vue | 11 +++++-- src/talk/renderer/getDesktopMediaSource.js | 6 ++-- src/talk/renderer/talk.main.js | 6 ++++ 6 files changed, 64 insertions(+), 6 deletions(-) diff --git a/src/main.js b/src/main.js index a7b7131f..cda1e57d 100644 --- a/src/main.js +++ b/src/main.js @@ -5,7 +5,7 @@ const path = require('node:path') const { spawn } = require('node:child_process') -const { app, dialog, ipcMain, desktopCapturer, systemPreferences, shell } = require('electron') +const { app, dialog, ipcMain, desktopCapturer, systemPreferences, shell, session } = require('electron') const { setupMenu } = require('./app/app.menu.js') const { setupReleaseNotificationScheduler } = require('./app/githubReleaseNotification.service.js') const { enableWebRequestInterceptor, disableWebRequestInterceptor } = require('./app/webRequestInterceptor.js') @@ -80,6 +80,7 @@ ipcMain.on('app:relaunch', () => { }) ipcMain.handle('app:config:get', (event, key) => getAppConfig(key)) ipcMain.handle('app:config:set', (event, key, value) => setAppConfig(key, value)) + ipcMain.handle('app:getDesktopCapturerSources', async () => { // macOS 10.15 Catalina or higher requires consent for screen access if (isMac && systemPreferences.getMediaAccessStatus('screen') !== 'granted') { @@ -140,6 +141,35 @@ app.whenReady().then(async () => { console.log() } + session.defaultSession.setDisplayMediaRequestHandler(async (request, callback) => { + if (isMac && systemPreferences.getMediaAccessStatus('screen') !== 'granted') { + // Open System Preferences to allow screen recording + await shell.openExternal('x-apple.systempreferences:com.apple.preference.security?Privacy_ScreenCapture') + // We cannot detect that the user has granted access, so return no sources + // The user will have to try again after granting access + return null + } + + // We cannot show live previews on Wayland, so we show thumbnails + const thumbnailWidth = isWayland ? 320 : 0 + + const sources = await desktopCapturer.getSources({ + types: ['screen', 'window'], + fetchWindowIcons: true, + thumbnailSize: { + width: thumbnailWidth, + height: thumbnailWidth * 9 / 16, + }, + }) + + sources.map((source) => ({ + id: source.id, + name: source.name, + icon: source.appIcon && !source.appIcon.isEmpty() ? source.appIcon.toDataURL() : null, + thumbnail: source.thumbnail && !source.thumbnail.isEmpty() ? source.thumbnail.toDataURL() : null, + })) + }, { useSystemPicker: true }) + // TODO: add windows manager /** * @type {import('electron').BrowserWindow} diff --git a/src/preload.js b/src/preload.js index 77c4cceb..3d0396fc 100644 --- a/src/preload.js +++ b/src/preload.js @@ -165,6 +165,9 @@ const TALK_DESKTOP = { * @return {Promise} */ showUpgrade: () => ipcRenderer.invoke('upgrade:show'), + + onPromptDesktopCaptureSource: (callback) => ipcRenderer.on('talk:onPromptDesktopCaptureSource', (_event, sources) => callback(sources)), + desktopCaptureSourceSelected: (source) => ipcRenderer.send('talk:desktopCaptureSourceSelected', source), } // Set global window.TALK_DESKTOP diff --git a/src/talk/renderer/AppGetDesktopMediaSource.vue b/src/talk/renderer/AppGetDesktopMediaSource.vue index ce408048..7b752797 100644 --- a/src/talk/renderer/AppGetDesktopMediaSource.vue +++ b/src/talk/renderer/AppGetDesktopMediaSource.vue @@ -8,6 +8,13 @@ import { ref } from 'vue' import DesktopMediaSourceDialog from './components/DesktopMediaSourceDialog.vue' +const props = defineProps({ + sources: { + type: Array, + required: true, + }, +}) + const showDialog = ref(false) let promiseWithResolvers = null @@ -36,5 +43,8 @@ defineExpose({ promptDesktopMediaSource }) diff --git a/src/talk/renderer/components/DesktopMediaSourceDialog.vue b/src/talk/renderer/components/DesktopMediaSourceDialog.vue index ec2ce14d..db35f055 100644 --- a/src/talk/renderer/components/DesktopMediaSourceDialog.vue +++ b/src/talk/renderer/components/DesktopMediaSourceDialog.vue @@ -16,6 +16,13 @@ import NcLoadingIcon from '@nextcloud/vue/dist/Components/NcLoadingIcon.js' import { translate as t } from '@nextcloud/l10n' import DesktopMediaSourcePreview from './DesktopMediaSourcePreview.vue' +const props = defineProps({ + sources: { + type: Array, + required: true, + }, +}) + const emit = defineEmits(['submit', 'cancel']) const RE_REQUEST_SOURCES_TIMEOUT = 1000 @@ -47,10 +54,10 @@ const dialogButtons = computed(() => [ ]) const requestDesktopCapturerSources = async () => { - sources.value = await window.TALK_DESKTOP.getDesktopCapturerSources() + // props.sources = await window.TALK_DESKTOP.getDesktopCapturerSources() // There is no source. Probably the user hasn't granted the permission. - if (!sources.value) { + if (!props.sources) { emit('cancel') } diff --git a/src/talk/renderer/getDesktopMediaSource.js b/src/talk/renderer/getDesktopMediaSource.js index 04e8d6e9..cff05f74 100644 --- a/src/talk/renderer/getDesktopMediaSource.js +++ b/src/talk/renderer/getDesktopMediaSource.js @@ -13,12 +13,14 @@ let appGetDesktopMediaSourceInstance /** * Prompt user to select a desktop media source to share and return the selected sourceId or an empty string if canceled * + * @param sources * @return {Promise<{ sourceId: string }>} sourceId of the selected mediaSource or an empty string if canceled */ -export async function getDesktopMediaSource() { +export async function getDesktopMediaSource(sources) { if (!appGetDesktopMediaSourceInstance) { const container = document.body.appendChild(document.createElement('div')) - appGetDesktopMediaSourceInstance = new Vue(AppGetDesktopMediaSource).$mount(container) + const AppGetDesktopMediaSourceCreator = Vue.extend(AppGetDesktopMediaSource) + appGetDesktopMediaSourceInstance = new AppGetDesktopMediaSourceCreator({ propsData: { sources } }).$mount(container) } return appGetDesktopMediaSourceInstance.promptDesktopMediaSource() diff --git a/src/talk/renderer/talk.main.js b/src/talk/renderer/talk.main.js index a73e7d14..8fc5cba3 100644 --- a/src/talk/renderer/talk.main.js +++ b/src/talk/renderer/talk.main.js @@ -39,3 +39,9 @@ registerTalkDesktopSettingsSection() await import('./notifications/notifications.store.js') subscribeBroadcast('talk:conversation:open', ({ token, directCall }) => openConversation(token, { directCall })) + +window.TALK_DESKTOP.onPromptDesktopCaptureSource(async (sources) => { + const { sourceId } = await getDesktopMediaSource(sources) + console.log('Selected sourceId:', sourceId) + window.TALK_DESKTOP.desktopCaptureSourceSelected(sourceId) +})