Merge branch 'main' into isMaximized-in-renderer

This commit is contained in:
Sqaaakoi 2024-04-26 04:18:16 +12:00
commit 7ed49726f8
No known key found for this signature in database
16 changed files with 454 additions and 163 deletions

View file

@ -24,10 +24,10 @@
"updateMeta": "tsx scripts/utils/updateMeta.mts" "updateMeta": "tsx scripts/utils/updateMeta.mts"
}, },
"dependencies": { "dependencies": {
"arrpc": "github:OpenAsar/arrpc#98879cae0565e6fce34e4cb6f544bf42c6a7e7c8" "arrpc": "github:OpenAsar/arrpc#6960a8fd4d65d566da93dbdb8a7ca474aa0a3c9c"
}, },
"optionalDependencies": { "optionalDependencies": {
"@vencord/venmic": "^3.3.2" "@vencord/venmic": "^3.4.2"
}, },
"devDependencies": { "devDependencies": {
"@fal-works/esbuild-plugin-global-externals": "^2.1.2", "@fal-works/esbuild-plugin-global-externals": "^2.1.2",

View file

@ -6,13 +6,13 @@ settings:
dependencies: dependencies:
arrpc: arrpc:
specifier: github:OpenAsar/arrpc#98879cae0565e6fce34e4cb6f544bf42c6a7e7c8 specifier: github:OpenAsar/arrpc#6960a8fd4d65d566da93dbdb8a7ca474aa0a3c9c
version: github.com/OpenAsar/arrpc/98879cae0565e6fce34e4cb6f544bf42c6a7e7c8 version: github.com/OpenAsar/arrpc/6960a8fd4d65d566da93dbdb8a7ca474aa0a3c9c
optionalDependencies: optionalDependencies:
'@vencord/venmic': '@vencord/venmic':
specifier: ^3.3.2 specifier: ^3.4.2
version: 3.3.2 version: 3.4.2
devDependencies: devDependencies:
'@fal-works/esbuild-plugin-global-externals': '@fal-works/esbuild-plugin-global-externals':
@ -1000,14 +1000,14 @@ packages:
type-fest: 3.13.1 type-fest: 3.13.1
dev: true dev: true
/@vencord/venmic@3.3.2: /@vencord/venmic@3.4.2:
resolution: {integrity: sha512-fwGr5v+Xe4EisKxxhTDlUDamBGlBzSK3+yYZH/7d9ui1j8Q78wCbE1iP8MnDjBvV2kIKn/xV/84wjt9iQvDkFw==} resolution: {integrity: sha512-nwGjarof1wVvecGksGONfb+PduhY4gSuHTm39LktiQayHS69+yv4lepq4k1lxZzjOgFTy5VXfmJqxt+BpqoUUQ==}
engines: {node: '>=14.15'} engines: {node: '>=14.15'}
os: [linux] os: [linux]
requiresBuild: true requiresBuild: true
dependencies: dependencies:
cmake-js: 7.3.0 cmake-js: 7.3.0
node-addon-api: 7.1.0 node-addon-api: 8.0.0
pkg-prebuilds: 0.2.1 pkg-prebuilds: 0.2.1
transitivePeerDependencies: transitivePeerDependencies:
- supports-color - supports-color
@ -1333,11 +1333,11 @@ packages:
possible-typed-array-names: 1.0.0 possible-typed-array-names: 1.0.0
dev: true dev: true
/axios@1.6.7(debug@4.3.4): /axios@1.6.8(debug@4.3.4):
resolution: {integrity: sha512-/hDJGff6/c7u0hDkvkGxR/oy6CbCs8ziCsC7SqmhjfozqiJGc8Z11wrv9z9lYfY4K8l+H9TpjcMDX0xOZmx+RA==} resolution: {integrity: sha512-v/ZHtJDU39mDpyBoFVkETcd/uNdxrWRrg3bKpOKzXFA6Bvqopts6ALSMU3y6ijYxbw2B+wPrIv46egTzJXCLGQ==}
requiresBuild: true requiresBuild: true
dependencies: dependencies:
follow-redirects: 1.15.5(debug@4.3.4) follow-redirects: 1.15.6(debug@4.3.4)
form-data: 4.0.0 form-data: 4.0.0
proxy-from-env: 1.1.0 proxy-from-env: 1.1.0
transitivePeerDependencies: transitivePeerDependencies:
@ -1576,7 +1576,7 @@ packages:
hasBin: true hasBin: true
requiresBuild: true requiresBuild: true
dependencies: dependencies:
axios: 1.6.7(debug@4.3.4) axios: 1.6.8(debug@4.3.4)
debug: 4.3.4 debug: 4.3.4
fs-extra: 11.2.0 fs-extra: 11.2.0
lodash.isplainobject: 4.0.6 lodash.isplainobject: 4.0.6
@ -2540,8 +2540,8 @@ packages:
resolution: {integrity: sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==} resolution: {integrity: sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==}
dev: true dev: true
/follow-redirects@1.15.5(debug@4.3.4): /follow-redirects@1.15.6(debug@4.3.4):
resolution: {integrity: sha512-vSFWUON1B+yAw1VN4xMfxgn5fTUiaOzAJCKBwIIgT/+7CuGy9+r+5gITvP62j3RmaD5Ph65UaERdOSRGUzZtgw==} resolution: {integrity: sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==}
engines: {node: '>=4.0'} engines: {node: '>=4.0'}
requiresBuild: true requiresBuild: true
peerDependencies: peerDependencies:
@ -3581,9 +3581,9 @@ packages:
dev: true dev: true
optional: true optional: true
/node-addon-api@7.1.0: /node-addon-api@8.0.0:
resolution: {integrity: sha512-mNcltoe1R8o7STTegSOHdnJNN7s5EUvhoS7ShnTHDyOSd+8H+UdWODq6qSv67PjC8Zc5JRT8+oLAMCr0SIXw7g==} resolution: {integrity: sha512-ipO7rsHEBqa9STO5C5T10fj732ml+5kLN1cAG8/jdHd56ldQeGj3Q7+scUS+VHK/qy1zLEwC4wMK5+yM0btPvw==}
engines: {node: ^16 || ^18 || >= 20} engines: {node: ^18 || ^20 || >= 21}
requiresBuild: true requiresBuild: true
dev: false dev: false
optional: true optional: true
@ -4783,10 +4783,10 @@ packages:
readable-stream: 3.6.2 readable-stream: 3.6.2
dev: true dev: true
github.com/OpenAsar/arrpc/98879cae0565e6fce34e4cb6f544bf42c6a7e7c8: github.com/OpenAsar/arrpc/6960a8fd4d65d566da93dbdb8a7ca474aa0a3c9c:
resolution: {tarball: https://codeload.github.com/OpenAsar/arrpc/tar.gz/98879cae0565e6fce34e4cb6f544bf42c6a7e7c8} resolution: {tarball: https://codeload.github.com/OpenAsar/arrpc/tar.gz/6960a8fd4d65d566da93dbdb8a7ca474aa0a3c9c}
name: arrpc name: arrpc
version: 3.2.0 version: 3.3.1
hasBin: true hasBin: true
dependencies: dependencies:
ws: 8.13.0 ws: 8.13.0

View file

@ -27,14 +27,18 @@ process.env.VENCORD_USER_DATA_DIR = DATA_DIR;
function init() { function init() {
const { disableSmoothScroll, hardwareAcceleration } = Settings.store; const { disableSmoothScroll, hardwareAcceleration } = Settings.store;
if (hardwareAcceleration === false) app.disableHardwareAcceleration(); if (hardwareAcceleration === false) {
app.disableHardwareAcceleration();
} else {
app.commandLine.appendSwitch("enable-features", "VaapiVideoDecodeLinuxGL,VaapiVideoEncoder,VaapiVideoDecoder");
}
if (disableSmoothScroll) { if (disableSmoothScroll) {
app.commandLine.appendSwitch("disable-smooth-scrolling"); app.commandLine.appendSwitch("disable-smooth-scrolling");
} }
// work around chrome 66 disabling autoplay by default // work around chrome 66 disabling autoplay by default
app.commandLine.appendSwitch("autoplay-policy", "no-user-gesture-required"); app.commandLine.appendSwitch("autoplay-policy", "no-user-gesture-required");
// WinRetrieveSuggestionsOnlyOnDemand: Work around electron 13 bug w/ async spellchecking on Windows. // WinRetrieveSuggestionsOnlyOnDemand: Work around electron 13 bug w/ async spellchecking on Windows.
// HardwareMediaKeyHandling,MediaSessionService: Prevent Discord from registering as a media service. // HardwareMediaKeyHandling,MediaSessionService: Prevent Discord from registering as a media service.
// //

View file

@ -73,6 +73,10 @@ const [addSettingsListener, removeSettingsListeners] = makeSettingsListenerHelpe
const [addVencordSettingsListener, removeVencordSettingsListeners] = makeSettingsListenerHelpers(VencordSettings); const [addVencordSettingsListener, removeVencordSettingsListeners] = makeSettingsListenerHelpers(VencordSettings);
function initTray(win: BrowserWindow) { function initTray(win: BrowserWindow) {
const onTrayClick = () => {
if (Settings.store.clickTrayToShowHide && win.isVisible()) win.hide();
else win.show();
};
const trayMenu = Menu.buildFromTemplate([ const trayMenu = Menu.buildFromTemplate([
{ {
label: "Open", label: "Open",
@ -120,7 +124,7 @@ function initTray(win: BrowserWindow) {
tray = new Tray(ICON_PATH); tray = new Tray(ICON_PATH);
tray.setToolTip("Vesktop"); tray.setToolTip("Vesktop");
tray.setContextMenu(trayMenu); tray.setContextMenu(trayMenu);
tray.on("click", () => win.show()); tray.on("click", onTrayClick);
} }
async function clearData(win: BrowserWindow) { async function clearData(win: BrowserWindow) {

View file

@ -4,17 +4,58 @@
* Copyright (c) 2023 Vendicated and Vencord contributors * Copyright (c) 2023 Vendicated and Vencord contributors
*/ */
import type { PatchBay } from "@vencord/venmic"; import type { PatchBay as PatchBayType } from "@vencord/venmic";
import { app, ipcMain } from "electron"; import { app, ipcMain } from "electron";
import { join } from "path"; import { join } from "path";
import { IpcEvents } from "shared/IpcEvents"; import { IpcEvents } from "shared/IpcEvents";
import { STATIC_DIR } from "shared/paths"; import { STATIC_DIR } from "shared/paths";
type LinkData = Parameters<PatchBay["link"]>[0]; type LinkData = Parameters<PatchBayType["link"]>[0];
let PatchBay: typeof PatchBayType | undefined;
let patchBayInstance: PatchBayType | undefined;
let imported = false;
let initialized = false; let initialized = false;
let patchBay: import("@vencord/venmic").PatchBay | undefined;
let isGlibcxxToOld = false; let hasPipewirePulse = false;
let isGlibCxxOutdated = false;
function importVenmic() {
if (imported) {
return;
}
imported = true;
try {
PatchBay = (require(join(STATIC_DIR, `dist/venmic-${process.arch}.node`)) as typeof import("@vencord/venmic"))
.PatchBay;
hasPipewirePulse = PatchBay.hasPipeWire();
} catch (e: any) {
console.error("Failed to import venmic", e);
isGlibCxxOutdated = (e?.stack || e?.message || "").toLowerCase().includes("glibc");
}
}
function obtainVenmic() {
if (!imported) {
importVenmic();
}
if (PatchBay && !initialized) {
initialized = true;
try {
patchBayInstance = new PatchBay();
} catch (e: any) {
console.error("Failed to instantiate venmic", e);
}
}
return patchBayInstance;
}
function getRendererAudioServicePid() { function getRendererAudioServicePid() {
return ( return (
@ -25,33 +66,17 @@ function getRendererAudioServicePid() {
); );
} }
function obtainVenmic() {
if (!initialized) {
initialized = true;
try {
const { PatchBay } = require(
join(STATIC_DIR, `dist/venmic-${process.arch}.node`)
) as typeof import("@vencord/venmic");
patchBay = new PatchBay();
} catch (e: any) {
console.error("Failed to initialise venmic. Make sure you're using pipewire", e);
isGlibcxxToOld = (e?.stack || e?.message || "").toLowerCase().includes("glibc");
}
}
return patchBay;
}
ipcMain.handle(IpcEvents.VIRT_MIC_LIST, () => { ipcMain.handle(IpcEvents.VIRT_MIC_LIST, () => {
const audioPid = getRendererAudioServicePid(); const audioPid = getRendererAudioServicePid();
const list = obtainVenmic() const list = obtainVenmic()
?.list() ?.list()
.filter(s => s["application.process.id"] !== audioPid) .filter(s => s["application.process.id"] !== audioPid)
.map(s => s["application.name"]); .map(s => s["application.name"]);
return list const uniqueTargets = [...new Set(list)];
? { ok: true, targets: [...new Set(list)] } // Remove duplicates
: { ok: false, isGlibcxxToOld }; return list ? { ok: true, targets: uniqueTargets, hasPipewirePulse } : { ok: false, isGlibCxxOutdated };
}); });
ipcMain.handle(IpcEvents.VIRT_MIC_START, (_, targets: string[], workaround?: boolean) => { ipcMain.handle(IpcEvents.VIRT_MIC_START, (_, targets: string[], workaround?: boolean) => {
@ -72,11 +97,12 @@ ipcMain.handle(IpcEvents.VIRT_MIC_START, (_, targets: string[], workaround?: boo
return obtainVenmic()?.link(data); return obtainVenmic()?.link(data);
}); });
ipcMain.handle(IpcEvents.VIRT_MIC_START_SYSTEM, (_, workaround?: boolean) => { ipcMain.handle(IpcEvents.VIRT_MIC_START_SYSTEM, (_, workaround?: boolean, onlyDefaultSpeakers?: boolean) => {
const pid = getRendererAudioServicePid(); const pid = getRendererAudioServicePid();
const data: LinkData = { const data: LinkData = {
exclude: [{ key: "application.process.id", value: pid }] exclude: [{ key: "application.process.id", value: pid }],
only_default_speakers: onlyDefaultSpeakers
}; };
if (workaround) { if (workaround) {

View file

@ -77,9 +77,12 @@ export const VesktopNative = {
/** only available on Linux. */ /** only available on Linux. */
virtmic: { virtmic: {
list: () => list: () =>
invoke<{ ok: false; isGlibcxxToOld: boolean } | { ok: true; targets: string[] }>(IpcEvents.VIRT_MIC_LIST), invoke<
{ ok: false; isGlibCxxOutdated: boolean } | { ok: true; targets: string[]; hasPipewirePulse: boolean }
>(IpcEvents.VIRT_MIC_LIST),
start: (targets: string[], workaround?: boolean) => invoke<void>(IpcEvents.VIRT_MIC_START, targets, workaround), start: (targets: string[], workaround?: boolean) => invoke<void>(IpcEvents.VIRT_MIC_START, targets, workaround),
startSystem: (workaround?: boolean) => invoke<void>(IpcEvents.VIRT_MIC_START_SYSTEM, workaround), startSystem: (workaround?: boolean, onlyDefaultSpeakers?: boolean) =>
invoke<void>(IpcEvents.VIRT_MIC_START_SYSTEM, workaround, onlyDefaultSpeakers),
stop: () => invoke<void>(IpcEvents.VIRT_MIC_STOP) stop: () => invoke<void>(IpcEvents.VIRT_MIC_STOP)
}, },
arrpc: { arrpc: {

View file

@ -6,7 +6,7 @@
import "./screenSharePicker.css"; import "./screenSharePicker.css";
import { closeModal, Margins, Modals, openModal, useAwaiter } from "@vencord/types/utils"; import { closeModal, Logger, Margins, Modals, ModalSize, openModal, useAwaiter } from "@vencord/types/utils";
import { findStoreLazy, onceReady } from "@vencord/types/webpack"; import { findStoreLazy, onceReady } from "@vencord/types/webpack";
import { import {
Button, Button,
@ -36,7 +36,9 @@ interface StreamSettings {
fps: StreamFps; fps: StreamFps;
audio: boolean; audio: boolean;
audioSource?: string; audioSource?: string;
contentHint?: string;
workaround?: boolean; workaround?: boolean;
onlyDefaultSpeakers?: boolean;
} }
export interface StreamPick extends StreamSettings { export interface StreamPick extends StreamSettings {
@ -49,7 +51,9 @@ interface Source {
url: string; url: string;
} }
let currentSettings: StreamSettings | null = null; export let currentSettings: StreamSettings | null = null;
const logger = new Logger("VesktopScreenShare");
addPatch({ addPatch({
patches: [ patches: [
@ -59,6 +63,20 @@ addPatch({
match: /this.localWant=/, match: /this.localWant=/,
replace: "$self.patchStreamQuality(this);$&" replace: "$self.patchStreamQuality(this);$&"
} }
},
{
find: "x-google-max-bitrate",
replacement: [
{
// eslint-disable-next-line no-useless-escape
match: /"x-google-max-bitrate=".concat\(\i\)/,
replace: '"x-google-max-bitrate=".concat("80_000")'
},
{
match: /;level-asymmetry-allowed=1/,
replace: ";b=AS:800000;level-asymmetry-allowed=1"
}
]
} }
], ],
patchStreamQuality(opts: any) { patchStreamQuality(opts: any) {
@ -73,6 +91,14 @@ addPatch({
bitrateMax: 8000000, bitrateMax: 8000000,
bitrateTarget: 600000 bitrateTarget: 600000
}); });
if (opts?.encode) {
Object.assign(opts.encode, {
framerate,
width,
height,
pixelCount: height * width
});
}
Object.assign(opts.capture, { Object.assign(opts.capture, {
framerate, framerate,
width, width,
@ -167,54 +193,102 @@ function StreamSettings({
); );
return ( return (
<div> <div className="vcd-screen-picker-settings-grid">
<Forms.FormTitle>What you're streaming</Forms.FormTitle> <div>
<Card className="vcd-screen-picker-card vcd-screen-picker-preview"> <Forms.FormTitle>What you're streaming</Forms.FormTitle>
<img src={thumb} alt="" /> <Card className="vcd-screen-picker-card vcd-screen-picker-preview">
<Text variant="text-sm/normal">{source.name}</Text> <img src={thumb} alt="" />
</Card> <Text variant="text-sm/normal">{source.name}</Text>
</Card>
<Forms.FormTitle>Stream Settings</Forms.FormTitle> <Forms.FormTitle>Stream Settings</Forms.FormTitle>
<Card className="vcd-screen-picker-card"> <Card className="vcd-screen-picker-card">
<div className="vcd-screen-picker-quality"> <div className="vcd-screen-picker-quality">
<section> <section>
<Forms.FormTitle>Resolution</Forms.FormTitle> <Forms.FormTitle>Resolution</Forms.FormTitle>
<div className="vcd-screen-picker-radios"> <div className="vcd-screen-picker-radios">
{StreamResolutions.map(res => ( {StreamResolutions.map(res => (
<label className="vcd-screen-picker-radio" data-checked={settings.resolution === res}> <label
<Text variant="text-sm/bold">{res}</Text> className="vcd-screen-picker-radio"
<input data-checked={settings.resolution === res}
type="radio" >
name="resolution" <Text variant="text-sm/bold">{res}</Text>
value={res} <input
checked={settings.resolution === res} type="radio"
onChange={() => setSettings(s => ({ ...s, resolution: res }))} name="resolution"
/> value={res}
</label> checked={settings.resolution === res}
))} onChange={() => setSettings(s => ({ ...s, resolution: res }))}
</div> />
</section> </label>
))}
</div>
</section>
<section> <section>
<Forms.FormTitle>Frame Rate</Forms.FormTitle> <Forms.FormTitle>Frame Rate</Forms.FormTitle>
<div className="vcd-screen-picker-radios"> <div className="vcd-screen-picker-radios">
{StreamFps.map(fps => ( {StreamFps.map(fps => (
<label className="vcd-screen-picker-radio" data-checked={settings.fps === fps}> <label className="vcd-screen-picker-radio" data-checked={settings.fps === fps}>
<Text variant="text-sm/bold">{fps}</Text> <Text variant="text-sm/bold">{fps}</Text>
<input <input
type="radio" type="radio"
name="fps" name="fps"
value={fps} value={fps}
checked={settings.fps === fps} checked={settings.fps === fps}
onChange={() => setSettings(s => ({ ...s, fps }))} onChange={() => setSettings(s => ({ ...s, fps }))}
/> />
</label> </label>
))} ))}
</div> </div>
</section> </section>
</div> </div>
<div className="vcd-screen-picker-quality">
<section>
<Forms.FormTitle>Content Type</Forms.FormTitle>
<div>
<div className="vcd-screen-picker-radios">
<label
className="vcd-screen-picker-radio"
data-checked={settings.contentHint === "motion"}
>
<Text variant="text-sm/bold">Prefer Smoothness</Text>
<input
type="radio"
name="contenthint"
value="motion"
checked={settings.contentHint === "motion"}
onChange={() => setSettings(s => ({ ...s, contentHint: "motion" }))}
/>
</label>
<label
className="vcd-screen-picker-radio"
data-checked={settings.contentHint === "detail"}
>
<Text variant="text-sm/bold">Prefer Clarity</Text>
<input
type="radio"
name="contenthint"
value="detail"
checked={settings.contentHint === "detail"}
onChange={() => setSettings(s => ({ ...s, contentHint: "detail" }))}
/>
</label>
</div>
<div className="vcd-screen-picker-hint-description">
<p>
Choosing "Prefer Clarity" will result in a significantly lower framerate in
exchange for a much sharper and clearer image.
</p>
</div>
</div>
</section>
</div>
</Card>
</div>
<div>
{isWindows && ( {isWindows && (
<Switch <Switch
value={settings.audio} value={settings.audio}
@ -230,11 +304,13 @@ function StreamSettings({
<AudioSourcePickerLinux <AudioSourcePickerLinux
audioSource={settings.audioSource} audioSource={settings.audioSource}
workaround={settings.workaround} workaround={settings.workaround}
onlyDefaultSpeakers={settings.onlyDefaultSpeakers}
setAudioSource={source => setSettings(s => ({ ...s, audioSource: source }))} setAudioSource={source => setSettings(s => ({ ...s, audioSource: source }))}
setWorkaround={workaround => setSettings(s => ({ ...s, workaround: workaround }))} setWorkaround={value => setSettings(s => ({ ...s, workaround: value }))}
setOnlyDefaultSpeakers={value => setSettings(s => ({ ...s, onlyDefaultSpeakers: value }))}
/> />
)} )}
</Card> </div>
</div> </div>
); );
} }
@ -242,63 +318,102 @@ function StreamSettings({
function AudioSourcePickerLinux({ function AudioSourcePickerLinux({
audioSource, audioSource,
workaround, workaround,
onlyDefaultSpeakers,
setAudioSource, setAudioSource,
setWorkaround setWorkaround,
setOnlyDefaultSpeakers
}: { }: {
audioSource?: string; audioSource?: string;
workaround?: boolean; workaround?: boolean;
onlyDefaultSpeakers?: boolean;
setAudioSource(s: string): void; setAudioSource(s: string): void;
setWorkaround(b: boolean): void; setWorkaround(b: boolean): void;
setOnlyDefaultSpeakers(b: boolean): void;
}) { }) {
const [sources, _, loading] = useAwaiter(() => VesktopNative.virtmic.list(), { const [sources, _, loading] = useAwaiter(() => VesktopNative.virtmic.list(), {
fallbackValue: { ok: true, targets: [] } fallbackValue: { ok: true, targets: [], hasPipewirePulse: true }
}); });
const allSources = sources.ok ? ["None", "Entire System", ...sources.targets] : null; const allSources = sources.ok ? ["None", "Entire System", ...sources.targets] : null;
const hasPipewirePulse = sources.ok ? sources.hasPipewirePulse : true;
const [ignorePulseWarning, setIgnorePulseWarning] = useState(false);
return ( return (
<section> <>
<Forms.FormTitle>Audio</Forms.FormTitle> <Forms.FormTitle>Audio Settings</Forms.FormTitle>
{loading && <Forms.FormTitle>Loading Audio sources...</Forms.FormTitle>} <Card className="vcd-screen-picker-card">
{!sources.ok && {loading ? (
(sources.isGlibcxxToOld ? ( <Forms.FormTitle>Loading Audio Sources...</Forms.FormTitle>
) : (
<Forms.FormTitle>Audio Source</Forms.FormTitle>
)}
{!sources.ok && sources.isGlibCxxOutdated && (
<Forms.FormText> <Forms.FormText>
Failed to retrieve Audio Sources because your C++ library is too old to run venmic. If you would Failed to retrieve Audio Sources because your C++ library is too old to run
like to stream with Audio, see{" "} <a href="https://github.com/Vencord/venmic" target="_blank">
venmic
</a>
. See{" "}
<a href="https://gist.github.com/Vendicated/b655044ffbb16b2716095a448c6d827a" target="_blank"> <a href="https://gist.github.com/Vendicated/b655044ffbb16b2716095a448c6d827a" target="_blank">
this guide this guide
</a> </a>{" "}
for possible solutions.
</Forms.FormText> </Forms.FormText>
)}
{hasPipewirePulse || ignorePulseWarning ? (
allSources && (
<Select
options={allSources.map(s => ({ label: s, value: s, default: s === "None" }))}
isSelected={s => s === audioSource}
select={setAudioSource}
serialize={String}
/>
)
) : ( ) : (
<Forms.FormText> <Text variant="text-sm/normal">
Failed to retrieve Audio Sources. If you would like to stream with Audio, make sure you're using Could not find pipewire-pulse. This usually means that you do not run pipewire as your main
Pipewire, not Pulseaudio audio-server. <br />
</Forms.FormText> You can still continue, however, please beware that you can only share audio of apps that are
))} running under pipewire.
<br />
<a onClick={() => setIgnorePulseWarning(true)}>I know what I'm doing</a>
</Text>
)}
{allSources && ( <Forms.FormDivider className={Margins.top16 + " " + Margins.bottom16} />
<Select
options={allSources.map(s => ({ label: s, value: s, default: s === "None" }))}
isSelected={s => s === audioSource}
select={setAudioSource}
serialize={String}
/>
)}
<Forms.FormDivider className={Margins.top16 + " " + Margins.bottom16} /> <Switch
onChange={setWorkaround}
value={workaround ?? false}
note={
<>
Work around an issue that causes the microphone to be shared instead of the correct audio.
Only enable if you're experiencing this issue.
</>
}
>
Microphone Workaround
</Switch>
<Switch <Switch
onChange={setWorkaround} hideBorder
value={workaround ?? false} onChange={setOnlyDefaultSpeakers}
note={ disabled={audioSource !== "Entire System"}
<> value={onlyDefaultSpeakers ?? true}
Work around an issue that causes the microphone to be shared instead of the correct audio. Only note={
enable if you're experiencing this issue. <>
</> When sharing entire desktop audio, only share apps that play to the default speakers and
} ignore apps that play to other speakers or devices.
> </>
Microphone Workaround }
</Switch> >
</section> Only Default Speakers
</Switch>
</Card>
</>
); );
} }
@ -319,16 +434,16 @@ function ModalComponent({
const [settings, setSettings] = useState<StreamSettings>({ const [settings, setSettings] = useState<StreamSettings>({
resolution: "1080", resolution: "1080",
fps: "60", fps: "60",
contentHint: "motion",
audio: true audio: true
}); });
return ( return (
<Modals.ModalRoot {...modalProps}> <Modals.ModalRoot {...modalProps} size={ModalSize.MEDIUM}>
<Modals.ModalHeader className="vcd-screen-picker-header"> <Modals.ModalHeader className="vcd-screen-picker-header">
<Forms.FormTitle tag="h2">ScreenShare</Forms.FormTitle> <Forms.FormTitle tag="h2">ScreenShare</Forms.FormTitle>
<Modals.ModalCloseButton onClick={close} /> <Modals.ModalCloseButton onClick={close} />
</Modals.ModalHeader> </Modals.ModalHeader>
<Modals.ModalContent className="vcd-screen-picker-modal"> <Modals.ModalContent className="vcd-screen-picker-modal">
{!selected ? ( {!selected ? (
<ScreenPicker screens={screens} chooseScreen={setSelected} /> <ScreenPicker screens={screens} chooseScreen={setSelected} />
@ -341,35 +456,62 @@ function ModalComponent({
/> />
)} )}
</Modals.ModalContent> </Modals.ModalContent>
<Modals.ModalFooter className="vcd-screen-picker-footer"> <Modals.ModalFooter className="vcd-screen-picker-footer">
<Button <Button
disabled={!selected} disabled={!selected}
onClick={() => { onClick={() => {
currentSettings = settings; currentSettings = settings;
try {
// If there are 2 connections, the second one is the existing stream. const frameRate = Number(settings.fps);
// In that case, we patch its quality
const conn = [...MediaEngineStore.getMediaEngine().connections][1];
if (conn && conn.videoStreamParameters.length > 0) {
const height = Number(settings.resolution); const height = Number(settings.resolution);
const width = Math.round(height * (16 / 9)); const width = Math.round(height * (16 / 9));
Object.assign(conn.videoStreamParameters[0], {
maxFrameRate: Number(settings.fps),
maxPixelCount: width * height,
maxBitrate: 8000000,
maxResolution: {
type: "fixed",
width,
height
}
});
}
submit({ const conn = [...MediaEngineStore.getMediaEngine().connections].find(
id: selected!, connection => connection.streamUserId === UserStore.getCurrentUser().id
...settings );
});
if (conn) {
conn.videoStreamParameters[0].maxFrameRate = frameRate;
conn.videoStreamParameters[0].maxResolution.height = height;
conn.videoStreamParameters[0].maxResolution.width = width;
}
submit({
id: selected!,
...settings
});
setTimeout(async () => {
const conn = [...MediaEngineStore.getMediaEngine().connections].find(
connection => connection.streamUserId === UserStore.getCurrentUser().id
);
if (!conn) return;
const track = conn.input.stream.getVideoTracks()[0];
const constraints = {
...track.getConstraints(),
frameRate,
width: { min: 640, ideal: width, max: width },
height: { min: 480, ideal: height, max: height },
advanced: [{ width: width, height: height }],
resizeMode: "none"
};
try {
await track.applyConstraints(constraints);
logger.info(
"Applied constraints successfully. New constraints:",
track.getConstraints()
);
} catch (e) {
logger.error("Failed to apply constraints.", e);
}
}, 100);
} catch (error) {
logger.error("Error while submitting stream.", error);
}
close(); close();
}} }}

View file

@ -11,6 +11,21 @@
gap: 1em; gap: 1em;
} }
.vcd-screen-picker-settings-grid {
gap: 1em;
display: grid;
grid-template-columns: 1fr 1fr;
}
.vcd-screen-picker-settings-grid > div {
display: flex;
flex-direction: column;
}
.vcd-screen-picker-card {
flex-grow: 1;
}
.vcd-screen-picker-grid { .vcd-screen-picker-grid {
display: grid; display: grid;
grid-template-columns: 1fr 1fr; grid-template-columns: 1fr 1fr;
@ -122,3 +137,10 @@
.vcd-screen-picker-audio { .vcd-screen-picker-audio {
margin-bottom: 0; margin-bottom: 0;
} }
.vcd-screen-picker-hint-description {
color: var(--header-secondary);
font-size: 14px;
line-height: 20px;
font-weight: 400;
}

View file

@ -83,6 +83,12 @@ const SettingsOptions: Record<string, Array<BooleanSetting | SettingsComponent>>
invisible: () => isMac, invisible: () => isMac,
disabled: () => Settings.store.tray === false disabled: () => Settings.store.tray === false
}, },
{
key: "clickTrayToShowHide",
title: "Hide/Show on tray click",
description: "Left clicking tray icon will toggle the vesktop window visibility.",
defaultValue: false
},
{ {
key: "disableMinSize", key: "disableMinSize",
title: "Disable minimum window size", title: "Disable minimum window size",

View file

@ -3,3 +3,9 @@
[class^=listItem_]:has(+ [class^=listItem_] [data-list-item-id=guildsnav___app-download-button]) { [class^=listItem_]:has(+ [class^=listItem_] [data-list-item-id=guildsnav___app-download-button]) {
display: none; display: none;
} }
/* FIXME: remove this once Discord fixes their css to not explode scrollbars on chromium >=121 */
* {
scrollbar-width: unset !important;
scrollbar-color: unset !important;
}

View file

@ -4,7 +4,7 @@
* Copyright (c) 2023 Vendicated and Vencord contributors * Copyright (c) 2023 Vendicated and Vencord contributors
*/ */
import "./hideGarbage.css"; import "./fixes.css";
import { isWindows, localStorage } from "./utils"; import { isWindows, localStorage } from "./utils";

View file

@ -0,0 +1,24 @@
/*
* 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 { addPatch } from "./shared";
addPatch({
patches: [
{
find: "lastOutputSystemDevice.justChanged",
replacement: {
// eslint-disable-next-line no-useless-escape
match: /(\i)\.default\.getState\(\).neverShowModal/,
replace: "$& || $self.shouldIgnore($1)"
}
}
],
shouldIgnore(state: any) {
return Object.keys(state?.default?.lastDeviceConnected ?? {})?.[0] === "vencord-screen-share";
}
});

View file

@ -0,0 +1,25 @@
/*
* 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 { addPatch } from "./shared";
addPatch({
patches: [
{
find: 'setSinkId"in',
replacement: {
// eslint-disable-next-line no-useless-escape
match: /return (\i)\?navigator\.mediaDevices\.enumerateDevices/,
replace: "return $1 ? $self.filteredDevices"
}
}
],
async filteredDevices() {
const original = await navigator.mediaDevices.enumerateDevices();
return original.filter(x => x.label !== "vencord-screen-share");
}
});

View file

@ -7,6 +7,8 @@
// TODO: Possibly auto generate glob if we have more patches in the future // TODO: Possibly auto generate glob if we have more patches in the future
import "./enableNotificationsByDefault"; import "./enableNotificationsByDefault";
import "./platformClass"; import "./platformClass";
import "./screenShareAudio"; import "./hideSwitchDevice";
import "./hideVenmicInput";
import "./screenShareFixes";
import "./spellCheck"; import "./spellCheck";
import "./windowsTitleBar"; import "./windowsTitleBar";

View file

@ -4,8 +4,12 @@
* Copyright (c) 2023 Vendicated and Vencord contributors * Copyright (c) 2023 Vendicated and Vencord contributors
*/ */
import { Logger } from "@vencord/types/utils";
import { currentSettings } from "renderer/components/ScreenSharePicker";
import { isLinux } from "renderer/utils"; import { isLinux } from "renderer/utils";
const logger = new Logger("VesktopStreamFixes");
if (isLinux) { if (isLinux) {
const original = navigator.mediaDevices.getDisplayMedia; const original = navigator.mediaDevices.getDisplayMedia;
@ -23,6 +27,29 @@ if (isLinux) {
const stream = await original.call(this, opts); const stream = await original.call(this, opts);
const id = await getVirtmic(); const id = await getVirtmic();
const frameRate = Number(currentSettings?.fps);
const height = Number(currentSettings?.resolution);
const width = Math.round(height * (16 / 9));
const track = stream.getVideoTracks()[0];
track.contentHint = String(currentSettings?.contentHint);
const constraints = {
...track.getConstraints(),
frameRate,
width: { min: 640, ideal: width, max: width },
height: { min: 480, ideal: height, max: height },
advanced: [{ width: width, height: height }],
resizeMode: "none"
};
track
.applyConstraints(constraints)
.then(() => {
logger.info("Applied constraints successfully. New constraints: ", track.getConstraints());
})
.catch(e => logger.error("Failed to apply constraints.", e));
if (id) { if (id) {
const audio = await navigator.mediaDevices.getUserMedia({ const audio = await navigator.mediaDevices.getUserMedia({
audio: { audio: {

View file

@ -20,7 +20,7 @@ export interface Settings {
arRPC?: boolean; arRPC?: boolean;
appBadge?: boolean; appBadge?: boolean;
disableMinSize?: boolean; disableMinSize?: boolean;
clickTrayToShowHide?: boolean;
/** @deprecated use customTitleBar */ /** @deprecated use customTitleBar */
discordWindowsTitleBar?: boolean; discordWindowsTitleBar?: boolean;
customTitleBar?: boolean; customTitleBar?: boolean;