Compare commits

..

15 Commits

Author SHA1 Message Date
a71d798622 merge unconflict
Some checks failed
Release / release (ubuntu-latest, linux) (push) Has been cancelled
Release / release (windows-latest, windows) (push) Has been cancelled
test / test (push) Has been cancelled
2025-10-06 15:44:12 +02:00
V
a55b1f0250 Add splash/tray image customisation & change default splash (#1179) 2025-10-05 18:14:13 +00:00
Vendicated
e79635f15e fix: explicitly set executableName
fixes an upcoming regression caused by https://github.com/electron-userland/electron-builder/pull/9068
2025-10-04 00:38:17 +02:00
Cookie
6e2da1d294 feat: New Vesktop icon (#865)
changes the app icon and tray

Co-authored-by: Wing <44992537+wingio@users.noreply.github.com>
Co-authored-by: khcrysalis <sam4r16@gmail.com>
Co-authored-by: Vendicated <vendicated@riseup.net>
2025-10-02 22:02:43 +02:00
Vendicated
0d9ca2270c fix Vesktop being registered as html mime handler
This is actually a bug in xdg-settings that was already fixed years ago, but Ubuntu
still ships 7 (!!!!!) years out of date xdg-utils. Please just switch to Fedora what
are we doing man
2025-09-18 03:37:27 +02:00
d7650c927e Merge branch 'Vencord:main' into main
Some checks failed
Release / release (ubuntu-latest, linux) (push) Failing after 2m4s
Release / release (windows-latest, windows) (push) Failing after 2s
test / test (push) Failing after 2s
2025-09-07 17:42:39 +02:00
Tiagoquix
497c251d72 Add icon to auto-start script (#1172) 2025-09-05 00:03:12 +00:00
Vendicated
b221882c5b upgrade dependencies 2025-09-04 19:45:04 +02:00
Vendicated
0ee194698d upgrade to electron 38 2025-09-04 19:43:27 +02:00
5c856220b2 logo 2025-07-23 22:53:07 +02:00
f28d4d67cb !nextop 2025-07-23 15:04:46 +02:00
a14c200433 wait im stupi 2025-07-19 14:19:11 +02:00
87416e937b blemg 2025-07-19 14:17:25 +02:00
bc1c047fde v1.6.0 2025-07-19 13:55:11 +02:00
6464ef2a34 v1.5.9 2025-07-19 13:30:22 +02:00
45 changed files with 896 additions and 546 deletions

View File

@@ -39,4 +39,4 @@ jobs:
git commit -m "metainfo: add entry for ${{ github.event.release.tag_name }}"
git push origin HEAD:main
env:
GITHUB_TOKEN: ${{ secrets.PERSONAL_TOKEN }}
GH_TOKEN: ${{ secrets.ACCESS_TOKEN }}

BIN
build/Assets.car Normal file

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
build/icon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 364 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.5 KiB

1
build/icon.svg Normal file
View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="1024" height="1024" style="isolation:isolate" viewBox="0 0 1024 1024"><path fill="#eb7396" d="M336.23 886.35c56.07 31.62 150.18 51.4 247.12 37.3C816.1 889.78 977.57 673.33 943.7 440.57 909.85 207.83 693.41 46.36 460.65 80.22 227.9 114.08 66.43 330.53 100.3 563.28c6.86 47.2 21.23 91.46 29.65 108.01 6.67 13.1 9.03 35.28 5.28 49.5l-26.8 101.43c-16.75 63.41 21.03 100.93 84.32 83.73l94.59-25.7c14.18-3.86 36.1-1.12 48.9 6.1z" style="display:inline;isolation:isolate"/><g style="display:inline;isolation:isolate"><g style="isolation:isolate"><path d="M494.586 109.162c-3.861.14-7.743.508-11.621 1.004-72.122 6.505-117.149 63.067-128.176 116.693l-17.812 86.508-35.555-76.785v-.002c-23.455-50.637-77.415-90.65-141.998-81.877-32.285 4.386-60.608 22.22-76.123 48.385-13.722 23.14-16.564 52.439-7.66 78.943v1.608l157.394 336.037c24.863 53.102 81.404 87.116 143.43 78.681 61.475-8.36 109.47-52.362 123.914-110.074l91.158-364.25c7.385-29.508 1.643-63.11-20.129-86.328-19.05-20.315-47.417-29.61-76.822-28.543" style="baseline-shift:baseline;display:inline;overflow:visible;opacity:1;vector-effect:none;fill:#1d2021;stop-color:#000;stop-opacity:1"/><path fill="#fff" d="m118.49 274.1 153.35 327.4c16 34.18 58.517 59.883 98.852 54.398s78.958-41.418 88.118-78.018l91.16-364.25c9.16-36.6-14.593-67.398-62.253-60.918-51.41 4.189-83.357 45.818-90.957 82.778l-37.84 183.78c-3.42 16.62-11.98 17.61-19.1 2.22l-77.28-166.9c-15.86-34.24-55.539-63.108-97.349-57.428S102.48 239.92 118.49 274.09Z" style="display:inline;fill:#ededed;fill-opacity:1"/><path d="M701.543 396.742a266.4 266.4 0 0 0-100.73 17.746l-.004.002h-.002a271.8 271.8 0 0 0-81.118 48.942 275 275 0 0 0-59.822 74.15v.002a277 277 0 0 0-31.572 93.527v.006l-.002.006c-1.984 13.48-3.023 27.136-3.024 40.893v.014a275.13 275.13 0 0 0 64.01 176.617l.004.004.002.004a269.5 269.5 0 0 0 72.846 61.008l.02.011.02.012a266.5 266.5 0 0 0 93.054 32.19l.017.002a266.3 266.3 0 0 0 114.906-8.061l.02-.006.02-.006a270.3 270.3 0 0 0 67.706-30.816l.004-.002c34.575-21.867 52.68-62.732 45.645-103.032-7.032-40.29-37.897-72.6-77.82-81.47a101.46 101.46 0 0 0-76.338 13.314l-.024.016-.023.015a67 67 0 0 1-16.83 7.67 63.3 63.3 0 0 1-17.957 2.6h-.081a63.86 63.86 0 0 1-31.726-8.37 67 67 0 0 1-17.951-15.042l-.006-.008a72.44 72.44 0 0 1-16.838-46.524v-.016a76.6 76.6 0 0 1 9.367-36.642 72.5 72.5 0 0 1 15.688-19.395l.012-.012.011-.011a69.3 69.3 0 0 1 20.617-12.455 63.97 63.97 0 0 1 59.061 6.896 68.5 68.5 0 0 1 19.91 21.201h.002v.002c28.529 47.605 91.489 63.35 139.068 34.78l.01-.006.01-.006c47.508-28.562 63.227-91.41 34.76-138.975l-.013-.025-.016-.026a271.8 271.8 0 0 0-79.111-84.105l-.026-.02-.027-.017a266.5 266.5 0 0 0-111.506-43.725 266 266 0 0 0-34.223-2.857" style="baseline-shift:baseline;display:inline;overflow:visible;opacity:1;vector-effect:none;fill:#ededed;stop-color:#000;stop-opacity:1"/><path fill="#1d2021" d="M751.65 766.94a59.8 59.8 0 0 1 45.03-7.85c23.607 5.237 41.724 24.197 45.882 48.018 4.157 23.821-6.465 47.797-26.902 60.722a228.7 228.7 0 0 1-57.33 26.1 224.7 224.7 0 0 1-96.97 6.8 224.9 224.9 0 0 1-78.56-27.17 227.9 227.9 0 0 1-61.6-51.59 233.5 233.5 0 0 1-54.33-149.94c0-11.67.88-23.3 2.58-34.85a235.4 235.4 0 0 1 26.83-79.48 233.4 233.4 0 0 1 50.78-62.94 230.2 230.2 0 0 1 68.7-41.45 224.8 224.8 0 0 1 113.91-12.56c33.75 5 65.94 17.62 94.1 36.9a230.15 230.15 0 0 1 67 71.23c16.936 28.299 7.765 64.967-20.5 81.96-28.294 16.99-65.005 7.81-81.97-20.5a110.1 110.1 0 0 0-32.08-34.14 105.7 105.7 0 0 0-97.52-11.4 110.9 110.9 0 0 0-33.05 19.96 114 114 0 0 0-24.79 30.69 118.2 118.2 0 0 0-14.51 56.66 114.07 114.07 0 0 0 26.51 73.24 108.6 108.6 0 0 0 29.24 24.5 105.5 105.5 0 0 0 52.45 13.85c10.08 0 20.12-1.45 29.8-4.32a108.6 108.6 0 0 0 27.3-12.44" style="display:inline"/></g></g></svg>

After

Width:  |  Height:  |  Size: 3.7 KiB

View File

@@ -1,6 +1,6 @@
{
"name": "not-nextop",
"version": "1.5.8",
"version": "1.6.0",
"private": true,
"description": "Not-Nextop is a custom Discord desktop app",
"keywords": [],
@@ -34,27 +34,27 @@
},
"devDependencies": {
"@fal-works/esbuild-plugin-global-externals": "^2.1.2",
"@stylistic/eslint-plugin": "^5.1.0",
"@types/node": "^24.0.10",
"@stylistic/eslint-plugin": "^5.3.1",
"@types/node": "^24.3.1",
"@types/react": "18.3.1",
"@vencord/types": "^1.11.5",
"dotenv": "^16.5.0",
"electron": "^37.2.0",
"dotenv": "^17.2.2",
"electron": "^38.0.0",
"electron-builder": "^26.0.12",
"esbuild": "^0.25.5",
"eslint": "^9.30.1",
"esbuild": "^0.25.9",
"eslint": "^9.34.0",
"eslint-import-resolver-alias": "^1.1.2",
"eslint-plugin-path-alias": "^2.1.0",
"eslint-plugin-prettier": "^5.5.1",
"eslint-plugin-prettier": "^5.5.4",
"eslint-plugin-simple-header": "^1.2.2",
"eslint-plugin-simple-import-sort": "^12.1.1",
"eslint-plugin-unused-imports": "^4.1.4",
"eslint-plugin-unused-imports": "^4.2.0",
"prettier": "^3.6.2",
"source-map-support": "^0.5.21",
"tsx": "^4.20.3",
"tsx": "^4.20.5",
"type-fest": "^4.41.0",
"typescript": "^5.8.3",
"typescript-eslint": "^8.35.1",
"typescript": "^5.9.2",
"typescript-eslint": "^8.42.0",
"xml-formatter": "^3.6.6"
},
"packageManager": "pnpm@10.7.1",
@@ -65,6 +65,7 @@
"build": {
"appId": "xyz.defautluser0.notnextop",
"productName": "Not-Nextop",
"executableName": "not-nextop",
"files": [
"!*",
"!node_modules",
@@ -79,9 +80,10 @@
"discord"
]
},
"beforePack": "scripts/build/sandboxFix.js",
"beforePack": "scripts/build/beforePack.mjs",
"afterPack": "scripts/build/afterPack.mjs",
"linux": {
"icon": "build/icon.icns",
"icon": "build/icon.svg",
"category": "Network",
"maintainer": "jaegerwald.dev+nexop@gmail.com",
"target": [
@@ -141,7 +143,8 @@
"NSMicrophoneUsageDescription": "This app needs access to the microphone",
"NSCameraUsageDescription": "This app needs access to the camera",
"com.apple.security.device.audio-input": true,
"com.apple.security.device.camera": true
"com.apple.security.device.camera": true,
"CFBundleIconName": "Icon"
},
"notarize": true
},
@@ -171,6 +174,7 @@
"oneClick": false
},
"win": {
"icon": "build/icon.ico",
"target": [
{
"target": "nsis",

731
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,24 @@
import { copyFile, readdir } from "fs/promises";
/**
* @param {{
* readonly appOutDir: string;
* readonly arch: Arch;
* readonly electronPlatformName: string;
* readonly outDir: string;
* readonly packager: PlatformPackager;
* readonly targets: Target[];
* }} context
*/
export async function addAssetsCar({ appOutDir }) {
if (process.platform !== "darwin") return;
const appName = (await readdir(appOutDir)).find(item => item.endsWith(".app"));
if (!appName) {
console.warn(`Could not find .app directory in ${appOutDir}. Skipping adding assets.car`);
return;
}
await copyFile("build/Assets.car", `${appOutDir}/${appName}/Contents/Resources/Assets.car`);
}

View File

@@ -0,0 +1,5 @@
import { addAssetsCar } from "./addAssetsCar.mjs";
export default async function afterPack(context) {
await addAssetsCar(context);
}

View File

@@ -0,0 +1,5 @@
import { applyAppImageSandboxFix } from "./sandboxFix.mjs";
export default async function beforePack() {
await applyAppImageSandboxFix();
}

View File

@@ -6,18 +6,21 @@
// Based on https://github.com/gergof/electron-builder-sandbox-fix/blob/master/lib/index.js
const fs = require("fs/promises");
const path = require("path");
import fs from "fs/promises";
import path from "path";
import AppImageTarget from "app-builder-lib/out/targets/AppImageTarget.js";
let isApplied = false;
const hook = async () => {
if (isApplied) return;
isApplied = true;
export async function applyAppImageSandboxFix() {
if (process.platform !== "linux") {
// this fix is only required on linux
return;
}
const AppImageTarget = require("app-builder-lib/out/targets/AppImageTarget");
if (isApplied) return;
isApplied = true;
const oldBuildMethod = AppImageTarget.default.prototype.build;
AppImageTarget.default.prototype.build = async function (...args) {
console.log("Running AppImage builder hook", args);
@@ -69,6 +72,4 @@ exec "$SCRIPT_DIR/${this.packager.executableName}.bin" "$([ "$IS_STEAMOS" == 1 ]
return ret;
};
};
module.exports = hook;
}

View File

@@ -6,7 +6,7 @@
import { app, BrowserWindow } from "electron";
import { join } from "path";
import { ICON_PATH, VIEW_DIR } from "shared/paths";
import { VIEW_DIR } from "shared/paths";
import { makeLinksOpenExternally } from "./utils/makeLinksOpenExternally";
@@ -17,7 +17,6 @@ export async function createAboutWindow() {
const about = new BrowserWindow({
center: true,
autoHideMenuBar: true,
icon: ICON_PATH,
height,
width
});

View File

@@ -8,6 +8,8 @@ import { app, NativeImage, nativeImage } from "electron";
import { join } from "path";
import { BADGE_DIR } from "shared/paths";
import { AppEvents } from "./events";
const imgCache = new Map<number, NativeImage>();
function loadBadge(index: number) {
const cached = imgCache.get(index);
@@ -21,7 +23,13 @@ function loadBadge(index: number) {
let lastIndex: null | number = -1;
/**
* -1 = show unread indicator
* 0 = clear
*/
export function setBadgeCount(count: number) {
AppEvents.emit("setTrayVariant", count !== 0 ? "trayUnread" : "tray");
switch (process.platform) {
case "linux":
if (count === -1) count = 0;

View File

@@ -40,6 +40,7 @@ function makeAutoStartLinux(): AutoStart {
Exec=${commandLine}
StartupNotify=false
Terminal=false
Icon=vesktop
`;
mkdirSync(dir, { recursive: true });

View File

@@ -34,7 +34,7 @@ export const VENCORD_FILES_DIR =
(require("./settings") as typeof import("./settings")).State.store.vencordDir ||
join(SESSION_DATA_DIR, "vencordFiles");
export const USER_AGENT = `Vesktop/${app.getVersion()} (https://github.com/Vencord/Vesktop)`;
export const USER_AGENT = `Vesktop/${app.getVersion()} (https://github.com/Not-Nexulien/Not-Nextop)`;
// dimensions shamelessly stolen from Discord Desktop :3
export const MIN_WIDTH = 940;

15
src/main/events.ts Normal file
View File

@@ -0,0 +1,15 @@
/*
* 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 { EventEmitter } from "events";
import { UserAssetType } from "./userAssets";
export const AppEvents = new EventEmitter<{
appLoaded: [];
userAssetChanged: [UserAssetType];
setTrayVariant: ["tray" | "trayUnread"];
}>();

View File

@@ -9,7 +9,7 @@ import { BrowserWindow } from "electron/main";
import { copyFileSync, mkdirSync, readdirSync } from "fs";
import { join } from "path";
import { SplashProps } from "shared/browserWinProperties";
import { ICON_PATH, VIEW_DIR } from "shared/paths";
import { VIEW_DIR } from "shared/paths";
import { autoStart } from "./autoStart";
import { DATA_DIR } from "./constants";
@@ -32,8 +32,7 @@ export function createFirstLaunchTour() {
frame: true,
autoHideMenuBar: true,
height: 470,
width: 550,
icon: ICON_PATH
width: 550
});
makeLinksOpenExternally(win);

View File

@@ -5,6 +5,7 @@
*/
import "./ipc";
import "./userAssets";
import { app, BrowserWindow, nativeTheme } from "electron";
import { autoUpdater } from "electron-updater";
@@ -15,6 +16,7 @@ import { createWindows, mainWin } from "./mainWindow";
import { registerMediaPermissionsHandler } from "./mediaPermissions";
import { registerScreenShareHandler } from "./screenShare";
import { Settings, State } from "./settings";
import { setAsDefaultProtocolClient } from "./utils/setAsDefaultProtocolClient";
import { isDeckGameMode } from "./utils/steamOS";
if (!IS_DEV) {
@@ -31,7 +33,7 @@ const isLinux = process.platform === "linux";
export let enableHardwareAcceleration = true;
function init() {
app.setAsDefaultProtocolClient("discord");
setAsDefaultProtocolClient("discord");
const { disableSmoothScroll, hardwareAcceleration, hardwareVideoAcceleration } = Settings.store;
@@ -126,10 +128,10 @@ function init() {
if (!app.requestSingleInstanceLock({ IS_DEV })) {
if (IS_DEV) {
console.log("Vesktop is already running. Quitting previous instance...");
console.log("Not-Nextop is already running. Quitting previous instance...");
init();
} else {
console.log("Vesktop is already running. Quitting...");
console.log("Not-Nextop is already running. Quitting...");
app.quit();
}
} else {

View File

@@ -1,52 +1,44 @@
/*
* Vesktop, a desktop app aiming to give you a snappier Discord Experience
* Copyright (c) 2023 Vendicated and Vencord contributors
* Copyright (c) 2025 Vendicated and Vesktop contributors
* SPDX-License-Identifier: GPL-3.0-or-later
*/
import {
app,
BrowserWindow,
BrowserWindowConstructorOptions,
dialog,
Menu,
MenuItemConstructorOptions,
nativeTheme,
screen,
session,
Tray
session
} from "electron";
import { EventEmitter } from "events";
import { rm } from "fs/promises";
import { join } from "path";
import { IpcCommands, IpcEvents } from "shared/IpcEvents";
import { isTruthy } from "shared/utils/guards";
import { once } from "shared/utils/once";
import type { SettingsStore } from "shared/utils/SettingsStore";
import { ICON_PATH } from "../shared/paths";
import { createAboutWindow } from "./about";
import { initArRPC } from "./arrpc";
import {
BrowserUserAgent,
DATA_DIR,
DEFAULT_HEIGHT,
DEFAULT_WIDTH,
MessageBoxChoice,
MIN_HEIGHT,
MIN_WIDTH,
VENCORD_FILES_DIR
} from "./constants";
import { BrowserUserAgent, DEFAULT_HEIGHT, DEFAULT_WIDTH, MIN_HEIGHT, MIN_WIDTH, VENCORD_FILES_DIR } from "./constants";
import { AppEvents } from "./events";
import { darwinURL } from "./index";
import { sendRendererCommand } from "./ipcCommands";
import { Settings, State, VencordSettings } from "./settings";
import { createSplashWindow, updateSplashMessage } from "./splash";
import { destroyTray, initTray } from "./tray";
import { clearData } from "./utils/clearData";
import { makeLinksOpenExternally } from "./utils/makeLinksOpenExternally";
import { applyDeckKeyboardFix, askToApplySteamLayout, isDeckGameMode } from "./utils/steamOS";
import { downloadVencordFiles, ensureVencordFiles } from "./utils/vencordLoader";
let isQuitting = false;
let tray: Tray;
applyDeckKeyboardFix();
@@ -77,84 +69,6 @@ function makeSettingsListenerHelpers<O extends object>(o: SettingsStore<O>) {
const [addSettingsListener, removeSettingsListeners] = makeSettingsListenerHelpers(Settings);
const [addVencordSettingsListener, removeVencordSettingsListeners] = makeSettingsListenerHelpers(VencordSettings);
function initTray(win: BrowserWindow) {
const onTrayClick = () => {
if (Settings.store.clickTrayToShowHide && win.isVisible()) win.hide();
else win.show();
};
const trayMenu = Menu.buildFromTemplate([
{
label: "Open",
click() {
win.show();
}
},
{
label: "About",
click: createAboutWindow
},
{
label: "Repair Vencord",
async click() {
await downloadVencordFiles();
app.relaunch();
app.quit();
}
},
{
label: "Reset Vesktop",
async click() {
await clearData(win);
}
},
{
type: "separator"
},
{
label: "Restart",
click() {
app.relaunch();
app.quit();
}
},
{
label: "Quit",
click() {
isQuitting = true;
app.quit();
}
}
]);
tray = new Tray(ICON_PATH);
tray.setToolTip("Vesktop");
tray.setContextMenu(trayMenu);
tray.on("click", onTrayClick);
}
async function clearData(win: BrowserWindow) {
const { response } = await dialog.showMessageBox(win, {
message: "Are you sure you want to reset Vesktop?",
detail: "This will log you out, clear caches and reset all your settings!\n\nVesktop will automatically restart after this operation.",
buttons: ["Yes", "No"],
cancelId: MessageBoxChoice.Cancel,
defaultId: MessageBoxChoice.Default,
type: "warning"
});
if (response === MessageBoxChoice.Cancel) return;
win.close();
await win.webContents.session.clearStorageData();
await win.webContents.session.clearCache();
await win.webContents.session.clearCodeCaches({});
await rm(DATA_DIR, { force: true, recursive: true });
app.relaunch();
app.quit();
}
type MenuItemList = Array<MenuItemConstructorOptions | false>;
function initMenuBar(win: BrowserWindow) {
@@ -164,24 +78,24 @@ function initMenuBar(win: BrowserWindow) {
const subMenu = [
{
label: "About Vesktop",
label: "About Not-Nextop",
click: createAboutWindow
},
{
label: "Force Update Vencord",
label: "Force Update Not-Nexulien",
async click() {
await downloadVencordFiles();
app.relaunch();
app.quit();
},
toolTip: "Vesktop will automatically restart after this operation"
toolTip: "Not-Nextop will automatically restart after this operation"
},
{
label: "Reset Vesktop",
label: "Reset Not-Nextop",
async click() {
await clearData(win);
},
toolTip: "Vesktop will automatically restart after this operation"
toolTip: "Not-Nextop will automatically restart after this operation"
},
{
label: "Relaunch",
@@ -248,7 +162,7 @@ function initMenuBar(win: BrowserWindow) {
const menuItems = [
{
label: "Vesktop",
label: "Not-Nextop",
role: "appMenu",
submenu: subMenu.filter(isTruthy)
},
@@ -333,8 +247,8 @@ function initWindowBoundsListeners(win: BrowserWindow) {
function initSettingsListeners(win: BrowserWindow) {
addSettingsListener("tray", enable => {
if (enable) initTray(win);
else tray?.destroy();
if (enable) initTray(win, q => (isQuitting = q));
else destroyTray();
});
addSettingsListener("disableMinSize", disable => {
@@ -439,7 +353,6 @@ function createMainWindow() {
// disable renderer backgrounding to prevent the app from unloading when in the background
backgroundThrottling: false
},
icon: ICON_PATH,
frame: !noFrame,
...(transparent && {
transparent: true,
@@ -477,7 +390,9 @@ function createMainWindow() {
});
initWindowBoundsListeners(win);
if (!isDeckGameMode && (Settings.store.tray ?? true) && process.platform !== "darwin") initTray(win);
if (!isDeckGameMode && (Settings.store.tray ?? true) && process.platform !== "darwin")
initTray(win, q => (isQuitting = q));
initMenuBar(win);
makeLinksOpenExternally(win);
initSettingsListeners(win);
@@ -497,8 +412,6 @@ function createMainWindow() {
const runVencordMain = once(() => require(join(VENCORD_FILES_DIR, "vencordDesktopMain.js")));
const loadEvents = new EventEmitter();
export function loadUrl(uri: string | undefined) {
const branch = Settings.store.discordBranch;
const subdomain = branch === "canary" || branch === "ptb" ? `${branch}.` : "";
@@ -506,7 +419,7 @@ export function loadUrl(uri: string | undefined) {
// we do not rely on 'did-finish-load' because it fires even if loadURL fails which triggers early detruction of the splash
mainWin
.loadURL(`https://${subdomain}discord.com/${uri ? new URL(uri).pathname.slice(1) || "app" : "app"}`)
.then(() => loadEvents.emit("app-loaded"))
.then(() => AppEvents.emit("appLoaded"))
.catch(error => retryUrl(error.url, error.code));
}
@@ -533,7 +446,7 @@ export async function createWindows() {
mainWin = createMainWindow();
loadEvents.on("app-loaded", () => {
AppEvents.on("appLoaded", () => {
splash?.destroy();
if (!startMinimized) {

View File

@@ -34,6 +34,6 @@ function loadSettings<T extends object = any>(file: string, name: string) {
return store;
}
export const Settings = loadSettings<TSettings>(SETTINGS_FILE, "Vesktop settings");
export const VencordSettings = loadSettings<any>(VENCORD_SETTINGS_FILE, "Vencord settings");
export const State = loadSettings<TState>(STATE_FILE, "Vesktop state");
export const Settings = loadSettings<TSettings>(SETTINGS_FILE, "Not-Nextop settings");
export const VencordSettings = loadSettings<any>(VENCORD_SETTINGS_FILE, "Not-Nextop settings");
export const State = loadSettings<TState>(STATE_FILE, "Not-Nextop state");

View File

@@ -7,7 +7,7 @@
import { BrowserWindow } from "electron";
import { join } from "path";
import { SplashProps } from "shared/browserWinProperties";
import { ICON_PATH, VIEW_DIR } from "shared/paths";
import { VIEW_DIR } from "shared/paths";
import { Settings } from "./settings";
@@ -16,7 +16,6 @@ let splash: BrowserWindow | undefined;
export function createSplashWindow(startMinimized = false) {
splash = new BrowserWindow({
...SplashProps,
icon: ICON_PATH,
show: !startMinimized,
webPreferences: {
preload: join(__dirname, "splashPreload.js")

92
src/main/tray.ts Normal file
View File

@@ -0,0 +1,92 @@
/*
* 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 { app, BrowserWindow, Menu, Tray } from "electron";
import { createAboutWindow } from "./about";
import { AppEvents } from "./events";
import { Settings } from "./settings";
import { resolveAssetPath } from "./userAssets";
import { clearData } from "./utils/clearData";
import { downloadVencordFiles } from "./utils/vencordLoader";
let tray: Tray;
let trayVariant: "tray" | "trayUnread" = "tray";
AppEvents.on("userAssetChanged", async asset => {
if (tray && (asset === "tray" || asset === "trayUnread")) {
tray.setImage(await resolveAssetPath(trayVariant));
}
});
AppEvents.on("setTrayVariant", async variant => {
if (trayVariant === variant) return;
trayVariant = variant;
if (!tray) return;
tray.setImage(await resolveAssetPath(trayVariant));
});
export function destroyTray() {
tray?.destroy();
}
export async function initTray(win: BrowserWindow, setIsQuitting: (val: boolean) => void) {
const onTrayClick = () => {
if (Settings.store.clickTrayToShowHide && win.isVisible()) win.hide();
else win.show();
};
const trayMenu = Menu.buildFromTemplate([
{
label: "Open",
click() {
win.show();
}
},
{
label: "About",
click: createAboutWindow
},
{
label: "Repair Vencord",
async click() {
await downloadVencordFiles();
app.relaunch();
app.quit();
}
},
{
label: "Reset Vesktop",
async click() {
await clearData(win);
}
},
{
type: "separator"
},
{
label: "Restart",
click() {
app.relaunch();
app.quit();
}
},
{
label: "Quit",
click() {
setIsQuitting(true);
app.quit();
}
}
]);
tray = new Tray(await resolveAssetPath(trayVariant));
tray.setToolTip("Vesktop");
tray.setContextMenu(trayMenu);
tray.on("click", onTrayClick);
}

110
src/main/userAssets.ts Normal file
View File

@@ -0,0 +1,110 @@
/*
* 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 { app, dialog, net, protocol } from "electron";
import { copyFile, mkdir, rm } from "fs/promises";
import { join } from "path";
import { IpcEvents } from "shared/IpcEvents";
import { STATIC_DIR } from "shared/paths";
import { pathToFileURL } from "url";
import { DATA_DIR } from "./constants";
import { AppEvents } from "./events";
import { mainWin } from "./mainWindow";
import { fileExistsAsync } from "./utils/fileExists";
import { handle } from "./utils/ipcWrappers";
const CUSTOMIZABLE_ASSETS = ["splash", "tray", "trayUnread"] as const;
export type UserAssetType = (typeof CUSTOMIZABLE_ASSETS)[number];
const DEFAULT_ASSETS: Record<UserAssetType, string> = {
splash: "splash.webp",
tray: `tray/${process.platform === "darwin" ? "trayTemplate" : "tray"}.png`,
trayUnread: "tray/trayUnread.png"
};
const UserAssetFolder = join(DATA_DIR, "userAssets");
export async function resolveAssetPath(asset: UserAssetType) {
if (!CUSTOMIZABLE_ASSETS.includes(asset)) {
throw new Error(`Invalid asset: ${asset}`);
}
const assetPath = join(UserAssetFolder, asset);
if (await fileExistsAsync(assetPath)) {
return assetPath;
}
return join(STATIC_DIR, DEFAULT_ASSETS[asset]);
}
app.whenReady().then(() => {
protocol.handle("vesktop", async req => {
if (!req.url.startsWith("vesktop://assets/")) {
return new Response(null, { status: 404 });
}
const asset = decodeURI(req.url)
.slice("vesktop://assets/".length)
.replace(/\?v=\d+$/, "")
.replace(/\/+$/, "");
// @ts-expect-error dumb types
if (!CUSTOMIZABLE_ASSETS.includes(asset)) {
return new Response(null, { status: 404 });
}
try {
const res = await net.fetch(pathToFileURL(join(UserAssetFolder, asset)).href);
if (res.ok) return res;
} catch {}
return net.fetch(pathToFileURL(join(STATIC_DIR, DEFAULT_ASSETS[asset])).href);
});
});
handle(IpcEvents.CHOOSE_USER_ASSET, async (_event, asset: UserAssetType, value?: null) => {
if (!CUSTOMIZABLE_ASSETS.includes(asset)) {
throw `Invalid asset: ${asset}`;
}
const assetPath = join(UserAssetFolder, asset);
if (value === null) {
try {
await rm(assetPath, { force: true });
AppEvents.emit("userAssetChanged", asset);
return "ok";
} catch (e) {
console.error(`Failed to remove user asset ${asset}:`, e);
return "failed";
}
}
const res = await dialog.showOpenDialog(mainWin, {
properties: ["openFile"],
title: `Select an image to use as ${asset}`,
defaultPath: app.getPath("pictures"),
filters: [
{
name: "Images",
extensions: ["png", "jpg", "jpeg", "webp", "gif", "avif", "svg"]
}
]
});
if (res.canceled || !res.filePaths.length) return "cancelled";
try {
await mkdir(UserAssetFolder, { recursive: true });
await copyFile(res.filePaths[0], assetPath);
AppEvents.emit("userAssetChanged", asset);
return "ok";
} catch (e) {
console.error(`Failed to copy user asset ${asset}:`, e);
return "failed";
}
});

View File

@@ -0,0 +1,32 @@
/*
* 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 { app, BrowserWindow, dialog } from "electron";
import { rm } from "fs/promises";
import { DATA_DIR, MessageBoxChoice } from "main/constants";
export async function clearData(win: BrowserWindow) {
const { response } = await dialog.showMessageBox(win, {
message: "Are you sure you want to reset Vesktop?",
detail: "This will log you out, clear caches and reset all your settings!\n\nVesktop will automatically restart after this operation.",
buttons: ["Yes", "No"],
cancelId: MessageBoxChoice.Cancel,
defaultId: MessageBoxChoice.Default,
type: "warning"
});
if (response === MessageBoxChoice.Cancel) return;
win.close();
await win.webContents.session.clearStorageData();
await win.webContents.session.clearCache();
await win.webContents.session.clearCodeCaches({});
await rm(DATA_DIR, { force: true, recursive: true });
app.relaunch();
app.quit();
}

View File

@@ -0,0 +1,13 @@
/*
* 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 { access, constants } from "fs/promises";
export async function fileExistsAsync(path: string) {
return await access(path, constants.F_OK)
.then(() => true)
.catch(() => false);
}

View File

@@ -0,0 +1,29 @@
/*
* 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 { execFile } from "child_process";
import { app } from "electron";
export async function setAsDefaultProtocolClient(protocol: string) {
if (process.platform !== "linux") {
return app.setAsDefaultProtocolClient(protocol);
}
// electron setAsDefaultProtocolClient uses xdg-settings instead of xdg-mime.
// xdg-settings had a bug where it would also register the app as a handler for text/html,
// aka become your browser. This bug was fixed years ago (xdg-utils 1.2.0) but Ubuntu ships
// 7 (YES, SEVEN) years out of date xdg-utils which STILL has the bug.
// FIXME: remove this workaround when Ubuntu updates their xdg-utils or electron switches to xdg-mime.
const { CHROME_DESKTOP } = process.env;
if (!CHROME_DESKTOP) return false;
return new Promise<boolean>(resolve => {
execFile("xdg-mime", ["default", CHROME_DESKTOP, `x-scheme-handler/${protocol}`], err => {
resolve(err == null);
});
});
}

View File

@@ -44,7 +44,7 @@ export async function githubGet(endpoint: string) {
}
export async function downloadVencordFiles() {
const release = await githubGet("/repos/not-nexulien/Not-Nexulien/releases/latest");
const release = await githubGet("/repos/Not-Nexulien/Not-Nexulien/releases/latest");
const { assets }: ReleaseData = await release.json();

View File

@@ -42,7 +42,9 @@ export const VesktopNative = {
fileManager: {
showItemInFolder: (path: string) => invoke<void>(IpcEvents.SHOW_ITEM_IN_FOLDER, path),
getVencordDir: () => sendSync<string | undefined>(IpcEvents.GET_VENCORD_DIR),
selectVencordDir: (value?: null) => invoke<"cancelled" | "invalid" | "ok">(IpcEvents.SELECT_VENCORD_DIR, value)
selectVencordDir: (value?: null) => invoke<"cancelled" | "invalid" | "ok">(IpcEvents.SELECT_VENCORD_DIR, value),
chooseUserAsset: (asset: string, value?: null) =>
invoke<"cancelled" | "invalid" | "ok" | "failed">(IpcEvents.CHOOSE_USER_ASSET, asset, value)
},
settings: {
get: () => sendSync<Settings>(IpcEvents.GET_SETTINGS),

View File

@@ -16,6 +16,7 @@ import { AutoStartToggle } from "./AutoStartToggle";
import { DeveloperOptionsButton } from "./DeveloperOptions";
import { DiscordBranchPicker } from "./DiscordBranchPicker";
import { NotificationBadgeToggle } from "./NotificationBadgeToggle";
import { UserAssetsButton } from "./UserAssets";
import { VesktopSettingsSwitch } from "./VesktopSettingsSwitch";
import { WindowsTransparencyControls } from "./WindowsTransparencyControls";
@@ -82,7 +83,8 @@ const SettingsOptions: Record<string, Array<BooleanSetting | SettingsComponent>>
description: "Adapt the splash window colors to your custom theme",
defaultValue: true
},
WindowsTransparencyControls
WindowsTransparencyControls,
UserAssetsButton
],
Behaviour: [
{
@@ -179,15 +181,10 @@ export default ErrorBoundary.wrap(
function SettingsUI() {
return (
<Forms.FormSection>
<<<<<<< HEAD
<Text variant="heading-lg/semibold" style={{ color: "var(--header-primary)" }} tag="h2">
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
Not-Nextop Settings
</Text>
<SettingsSections />
</Forms.FormSection>

View File

@@ -0,0 +1,25 @@
.vcd-user-assets {
display: flex;
margin-block: 1em 2em;
flex-direction: column;
gap: 1em;
}
.vcd-user-assets-asset {
display: flex;
margin-top: 0.5em;
align-items: center;
gap: 1em;
}
.vcd-user-assets-actions {
display: flex;
flex-direction: column;
gap: 0.5em;
}
.vcd-user-assets-image {
height: 7.5em;
width: 7.5em;
object-fit: contain;
}

View File

@@ -0,0 +1,80 @@
/*
* Vesktop, a desktop app aiming to give you a snappier Discord Experience
* Copyright (c) 2025 Vendicated and Vencord contributors
* SPDX-License-Identifier: GPL-3.0-or-later
*/
import "./UserAssets.css";
import {
ModalCloseButton,
ModalContent,
ModalHeader,
ModalRoot,
ModalSize,
openModal,
wordsFromCamel,
wordsToTitle
} from "@vencord/types/utils";
import { Button, showToast, Text, useState } from "@vencord/types/webpack/common";
import { UserAssetType } from "main/userAssets";
import { SettingsComponent } from "./Settings";
const CUSTOMIZABLE_ASSETS: UserAssetType[] = ["splash", "tray", "trayUnread"];
export const UserAssetsButton: SettingsComponent = () => {
return <Button onClick={() => openAssetsModal()}>Customize App Assets</Button>;
};
function openAssetsModal() {
openModal(props => (
<ModalRoot {...props} size={ModalSize.MEDIUM}>
<ModalHeader>
<Text variant="heading-lg/semibold" style={{ flexGrow: 1 }}>
User Assets
</Text>
<ModalCloseButton onClick={props.onClose} />
</ModalHeader>
<ModalContent>
<div className="vcd-user-assets">
{CUSTOMIZABLE_ASSETS.map(asset => (
<Asset key={asset} asset={asset} />
))}
</div>
</ModalContent>
</ModalRoot>
));
}
function Asset({ asset }: { asset: UserAssetType }) {
// cache busting
const [version, setVersion] = useState(Date.now());
const onChooseAsset = (value?: null) => async () => {
const res = await VesktopNative.fileManager.chooseUserAsset(asset, value);
if (res === "ok") {
setVersion(Date.now());
} else if (res === "failed") {
showToast("Something went wrong. Please try again");
}
};
return (
<section>
<Text tag="h3" variant="text-md/semibold">
{wordsToTitle(wordsFromCamel(asset))}
</Text>
<div className="vcd-user-assets-asset">
<img className="vcd-user-assets-image" src={`vesktop://assets/${asset}?v=${version}`} alt="" />
<div className="vcd-user-assets-actions">
<Button onClick={onChooseAsset()}>Customize</Button>
<Button color={Button.Colors.PRIMARY} onClick={onChooseAsset(null)}>
Reset to default
</Button>
</div>
</div>
</section>
);
}

View File

@@ -57,7 +57,9 @@ export const enum IpcEvents {
IPC_COMMAND = "VCD_IPC_COMMAND",
DEVTOOLS_OPENED = "VCD_DEVTOOLS_OPENED",
DEVTOOLS_CLOSED = "VCD_DEVTOOLS_CLOSED"
DEVTOOLS_CLOSED = "VCD_DEVTOOLS_CLOSED",
CHOOSE_USER_ASSET = "VCD_CHOOSE_USER_ASSET"
}
export const enum IpcCommands {

View File

@@ -9,4 +9,3 @@ import { join } from "path";
export const STATIC_DIR = /* @__PURE__ */ join(__dirname, "..", "..", "static");
export const VIEW_DIR = /* @__PURE__ */ join(STATIC_DIR, "views");
export const BADGE_DIR = /* @__PURE__ */ join(STATIC_DIR, "badges");
export const ICON_PATH = /* @__PURE__ */ join(STATIC_DIR, "icon.png");

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

BIN
static/splash.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 117 KiB

BIN
static/tray.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

BIN
static/tray/tray.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

BIN
static/tray/trayUnread.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

View File

@@ -1,5 +1,5 @@
<head>
<title>About Vesktop</title>
<title>About Not-Nextop</title>
<link rel="stylesheet" href="./style.css" type="text/css" />
@@ -21,23 +21,17 @@
</head>
<body>
<h1 id="title">Vesktop v{{APP_VERSION}}</h1>
<p>Vesktop is a cross platform Discord Desktop client, aiming to give you a better Discord experience</p>
<h1 id="title">Not-Nextop v{{APP_VERSION}}</h1>
<p>Not-Nextop is a cross platform Discord Desktop client, aiming to give you a better Discord experience</p>
<section>
<h2>Links</h2>
<ul>
<li>
<a href="https://vesktop.vencord.dev/wiki" target="_blank">Vesktop Wiki</a>
<a href="https://github.com/Not-Nexulien/Not-Nextop" target="_blank">Source Code</a>
</li>
<li>
<a href="https://vencord.dev" target="_blank">Vencord Website</a>
</li>
<li>
<a href="https://github.com/Vencord/Vesktop" target="_blank">Source Code</a>
</li>
<li>
<a href="https://github.com/Vencord/Vesktop/issues" target="_blank">Report bugs / Request features</a>
<a href="https://github.com/Not-Nexulien/Not-Nextop/issues" target="_blank">Report bugs / Request features</a>
</li>
</ul>
</section>
@@ -45,7 +39,7 @@
<section>
<h2>License</h2>
<p>
Vesktop is licensed under the
Not-Nextop 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
@@ -55,7 +49,7 @@
<section>
<h2>Acknowledgements</h2>
<p>These awesome libraries empower Vesktop</p>
<p>These awesome libraries empower Not-Nextop</p>
<ul>
<li>
<a href="https://github.com/electron/electron" target="_blank">Electron</a>
@@ -76,11 +70,12 @@
</li>
<li>
And many
<a href="https://github.com/Vencord/Vesktop/blob/main/pnpm-lock.yaml" target="_blank"
<a href="https://github.com/Not-Nexulien/Not-Nextop/blob/main/pnpm-lock.yaml" target="_blank"
>more open source libraries</a
>
</li>
</ul>
<p>And thanks <a href="https://github.com/Vencord/Vesktop" target="_blank">Vesktop</a> for the original project.</p>
</section>
</body>

View File

@@ -36,19 +36,14 @@
img {
width: 128px;
height: 128px;
image-rendering: pixelated;
object-fit: contain;
}
</style>
</head>
<body>
<div class="wrapper">
<img
draggable="false"
src="../shiggy.gif"
alt="shiggy"
role="presentation"
/>
<img draggable="false" src="vesktop://assets/splash" alt="" role="presentation" />
<p>Loading Vesktop...</p>
<p class="message"></p>
</div>