add everything

This commit is contained in:
2025-07-19 13:28:38 +02:00
34 changed files with 967 additions and 587 deletions

View File

@@ -5,8 +5,13 @@ body:
- type: markdown - type: markdown
attributes: attributes:
value: | value: |
![Are you a developer? No? This form is not for you!](https://github.com/Vencord/Vesktop/blob/main/.github/ISSUE_TEMPLATE/developer-banner.png?raw=true) # This form is reserved for Vesktop Developers. Do not open an issue.
GitHub Issues are for developers, not support. Please use our [support server](https://vencord.dev/discord) if you are not a developer.
Instead, use the [#vesktop-support channel](https://discord.com/channels/1015060230222131221/1345457031426871417) on our [Discord server](https://vencord.dev/discord) for help and reporting issues.
Your issue will be closed immediately with no comment and you will be blocked if you ignore this.
This is because 99% of issues are not actually bugs, but rather user or system issues and it adds a lot of noise to our development process.
- type: textarea - type: textarea
id: content id: content
attributes: attributes:

Binary file not shown.

Before

Width:  |  Height:  |  Size: 31 KiB

View File

@@ -37,6 +37,6 @@ jobs:
git add meta/dev.vencord.Vesktop.metainfo.xml git add meta/dev.vencord.Vesktop.metainfo.xml
git commit -m "metainfo: add entry for ${{ github.event.release.tag_name }}" git commit -m "metainfo: add entry for ${{ github.event.release.tag_name }}"
git push git push origin HEAD:main
env: env:
GITHUB_TOKEN: ${{ secrets.PERSONAL_TOKEN }} GITHUB_TOKEN: ${{ secrets.PERSONAL_TOKEN }}

View File

@@ -1,6 +1,6 @@
{ {
"name": "not-nextop", "name": "not-nextop",
"version": "1.5.6", "version": "1.5.8",
"private": true, "private": true,
"description": "Not-Nextop is a custom Discord desktop app", "description": "Not-Nextop is a custom Discord desktop app",
"keywords": [], "keywords": [],
@@ -34,27 +34,27 @@
}, },
"devDependencies": { "devDependencies": {
"@fal-works/esbuild-plugin-global-externals": "^2.1.2", "@fal-works/esbuild-plugin-global-externals": "^2.1.2",
"@stylistic/eslint-plugin": "^4.2.0", "@stylistic/eslint-plugin": "^5.1.0",
"@types/node": "^22.15.18", "@types/node": "^24.0.10",
"@types/react": "18.3.1", "@types/react": "18.3.1",
"@vencord/types": "^1.11.5", "@vencord/types": "^1.11.5",
"dotenv": "^16.5.0", "dotenv": "^16.5.0",
"electron": "^36.2.1", "electron": "^37.2.0",
"electron-builder": "^26.0.12", "electron-builder": "^26.0.12",
"esbuild": "^0.25.4", "esbuild": "^0.25.5",
"eslint": "^9.27.0", "eslint": "^9.30.1",
"eslint-import-resolver-alias": "^1.1.2", "eslint-import-resolver-alias": "^1.1.2",
"eslint-plugin-path-alias": "^2.1.0", "eslint-plugin-path-alias": "^2.1.0",
"eslint-plugin-prettier": "^5.4.0", "eslint-plugin-prettier": "^5.5.1",
"eslint-plugin-simple-header": "^1.2.2", "eslint-plugin-simple-header": "^1.2.2",
"eslint-plugin-simple-import-sort": "^12.1.1", "eslint-plugin-simple-import-sort": "^12.1.1",
"eslint-plugin-unused-imports": "^4.1.4", "eslint-plugin-unused-imports": "^4.1.4",
"prettier": "^3.5.3", "prettier": "^3.6.2",
"source-map-support": "^0.5.21", "source-map-support": "^0.5.21",
"tsx": "^4.19.4", "tsx": "^4.20.3",
"type-fest": "^4.41.0", "type-fest": "^4.41.0",
"typescript": "^5.8.3", "typescript": "^5.8.3",
"typescript-eslint": "^8.32.1", "typescript-eslint": "^8.35.1",
"xml-formatter": "^3.6.6" "xml-formatter": "^3.6.6"
}, },
"packageManager": "pnpm@10.7.1", "packageManager": "pnpm@10.7.1",
@@ -199,7 +199,8 @@
}, },
"pnpm": { "pnpm": {
"patchedDependencies": { "patchedDependencies": {
"arrpc@3.5.0": "patches/arrpc@3.5.0.patch" "arrpc@3.5.0": "patches/arrpc@3.5.0.patch",
"electron-updater": "patches/electron-updater.patch"
}, },
"onlyBuiltDependencies": [ "onlyBuiltDependencies": [
"@vencord/venmic", "@vencord/venmic",

View File

@@ -0,0 +1,16 @@
diff --git a/out/RpmUpdater.js b/out/RpmUpdater.js
index 563187bb18cb0bd154dff6620cb62b8c8f534cd6..d91594026c2bac9cc78ef3b1183df3241d7d9624 100644
--- a/out/RpmUpdater.js
+++ b/out/RpmUpdater.js
@@ -32,7 +32,10 @@ class RpmUpdater extends BaseUpdater_1.BaseUpdater {
const sudo = this.wrapSudo();
// pkexec doesn't want the command to be wrapped in " quotes
const wrapper = /pkexec/i.test(sudo) ? "" : `"`;
- const packageManager = this.spawnSyncLog("which zypper");
+ let packageManager;
+ try {
+ packageManager = this.spawnSyncLog("which zypper");
+ } catch {}
const installerPath = this.installerPath;
if (installerPath == null) {
this.dispatchError(new Error("No valid update available, can't quit and install"));

902
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@@ -58,6 +58,12 @@ await Promise.all([
outfile: "dist/js/main.js", outfile: "dist/js/main.js",
footer: { js: "//# sourceURL=VCDMain" } footer: { js: "//# sourceURL=VCDMain" }
}), }),
createContext({
...NodeCommonOpts,
entryPoints: ["src/main/arrpc/worker.ts"],
outfile: "dist/js/arRpcWorker.js",
footer: { js: "//# sourceURL=VCDArRpcWorker" }
}),
createContext({ createContext({
...NodeCommonOpts, ...NodeCommonOpts,
entryPoints: ["src/preload/index.ts"], entryPoints: ["src/preload/index.ts"],

View File

@@ -4,25 +4,33 @@
* SPDX-License-Identifier: GPL-3.0-or-later * SPDX-License-Identifier: GPL-3.0-or-later
*/ */
import { BrowserWindow } from "electron"; import { app, BrowserWindow } from "electron";
import { join } from "path"; import { join } from "path";
import { ICON_PATH, VIEW_DIR } from "shared/paths"; import { ICON_PATH, VIEW_DIR } from "shared/paths";
import { makeLinksOpenExternally } from "./utils/makeLinksOpenExternally"; import { makeLinksOpenExternally } from "./utils/makeLinksOpenExternally";
export function createAboutWindow() { export async function createAboutWindow() {
const height = 750;
const width = height * (4 / 3);
const about = new BrowserWindow({ const about = new BrowserWindow({
center: true, center: true,
autoHideMenuBar: true, autoHideMenuBar: true,
icon: ICON_PATH, icon: ICON_PATH,
webPreferences: { height,
preload: join(__dirname, "updaterPreload.js") width
}
}); });
makeLinksOpenExternally(about); makeLinksOpenExternally(about);
about.loadFile(join(VIEW_DIR, "about.html")); const data = new URLSearchParams({
APP_VERSION: app.getVersion()
});
about.loadFile(join(VIEW_DIR, "about.html"), {
search: data.toString()
});
return about; return about;
} }

View File

@@ -1,37 +0,0 @@
/*
* Vesktop, a desktop app aiming to give you a snappier Discord Experience
* Copyright (c) 2023 Vendicated and Vencord contributors
* SPDX-License-Identifier: GPL-3.0-or-later
*/
import Server from "arrpc";
import { IpcCommands } from "shared/IpcEvents";
import { sendRendererCommand } from "./ipcCommands";
import { Settings } from "./settings";
let server: any;
const inviteCodeRegex = /^(\w|-)+$/;
export async function initArRPC() {
if (server || !Settings.store.arRPC) return;
try {
server = await new Server();
server.on("activity", (data: any) => sendRendererCommand(IpcCommands.RPC_ACTIVITY, JSON.stringify(data)));
server.on("invite", async (invite: string, callback: (valid: boolean) => void) => {
invite = String(invite);
if (!inviteCodeRegex.test(invite)) return callback(false);
await sendRendererCommand(IpcCommands.RPC_INVITE, invite).then(callback);
});
server.on("link", async (data: any, deepCallback: (valid: boolean) => void) => {
await sendRendererCommand(IpcCommands.RPC_DEEP_LINK, data).then(deepCallback);
});
} catch (e) {
console.error("Failed to start arRPC server", e);
}
}
Settings.addChangeListener("arRPC", initArRPC);

77
src/main/arrpc/index.ts Normal file
View File

@@ -0,0 +1,77 @@
/*
* Vesktop, a desktop app aiming to give you a snappier Discord Experience
* Copyright (c) 2023 Vendicated and Vencord contributors
* SPDX-License-Identifier: GPL-3.0-or-later
*/
import { resolve } from "path";
import { IpcCommands } from "shared/IpcEvents";
import { MessageChannel, Worker } from "worker_threads";
import { sendRendererCommand } from "../ipcCommands";
import { Settings } from "../settings";
import { ArRpcEvent, ArRpcHostEvent } from "./types";
let worker: Worker;
const inviteCodeRegex = /^(\w|-)+$/;
export async function initArRPC() {
if (worker || !Settings.store.arRPC) return;
try {
const { port1: hostPort, port2: workerPort } = new MessageChannel();
worker = new Worker(resolve(__dirname, "./arRpcWorker.js"), {
workerData: {
workerPort
},
transferList: [workerPort]
});
hostPort.on("message", async ({ type, nonce, data }: ArRpcEvent) => {
switch (type) {
case "activity": {
sendRendererCommand(IpcCommands.RPC_ACTIVITY, data);
break;
}
case "invite": {
const invite = String(data);
const response: ArRpcHostEvent = {
type: "ack-invite",
nonce,
data: false
};
if (!inviteCodeRegex.test(invite)) {
return hostPort.postMessage(response);
}
response.data = await sendRendererCommand(IpcCommands.RPC_INVITE, invite).catch(() => false);
hostPort.postMessage(response);
break;
}
case "link": {
const response: ArRpcHostEvent = {
type: "ack-link",
nonce: nonce,
data: false
};
response.data = await sendRendererCommand(IpcCommands.RPC_DEEP_LINK, data).catch(() => false);
hostPort.postMessage(response);
break;
}
}
});
} catch (e) {
console.error("Failed to start arRPC server", e);
}
}
Settings.addChangeListener("arRPC", initArRPC);

38
src/main/arrpc/types.ts Normal file
View File

@@ -0,0 +1,38 @@
/*
* 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
*/
export type ArRpcEvent = ArRpcActivityEvent | ArRpcInviteEvent | ArRpcLinkEvent;
export type ArRpcHostEvent = ArRpcHostAckInviteEvent | ArRpcHostAckLinkEvent;
export interface ArRpcActivityEvent {
type: "activity";
nonce: string;
data: string;
}
export interface ArRpcInviteEvent {
type: "invite";
nonce: string;
data: string;
}
export interface ArRpcLinkEvent {
type: "link";
nonce: string;
data: any;
}
export interface ArRpcHostAckInviteEvent {
type: "ack-invite";
nonce: string;
data: boolean;
}
export interface ArRpcHostAckLinkEvent {
type: "ack-link";
nonce: string;
data: boolean;
}

73
src/main/arrpc/worker.ts Normal file
View File

@@ -0,0 +1,73 @@
/*
* 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 Server from "arrpc";
import { randomUUID } from "crypto";
import { MessagePort, workerData } from "worker_threads";
import { ArRpcEvent, ArRpcHostEvent } from "./types";
let server: any;
type InviteCallback = (valid: boolean) => void;
type LinkCallback = InviteCallback;
const inviteCallbacks = new Map<string, InviteCallback>();
const linkCallbacks = new Map<string, LinkCallback>();
(async function () {
const { workerPort } = workerData as { workerPort: MessagePort };
server = await new Server();
server.on("activity", (data: any) => {
const event: ArRpcEvent = {
type: "activity",
data: JSON.stringify(data),
nonce: randomUUID()
};
workerPort.postMessage(event);
});
server.on("invite", (invite: string, callback: InviteCallback) => {
const nonce = randomUUID();
inviteCallbacks.set(nonce, callback);
const event: ArRpcEvent = {
type: "invite",
data: invite,
nonce
};
workerPort.postMessage(event);
});
server.on("link", async (data: any, callback: LinkCallback) => {
const nonce = randomUUID();
linkCallbacks.set(nonce, callback);
const event: ArRpcEvent = {
type: "link",
data,
nonce
};
workerPort.postMessage(event);
});
workerPort.on("message", (e: ArRpcHostEvent) => {
switch (e.type) {
case "ack-invite": {
inviteCallbacks.get(e.nonce)?.(e.data);
inviteCallbacks.delete(e.nonce);
break;
}
case "ack-link": {
linkCallbacks.get(e.nonce)?.(e.data);
linkCallbacks.delete(e.nonce);
break;
}
}
});
})();

View File

@@ -28,23 +28,30 @@ process.env.VENCORD_USER_DATA_DIR = DATA_DIR;
const isLinux = process.platform === "linux"; const isLinux = process.platform === "linux";
export let enableHardwareAcceleration = true;
function init() { function init() {
app.setAsDefaultProtocolClient("discord"); app.setAsDefaultProtocolClient("discord");
const { disableSmoothScroll, hardwareAcceleration } = Settings.store; const { disableSmoothScroll, hardwareAcceleration, hardwareVideoAcceleration } = Settings.store;
const enabledFeatures = new Set(app.commandLine.getSwitchValue("enable-features").split(",")); const enabledFeatures = new Set(app.commandLine.getSwitchValue("enable-features").split(","));
const disabledFeatures = new Set(app.commandLine.getSwitchValue("disable-features").split(",")); const disabledFeatures = new Set(app.commandLine.getSwitchValue("disable-features").split(","));
app.commandLine.removeSwitch("enable-features");
app.commandLine.removeSwitch("disable-features");
if (hardwareAcceleration === false) { if (hardwareAcceleration === false || process.argv.includes("--disable-gpu")) {
enableHardwareAcceleration = false;
app.disableHardwareAcceleration(); app.disableHardwareAcceleration();
} else { } else {
enabledFeatures.add("AcceleratedVideoEncoder"); if (hardwareVideoAcceleration) {
enabledFeatures.add("AcceleratedVideoDecoder"); enabledFeatures.add("AcceleratedVideoEncoder");
enabledFeatures.add("AcceleratedVideoDecoder");
if (isLinux) { if (isLinux) {
enabledFeatures.add("AcceleratedVideoDecodeLinuxGL"); enabledFeatures.add("AcceleratedVideoDecodeLinuxGL");
enabledFeatures.add("AcceleratedVideoDecodeLinuxZeroCopyGL"); enabledFeatures.add("AcceleratedVideoDecodeLinuxZeroCopyGL");
}
} }
} }
@@ -74,17 +81,22 @@ function init() {
if (isLinux) { if (isLinux) {
// Support TTS on Linux using https://wiki.archlinux.org/title/Speech_dispatcher // Support TTS on Linux using https://wiki.archlinux.org/title/Speech_dispatcher
app.commandLine.appendSwitch("enable-speech-dispatcher"); app.commandLine.appendSwitch("enable-speech-dispatcher");
// Work around Gtk-ERROR: GTK 2/3 symbols detected. Using GTK 2/3 and GTK 4 in the same process is not supported
// https://github.com/electron/electron/issues/46538
// TODO: Remove this when upstream fixes it
app.commandLine.appendSwitch("gtk-version", "3");
} }
disabledFeatures.forEach(feat => enabledFeatures.delete(feat)); disabledFeatures.forEach(feat => enabledFeatures.delete(feat));
app.commandLine.appendSwitch("enable-features", [...enabledFeatures].filter(Boolean).join(",")); const enabledFeaturesArray = enabledFeatures.values().filter(Boolean).toArray();
app.commandLine.appendSwitch("disable-features", [...disabledFeatures].filter(Boolean).join(",")); const disabledFeaturesArray = disabledFeatures.values().filter(Boolean).toArray();
if (enabledFeaturesArray.length) {
app.commandLine.appendSwitch("enable-features", enabledFeaturesArray.join(","));
console.log("Enabled Chromium features:", enabledFeaturesArray.join(", "));
}
if (disabledFeaturesArray.length) {
app.commandLine.appendSwitch("disable-features", disabledFeaturesArray.join(","));
console.log("Disabled Chromium features:", disabledFeaturesArray.join(", "));
}
// In the Flatpak on SteamOS the theme is detected as light, but SteamOS only has a dark mode, so we just override it // In the Flatpak on SteamOS the theme is detected as light, but SteamOS only has a dark mode, so we just override it
if (isDeckGameMode) nativeTheme.themeSource = "dark"; if (isDeckGameMode) nativeTheme.themeSource = "dark";

View File

@@ -20,6 +20,7 @@ import {
} from "electron"; } from "electron";
import { mkdirSync, readFileSync, watch } from "fs"; import { mkdirSync, readFileSync, watch } from "fs";
import { open, readFile } from "fs/promises"; import { open, readFile } from "fs/promises";
import { enableHardwareAcceleration } from "main";
import { release } from "os"; import { release } from "os";
import { join } from "path"; import { join } from "path";
import { debounce } from "shared/utils/debounce"; import { debounce } from "shared/utils/debounce";
@@ -45,6 +46,7 @@ handleSync(IpcEvents.GET_RENDERER_CSS_FILE, () => join(__dirname, "renderer.css"
handleSync(IpcEvents.GET_SETTINGS, () => Settings.plain); handleSync(IpcEvents.GET_SETTINGS, () => Settings.plain);
handleSync(IpcEvents.GET_VERSION, () => app.getVersion()); handleSync(IpcEvents.GET_VERSION, () => app.getVersion());
handleSync(IpcEvents.GET_ENABLE_HARDWARE_ACCELERATION, () => enableHardwareAcceleration);
handleSync( handleSync(
IpcEvents.SUPPORTS_WINDOWS_TRANSPARENCY, IpcEvents.SUPPORTS_WINDOWS_TRANSPARENCY,

View File

@@ -31,6 +31,11 @@ export interface IpcResponse {
* You must add a handler for the message in the renderer process. * You must add a handler for the message in the renderer process.
*/ */
export function sendRendererCommand<T = any>(message: string, data?: any) { export function sendRendererCommand<T = any>(message: string, data?: any) {
if (mainWin.isDestroyed()) {
console.warn("Main window is destroyed, cannot send IPC command:", message);
return Promise.reject(new Error("Main window is destroyed"));
}
const nonce = randomUUID(); const nonce = randomUUID();
const promise = new Promise<T>((resolve, reject) => { const promise = new Promise<T>((resolve, reject) => {

View File

@@ -388,6 +388,15 @@ function initSpellCheck(win: BrowserWindow) {
initSpellCheckLanguages(win, Settings.store.spellCheckLanguages); initSpellCheckLanguages(win, Settings.store.spellCheckLanguages);
} }
function initDevtoolsListeners(win: BrowserWindow) {
win.webContents.on("devtools-opened", () => {
win.webContents.send(IpcEvents.DEVTOOLS_OPENED);
});
win.webContents.on("devtools-closed", () => {
win.webContents.send(IpcEvents.DEVTOOLS_CLOSED);
});
}
function initStaticTitle(win: BrowserWindow) { function initStaticTitle(win: BrowserWindow) {
const listener = (e: { preventDefault: Function }) => e.preventDefault(); const listener = (e: { preventDefault: Function }) => e.preventDefault();
@@ -473,6 +482,7 @@ function createMainWindow() {
makeLinksOpenExternally(win); makeLinksOpenExternally(win);
initSettingsListeners(win); initSettingsListeners(win);
initSpellCheck(win); initSpellCheck(win);
initDevtoolsListeners(win);
initStaticTitle(win); initStaticTitle(win);
win.webContents.setUserAgent(BrowserUserAgent); win.webContents.setUserAgent(BrowserUserAgent);

View File

@@ -20,12 +20,19 @@ ipcRenderer.on(IpcEvents.SPELLCHECK_RESULT, (_, w: string, s: string[]) => {
spellCheckCallbacks.forEach(cb => cb(w, s)); spellCheckCallbacks.forEach(cb => cb(w, s));
}); });
let onDevtoolsOpen = () => {};
let onDevtoolsClose = () => {};
ipcRenderer.on(IpcEvents.DEVTOOLS_OPENED, () => onDevtoolsOpen());
ipcRenderer.on(IpcEvents.DEVTOOLS_CLOSED, () => onDevtoolsClose());
export const VesktopNative = { export const VesktopNative = {
app: { app: {
relaunch: () => invoke<void>(IpcEvents.RELAUNCH), relaunch: () => invoke<void>(IpcEvents.RELAUNCH),
getVersion: () => sendSync<void>(IpcEvents.GET_VERSION), getVersion: () => sendSync<void>(IpcEvents.GET_VERSION),
setBadgeCount: (count: number) => invoke<void>(IpcEvents.SET_BADGE_COUNT, count), setBadgeCount: (count: number) => invoke<void>(IpcEvents.SET_BADGE_COUNT, count),
supportsWindowsTransparency: () => sendSync<boolean>(IpcEvents.SUPPORTS_WINDOWS_TRANSPARENCY) supportsWindowsTransparency: () => sendSync<boolean>(IpcEvents.SUPPORTS_WINDOWS_TRANSPARENCY),
getEnableHardwareAcceleration: () => sendSync<boolean>(IpcEvents.GET_ENABLE_HARDWARE_ACCELERATION)
}, },
autostart: { autostart: {
isEnabled: () => sendSync<boolean>(IpcEvents.AUTOSTART_ENABLED), isEnabled: () => sendSync<boolean>(IpcEvents.AUTOSTART_ENABLED),
@@ -56,7 +63,11 @@ export const VesktopNative = {
focus: () => invoke<void>(IpcEvents.FOCUS), focus: () => invoke<void>(IpcEvents.FOCUS),
close: (key?: string) => invoke<void>(IpcEvents.CLOSE, key), close: (key?: string) => invoke<void>(IpcEvents.CLOSE, key),
minimize: (key?: string) => invoke<void>(IpcEvents.MINIMIZE, key), minimize: (key?: string) => invoke<void>(IpcEvents.MINIMIZE, key),
maximize: (key?: string) => invoke<void>(IpcEvents.MAXIMIZE, key) maximize: (key?: string) => invoke<void>(IpcEvents.MAXIMIZE, key),
setDevtoolsCallbacks: (onOpen: () => void, onClose: () => void) => {
onDevtoolsOpen = onOpen;
onDevtoolsClose = onClose;
}
}, },
capturer: { capturer: {
getLargeThumbnail: (id: string) => invoke<string>(IpcEvents.CAPTURER_GET_LARGE_THUMBNAIL, id) getLargeThumbnail: (id: string) => invoke<string>(IpcEvents.CAPTURER_GET_LARGE_THUMBNAIL, id)

View File

@@ -4,15 +4,16 @@
* SPDX-License-Identifier: GPL-3.0-or-later * SPDX-License-Identifier: GPL-3.0-or-later
*/ */
import { Switch, useState } from "@vencord/types/webpack/common"; import { useState } from "@vencord/types/webpack/common";
import { SettingsComponent } from "./Settings"; import { SettingsComponent } from "./Settings";
import { VesktopSettingsSwitch } from "./VesktopSettingsSwitch";
export const AutoStartToggle: SettingsComponent = () => { export const AutoStartToggle: SettingsComponent = () => {
const [autoStartEnabled, setAutoStartEnabled] = useState(VesktopNative.autostart.isEnabled()); const [autoStartEnabled, setAutoStartEnabled] = useState(VesktopNative.autostart.isEnabled());
return ( return (
<Switch <VesktopSettingsSwitch
value={autoStartEnabled} value={autoStartEnabled}
onChange={async v => { onChange={async v => {
await VesktopNative.autostart[v ? "enable" : "disable"](); await VesktopNative.autostart[v ? "enable" : "disable"]();
@@ -21,6 +22,6 @@ export const AutoStartToggle: SettingsComponent = () => {
note="Automatically start Not-Nextop on computer start-up" note="Automatically start Not-Nextop on computer start-up"
> >
Start With System Start With System
</Switch> </VesktopSettingsSwitch>
); );
}; };

View File

@@ -4,14 +4,14 @@
* SPDX-License-Identifier: GPL-3.0-or-later * SPDX-License-Identifier: GPL-3.0-or-later
*/ */
import { Switch } from "@vencord/types/webpack/common";
import { setBadge } from "renderer/appBadge"; import { setBadge } from "renderer/appBadge";
import { SettingsComponent } from "./Settings"; import { SettingsComponent } from "./Settings";
import { VesktopSettingsSwitch } from "./VesktopSettingsSwitch";
export const NotificationBadgeToggle: SettingsComponent = ({ settings }) => { export const NotificationBadgeToggle: SettingsComponent = ({ settings }) => {
return ( return (
<Switch <VesktopSettingsSwitch
value={settings.appBadge ?? true} value={settings.appBadge ?? true}
onChange={v => { onChange={v => {
settings.appBadge = v; settings.appBadge = v;
@@ -21,6 +21,6 @@ export const NotificationBadgeToggle: SettingsComponent = ({ settings }) => {
note="Show mention badge on the app icon" note="Show mention badge on the app icon"
> >
Notification Badge Notification Badge
</Switch> </VesktopSettingsSwitch>
); );
}; };

View File

@@ -7,7 +7,7 @@
import "./settings.css"; import "./settings.css";
import { ErrorBoundary } from "@vencord/types/components"; import { ErrorBoundary } from "@vencord/types/components";
import { Forms, Switch, Text } from "@vencord/types/webpack/common"; import { Forms, Text } from "@vencord/types/webpack/common";
import { ComponentType } from "react"; import { ComponentType } from "react";
import { Settings, useSettings } from "renderer/settings"; import { Settings, useSettings } from "renderer/settings";
import { isMac, isWindows } from "renderer/utils"; import { isMac, isWindows } from "renderer/utils";
@@ -16,6 +16,7 @@ import { AutoStartToggle } from "./AutoStartToggle";
import { DeveloperOptionsButton } from "./DeveloperOptions"; import { DeveloperOptionsButton } from "./DeveloperOptions";
import { DiscordBranchPicker } from "./DiscordBranchPicker"; import { DiscordBranchPicker } from "./DiscordBranchPicker";
import { NotificationBadgeToggle } from "./NotificationBadgeToggle"; import { NotificationBadgeToggle } from "./NotificationBadgeToggle";
import { VesktopSettingsSwitch } from "./VesktopSettingsSwitch";
import { WindowsTransparencyControls } from "./WindowsTransparencyControls"; import { WindowsTransparencyControls } from "./WindowsTransparencyControls";
interface BooleanSetting { interface BooleanSetting {
@@ -38,6 +39,14 @@ const SettingsOptions: Record<string, Array<BooleanSetting | SettingsComponent>>
title: "Hardware Acceleration", title: "Hardware Acceleration",
description: "Enable hardware acceleration", description: "Enable hardware acceleration",
defaultValue: true defaultValue: true
},
{
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
} }
], ],
"User Interface": [ "User Interface": [
@@ -132,32 +141,35 @@ const SettingsOptions: Record<string, Array<BooleanSetting | SettingsComponent>>
function SettingsSections() { function SettingsSections() {
const Settings = useSettings(); const Settings = useSettings();
const sections = Object.entries(SettingsOptions).map(([title, settings]) => ( const sections = Object.entries(SettingsOptions).map(([title, settings], i, arr) => (
<Forms.FormSection <div key={title} className="vcd-settings-category">
title={title} <Text variant="heading-lg/semibold" color="header-primary" className="vcd-settings-category-title">
key={title} {title}
className="vcd-settings-section" </Text>
titleClassName="vcd-settings-title"
>
{settings.map(Setting => {
if (typeof Setting === "function") return <Setting settings={Settings} />;
const { defaultValue, title, description, key, disabled, invisible } = Setting; <div className="vcd-settings-category-content">
if (invisible?.()) return null; {settings.map(Setting => {
if (typeof Setting === "function") return <Setting settings={Settings} />;
return ( const { defaultValue, title, description, key, disabled, invisible } = Setting;
<Switch if (invisible?.()) return null;
value={Settings[key as any] ?? defaultValue}
onChange={v => (Settings[key as any] = v)} return (
note={description} <VesktopSettingsSwitch
disabled={disabled?.()} value={Settings[key as any] ?? defaultValue}
key={key} onChange={v => (Settings[key as any] = v)}
> note={description}
{title} disabled={disabled?.()}
</Switch> key={key}
); >
})} {title}
</Forms.FormSection> </VesktopSettingsSwitch>
);
})}
</div>
{i < arr.length - 1 && <Forms.FormDivider className="vcd-settings-category-divider" />}
</div>
)); ));
return <>{sections}</>; return <>{sections}</>;
@@ -167,10 +179,16 @@ export default ErrorBoundary.wrap(
function SettingsUI() { function SettingsUI() {
return ( return (
<Forms.FormSection> <Forms.FormSection>
<<<<<<< HEAD
<Text variant="heading-lg/semibold" style={{ color: "var(--header-primary)" }} tag="h2"> <Text variant="heading-lg/semibold" style={{ color: "var(--header-primary)" }} tag="h2">
Not-Nextop Settings Not-Nextop Settings
=======
{/* FIXME: Outdated type */}
{/* @ts-expect-error Outdated type */}
<Text variant="heading-xl/semibold" color="header-primary" className="vcd-settings-title">
Vesktop Settings
>>>>>>> 27293d4ae9db3a395325f7993cf3cc74a21e0122
</Text> </Text>
<SettingsSections /> <SettingsSections />
</Forms.FormSection> </Forms.FormSection>
); );

View File

@@ -0,0 +1,16 @@
/*
* 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 { Switch } from "@vencord/types/webpack/common";
import { ComponentProps } from "react";
export function VesktopSettingsSwitch(props: ComponentProps<typeof Switch>) {
return (
<Switch {...props} hideBorder className="vcd-settings-switch">
{props.children}
</Switch>
);
}

View File

@@ -13,8 +13,8 @@ export const WindowsTransparencyControls: SettingsComponent = ({ settings }) =>
if (!VesktopNative.app.supportsWindowsTransparency()) return null; if (!VesktopNative.app.supportsWindowsTransparency()) return null;
return ( return (
<> <div>
<Forms.FormTitle className={Margins.top16 + " " + Margins.bottom8}>Transparency Options</Forms.FormTitle> <Forms.FormTitle className={Margins.bottom8}>Transparency Options</Forms.FormTitle>
<Forms.FormText className={Margins.bottom8}> <Forms.FormText className={Margins.bottom8}>
Requires a full restart. You will need a theme that supports transparency for this to work. Requires a full restart. You will need a theme that supports transparency for this to work.
</Forms.FormText> </Forms.FormText>
@@ -42,8 +42,6 @@ export const WindowsTransparencyControls: SettingsComponent = ({ settings }) =>
isSelected={v => v === settings.transparencyOption} isSelected={v => v === settings.transparencyOption}
serialize={s => s} serialize={s => s}
/> />
</div>
<Forms.FormDivider className={Margins.top16 + " " + Margins.bottom16} />
</>
); );
}; };

View File

@@ -5,10 +5,30 @@
margin-top: 0.5em; margin-top: 0.5em;
} }
.vcd-settings-section { .vcd-settings-title {
margin-top: 1.5rem; margin-bottom: 32px;
} }
.vcd-settings-title { .vcd-settings-category {
margin-bottom: 0.5rem; display: flex;
flex-direction: column;
}
.vcd-settings-category-title {
margin-bottom: 16px;
}
.vcd-settings-category-content {
display: flex;
flex-direction: column;
gap: 24px;
}
.vcd-settings-category-divider {
margin-top: 32px;
margin-bottom: 32px;
}
.vcd-settings-switch {
margin-bottom: 0;
} }

View File

@@ -1,19 +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 { addPatch } from "./shared";
addPatch({
patches: [
{
find: '"mod+alt+i"',
replacement: {
match: /"discord\.com"===location\.host/,
replace: "false"
}
}
]
});

View File

@@ -0,0 +1,41 @@
/*
* 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 { addPatch } from "./shared";
addPatch({
patches: [
// Discord Web blocks the devtools keybin on mac specifically, disable that
{
find: '"mod+alt+i"',
replacement: {
match: /"discord\.com"===location\.host/,
replace: "false"
}
},
// Discord Web uses an incredibly broken devtools detector with false positives.
// They "hide" (aka remove from storage) your token if it "detects" open devtools.
// Due to the false positives, this leads to random logouts.
// Patch their devtools detection to use proper Electron APIs instead to fix the false positives
{
find: ".setDevtoolsCallbacks(",
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

@@ -56,7 +56,7 @@ addContextMenuPatch("textarea-context", children => {
const settings = useSettings(); const settings = useSettings();
const spellCheckLanguages = (settings.spellCheckLanguages ??= [...new Set(navigator.languages)]); const spellCheckLanguages = (settings.spellCheckLanguages ??= [...new Set(navigator.languages)]);
const pasteSectionIndex = children.findIndex(c => c?.props?.children?.some(c => c?.props?.id === "paste")); const pasteSectionIndex = children.findIndex(c => c?.props?.children?.some?.(c => c?.props?.id === "paste"));
children.splice( children.splice(
pasteSectionIndex === -1 ? children.length : pasteSectionIndex, pasteSectionIndex === -1 ? children.length : pasteSectionIndex,

View File

@@ -21,6 +21,10 @@ addPatch({
// eslint-disable-next-line no-useless-escape // eslint-disable-next-line no-useless-escape
match: /(focus(\(\i\)){).{0,150}?\.focus\(\i,\i\)/, match: /(focus(\(\i\)){).{0,150}?\.focus\(\i,\i\)/,
replace: "$1VesktopNative.win.focus$2" replace: "$1VesktopNative.win.focus$2"
},
{
match: /,getEnableHardwareAcceleration/,
replace: "$&:VesktopNative.app.getEnableHardwareAcceleration,_oldGetEnableHardwareAcceleration"
} }
] ]
} }

View File

@@ -24,7 +24,7 @@ if (Settings.store.customTitleBar)
}, },
// Visual Refresh // Visual Refresh
{ {
find: '"data-windows":', find: ".systemBar,",
replacement: [ replacement: [
{ {
// TODO: Fix eslint rule // TODO: Fix eslint rule

View File

@@ -59,7 +59,7 @@ function resolveColor(color: string) {
const updateSplashColors = () => { const updateSplashColors = () => {
const bodyStyles = document.body.computedStyleMap(); const bodyStyles = document.body.computedStyleMap();
const color = bodyStyles.get("--text-normal"); const color = bodyStyles.get("--text-default");
const backgroundColor = bodyStyles.get("--background-primary"); const backgroundColor = bodyStyles.get("--background-primary");
if (isValidColor(color)) { if (isValidColor(color)) {

View File

@@ -12,6 +12,7 @@ export const enum IpcEvents {
GET_VERSION = "VCD_GET_VERSION", GET_VERSION = "VCD_GET_VERSION",
SUPPORTS_WINDOWS_TRANSPARENCY = "VCD_SUPPORTS_WINDOWS_TRANSPARENCY", SUPPORTS_WINDOWS_TRANSPARENCY = "VCD_SUPPORTS_WINDOWS_TRANSPARENCY",
GET_ENABLE_HARDWARE_ACCELERATION = "VCD_GET_ENABLE_HARDWARE_ACCELERATION",
RELAUNCH = "VCD_RELAUNCH", RELAUNCH = "VCD_RELAUNCH",
CLOSE = "VCD_CLOSE", CLOSE = "VCD_CLOSE",
@@ -48,14 +49,15 @@ export const enum IpcEvents {
VIRT_MIC_START_SYSTEM = "VCD_VIRT_MIC_START_ALL", VIRT_MIC_START_SYSTEM = "VCD_VIRT_MIC_START_ALL",
VIRT_MIC_STOP = "VCD_VIRT_MIC_STOP", VIRT_MIC_STOP = "VCD_VIRT_MIC_STOP",
ARRPC_ACTIVITY = "VCD_ARRPC_ACTIVITY",
CLIPBOARD_COPY_IMAGE = "VCD_CLIPBOARD_COPY_IMAGE", CLIPBOARD_COPY_IMAGE = "VCD_CLIPBOARD_COPY_IMAGE",
DEBUG_LAUNCH_GPU = "VCD_DEBUG_LAUNCH_GPU", DEBUG_LAUNCH_GPU = "VCD_DEBUG_LAUNCH_GPU",
DEBUG_LAUNCH_WEBRTC_INTERNALS = "VCD_DEBUG_LAUNCH_WEBRTC", DEBUG_LAUNCH_WEBRTC_INTERNALS = "VCD_DEBUG_LAUNCH_WEBRTC",
IPC_COMMAND = "VCD_IPC_COMMAND" IPC_COMMAND = "VCD_IPC_COMMAND",
DEVTOOLS_OPENED = "VCD_DEVTOOLS_OPENED",
DEVTOOLS_CLOSED = "VCD_DEVTOOLS_CLOSED"
} }
export const enum IpcCommands { export const enum IpcCommands {

View File

@@ -16,6 +16,7 @@ export interface Settings {
enableMenu?: boolean; enableMenu?: boolean;
disableSmoothScroll?: boolean; disableSmoothScroll?: boolean;
hardwareAcceleration?: boolean; hardwareAcceleration?: boolean;
hardwareVideoAcceleration?: boolean;
arRPC?: boolean; arRPC?: boolean;
appBadge?: boolean; appBadge?: boolean;
disableMinSize?: boolean; disableMinSize?: boolean;

View File

@@ -1,4 +1,6 @@
<head> <head>
<title>About Vesktop</title>
<link rel="stylesheet" href="./style.css" type="text/css" /> <link rel="stylesheet" href="./style.css" type="text/css" />
<style> <style>
@@ -9,19 +11,25 @@
h1 { h1 {
text-align: center; text-align: center;
} }
ul {
display: flex;
flex-direction: column;
gap: 0.5em;
}
</style> </style>
</head> </head>
<body> <body>
<h1 id="title">Vesktop</h1> <h1 id="title">Vesktop v{{APP_VERSION}}</h1>
<p> <p>Vesktop is a cross platform Discord Desktop client, aiming to give you a better Discord experience</p>
Vesktop is a free/libre cross platform desktop app aiming to give you a snappier Discord experience with Vencord
pre-installed
</p>
<section> <section>
<h2>Links</h2> <h2>Links</h2>
<ul> <ul>
<li>
<a href="https://vesktop.vencord.dev/wiki" target="_blank">Vesktop Wiki</a>
</li>
<li> <li>
<a href="https://vencord.dev" target="_blank">Vencord Website</a> <a href="https://vencord.dev" target="_blank">Vencord Website</a>
</li> </li>
@@ -34,6 +42,17 @@
</ul> </ul>
</section> </section>
<section>
<h2>License</h2>
<p>
Vesktop is licensed under the
<a href="https://www.gnu.org/licenses/gpl-3.0.txt" target="_blank">GNU General Public License v3.0</a>.
<br />
This is free software, and you are welcome to redistribute it under certain conditions; see the license for
details.
</p>
</section>
<section> <section>
<h2>Acknowledgements</h2> <h2>Acknowledgements</h2>
<p>These awesome libraries empower Vesktop</p> <p>These awesome libraries empower Vesktop</p>
@@ -53,23 +72,36 @@
</li> </li>
<li> <li>
<a href="https://github.com/Soundux/rohrkabel" target="_blank">rohrkabel</a> <a href="https://github.com/Soundux/rohrkabel" target="_blank">rohrkabel</a>
- A C++ RAII Pipewire-API Wrapper - A C++ RAII Pipewire-API Wrapper
</li> </li>
<li> <li>
And many And many
<a href="https://github.com/Vencord/Vesktop/blob/main/pnpm-lock.yaml" target="_blank" <a href="https://github.com/Vencord/Vesktop/blob/main/pnpm-lock.yaml" target="_blank"
>more awesome open source libraries</a >more open source libraries</a
> >
</li> </li>
</ul> </ul>
</section> </section>
</body> </body>
<script type="module"> <script>
const data = await Updater.getData(); const data = new URLSearchParams(location.search);
if (data.currentVersion) {
const title = document.getElementById("title");
title.textContent += ` v${data.currentVersion}`; // replace all {{FOO}} placeholders in the document with the values from the URL
/** @param {Node} [node] */
function walk(node) {
if (node.nodeType === Node.TEXT_NODE) {
node.textContent = node.textContent.replace(/{{(\w+)}}/g, (match, key) => data.get(key) || match);
return;
}
if (node.nodeType === Node.ELEMENT_NODE && node.nodeName !== "SCRIPT") {
for (const child of node.childNodes) {
walk(child);
}
}
} }
walk(document.body);
</script> </script>

View File

@@ -1,4 +1,6 @@
<head> <head>
<title>Vesktop Setup</title>
<link rel="stylesheet" href="./style.css" type="text/css" /> <link rel="stylesheet" href="./style.css" type="text/css" />
<style> <style>

View File

@@ -4,6 +4,7 @@
--fg-secondary: #313338; --fg-secondary: #313338;
--fg-semi-trans: rgb(0 0 0 / 0.2); --fg-semi-trans: rgb(0 0 0 / 0.2);
--link: #006ce7; --link: #006ce7;
--link-hover: #005bb5;
} }
@media (prefers-color-scheme: dark) { @media (prefers-color-scheme: dark) {
@@ -13,6 +14,7 @@
--fg-secondary: #b5bac1; --fg-secondary: #b5bac1;
--fg-semi-trans: rgb(255 255 255 / 0.2); --fg-semi-trans: rgb(255 255 255 / 0.2);
--link: #00a8fc; --link: #00a8fc;
--link-hover: #0086c3;
} }
} }
@@ -27,4 +29,9 @@ body {
a { a {
color: var(--link); color: var(--link);
transition: color 0.2s linear;
} }
a:hover {
color: var(--link-hover);
}