feat(ScreenShare): add granular selection

This commit is contained in:
Curve 2024-05-31 00:44:56 +02:00
parent c29e1c38ff
commit 50fc6844f9
No known key found for this signature in database
GPG key ID: 460F6C466BD35813
5 changed files with 161 additions and 88 deletions

View file

@ -27,7 +27,7 @@
"arrpc": "github:OpenAsar/arrpc#6960a8fd4d65d566da93dbdb8a7ca474aa0a3c9c" "arrpc": "github:OpenAsar/arrpc#6960a8fd4d65d566da93dbdb8a7ca474aa0a3c9c"
}, },
"optionalDependencies": { "optionalDependencies": {
"@vencord/venmic": "^3.5.0" "@vencord/venmic": "^4.0.1"
}, },
"devDependencies": { "devDependencies": {
"@fal-works/esbuild-plugin-global-externals": "^2.1.2", "@fal-works/esbuild-plugin-global-externals": "^2.1.2",

View file

@ -13,8 +13,8 @@ importers:
version: https://codeload.github.com/OpenAsar/arrpc/tar.gz/6960a8fd4d65d566da93dbdb8a7ca474aa0a3c9c version: https://codeload.github.com/OpenAsar/arrpc/tar.gz/6960a8fd4d65d566da93dbdb8a7ca474aa0a3c9c
optionalDependencies: optionalDependencies:
'@vencord/venmic': '@vencord/venmic':
specifier: ^3.5.0 specifier: ^4.0.1
version: 3.5.0 version: 4.0.1
devDependencies: devDependencies:
'@fal-works/esbuild-plugin-global-externals': '@fal-works/esbuild-plugin-global-externals':
specifier: ^2.1.2 specifier: ^2.1.2
@ -603,8 +603,8 @@ packages:
'@vencord/types@1.8.4': '@vencord/types@1.8.4':
resolution: {integrity: sha512-ogLqIOHVO+5zxKUVxAfGIAUZoEfIomrlg6f0cZ/2yd5vBAn1fA9Gi/NASoKfHZuJt8ZcYw329bgn0ah/VufqMg==} resolution: {integrity: sha512-ogLqIOHVO+5zxKUVxAfGIAUZoEfIomrlg6f0cZ/2yd5vBAn1fA9Gi/NASoKfHZuJt8ZcYw329bgn0ah/VufqMg==}
'@vencord/venmic@3.5.0': '@vencord/venmic@4.0.1':
resolution: {integrity: sha512-kPvrPcIeMkuqQriuiQAJ9rEBeqGR2nmFBuUtbZRGyiNRF9RDAfWSJYqhHVm6F7wbcqrSZio6FazZuBo0LvjJRw==} resolution: {integrity: sha512-3NcX1IOwA3n/wyDG0AfAVqKMzTP7yD8p1oXxcJXAVlx9NQLFKXXjUmRIC13fpDUxEfmU9hCCuqnn9BkVr7DIWA==}
engines: {node: '>=14.15'} engines: {node: '>=14.15'}
os: [linux] os: [linux]
@ -1441,6 +1441,7 @@ packages:
glob@7.2.3: glob@7.2.3:
resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==} resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==}
deprecated: Glob versions prior to v9 are no longer supported
global-agent@3.0.0: global-agent@3.0.0:
resolution: {integrity: sha512-PT6XReJ+D07JvGoxQMkT6qji/jVNfX/h364XHZOWeRzy64sSFr+xJ5OX7LI3b4MPQzdL4H8Y8M0xzPpsVMwA8Q==} resolution: {integrity: sha512-PT6XReJ+D07JvGoxQMkT6qji/jVNfX/h364XHZOWeRzy64sSFr+xJ5OX7LI3b4MPQzdL4H8Y8M0xzPpsVMwA8Q==}
@ -1561,6 +1562,7 @@ packages:
inflight@1.0.6: inflight@1.0.6:
resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==} resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==}
deprecated: This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.
inherits@2.0.4: inherits@2.0.4:
resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==}
@ -2155,6 +2157,7 @@ packages:
rimraf@3.0.2: rimraf@3.0.2:
resolution: {integrity: sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==} resolution: {integrity: sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==}
deprecated: Rimraf versions prior to v4 are no longer supported
hasBin: true hasBin: true
roarr@2.15.4: roarr@2.15.4:
@ -3031,7 +3034,7 @@ snapshots:
standalone-electron-types: 1.0.0 standalone-electron-types: 1.0.0
type-fest: 3.13.1 type-fest: 3.13.1
'@vencord/venmic@3.5.0': '@vencord/venmic@4.0.1':
dependencies: dependencies:
cmake-js: 7.3.0 cmake-js: 7.3.0
node-addon-api: 8.0.0 node-addon-api: 8.0.0
@ -3077,7 +3080,7 @@ snapshots:
app-builder-bin@4.0.0: {} app-builder-bin@4.0.0: {}
app-builder-lib@24.13.3(dmg-builder@24.13.3(electron-builder-squirrel-windows@24.13.3(dmg-builder@24.13.3)))(electron-builder-squirrel-windows@24.13.3(dmg-builder@24.13.3)): app-builder-lib@24.13.3(dmg-builder@24.13.3(electron-builder-squirrel-windows@24.13.3))(electron-builder-squirrel-windows@24.13.3(dmg-builder@24.13.3)):
dependencies: dependencies:
'@develar/schema-utils': 2.6.5 '@develar/schema-utils': 2.6.5
'@electron/notarize': 2.2.1 '@electron/notarize': 2.2.1
@ -3091,7 +3094,7 @@ snapshots:
builder-util-runtime: 9.2.4 builder-util-runtime: 9.2.4
chromium-pickle-js: 0.2.0 chromium-pickle-js: 0.2.0
debug: 4.3.4 debug: 4.3.4
dmg-builder: 24.13.3(electron-builder-squirrel-windows@24.13.3(dmg-builder@24.13.3)) dmg-builder: 24.13.3(electron-builder-squirrel-windows@24.13.3)
ejs: 3.1.9 ejs: 3.1.9
electron-builder-squirrel-windows: 24.13.3(dmg-builder@24.13.3) electron-builder-squirrel-windows: 24.13.3(dmg-builder@24.13.3)
electron-publish: 24.13.1 electron-publish: 24.13.1
@ -3566,9 +3569,9 @@ snapshots:
'@types/react': 17.0.2 '@types/react': 17.0.2
moment: 2.30.1 moment: 2.30.1
dmg-builder@24.13.3(electron-builder-squirrel-windows@24.13.3(dmg-builder@24.13.3)): dmg-builder@24.13.3(electron-builder-squirrel-windows@24.13.3):
dependencies: dependencies:
app-builder-lib: 24.13.3(dmg-builder@24.13.3(electron-builder-squirrel-windows@24.13.3(dmg-builder@24.13.3)))(electron-builder-squirrel-windows@24.13.3(dmg-builder@24.13.3)) app-builder-lib: 24.13.3(dmg-builder@24.13.3(electron-builder-squirrel-windows@24.13.3))(electron-builder-squirrel-windows@24.13.3(dmg-builder@24.13.3))
builder-util: 24.13.1 builder-util: 24.13.1
builder-util-runtime: 9.2.4 builder-util-runtime: 9.2.4
fs-extra: 10.1.0 fs-extra: 10.1.0
@ -3614,7 +3617,7 @@ snapshots:
electron-builder-squirrel-windows@24.13.3(dmg-builder@24.13.3): electron-builder-squirrel-windows@24.13.3(dmg-builder@24.13.3):
dependencies: dependencies:
app-builder-lib: 24.13.3(dmg-builder@24.13.3(electron-builder-squirrel-windows@24.13.3(dmg-builder@24.13.3)))(electron-builder-squirrel-windows@24.13.3(dmg-builder@24.13.3)) app-builder-lib: 24.13.3(dmg-builder@24.13.3(electron-builder-squirrel-windows@24.13.3))(electron-builder-squirrel-windows@24.13.3(dmg-builder@24.13.3))
archiver: 5.3.2 archiver: 5.3.2
builder-util: 24.13.1 builder-util: 24.13.1
fs-extra: 10.1.0 fs-extra: 10.1.0
@ -3624,11 +3627,11 @@ snapshots:
electron-builder@24.13.3(electron-builder-squirrel-windows@24.13.3(dmg-builder@24.13.3)): electron-builder@24.13.3(electron-builder-squirrel-windows@24.13.3(dmg-builder@24.13.3)):
dependencies: dependencies:
app-builder-lib: 24.13.3(dmg-builder@24.13.3(electron-builder-squirrel-windows@24.13.3(dmg-builder@24.13.3)))(electron-builder-squirrel-windows@24.13.3(dmg-builder@24.13.3)) app-builder-lib: 24.13.3(dmg-builder@24.13.3(electron-builder-squirrel-windows@24.13.3))(electron-builder-squirrel-windows@24.13.3(dmg-builder@24.13.3))
builder-util: 24.13.1 builder-util: 24.13.1
builder-util-runtime: 9.2.4 builder-util-runtime: 9.2.4
chalk: 4.1.2 chalk: 4.1.2
dmg-builder: 24.13.3(electron-builder-squirrel-windows@24.13.3(dmg-builder@24.13.3)) dmg-builder: 24.13.3(electron-builder-squirrel-windows@24.13.3)
fs-extra: 10.1.0 fs-extra: 10.1.0
is-ci: 3.0.1 is-ci: 3.0.1
lazy-val: 1.0.5 lazy-val: 1.0.5

