diff --git a/src/ipc/channels.ts b/src/ipc/channels.ts index 11991140ae..2108cbcbd1 100644 --- a/src/ipc/channels.ts +++ b/src/ipc/channels.ts @@ -34,7 +34,15 @@ type ChannelToArgsMap = { 'video-call-window/open-url': (url: string) => void; 'video-call-window/web-contents-id': (webContentsId: number) => void; 'video-call-window/open-screen-picker': () => { success: boolean }; - 'video-call-window/screen-sharing-source-responded': (source: string) => void; + 'video-call-window/screen-sharing-source-responded': ( + source: + | string + | null + | { + sourceId: string | null; + shareAudio?: boolean; + } + ) => void; 'video-call-window/screen-recording-is-permission-granted': () => boolean; 'video-call-window/close-requested': () => { success: boolean }; 'video-call-window/open-webview-dev-tools': () => boolean; diff --git a/src/screenSharing/ScreenSharingRequestTracker.ts b/src/screenSharing/ScreenSharingRequestTracker.ts index ba2a7d4dd3..d9abfe9dc7 100644 --- a/src/screenSharing/ScreenSharingRequestTracker.ts +++ b/src/screenSharing/ScreenSharingRequestTracker.ts @@ -4,10 +4,17 @@ import { desktopCapturer, ipcMain } from 'electron'; import type { DisplayMediaCallback } from './screenPicker/types'; const DEFAULT_TIMEOUT = 60000; +type ScreenSharingSelectionPayload = { + sourceId: string | null; + shareAudio?: boolean; +}; export class ScreenSharingRequestTracker { private activeListener: - | ((event: Event, sourceId: string | null) => void) + | (( + event: Event, + payload: string | null | ScreenSharingSelectionPayload + ) => void) | null = null; private activeRequestId: string | null = null; @@ -73,7 +80,10 @@ export class ScreenSharingRequestTracker { let callbackInvoked = false; - const listener = async (_event: Event, sourceId: string | null) => { + const listener = async ( + _event: Event, + payload: string | null | ScreenSharingSelectionPayload + ) => { if (this.activeRequestId !== requestId) { return; } @@ -86,6 +96,15 @@ export class ScreenSharingRequestTracker { this.removeListenerOnly(); this.markComplete(); + const sourceId = + typeof payload === 'object' && payload !== null + ? payload.sourceId + : payload; + const shareAudio = + typeof payload === 'object' && payload !== null + ? payload.shareAudio === true + : false; + if (!sourceId) { cb({ video: false } as any); return; @@ -107,7 +126,11 @@ export class ScreenSharingRequestTracker { return; } - cb({ video: selectedSource }); + cb( + shareAudio + ? ({ video: selectedSource, audio: 'loopback' } as any) + : { video: selectedSource } + ); } catch (error) { console.error(`${this.label}: error validating source:`, error); cb({ video: false } as any); diff --git a/src/screenSharing/screenSharePicker.tsx b/src/screenSharing/screenSharePicker.tsx index 8cc8b357cf..8eeaf49265 100644 --- a/src/screenSharing/screenSharePicker.tsx +++ b/src/screenSharing/screenSharePicker.tsx @@ -2,6 +2,7 @@ import { Box, Button, Callout, + CheckBox, Label, Tabs, Scrollable, @@ -13,6 +14,7 @@ import type { SourcesOptions, } from 'electron'; import { ipcRenderer } from 'electron'; +import type { ChangeEvent } from 'react'; import { useCallback, useEffect, useRef, useState } from 'react'; import { useTranslation } from 'react-i18next'; @@ -31,6 +33,11 @@ interface IScreenSharePickerProps { includeTheme?: boolean; } +type ScreenSharingSelectionPayload = { + sourceId: string | null; + shareAudio?: boolean; +}; + export function ScreenSharePicker({ onMounted, responseChannel = 'video-call-window/screen-sharing-source-responded', @@ -43,6 +50,7 @@ export function ScreenSharePicker({ const [sources, setSources] = useState([]); const [currentTab, setCurrentTab] = useState<'screen' | 'window'>('screen'); const [selectedSourceId, setSelectedSourceId] = useState(null); + const [shareAudio, setShareAudio] = useState(false); const [ isScreenRecordingPermissionGranted, setIsScreenRecordingPermissionGranted, @@ -116,11 +124,16 @@ export function ScreenSharePicker({ if (visible) { responseSentRef.current = false; wasVisibleRef.current = true; + setShareAudio(false); } else if (wasVisibleRef.current) { wasVisibleRef.current = false; if (!responseSentRef.current) { responseSentRef.current = true; - ipcRenderer.send(responseChannel, null); + const payload: ScreenSharingSelectionPayload = { + sourceId: null, + shareAudio: false, + }; + ipcRenderer.send(responseChannel, payload); } } }, [visible, responseChannel]); @@ -175,7 +188,11 @@ export function ScreenSharePicker({ responseSentRef.current = true; setVisible(false); - ipcRenderer.send(responseChannel, selectedSourceId); + const payload: ScreenSharingSelectionPayload = { + sourceId: selectedSourceId, + shareAudio, + }; + ipcRenderer.send(responseChannel, payload); } }; @@ -186,7 +203,11 @@ export function ScreenSharePicker({ } responseSentRef.current = true; setVisible(false); - ipcRenderer.send(responseChannel, null); + const payload: ScreenSharingSelectionPayload = { + sourceId: null, + shareAudio: false, + }; + ipcRenderer.send(responseChannel, payload); }; // Filter sources based on the current tab @@ -383,8 +404,25 @@ export function ScreenSharePicker({ + + ) => + setShareAudio(event.currentTarget.checked) + } + /> + +