feat(ScreenShare): allow additional exclusions

This commit is contained in:
Curve 2024-06-01 22:56:03 +02:00
parent 379861616b
commit 70b530d2e5
No known key found for this signature in database
GPG key ID: D4A4F11797B83017
3 changed files with 138 additions and 105 deletions

View file

@ -78,12 +78,12 @@ ipcMain.handle(IpcEvents.VIRT_MIC_LIST, () => {
return targets ? { ok: true, targets, hasPipewirePulse } : { ok: false, isGlibCxxOutdated }; 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 pid = getRendererAudioServicePid();
const settings = Settings.store; const settings = Settings.store;
const data: LinkData = { const data: LinkData = {
include: targets, include: include,
exclude: [{ "application.process.id": pid }], exclude: [{ "application.process.id": pid }],
ignore_devices: settings.audioIgnoreDevices ignore_devices: settings.audioIgnoreDevices
}; };
@ -103,12 +103,12 @@ ipcMain.handle(IpcEvents.VIRT_MIC_START, (_, targets: Node[]) => {
return obtainVenmic()?.link(data); return obtainVenmic()?.link(data);
}); });
ipcMain.handle(IpcEvents.VIRT_MIC_START_SYSTEM, () => { ipcMain.handle(IpcEvents.VIRT_MIC_START_SYSTEM, (_, exclude: Node[]) => {
const pid = getRendererAudioServicePid(); const pid = getRendererAudioServicePid();
const settings = Settings.store; const settings = Settings.store;
const data: LinkData = { const data: LinkData = {
exclude: [{ "application.process.id": pid }], exclude: [{ "application.process.id": pid }, ...exclude],
only_speakers: settings.audioOnlySpeakers, only_speakers: settings.audioOnlySpeakers,
ignore_devices: settings.audioIgnoreDevices, ignore_devices: settings.audioIgnoreDevices,
only_default_speakers: settings.audioOnlyDefaultSpeakers only_default_speakers: settings.audioOnlyDefaultSpeakers

View file

@ -66,8 +66,8 @@ export const VesktopNative = {
invoke< invoke<
{ ok: false; isGlibCxxOutdated: boolean } | { ok: true; targets: Node[]; hasPipewirePulse: boolean } { ok: false; isGlibCxxOutdated: boolean } | { ok: true; targets: Node[]; hasPipewirePulse: boolean }
>(IpcEvents.VIRT_MIC_LIST), >(IpcEvents.VIRT_MIC_LIST),
start: (targets: Node[]) => invoke<void>(IpcEvents.VIRT_MIC_START, targets), start: (include: Node[]) => invoke<void>(IpcEvents.VIRT_MIC_START, include),
startSystem: () => invoke<void>(IpcEvents.VIRT_MIC_START_SYSTEM), startSystem: (exclude: Node[]) => invoke<void>(IpcEvents.VIRT_MIC_START_SYSTEM, exclude),
stop: () => invoke<void>(IpcEvents.VIRT_MIC_STOP) stop: () => invoke<void>(IpcEvents.VIRT_MIC_STOP)
}, },
arrpc: { arrpc: {

View file

@ -47,8 +47,9 @@ interface StreamSettings {
resolution: StreamResolution; resolution: StreamResolution;
fps: StreamFps; fps: StreamFps;
audio: boolean; audio: boolean;
audioSources?: AudioSources;
contentHint?: string; contentHint?: string;
includeSources?: AudioSources;
excludeSources?: AudioSources;
} }
export interface StreamPick extends StreamSettings { export interface StreamPick extends StreamSettings {
@ -128,13 +129,17 @@ export function openScreenSharePicker(screens: Source[], skipPicker: boolean) {
modalProps={props} modalProps={props}
submit={async v => { submit={async v => {
didSubmit = true; didSubmit = true;
if (v.audioSources && v.audioSources !== "None") {
if (v.audioSources === "Entire System") { if (v.includeSources && v.includeSources !== "None") {
await VesktopNative.virtmic.startSystem(); if (v.includeSources === "Entire System") {
await VesktopNative.virtmic.startSystem(
!v.excludeSources || isSpecialSource(v.excludeSources) ? [] : v.excludeSources
);
} else { } else {
await VesktopNative.virtmic.start(v.audioSources); await VesktopNative.virtmic.start(v.includeSources);
} }
} }
resolve(v); resolve(v);
}} }}
close={() => { close={() => {
@ -302,7 +307,9 @@ function StreamSettings({
<AudioSettingsModal <AudioSettingsModal
modalProps={props} modalProps={props}
close={() => props.onClose()} close={() => 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 && ( {isLinux && (
<AudioSourcePickerLinux <AudioSourcePickerLinux
openSettings={openSettings} openSettings={openSettings}
audioSources={settings.audioSources} includeSources={settings.includeSources}
excludeSources={settings.excludeSources}
granularSelect={Settings.audioGranularSelect} granularSelect={Settings.audioGranularSelect}
setAudioSources={sources => setSettings(s => ({ ...s, audioSources: sources }))} setIncludeSources={sources => setSettings(s => ({ ...s, includeSources: sources }))}
setExcludeSources={sources => setSettings(s => ({ ...s, excludeSources: sources }))}
/> />
)} )}
</Card> </Card>
@ -488,128 +497,152 @@ function mapToAudioItem(node: AudioSource, granularSelect?: boolean): AudioItem[
return rtn; 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({ function AudioSourcePickerLinux({
audioSources, includeSources,
excludeSources,
granularSelect, granularSelect,
setAudioSources, openSettings,
openSettings setIncludeSources,
setExcludeSources
}: { }: {
audioSources?: AudioSources; includeSources?: AudioSources;
excludeSources?: AudioSources;
granularSelect?: boolean; granularSelect?: boolean;
openSettings: () => void; openSettings: () => void;
setAudioSources: (s: AudioSources) => void; setIncludeSources: (s: AudioSources) => void;
setExcludeSources: (s: AudioSources) => void;
}) { }) {
const [sources, _, loading] = useAwaiter(() => VesktopNative.virtmic.list(), { const [sources, _, loading] = useAwaiter(() => VesktopNative.virtmic.list(), {
fallbackValue: { ok: true, targets: [], hasPipewirePulse: true } 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 hasPipewirePulse = sources.ok ? sources.hasPipewirePulse : true;
const [ignorePulseWarning, setIgnorePulseWarning] = useState(false); const [ignorePulseWarning, setIgnorePulseWarning] = useState(false);
const isSelected = (value: AudioSource) => { if (!sources.ok && sources.isGlibCxxOutdated) {
if (!audioSources) { return (
return false; <Forms.FormText>
} Failed to retrieve Audio Sources because your C++ library is too old to run
<a href="https://github.com/Vencord/venmic" target="_blank">
venmic
</a>
. See{" "}
<a href="https://gist.github.com/Vendicated/b655044ffbb16b2716095a448c6d827a" target="_blank">
this guide
</a>{" "}
for possible solutions.
</Forms.FormText>
);
}
if (isSpecialSource(audioSources) || isSpecialSource(value)) { if (!hasPipewirePulse && !ignorePulseWarning) {
return audioSources === value; return (
} <Text variant="text-sm/normal">
Could not find pipewire-pulse. See{" "}
<a href="https://gist.github.com/the-spyke/2de98b22ff4f978ebf0650c90e82027e#install" target="_blank">
this guide
</a>{" "}
on how to switch to pipewire. <br />
You can still continue, however, please{" "}
<b>beware that you can only share audio of apps that are running under pipewire</b>.{" "}
<a onClick={() => setIgnorePulseWarning(true)}>I know what I'm doing!</a>
</Text>
);
}
return audioSources.some(source => hasMatchingProps(source, value)); const specialSources: SpecialSource[] = ["None", "Entire System"] as const;
};
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 uniqueName = (value: AudioItem, index: number, list: AudioItem[]) => const uniqueName = (value: AudioItem, index: number, list: AudioItem[]) =>
list.findIndex(x => x.name === value.name) === index; list.findIndex(x => x.name === value.name) === index;
const allSources = sources.ok
? [...specialSources, ...sources.targets]
.map(target => mapToAudioItem(target, granularSelect))
.flat()
.filter(uniqueName)
: [];
return ( return (
<div> <>
{loading ? ( <div className={includeSources === "Entire System" ? "vcd-screen-picker-quality" : undefined}>
<Forms.FormTitle>Loading Audio Sources...</Forms.FormTitle> <section>
) : ( <Forms.FormTitle>{loading ? "Loading Sources..." : "Audio Sources"}</Forms.FormTitle>
<Forms.FormTitle>Audio Source</Forms.FormTitle> <Select
)} options={allSources.map(({ name, value }) => ({
label: name,
{!sources.ok && sources.isGlibCxxOutdated && ( value: value,
<Forms.FormText> default: name === "None"
Failed to retrieve Audio Sources because your C++ library is too old to run }))}
<a href="https://github.com/Vencord/venmic" target="_blank"> isSelected={isItemSelected(includeSources)}
venmic select={updateItems(setIncludeSources, includeSources)}
</a> serialize={String}
. See{" "} popoutPosition="top"
<a href="https://gist.github.com/Vendicated/b655044ffbb16b2716095a448c6d827a" target="_blank"> closeOnSelect={false}
this guide />
</a>{" "} </section>
for possible solutions. {includeSources === "Entire System" && (
</Forms.FormText> <section>
)} <Forms.FormTitle>Exclude Sources</Forms.FormTitle>
{hasPipewirePulse || ignorePulseWarning ? (
allSources && (
<>
<Select <Select
options={allSources options={allSources
.map(target => mapToAudioItem(target, granularSelect)) .filter(x => x.name !== "Entire System")
.flat()
.filter(uniqueName)
.map(({ name, value }) => ({ .map(({ name, value }) => ({
label: name, label: name,
value: value, value: value,
default: name === "None" default: name === "None"
}))} }))}
isSelected={isSelected} isSelected={isItemSelected(excludeSources)}
select={update} select={updateItems(setExcludeSources, excludeSources)}
serialize={String} serialize={String}
popoutPosition="top" popoutPosition="top"
closeOnSelect={false} closeOnSelect={false}
/> />
</section>
<Button )}
color={Button.Colors.TRANSPARENT} </div>
onClick={openSettings} <Button
className="vcd-screen-picker-settings-button" color={Button.Colors.TRANSPARENT}
> onClick={openSettings}
Open Audio Settings className="vcd-screen-picker-settings-button"
</Button> >
</> Open Audio Settings
) </Button>
) : ( </>
<Text variant="text-sm/normal">
Could not find pipewire-pulse. See{" "}
<a
href="https://gist.github.com/the-spyke/2de98b22ff4f978ebf0650c90e82027e#install"
target="_blank"
>
this guide
</a>{" "}
on how to switch to pipewire. <br />
You can still continue, however, please{" "}
<b>beware that you can only share audio of apps that are running under pipewire</b>.{" "}
<a onClick={() => setIgnorePulseWarning(true)}>I know what I'm doing!</a>
</Text>
)}
</div>
); );
} }