diff --git a/package.json b/package.json index ec7e229..99b6e2f 100644 --- a/package.json +++ b/package.json @@ -31,7 +31,7 @@ "electron-updater": "6.6.2" }, "optionalDependencies": { - "@vencord/venmic": "^6.1.0" + "@vencord/venmic": "^7.1.0" }, "devDependencies": { "@fal-works/esbuild-plugin-global-externals": "^2.1.2", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 4cb06d8..41a9a3f 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -96,8 +96,8 @@ importers: version: 3.7.0 optionalDependencies: '@vencord/venmic': - specifier: ^6.1.0 - version: 6.1.0 + specifier: ^7.1.0 + version: 7.1.0 packages/libvesktop: devDependencies: @@ -555,8 +555,8 @@ packages: '@types/react': 18.3.1 '@types/react-dom': 18.3.1 - '@vencord/venmic@6.1.0': - resolution: {integrity: sha512-YiCtzml/W8tYbGhu3jm5jfbbEnl2slKKARNK0jO+8qV979k9eFnfIRTxvhMN/SWq1h8ZNJdXVwvXpffQwq0RuA==} + '@vencord/venmic@7.1.0': + resolution: {integrity: sha512-HQpZoUKGz8j7m1+1BCgE+7B6T/+/IArVO8km7SjsWxe/5+2YblAs0Fwgy9C9XqCCir/u0+F1FMqrQQUX2PATYw==} engines: {node: '>=14.15'} os: [linux] @@ -3317,7 +3317,7 @@ snapshots: ts-pattern: 5.9.0 type-fest: 4.41.0 - '@vencord/venmic@6.1.0': + '@vencord/venmic@7.1.0': dependencies: cmake-js: 7.4.0 node-addon-api: 8.9.0 diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 23f5fc8..8306458 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -13,4 +13,5 @@ recursiveInstall: false packages: - packages/* minimumReleaseAgeExclude: - - electron \ No newline at end of file + - electron + - '@vencord/*' diff --git a/src/main/venmic.ts b/src/main/venmic.ts index b6a0d08..80192c5 100644 --- a/src/main/venmic.ts +++ b/src/main/venmic.ts @@ -66,70 +66,60 @@ function getRendererAudioServicePid() { ); } +function buildLinkData({ include, exclude }: { include?: Node[]; exclude?: Node[] }) { + const pid = getRendererAudioServicePid(); + + const { mute, ignoreDevices, ignoreInputMedia, ignoreVirtual, workaround, onlySpeakers, onlyDefaultSpeakers } = + Settings.store.audio ?? {}; + + const data: LinkData = { + mute: mute ?? true, + include: include ?? [], + exclude: exclude ?? [], + only_speakers: onlySpeakers, + ignore_devices: ignoreDevices, + only_default_speakers: onlyDefaultSpeakers + }; + + data.exclude.push({ "application.process.id": pid }); + + if (ignoreInputMedia ?? true) { + data.exclude.push({ "media.class": "Stream/Input/Audio" }); + } + + if (ignoreVirtual) { + data.exclude.push({ "node.virtual": "true" }); + } + + if (workaround) { + data.workaround = [{ "application.process.id": pid, "media.name": "RecordStream" }]; + } + + return data; +} + ipcMain.handle(IpcEvents.VIRT_MIC_LIST, () => { const audioPid = getRendererAudioServicePid(); const { granularSelect } = Settings.store.audio ?? {}; const targets = obtainVenmic() - ?.list(granularSelect ? ["node.name"] : undefined) + ?.list(granularSelect ? [] : ["application.name", "node.name"]) .filter(s => s["application.process.id"] !== audioPid); return targets ? { ok: true, targets, hasPipewirePulse } : { ok: false, isGlibCxxOutdated }; }); ipcMain.handle(IpcEvents.VIRT_MIC_START, (_, include: Node[]) => { - const pid = getRendererAudioServicePid(); - const { ignoreDevices, ignoreInputMedia, ignoreVirtual, workaround } = Settings.store.audio ?? {}; - - const data: LinkData = { - include, - exclude: [{ "application.process.id": pid }], - ignore_devices: ignoreDevices - }; - - if (ignoreInputMedia ?? true) { - data.exclude.push({ "media.class": "Stream/Input/Audio" }); - } - - if (ignoreVirtual) { - data.exclude.push({ "node.virtual": "true" }); - } - - if (workaround) { - data.workaround = [{ "application.process.id": pid, "media.name": "RecordStream" }]; - } - - return obtainVenmic()?.link(data); + return obtainVenmic()?.link(buildLinkData({ include })); }); ipcMain.handle(IpcEvents.VIRT_MIC_START_SYSTEM, (_, exclude: Node[]) => { - const pid = getRendererAudioServicePid(); + return obtainVenmic()?.link(buildLinkData({ exclude })); +}); - const { workaround, ignoreDevices, ignoreInputMedia, ignoreVirtual, onlySpeakers, onlyDefaultSpeakers } = - Settings.store.audio ?? {}; - - const data: LinkData = { - include: [], - exclude: [{ "application.process.id": pid }, ...exclude], - only_speakers: onlySpeakers, - ignore_devices: ignoreDevices, - only_default_speakers: onlyDefaultSpeakers - }; - - if (ignoreInputMedia ?? true) { - data.exclude.push({ "media.class": "Stream/Input/Audio" }); - } - - if (ignoreVirtual) { - data.exclude.push({ "node.virtual": "true" }); - } - - if (workaround) { - data.workaround = [{ "application.process.id": pid, "media.name": "RecordStream" }]; - } - - return obtainVenmic()?.link(data); +ipcMain.handle(IpcEvents.VIRT_MIC_UNMUTE, () => { + return obtainVenmic()?.unmute(); }); ipcMain.handle(IpcEvents.VIRT_MIC_STOP, () => obtainVenmic()?.unlink()); diff --git a/src/preload/VesktopNative.ts b/src/preload/VesktopNative.ts index e11fae7..1dd6710 100644 --- a/src/preload/VesktopNative.ts +++ b/src/preload/VesktopNative.ts @@ -92,6 +92,7 @@ export const VesktopNative = { >(IpcEvents.VIRT_MIC_LIST), start: (include: Node[]) => invoke(IpcEvents.VIRT_MIC_START, include), startSystem: (exclude: Node[]) => invoke(IpcEvents.VIRT_MIC_START_SYSTEM, exclude), + unmute: () => invoke(IpcEvents.VIRT_MIC_UNMUTE), stop: () => invoke(IpcEvents.VIRT_MIC_STOP) }, clipboard: { diff --git a/src/renderer/components/ScreenSharePicker.tsx b/src/renderer/components/ScreenSharePicker.tsx index a5fb8a0..26e8b1b 100644 --- a/src/renderer/components/ScreenSharePicker.tsx +++ b/src/renderer/components/ScreenSharePicker.tsx @@ -122,15 +122,25 @@ addPatch({ if (isLinux) { onceReady.then(() => { - FluxDispatcher.subscribe("STREAM_CLOSE", ({ streamKey }: { streamKey: string }) => { - const owner = streamKey.split(":").at(-1); + const ownsStream = (streamKey: string) => { + return streamKey.split(":").at(-1) === UserStore.getCurrentUser().id; + }; - if (owner !== UserStore.getCurrentUser().id) { + FluxDispatcher.subscribe("STREAM_CLOSE", ({ streamKey }: { streamKey: string }) => { + if (!ownsStream(streamKey)) { return; } VesktopNative.virtmic.stop(); }); + + FluxDispatcher.subscribe("STREAM_UPDATE", ({ streamKey }: { streamKey: string }) => { + if (!ownsStream(streamKey)) { + return; + } + + VesktopNative.virtmic.unmute(); + }); }); } @@ -219,6 +229,13 @@ function AudioSettingsModal({ + (Settings.audio = { ...Settings.audio, mute: v })} + value={Settings.audio?.mute ?? true} + /> value[key] === other[key]); -} - function mapToAudioItem(node: AudioSource, granularSelect?: boolean, deviceSelect?: boolean): AudioItem[] { if (isSpecialSource(node)) { return [{ name: node, value: node }]; } - const rtn: AudioItem[] = []; - const mediaClass = node["media.class"]; if (mediaClass?.includes("Video") || mediaClass?.includes("Midi")) { - return rtn; + return []; } if (!deviceSelect && node["device.id"]) { - return rtn; + return []; } - const name = node["application.name"]; + const preferred = ["application.name", "node.description", "node.name", "application.process.binary"] as const; + const prop = preferred.find(prop => node[prop]); - if (name) { - rtn.push({ name: name, value: { "application.name": name } }); + if (!prop) { + return []; } + const name = node[prop]!; + const items: AudioItem[] = [{ name: name, value: { [prop]: name } }]; + if (!granularSelect) { - return rtn; + return items; } - const rawName = node["node.name"]; + let granularName = name; + const granularProps: Node = { [prop]: name }; - if (!name) { - rtn.push({ name: rawName, value: { "node.name": rawName } }); - } + const append = (name: string, brackets: string) => { + const value = node[name]; - const binary = node["application.process.binary"]; + if (!value) { + return; + } - if (!name && binary) { - rtn.push({ name: binary, value: { "application.process.binary": binary } }); - } + granularName += ` ${brackets[0]}${value}${brackets[1]}`; + granularProps[name] = value; + }; - const pid = node["application.process.id"]; + append("application.process.id", "<>"); + append("application.process.binary", "()"); + append("media.name", "[]"); - const first = rtn[0]; - const firstValues = first.value as Node; + items.push({ name: granularName, value: granularProps }); - if (pid) { - rtn.push({ - name: `${first.name} (${pid})`, - value: { ...firstValues, "application.process.id": pid } - }); - } + return items; +} - const mediaName = node["media.name"]; - - if (mediaName) { - rtn.push({ - name: `${first.name} [${mediaName}]`, - value: { ...firstValues, "media.name": mediaName } - }); - } - - if (mediaClass) { - rtn.push({ - name: `${first.name} [${mediaClass}]`, - value: { ...firstValues, "media.class": mediaClass } - }); - } - - return rtn; +function hasMatchingProps(value: Node, other: Node) { + const keys = Object.keys(value); + return keys.length === Object.keys(other).length && keys.every(key => value[key] === other[key]); } function isItemSelected(sources?: AudioSources) { @@ -655,9 +655,10 @@ function AudioSourcePickerLinux({ }))} isSelected={isItemSelected(includeSources)} select={updateItems(setIncludeSources, includeSources)} - serialize={String} + serialize={JSON.stringify} popoutPosition="top" closeOnSelect={false} + renderOptionLabel={o => o.label} /> @@ -675,9 +676,10 @@ function AudioSourcePickerLinux({ }))} isSelected={isItemSelected(excludeSources)} select={updateItems(setExcludeSources, excludeSources)} - serialize={String} + serialize={JSON.stringify} popoutPosition="top" closeOnSelect={false} + renderOptionLabel={o => o.label} /> diff --git a/src/shared/IpcEvents.ts b/src/shared/IpcEvents.ts index 13f27b4..55efe80 100644 --- a/src/shared/IpcEvents.ts +++ b/src/shared/IpcEvents.ts @@ -49,6 +49,7 @@ export const enum IpcEvents { VIRT_MIC_LIST = "VCD_VIRT_MIC_LIST", VIRT_MIC_START = "VCD_VIRT_MIC_START", VIRT_MIC_START_SYSTEM = "VCD_VIRT_MIC_START_ALL", + VIRT_MIC_UNMUTE = "VCD_VIRT_MIC_UNMUTE", VIRT_MIC_STOP = "VCD_VIRT_MIC_STOP", CLIPBOARD_COPY_IMAGE = "VCD_CLIPBOARD_COPY_IMAGE", diff --git a/src/shared/settings.d.ts b/src/shared/settings.d.ts index b127f6d..f992d61 100644 --- a/src/shared/settings.d.ts +++ b/src/shared/settings.d.ts @@ -48,6 +48,7 @@ export interface Settings { ignoreDevices?: boolean; ignoreInputMedia?: boolean; + mute?: boolean; onlySpeakers?: boolean; onlyDefaultSpeakers?: boolean; };