Merge branch 'wayland-fixes' of https://github.com/kaitlynkittyy/vesktop-screenaudio into wayland-fixes

This commit is contained in:
Kaitlyn 2023-09-28 14:11:25 -04:00
commit 7409c65292
16 changed files with 217 additions and 13 deletions

View file

@ -4,3 +4,5 @@
# all permissions at the defaults (public repos read only, 0 permissions):
# https://github.com/settings/personal-access-tokens/new
GITHUB_TOKEN=
ELECTRON_LAUNCH_FLAGS="--ozone-platform-hint=auto --enable-webrtc-pipewire-capturer --enable-features=WaylandWindowDecorations"

View file

@ -34,11 +34,19 @@
"@typescript-eslint/parser": "^6.7.3",
"@vencord/types": "^0.1.2",
"dotenv": "^16.3.1",
<<<<<<< HEAD
"electron": "^26.2.2",
"electron-builder": "^24.6.4",
"esbuild": "^0.18.20",
"eslint": "^8.50.0",
"eslint-config-prettier": "^8.10.0",
=======
"electron": "26.2.3",
"electron-builder": "^24.6.3",
"esbuild": "^0.18.17",
"eslint": "^8.46.0",
"eslint-config-prettier": "^8.9.0",
>>>>>>> 2e5c450b14553561ad6ca505152d2a93766ca138
"eslint-import-resolver-alias": "^1.1.2",
"eslint-plugin-license-header": "^0.6.0",
"eslint-plugin-path-alias": "^1.0.0",

View file

@ -35,8 +35,13 @@ devDependencies:
specifier: ^16.3.1
version: 16.3.1
electron:
<<<<<<< HEAD
specifier: ^26.2.2
version: 26.2.2
=======
specifier: 26.2.3
version: 26.2.3
>>>>>>> 2e5c450b14553561ad6ca505152d2a93766ca138
electron-builder:
specifier: ^24.6.4
version: 24.6.4
@ -1610,8 +1615,13 @@ packages:
- supports-color
dev: true
<<<<<<< HEAD
/electron@26.2.2:
resolution: {integrity: sha512-Ihb3Zt4XYnHF52DYSq17ySkgFqJV4OT0VnfhUYZASAql7Vembz3VsAq7mB3OALBHXltAW34P8BxTIwTqZaMS3g==}
=======
/electron@26.2.3:
resolution: {integrity: sha512-osdKf9mbhrqE81ITdvQ7TjVOayXfcAlWm8A6EtBt/eFSh7a/FijebGVkgs0S7qWQdhO0KaNZDb1Gx00sWuDQdw==}
>>>>>>> 2e5c450b14553561ad6ca505152d2a93766ca138
engines: {node: '>= 12.20.55'}
hasBin: true
requiresBuild: true

View file

@ -8,4 +8,4 @@ import "./utils/dotenv";
import { spawnNodeModuleBin } from "./utils/spawn.mjs";
spawnNodeModuleBin("electron", ["."]);
spawnNodeModuleBin("electron", [".", ...(process.env.ELECTRON_LAUNCH_FLAGS?.split(" ") ?? [])]);

View file

@ -4,6 +4,8 @@
* Copyright (c) 2023 Vendicated and Vencord contributors
*/
if (process.platform === "linux") import("./virtmic");
import { execFile } from "child_process";
import { app, dialog, RelaunchOptions, session, shell } from "electron";
import { mkdirSync, readFileSync, watch } from "fs";

View file

