Merge branch 'wayland-fixes' of https://github.com/kaitlynkittyy/vesktop-screenaudio into wayland-fixes
This commit is contained in:
commit
7409c65292
16 changed files with 217 additions and 13 deletions
|
@ -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"
|
|
@ -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",
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -8,4 +8,4 @@ import "./utils/dotenv";
|
|||
|
||||
import { spawnNodeModuleBin } from "./utils/spawn.mjs";
|
||||
|
||||
spawnNodeModuleBin("electron", ["."]);
|
||||
spawnNodeModuleBin("electron", [".", ...(process.env.ELECTRON_LAUNCH_FLAGS?.split(" ") ?? [])]);
|
||||
|
|
|
@ -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";
|
||||
|
|
|
@ -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
54
src/main/virtmic.ts
Normal 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());
|
|
@ -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)
|
||||
}
|
||||
};
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -8,3 +8,4 @@
|
|||
import "./spellCheck";
|
||||
import "./platformClass";
|
||||
import "./windowsTitleBar";
|
||||
import "./screenShareAudio";
|
||||
|
|
42
src/renderer/patches/screenShareAudio.ts
Normal file
42
src/renderer/patches/screenShareAudio.ts
Normal 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;
|
||||
};
|
||||
}
|
|
@ -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");
|
||||
|
|
|
@ -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"
|
||||
}
|
||||
|
|
|
@ -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
5
static/virtmic/README.md
Normal 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
BIN
static/virtmic/vencord-virtmic
Executable file
Binary file not shown.
Reference in a new issue