Merge branch 'main' into global-shortcuts
# Conflicts: # src/renderer/components/settings/Settings.tsx
This commit is contained in:
@@ -4,7 +4,8 @@
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { resolve } from "path";
|
||||
import { SESSION_DATA_DIR } from "main/constants";
|
||||
import { join, resolve } from "path";
|
||||
import { IpcCommands } from "shared/IpcEvents";
|
||||
import { MessageChannel, Worker } from "worker_threads";
|
||||
|
||||
@@ -19,6 +20,8 @@ const inviteCodeRegex = /^(\w|-)+$/;
|
||||
export async function initArRPC() {
|
||||
if (worker || !Settings.store.arRPC) return;
|
||||
|
||||
process.env.ARRPC_DETECTABLE_CACHE_PATH = join(SESSION_DATA_DIR, "arrpc-detectable-db.json");
|
||||
|
||||
try {
|
||||
const { port1: hostPort, port2: workerPort } = new MessageChannel();
|
||||
|
||||
|
||||
+1
-1
@@ -149,7 +149,7 @@ handle(IpcEvents.SELECT_VENCORD_DIR, async (_e, value?: null) => {
|
||||
if (!res.filePaths.length) return "cancelled";
|
||||
|
||||
const dir = res.filePaths[0];
|
||||
if (!isValidVencordInstall(dir)) return "invalid";
|
||||
if (!(await isValidVencordInstall(dir))) return "invalid";
|
||||
|
||||
State.store.vencordDir = dir;
|
||||
|
||||
|
||||
+4
-19
@@ -46,7 +46,7 @@ function init() {
|
||||
app.commandLine.removeSwitch("enable-features");
|
||||
app.commandLine.removeSwitch("disable-features");
|
||||
|
||||
if (hardwareAcceleration === false || process.argv.includes("--disable-gpu")) {
|
||||
if (!hardwareAcceleration || process.argv.includes("--disable-gpu")) {
|
||||
enableHardwareAcceleration = false;
|
||||
app.disableHardwareAcceleration();
|
||||
} else {
|
||||
@@ -65,29 +65,14 @@ function init() {
|
||||
app.commandLine.appendSwitch("disable-smooth-scrolling");
|
||||
}
|
||||
|
||||
// disable renderer backgrounding to prevent the app from unloading when in the background
|
||||
// https://github.com/electron/electron/issues/2822
|
||||
// https://github.com/GoogleChrome/chrome-launcher/blob/5a27dd574d47a75fec0fb50f7b774ebf8a9791ba/docs/chrome-flags-for-tools.md#task-throttling
|
||||
app.commandLine.appendSwitch("disable-renderer-backgrounding");
|
||||
app.commandLine.appendSwitch("disable-background-timer-throttling");
|
||||
app.commandLine.appendSwitch("disable-backgrounding-occluded-windows");
|
||||
if (process.platform === "win32") {
|
||||
disabledFeatures.add("CalculateNativeWinOcclusion");
|
||||
}
|
||||
|
||||
// work around chrome 66 disabling autoplay by default
|
||||
app.commandLine.appendSwitch("autoplay-policy", "no-user-gesture-required");
|
||||
|
||||
// WinRetrieveSuggestionsOnlyOnDemand: Work around electron 13 bug w/ async spellchecking on Windows.
|
||||
// HardwareMediaKeyHandling, MediaSessionService: Prevent Discord from registering as a media service.
|
||||
disabledFeatures.add("WinRetrieveSuggestionsOnlyOnDemand");
|
||||
// Prevent Discord from registering as a media service.
|
||||
disabledFeatures.add("HardwareMediaKeyHandling");
|
||||
disabledFeatures.add("MediaSessionService");
|
||||
|
||||
if (isLinux) {
|
||||
// Support TTS on Linux using https://wiki.archlinux.org/title/Speech_dispatcher
|
||||
app.commandLine.appendSwitch("enable-speech-dispatcher");
|
||||
|
||||
// This is needed to fix washed out colours - https://github.com/electron/electron/issues/49566
|
||||
// Supposed to be fixed already according to comments there, but it's just not lol, I can repro on Electron 43.0.0
|
||||
// when moving the window from my main monitor (HDR - not sure if this is relevant lol) to second monitor (SDR) and back
|
||||
@@ -99,8 +84,8 @@ function init() {
|
||||
|
||||
disabledFeatures.forEach(feat => enabledFeatures.delete(feat));
|
||||
|
||||
const enabledFeaturesArray = enabledFeatures.values().filter(Boolean).toArray();
|
||||
const disabledFeaturesArray = disabledFeatures.values().filter(Boolean).toArray();
|
||||
const enabledFeaturesArray = [...enabledFeatures].filter(Boolean);
|
||||
const disabledFeaturesArray = [...disabledFeatures].filter(Boolean);
|
||||
|
||||
if (enabledFeaturesArray.length) {
|
||||
app.commandLine.appendSwitch("enable-features", enabledFeaturesArray.join(","));
|
||||
|
||||
+40
-12
@@ -156,6 +156,25 @@ function initMenuBar(win: BrowserWindow) {
|
||||
accelerator: "CmdOrCtrl+=",
|
||||
role: "zoomIn",
|
||||
visible: false
|
||||
},
|
||||
// numpad zooms
|
||||
{
|
||||
label: "Zoom in (hidden)",
|
||||
accelerator: "CmdOrCtrl+numadd",
|
||||
role: "zoomIn",
|
||||
visible: false
|
||||
},
|
||||
{
|
||||
label: "Zoom out (hidden)",
|
||||
accelerator: "CmdOrCtrl+numsub",
|
||||
role: "zoomOut",
|
||||
visible: false
|
||||
},
|
||||
{
|
||||
label: "Reset Zoom (hidden)",
|
||||
accelerator: "CmdOrCtrl+num0",
|
||||
role: "resetZoom",
|
||||
visible: false
|
||||
}
|
||||
] satisfies MenuItemList;
|
||||
|
||||
@@ -312,17 +331,24 @@ function getWindowBoundsOptions(): BrowserWindowConstructorOptions {
|
||||
}
|
||||
|
||||
function buildBrowserWindowOptions(): BrowserWindowConstructorOptions {
|
||||
const { staticTitle, transparencyOption, enableMenu, customTitleBar, splashTheming, splashBackground } =
|
||||
Settings.store;
|
||||
const {
|
||||
staticTitle,
|
||||
transparencyOption,
|
||||
enableMenu,
|
||||
enableShadow,
|
||||
enableRoundedCorners,
|
||||
nativeTitleBar,
|
||||
splashTheming,
|
||||
splashBackground
|
||||
} = Settings.store;
|
||||
|
||||
const { frameless, transparent, macosVibrancyStyle } = VencordSettings.store;
|
||||
const { transparent, macosVibrancyStyle } = VencordSettings.store;
|
||||
|
||||
const noFrame = frameless === true || customTitleBar === true;
|
||||
const backgroundColor =
|
||||
splashTheming !== false ? splashBackground : nativeTheme.shouldUseDarkColors ? "#313338" : "#ffffff";
|
||||
const frameless = !nativeTitleBar;
|
||||
const backgroundColor = splashTheming ? splashBackground : nativeTheme.shouldUseDarkColors ? "#313338" : "#ffffff";
|
||||
|
||||
const options: BrowserWindowConstructorOptions = {
|
||||
show: Settings.store.enableSplashScreen === false && !CommandLine.values["start-minimized"],
|
||||
show: !Settings.store.enableSplashScreen && !CommandLine.values["start-minimized"],
|
||||
backgroundColor,
|
||||
webPreferences: {
|
||||
nodeIntegration: false,
|
||||
@@ -334,8 +360,10 @@ function buildBrowserWindowOptions(): BrowserWindowConstructorOptions {
|
||||
// disable renderer backgrounding to prevent the app from unloading when in the background
|
||||
backgroundThrottling: false
|
||||
},
|
||||
frame: !noFrame,
|
||||
frame: !frameless,
|
||||
autoHideMenuBar: enableMenu,
|
||||
hasShadow: enableShadow,
|
||||
roundedCorners: enableRoundedCorners,
|
||||
...getWindowBoundsOptions()
|
||||
};
|
||||
|
||||
@@ -348,7 +376,7 @@ function buildBrowserWindowOptions(): BrowserWindowConstructorOptions {
|
||||
options.backgroundColor = "#00000000";
|
||||
options.backgroundMaterial = transparencyOption;
|
||||
|
||||
if (customTitleBar) {
|
||||
if (frameless) {
|
||||
options.transparent = true;
|
||||
}
|
||||
}
|
||||
@@ -378,10 +406,10 @@ function createMainWindow() {
|
||||
const win = (mainWin = new BrowserWindow(buildBrowserWindowOptions()));
|
||||
|
||||
win.setMenuBarVisibility(false);
|
||||
if (process.platform === "darwin" && Settings.store.customTitleBar) win.setWindowButtonVisibility(false);
|
||||
if (process.platform === "darwin" && Settings.store.nativeTitleBar) win.setWindowButtonVisibility(false);
|
||||
|
||||
win.on("close", e => {
|
||||
const useTray = !isDeckGameMode && Settings.store.minimizeToTray !== false && Settings.store.tray !== false;
|
||||
const useTray = !isDeckGameMode && Settings.store.minimizeToTray && Settings.store.tray;
|
||||
if (isQuitting || (process.platform !== "darwin" && !useTray)) return;
|
||||
|
||||
e.preventDefault();
|
||||
@@ -441,7 +469,7 @@ export async function createWindows() {
|
||||
const startMinimized = CommandLine.values["start-minimized"];
|
||||
|
||||
let splash: BrowserWindow | undefined;
|
||||
if (Settings.store.enableSplashScreen !== false) {
|
||||
if (Settings.store.enableSplashScreen) {
|
||||
splash = createSplashWindow(startMinimized);
|
||||
|
||||
// SteamOS letterboxes and scales it terribly, so just full screen it
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
*/
|
||||
|
||||
import { desktopCapturer, session, Streams } from "electron";
|
||||
import { release } from "os";
|
||||
import type { StreamPick } from "renderer/components/ScreenSharePicker";
|
||||
import { IpcCommands, IpcEvents } from "shared/IpcEvents";
|
||||
|
||||
@@ -14,6 +15,8 @@ import { handle } from "./utils/ipcWrappers";
|
||||
const isWayland =
|
||||
process.platform === "linux" && (process.env.XDG_SESSION_TYPE === "wayland" || !!process.env.WAYLAND_DISPLAY);
|
||||
|
||||
const supportsLoopbackWithoutChrome = process.platform === "win32" && Number(release().split(".").pop()) >= 19045;
|
||||
|
||||
export function registerScreenShareHandler() {
|
||||
handle(IpcEvents.CAPTURER_GET_LARGE_THUMBNAIL, async (_, id: string) => {
|
||||
const sources = await desktopCapturer.getSources({
|
||||
@@ -78,7 +81,10 @@ export function registerScreenShareHandler() {
|
||||
const streams: Streams = {
|
||||
video: source
|
||||
};
|
||||
if (choice.audio && process.platform === "win32") streams.audio = "loopback";
|
||||
if (choice.audio && process.platform === "win32") {
|
||||
// @ts-expect-error loopbackWithoutChrome is real but not documented
|
||||
streams.audio = supportsLoopbackWithoutChrome ? "loopbackWithoutChrome" : "loopback";
|
||||
}
|
||||
|
||||
callback(streams);
|
||||
});
|
||||
|
||||
@@ -7,15 +7,17 @@
|
||||
import { type Settings as TVencordSettings } from "@vencord/types/Vencord";
|
||||
import { mkdirSync, readFileSync, writeFileSync } from "fs";
|
||||
import { dirname, join } from "path";
|
||||
import { DefaultVesktopSettings } from "shared/defaultSettings";
|
||||
import type { Settings as TSettings, State as TState } from "shared/settings";
|
||||
import { SettingsStore } from "shared/utils/SettingsStore";
|
||||
|
||||
import { DATA_DIR, VENCORD_SETTINGS_FILE } from "./constants";
|
||||
import { mergeDefaults } from "./utils/mergeDefaults";
|
||||
|
||||
const SETTINGS_FILE = join(DATA_DIR, "settings.json");
|
||||
const STATE_FILE = join(DATA_DIR, "state.json");
|
||||
|
||||
function loadSettings<T extends object = any>(file: string, name: string) {
|
||||
function loadSettings<T extends object = any>(file: string, name: string, defaults?: T) {
|
||||
let settings = {} as T;
|
||||
try {
|
||||
const content = readFileSync(file, "utf8");
|
||||
@@ -26,6 +28,10 @@ function loadSettings<T extends object = any>(file: string, name: string) {
|
||||
}
|
||||
} catch {}
|
||||
|
||||
if (defaults) {
|
||||
mergeDefaults(settings, defaults);
|
||||
}
|
||||
|
||||
const store = new SettingsStore(settings);
|
||||
store.addGlobalChangeListener(o => {
|
||||
try {
|
||||
@@ -39,6 +45,6 @@ function loadSettings<T extends object = any>(file: string, name: string) {
|
||||
return store;
|
||||
}
|
||||
|
||||
export const Settings = loadSettings<TSettings>(SETTINGS_FILE, "Vesktop settings");
|
||||
export const Settings = loadSettings<TSettings>(SETTINGS_FILE, "Vesktop settings", DefaultVesktopSettings);
|
||||
export const VencordSettings = loadSettings<TVencordSettings>(VENCORD_SETTINGS_FILE, "Vencord settings");
|
||||
export const State = loadSettings<TState>(STATE_FILE, "Vesktop state");
|
||||
|
||||
+1
-1
@@ -26,7 +26,7 @@ export function createSplashWindow(startMinimized = false) {
|
||||
|
||||
const { splashBackground, splashColor, splashTheming, splashPixelated } = Settings.store;
|
||||
|
||||
if (splashTheming !== false) {
|
||||
if (splashTheming) {
|
||||
if (splashColor) {
|
||||
const semiTransparentSplashColor = splashColor.replace("rgb(", "rgba(").replace(")", ", 0.2)");
|
||||
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
/*
|
||||
* Vesktop, a desktop app aiming to give you a snappier Discord Experience
|
||||
* Copyright (c) 2026 Vendicated and Vesktop contributors
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
/**
|
||||
* Recursively merges defaults into an object and returns the same object
|
||||
* @param obj Object
|
||||
* @param defaults Defaults
|
||||
* @returns obj
|
||||
*/
|
||||
export function mergeDefaults<T>(obj: T, defaults: T): T {
|
||||
for (const key in defaults) {
|
||||
const v = defaults[key];
|
||||
if (typeof v === "object" && !Array.isArray(v) && v !== null) {
|
||||
obj[key] ??= {} as any;
|
||||
mergeDefaults(obj[key], v);
|
||||
} else {
|
||||
obj[key] ??= v;
|
||||
}
|
||||
}
|
||||
return obj;
|
||||
}
|
||||
@@ -37,7 +37,7 @@ const DEFAULT_POPOUT_OPTIONS: BrowserWindowConstructorOptions = {
|
||||
backgroundColor: "#2f3136",
|
||||
minWidth: MIN_POPOUT_WIDTH,
|
||||
minHeight: MIN_POPOUT_HEIGHT,
|
||||
frame: Settings.store.customTitleBar !== true,
|
||||
frame: Settings.store.nativeTitleBar,
|
||||
titleBarStyle: process.platform === "darwin" ? "hidden" : undefined,
|
||||
trafficLightPosition:
|
||||
process.platform === "darwin"
|
||||
|
||||
@@ -64,7 +64,7 @@ const existsAsync = (path: string) =>
|
||||
.catch(() => false);
|
||||
|
||||
export async function isValidVencordInstall(dir: string) {
|
||||
const results = await Promise.all(["package.json", ...FILES_TO_DOWNLOAD].map(f => existsAsync(join(dir, f))));
|
||||
const results = await Promise.all(FILES_TO_DOWNLOAD.map(f => existsAsync(join(dir, f))));
|
||||
return !results.includes(false);
|
||||
}
|
||||
|
||||
|
||||
+38
-48
@@ -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());
|
||||
|
||||
@@ -23,3 +23,16 @@ app.whenReady().then(() => {
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
protocol.registerSchemesAsPrivileged([
|
||||
{
|
||||
scheme: "vesktop",
|
||||
privileges: {
|
||||
standard: true,
|
||||
secure: true,
|
||||
supportFetchAPI: true,
|
||||
corsEnabled: true,
|
||||
stream: true
|
||||
}
|
||||
}
|
||||
]);
|
||||
|
||||
@@ -92,6 +92,7 @@ export const VesktopNative = {
|
||||
>(IpcEvents.VIRT_MIC_LIST),
|
||||
start: (include: Node[]) => invoke<void>(IpcEvents.VIRT_MIC_START, include),
|
||||
startSystem: (exclude: Node[]) => invoke<void>(IpcEvents.VIRT_MIC_START_SYSTEM, exclude),
|
||||
unmute: () => invoke<void>(IpcEvents.VIRT_MIC_UNMUTE),
|
||||
stop: () => invoke<void>(IpcEvents.VIRT_MIC_STOP)
|
||||
},
|
||||
clipboard: {
|
||||
|
||||
@@ -14,7 +14,7 @@ let GuildReadStateStore: any;
|
||||
let NotificationSettingsStore: any;
|
||||
|
||||
export function setBadge() {
|
||||
if (Settings.store.appBadge === false) return;
|
||||
if (!Settings.store.appBadge) return;
|
||||
|
||||
try {
|
||||
const mentionCount = GuildReadStateStore.getTotalMentionCount();
|
||||
|
||||
@@ -8,7 +8,6 @@ import "./screenSharePicker.css";
|
||||
|
||||
import { classNameFactory } from "@vencord/types/api/Styles";
|
||||
import {
|
||||
BaseText,
|
||||
Button,
|
||||
Card,
|
||||
CogWheel,
|
||||
@@ -20,18 +19,18 @@ import {
|
||||
RestartIcon,
|
||||
Span
|
||||
} from "@vencord/types/components";
|
||||
import { Logger, useAwaiter, useForceUpdater } from "@vencord/types/utils";
|
||||
import { onceReady } from "@vencord/types/webpack";
|
||||
import {
|
||||
closeModal,
|
||||
Logger,
|
||||
ModalCloseButton,
|
||||
Modals,
|
||||
ModalSize,
|
||||
FluxDispatcher,
|
||||
MediaEngineStore,
|
||||
Modal,
|
||||
openModal,
|
||||
useAwaiter,
|
||||
useForceUpdater
|
||||
} from "@vencord/types/utils";
|
||||
import { onceReady } from "@vencord/types/webpack";
|
||||
import { FluxDispatcher, MediaEngineStore, Select, UserStore, useState } from "@vencord/types/webpack/common";
|
||||
Select,
|
||||
UserStore,
|
||||
useState
|
||||
} from "@vencord/types/webpack/common";
|
||||
import { Node } from "@vencord/venmic";
|
||||
import type { Dispatch, SetStateAction } from "react";
|
||||
import { addPatch } from "renderer/patches/shared";
|
||||
@@ -122,15 +121,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();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -210,15 +219,20 @@ function AudioSettingsModal({
|
||||
const Settings = useSettings();
|
||||
|
||||
return (
|
||||
<Modals.ModalRoot {...modalProps} size={ModalSize.MEDIUM}>
|
||||
<Modals.ModalHeader className={cl("header")}>
|
||||
<BaseText size="lg" weight="semibold" tag="h3" style={{ flexGrow: 1 }}>
|
||||
Audio Settings
|
||||
</BaseText>
|
||||
<ModalCloseButton onClick={close} />
|
||||
</Modals.ModalHeader>
|
||||
|
||||
<Modals.ModalContent className={cl("modal", "venmic-settings")}>
|
||||
<Modal
|
||||
{...modalProps}
|
||||
size="lg"
|
||||
title="Audio Settings"
|
||||
actions={[{ text: "Back", variant: "secondary", onClick: close }]}
|
||||
>
|
||||
<div className={cl("venmic-settings")}>
|
||||
<FormSwitch
|
||||
title="Initial Mute"
|
||||
description="Fix an initial audio spike caused by chromium."
|
||||
hideBorder
|
||||
onChange={v => (Settings.audio = { ...Settings.audio, mute: v })}
|
||||
value={Settings.audio?.mute ?? true}
|
||||
/>
|
||||
<FormSwitch
|
||||
title="Microphone Workaround"
|
||||
description="Work around an issue that causes the microphone to be shared instead of the correct audio. Only enable if you're experiencing this issue."
|
||||
@@ -302,13 +316,8 @@ function AudioSettingsModal({
|
||||
value={Settings.audio?.deviceSelect ?? false}
|
||||
disabled={Settings.audio?.ignoreDevices}
|
||||
/>
|
||||
</Modals.ModalContent>
|
||||
<Modals.ModalFooter className={cl("footer")}>
|
||||
<Button variant="secondary" onClick={close}>
|
||||
Back
|
||||
</Button>
|
||||
</Modals.ModalFooter>
|
||||
</Modals.ModalRoot>
|
||||
</div>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -455,78 +464,61 @@ function isSpecialSource(value?: AudioSource | AudioSources): value is SpecialSo
|
||||
return typeof value === "string";
|
||||
}
|
||||
|
||||
function hasMatchingProps(value: Node, other: Node) {
|
||||
return Object.keys(value).every(key => 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 +647,10 @@ function AudioSourcePickerLinux({
|
||||
}))}
|
||||
isSelected={isItemSelected(includeSources)}
|
||||
select={updateItems(setIncludeSources, includeSources)}
|
||||
serialize={String}
|
||||
serialize={JSON.stringify}
|
||||
popoutPosition="top"
|
||||
closeOnSelect={false}
|
||||
renderOptionLabel={o => o.label}
|
||||
/>
|
||||
</SimpleErrorBoundary>
|
||||
</section>
|
||||
@@ -675,9 +668,10 @@ function AudioSourcePickerLinux({
|
||||
}))}
|
||||
isSelected={isItemSelected(excludeSources)}
|
||||
select={updateItems(setExcludeSources, excludeSources)}
|
||||
serialize={String}
|
||||
serialize={JSON.stringify}
|
||||
popoutPosition="top"
|
||||
closeOnSelect={false}
|
||||
renderOptionLabel={o => o.label}
|
||||
/>
|
||||
</SimpleErrorBoundary>
|
||||
</section>
|
||||
@@ -721,101 +715,91 @@ function ModalComponent({
|
||||
frameRate: "30"
|
||||
});
|
||||
|
||||
function handleGoLive() {
|
||||
currentSettings = settings;
|
||||
try {
|
||||
const frameRate = Number(qualitySettings.frameRate);
|
||||
const height = Number(qualitySettings.resolution);
|
||||
const width = Math.round(height * (16 / 9));
|
||||
|
||||
const conn = [...MediaEngineStore.getMediaEngine().connections].find(
|
||||
connection => connection.streamUserId === UserStore.getCurrentUser().id
|
||||
);
|
||||
|
||||
if (conn) {
|
||||
conn.videoStreamParameters[0].maxFrameRate = frameRate;
|
||||
conn.videoStreamParameters[0].maxResolution ??= { width: 0, height: 0 };
|
||||
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;
|
||||
|
||||
// @ts-expect-error incorrect type
|
||||
const track = conn.input.stream.getVideoTracks()[0];
|
||||
|
||||
const constraints = {
|
||||
...track.getConstraints(),
|
||||
frameRate: { min: frameRate, ideal: 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();
|
||||
}
|
||||
|
||||
const showGoBack = selected && !skipPicker;
|
||||
return (
|
||||
<Modals.ModalRoot {...modalProps} size={ModalSize.MEDIUM}>
|
||||
<Modals.ModalHeader className={cl("header")}>
|
||||
<BaseText size="lg" weight="semibold" tag="h3" style={{ flexGrow: 1 }}>
|
||||
Screen Share Picker
|
||||
</BaseText>
|
||||
<ModalCloseButton onClick={close} />
|
||||
</Modals.ModalHeader>
|
||||
<Modals.ModalContent className={cl("modal")}>
|
||||
{!selected ? (
|
||||
<ScreenPicker screens={screens} chooseScreen={setSelected} />
|
||||
) : (
|
||||
<StreamSettingsUi
|
||||
source={screens.find(s => s.id === selected)!}
|
||||
settings={settings}
|
||||
setSettings={setSettings}
|
||||
skipPicker={skipPicker}
|
||||
/>
|
||||
)}
|
||||
</Modals.ModalContent>
|
||||
<Modals.ModalFooter className={cl("footer")}>
|
||||
<Button
|
||||
disabled={!selected}
|
||||
onClick={() => {
|
||||
currentSettings = settings;
|
||||
try {
|
||||
const frameRate = Number(qualitySettings.frameRate);
|
||||
const height = Number(qualitySettings.resolution);
|
||||
const width = Math.round(height * (16 / 9));
|
||||
|
||||
const conn = [...MediaEngineStore.getMediaEngine().connections].find(
|
||||
connection => connection.streamUserId === UserStore.getCurrentUser().id
|
||||
);
|
||||
|
||||
if (conn) {
|
||||
conn.videoStreamParameters[0].maxFrameRate = frameRate;
|
||||
conn.videoStreamParameters[0].maxResolution ??= { width: 0, height: 0 };
|
||||
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;
|
||||
|
||||
// @ts-expect-error incorrect type
|
||||
const track = conn.input.stream.getVideoTracks()[0];
|
||||
|
||||
const constraints = {
|
||||
...track.getConstraints(),
|
||||
frameRate: { min: frameRate, ideal: 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();
|
||||
}}
|
||||
>
|
||||
Go Live
|
||||
</Button>
|
||||
|
||||
{selected && !skipPicker ? (
|
||||
<Button variant="secondary" onClick={() => setSelected(void 0)}>
|
||||
Back
|
||||
</Button>
|
||||
) : (
|
||||
<Button variant="secondary" onClick={close}>
|
||||
Cancel
|
||||
</Button>
|
||||
)}
|
||||
</Modals.ModalFooter>
|
||||
</Modals.ModalRoot>
|
||||
<Modal
|
||||
{...modalProps}
|
||||
size="lg"
|
||||
title="Screen Share Picker"
|
||||
actions={[
|
||||
{
|
||||
text: showGoBack ? "Back" : "Cancel",
|
||||
variant: "secondary",
|
||||
onClick: () => (showGoBack ? setSelected(void 0) : close())
|
||||
},
|
||||
{
|
||||
text: "Go Live",
|
||||
disabled: !selected,
|
||||
onClick: handleGoLive
|
||||
}
|
||||
]}
|
||||
>
|
||||
{!selected ? (
|
||||
<ScreenPicker screens={screens} chooseScreen={setSelected} />
|
||||
) : (
|
||||
<StreamSettingsUi
|
||||
source={screens.find(s => s.id === selected)!}
|
||||
settings={settings}
|
||||
setSettings={setSettings}
|
||||
skipPicker={skipPicker}
|
||||
/>
|
||||
)}
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,7 +1,3 @@
|
||||
.vcd-screen-picker-modal {
|
||||
padding: 1em;
|
||||
}
|
||||
|
||||
.vcd-screen-picker-header-close-button {
|
||||
margin-left: auto;
|
||||
}
|
||||
|
||||
@@ -27,7 +27,7 @@ export const AutoStartToggle: SettingsComponent = ({ settings }) => {
|
||||
<VesktopSettingsSwitch
|
||||
title="Auto Start Minimized"
|
||||
description={"Start Vesktop minimized when starting with system"}
|
||||
value={settings.autoStartMinimized ?? false}
|
||||
value={settings.autoStartMinimized}
|
||||
onChange={v => (settings.autoStartMinimized = v)}
|
||||
disabled={!autoStartEnabled}
|
||||
/>
|
||||
|
||||
@@ -4,18 +4,9 @@
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { BaseText, Button, Heading, Paragraph, TextButton } from "@vencord/types/components";
|
||||
import {
|
||||
Margins,
|
||||
ModalCloseButton,
|
||||
ModalContent,
|
||||
ModalHeader,
|
||||
ModalRoot,
|
||||
ModalSize,
|
||||
openModal,
|
||||
useForceUpdater
|
||||
} from "@vencord/types/utils";
|
||||
import { Toasts } from "@vencord/types/webpack/common";
|
||||
import { Button, Heading, Paragraph, TextButton } from "@vencord/types/components";
|
||||
import { Margins, useForceUpdater } from "@vencord/types/utils";
|
||||
import { Modal, openModal, Toasts } from "@vencord/types/webpack/common";
|
||||
import { Settings } from "shared/settings";
|
||||
|
||||
import { cl, SettingsComponent } from "./Settings";
|
||||
@@ -26,31 +17,20 @@ export const DeveloperOptionsButton: SettingsComponent = ({ settings }) => {
|
||||
|
||||
function openDeveloperOptionsModal(settings: Settings) {
|
||||
openModal(props => (
|
||||
<ModalRoot {...props} size={ModalSize.MEDIUM}>
|
||||
<ModalHeader>
|
||||
<BaseText size="lg" weight="semibold" tag="h3" style={{ flexGrow: 1 }}>
|
||||
Vesktop Developer Options
|
||||
</BaseText>
|
||||
<ModalCloseButton onClick={props.onClose} />
|
||||
</ModalHeader>
|
||||
<Modal {...props} size="lg" title="Vesktop Developer Options">
|
||||
<Heading tag="h4">Vencord Location</Heading>
|
||||
<VencordLocationPicker settings={settings} />
|
||||
|
||||
<ModalContent>
|
||||
<div style={{ padding: "1em 0" }}>
|
||||
<Heading tag="h5">Vencord Location</Heading>
|
||||
<VencordLocationPicker settings={settings} />
|
||||
|
||||
<Heading tag="h5" className={Margins.top16}>
|
||||
Debugging
|
||||
</Heading>
|
||||
<div className={cl("button-grid")}>
|
||||
<Button onClick={() => VesktopNative.debug.launchGpu()}>Open chrome://gpu</Button>
|
||||
<Button onClick={() => VesktopNative.debug.launchWebrtcInternals()}>
|
||||
Open chrome://webrtc-internals
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</ModalContent>
|
||||
</ModalRoot>
|
||||
<Heading tag="h4" className={Margins.top16}>
|
||||
Debugging
|
||||
</Heading>
|
||||
<div className={cl("button-grid")}>
|
||||
<Button onClick={() => VesktopNative.debug.launchGpu()}>Open chrome://gpu</Button>
|
||||
<Button onClick={() => VesktopNative.debug.launchWebrtcInternals()}>
|
||||
Open chrome://webrtc-internals
|
||||
</Button>
|
||||
</div>
|
||||
</Modal>
|
||||
));
|
||||
}
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@ export const NotificationBadgeToggle: SettingsComponent = ({ settings }) => {
|
||||
<VesktopSettingsSwitch
|
||||
title="Notification Badge"
|
||||
description="Show mention badge on the app icon"
|
||||
value={settings.appBadge ?? true}
|
||||
value={settings.appBadge}
|
||||
onChange={v => {
|
||||
settings.appBadge = v;
|
||||
if (v) setBadge();
|
||||
|
||||
@@ -11,8 +11,8 @@ import { BaseText, Divider, ErrorBoundary } from "@vencord/types/components";
|
||||
import { ComponentType } from "react";
|
||||
import { WebRTCIPHandlingPolicyPicker } from "renderer/components/settings/WebRTCIPHandlingPolicyPicker";
|
||||
import { KeybindsButton } from "renderer/globalShortcuts/ShortcutSettings";
|
||||
import { Settings, useSettings } from "renderer/settings";
|
||||
import { isMac, isWindows } from "renderer/utils";
|
||||
import { getValueAndOnChange, Settings, useSettings } from "renderer/settings";
|
||||
import { isMac } from "renderer/utils";
|
||||
|
||||
import { AutoStartToggle } from "./AutoStartToggle";
|
||||
import { DeveloperOptionsButton } from "./DeveloperOptions";
|
||||
@@ -27,7 +27,6 @@ interface BooleanSetting {
|
||||
key: keyof typeof Settings.store;
|
||||
title: string;
|
||||
description: string;
|
||||
defaultValue: boolean;
|
||||
disabled?(): boolean;
|
||||
invisible?(): boolean;
|
||||
}
|
||||
@@ -43,50 +42,55 @@ const SettingsOptions: Record<string, Array<BooleanSetting | SettingsComponent>>
|
||||
{
|
||||
key: "hardwareAcceleration",
|
||||
title: "Hardware Acceleration",
|
||||
description: "Enable hardware acceleration",
|
||||
defaultValue: true
|
||||
description: "Enable hardware acceleration"
|
||||
},
|
||||
{
|
||||
key: "hardwareVideoAcceleration",
|
||||
title: "Video Hardware Acceleration",
|
||||
description:
|
||||
"Enable hardware video acceleration. This can improve performance of screenshare and video playback, but may cause graphical glitches and infinitely loading streams.",
|
||||
defaultValue: false,
|
||||
disabled: () => Settings.store.hardwareAcceleration === false
|
||||
disabled: () => !Settings.store.hardwareAcceleration
|
||||
}
|
||||
],
|
||||
"User Interface": [
|
||||
{
|
||||
key: "customTitleBar",
|
||||
title: "Discord Titlebar",
|
||||
description: "Use Discord's custom title bar instead of the native system one. Requires a full restart.",
|
||||
defaultValue: isWindows
|
||||
key: "nativeTitleBar",
|
||||
title: "Native Titlebar",
|
||||
description: "Enable the system titlebar in addition to Discord's custom one. Requires a full restart."
|
||||
},
|
||||
{
|
||||
key: "staticTitle",
|
||||
title: "Static Title",
|
||||
description: 'Makes the window title "Vesktop" instead of changing to the current page',
|
||||
defaultValue: false
|
||||
description: 'Makes the window title "Vesktop" instead of changing to the current page'
|
||||
},
|
||||
{
|
||||
key: "enableMenu",
|
||||
title: "Enable Menu Bar",
|
||||
description: "Enables the application menu bar. Press ALT to toggle visibility.",
|
||||
defaultValue: false,
|
||||
disabled: () => Settings.store.customTitleBar ?? isWindows
|
||||
disabled: () => !Settings.store.nativeTitleBar
|
||||
},
|
||||
{
|
||||
key: "enableShadow",
|
||||
title: "Enable Window Shadow",
|
||||
description: "Enables the window shadow. Requires a full restart.",
|
||||
disabled: () => Settings.store.nativeTitleBar
|
||||
},
|
||||
{
|
||||
key: "enableRoundedCorners",
|
||||
title: "Enable Rounded Corners",
|
||||
description: "Enables rounded corners. Requires a full restart.",
|
||||
disabled: () => Settings.store.nativeTitleBar
|
||||
},
|
||||
{
|
||||
key: "enableSplashScreen",
|
||||
title: "Enable Splash Screen",
|
||||
description:
|
||||
"Shows a small splash screen while Vesktop is loading. Disabling this option will show the main window earlier while it's still loading.",
|
||||
defaultValue: true
|
||||
"Shows a small splash screen while Vesktop is loading. Disabling this option will show the main window earlier while it's still loading."
|
||||
},
|
||||
{
|
||||
key: "splashTheming",
|
||||
title: "Splash theming",
|
||||
description: "Adapt the splash window colors to your custom theme",
|
||||
defaultValue: true
|
||||
description: "Adapt the splash window colors to your custom theme"
|
||||
},
|
||||
WindowsTransparencyControls,
|
||||
UserAssetsButton
|
||||
@@ -96,34 +100,29 @@ const SettingsOptions: Record<string, Array<BooleanSetting | SettingsComponent>>
|
||||
key: "tray",
|
||||
title: "Tray Icon",
|
||||
description: "Add a tray icon for Vesktop",
|
||||
defaultValue: true,
|
||||
invisible: () => isMac
|
||||
},
|
||||
{
|
||||
key: "minimizeToTray",
|
||||
title: "Minimize to tray",
|
||||
description: "Hitting X will make Vesktop minimize to the tray instead of closing",
|
||||
defaultValue: true,
|
||||
invisible: () => isMac,
|
||||
disabled: () => Settings.store.tray === false
|
||||
disabled: () => !Settings.store.tray
|
||||
},
|
||||
{
|
||||
key: "clickTrayToShowHide",
|
||||
title: "Hide/Show on tray click",
|
||||
description: "Left clicking tray icon will toggle the vesktop window visibility.",
|
||||
defaultValue: false
|
||||
description: "Left clicking tray icon will toggle the vesktop window visibility."
|
||||
},
|
||||
{
|
||||
key: "disableMinSize",
|
||||
title: "Disable minimum window size",
|
||||
description: "Allows you to make the window as small as your heart desires",
|
||||
defaultValue: false
|
||||
description: "Allows you to make the window as small as your heart desires"
|
||||
},
|
||||
{
|
||||
key: "disableSmoothScroll",
|
||||
title: "Disable smooth scrolling",
|
||||
description: "Disables smooth scrolling",
|
||||
defaultValue: false
|
||||
description: "Disables smooth scrolling"
|
||||
},
|
||||
KeybindsButton
|
||||
],
|
||||
@@ -132,23 +131,20 @@ const SettingsOptions: Record<string, Array<BooleanSetting | SettingsComponent>>
|
||||
{
|
||||
key: "enableTaskbarFlashing",
|
||||
title: "Enable Taskbar Flashing",
|
||||
description: "Flashes the app in your taskbar when you have new notifications.",
|
||||
defaultValue: false
|
||||
description: "Flashes the app in your taskbar when you have new notifications."
|
||||
}
|
||||
],
|
||||
Miscellaneous: [
|
||||
{
|
||||
key: "arRPC",
|
||||
title: "Rich Presence",
|
||||
description: "Enables Rich Presence via arRPC",
|
||||
defaultValue: false
|
||||
description: "Enables Rich Presence via arRPC"
|
||||
},
|
||||
|
||||
{
|
||||
key: "openLinksWithElectron",
|
||||
title: "Open Links in app (experimental)",
|
||||
description: "Opens links in a new Vesktop window instead of your web browser",
|
||||
defaultValue: false
|
||||
description: "Opens links in a new Vesktop window instead of your web browser"
|
||||
},
|
||||
|
||||
WebRTCIPHandlingPolicyPicker
|
||||
@@ -170,16 +166,15 @@ function SettingsSections() {
|
||||
{settings.map((Setting, i) => {
|
||||
if (typeof Setting === "function") return <Setting key={`Custom-${i}`} settings={Settings} />;
|
||||
|
||||
const { defaultValue, title, description, key, disabled, invisible } = Setting;
|
||||
const { title, description, key, disabled, invisible } = Setting;
|
||||
if (invisible?.()) return null;
|
||||
|
||||
return (
|
||||
<VesktopSettingsSwitch
|
||||
title={title}
|
||||
description={description}
|
||||
value={Settings[key as any] ?? defaultValue}
|
||||
onChange={v => (Settings[key as any] = v)}
|
||||
disabled={disabled?.()}
|
||||
{...getValueAndOnChange(key)}
|
||||
key={key}
|
||||
/>
|
||||
);
|
||||
|
||||
@@ -7,18 +7,8 @@
|
||||
import "./UserAssets.css";
|
||||
|
||||
import { BaseText, Button, FormSwitch } from "@vencord/types/components";
|
||||
import {
|
||||
Margins,
|
||||
ModalCloseButton,
|
||||
ModalContent,
|
||||
ModalHeader,
|
||||
ModalRoot,
|
||||
ModalSize,
|
||||
openModal,
|
||||
wordsFromCamel,
|
||||
wordsToTitle
|
||||
} from "@vencord/types/utils";
|
||||
import { showToast, useState } from "@vencord/types/webpack/common";
|
||||
import { Margins, wordsFromCamel, wordsToTitle } from "@vencord/types/utils";
|
||||
import { Modal, openModal, showToast, useState } from "@vencord/types/webpack/common";
|
||||
import { UserAssetType } from "main/userAssets";
|
||||
import { useSettings } from "renderer/settings";
|
||||
|
||||
@@ -32,22 +22,13 @@ export const UserAssetsButton: SettingsComponent = () => {
|
||||
|
||||
function openAssetsModal() {
|
||||
openModal(props => (
|
||||
<ModalRoot {...props} size={ModalSize.MEDIUM}>
|
||||
<ModalHeader>
|
||||
<BaseText size="lg" weight="semibold" tag="h3" style={{ flexGrow: 1 }}>
|
||||
User Assets
|
||||
</BaseText>
|
||||
<ModalCloseButton onClick={props.onClose} />
|
||||
</ModalHeader>
|
||||
|
||||
<ModalContent>
|
||||
<div className="vcd-user-assets">
|
||||
{CUSTOMIZABLE_ASSETS.map(asset => (
|
||||
<Asset key={asset} asset={asset} />
|
||||
))}
|
||||
</div>
|
||||
</ModalContent>
|
||||
</ModalRoot>
|
||||
<Modal {...props} size="lg" title="User Assets">
|
||||
<div className="vcd-user-assets">
|
||||
{CUSTOMIZABLE_ASSETS.map(asset => (
|
||||
<Asset key={asset} asset={asset} />
|
||||
))}
|
||||
</div>
|
||||
</Modal>
|
||||
));
|
||||
}
|
||||
|
||||
@@ -93,7 +74,7 @@ function Asset({ asset }: { asset: UserAssetType }) {
|
||||
{isSplash && (
|
||||
<FormSwitch
|
||||
title="Nearest-Neighbor Scaling (for pixel art)"
|
||||
value={settings.splashPixelated ?? false}
|
||||
value={settings.splashPixelated}
|
||||
onChange={val => (settings.splashPixelated = val)}
|
||||
className={Margins.top16}
|
||||
hideBorder
|
||||
|
||||
@@ -33,7 +33,7 @@ export const WebRTCIPHandlingPolicyPicker: SettingsComponent = ({ settings }) =>
|
||||
]}
|
||||
closeOnSelect={true}
|
||||
select={v => (settings.webRTCIPHandlingPolicy = v)}
|
||||
isSelected={v => v === (settings.webRTCIPHandlingPolicy ?? "default")}
|
||||
isSelected={v => v === settings.webRTCIPHandlingPolicy}
|
||||
serialize={s => s}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -7,17 +7,8 @@
|
||||
import "./ShortcutSettings.css";
|
||||
|
||||
import { BaseText, Button, Card, DeleteIcon, Switch } from "@vencord/types/components";
|
||||
import {
|
||||
classNameFactory,
|
||||
identity,
|
||||
ModalCloseButton,
|
||||
ModalContent,
|
||||
ModalHeader,
|
||||
ModalRoot,
|
||||
ModalSize,
|
||||
openModal
|
||||
} from "@vencord/types/utils";
|
||||
import { React, Select, useState } from "@vencord/types/webpack/common";
|
||||
import { classNameFactory, identity, openModal } from "@vencord/types/utils";
|
||||
import { Modal, React, Select, useState } from "@vencord/types/webpack/common";
|
||||
import { SettingsComponent } from "renderer/components/settings/Settings";
|
||||
import { onIpcCommand } from "renderer/ipcCommands";
|
||||
import { reactiveValue } from "renderer/reactiveState";
|
||||
@@ -181,18 +172,9 @@ export function KeyBindSettings() {
|
||||
|
||||
export function openKeybindsModal() {
|
||||
openModal(props => (
|
||||
<ModalRoot {...props} size={ModalSize.LARGE}>
|
||||
<ModalHeader>
|
||||
<BaseText size="lg" weight="semibold" tag="h3" style={{ flexGrow: 1 }}>
|
||||
Keybinds
|
||||
</BaseText>
|
||||
<ModalCloseButton onClick={props.onClose} />
|
||||
</ModalHeader>
|
||||
|
||||
<ModalContent>
|
||||
<KeyBindSettings />
|
||||
</ModalContent>
|
||||
</ModalRoot>
|
||||
<Modal {...props} size="lg" title="Keybinds">
|
||||
<KeyBindSettings />
|
||||
</Modal>
|
||||
));
|
||||
}
|
||||
|
||||
|
||||
@@ -21,8 +21,7 @@ addPatch({
|
||||
],
|
||||
|
||||
getPlatformClass() {
|
||||
if (Settings.store.customTitleBar) return "platform-win";
|
||||
if (isMac) return "platform-osx";
|
||||
return "platform-web";
|
||||
if (Settings.store.nativeTitleBar) return "platform-web";
|
||||
return isMac ? "platform-osx" : "platform-win";
|
||||
}
|
||||
});
|
||||
|
||||
@@ -8,7 +8,7 @@ import { Settings } from "renderer/settings";
|
||||
|
||||
import { addPatch } from "./shared";
|
||||
|
||||
if (Settings.store.customTitleBar)
|
||||
if (!Settings.store.nativeTitleBar)
|
||||
addPatch({
|
||||
patches: [
|
||||
{
|
||||
|
||||
@@ -25,10 +25,12 @@ export function useSettings() {
|
||||
return Settings.store;
|
||||
}
|
||||
|
||||
export function getValueAndOnChange(key: keyof typeof Settings.store) {
|
||||
export function getValueAndOnChange<K extends keyof typeof Settings.store, V extends (typeof Settings.store)[K]>(
|
||||
key: K
|
||||
) {
|
||||
return {
|
||||
value: Settings.store[key] as any,
|
||||
onChange: (value: any) => (Settings.store[key] = value)
|
||||
value: Settings.store[key] as V,
|
||||
onChange: (value: V) => (Settings.store[key] = value)
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
/*
|
||||
* Vesktop, a desktop app aiming to give you a snappier Discord Experience
|
||||
* Copyright (c) 2026 Vendicated and Vesktop contributors
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { Settings } from "./settings";
|
||||
|
||||
export const DefaultVesktopSettings: Settings = {
|
||||
discordBranch: "stable",
|
||||
hardwareAcceleration: true,
|
||||
hardwareVideoAcceleration: false,
|
||||
nativeTitleBar: false,
|
||||
staticTitle: false,
|
||||
enableMenu: false,
|
||||
enableShadow: true,
|
||||
enableRoundedCorners: true,
|
||||
enableSplashScreen: true,
|
||||
splashTheming: true,
|
||||
tray: true,
|
||||
minimizeToTray: true,
|
||||
clickTrayToShowHide: false,
|
||||
disableMinSize: false,
|
||||
disableSmoothScroll: false,
|
||||
enableTaskbarFlashing: false,
|
||||
arRPC: true,
|
||||
openLinksWithElectron: false,
|
||||
autoStartMinimized: false,
|
||||
splashPixelated: false,
|
||||
webRTCIPHandlingPolicy: "default",
|
||||
appBadge: true,
|
||||
transparencyOption: "none"
|
||||
};
|
||||
Vendored
+24
-21
@@ -8,34 +8,36 @@ import type { Rectangle } from "electron";
|
||||
import { KeyBind } from "renderer/globalShortcuts/ShortcutSettings";
|
||||
|
||||
export interface Settings {
|
||||
discordBranch?: "stable" | "canary" | "ptb";
|
||||
transparencyOption?: "none" | "mica" | "tabbed" | "acrylic";
|
||||
webRTCIPHandlingPolicy?:
|
||||
discordBranch: "stable" | "canary" | "ptb";
|
||||
transparencyOption: "none" | "mica" | "tabbed" | "acrylic";
|
||||
webRTCIPHandlingPolicy:
|
||||
| "default"
|
||||
| "default_public_interface_only"
|
||||
| "default_public_and_private_interfaces"
|
||||
| "disable_non_proxied_udp";
|
||||
tray?: boolean;
|
||||
minimizeToTray?: boolean;
|
||||
autoStartMinimized?: boolean;
|
||||
openLinksWithElectron?: boolean;
|
||||
staticTitle?: boolean;
|
||||
enableMenu?: boolean;
|
||||
disableSmoothScroll?: boolean;
|
||||
hardwareAcceleration?: boolean;
|
||||
hardwareVideoAcceleration?: boolean;
|
||||
arRPC?: boolean;
|
||||
appBadge?: boolean;
|
||||
enableTaskbarFlashing?: boolean;
|
||||
disableMinSize?: boolean;
|
||||
clickTrayToShowHide?: boolean;
|
||||
customTitleBar?: boolean;
|
||||
tray: boolean;
|
||||
minimizeToTray: boolean;
|
||||
autoStartMinimized: boolean;
|
||||
openLinksWithElectron: boolean;
|
||||
staticTitle: boolean;
|
||||
enableMenu: boolean;
|
||||
enableShadow: boolean;
|
||||
enableRoundedCorners: boolean;
|
||||
disableSmoothScroll: boolean;
|
||||
hardwareAcceleration: boolean;
|
||||
hardwareVideoAcceleration: boolean;
|
||||
arRPC: boolean;
|
||||
appBadge: boolean;
|
||||
enableTaskbarFlashing: boolean;
|
||||
disableMinSize: boolean;
|
||||
clickTrayToShowHide: boolean;
|
||||
nativeTitleBar: boolean;
|
||||
|
||||
enableSplashScreen?: boolean;
|
||||
splashTheming?: boolean;
|
||||
enableSplashScreen: boolean;
|
||||
splashTheming: boolean;
|
||||
splashPixelated: boolean;
|
||||
splashColor?: string;
|
||||
splashBackground?: string;
|
||||
splashPixelated?: boolean;
|
||||
|
||||
spellCheckLanguages?: string[];
|
||||
|
||||
@@ -49,6 +51,7 @@ export interface Settings {
|
||||
ignoreDevices?: boolean;
|
||||
ignoreInputMedia?: boolean;
|
||||
|
||||
mute?: boolean;
|
||||
onlySpeakers?: boolean;
|
||||
onlyDefaultSpeakers?: boolean;
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user