View file

@ -67,32 +67,26 @@ function getRendererAudioServicePid() {
); );
} }
ipcMain.handle(IpcEvents.VIRT_MIC_LIST, (_, props: string[]) => { ipcMain.handle(IpcEvents.VIRT_MIC_LIST, (_, props?: string[]) => {
const audioPid = getRendererAudioServicePid(); const audioPid = getRendererAudioServicePid();
const list = obtainVenmic() const targets = obtainVenmic()
?.list(props) ?.list(props)
.filter(s => s["application.process.id"] !== audioPid); .filter(s => s["application.process.id"] !== audioPid);
const sameProps = (x: Node, y: Node) => props.every(prop => x[prop] === y[prop]); return targets ? { ok: true, targets, hasPipewirePulse } : { ok: false, isGlibCxxOutdated };
const unique = list?.filter((x, i, array) => array.findIndex(y => sameProps(x, y)) === i);
return list ? { ok: true, targets: unique, hasPipewirePulse } : { ok: false, isGlibCxxOutdated };
}); });
ipcMain.handle(IpcEvents.VIRT_MIC_START, (_, targets: string[], workaround?: boolean) => { ipcMain.handle(IpcEvents.VIRT_MIC_START, (_, targets: Node[], workaround?: boolean) => {
const pid = getRendererAudioServicePid(); const pid = getRendererAudioServicePid();
const data: LinkData = { const data: LinkData = {
include: targets.map(target => ({ key: "application.name", value: target })), include: targets,
exclude: [{ key: "application.process.id", value: pid }] exclude: [{ "application.process.id": pid }]
}; };
if (workaround) { if (workaround) {
data.workaround = [ data.workaround = [{ "application.process.id": pid, "media.name": "RecordStream" }];
{ key: "application.process.id", value: pid },
{ key: "media.name", value: "RecordStream" }
];
} }
return obtainVenmic()?.link(data); return obtainVenmic()?.link(data);
@ -102,15 +96,12 @@ ipcMain.handle(IpcEvents.VIRT_MIC_START_SYSTEM, (_, workaround?: boolean, onlyDe
const pid = getRendererAudioServicePid(); const pid = getRendererAudioServicePid();
const data: LinkData = { const data: LinkData = {
exclude: [{ key: "application.process.id", value: pid }], exclude: [{ "application.process.id": pid }],
only_default_speakers: onlyDefaultSpeakers only_default_speakers: onlyDefaultSpeakers
}; };
if (workaround) { if (workaround) {
data.workaround = [ data.workaround = [{ "application.process.id": pid, "media.name": "RecordStream" }];
{ key: "application.process.id", value: pid },
{ key: "media.name", value: "RecordStream" }
];
} }
return obtainVenmic()?.link(data); return obtainVenmic()?.link(data);

View file

@ -4,6 +4,7 @@
* Copyright (c) 2023 Vendicated and Vencord contributors * Copyright (c) 2023 Vendicated and Vencord contributors
*/ */
import { Node } from "@vencord/venmic";
import { ipcRenderer } from "electron"; import { ipcRenderer } from "electron";
import type { Settings } from "shared/settings"; import type { Settings } from "shared/settings";
import type { LiteralUnion } from "type-fest"; import type { LiteralUnion } from "type-fest";
@ -63,10 +64,9 @@ export const VesktopNative = {
virtmic: { virtmic: {
list: (props?: string[]) => list: (props?: string[]) =>
invoke< invoke<
| { ok: false; isGlibCxxOutdated: boolean } { ok: false; isGlibCxxOutdated: boolean } | { ok: true; targets: Node[]; hasPipewirePulse: boolean }
| { ok: true; targets: Record<string, string>[]; hasPipewirePulse: boolean }
>(IpcEvents.VIRT_MIC_LIST, props), >(IpcEvents.VIRT_MIC_LIST, props),
start: (targets: string[], workaround?: boolean) => invoke<void>(IpcEvents.VIRT_MIC_START, targets, workaround), start: (targets: Node[], workaround?: boolean) => invoke<void>(IpcEvents.VIRT_MIC_START, targets, workaround),
startSystem: (workaround?: boolean, onlyDefaultSpeakers?: boolean) => startSystem: (workaround?: boolean, onlyDefaultSpeakers?: boolean) =>
invoke<void>(IpcEvents.VIRT_MIC_START_SYSTEM, workaround, onlyDefaultSpeakers), invoke<void>(IpcEvents.VIRT_MIC_START_SYSTEM, workaround, onlyDefaultSpeakers),
stop: () => invoke<void>(IpcEvents.VIRT_MIC_STOP) stop: () => invoke<void>(IpcEvents.VIRT_MIC_STOP)

View file

@ -19,6 +19,7 @@ import {
UserStore, UserStore,
useState useState
} from "@vencord/types/webpack/common"; } from "@vencord/types/webpack/common";
import { Node } from "@vencord/venmic";
import type { Dispatch, SetStateAction } from "react"; import type { Dispatch, SetStateAction } from "react";
import { addPatch } from "renderer/patches/shared"; import { addPatch } from "renderer/patches/shared";
import { isLinux, isWindows } from "renderer/utils"; import { isLinux, isWindows } from "renderer/utils";
@ -31,15 +32,25 @@ const MediaEngineStore = findStoreLazy("MediaEngineStore");
export type StreamResolution = (typeof StreamResolutions)[number]; export type StreamResolution = (typeof StreamResolutions)[number];
export type StreamFps = (typeof StreamFps)[number]; export type StreamFps = (typeof StreamFps)[number];
type SpecialSource = "None" | "Entire System";
type AudioSource = SpecialSource | Node;
type AudioSources = SpecialSource | Node[];
interface AudioItem {
name: string;
value: AudioSource;
}
interface StreamSettings { interface StreamSettings {
resolution: StreamResolution; resolution: StreamResolution;
fps: StreamFps; fps: StreamFps;
audio: boolean; audio: boolean;
audioSources?: string[]; audioSources?: AudioSources;
contentHint?: string; contentHint?: string;
workaround?: boolean; workaround?: boolean;
onlyDefaultSpeakers?: boolean; onlyDefaultSpeakers?: boolean;
selectByProcess?: boolean; granularSelect?: boolean;
} }
export interface StreamPick extends StreamSettings { export interface StreamPick extends StreamSettings {
@ -119,8 +130,8 @@ 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?.[0] !== "None") { if (v.audioSources && v.audioSources !== "None") {
if (v.audioSources?.[0] === "Entire System") { if (v.audioSources === "Entire System") {
await VesktopNative.virtmic.startSystem(v.workaround); await VesktopNative.virtmic.startSystem(v.workaround);
} else { } else {
await VesktopNative.virtmic.start(v.audioSources, v.workaround); await VesktopNative.virtmic.start(v.audioSources, v.workaround);
@ -295,11 +306,11 @@ function StreamSettings({
audioSources={settings.audioSources} audioSources={settings.audioSources}
workaround={settings.workaround} workaround={settings.workaround}
onlyDefaultSpeakers={settings.onlyDefaultSpeakers} onlyDefaultSpeakers={settings.onlyDefaultSpeakers}
selectByProcess={settings.selectByProcess} granularSelect={settings.granularSelect}
setAudioSources={source => setSettings(s => ({ ...s, audioSources: source }))} setAudioSources={sources => setSettings(s => ({ ...s, audioSources: sources }))}
setWorkaround={value => setSettings(s => ({ ...s, workaround: value }))} setWorkaround={value => setSettings(s => ({ ...s, workaround: value }))}
setOnlyDefaultSpeakers={value => setSettings(s => ({ ...s, onlyDefaultSpeakers: value }))} setOnlyDefaultSpeakers={value => setSettings(s => ({ ...s, onlyDefaultSpeakers: value }))}
setSelectByProcess={value => setSettings(s => ({ ...s, selectByProcess: value }))} setGranularSelect={value => setSettings(s => ({ ...s, granularSelect: value }))}
/> />
)} )}
</div> </div>
@ -307,62 +318,126 @@ function StreamSettings({
); );
} }
function isSpecialSource(value?: AudioSource | AudioSources): value is SpecialSource {
return typeof value === "string";
}
function hasMatchingProps(value: Node, other: Node) {
return Object.keys(value).every(key => value[key] === other[key]);
}
function mapToAudioItem(node: AudioSource, granularSelect?: boolean): AudioItem[] {
if (isSpecialSource(node)) {
return [{ name: node, value: node }];
}
const rtn: AudioItem[] = [];
const name = node["application.name"];
if (name) {
rtn.push({ name: name, value: { "application.name": name } });
}
if (!granularSelect) {
return rtn;
}
const binary = node["application.process.binary"];
if (!name) {
rtn.push({ name: binary, value: { "application.process.binary": binary } });
}
const pid = node["application.process.id"];
const first = rtn[0];
const firstValues = first.value as Node;
rtn.push({
name: `${first.name} (${pid})`,
value: { ...firstValues, "application.process.id": pid }
});
const mediaName = node["media.name"];
if (!mediaName) {
return rtn;
}
rtn.push({
name: `${first.name} [${mediaName}]`,
value: { ...firstValues, "media.name": mediaName }
});
return rtn;
}
function AudioSourcePickerLinux({ function AudioSourcePickerLinux({
audioSources, audioSources,
workaround, workaround,
onlyDefaultSpeakers, onlyDefaultSpeakers,
selectByProcess, granularSelect,
setAudioSources, setAudioSources,
setWorkaround, setWorkaround,
setOnlyDefaultSpeakers setOnlyDefaultSpeakers,
setGranularSelect
}: { }: {
audioSources?: string[]; audioSources?: AudioSources;
workaround?: boolean; workaround?: boolean;
onlyDefaultSpeakers?: boolean; onlyDefaultSpeakers?: boolean;
selectByProcess?: boolean; granularSelect?: boolean;
setAudioSources(s: string[]): void; setAudioSources(s: AudioSources): void;
setWorkaround(b: boolean): void; setWorkaround(b: boolean): void;
setOnlyDefaultSpeakers(b: boolean): void; setOnlyDefaultSpeakers(b: boolean): void;
setSelectByProcess(b: boolean): void; setGranularSelect(b: boolean): void;
}) { }) {
const properties = selectByProcess ? ["application.process.id", "media.name"] : undefined; const properties = granularSelect ? ["application.process.id"] : undefined;
const [sources, _, loading] = useAwaiter(() => VesktopNative.virtmic.list(properties), { const [sources, _, loading] = useAwaiter(() => VesktopNative.virtmic.list(properties), {
fallbackValue: { ok: true, targets: [], hasPipewirePulse: true } fallbackValue: { ok: true, targets: [], hasPipewirePulse: true }
}); });
const specialSources = ["None", "Entire System"]; const specialSources: SpecialSource[] = ["None", "Entire System"];
const allSources = sources.ok ? [...specialSources, ...sources.targets] : null; 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 getName = (x: any) => { const isSelected = (value: AudioSource) => {
if (specialSources.includes(x)) { if (!audioSources) {
return x; return false;
} }
if (!selectByProcess) { if (isSpecialSource(audioSources) || isSpecialSource(value)) {
return x["application.name"]; return audioSources === value;
} }
let name = `${x["application.name"] ?? "Unknown"}`; return audioSources.some(source => hasMatchingProps(source, value));
if (x["media.name"]) {
name += ` - ${x["media.name"]} `;
}
return `${name} - ${x["application.process.id"]}`;
}; };
const getValue = (x: any) => { const update = (value: SpecialSource | Node) => {
if (specialSources.includes(x)) { if (isSpecialSource(value)) {
return x; setAudioSources(value);
return;
} }
return x["object.id"]; 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[]) =>
list.findIndex(x => x.name === value.name) === index;
return ( return (
<> <>
<Forms.FormTitle>Audio Settings</Forms.FormTitle> <Forms.FormTitle>Audio Settings</Forms.FormTitle>
@ -390,31 +465,33 @@ function AudioSourcePickerLinux({
{hasPipewirePulse || ignorePulseWarning ? ( {hasPipewirePulse || ignorePulseWarning ? (
allSources && ( allSources && (
<Select <Select
options={allSources.map(s => ({ options={allSources
label: getName(s), .map(target => mapToAudioItem(target, granularSelect))
value: getValue(s), .flat()
default: s === "None" .filter(uniqueName)
}))} .map(({ name, value }) => ({
isSelected={s => audioSources?.includes(s) || false} label: name,
select={source => { value: value,
const updated = [...(audioSources || []), source]; default: name === "None"
}))}
if (updated?.some(x => specialSources.includes(x))) { isSelected={isSelected}
setAudioSources([source]); select={update}
return;
}
setAudioSources(updated);
}}
serialize={String} serialize={String}
/> />
) )
) : ( ) : (
<Text variant="text-sm/normal"> <Text variant="text-sm/normal">
Could not find pipewire-pulse. This usually means that you do not run pipewire as your main Could not find pipewire-pulse. See{" "}
audio-server. <br /> <a
You can still continue, however, please beware that you can only share audio of apps that are href="https://gist.github.com/the-spyke/2de98b22ff4f978ebf0650c90e82027e#install"
running under pipewire. 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>.
<br />
<br /> <br />
<a onClick={() => setIgnorePulseWarning(true)}>I know what I'm doing</a> <a onClick={() => setIgnorePulseWarning(true)}>I know what I'm doing</a>
</Text> </Text>
@ -451,9 +528,11 @@ function AudioSourcePickerLinux({
</Switch> </Switch>
<Switch <Switch
hideBorder hideBorder
onChange={setOnlyDefaultSpeakers} onChange={value => {
disabled={audioSources?.[0] !== "Entire System"} setGranularSelect(value);
value={onlyDefaultSpeakers ?? true} setAudioSources("None");
}}
value={granularSelect ?? false}
note={<>Allow to select applications more granularly.</>} note={<>Allow to select applications more granularly.</>}
> >
Granular Selection Granular Selection