28 Commits
v1.6.0 ... main

Author SHA1 Message Date
Vendicated
e9ff3cbb3b add electron fuses config for better security 2025-12-14 17:48:58 +01:00
DemonicSavage
8e91df0376 fix camera resolution being stuck to 640x480 (#1199)
Co-authored-by: V <vendicated@riseup.net>
2025-12-12 22:29:44 +01:00
futamebore
da8e8f52ab feat: add taskbar flashing on new notifications (#1209)
Co-authored-by: V <vendicated@riseup.net>
2025-12-12 22:23:13 +01:00
Vendicated
98b0ba85a3 fix macOS vibrancy support not working 2025-12-12 21:33:47 +01:00
Vendicated
d83aa6675e handle potential filesystem errors when saving settings 2025-12-12 21:11:32 +01:00
Vendicated
b3d595ef98 bump dependencies 2025-12-12 21:01:26 +01:00
Vendicated
24047d7fdd fix splash themeing 2025-12-12 20:56:56 +01:00
Vendicated
5c992d66a6 fix ScreensharePicker ui & modernise all UI 2025-12-12 18:43:30 +01:00
Vendicated
02ab7165aa use \i instead of .{1,3} 2025-11-23 23:57:14 +01:00
Vendicated
0483a1abdc improve eslint config 2025-11-23 23:55:37 +01:00
Vendicated
34c92cfb59 fix screenshare picker ui 2025-11-18 17:24:35 +01:00
Vendicated
28aeab979b Upgrade to electron 39.2.1
Fixes https://github.com/Vencord/Vesktop/issues/1204
2025-11-17 22:24:39 +01:00
Vendicated
8ca3e4f3a1 fix potential edge case in cli argument parsing 2025-11-15 11:33:21 +01:00
Vendicated
ae47c204fa fix --start-minimized not working when splash is disabled 2025-11-15 11:29:44 +01:00
Vendicated
f57245f297 fix views using quirks mode & non utf-8 2025-11-03 20:41:33 +01:00
Vendicated
9193ed58c9 resize splash image to 128x128 2025-10-28 17:32:54 +01:00
khcrysalis
38b7716b2f add new splash animation (#1195) 2025-10-28 16:26:27 +00:00
Vendicated
4b735b9739 bump to v1.6.1 2025-10-28 17:16:27 +01:00
Vendicated
707dbf4f75 upgrade electron to v39 2025-10-28 16:25:38 +01:00
Vendicated
a242d5d694 bound check entire window area instead of only top left corner
Co-Authored-By: MBStarup <mavi.nexu@gmail.com>
2025-10-23 21:17:17 +02:00
Vendicated
b75ed0766d clean up BrowserWindow options logic 2025-10-23 18:56:33 +02:00
Vendicated
03aa30dfc6 do window bound checks without relying on displayId
Fixes Vesktop not remembering its position correctly.
Seems like the display ids aren't consistent on some windows systems...
2025-10-23 18:17:04 +02:00
Vendicated
cd1a40e6c7 oh god no 2025-10-23 02:55:22 +02:00
Vendicated
3aa0bb806e fix some ENOENT errors on first launch 2025-10-23 02:53:13 +02:00
Vendicated
800a97105c fix metainfo generation 2025-10-22 16:30:20 +02:00
Vendicated
b02acd6a7b fix updater printing on --help / --version commands 2025-10-21 21:16:24 +02:00
Vendicated
d293b166fe fix cli argument parsing 2025-10-21 19:56:22 +02:00
Vendicated
28a13be709 add nearest neighbor option to splash customisation 2025-10-20 01:56:04 +02:00
47 changed files with 1182 additions and 798 deletions

View File

@@ -6,17 +6,34 @@
//@ts-check
import { defineConfig } from "eslint/config";
import stylistic from "@stylistic/eslint-plugin";
import pathAlias from "eslint-plugin-path-alias";
import react from "eslint-plugin-react";
import simpleHeader from "eslint-plugin-simple-header";
import importSort from "eslint-plugin-simple-import-sort";
import unusedImports from "eslint-plugin-unused-imports";
import tseslint from "typescript-eslint";
import prettier from "eslint-plugin-prettier";
export default tseslint.config(
export default defineConfig(
{ ignores: ["dist"] },
{
files: ["src/**/*.{tsx,ts,mts,mjs,js,jsx}"],
settings: {
react: {
version: "19"
}
},
...react.configs.flat.recommended,
rules: {
...react.configs.flat.recommended.rules,
"react/react-in-jsx-scope": "off",
"react/prop-types": "off",
"react/display-name": "off",
"react/no-unescaped-entities": "off"
}
},
{
files: ["src/**/*.{tsx,ts,mts,mjs,js,jsx}"],
plugins: {
@@ -24,8 +41,10 @@ export default tseslint.config(
stylistic,
importSort,
unusedImports,
// @ts-expect-error Missing types
pathAlias,
prettier
prettier,
"@typescript-eslint": tseslint.plugin
},
settings: {
"import/resolver": {
@@ -51,7 +70,6 @@ export default tseslint.config(
],
// ESLint Rules
yoda: "error",
eqeqeq: ["error", "always", { null: "ignore" }],
"prefer-destructuring": [
@@ -67,8 +85,19 @@ export default tseslint.config(
"no-invalid-regexp": "error",
"no-constant-condition": ["error", { checkLoops: false }],
"no-duplicate-imports": "error",
"dot-notation": "error",
"no-useless-escape": "error",
"@typescript-eslint/dot-notation": [
"error",
{
allowPrivateClassPropertyAccess: true,
allowProtectedClassPropertyAccess: true
}
],
"no-useless-escape": [
"error",
{
allowRegexCharacters: ["i"]
}
],
"no-fallthrough": "error",
"for-direction": "error",
"no-async-promise-executor": "error",
@@ -85,7 +114,7 @@ export default tseslint.config(
"no-unsafe-optional-chaining": "error",
"no-useless-backreference": "error",
"use-isnan": "error",
"prefer-const": "error",
"prefer-const": ["error", { destructuring: "all" }],
"prefer-spread": "error",
// Styling Rules

View File

@@ -1,6 +1,6 @@
{
"name": "vesktop",
"version": "1.6.0",
"version": "1.6.1",
"private": true,
"description": "Vesktop is a custom Discord desktop app",
"keywords": [],
@@ -35,28 +35,29 @@
},
"devDependencies": {
"@fal-works/esbuild-plugin-global-externals": "^2.1.2",
"@stylistic/eslint-plugin": "^5.4.0",
"@types/node": "^24.7.0",
"@stylistic/eslint-plugin": "^5.6.1",
"@types/node": "^25.0.1",
"@types/react": "19.2.1",
"@vencord/types": "^1.13.2",
"@vencord/types": "^1.13.7",
"dotenv": "^17.2.3",
"electron": "^38.0.0",
"electron": "^39.2.7",
"electron-builder": "^26.0.12",
"esbuild": "^0.25.10",
"eslint": "^9.37.0",
"esbuild": "^0.27.1",
"eslint": "^9.39.1",
"eslint-import-resolver-alias": "^1.1.2",
"eslint-plugin-path-alias": "^2.1.0",
"eslint-plugin-prettier": "^5.5.4",
"eslint-plugin-react": "^7.37.5",
"eslint-plugin-simple-header": "^1.2.2",
"eslint-plugin-simple-import-sort": "^12.1.1",
"eslint-plugin-unused-imports": "^4.2.0",
"eslint-plugin-unused-imports": "^4.3.0",
"libvesktop": "link:packages/libvesktop",
"prettier": "^3.6.2",
"prettier": "^3.7.4",
"source-map-support": "^0.5.21",
"tsx": "^4.20.6",
"type-fest": "^5.0.1",
"tsx": "^4.21.0",
"type-fest": "^5.3.1",
"typescript": "^5.9.3",
"typescript-eslint": "^8.46.0",
"typescript-eslint": "^8.49.0",
"xml-formatter": "^3.6.7"
},
"packageManager": "pnpm@10.7.1",
@@ -201,6 +202,16 @@
"fpm": [
"--rpm-rpmbuild-define=_build_id_links none"
]
},
"electronFuses": {
"runAsNode": false,
"enableCookieEncryption": false,
"enableNodeOptionsEnvironmentVariable": false,
"enableNodeCliInspectArguments": false,
"enableEmbeddedAsarIntegrityValidation": false,
"onlyLoadAppFromAsar": true,
"loadBrowserProcessSpecificV8Snapshot": false,
"grantFileProtocolExtraPrivileges": false
}
},
"pnpm": {

1069
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@@ -43,16 +43,25 @@ function generateDescription(description: string, descriptionNode: Element) {
}
}
const latestReleaseInformation = await fetch("https://api.github.com/repos/Vencord/Vesktop/releases/latest", {
const releases = await fetch("https://api.github.com/repos/Vencord/Vesktop/releases", {
headers: {
Accept: "application/vnd.github+json",
"X-Github-Api-Version": "2022-11-28"
}
}).then(res => res.json());
const metaInfo = await fetch(
"https://github.com/Vencord/Vesktop/releases/latest/download/dev.vencord.Vesktop.metainfo.xml"
).then(res => res.text());
const latestReleaseInformation = releases[0];
const metaInfo = await (async () => {
for (const release of releases) {
const metaAsset = release.assets.find((a: any) => a.name === "dev.vencord.Vesktop.metainfo.xml");
if (metaAsset) return fetch(metaAsset.browser_download_url).then(res => res.text());
}
})();
if (!metaInfo) {
throw new Error("Could not find existing meta information from any release");
}
const parser = new DOMParser().parseFromString(metaInfo, "text/xml");

View File

@@ -5,6 +5,7 @@
*/
import { app } from "electron";
import { basename } from "path";
import { stripIndent } from "shared/utils/text";
import { parseArgs, ParseArgsOptionDescriptor } from "util";
@@ -64,7 +65,12 @@ const extraOptions = {
}
} satisfies Record<string, Option>;
const args = basename(process.argv[0]).toLowerCase().startsWith("electron")
? process.argv.slice(2)
: process.argv.slice(1);
export const CommandLine = parseArgs({
args,
options,
strict: false as true, // we manually check later, so cast to true to get better types
allowPositionals: true
@@ -82,7 +88,7 @@ export function checkCommandLineForHelpOrVersion() {
const base = stripIndent`
Vesktop v${app.getVersion()}
Usage: ${process.execPath} [options] [url]
Usage: ${basename(process.execPath)} [options] [url]
Electron Options:
See <https://www.electronjs.org/docs/latest/api/command-line-switches#electron-cli-flags>
@@ -135,3 +141,5 @@ export function checkCommandLineForHelpOrVersion() {
}
}
}
checkCommandLineForHelpOrVersion();

View File

@@ -26,6 +26,7 @@ export const SESSION_DATA_DIR = join(DATA_DIR, "sessionData");
app.setPath("sessionData", SESSION_DATA_DIR);
export const VENCORD_SETTINGS_DIR = join(DATA_DIR, "settings");
mkdirSync(VENCORD_SETTINGS_DIR, { recursive: true });
export const VENCORD_QUICKCSS_FILE = join(VENCORD_SETTINGS_DIR, "quickCss.css");
export const VENCORD_SETTINGS_FILE = join(VENCORD_SETTINGS_DIR, "settings.json");
export const VENCORD_THEMES_DIR = join(DATA_DIR, "themes");

View File

@@ -31,8 +31,8 @@ export function createFirstLaunchTour() {
transparent: false,
frame: true,
autoHideMenuBar: true,
height: 470,
width: 550
height: 550,
width: 600
});
makeLinksOpenExternally(win);
@@ -44,7 +44,6 @@ export function createFirstLaunchTour() {
if (!msg.startsWith("form:")) return;
const data = JSON.parse(msg.slice(5)) as Data;
console.log(data);
State.store.firstLaunch = false;
Settings.store.discordBranch = data.discordBranch;
Settings.store.minimizeToTray = !!data.minimizeToTray;
@@ -63,7 +62,11 @@ export function createFirstLaunchTour() {
copyFileSync(join(from, file), join(to, file));
}
} catch (e) {
console.error("Failed to import settings:", e);
if (e instanceof Error && "code" in e && e.code === "ENOENT") {
console.log("No Vencord settings found to import.");
} else {
console.error("Failed to import Vencord settings:", e);
}
}
}

View File

@@ -4,6 +4,7 @@
* SPDX-License-Identifier: GPL-3.0-or-later
*/
import "./cli";
import "./updater";
import "./ipc";
import "./userAssets";
@@ -11,7 +12,6 @@ import "./vesktopProtocol";
import { app, BrowserWindow, nativeTheme } from "electron";
import { checkCommandLineForHelpOrVersion } from "./cli";
import { DATA_DIR } from "./constants";
import { createFirstLaunchTour } from "./firstLaunch";
import { createWindows, mainWin } from "./mainWindow";
@@ -21,8 +21,6 @@ import { Settings, State } from "./settings";
import { setAsDefaultProtocolClient } from "./utils/setAsDefaultProtocolClient";
import { isDeckGameMode } from "./utils/steamOS";
checkCommandLineForHelpOrVersion();
console.log("Vesktop v" + app.getVersion());
// Make the Vencord files use our DATA_DIR

View File

@@ -142,6 +142,11 @@ handle(IpcEvents.SELECT_VENCORD_DIR, async (_e, value?: null) => {
handle(IpcEvents.SET_BADGE_COUNT, (_, count: number) => setBadgeCount(count));
handle(IpcEvents.FLASH_FRAME, (_, flag: boolean) => {
if (!mainWin || mainWin.isDestroyed() || (flag && mainWin.isFocused())) return;
mainWin.flashFrame(flag);
});
handle(IpcEvents.CLIPBOARD_COPY_IMAGE, async (_, buf: ArrayBuffer, src: string) => {
clipboard.write({
html: `<img src="${src.replaceAll('"', '\\"')}">`,

View File

@@ -11,6 +11,7 @@ import {
Menu,
MenuItemConstructorOptions,
nativeTheme,
Rectangle,
screen,
session
} from "electron";
@@ -175,55 +176,6 @@ function initMenuBar(win: BrowserWindow) {
Menu.setApplicationMenu(menu);
}
function getWindowBoundsOptions(): BrowserWindowConstructorOptions {
// We want the default window behaivour to apply in game mode since it expects everything to be fullscreen and maximized.
if (isDeckGameMode) return {};
const { x, y, width, height } = State.store.windowBounds ?? {};
const options = {
width: width ?? DEFAULT_WIDTH,
height: height ?? DEFAULT_HEIGHT
} as BrowserWindowConstructorOptions;
const storedDisplay = screen.getAllDisplays().find(display => display.id === State.store.displayId);
if (x != null && y != null && storedDisplay) {
options.x = x;
options.y = y;
}
if (!Settings.store.disableMinSize) {
options.minWidth = MIN_WIDTH;
options.minHeight = MIN_HEIGHT;
}
return options;
}
function getDarwinOptions(): BrowserWindowConstructorOptions {
const options = {
titleBarStyle: "hidden",
trafficLightPosition: { x: 10, y: 10 }
} as BrowserWindowConstructorOptions;
const { splashTheming, splashBackground } = Settings.store;
const { macosTranslucency } = VencordSettings.store;
if (macosTranslucency) {
options.vibrancy = "sidebar";
options.backgroundColor = "#ffffff00";
} else {
if (splashTheming !== false) {
options.backgroundColor = splashBackground;
} else {
options.backgroundColor = nativeTheme.shouldUseDarkColors ? "#313338" : "#ffffff";
}
}
return options;
}
function initWindowBoundsListeners(win: BrowserWindow) {
const saveState = () => {
State.store.maximized = win.isMaximized();
@@ -236,7 +188,6 @@ function initWindowBoundsListeners(win: BrowserWindow) {
const saveBounds = () => {
State.store.windowBounds = win.getBounds();
State.store.displayId = screen.getDisplayMatching(State.store.windowBounds).id;
};
win.on("resize", saveBounds);
@@ -324,26 +275,55 @@ function initStaticTitle(win: BrowserWindow) {
});
}
function createMainWindow() {
// Clear up previous settings listeners
removeSettingsListeners();
removeVencordSettingsListeners();
function getWindowBoundsOptions(): BrowserWindowConstructorOptions {
// We want the default window behaviour to apply in game mode since it expects everything to be fullscreen and maximized.
if (isDeckGameMode) return {};
const { x, y, width = DEFAULT_WIDTH, height = DEFAULT_HEIGHT } = State.store.windowBounds ?? {};
const options = { width, height } as BrowserWindowConstructorOptions;
if (x != null && y != null) {
function isInBounds(rect: Rectangle, display: Rectangle) {
return !(
rect.x + rect.width < display.x ||
rect.x > display.x + display.width ||
rect.y + rect.height < display.y ||
rect.y > display.y + display.height
);
}
const inBounds = screen.getAllDisplays().some(d => isInBounds({ x, y, width, height }, d.bounds));
if (inBounds) {
options.x = x;
options.y = y;
}
}
if (!Settings.store.disableMinSize) {
options.minWidth = MIN_WIDTH;
options.minHeight = MIN_HEIGHT;
}
return options;
}
function buildBrowserWindowOptions(): BrowserWindowConstructorOptions {
const { staticTitle, transparencyOption, enableMenu, customTitleBar, splashTheming, splashBackground } =
Settings.store;
const { frameless, transparent } = VencordSettings.store;
const { frameless, transparent, macosVibrancyStyle } = VencordSettings.store;
const noFrame = frameless === true || customTitleBar === true;
const backgroundColor =
splashTheming !== false ? splashBackground : nativeTheme.shouldUseDarkColors ? "#313338" : "#ffffff";
const win = (mainWin = new BrowserWindow({
show: Settings.store.enableSplashScreen === false,
const options: BrowserWindowConstructorOptions = {
show: Settings.store.enableSplashScreen === false && !CommandLine.values["start-minimized"],
backgroundColor,
webPreferences: {
nodeIntegration: false,
sandbox: false,
sandbox: false, // TODO
contextIsolation: true,
devTools: true,
preload: join(__dirname, "preload.js"),
@@ -352,28 +332,50 @@ function createMainWindow() {
backgroundThrottling: false
},
frame: !noFrame,
...(transparent && {
transparent: true,
backgroundColor: "#00000000"
}),
...(transparencyOption &&
transparencyOption !== "none" && {
backgroundColor: "#00000000",
backgroundMaterial: transparencyOption
}),
// Fix transparencyOption for custom discord titlebar
...(customTitleBar &&
transparencyOption &&
transparencyOption !== "none" && {
transparent: true
}),
...(staticTitle && { title: "Vesktop" }),
...(process.platform === "darwin" && getDarwinOptions()),
...getWindowBoundsOptions(),
autoHideMenuBar: enableMenu
}));
autoHideMenuBar: enableMenu,
...getWindowBoundsOptions()
};
if (transparent) {
options.transparent = true;
options.backgroundColor = "#00000000";
}
if (transparencyOption && transparencyOption !== "none") {
options.backgroundColor = "#00000000";
options.backgroundMaterial = transparencyOption;
if (customTitleBar) {
options.transparent = true;
}
}
if (staticTitle) {
options.title = "Vesktop";
}
if (process.platform === "darwin") {
options.titleBarStyle = "hidden";
options.trafficLightPosition = { x: 10, y: 10 };
if (macosVibrancyStyle) {
options.vibrancy = macosVibrancyStyle;
options.backgroundColor = "#00000000";
}
}
return options;
}
function createMainWindow() {
// Clear up previous settings listeners
removeSettingsListeners();
removeVencordSettingsListeners();
const win = (mainWin = new BrowserWindow(buildBrowserWindowOptions()));
win.setMenuBarVisibility(false);
if (process.platform === "darwin" && customTitleBar) win.setWindowButtonVisibility(false);
if (process.platform === "darwin" && Settings.store.customTitleBar) win.setWindowButtonVisibility(false);
win.on("close", e => {
const useTray = !isDeckGameMode && Settings.store.minimizeToTray !== false && Settings.store.tray !== false;
@@ -387,6 +389,10 @@ function createMainWindow() {
return false;
});
win.on("focus", () => {
win.flashFrame(false);
});
initWindowBoundsListeners(win);
if (!isDeckGameMode && (Settings.store.tray ?? true) && process.platform !== "darwin")
initTray(win, q => (isQuitting = q));

View File

@@ -4,6 +4,7 @@
* SPDX-License-Identifier: GPL-3.0-or-later
*/
import { type Settings as TVencordSettings } from "@vencord/types/Vencord";
import { mkdirSync, readFileSync, writeFileSync } from "fs";
import { dirname, join } from "path";
import type { Settings as TSettings, State as TState } from "shared/settings";
@@ -27,13 +28,17 @@ function loadSettings<T extends object = any>(file: string, name: string) {
const store = new SettingsStore(settings);
store.addGlobalChangeListener(o => {
mkdirSync(dirname(file), { recursive: true });
writeFileSync(file, JSON.stringify(o, null, 4));
try {
mkdirSync(dirname(file), { recursive: true });
writeFileSync(file, JSON.stringify(o, null, 4));
} catch (err) {
console.error(`Failed to save settings to ${name}.json:`, err);
}
});
return store;
}
export const Settings = loadSettings<TSettings>(SETTINGS_FILE, "Vesktop settings");
export const VencordSettings = loadSettings<any>(VENCORD_SETTINGS_FILE, "Vencord settings");
export const VencordSettings = loadSettings<TVencordSettings>(VENCORD_SETTINGS_FILE, "Vencord settings");
export const State = loadSettings<TState>(STATE_FILE, "Vesktop state");

View File

@@ -24,7 +24,7 @@ export function createSplashWindow(startMinimized = false) {
loadView(splash, "splash.html");
const { splashBackground, splashColor, splashTheming } = Settings.store;
const { splashBackground, splashColor, splashTheming, splashPixelated } = Settings.store;
if (splashTheming !== false) {
if (splashColor) {
@@ -39,6 +39,10 @@ export function createSplashWindow(startMinimized = false) {
}
}
if (splashPixelated) {
splash.webContents.insertCSS(`img { image-rendering: pixelated; }`);
}
return splash;
}

View File

@@ -68,6 +68,7 @@ export const VesktopNative = {
close: (key?: string) => invoke<void>(IpcEvents.CLOSE, key),
minimize: (key?: string) => invoke<void>(IpcEvents.MINIMIZE, key),
maximize: (key?: string) => invoke<void>(IpcEvents.MAXIMIZE, key),
flashFrame: (flag: boolean) => invoke<void>(IpcEvents.FLASH_FRAME, flag),
setDevtoolsCallbacks: (onOpen: () => void, onClose: () => void) => {
onDevtoolsOpen = onOpen;
onDevtoolsClose = onClose;

View File

@@ -4,6 +4,7 @@
* SPDX-License-Identifier: GPL-3.0-or-later
*/
import type arRpcPlugin from "@vencord/types/plugins/arRPC.web";
import { Logger } from "@vencord/types/utils";
import { findLazy, findStoreLazy, onceReady } from "@vencord/types/webpack";
import { FluxDispatcher, InviteActions } from "@vencord/types/webpack/common";
@@ -15,9 +16,7 @@ import { Settings } from "./settings";
const logger = new Logger("VesktopRPC", "#5865f2");
const StreamerModeStore = findStoreLazy("StreamerModeStore");
const arRPC = Vencord.Plugins.plugins["WebRichPresence (arRPC)"] as any as {
handleEvent(e: MessageEvent): void;
};
const arRPC = Vencord.Plugins.plugins["WebRichPresence (arRPC)"] as typeof arRpcPlugin;
onIpcCommand(IpcCommands.RPC_ACTIVITY, async jsonData => {
if (!Settings.store.arRPC) return;

View File

@@ -7,10 +7,31 @@
import "./screenSharePicker.css";
import { classNameFactory } from "@vencord/types/api/Styles";
import { FormSwitch } from "@vencord/types/components";
import { closeModal, Logger, Modals, ModalSize, openModal, useAwaiter } from "@vencord/types/utils";
import {
BaseText,
Button,
Card,
CogWheel,
FormSwitch,
Heading,
HeadingTertiary,
Margins,
Paragraph,
RestartIcon,
Span
} from "@vencord/types/components";
import {
closeModal,
Logger,
ModalCloseButton,
Modals,
ModalSize,
openModal,
useAwaiter,
useForceUpdater
} from "@vencord/types/utils";
import { onceReady } from "@vencord/types/webpack";
import { Button, Card, FluxDispatcher, Forms, Select, Text, UserStore, useState } from "@vencord/types/webpack/common";
import { FluxDispatcher, Select, UserStore, useState } from "@vencord/types/webpack/common";
import { Node } from "@vencord/venmic";
import type { Dispatch, SetStateAction } from "react";
import { MediaEngineStore } from "renderer/common";
@@ -18,6 +39,8 @@ import { addPatch } from "renderer/patches/shared";
import { State, useSettings, useVesktopState } from "renderer/settings";
import { isLinux, isWindows } from "renderer/utils";
import { SimpleErrorBoundary } from "./SimpleErrorBoundary";
const StreamResolutions = ["480", "720", "1080", "1440", "2160"] as const;
const StreamFps = ["15", "30", "60"] as const;
@@ -60,16 +83,16 @@ const logger = new Logger("VesktopScreenShare");
addPatch({
patches: [
{
find: "this.localWant=",
find: "this.getDefaultGoliveQuality()",
replacement: {
match: /this.localWant=/,
replace: "$self.patchStreamQuality(this);$&"
match: /this\.getDefaultGoliveQuality\(\)/,
replace: "$self.patchStreamQuality($&)"
}
}
],
patchStreamQuality(opts: any) {
const { screenshareQuality } = State.store;
if (!screenshareQuality) return;
if (!screenshareQuality) return opts;
const framerate = Number(screenshareQuality.frameRate);
const height = Number(screenshareQuality.resolution);
@@ -94,6 +117,7 @@ addPatch({
height,
pixelCount: height * width
});
return opts;
}
});
@@ -145,6 +169,9 @@ export function openScreenSharePicker(screens: Source[], skipPicker: boolean) {
onCloseRequest() {
closeModal(key);
reject("Aborted");
},
onCloseCallback() {
reject("Aborted");
}
}
);
@@ -165,9 +192,7 @@ function ScreenPicker({ screens, chooseScreen }: { screens: Source[]; chooseScre
/>
<img src={url} alt="" />
<Text className={cl("screen-name")} variant="text-sm/normal">
{name}
</Text>
<Paragraph className={cl("screen-name")}>{name}</Paragraph>
</label>
))}
</div>
@@ -188,20 +213,16 @@ function AudioSettingsModal({
return (
<Modals.ModalRoot {...modalProps} size={ModalSize.MEDIUM}>
<Modals.ModalHeader className={cl("header")}>
<Forms.FormTitle tag="h2" className={cl("header-title")}>
Venmic Settings
</Forms.FormTitle>
<Modals.ModalCloseButton onClick={close} />
<BaseText size="lg" weight="semibold" tag="h3" style={{ flexGrow: 1 }}>
Audio Settings
</BaseText>
<ModalCloseButton onClick={close} />
</Modals.ModalHeader>
<Modals.ModalContent className={cl("modal")}>
<Modals.ModalContent className={cl("modal", "venmic-settings")}>
<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.
</>
}
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."
hideBorder
onChange={v => (Settings.audio = { ...Settings.audio, workaround: v })}
value={Settings.audio?.workaround ?? false}
@@ -209,10 +230,7 @@ function AudioSettingsModal({
<FormSwitch
title="Only Speakers"
description={
<>
When sharing entire desktop audio, only share apps that play to a speaker. You may want to
disable this when using "mix bussing".
</>
'When sharing entire desktop audio, only share apps that play to a speaker. You may want to disable this when using "mix bussing".'
}
hideBorder
onChange={v => (Settings.audio = { ...Settings.audio, onlySpeakers: v })}
@@ -232,7 +250,7 @@ function AudioSettingsModal({
/>
<FormSwitch
title="Ignore Inputs"
description={<>Exclude nodes that are intended to capture audio.</>}
description="Exclude nodes that are intended to capture audio."
hideBorder
onChange={v => (Settings.audio = { ...Settings.audio, ignoreInputMedia: v })}
value={Settings.audio?.ignoreInputMedia ?? true}
@@ -240,10 +258,7 @@ function AudioSettingsModal({
<FormSwitch
title="Ignore Virtual"
description={
<>
Exclude virtual nodes, such as nodes belonging to loopbacks. This might be useful when using
"mix bussing".
</>
'Exclude virtual nodes, such as nodes belonging to loopbacks. This might be useful when using "mix bussing".'
}
hideBorder
onChange={v => (Settings.audio = { ...Settings.audio, ignoreVirtual: v })}
@@ -251,7 +266,7 @@ function AudioSettingsModal({
/>
<FormSwitch
title="Ignore Devices"
description={<>Exclude device nodes, such as nodes belonging to microphones or speakers.</>}
description="Exclude device nodes, such as nodes belonging to microphones or speakers."
hideBorder
onChange={v =>
(Settings.audio = {
@@ -264,7 +279,7 @@ function AudioSettingsModal({
/>
<FormSwitch
title="Granular Selection"
description={<>Allow to select applications more granularly.</>}
description="Allow to select applications more granularly."
hideBorder
onChange={value => {
Settings.audio = { ...Settings.audio, granularSelect: value };
@@ -290,7 +305,7 @@ function AudioSettingsModal({
/>
</Modals.ModalContent>
<Modals.ModalFooter className={cl("footer")}>
<Button color={Button.Colors.TRANSPARENT} onClick={close}>
<Button variant="secondary" onClick={close}>
Back
</Button>
</Modals.ModalFooter>
@@ -311,7 +326,7 @@ function OptionRadio<Settings extends object, Key extends keyof Settings>(props:
<div className={cl("option-radios")}>
{(options as string[]).map((option, idx) => (
<label className={cl("option-radio")} data-checked={settings[settingsKey] === option} key={option}>
<Text variant="text-sm/bold">{labels?.[idx] ?? option}</Text>
<Span weight="bold">{labels?.[idx] ?? option}</Span>
<input
className={cl("option-input")}
type="radio"
@@ -349,7 +364,7 @@ function StreamSettingsUi({
);
const openSettings = () => {
const key = openModal(props => (
openModal(props => (
<AudioSettingsModal
modalProps={props}
close={() => props.onClose()}
@@ -362,18 +377,18 @@ function StreamSettingsUi({
return (
<div>
<Forms.FormTitle>What you're streaming</Forms.FormTitle>
<HeadingTertiary className={Margins.bottom8}>What you're streaming</HeadingTertiary>
<Card className={cl("card", "preview")}>
<img src={thumb} alt="" className={cl(isLinux ? "preview-img-linux" : "preview-img")} />
<Text variant="text-sm/normal">{source.name}</Text>
<Paragraph>{source.name}</Paragraph>
</Card>
<Forms.FormTitle>Stream Settings</Forms.FormTitle>
<HeadingTertiary className={Margins.bottom8}>Stream Settings</HeadingTertiary>
<Card className={cl("card")}>
<div className={cl("quality")}>
<section className={cl("quality-section")}>
<Forms.FormTitle>Resolution</Forms.FormTitle>
<Heading tag="h5">Resolution</Heading>
<OptionRadio
options={StreamResolutions}
settings={qualitySettings}
@@ -383,7 +398,7 @@ function StreamSettingsUi({
</section>
<section className={cl("quality-section")}>
<Forms.FormTitle>Frame Rate</Forms.FormTitle>
<Heading tag="h5">Frame Rate</Heading>
<OptionRadio
options={StreamFps}
settings={qualitySettings}
@@ -394,7 +409,7 @@ function StreamSettingsUi({
</div>
<div className={cl("quality")}>
<section className={cl("quality-section")}>
<Forms.FormTitle>Content Type</Forms.FormTitle>
<Heading tag="h5">Content Type</Heading>
<div>
<OptionRadio
options={["motion", "detail"]}
@@ -403,12 +418,11 @@ function StreamSettingsUi({
settingsKey="contentHint"
onChange={option => setSettings(s => ({ ...s, contentHint: option }))}
/>
<div className={cl("hint-description")}>
<p>
Choosing "Prefer Clarity" will result in a significantly lower framerate in exchange
for a much sharper and clearer image.
</p>
</div>
<Paragraph className={Margins.top8}>
Choosing "Prefer Clarity" will result in a significantly lower framerate in exchange for
a much sharper and clearer image.
</Paragraph>
</div>
{isWindows && (
<FormSwitch
@@ -568,8 +582,10 @@ function AudioSourcePickerLinux({
setIncludeSources: (s: AudioSources) => void;
setExcludeSources: (s: AudioSources) => void;
}) {
const [audioSourcesSignal, refreshAudioSources] = useForceUpdater(true);
const [sources, _, loading] = useAwaiter(() => VesktopNative.virtmic.list(), {
fallbackValue: { ok: true, targets: [], hasPipewirePulse: true }
fallbackValue: { ok: true, targets: [], hasPipewirePulse: true },
deps: [audioSourcesSignal]
});
const hasPipewirePulse = sources.ok ? sources.hasPipewirePulse : true;
@@ -577,32 +593,40 @@ function AudioSourcePickerLinux({
if (!sources.ok && sources.isGlibCxxOutdated) {
return (
<Forms.FormText>
<Paragraph>
Failed to retrieve Audio Sources because your C++ library is too old to run
<a href="https://github.com/Vencord/venmic" target="_blank">
<a href="https://github.com/Vencord/venmic" target="_blank" rel="noreferrer">
venmic
</a>
. See{" "}
<a href="https://gist.github.com/Vendicated/b655044ffbb16b2716095a448c6d827a" target="_blank">
<a
href="https://gist.github.com/Vendicated/b655044ffbb16b2716095a448c6d827a"
target="_blank"
rel="noreferrer"
>
this guide
</a>{" "}
for possible solutions.
</Forms.FormText>
</Paragraph>
);
}
if (!hasPipewirePulse && !ignorePulseWarning) {
return (
<Text variant="text-sm/normal">
<Paragraph>
Could not find pipewire-pulse. See{" "}
<a href="https://gist.github.com/the-spyke/2de98b22ff4f978ebf0650c90e82027e#install" target="_blank">
<a
href="https://gist.github.com/the-spyke/2de98b22ff4f978ebf0650c90e82027e#install"
target="_blank"
rel="noreferrer"
>
this guide
</a>{" "}
on how to switch to pipewire. <br />
You can still continue, however, please{" "}
<b>beware that you can only share audio of apps that are running under pipewire</b>.{" "}
<a onClick={() => setIgnorePulseWarning(true)}>I know what I'm doing!</a>
</Text>
</Paragraph>
);
}
@@ -620,45 +644,56 @@ function AudioSourcePickerLinux({
return (
<>
<div className={cl({ quality: includeSources === "Entire System" })}>
<div className={cl("audio-sources")}>
<section>
<Forms.FormTitle>{loading ? "Loading Sources..." : "Audio Sources"}</Forms.FormTitle>
<Select
options={allSources.map(({ name, value }) => ({
label: name,
value: value,
default: name === "None"
}))}
isSelected={isItemSelected(includeSources)}
select={updateItems(setIncludeSources, includeSources)}
serialize={String}
popoutPosition="top"
closeOnSelect={false}
/>
</section>
{includeSources === "Entire System" && (
<section>
<Forms.FormTitle>Exclude Sources</Forms.FormTitle>
<Heading tag="h5">{loading ? "Loading Sources..." : "Audio Sources"}</Heading>
<SimpleErrorBoundary>
<Select
options={allSources
.filter(x => x.name !== "Entire System")
.map(({ name, value }) => ({
label: name,
value: value,
default: name === "None"
}))}
isSelected={isItemSelected(excludeSources)}
select={updateItems(setExcludeSources, excludeSources)}
options={allSources.map(({ name, value }) => ({
label: name,
value: value,
default: name === "None"
}))}
isSelected={isItemSelected(includeSources)}
select={updateItems(setIncludeSources, includeSources)}
serialize={String}
popoutPosition="top"
closeOnSelect={false}
/>
</SimpleErrorBoundary>
</section>
{includeSources === "Entire System" && (
<section>
<Heading tag="h5">Exclude Sources</Heading>
<SimpleErrorBoundary>
<Select
options={allSources
.filter(x => x.name !== "Entire System")
.map(({ name, value }) => ({
label: name,
value: value,
default: name === "None"
}))}
isSelected={isItemSelected(excludeSources)}
select={updateItems(setExcludeSources, excludeSources)}
serialize={String}
popoutPosition="top"
closeOnSelect={false}
/>
</SimpleErrorBoundary>
</section>
)}
</div>
<Button color={Button.Colors.TRANSPARENT} onClick={openSettings} className={cl("settings-button")}>
Open Audio Settings
</Button>
<div className={cl("settings-buttons")}>
<Button variant="secondary" onClick={refreshAudioSources} className={cl("settings-button")}>
<RestartIcon className={cl("settings-button-icon")} />
Refresh Audio Sources
</Button>
<Button variant="secondary" onClick={openSettings} className={cl("settings-button")}>
<CogWheel className={cl("settings-button-icon")} />
Open Audio Settings
</Button>
</div>
</>
);
}
@@ -690,8 +725,10 @@ function ModalComponent({
return (
<Modals.ModalRoot {...modalProps} size={ModalSize.MEDIUM}>
<Modals.ModalHeader className={cl("header")}>
<Forms.FormTitle tag="h2">ScreenShare</Forms.FormTitle>
<Modals.ModalCloseButton onClick={close} />
<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 ? (
@@ -769,11 +806,11 @@ function ModalComponent({
</Button>
{selected && !skipPicker ? (
<Button color={Button.Colors.TRANSPARENT} onClick={() => setSelected(void 0)}>
<Button variant="secondary" onClick={() => setSelected(void 0)}>
Back
</Button>
) : (
<Button color={Button.Colors.TRANSPARENT} onClick={close}>
<Button variant="secondary" onClick={close}>
Cancel
</Button>
)}

View File

@@ -0,0 +1,46 @@
/*
* Vesktop, a desktop app aiming to give you a snappier Discord Experience
* Copyright (c) 2025 Vendicated and Vesktop contributors
* SPDX-License-Identifier: GPL-3.0-or-later
*/
import { Card, ErrorBoundary, HeadingTertiary, Paragraph, TextButton } from "@vencord/types/components";
import { FluxDispatcher, InviteActions } from "@vencord/types/webpack/common";
import type { PropsWithChildren } from "react";
async function openSupportChannel() {
const code = "YVbdG2ZRG4";
try {
const { invite } = await InviteActions.resolveInvite(code, "Desktop Modal");
if (!invite) throw 0;
await FluxDispatcher.dispatch({
type: "INVITE_MODAL_OPEN",
invite,
code,
context: "APP"
});
} catch {
window.open(`https://discord.gg/${code}`, "_blank");
}
}
function Fallback() {
return (
<Card variant="danger">
<HeadingTertiary>Something went wrong.</HeadingTertiary>
<Paragraph>
Please make sure Vencord and Vesktop are fully up to date. You can get help in our{" "}
<TextButton variant="link" onClick={openSupportChannel}>
Support Channel
</TextButton>
</Paragraph>
</Card>
);
}
export function SimpleErrorBoundary({ children }: PropsWithChildren<{}>) {
return <ErrorBoundary fallback={Fallback}>{children}</ErrorBoundary>;
}

View File

@@ -2,8 +2,13 @@
padding: 1em;
}
.vcd-screen-picker-header-title {
margin: 0;
.vcd-screen-picker-header-close-button {
margin-left: auto;
}
.vcd-screen-picker-venmic-settings {
display: grid;
gap: 8px;
}
.vcd-screen-picker-footer {
@@ -76,13 +81,13 @@
/* Option Radios */
.vcd-screen-picker-option-radios {
display: flex;
display: grid;
grid-template-columns: repeat(auto-fit, minmax(0, 1fr));
width: 100%;
border-radius: 3px;
}
.vcd-screen-picker-option-radio {
flex: 1 1 auto;
text-align: center;
background-color: var(--background-secondary);
border: 1px solid var(--primary-800);
@@ -117,19 +122,33 @@
flex: 1 1 auto;
}
.vcd-screen-picker-settings-button {
margin-left: auto;
margin-top: 0.3rem;
.vcd-screen-picker-settings-buttons {
display: flex;
justify-content: end;
gap: 0.5em;
margin-top: 0.75em;
}
.vcd-screen-picker-settings-button {
display: flex;
gap: 0.25em;
padding-inline: 0.5em 1em;
}
.vcd-screen-picker-settings-button-icon {
height: 1em;
}
.vcd-screen-picker-audio {
margin-bottom: 0;
}
.vcd-screen-picker-hint-description {
color: var(--header-secondary);
font-size: 14px;
line-height: 20px;
font-weight: 400;
}
.vcd-screen-picker-audio-sources {
display: flex;
gap: 1em;
>section {
flex: 1;
}
}

View File

@@ -4,6 +4,7 @@
* SPDX-License-Identifier: GPL-3.0-or-later
*/
import { BaseText, Button, Heading, Paragraph, TextButton } from "@vencord/types/components";
import {
Margins,
ModalCloseButton,
@@ -14,7 +15,7 @@ import {
openModal,
useForceUpdater
} from "@vencord/types/utils";
import { Button, Forms, Text, Toasts } from "@vencord/types/webpack/common";
import { Toasts } from "@vencord/types/webpack/common";
import { Settings } from "shared/settings";
import { cl, SettingsComponent } from "./Settings";
@@ -27,20 +28,20 @@ function openDeveloperOptionsModal(settings: Settings) {
openModal(props => (
<ModalRoot {...props} size={ModalSize.MEDIUM}>
<ModalHeader>
<Text variant="heading-lg/semibold" style={{ flexGrow: 1 }}>
<BaseText size="lg" weight="semibold" tag="h3" style={{ flexGrow: 1 }}>
Vesktop Developer Options
</Text>
</BaseText>
<ModalCloseButton onClick={props.onClose} />
</ModalHeader>
<ModalContent>
<div style={{ padding: "1em 0" }}>
<Forms.FormTitle tag="h5">Vencord Location</Forms.FormTitle>
<Heading tag="h5">Vencord Location</Heading>
<VencordLocationPicker settings={settings} />
<Forms.FormTitle tag="h5" className={Margins.top16}>
<Heading tag="h5" className={Margins.top16}>
Debugging
</Forms.FormTitle>
</Heading>
<div className={cl("button-grid")}>
<Button onClick={() => VesktopNative.debug.launchGpu()}>Open chrome://gpu</Button>
<Button onClick={() => VesktopNative.debug.launchWebrtcInternals()}>
@@ -59,25 +60,24 @@ const VencordLocationPicker: SettingsComponent = ({ settings }) => {
return (
<>
<Forms.FormText>
<Paragraph>
Vencord files are loaded from{" "}
{vencordDir ? (
<a
href="about:blank"
<TextButton
variant="link"
onClick={e => {
e.preventDefault();
VesktopNative.fileManager.showItemInFolder(vencordDir!);
}}
>
{vencordDir}
</a>
</TextButton>
) : (
"the default location"
)}
</Forms.FormText>
</Paragraph>
<div className={cl("button-grid")}>
<Button
size={Button.Sizes.SMALL}
onClick={async () => {
const choice = await VesktopNative.fileManager.selectVencordDir();
switch (choice) {
@@ -105,8 +105,7 @@ const VencordLocationPicker: SettingsComponent = ({ settings }) => {
Change
</Button>
<Button
size={Button.Sizes.SMALL}
color={Button.Colors.RED}
variant="dangerPrimary"
onClick={async () => {
await VesktopNative.fileManager.selectVencordDir(null);
forceUpdate();

View File

@@ -4,14 +4,14 @@
* SPDX-License-Identifier: GPL-3.0-or-later
*/
import { ErrorBoundary } from "@vencord/types/components";
import { Select } from "@vencord/types/webpack/common";
import { SimpleErrorBoundary } from "../SimpleErrorBoundary";
import { SettingsComponent } from "./Settings";
export const DiscordBranchPicker: SettingsComponent = ({ settings }) => {
return (
<ErrorBoundary noop>
<SimpleErrorBoundary>
<Select
placeholder="Stable"
options={[
@@ -24,6 +24,6 @@ export const DiscordBranchPicker: SettingsComponent = ({ settings }) => {
isSelected={v => v === settings.discordBranch}
serialize={s => s}
/>
</ErrorBoundary>
</SimpleErrorBoundary>
);
};

View File

@@ -0,0 +1,27 @@
/*
* Vesktop, a desktop app aiming to give you a snappier Discord Experience
* Copyright (c) 2025 Vendicated and Vesktop contributors
* SPDX-License-Identifier: GPL-3.0-or-later
*/
import { Button, Card, HeadingTertiary, Paragraph } from "@vencord/types/components";
import { useAwaiter } from "@vencord/types/utils";
import { cl } from "./Settings";
export function OutdatedVesktopWarning() {
const [isOutdated] = useAwaiter(VesktopNative.app.isOutdated);
if (!isOutdated) return null;
return (
<Card variant="warning" className={cl("updater-card")}>
<HeadingTertiary>Your Vesktop is outdated!</HeadingTertiary>
<Paragraph>Staying up to date is important for security and stability.</Paragraph>
<Button onClick={() => VesktopNative.app.openUpdater()} variant="secondary">
Open Updater
</Button>
</Card>
);
}

View File

@@ -7,8 +7,7 @@
import "./settings.css";
import { classNameFactory } from "@vencord/types/api/Styles";
import { ErrorBoundary } from "@vencord/types/components";
import { Forms, Text } from "@vencord/types/webpack/common";
import { BaseText, Divider, ErrorBoundary } from "@vencord/types/components";
import { ComponentType } from "react";
import { Settings, useSettings } from "renderer/settings";
import { isMac, isWindows } from "renderer/utils";
@@ -17,7 +16,7 @@ import { AutoStartToggle } from "./AutoStartToggle";
import { DeveloperOptionsButton } from "./DeveloperOptions";
import { DiscordBranchPicker } from "./DiscordBranchPicker";
import { NotificationBadgeToggle } from "./NotificationBadgeToggle";
import { Updater } from "./Updater";
import { OutdatedVesktopWarning } from "./OutdatedVesktopWarning";
import { UserAssetsButton } from "./UserAssets";
import { VesktopSettingsSwitch } from "./VesktopSettingsSwitch";
import { WindowsTransparencyControls } from "./WindowsTransparencyControls";
@@ -125,7 +124,15 @@ const SettingsOptions: Record<string, Array<BooleanSetting | SettingsComponent>>
defaultValue: false
}
],
Notifications: [NotificationBadgeToggle],
Notifications: [
NotificationBadgeToggle,
{
key: "enableTaskbarFlashing",
title: "Enable Taskbar Flashing",
description: "Flashes the app in your taskbar when you have new notifications.",
defaultValue: false
}
],
Miscellaneous: [
{
key: "arRPC",
@@ -149,13 +156,13 @@ function SettingsSections() {
const sections = Object.entries(SettingsOptions).map(([title, settings], i, arr) => (
<div key={title} className={cl("category")}>
<Text variant="heading-lg/semibold" color="header-primary" className={cl("category-title")}>
<BaseText size="lg" weight="semibold" tag="h3" className={cl("category-title")}>
{title}
</Text>
</BaseText>
<div className={cl("category-content")}>
{settings.map(Setting => {
if (typeof Setting === "function") return <Setting settings={Settings} />;
{settings.map((Setting, i) => {
if (typeof Setting === "function") return <Setting key={`Custom-${i}`} settings={Settings} />;
const { defaultValue, title, description, key, disabled, invisible } = Setting;
if (invisible?.()) return null;
@@ -173,7 +180,7 @@ function SettingsSections() {
})}
</div>
{i < arr.length - 1 && <Forms.FormDivider className={cl("category-divider")} />}
{i < arr.length - 1 && <Divider className={cl("category-divider")} />}
</div>
));
@@ -184,10 +191,7 @@ export default ErrorBoundary.wrap(
function SettingsUI() {
return (
<section>
<Text variant="heading-xl/semibold" color="header-primary" className={cl("title")}>
Vesktop Settings
</Text>
<Updater />
<OutdatedVesktopWarning />
<SettingsSections />
</section>
);

View File

@@ -1,31 +0,0 @@
/*
* Vesktop, a desktop app aiming to give you a snappier Discord Experience
* Copyright (c) 2025 Vendicated and Vesktop contributors
* SPDX-License-Identifier: GPL-3.0-or-later
*/
import { useAwaiter } from "@vencord/types/utils";
import { Button, Text } from "@vencord/types/webpack/common";
import { cl } from "./Settings";
export function Updater() {
const [isOutdated] = useAwaiter(VesktopNative.app.isOutdated);
if (!isOutdated) return null;
return (
<div className={cl("updater-card")}>
<Text variant="text-md/semibold">Your Vesktop is outdated!</Text>
<Text variant="text-sm/normal">Staying up to date is important for security and stability.</Text>
<Button
onClick={() => VesktopNative.app.openUpdater()}
size={Button.Sizes.SMALL}
color={Button.Colors.TRANSPARENT}
>
Open Updater
</Button>
</div>
);
}

View File

@@ -13,8 +13,14 @@
}
.vcd-user-assets-actions {
display: grid;
width: 100%;
gap: 0.5em;
margin-bottom: auto;
}
.vcd-user-assets-buttons {
display: flex;
flex-direction: column;
gap: 0.5em;
}

View File

@@ -6,7 +6,9 @@
import "./UserAssets.css";
import { BaseText, Button, FormSwitch } from "@vencord/types/components";
import {
Margins,
ModalCloseButton,
ModalContent,
ModalHeader,
@@ -16,8 +18,9 @@ import {
wordsFromCamel,
wordsToTitle
} from "@vencord/types/utils";
import { Button, showToast, Text, useState } from "@vencord/types/webpack/common";
import { showToast, useState } from "@vencord/types/webpack/common";
import { UserAssetType } from "main/userAssets";
import { useSettings } from "renderer/settings";
import { SettingsComponent } from "./Settings";
@@ -31,9 +34,9 @@ function openAssetsModal() {
openModal(props => (
<ModalRoot {...props} size={ModalSize.MEDIUM}>
<ModalHeader>
<Text variant="heading-lg/semibold" style={{ flexGrow: 1 }}>
<BaseText size="lg" weight="semibold" tag="h3" style={{ flexGrow: 1 }}>
User Assets
</Text>
</BaseText>
<ModalCloseButton onClick={props.onClose} />
</ModalHeader>
@@ -51,11 +54,18 @@ function openAssetsModal() {
function Asset({ asset }: { asset: UserAssetType }) {
// cache busting
const [version, setVersion] = useState(Date.now());
const settings = useSettings();
const isSplash = asset === "splash";
const imageRendering = isSplash && settings.splashPixelated ? "pixelated" : "auto";
const onChooseAsset = (value?: null) => async () => {
const res = await VesktopNative.fileManager.chooseUserAsset(asset, value);
if (res === "ok") {
setVersion(Date.now());
if (isSplash && value === null) {
settings.splashPixelated = false;
}
} else if (res === "failed") {
showToast("Something went wrong. Please try again");
}
@@ -63,16 +73,32 @@ function Asset({ asset }: { asset: UserAssetType }) {
return (
<section>
<Text tag="h3" variant="text-md/semibold">
<BaseText size="md" weight="medium" tag="h3">
{wordsToTitle(wordsFromCamel(asset))}
</Text>
</BaseText>
<div className="vcd-user-assets-asset">
<img className="vcd-user-assets-image" src={`vesktop://assets/${asset}?v=${version}`} alt="" />
<img
className="vcd-user-assets-image"
src={`vesktop://assets/${asset}?v=${version}`}
alt=""
style={{ imageRendering }}
/>
<div className="vcd-user-assets-actions">
<Button onClick={onChooseAsset()}>Customize</Button>
<Button color={Button.Colors.PRIMARY} onClick={onChooseAsset(null)}>
Reset to default
</Button>
<div className="vcd-user-assets-buttons">
<Button onClick={onChooseAsset()}>Customize</Button>
<Button variant="secondary" onClick={onChooseAsset(null)}>
Reset to default
</Button>
</div>
{isSplash && (
<FormSwitch
title="Nearest-Neighbor Scaling (for pixel art)"
value={settings.splashPixelated ?? false}
onChange={val => (settings.splashPixelated = val)}
className={Margins.top16}
hideBorder
/>
)}
</div>
</div>
</section>

View File

@@ -5,7 +5,7 @@
*/
import { FormSwitch } from "@vencord/types/components";
import { ComponentProps } from "react";
import type { ComponentProps } from "react";
import { cl } from "./Settings";

View File

@@ -4,23 +4,24 @@
* SPDX-License-Identifier: GPL-3.0-or-later
*/
import { ErrorBoundary } from "@vencord/types/components";
import { Heading, Paragraph } from "@vencord/types/components";
import { Margins } from "@vencord/types/utils";
import { Forms, Select } from "@vencord/types/webpack/common";
import { Select } from "@vencord/types/webpack/common";
import { SimpleErrorBoundary } from "../SimpleErrorBoundary";
import { SettingsComponent } from "./Settings";
export const WindowsTransparencyControls: SettingsComponent = ({ settings }) => {
if (!VesktopNative.app.supportsWindowsTransparency()) return null;
return (
<ErrorBoundary noop>
<div>
<Forms.FormTitle className={Margins.bottom8}>Transparency Options</Forms.FormTitle>
<Forms.FormText className={Margins.bottom8}>
Requires a full restart. You will need a theme that supports transparency for this to work.
</Forms.FormText>
<div>
<Heading tag="h5">Transparency Options</Heading>
<Paragraph className={Margins.bottom8}>
Requires a full restart. You will need a theme that supports transparency for this to work.
</Paragraph>
<SimpleErrorBoundary>
<Select
placeholder="None"
options={[
@@ -44,7 +45,7 @@ export const WindowsTransparencyControls: SettingsComponent = ({ settings }) =>
isSelected={v => v === settings.transparencyOption}
serialize={s => s}
/>
</div>
</ErrorBoundary>
</SimpleErrorBoundary>
</div>
);
};

View File

@@ -38,10 +38,4 @@
margin-bottom: 1em;
display: grid;
gap: 0.5em;
border-radius: 8px;
background-color: var(--bg-secondary);
background: var(--background-feedback-warning);
border: 1px solid var(--info-warning-foreground);
color: var(--text-feedback-warning);
}

View File

@@ -23,7 +23,8 @@ import type SettingsPlugin from "@vencord/types/plugins/_core/settings";
VesktopLogger.log("read if cute :3");
VesktopLogger.log("Vesktop v" + VesktopNative.app.getVersion());
const customSettingsSections = (Vencord.Plugins.plugins.Settings as any as typeof SettingsPlugin).customSections;
// TODO
const customSettingsSections = (Vencord.Plugins.plugins.Settings as typeof SettingsPlugin).customSections;
customSettingsSections.push(() => ({
section: "Vesktop",

View File

@@ -26,12 +26,10 @@ addPatch({
group: true,
replacement: [
{
// eslint-disable-next-line no-useless-escape
match: /if\(null!=(\i)\)(?=.{0,50}\1\.window\.setDevtoolsCallbacks)/,
replace: "if(true)"
},
{
// eslint-disable-next-line no-useless-escape
match: /\b\i\.window\.setDevtoolsCallbacks/g,
replace: "VesktopNative.win.setDevtoolsCallbacks"
}

View File

@@ -11,8 +11,6 @@ addPatch({
{
find: '"NotificationSettingsStore',
replacement: {
// FIXME: fix eslint rule
// eslint-disable-next-line no-useless-escape
match: /\.isPlatformEmbedded(?=\?\i\.\i\.ALL)/g,
replace: "$&||true"
}

View File

@@ -11,7 +11,6 @@ addPatch({
{
find: "lastOutputSystemDevice.justChanged",
replacement: {
// eslint-disable-next-line no-useless-escape
match: /(\i)\.\i\.getState\(\).neverShowModal/,
replace: "$& || $self.shouldIgnoreDevice($1)"
}

View File

@@ -11,7 +11,6 @@ addPatch({
{
find: 'setSinkId"in',
replacement: {
// eslint-disable-next-line no-useless-escape
match: /return (\i)\?navigator\.mediaDevices\.enumerateDevices/,
replace: "return $1 ? $self.filteredDevices"
}

View File

@@ -14,7 +14,6 @@ addPatch({
{
find: "platform-web",
replacement: {
// eslint-disable-next-line no-useless-escape
match: /(?<=" platform-overlay"\):)\i/,
replace: "$self.getPlatformClass()"
}

View File

@@ -23,7 +23,7 @@ addPatch({
find: ".enableSpellCheck)",
replacement: {
// if (isDesktop) { DiscordNative.onSpellcheck(openMenu(props)) } else { e.preventDefault(); openMenu(props) }
match: /else (.{1,3})\.preventDefault\(\),(.{1,3}\(.{1,3}\))(?<=:(.{1,3})\.enableSpellCheck\).+?)/,
match: /else (\i)\.preventDefault\(\),(\i\(\i\))(?<=:(\i)\.enableSpellCheck\).+?)/,
// ... else { $self.onSlateContext(() => openMenu(props)) }
replace: "else {$self.onSlateContext($1, $3?.enableSpellCheck, () => $2)}"
}
@@ -66,6 +66,7 @@ addContextMenuPatch("textarea-context", children => {
<>
{corrections.map(c => (
<Menu.MenuItem
key={c}
id={"vcd-spellcheck-suggestion-" + c}
label={c}
action={() => VesktopNative.spellcheck.replaceMisspelling(c)}
@@ -95,6 +96,7 @@ addContextMenuPatch("textarea-context", children => {
const isEnabled = spellCheckLanguages.includes(lang);
return (
<Menu.MenuCheckboxItem
key={lang}
id={"vcd-spellcheck-lang-" + lang}
label={lang}
checked={isEnabled}

View File

@@ -12,7 +12,6 @@ addPatch({
find: ".STREAMER_MODE_ENABLE,",
replacement: {
// remove if (platformEmbedded) check from streamer mode toggle
// eslint-disable-next-line no-useless-escape
match: /if\(\i\.\i\)(?=return.{0,200}?"autoToggle")/g,
replace: ""
}

View File

@@ -0,0 +1,27 @@
/*
* Vesktop, a desktop app aiming to give you a snappier Discord Experience
* Copyright (c) 2025 Vendicated and Vesktop contributors
* SPDX-License-Identifier: GPL-3.0-or-later
*/
import { Settings } from "renderer/settings";
import { addPatch } from "./shared";
addPatch({
patches: [
{
find: ".flashFrame(!0)",
replacement: {
match: /(\i)&&\i\.\i\.taskbarFlash&&\i\.\i\.flashFrame\(!0\)/,
replace: "$self.flashFrame()"
}
}
],
flashFrame() {
if (Settings.store.enableTaskbarFlashing) {
VesktopNative.win.flashFrame(true);
}
}
});

View File

@@ -12,13 +12,10 @@ addPatch({
find: ",setSystemTrayApplications",
replacement: [
{
// eslint-disable-next-line no-useless-escape
match: /\i\.window\.(close|minimize|maximize)/g,
replace: `VesktopNative.win.$1`
},
{
// TODO: Fix eslint rule
// eslint-disable-next-line no-useless-escape
match: /(focus(\(\i\)){).{0,150}?\.focus\(\i,\i\)/,
replace: "$1VesktopNative.win.focus$2"
},

View File

@@ -15,8 +15,6 @@ if (Settings.store.customTitleBar)
find: ".wordmarkWindows",
replacement: [
{
// TODO: Fix eslint rule
// eslint-disable-next-line no-useless-escape
match: /case \i\.\i\.WINDOWS:/,
replace: 'case "WEB":'
}
@@ -27,14 +25,10 @@ if (Settings.store.customTitleBar)
find: ".systemBar,",
replacement: [
{
// TODO: Fix eslint rule
// eslint-disable-next-line no-useless-escape
match: /\i===\i\.PlatformTypes\.WINDOWS/g,
replace: "true"
},
{
// TODO: Fix eslint rule
// eslint-disable-next-line no-useless-escape
match: /\i===\i\.PlatformTypes\.WEB/g,
replace: "false"
}

View File

@@ -60,7 +60,7 @@ const updateSplashColors = () => {
const bodyStyles = document.body.computedStyleMap();
const color = bodyStyles.get("--text-default");
const backgroundColor = bodyStyles.get("--background-primary");
const backgroundColor = bodyStyles.get("--background-base-lowest");
if (isValidColor(color)) {
Settings.store.splashColor = resolveColor(color[0]);

View File

@@ -36,6 +36,7 @@ export const enum IpcEvents {
SPELLCHECK_ADD_TO_DICTIONARY = "VCD_SPELLCHECK_ADD_TO_DICTIONARY",
SET_BADGE_COUNT = "VCD_SET_BADGE_COUNT",
FLASH_FRAME = "FLASH_FRAME",
CAPTURER_GET_LARGE_THUMBNAIL = "VCD_CAPTURER_GET_LARGE_THUMBNAIL",

View File

@@ -20,6 +20,7 @@ export interface Settings {
hardwareVideoAcceleration?: boolean;
arRPC?: boolean;
appBadge?: boolean;
enableTaskbarFlashing?: boolean;
disableMinSize?: boolean;
clickTrayToShowHide?: boolean;
customTitleBar?: boolean;
@@ -28,6 +29,7 @@ export interface Settings {
splashTheming?: boolean;
splashColor?: string;
splashBackground?: string;
splashPixelated?: boolean;
spellCheckLanguages?: string[];
@@ -50,7 +52,6 @@ export interface State {
maximized?: boolean;
minimized?: boolean;
windowBounds?: Rectangle;
displayId: number;
firstLaunch?: boolean;

Binary file not shown.

Before

Width:  |  Height:  |  Size: 117 KiB

After

Width:  |  Height:  |  Size: 145 KiB

View File

@@ -1,3 +1,6 @@
<!doctype html>
<meta charset="utf-8" />
<head>
<title>About Vesktop</title>
@@ -63,7 +66,7 @@
</li>
<li>
<a href="https://github.com/electron-userland/electron-builder" target="_blank">Electron Builder</a>
- A complete solution to package and build a ready for distribution Electron app with auto update
- A complete solution to package and build a ready for distribution Electron app with "auto update"
support out of the box
</li>
<li>

View File

@@ -9,11 +9,15 @@
--link-hover: light-dark(#005bb5, #0086c3);
}
html,
body {
margin: 0;
padding: 0;
}
body {
font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, Ubuntu, Cantarell,
"Open Sans", "Helvetica Neue", sans-serif;
margin: 0;
padding: 0;
background: var(--bg);
color: var(--fg);
}

View File

@@ -1,3 +1,6 @@
<!doctype html>
<meta charset="utf-8" />
<head>
<title>Vesktop Setup</title>

View File

@@ -1,7 +1,16 @@
<!doctype html>
<meta charset="utf-8" />
<head>
<link rel="stylesheet" href="./common.css" type="text/css" />
<style>
html,
body {
width: 100%;
height: 100%;
}
body {
background: none;
user-select: none;
@@ -10,8 +19,9 @@
}
.wrapper {
box-sizing: border-box;
width: 100%;
height: 100%;
box-sizing: border-box;
display: flex;
flex-direction: column;
justify-content: center;

View File

@@ -1,3 +1,6 @@
<!doctype html>
<meta charset="utf-8" />
<head>
<title>Vesktop Updater</title>
<meta http-equiv="Content-Security-Policy" content="