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,6 +193,7 @@ function StreamSettings({
); );
return ( return (
<div className="vcd-screen-picker-settings-grid">
<div> <div>
<Forms.FormTitle>What you're streaming</Forms.FormTitle> <Forms.FormTitle>What you're streaming</Forms.FormTitle>
<Card className="vcd-screen-picker-card vcd-screen-picker-preview"> <Card className="vcd-screen-picker-card vcd-screen-picker-preview">
@ -182,7 +209,10 @@ function StreamSettings({
<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
className="vcd-screen-picker-radio"
data-checked={settings.resolution === res}
>
<Text variant="text-sm/bold">{res}</Text> <Text variant="text-sm/bold">{res}</Text>
<input <input
type="radio" type="radio"
@ -214,7 +244,51 @@ function StreamSettings({
</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,46 +318,69 @@ 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>
) : ( )}
<Forms.FormText>
Failed to retrieve Audio Sources. If you would like to stream with Audio, make sure you're using
Pipewire, not Pulseaudio
</Forms.FormText>
))}
{allSources && ( {hasPipewirePulse || ignorePulseWarning ? (
allSources && (
<Select <Select
options={allSources.map(s => ({ label: s, value: s, default: s === "None" }))} options={allSources.map(s => ({ label: s, value: s, default: s === "None" }))}
isSelected={s => s === audioSource} isSelected={s => s === audioSource}
select={setAudioSource} select={setAudioSource}
serialize={String} serialize={String}
/> />
)
) : (
<Text variant="text-sm/normal">
Could not find pipewire-pulse. This usually means that you do not run pipewire as your main
audio-server. <br />
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>
)} )}
<Forms.FormDivider className={Margins.top16 + " " + Margins.bottom16} /> <Forms.FormDivider className={Margins.top16 + " " + Margins.bottom16} />
@ -291,14 +390,30 @@ function AudioSourcePickerLinux({
value={workaround ?? false} value={workaround ?? false}
note={ note={
<> <>
Work around an issue that causes the microphone to be shared instead of the correct audio. Only Work around an issue that causes the microphone to be shared instead of the correct audio.
enable if you're experiencing this issue. Only enable if you're experiencing this issue.
</> </>
} }
> >
Microphone Workaround Microphone Workaround
</Switch> </Switch>
</section>
<Switch
hideBorder
onChange={setOnlyDefaultSpeakers}
disabled={audioSource !== "Entire System"}
value={onlyDefaultSpeakers ?? true}
note={
<>
When sharing entire desktop audio, only share apps that play to the default speakers and
ignore apps that play to other speakers or devices.
</>
}
>
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,29 +456,24 @@ 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), const conn = [...MediaEngineStore.getMediaEngine().connections].find(
maxPixelCount: width * height, connection => connection.streamUserId === UserStore.getCurrentUser().id
maxBitrate: 8000000, );
maxResolution: {
type: "fixed", if (conn) {
width, conn.videoStreamParameters[0].maxFrameRate = frameRate;
height conn.videoStreamParameters[0].maxResolution.height = height;
} conn.videoStreamParameters[0].maxResolution.width = width;
});
} }
submit({ submit({
@ -371,6 +481,38 @@ function ModalComponent({
...settings ...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;