Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[wip] feat(screensharing): use setDisplayMediaRequestHandler #1022

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 31 additions & 1 deletion src/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -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')
Expand Down Expand Up @@ -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') {
Expand Down Expand Up @@ -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}
Expand Down
3 changes: 3 additions & 0 deletions src/preload.js
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,9 @@ const TALK_DESKTOP = {
* @return {Promise<void>}
*/
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
Expand Down
12 changes: 11 additions & 1 deletion src/talk/renderer/AppGetDesktopMediaSource.vue
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -36,5 +43,8 @@ defineExpose({ promptDesktopMediaSource })
</script>

<template>
<DesktopMediaSourceDialog v-if="showDialog" @submit="handlePrompt($event)" @cancel="handlePrompt('')" />
<DesktopMediaSourceDialog v-if="showDialog"
:sources="sources"
@submit="handlePrompt($event)"
@cancel="handlePrompt('')" />
</template>
11 changes: 9 additions & 2 deletions src/talk/renderer/components/DesktopMediaSourceDialog.vue
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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')
}

Expand Down
6 changes: 4 additions & 2 deletions src/talk/renderer/getDesktopMediaSource.js
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down
6 changes: 6 additions & 0 deletions src/talk/renderer/talk.main.js
Original file line number Diff line number Diff line change
Expand Up @@ -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)
})