merge unconflict
BIN
build/Assets.car
Normal file
BIN
build/icon.icns
BIN
build/icon.ico
Normal file
|
After Width: | Height: | Size: 364 KiB |
BIN
build/icon.png
|
Before Width: | Height: | Size: 9.5 KiB |
1
build/icon.svg
Normal 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 |
10
package.json
@@ -65,6 +65,7 @@
|
|||||||
"build": {
|
"build": {
|
||||||
"appId": "xyz.defautluser0.notnextop",
|
"appId": "xyz.defautluser0.notnextop",
|
||||||
"productName": "Not-Nextop",
|
"productName": "Not-Nextop",
|
||||||
|
"executableName": "not-nextop",
|
||||||
"files": [
|
"files": [
|
||||||
"!*",
|
"!*",
|
||||||
"!node_modules",
|
"!node_modules",
|
||||||
@@ -79,9 +80,10 @@
|
|||||||
"discord"
|
"discord"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"beforePack": "scripts/build/sandboxFix.js",
|
"beforePack": "scripts/build/beforePack.mjs",
|
||||||
|
"afterPack": "scripts/build/afterPack.mjs",
|
||||||
"linux": {
|
"linux": {
|
||||||
"icon": "build/icon.icns",
|
"icon": "build/icon.svg",
|
||||||
"category": "Network",
|
"category": "Network",
|
||||||
"maintainer": "jaegerwald.dev+nexop@gmail.com",
|
"maintainer": "jaegerwald.dev+nexop@gmail.com",
|
||||||
"target": [
|
"target": [
|
||||||
@@ -141,7 +143,8 @@
|
|||||||
"NSMicrophoneUsageDescription": "This app needs access to the microphone",
|
"NSMicrophoneUsageDescription": "This app needs access to the microphone",
|
||||||
"NSCameraUsageDescription": "This app needs access to the camera",
|
"NSCameraUsageDescription": "This app needs access to the camera",
|
||||||
"com.apple.security.device.audio-input": true,
|
"com.apple.security.device.audio-input": true,
|
||||||
"com.apple.security.device.camera": true
|
"com.apple.security.device.camera": true,
|
||||||
|
"CFBundleIconName": "Icon"
|
||||||
},
|
},
|
||||||
"notarize": true
|
"notarize": true
|
||||||
},
|
},
|
||||||
@@ -171,6 +174,7 @@
|
|||||||
"oneClick": false
|
"oneClick": false
|
||||||
},
|
},
|
||||||
"win": {
|
"win": {
|
||||||
|
"icon": "build/icon.ico",
|
||||||
"target": [
|
"target": [
|
||||||
{
|
{
|
||||||
"target": "nsis",
|
"target": "nsis",
|
||||||
|
|||||||
24
scripts/build/addAssetsCar.mjs
Normal 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`);
|
||||||
|
}
|
||||||
5
scripts/build/afterPack.mjs
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
import { addAssetsCar } from "./addAssetsCar.mjs";
|
||||||
|
|
||||||
|
export default async function afterPack(context) {
|
||||||
|
await addAssetsCar(context);
|
||||||
|
}
|
||||||
5
scripts/build/beforePack.mjs
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
import { applyAppImageSandboxFix } from "./sandboxFix.mjs";
|
||||||
|
|
||||||
|
export default async function beforePack() {
|
||||||
|
await applyAppImageSandboxFix();
|
||||||
|
}
|
||||||
@@ -6,18 +6,21 @@
|
|||||||
|
|
||||||
// Based on https://github.com/gergof/electron-builder-sandbox-fix/blob/master/lib/index.js
|
// Based on https://github.com/gergof/electron-builder-sandbox-fix/blob/master/lib/index.js
|
||||||
|
|
||||||
const fs = require("fs/promises");
|
import fs from "fs/promises";
|
||||||
const path = require("path");
|
import path from "path";
|
||||||
|
import AppImageTarget from "app-builder-lib/out/targets/AppImageTarget.js";
|
||||||
|
|
||||||
let isApplied = false;
|
let isApplied = false;
|
||||||
|
|
||||||
const hook = async () => {
|
export async function applyAppImageSandboxFix() {
|
||||||
if (isApplied) return;
|
|
||||||
isApplied = true;
|
|
||||||
if (process.platform !== "linux") {
|
if (process.platform !== "linux") {
|
||||||
// this fix is only required on linux
|
// this fix is only required on linux
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const AppImageTarget = require("app-builder-lib/out/targets/AppImageTarget");
|
|
||||||
|
if (isApplied) return;
|
||||||
|
isApplied = true;
|
||||||
|
|
||||||
const oldBuildMethod = AppImageTarget.default.prototype.build;
|
const oldBuildMethod = AppImageTarget.default.prototype.build;
|
||||||
AppImageTarget.default.prototype.build = async function (...args) {
|
AppImageTarget.default.prototype.build = async function (...args) {
|
||||||
console.log("Running AppImage builder hook", args);
|
console.log("Running AppImage builder hook", args);
|
||||||
@@ -69,6 +72,4 @@ exec "$SCRIPT_DIR/${this.packager.executableName}.bin" "$([ "$IS_STEAMOS" == 1 ]
|
|||||||
|
|
||||||
return ret;
|
return ret;
|
||||||
};
|
};
|
||||||
};
|
}
|
||||||
|
|
||||||
module.exports = hook;
|
|
||||||
@@ -6,7 +6,7 @@
|
|||||||
|
|
||||||
import { app, 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 { VIEW_DIR } from "shared/paths";
|
||||||
|
|
||||||
import { makeLinksOpenExternally } from "./utils/makeLinksOpenExternally";
|
import { makeLinksOpenExternally } from "./utils/makeLinksOpenExternally";
|
||||||
|
|
||||||
@@ -17,7 +17,6 @@ export async function createAboutWindow() {
|
|||||||
const about = new BrowserWindow({
|
const about = new BrowserWindow({
|
||||||
center: true,
|
center: true,
|
||||||
autoHideMenuBar: true,
|
autoHideMenuBar: true,
|
||||||
icon: ICON_PATH,
|
|
||||||
height,
|
height,
|
||||||
width
|
width
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -8,6 +8,8 @@ import { app, NativeImage, nativeImage } from "electron";
|
|||||||
import { join } from "path";
|
import { join } from "path";
|
||||||
import { BADGE_DIR } from "shared/paths";
|
import { BADGE_DIR } from "shared/paths";
|
||||||
|
|
||||||
|
import { AppEvents } from "./events";
|
||||||
|
|
||||||
const imgCache = new Map<number, NativeImage>();
|
const imgCache = new Map<number, NativeImage>();
|
||||||
function loadBadge(index: number) {
|
function loadBadge(index: number) {
|
||||||
const cached = imgCache.get(index);
|
const cached = imgCache.get(index);
|
||||||
@@ -21,7 +23,13 @@ function loadBadge(index: number) {
|
|||||||
|
|
||||||
let lastIndex: null | number = -1;
|
let lastIndex: null | number = -1;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* -1 = show unread indicator
|
||||||
|
* 0 = clear
|
||||||
|
*/
|
||||||
export function setBadgeCount(count: number) {
|
export function setBadgeCount(count: number) {
|
||||||
|
AppEvents.emit("setTrayVariant", count !== 0 ? "trayUnread" : "tray");
|
||||||
|
|
||||||
switch (process.platform) {
|
switch (process.platform) {
|
||||||
case "linux":
|
case "linux":
|
||||||
if (count === -1) count = 0;
|
if (count === -1) count = 0;
|
||||||
|
|||||||
15
src/main/events.ts
Normal 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"];
|
||||||
|
}>();
|
||||||
@@ -9,7 +9,7 @@ import { BrowserWindow } from "electron/main";
|
|||||||
import { copyFileSync, mkdirSync, readdirSync } from "fs";
|
import { copyFileSync, mkdirSync, readdirSync } from "fs";
|
||||||
import { join } from "path";
|
import { join } from "path";
|
||||||
import { SplashProps } from "shared/browserWinProperties";
|
import { SplashProps } from "shared/browserWinProperties";
|
||||||
import { ICON_PATH, VIEW_DIR } from "shared/paths";
|
import { VIEW_DIR } from "shared/paths";
|
||||||
|
|
||||||
import { autoStart } from "./autoStart";
|
import { autoStart } from "./autoStart";
|
||||||
import { DATA_DIR } from "./constants";
|
import { DATA_DIR } from "./constants";
|
||||||
@@ -32,8 +32,7 @@ export function createFirstLaunchTour() {
|
|||||||
frame: true,
|
frame: true,
|
||||||
autoHideMenuBar: true,
|
autoHideMenuBar: true,
|
||||||
height: 470,
|
height: 470,
|
||||||
width: 550,
|
width: 550
|
||||||
icon: ICON_PATH
|
|
||||||
});
|
});
|
||||||
|
|
||||||
makeLinksOpenExternally(win);
|
makeLinksOpenExternally(win);
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import "./ipc";
|
import "./ipc";
|
||||||
|
import "./userAssets";
|
||||||
|
|
||||||
import { app, BrowserWindow, nativeTheme } from "electron";
|
import { app, BrowserWindow, nativeTheme } from "electron";
|
||||||
import { autoUpdater } from "electron-updater";
|
import { autoUpdater } from "electron-updater";
|
||||||
@@ -15,6 +16,7 @@ import { createWindows, mainWin } from "./mainWindow";
|
|||||||
import { registerMediaPermissionsHandler } from "./mediaPermissions";
|
import { registerMediaPermissionsHandler } from "./mediaPermissions";
|
||||||
import { registerScreenShareHandler } from "./screenShare";
|
import { registerScreenShareHandler } from "./screenShare";
|
||||||
import { Settings, State } from "./settings";
|
import { Settings, State } from "./settings";
|
||||||
|
import { setAsDefaultProtocolClient } from "./utils/setAsDefaultProtocolClient";
|
||||||
import { isDeckGameMode } from "./utils/steamOS";
|
import { isDeckGameMode } from "./utils/steamOS";
|
||||||
|
|
||||||
if (!IS_DEV) {
|
if (!IS_DEV) {
|
||||||
@@ -31,7 +33,7 @@ const isLinux = process.platform === "linux";
|
|||||||
export let enableHardwareAcceleration = true;
|
export let enableHardwareAcceleration = true;
|
||||||
|
|
||||||
function init() {
|
function init() {
|
||||||
app.setAsDefaultProtocolClient("discord");
|
setAsDefaultProtocolClient("discord");
|
||||||
|
|
||||||
const { disableSmoothScroll, hardwareAcceleration, hardwareVideoAcceleration } = Settings.store;
|
const { disableSmoothScroll, hardwareAcceleration, hardwareVideoAcceleration } = Settings.store;
|
||||||
|
|
||||||
|
|||||||
@@ -1,52 +1,44 @@
|
|||||||
/*
|
/*
|
||||||
|
|
||||||
* Vesktop, a desktop app aiming to give you a snappier Discord Experience
|
* 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
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {
|
import {
|
||||||
app,
|
app,
|
||||||
BrowserWindow,
|
BrowserWindow,
|
||||||
BrowserWindowConstructorOptions,
|
BrowserWindowConstructorOptions,
|
||||||
dialog,
|
|
||||||
Menu,
|
Menu,
|
||||||
MenuItemConstructorOptions,
|
MenuItemConstructorOptions,
|
||||||
nativeTheme,
|
nativeTheme,
|
||||||
screen,
|
screen,
|
||||||
session,
|
session
|
||||||
Tray
|
|
||||||
} from "electron";
|
} from "electron";
|
||||||
import { EventEmitter } from "events";
|
|
||||||
import { rm } from "fs/promises";
|
|
||||||
import { join } from "path";
|
import { join } from "path";
|
||||||
import { IpcCommands, IpcEvents } from "shared/IpcEvents";
|
import { IpcCommands, IpcEvents } from "shared/IpcEvents";
|
||||||
import { isTruthy } from "shared/utils/guards";
|
import { isTruthy } from "shared/utils/guards";
|
||||||
import { once } from "shared/utils/once";
|
import { once } from "shared/utils/once";
|
||||||
import type { SettingsStore } from "shared/utils/SettingsStore";
|
import type { SettingsStore } from "shared/utils/SettingsStore";
|
||||||
|
|
||||||
import { ICON_PATH } from "../shared/paths";
|
|
||||||
import { createAboutWindow } from "./about";
|
import { createAboutWindow } from "./about";
|
||||||
import { initArRPC } from "./arrpc";
|
import { initArRPC } from "./arrpc";
|
||||||
import {
|
import { BrowserUserAgent, DEFAULT_HEIGHT, DEFAULT_WIDTH, MIN_HEIGHT, MIN_WIDTH, VENCORD_FILES_DIR } from "./constants";
|
||||||
BrowserUserAgent,
|
import { AppEvents } from "./events";
|
||||||
DATA_DIR,
|
|
||||||
DEFAULT_HEIGHT,
|
|
||||||
DEFAULT_WIDTH,
|
|
||||||
MessageBoxChoice,
|
|
||||||
MIN_HEIGHT,
|
|
||||||
MIN_WIDTH,
|
|
||||||
VENCORD_FILES_DIR
|
|
||||||
} from "./constants";
|
|
||||||
import { darwinURL } from "./index";
|
import { darwinURL } from "./index";
|
||||||
import { sendRendererCommand } from "./ipcCommands";
|
import { sendRendererCommand } from "./ipcCommands";
|
||||||
import { Settings, State, VencordSettings } from "./settings";
|
import { Settings, State, VencordSettings } from "./settings";
|
||||||
import { createSplashWindow, updateSplashMessage } from "./splash";
|
import { createSplashWindow, updateSplashMessage } from "./splash";
|
||||||
|
import { destroyTray, initTray } from "./tray";
|
||||||
|
import { clearData } from "./utils/clearData";
|
||||||
import { makeLinksOpenExternally } from "./utils/makeLinksOpenExternally";
|
import { makeLinksOpenExternally } from "./utils/makeLinksOpenExternally";
|
||||||
import { applyDeckKeyboardFix, askToApplySteamLayout, isDeckGameMode } from "./utils/steamOS";
|
import { applyDeckKeyboardFix, askToApplySteamLayout, isDeckGameMode } from "./utils/steamOS";
|
||||||
import { downloadVencordFiles, ensureVencordFiles } from "./utils/vencordLoader";
|
import { downloadVencordFiles, ensureVencordFiles } from "./utils/vencordLoader";
|
||||||
|
|
||||||
let isQuitting = false;
|
let isQuitting = false;
|
||||||
let tray: Tray;
|
|
||||||
|
|
||||||
applyDeckKeyboardFix();
|
applyDeckKeyboardFix();
|
||||||
|
|
||||||
@@ -77,84 +69,6 @@ function makeSettingsListenerHelpers<O extends object>(o: SettingsStore<O>) {
|
|||||||
const [addSettingsListener, removeSettingsListeners] = makeSettingsListenerHelpers(Settings);
|
const [addSettingsListener, removeSettingsListeners] = makeSettingsListenerHelpers(Settings);
|
||||||
const [addVencordSettingsListener, removeVencordSettingsListeners] = makeSettingsListenerHelpers(VencordSettings);
|
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 Not-Nexulien",
|
|
||||||
async click() {
|
|
||||||
await downloadVencordFiles();
|
|
||||||
app.relaunch();
|
|
||||||
app.quit();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "Reset Not-Nextop",
|
|
||||||
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("Not-Nextop");
|
|
||||||
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 Not-Nextop?",
|
|
||||||
detail: "This will log you out, clear caches and reset all your settings!\n\nNot-Nextop 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>;
|
type MenuItemList = Array<MenuItemConstructorOptions | false>;
|
||||||
|
|
||||||
function initMenuBar(win: BrowserWindow) {
|
function initMenuBar(win: BrowserWindow) {
|
||||||
@@ -333,8 +247,8 @@ function initWindowBoundsListeners(win: BrowserWindow) {
|
|||||||
|
|
||||||
function initSettingsListeners(win: BrowserWindow) {
|
function initSettingsListeners(win: BrowserWindow) {
|
||||||
addSettingsListener("tray", enable => {
|
addSettingsListener("tray", enable => {
|
||||||
if (enable) initTray(win);
|
if (enable) initTray(win, q => (isQuitting = q));
|
||||||
else tray?.destroy();
|
else destroyTray();
|
||||||
});
|
});
|
||||||
|
|
||||||
addSettingsListener("disableMinSize", disable => {
|
addSettingsListener("disableMinSize", disable => {
|
||||||
@@ -439,7 +353,6 @@ function createMainWindow() {
|
|||||||
// disable renderer backgrounding to prevent the app from unloading when in the background
|
// disable renderer backgrounding to prevent the app from unloading when in the background
|
||||||
backgroundThrottling: false
|
backgroundThrottling: false
|
||||||
},
|
},
|
||||||
icon: ICON_PATH,
|
|
||||||
frame: !noFrame,
|
frame: !noFrame,
|
||||||
...(transparent && {
|
...(transparent && {
|
||||||
transparent: true,
|
transparent: true,
|
||||||
@@ -477,7 +390,9 @@ function createMainWindow() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
initWindowBoundsListeners(win);
|
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);
|
initMenuBar(win);
|
||||||
makeLinksOpenExternally(win);
|
makeLinksOpenExternally(win);
|
||||||
initSettingsListeners(win);
|
initSettingsListeners(win);
|
||||||
@@ -497,8 +412,6 @@ function createMainWindow() {
|
|||||||
|
|
||||||
const runVencordMain = once(() => require(join(VENCORD_FILES_DIR, "vencordDesktopMain.js")));
|
const runVencordMain = once(() => require(join(VENCORD_FILES_DIR, "vencordDesktopMain.js")));
|
||||||
|
|
||||||
const loadEvents = new EventEmitter();
|
|
||||||
|
|
||||||
export function loadUrl(uri: string | undefined) {
|
export function loadUrl(uri: string | undefined) {
|
||||||
const branch = Settings.store.discordBranch;
|
const branch = Settings.store.discordBranch;
|
||||||
const subdomain = branch === "canary" || branch === "ptb" ? `${branch}.` : "";
|
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
|
// we do not rely on 'did-finish-load' because it fires even if loadURL fails which triggers early detruction of the splash
|
||||||
mainWin
|
mainWin
|
||||||
.loadURL(`https://${subdomain}discord.com/${uri ? new URL(uri).pathname.slice(1) || "app" : "app"}`)
|
.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));
|
.catch(error => retryUrl(error.url, error.code));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -533,7 +446,7 @@ export async function createWindows() {
|
|||||||
|
|
||||||
mainWin = createMainWindow();
|
mainWin = createMainWindow();
|
||||||
|
|
||||||
loadEvents.on("app-loaded", () => {
|
AppEvents.on("appLoaded", () => {
|
||||||
splash?.destroy();
|
splash?.destroy();
|
||||||
|
|
||||||
if (!startMinimized) {
|
if (!startMinimized) {
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
import { BrowserWindow } from "electron";
|
import { BrowserWindow } from "electron";
|
||||||
import { join } from "path";
|
import { join } from "path";
|
||||||
import { SplashProps } from "shared/browserWinProperties";
|
import { SplashProps } from "shared/browserWinProperties";
|
||||||
import { ICON_PATH, VIEW_DIR } from "shared/paths";
|
import { VIEW_DIR } from "shared/paths";
|
||||||
|
|
||||||
import { Settings } from "./settings";
|
import { Settings } from "./settings";
|
||||||
|
|
||||||
@@ -16,7 +16,6 @@ let splash: BrowserWindow | undefined;
|
|||||||
export function createSplashWindow(startMinimized = false) {
|
export function createSplashWindow(startMinimized = false) {
|
||||||
splash = new BrowserWindow({
|
splash = new BrowserWindow({
|
||||||
...SplashProps,
|
...SplashProps,
|
||||||
icon: ICON_PATH,
|
|
||||||
show: !startMinimized,
|
show: !startMinimized,
|
||||||
webPreferences: {
|
webPreferences: {
|
||||||
preload: join(__dirname, "splashPreload.js")
|
preload: join(__dirname, "splashPreload.js")
|
||||||
|
|||||||
92
src/main/tray.ts
Normal 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
@@ -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";
|
||||||
|
}
|
||||||
|
});
|
||||||
32
src/main/utils/clearData.ts
Normal 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();
|
||||||
|
}
|
||||||
13
src/main/utils/fileExists.ts
Normal 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);
|
||||||
|
}
|
||||||
29
src/main/utils/setAsDefaultProtocolClient.ts
Normal 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);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -42,7 +42,9 @@ export const VesktopNative = {
|
|||||||
fileManager: {
|
fileManager: {
|
||||||
showItemInFolder: (path: string) => invoke<void>(IpcEvents.SHOW_ITEM_IN_FOLDER, path),
|
showItemInFolder: (path: string) => invoke<void>(IpcEvents.SHOW_ITEM_IN_FOLDER, path),
|
||||||
getVencordDir: () => sendSync<string | undefined>(IpcEvents.GET_VENCORD_DIR),
|
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: {
|
settings: {
|
||||||
get: () => sendSync<Settings>(IpcEvents.GET_SETTINGS),
|
get: () => sendSync<Settings>(IpcEvents.GET_SETTINGS),
|
||||||
|
|||||||
@@ -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 { UserAssetsButton } from "./UserAssets";
|
||||||
import { VesktopSettingsSwitch } from "./VesktopSettingsSwitch";
|
import { VesktopSettingsSwitch } from "./VesktopSettingsSwitch";
|
||||||
import { WindowsTransparencyControls } from "./WindowsTransparencyControls";
|
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",
|
description: "Adapt the splash window colors to your custom theme",
|
||||||
defaultValue: true
|
defaultValue: true
|
||||||
},
|
},
|
||||||
WindowsTransparencyControls
|
WindowsTransparencyControls,
|
||||||
|
UserAssetsButton
|
||||||
],
|
],
|
||||||
Behaviour: [
|
Behaviour: [
|
||||||
{
|
{
|
||||||
|
|||||||
25
src/renderer/components/settings/UserAssets.css
Normal 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;
|
||||||
|
}
|
||||||
80
src/renderer/components/settings/UserAssets.tsx
Normal 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>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -57,7 +57,9 @@ export const enum IpcEvents {
|
|||||||
IPC_COMMAND = "VCD_IPC_COMMAND",
|
IPC_COMMAND = "VCD_IPC_COMMAND",
|
||||||
|
|
||||||
DEVTOOLS_OPENED = "VCD_DEVTOOLS_OPENED",
|
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 {
|
export const enum IpcCommands {
|
||||||
|
|||||||
@@ -9,4 +9,3 @@ import { join } from "path";
|
|||||||
export const STATIC_DIR = /* @__PURE__ */ join(__dirname, "..", "..", "static");
|
export const STATIC_DIR = /* @__PURE__ */ join(__dirname, "..", "..", "static");
|
||||||
export const VIEW_DIR = /* @__PURE__ */ join(STATIC_DIR, "views");
|
export const VIEW_DIR = /* @__PURE__ */ join(STATIC_DIR, "views");
|
||||||
export const BADGE_DIR = /* @__PURE__ */ join(STATIC_DIR, "badges");
|
export const BADGE_DIR = /* @__PURE__ */ join(STATIC_DIR, "badges");
|
||||||
export const ICON_PATH = /* @__PURE__ */ join(STATIC_DIR, "icon.png");
|
|
||||||
|
|||||||
BIN
static/icon.ico
|
Before Width: | Height: | Size: 12 KiB |
BIN
static/icon.png
|
Before Width: | Height: | Size: 9.0 KiB |
|
Before Width: | Height: | Size: 16 KiB |
BIN
static/splash.webp
Normal file
|
After Width: | Height: | Size: 117 KiB |
BIN
static/tray.png
Normal file
|
After Width: | Height: | Size: 3.5 KiB |
BIN
static/tray/tray.png
Normal file
|
After Width: | Height: | Size: 3.5 KiB |
BIN
static/tray/trayTemplate.png
Normal file
|
After Width: | Height: | Size: 1.4 KiB |
BIN
static/tray/trayUnread.png
Normal file
|
After Width: | Height: | Size: 3.9 KiB |
@@ -36,19 +36,14 @@
|
|||||||
img {
|
img {
|
||||||
width: 128px;
|
width: 128px;
|
||||||
height: 128px;
|
height: 128px;
|
||||||
image-rendering: pixelated;
|
object-fit: contain;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
<div class="wrapper">
|
<div class="wrapper">
|
||||||
<img
|
<img draggable="false" src="vesktop://assets/splash" alt="" role="presentation" />
|
||||||
draggable="false"
|
|
||||||
src="../shiggy.gif"
|
|
||||||
alt="shiggy"
|
|
||||||
role="presentation"
|
|
||||||
/>
|
|
||||||
<p>Loading Vesktop...</p>
|
<p>Loading Vesktop...</p>
|
||||||
<p class="message"></p>
|
<p class="message"></p>
|
||||||
</div>
|
</div>
|
||||||
@@ -61,4 +56,4 @@
|
|||||||
messageElement.textContent = message;
|
messageElement.textContent = message;
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||