@ -32,7 +32,10 @@ export function registerScreenShareHandler() {
}
}).catch(() => null);
if (sources === null) return callback({});
<<<<<<< HEAD
=======
>>>>>>> 2e5c450b14553561ad6ca505152d2a93766ca138
const isWayland =
process.platform === "linux" &&
(process.env.XDG_SESSION_TYPE === "wayland" || !!process.env.WAYLAND_DISPLAY);
@ -45,15 +48,27 @@ export function registerScreenShareHandler() {
if (isWayland) {
const video = data[0];
<<<<<<< HEAD
getAudioFromVirtmic();
callback(video ? { video } : {});
=======
if (video)
await request.frame.executeJavaScript(
`Vesktop.Components.ScreenShare.openScreenSharePicker(${JSON.stringify([video])}, true)`
);
callback(video ? { video: sources[0] } : {});
>>>>>>> 2e5c450b14553561ad6ca505152d2a93766ca138
return;
}
const choice = await request.frame
.executeJavaScript(`Vesktop.Components.ScreenShare.openScreenSharePicker(${JSON.stringify(data)})`)
.executeJavaScript(`Vesktop.Components.ScreenShare.openScreenSharePicker(${JSON.stringify(data)}, false)`)
.then(e => e as StreamPick)
.catch(() => null);
.catch(e => {
console.error("Error during screenshare picker", e);
return null;
});
if (!choice) return callback({});

54
src/main/virtmic.ts Normal file
View file

@ -0,0 +1,54 @@
/*
* SPDX-License-Identifier: GPL-3.0
* Vesktop, a desktop app aiming to give you a snappier Discord Experience
* Copyright (c) 2023 Vendicated and Vencord contributors
*/
import { ChildProcess, execFile } from "child_process";
import { ipcMain } from "electron";
import { join } from "path";
import { IpcEvents } from "shared/IpcEvents";
import { STATIC_DIR } from "shared/paths";
import { promisify } from "util";
const BIN = join(STATIC_DIR, "virtmic/vencord-virtmic");
const execFileP = promisify(execFile);
ipcMain.handle(IpcEvents.VIRT_MIC_LIST, async () => {
return execFileP(BIN, ["--list-targets"])
.then(res =>
res.stdout
.trim()
.split("\n")
.map(s => s.trim())
.filter(Boolean)
)
.catch(e => {
console.error("virt-mic-list failed", e);
return null;
});
});
let virtMicProc: ChildProcess | null = null;
function kill() {
virtMicProc?.kill();
virtMicProc = null;
}
ipcMain.handle(IpcEvents.VIRT_MIC_START, (_, target: string) => {
kill();
return new Promise<boolean>(resolve => {
virtMicProc = execFile(BIN, [target], { encoding: "utf-8" });
virtMicProc.stdout?.on("data", (chunk: string) => {
if (chunk.includes("vencord-virtmic")) resolve(true);
});
virtMicProc.on("error", () => resolve(false));
virtMicProc.on("exit", () => resolve(false));
setTimeout(() => resolve(false), 1000);
});
});
ipcMain.handle(IpcEvents.VIRT_MIC_KILL, () => kill());

View file

@ -58,5 +58,12 @@ export const VesktopNative = {
},
capturer: {
getLargeThumbnail: (id: string) => invoke<string>(IpcEvents.CAPTURER_GET_LARGE_THUMBNAIL, id)
},
/** only available on Linux. */
virtmic: {
list: () => invoke<string[] | null>(IpcEvents.VIRT_MIC_LIST),
start: (target: string) => invoke<void>(IpcEvents.VIRT_MIC_START, target),
kill: () => invoke<void>(IpcEvents.VIRT_MIC_KILL)
}
};

View file

