From 70b530d2e575403a2a72ffea1c54da91933d4986 Mon Sep 17 00:00:00 2001 From: Curve Date: Sat, 1 Jun 2024 22:56:03 +0200 Subject: [PATCH] feat(ScreenShare): allow additional exclusions --- src/main/venmic.ts | 8 +- src/preload/VesktopNative.ts | 4 +- src/renderer/components/ScreenSharePicker.tsx | 231 ++++++++++-------- 3 files changed, 138 insertions(+), 105 deletions(-) diff --git a/src/main/venmic.ts b/src/main/venmic.ts index 83c6fb5..f269a00 100644 --- a/src/main/venmic.ts +++ b/src/main/venmic.ts @@ -78,12 +78,12 @@ ipcMain.handle(IpcEvents.VIRT_MIC_LIST, () => { return targets ? { ok: true, targets, hasPipewirePulse } : { ok: false, isGlibCxxOutdated }; }); -ipcMain.handle(IpcEvents.VIRT_MIC_START, (_, targets: Node[]) => { +ipcMain.handle(IpcEvents.VIRT_MIC_START, (_, include: Node[]) => { const pid = getRendererAudioServicePid(); const settings = Settings.store; const data: LinkData = { - include: targets, + include: include, exclude: [{ "application.process.id": pid }], ignore_devices: settings.audioIgnoreDevices }; @@ -103,12 +103,12 @@ ipcMain.handle(IpcEvents.VIRT_MIC_START, (_, targets: Node[]) => { return obtainVenmic()?.link(data); }); -ipcMain.handle(IpcEvents.VIRT_MIC_START_SYSTEM, () => { +ipcMain.handle(IpcEvents.VIRT_MIC_START_SYSTEM, (_, exclude: Node[]) => { const pid = getRendererAudioServicePid(); const settings = Settings.store; const data: LinkData = { - exclude: [{ "application.process.id": pid }], + exclude: [{ "application.process.id": pid }, ...exclude], only_speakers: settings.audioOnlySpeakers, ignore_devices: settings.audioIgnoreDevices, only_default_speakers: settings.audioOnlyDefaultSpeakers diff --git a/src/preload/VesktopNative.ts b/src/preload/VesktopNative.ts index 1ff5f6a..c8fc13f 100644 --- a/src/preload/VesktopNative.ts +++ b/src/preload/VesktopNative.ts @@ -66,8 +66,8 @@ export const VesktopNative = { invoke< { ok: false; isGlibCxxOutdated: boolean } | { ok: true; targets: Node[]; hasPipewirePulse: boolean } >(IpcEvents.VIRT_MIC_LIST), - start: (targets: Node[]) => invoke(IpcEvents.VIRT_MIC_START, targets), - startSystem: () => invoke(IpcEvents.VIRT_MIC_START_SYSTEM), + start: (include: Node[]) => invoke(IpcEvents.VIRT_MIC_START, include), + startSystem: (exclude: Node[]) => invoke(IpcEvents.VIRT_MIC_START_SYSTEM, exclude), stop: () => invoke(IpcEvents.VIRT_MIC_STOP) }, arrpc: { diff --git a/src/renderer/components/ScreenSharePicker.tsx b/src/renderer/components/ScreenSharePicker.tsx index ac5a985..7748ea5 100644 --- a/src/renderer/components/ScreenSharePicker.tsx +++ b/src/renderer/components/ScreenSharePicker.tsx @@ -47,8 +47,9 @@ interface StreamSettings { resolution: StreamResolution; fps: StreamFps; audio: boolean; - audioSources?: AudioSources; contentHint?: string; + includeSources?: AudioSources; + excludeSources?: AudioSources; } export interface StreamPick extends StreamSettings { @@ -128,13 +129,17 @@ export function openScreenSharePicker(screens: Source[], skipPicker: boolean) { modalProps={props} submit={async v => { didSubmit = true; - if (v.audioSources && v.audioSources !== "None") { - if (v.audioSources === "Entire System") { - await VesktopNative.virtmic.startSystem(); + + if (v.includeSources && v.includeSources !== "None") { + if (v.includeSources === "Entire System") { + await VesktopNative.virtmic.startSystem( + !v.excludeSources || isSpecialSource(v.excludeSources) ? [] : v.excludeSources + ); } else { - await VesktopNative.virtmic.start(v.audioSources); + await VesktopNative.virtmic.start(v.includeSources); } } + resolve(v); }} close={() => { @@ -302,7 +307,9 @@ function StreamSettings({ props.onClose()} - setAudioSources={sources => setSettings(s => ({ ...s, audioSources: sources }))} + setAudioSources={sources => + setSettings(s => ({ ...s, includeSources: sources, excludeSources: sources })) + } /> )); }; @@ -414,9 +421,11 @@ function StreamSettings({ {isLinux && ( setSettings(s => ({ ...s, audioSources: sources }))} + setIncludeSources={sources => setSettings(s => ({ ...s, includeSources: sources }))} + setExcludeSources={sources => setSettings(s => ({ ...s, excludeSources: sources }))} /> )} @@ -488,128 +497,152 @@ function mapToAudioItem(node: AudioSource, granularSelect?: boolean): AudioItem[ return rtn; } +function isItemSelected(sources?: AudioSources) { + return (value: AudioSource) => { + if (!sources) { + return false; + } + + if (isSpecialSource(sources) || isSpecialSource(value)) { + return sources === value; + } + + return sources.some(source => hasMatchingProps(source, value)); + }; +} + +function updateItems(setSources: (s: AudioSources) => void, sources?: AudioSources) { + return (value: AudioSource) => { + if (isSpecialSource(value)) { + setSources(value); + return; + } + + if (isSpecialSource(sources)) { + setSources([value]); + return; + } + + if (isItemSelected(sources)(value)) { + setSources(sources?.filter(x => !hasMatchingProps(x, value)) ?? "None"); + return; + } + + setSources([...(sources || []), value]); + }; +} + function AudioSourcePickerLinux({ - audioSources, + includeSources, + excludeSources, granularSelect, - setAudioSources, - openSettings + openSettings, + setIncludeSources, + setExcludeSources }: { - audioSources?: AudioSources; + includeSources?: AudioSources; + excludeSources?: AudioSources; granularSelect?: boolean; openSettings: () => void; - setAudioSources: (s: AudioSources) => void; + setIncludeSources: (s: AudioSources) => void; + setExcludeSources: (s: AudioSources) => void; }) { const [sources, _, loading] = useAwaiter(() => VesktopNative.virtmic.list(), { fallbackValue: { ok: true, targets: [], hasPipewirePulse: true } }); - const specialSources: SpecialSource[] = ["None", "Entire System"]; - const allSources = sources.ok ? [...specialSources, ...sources.targets] : null; - const hasPipewirePulse = sources.ok ? sources.hasPipewirePulse : true; const [ignorePulseWarning, setIgnorePulseWarning] = useState(false); - const isSelected = (value: AudioSource) => { - if (!audioSources) { - return false; - } + if (!sources.ok && sources.isGlibCxxOutdated) { + return ( + + Failed to retrieve Audio Sources because your C++ library is too old to run + + venmic + + . See{" "} + + this guide + {" "} + for possible solutions. + + ); + } - if (isSpecialSource(audioSources) || isSpecialSource(value)) { - return audioSources === value; - } + if (!hasPipewirePulse && !ignorePulseWarning) { + return ( + + Could not find pipewire-pulse. See{" "} + + this guide + {" "} + on how to switch to pipewire.
+ You can still continue, however, please{" "} + beware that you can only share audio of apps that are running under pipewire.{" "} + setIgnorePulseWarning(true)}>I know what I'm doing! +
+ ); + } - return audioSources.some(source => hasMatchingProps(source, value)); - }; - - const update = (value: SpecialSource | Node) => { - if (isSpecialSource(value)) { - setAudioSources(value); - return; - } - - if (isSpecialSource(audioSources)) { - setAudioSources([value]); - return; - } - - if (isSelected(value)) { - setAudioSources(audioSources?.filter(x => !hasMatchingProps(x, value)) ?? "None"); - return; - } - - setAudioSources([...(audioSources || []), value]); - }; + const specialSources: SpecialSource[] = ["None", "Entire System"] as const; const uniqueName = (value: AudioItem, index: number, list: AudioItem[]) => list.findIndex(x => x.name === value.name) === index; + const allSources = sources.ok + ? [...specialSources, ...sources.targets] + .map(target => mapToAudioItem(target, granularSelect)) + .flat() + .filter(uniqueName) + : []; + return ( -
- {loading ? ( - Loading Audio Sources... - ) : ( - Audio Source - )} - - {!sources.ok && sources.isGlibCxxOutdated && ( - - Failed to retrieve Audio Sources because your C++ library is too old to run - - venmic - - . See{" "} - - this guide - {" "} - for possible solutions. - - )} - - {hasPipewirePulse || ignorePulseWarning ? ( - allSources && ( - <> + <> +
+
+ {loading ? "Loading Sources..." : "Audio Sources"} + mapToAudioItem(target, granularSelect)) - .flat() - .filter(uniqueName) + .filter(x => x.name !== "Entire System") .map(({ name, value }) => ({ label: name, value: value, default: name === "None" }))} - isSelected={isSelected} - select={update} + isSelected={isItemSelected(excludeSources)} + select={updateItems(setExcludeSources, excludeSources)} serialize={String} popoutPosition="top" closeOnSelect={false} /> - - - - ) - ) : ( - - Could not find pipewire-pulse. See{" "} - - this guide - {" "} - on how to switch to pipewire.
- You can still continue, however, please{" "} - beware that you can only share audio of apps that are running under pipewire.{" "} - setIgnorePulseWarning(true)}>I know what I'm doing! -
- )} -
+ + )} +
+ + ); }