13 Commits

Author SHA1 Message Date
Vendicated
9193ed58c9 resize splash image to 128x128 2025-10-28 17:32:54 +01:00
khcrysalis
38b7716b2f add new splash animation (#1195) 2025-10-28 16:26:27 +00:00
Vendicated
4b735b9739 bump to v1.6.1 2025-10-28 17:16:27 +01:00
Vendicated
707dbf4f75 upgrade electron to v39 2025-10-28 16:25:38 +01:00
Vendicated
a242d5d694 bound check entire window area instead of only top left corner
Co-Authored-By: MBStarup <mavi.nexu@gmail.com>
2025-10-23 21:17:17 +02:00
Vendicated
b75ed0766d clean up BrowserWindow options logic 2025-10-23 18:56:33 +02:00
Vendicated
03aa30dfc6 do window bound checks without relying on displayId
Fixes Vesktop not remembering its position correctly.
Seems like the display ids aren't consistent on some windows systems...
2025-10-23 18:17:04 +02:00
Vendicated
cd1a40e6c7 oh god no 2025-10-23 02:55:22 +02:00
Vendicated
3aa0bb806e fix some ENOENT errors on first launch 2025-10-23 02:53:13 +02:00
Vendicated
800a97105c fix metainfo generation 2025-10-22 16:30:20 +02:00
Vendicated
b02acd6a7b fix updater printing on --help / --version commands 2025-10-21 21:16:24 +02:00
Vendicated
d293b166fe fix cli argument parsing 2025-10-21 19:56:22 +02:00
Vendicated
28a13be709 add nearest neighbor option to splash customisation 2025-10-20 01:56:04 +02:00
13 changed files with 457 additions and 396 deletions

View File

@@ -1,6 +1,6 @@
{
"name": "vesktop",
"version": "1.6.0",
"version": "1.6.1",
"private": true,
"description": "Vesktop is a custom Discord desktop app",
"keywords": [],
@@ -35,28 +35,28 @@
},
"devDependencies": {
"@fal-works/esbuild-plugin-global-externals": "^2.1.2",
"@stylistic/eslint-plugin": "^5.4.0",
"@types/node": "^24.7.0",
"@stylistic/eslint-plugin": "^5.5.0",
"@types/node": "^24.9.1",
"@types/react": "19.2.1",
"@vencord/types": "^1.13.2",
"dotenv": "^17.2.3",
"electron": "^38.0.0",
"electron": "^39.0.0",
"electron-builder": "^26.0.12",
"esbuild": "^0.25.10",
"eslint": "^9.37.0",
"esbuild": "^0.25.11",
"eslint": "^9.38.0",
"eslint-import-resolver-alias": "^1.1.2",
"eslint-plugin-path-alias": "^2.1.0",
"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.2.0",
"eslint-plugin-unused-imports": "^4.3.0",
"libvesktop": "link:packages/libvesktop",
"prettier": "^3.6.2",
"source-map-support": "^0.5.21",
"tsx": "^4.20.6",
"type-fest": "^5.0.1",
"type-fest": "^5.1.0",
"typescript": "^5.9.3",
"typescript-eslint": "^8.46.0",
"typescript-eslint": "^8.46.2",
"xml-formatter": "^3.6.7"
},
"packageManager": "pnpm@10.7.1",

588
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -6,7 +6,9 @@
import "./UserAssets.css";
import { FormSwitch } from "@vencord/types/components";
import {
Margins,
ModalCloseButton,
ModalContent,
ModalHeader,
@@ -18,6 +20,7 @@ import {
} from "@vencord/types/utils";
import { Button, showToast, Text, useState } from "@vencord/types/webpack/common";
import { UserAssetType } from "main/userAssets";
import { useSettings } from "renderer/settings";
import { SettingsComponent } from "./Settings";
@@ -51,11 +54,18 @@ function openAssetsModal() {
function Asset({ asset }: { asset: UserAssetType }) {
// cache busting
const [version, setVersion] = useState(Date.now());
const settings = useSettings();
const isSplash = asset === "splash";
const imageRendering = isSplash && settings.splashPixelated ? "pixelated" : "auto";
const onChooseAsset = (value?: null) => async () => {
const res = await VesktopNative.fileManager.chooseUserAsset(asset, value);
if (res === "ok") {
setVersion(Date.now());
if (isSplash && value === null) {
settings.splashPixelated = false;
}
} else if (res === "failed") {
showToast("Something went wrong. Please try again");
}
@@ -67,12 +77,28 @@ function Asset({ asset }: { asset: UserAssetType }) {
{wordsToTitle(wordsFromCamel(asset))}
</Text>
<div className="vcd-user-assets-asset">
<img className="vcd-user-assets-image" src={`vesktop://assets/${asset}?v=${version}`} alt="" />
<img
className="vcd-user-assets-image"
src={`vesktop://assets/${asset}?v=${version}`}
alt=""
style={{ imageRendering }}
/>
<div className="vcd-user-assets-actions">
<Button onClick={onChooseAsset()}>Customize</Button>
<Button color={Button.Colors.PRIMARY} onClick={onChooseAsset(null)}>
Reset to default
</Button>
<div className="vcd-user-assets-buttons">
<Button onClick={onChooseAsset()}>Customize</Button>
<Button color={Button.Colors.PRIMARY} onClick={onChooseAsset(null)}>
Reset to default
</Button>
</div>
{isSplash && (
<FormSwitch
title="Nearest-Neighbor Scaling (for pixel art)"
value={settings.splashPixelated ?? false}
onChange={val => (settings.splashPixelated = val)}
className={Margins.top16}
hideBorder
/>
)}
</div>
</div>
</section>

View File

@@ -28,6 +28,7 @@ export interface Settings {
splashTheming?: boolean;
splashColor?: string;
splashBackground?: string;
splashPixelated?: boolean;
spellCheckLanguages?: string[];
@@ -50,7 +51,6 @@ export interface State {
maximized?: boolean;
minimized?: boolean;
windowBounds?: Rectangle;
displayId: number;
firstLaunch?: boolean;

Binary file not shown.

Before

Width:  |  Height:  |  Size: 117 KiB

After

Width:  |  Height:  |  Size: 145 KiB