@ -8,10 +8,10 @@ import "./screenSharePicker.css";
import { closeModal, Modals, openModal, useAwaiter } from "@vencord/types/utils";
import { findStoreLazy } from "@vencord/types/webpack";
import { Button, Card, Forms, Switch, Text, useState } from "@vencord/types/webpack/common";
import { Button, Card, Forms, Select, Switch, Text, useState } from "@vencord/types/webpack/common";
import type { Dispatch, SetStateAction } from "react";
import { addPatch } from "renderer/patches/shared";
import { isWindows } from "renderer/utils";
import { isLinux, isWindows } from "renderer/utils";
const StreamResolutions = ["480", "720", "1080", "1440"] as const;
const StreamFps = ["15", "30", "60"] as const;
@ -25,6 +25,7 @@ interface StreamSettings {
resolution: StreamResolution;
fps: StreamFps;
audio: boolean;
audioSource?: string;
}
export interface StreamPick extends StreamSettings {
@ -70,18 +71,24 @@ addPatch({
}
});
export function openScreenSharePicker(screens: Source[]) {
export function openScreenSharePicker(screens: Source[], skipPicker: boolean) {
let didSubmit = false;
return new Promise<StreamPick>((resolve, reject) => {
const key = openModal(
props => (
<ModalComponent
screens={screens}
modalProps={props}
submit={resolve}
submit={async v => {
didSubmit = true;
if (v.audioSource && v.audioSource !== "None") await VesktopNative.virtmic.start(v.audioSource);
resolve(v);
}}
close={() => {
props.onClose();
reject("Aborted");
if (!didSubmit) reject("Aborted");
}}
skipPicker={skipPicker}
/>
),
{
@ -109,7 +116,7 @@ function ScreenPicker({ screens, chooseScreen }: { screens: Source[]; chooseScre
);
}
function StreamSettings({
export function StreamSettings({
source,
settings,
setSettings
@ -182,23 +189,65 @@ function StreamSettings({
Stream With Audio
</Switch>
)}
{isLinux && (
<AudioSourcePickerLinux
audioSource={settings.audioSource}
setAudioSource={source => setSettings(s => ({ ...s, audioSource: source }))}
/>
)}
</Card>
</div>
);
}
function AudioSourcePickerLinux({
audioSource,
setAudioSource
}: {
audioSource?: string;
setAudioSource(s: string): void;
}) {
const [sources, _, loading] = useAwaiter(() => VesktopNative.virtmic.list(), { fallbackValue: [] });
const sourcesWithNone = sources ? ["None", ...sources] : null;
return (
<section>
<Forms.FormTitle>Audio</Forms.FormTitle>
{loading && <Forms.FormTitle>Loading Audio sources...</Forms.FormTitle>}
{sourcesWithNone === null && (
<Forms.FormTitle>
Failed to retrieve Audio Sources. If you would like to stream with Audio, make sure you're using
Pipewire, not Pulseaudio
</Forms.FormTitle>
)}
{sourcesWithNone && (
<Select
options={sourcesWithNone.map(s => ({ label: s, value: s, default: s === "None" }))}
isSelected={s => s === audioSource}
select={setAudioSource}
serialize={String}
/>
)}
</section>
);
}
function ModalComponent({
screens,
modalProps,
submit,
close
close,
skipPicker
}: {
screens: Source[];
modalProps: any;
submit: (data: StreamPick) => void;
close: () => void;
skipPicker: boolean;
}) {
const [selected, setSelected] = useState<string>();
const [selected, setSelected] = useState<string | undefined>(skipPicker ? screens[0].id : void 0);
const [settings, setSettings] = useState<StreamSettings>({
resolution: "1080",
fps: "60",
@ -259,7 +308,7 @@ function ModalComponent({
Go Live
</Button>
{selected ? (
{selected && !skipPicker ? (
<Button color={Button.Colors.TRANSPARENT} onClick={() => setSelected(void 0)}>
Back
</Button>

View file

@ -8,3 +8,4 @@
import "./spellCheck";
import "./platformClass";
import "./windowsTitleBar";
import "./screenShareAudio";

View file

@ -0,0 +1,42 @@
/*
* SPDX-License-Identifier: GPL-3.0
* Vesktop, a desktop app aiming to give you a snappier Discord Experience
* Copyright (c) 2023 Vendicated and Vencord contributors
*/
import { isLinux } from "renderer/utils";
if (isLinux) {
const original = navigator.mediaDevices.getDisplayMedia;
async function getVirtmic() {
try {
const devices = await navigator.mediaDevices.enumerateDevices();
const audioDevice = devices.find(({ label }) => label === "vencord-virtmic");
return audioDevice?.deviceId;
} catch (error) {
return null;
}
}
navigator.mediaDevices.getDisplayMedia = async function (opts) {
const stream = await original.call(this, opts);
const id = await getVirtmic();
if (id) {
const audio = await navigator.mediaDevices.getUserMedia({
audio: {
deviceId: {
exact: id
},
autoGainControl: false,
echoCancellation: false,
noiseSuppression: false
}
});
audio.getAudioTracks().forEach(t => stream.addTrack(t));
}
return stream;
};
}

View file

@ -17,3 +17,4 @@ const { platform } = navigator;
export const isWindows = platform.startsWith("Win");
export const isMac = platform.startsWith("Mac");
export const isLinux = platform.startsWith("Linux");

View file

@ -40,5 +40,9 @@ export const enum IpcEvents {
AUTOSTART_ENABLED = "VCD_AUTOSTART_ENABLED",
ENABLE_AUTOSTART = "VCD_ENABLE_AUTOSTART",
DISABLE_AUTOSTART = "VCD_DISABLE_AUTOSTART"
DISABLE_AUTOSTART = "VCD_DISABLE_AUTOSTART",
VIRT_MIC_LIST = "VCD_VIRT_MIC_LIST",
VIRT_MIC_START = "VCD_VIRT_MIC_START",
VIRT_MIC_KILL = "VCD_VIRT_MIC_STOP"
}

View file

@ -51,6 +51,10 @@
<a href="https://github.com/OpenAsar/arrpc" target="_blank">arrpc</a>
- An open implementation of Discord's Rich Presence server
</li>
<li>
<a href="https://github.com/Soundux/rohrkabel" target="_blank">rohrkabel</a>
- A C++ RAII Pipewire-API Wrapper
</li>
<li>
And many
<a href="https://github.com/Vencord/Vesktop/blob/main/pnpm-lock.yaml" target="_blank"

5
static/virtmic/README.md Normal file
View file

@ -0,0 +1,5 @@
# vencord virtmic
this is a prebuild of https://github.com/Vencord/linux-virtmic.
the source code and licensing information can be found there

BIN
static/virtmic/vencord-virtmic Executable file

Binary file not shown.