From 6ac7cb694e820fb4cdadbecbcce4a089a840f21b Mon Sep 17 00:00:00 2001 From: "Grigorii K. Shartsev" Date: Fri, 5 Apr 2024 22:20:06 +0200 Subject: [PATCH] fix: screen sharing on Wayland Signed-off-by: Grigorii K. Shartsev --- src/main.js | 14 ++++- src/shared/os.utils.js | 12 +++++ .../components/DesktopMediaSourceDialog.vue | 51 +++++++++++++++---- 3 files changed, 65 insertions(+), 12 deletions(-) diff --git a/src/main.js b/src/main.js index dc74c89c..6f26df6a 100644 --- a/src/main.js +++ b/src/main.js @@ -92,11 +92,21 @@ ipcMain.handle('app:getDesktopCapturerSources', async () => { return null } - const sources = await desktopCapturer.getSources({ types: ['window', 'screen'], fetchWindowIcons: true }) + const sources = await desktopCapturer.getSources({ + types: ['screen', 'window'], + fetchWindowIcons: true, + thumbnailSize: { + // 16:9 aspect ratio + width: 320, + height: 180, + }, + }) + return sources.map((source) => ({ id: source.id, name: source.name, - icon: source.appIcon?.toDataURL(), + icon: source.appIcon && !source.appIcon.isEmpty() ? source.appIcon.toDataURL() : null, + thumbnail: source.thumbnail && !source.thumbnail.isEmpty() ? source.thumbnail.toDataURL() : null, })) }) diff --git a/src/shared/os.utils.js b/src/shared/os.utils.js index 2cf17019..7d50a16c 100644 --- a/src/shared/os.utils.js +++ b/src/shared/os.utils.js @@ -76,11 +76,21 @@ function isWindows() { return os.type() === 'Windows_NT' } +/** + * Is it Linux with Wayland window communication protocol? + * @return {boolean} + */ +function isWayland() { + // TODO: is it better than checking for XDG_SESSION_TYPE === 'wayland'? + return !!process.env.WAYLAND_DISPLAY +} + /** * @typedef OsVersion * @property {boolean} isLinux - Is Linux? * @property {boolean} isMac - Is Mac? * @property {boolean} isWindows - Is Windows? + * @property {boolean} isWayland - Is Linux with Wayland window communication protocol? * @property {string} version - Full string representation of OS version */ @@ -94,6 +104,7 @@ function getOs() { isLinux: isLinux(), isMac: isMac(), isWindows: isWindows(), + isWayland: isWayland(), version: getOsVersion(), } } @@ -104,5 +115,6 @@ module.exports = { isLinux, isMac, isWindows, + isWayland, getOs, } diff --git a/src/talk/renderer/components/DesktopMediaSourceDialog.vue b/src/talk/renderer/components/DesktopMediaSourceDialog.vue index 3f376660..5e55245d 100644 --- a/src/talk/renderer/components/DesktopMediaSourceDialog.vue +++ b/src/talk/renderer/components/DesktopMediaSourceDialog.vue @@ -24,6 +24,7 @@ import { computed, nextTick, onBeforeUnmount, onMounted, ref } from 'vue' import MdiCancel from '@mdi/svg/svg/cancel.svg?raw' import MdiMonitorShare from '@mdi/svg/svg/monitor-share.svg?raw' +import MdiApplicationOutline from 'vue-material-design-icons/ApplicationOutline.vue' import MdiMonitor from 'vue-material-design-icons/Monitor.vue' import MdiMonitorSpeaker from 'vue-material-design-icons/MonitorSpeaker.vue' @@ -35,6 +36,11 @@ import { translate as t } from '@nextcloud/l10n' const emit = defineEmits(['submit', 'cancel']) +// On Wayland getting each stream for the live preview requests user to select the source via system dialog again +// Instead - show static images. +// See: https://github.com/electron/electron/issues/27732 +const previewType = window.OS.isWayland ? 'thumbnail' : 'live' + const selectedSourceId = ref(null) const sources = ref(null) const videoElements = {} @@ -100,13 +106,23 @@ const requestDesktopCapturerSources = async () => { emit('cancel') } - const hasMultipleScreens = sources.value.filter((source) => source.id.startsWith('screen:')).length > 1 + // On Wayland we don't manually provide the source stream. It is covered by Wayland and custom id is not supported + if (!window.OS.isWayland) { + // There is no sourceId for the entire desktop with all the screens and audio in Electron. + // But it is possible to capture it. "entire-desktop:0:0" is a custom sourceId for this specific case. + const hasMultipleScreens = sources.value.filter((source) => source.id.startsWith('screen:')).length > 1 + sources.value.unshift({ + id: 'entire-desktop:0:0', + name: hasMultipleScreens ? t('talk_desktop', 'All screens with audio') : t('talk_desktop', 'Entire screen with audio'), + }) + } - // There is no sourceId for the entire desktop in Electron - create a custom one - sources.value.unshift({ - id: 'entire-desktop:0:0', - name: hasMultipleScreens ? t('talk_desktop', 'All screens with audio') : t('talk_desktop', 'Entire screen with audio'), - }) + // On Wayland there might be no name from the desktopCapturer + if (window.OS.isWayland) { + for (const source of sources.value) { + source.name ||= t('talk_desktop', 'Selected screen or window') + } + } // Preselect the first media source if any selectedSourceId.value = sources.value[0]?.id @@ -116,16 +132,23 @@ const setVideoSources = async () => Promise.allSettled(sources.value.map(async ( videoElements[source.id].srcObject = await getStreamForMediaSource(source.id) })) -onMounted(async () => { - await requestDesktopCapturerSources() +const showLivePreviews = async () => { // Wait for video elements to be mounted await nextTick() // Set streams for all ids await setVideoSources() +} + +onMounted(async () => { + await requestDesktopCapturerSources() + + if (previewType === 'live') { + await showLivePreviews() + } }) onBeforeUnmount(() => { - if (!sources.value) { + if (!sources.value || previewType !== 'live') { return } // Release all streams, otherwise they are still captured even if no video element is using them @@ -150,10 +173,17 @@ onBeforeUnmount(() => { class="capture-source__input" type="radio" :value="source.id"> -