3 Commits

Author SHA1 Message Date
Lewis Crichton
bb09596485 chore: prep for flatpak 2023-12-09 23:09:54 +00:00
Lewis Crichton
894ec4b902 chore: lint 2023-12-09 23:01:35 +00:00
Lewis Crichton
dd44602730 fix: make minimize only run on autostart 2023-12-09 23:00:47 +00:00
33 changed files with 1049 additions and 1008 deletions

View File

@@ -22,13 +22,7 @@
"eqeqeq": ["error", "always", { "null": "ignore" }],
"spaced-comment": ["error", "always", { "markers": ["!"] }],
"yoda": "error",
"prefer-destructuring": [
"error",
{
"VariableDeclarator": { "array": false, "object": true },
"AssignmentExpression": { "array": false, "object": false }
}
],
"prefer-destructuring": ["error", { "object": true, "array": false }],
"operator-assignment": ["error", "always"],
"no-useless-computed-key": "error",
"no-unneeded-ternary": ["error", { "defaultAssignment": false }],

View File

@@ -1,32 +0,0 @@
---
name: Bug report
about: Create a report to help us improve
title: ''
labels: bug
assignees: ''
---
**Describe the bug**
A clear and concise description of what the bug is.
**To Reproduce**
Steps to reproduce the behavior:
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
4. See error
**Expected behavior**
A clear and concise description of what you expected to happen.
**Screenshots**
If applicable, add screenshots to help explain your problem.
**Desktop (please complete the following information):**
- OS/Distro: [e.g. Windows / Fedora Linux / MacOs]
- Desktop Environment (linux only): [e.g. gnome, kde, sway]
- Version: [e.g. 22]
**Additional context**
Add any other context about the problem here.

View File

@@ -1,10 +0,0 @@
---
name: Custom issue template
about: Describe this issue template's purpose here.
title: ''
labels: ''
assignees: ''
---

View File

@@ -1,20 +0,0 @@
---
name: Feature request
about: Suggest an idea for this project
title: ''
labels: enhancement
assignees: ''
---
**Is your feature request related to a problem? Please describe.**
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
**Describe the solution you'd like**
A clear and concise description of what you want to happen.
**Describe alternatives you've considered**
A clear and concise description of any alternative solutions or features you've considered.
**Additional context**
Add any other context or screenshots about the feature request here.

View File

@@ -37,16 +37,7 @@ jobs:
run: pnpm build
- name: Run Electron Builder
if: ${{ matrix.platform != 'mac' }}
run: |
pnpm electron-builder --${{ matrix.platform }} --publish always
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Run Electron Builder
if: ${{ matrix.platform == 'mac' }}
run: |
pnpm electron-builder --${{ matrix.platform }} --publish always
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
CSC_LINK_: ${{ secrets.APPLE_SIGNING_CERT }}

View File

@@ -3,14 +3,14 @@
Vesktop is a cross platform desktop app aiming to give you a snappier Discord experience with [Vencord](https://github.com/Vendicated/Vencord) pre-installed
**Not yet supported**:
- Global Keybinds
- Global Keybinds
Bug reports, feature requests & contributions are highly appreciated!!
![](https://github.com/Vencord/Vesktop/assets/45497981/8608a899-96a9-4027-9725-2cb02ba189fd)
![grafik](https://github.com/Vencord/Vesktop/assets/45497981/8701e5de-52c4-4346-a990-719cb971642e)
## Installing
### Windows
@@ -39,7 +39,7 @@ Download Vesktop-VERSION.rpm from [releases](https://github.com/Vencord/Vesktop/
#### Other
Either download Vesktop-VERSION.AppImage and just run it directly or grab Vesktop-VERSION.tar.gz, extract it somewhere and run `vesktop`.
Either download Vesktop-VERSION.AppImage and just run it directly or grab Vesktop-VERSION.tar.gz, extract it somewhere and run `vencorddesktop`.
If other packages are created, feel free to open an issue and we'll link them here.
@@ -68,3 +68,7 @@ pnpm package:dir
## Motivation
The official Discord Desktop app is very resource heavy compared to Discord in your Browser. There are multiple alternative Electron apps (ArmCord, WebCord, probably more) that prove how much of a performance gain you can gain by using a custom app. ArmCord already supports Vencord but makes it pretty limited for us. Making our own standalone app gives us much more control.
This is just a random idea I (V) got, and might not actually ever be finished heh
Gluon also seems very attractive for this because of how lightweight it can be and because unlike electron, streaming just works out of the box like in any chromium browser. However, at the time of writing this, it still lacks some features necessary to make it work (synchronous ipc or a way to get node process variables into the onLoad function for instance, plus onLoad seems to load a little too late sometimes)

View File

@@ -1,8 +1,8 @@
!macro preInit
SetRegView 64
WriteRegExpandStr HKLM "${INSTALL_REGISTRY_KEY}" InstallLocation "$LocalAppData\vesktop"
WriteRegExpandStr HKCU "${INSTALL_REGISTRY_KEY}" InstallLocation "$LocalAppData\vesktop"
WriteRegExpandStr HKLM "${INSTALL_REGISTRY_KEY}" InstallLocation "$LocalAppData\VencordDesktop"
WriteRegExpandStr HKCU "${INSTALL_REGISTRY_KEY}" InstallLocation "$LocalAppData\VencordDesktop"
SetRegView 32
WriteRegExpandStr HKLM "${INSTALL_REGISTRY_KEY}" InstallLocation "$LocalAppData\vesktop"
WriteRegExpandStr HKCU "${INSTALL_REGISTRY_KEY}" InstallLocation "$LocalAppData\vesktop"
WriteRegExpandStr HKLM "${INSTALL_REGISTRY_KEY}" InstallLocation "$LocalAppData\VencordDesktop"
WriteRegExpandStr HKCU "${INSTALL_REGISTRY_KEY}" InstallLocation "$LocalAppData\VencordDesktop"
!macroend

View File

@@ -1,6 +1,6 @@
{
"name": "vesktop",
"version": "1.5.0",
"name": "VencordDesktop",
"version": "0.4.4",
"private": true,
"description": "",
"keywords": [],
@@ -24,35 +24,35 @@
"updateMeta": "tsx scripts/utils/updateMeta.mts"
},
"dependencies": {
"arrpc": "github:OpenAsar/arrpc#98879cae0565e6fce34e4cb6f544bf42c6a7e7c8"
"arrpc": "github:OpenAsar/arrpc#3e22fd776273afaa4a80c51deb86077ffdd4d2ae"
},
"optionalDependencies": {
"@vencord/venmic": "^3.2.3"
"@vencord/venmic": "^2.1.3"
},
"devDependencies": {
"@fal-works/esbuild-plugin-global-externals": "^2.1.2",
"@types/node": "^20.11.2",
"@types/react": "^18.2.48",
"@typescript-eslint/eslint-plugin": "^6.19.0",
"@typescript-eslint/parser": "^6.19.0",
"@types/node": "^20.10.0",
"@types/react": "^18.2.39",
"@typescript-eslint/eslint-plugin": "^6.13.1",
"@typescript-eslint/parser": "^6.13.1",
"@vencord/types": "^0.1.2",
"dotenv": "^16.3.1",
"electron": "^28.1.3",
"electron": "^27.1.2",
"electron-builder": "^24.9.1",
"esbuild": "^0.19.11",
"eslint": "^8.56.0",
"eslint-config-prettier": "^9.1.0",
"esbuild": "^0.19.8",
"eslint": "^8.54.0",
"eslint-config-prettier": "^9.0.0",
"eslint-import-resolver-alias": "^1.1.2",
"eslint-plugin-license-header": "^0.6.0",
"eslint-plugin-path-alias": "^1.0.0",
"eslint-plugin-prettier": "^5.1.3",
"eslint-plugin-prettier": "^5.0.1",
"eslint-plugin-simple-import-sort": "^10.0.0",
"eslint-plugin-unused-imports": "^3.0.0",
"prettier": "^3.2.2",
"prettier": "^3.1.0",
"source-map-support": "^0.5.21",
"tsx": "^4.7.0",
"type-fest": "^4.9.0",
"typescript": "^5.3.3",
"tsx": "^4.6.0",
"type-fest": "^4.8.2",
"typescript": "^5.3.2",
"xml-formatter": "^3.6.0"
},
"packageManager": "pnpm@8.11.0",
@@ -61,7 +61,7 @@
"pnpm": ">=8"
},
"build": {
"appId": "dev.vencord.vesktop",
"appId": "dev.vencord.desktop",
"productName": "Vesktop",
"files": [
"!*",
@@ -110,7 +110,8 @@
"GenericName": "Internet Messenger",
"Type": "Application",
"Categories": "Network;InstantMessaging;Chat;",
"Keywords": "discord;vencord;electron;chat;"
"Keywords": "discord;vencord;electron;chat;",
"StartupWMClass": "VencordDesktop"
}
},
"mac": {

1365
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@@ -10,38 +10,51 @@ import { join } from "path";
interface AutoStart {
isEnabled(): boolean;
wasAutoStarted(): boolean;
enable(): void;
disable(): void;
}
const isFlatpak = process.env.FLATPAK_ID !== undefined;
function makeAutoStartLinux(): AutoStart {
const configDir = process.env.XDG_CONFIG_HOME || join(process.env.HOME!, ".config");
const dir = join(configDir, "autostart");
const file = join(dir, "vencord.desktop");
return {
isEnabled: () => existsSync(file),
isEnabled: () => existsSync(file), // TODO: flatpak
wasAutoStarted: () => process.argv.includes("--autostart"),
enable() {
const desktopFile = `
if (isFlatpak) {
} else {
const desktopFile = `
[Desktop Entry]
Type=Application
Version=1.0
Name=Vencord
Comment=Vencord autostart script
Exec=${process.execPath}
Exec=${process.execPath} --autostart
Terminal=false
StartupNotify=false
`.trim();
mkdirSync(dir, { recursive: true });
writeFileSync(file, desktopFile);
mkdirSync(dir, { recursive: true });
writeFileSync(file, desktopFile);
}
},
disable: () => rmSync(file, { force: true })
disable: () => {
if (isFlatpak) {
} else {
rmSync(file, { force: true });
}
}
};
}
const autoStartWindowsMac: AutoStart = {
isEnabled: () => app.getLoginItemSettings().openAtLogin,
wasAutoStarted: () => app.getLoginItemSettings().wasOpenedAtLogin,
enable: () => app.setLoginItemSettings({ openAtLogin: true }),
disable: () => app.setLoginItemSettings({ openAtLogin: false })
};

View File

@@ -5,29 +5,9 @@
*/
import { app } from "electron";
import { existsSync, readdirSync, renameSync, rmdirSync } from "fs";
import { join } from "path";
const LEGACY_DATA_DIR = join(app.getPath("appData"), "VencordDesktop", "VencordDesktop");
export const DATA_DIR = process.env.VENCORD_USER_DATA_DIR || join(app.getPath("userData"));
// TODO: remove eventually
if (existsSync(LEGACY_DATA_DIR)) {
try {
console.warn("Detected legacy settings dir", LEGACY_DATA_DIR + ".", "migrating to", DATA_DIR);
for (const file of readdirSync(LEGACY_DATA_DIR)) {
renameSync(join(LEGACY_DATA_DIR, file), join(DATA_DIR, file));
}
rmdirSync(LEGACY_DATA_DIR);
renameSync(
join(app.getPath("appData"), "VencordDesktop", "IndexedDB"),
join(DATA_DIR, "sessionData", "IndexedDB")
);
} catch (e) {
console.error("Migration failed", e);
}
}
app.setPath("sessionData", join(DATA_DIR, "sessionData"));
export const DATA_DIR = process.env.VENCORD_USER_DATA_DIR || join(app.getPath("userData"), "VencordDesktop");
export const VENCORD_SETTINGS_DIR = join(DATA_DIR, "settings");
export const VENCORD_QUICKCSS_FILE = join(VENCORD_SETTINGS_DIR, "quickCss.css");
export const VENCORD_SETTINGS_FILE = join(VENCORD_SETTINGS_DIR, "settings.json");
@@ -46,13 +26,11 @@ export const MIN_HEIGHT = 500;
export const DEFAULT_WIDTH = 1280;
export const DEFAULT_HEIGHT = 720;
export const DISCORD_HOSTNAMES = ["discord.com", "canary.discord.com", "ptb.discord.com"];
const UserAgents = {
darwin: "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
linux: "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
darwin: "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/118.0.0.0 Safari/537.36",
linux: "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/118.0.0.0 Safari/537.36",
windows:
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36"
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/118.0.0.0 Safari/537.36"
};
export const UserAgent = UserAgents[process.platform] || UserAgents.windows;

View File

@@ -14,7 +14,7 @@ import { ICON_PATH, VIEW_DIR } from "shared/paths";
import { autoStart } from "./autoStart";
import { DATA_DIR } from "./constants";
import { createWindows } from "./mainWindow";
import { Settings, State } from "./settings";
import { Settings } from "./settings";
import { makeLinksOpenExternally } from "./utils/makeLinksOpenExternally";
interface Data {
@@ -44,9 +44,9 @@ export function createFirstLaunchTour() {
if (!msg.startsWith("form:")) return;
const data = JSON.parse(msg.slice(5)) as Data;
State.store.firstLaunch = false;
Settings.store.minimizeToTray = data.minimizeToTray;
Settings.store.discordBranch = data.discordBranch;
Settings.store.firstLaunch = false;
Settings.store.arRPC = data.richPresence;
if (data.autoStart) autoStart.enable();

View File

@@ -6,7 +6,7 @@
import "./ipc";
import { app, BrowserWindow, nativeTheme } from "electron";
import { app, BrowserWindow } from "electron";
import { checkUpdates } from "updater/main";
import { DATA_DIR } from "./constants";
@@ -14,8 +14,7 @@ import { createFirstLaunchTour } from "./firstLaunch";
import { createWindows, mainWin } from "./mainWindow";
import { registerMediaPermissionsHandler } from "./mediaPermissions";
import { registerScreenShareHandler } from "./screenShare";
import { Settings, State } from "./settings";
import { isDeckGameMode } from "./utils/steamOS";
import { Settings } from "./settings";
if (IS_DEV) {
require("source-map-support").install();
@@ -25,9 +24,8 @@ if (IS_DEV) {
process.env.VENCORD_USER_DATA_DIR = DATA_DIR;
function init() {
const { disableSmoothScroll, hardwareAcceleration } = Settings.store;
const { disableSmoothScroll } = Settings.store;
if (hardwareAcceleration === false) app.disableHardwareAcceleration();
if (disableSmoothScroll) {
app.commandLine.appendSwitch("disable-smooth-scrolling");
}
@@ -44,9 +42,6 @@ function init() {
"WinRetrieveSuggestionsOnlyOnDemand,HardwareMediaKeyHandling,MediaSessionService,WidgetLayering"
);
// In the Flatpak on SteamOS the theme is detected as light, but SteamOS only has a dark mode, so we just override it
if (isDeckGameMode) nativeTheme.themeSource = "dark";
app.on("second-instance", (_event, _cmdLine, _cwd, data: any) => {
if (data.IS_DEV) app.quit();
else if (mainWin) {
@@ -58,7 +53,7 @@ function init() {
app.whenReady().then(async () => {
checkUpdates();
if (process.platform === "win32") app.setAppUserModelId("dev.vencord.vesktop");
if (process.platform === "win32") app.setAppUserModelId("dev.vencord.desktop");
registerScreenShareHandler();
registerMediaPermissionsHandler();
@@ -84,7 +79,7 @@ if (!app.requestSingleInstanceLock({ IS_DEV })) {
}
async function bootstrap() {
if (!Object.hasOwn(State.store, "firstLaunch")) {
if (!Object.hasOwn(Settings.store, "firstLaunch")) {
createFirstLaunchTour();
} else {
createWindows();

View File

@@ -4,10 +4,10 @@
* Copyright (c) 2023 Vendicated and Vencord contributors
*/
if (process.platform === "linux") import("./venmic");
if (process.platform === "linux") import("./virtmic");
import { execFile } from "child_process";
import { app, BrowserWindow, clipboard, dialog, nativeImage, RelaunchOptions, session, shell } from "electron";
import { app, BrowserWindow, dialog, RelaunchOptions, session, shell } from "electron";
import { mkdirSync, readFileSync, watch } from "fs";
import { open, readFile } from "fs/promises";
import { release } from "os";
@@ -21,7 +21,6 @@ import { VENCORD_FILES_DIR, VENCORD_QUICKCSS_FILE, VENCORD_THEMES_DIR } from "./
import { mainWin } from "./mainWindow";
import { Settings } from "./settings";
import { handle, handleSync } from "./utils/ipcWrappers";
import { isDeckGameMode, showGamePage } from "./utils/steamOS";
import { isValidVencordInstall } from "./utils/vencordLoader";
handleSync(IpcEvents.GET_VENCORD_PRELOAD_FILE, () => join(VENCORD_FILES_DIR, "vencordDesktopPreload.js"));
@@ -48,14 +47,11 @@ handle(IpcEvents.SET_SETTINGS, (_, settings: typeof Settings.store, path?: strin
Settings.setData(settings, path);
});
handle(IpcEvents.RELAUNCH, async () => {
handle(IpcEvents.RELAUNCH, () => {
const options: RelaunchOptions = {
args: process.argv.slice(1).concat(["--relaunch"])
};
if (isDeckGameMode) {
// We can't properly relaunch when running under gamescope, but we can at least navigate to our page in Steam.
await showGamePage();
} else if (app.isPackaged && process.env.APPIMAGE) {
if (app.isPackaged && process.env.APPIMAGE) {
execFile(process.env.APPIMAGE, options.args);
} else {
app.relaunch(options);
@@ -120,13 +116,6 @@ handle(IpcEvents.SELECT_VENCORD_DIR, async () => {
handle(IpcEvents.SET_BADGE_COUNT, (_, count: number) => setBadgeCount(count));
handle(IpcEvents.CLIPBOARD_COPY_IMAGE, async (_, buf: ArrayBuffer, src: string) => {
clipboard.write({
html: `<img src="${src.replaceAll('"', '\\"')}">`,
image: nativeImage.createFromBuffer(Buffer.from(buf))
});
});
function readCss() {
return readFile(VENCORD_QUICKCSS_FILE, "utf-8").catch(() => "");
}

View File

@@ -24,6 +24,7 @@ import type { SettingsStore } from "shared/utils/SettingsStore";
import { ICON_PATH } from "../shared/paths";
import { createAboutWindow } from "./about";
import { initArRPC } from "./arrpc";
import { autoStart } from "./autoStart";
import {
DATA_DIR,
DEFAULT_HEIGHT,
@@ -34,7 +35,7 @@ import {
UserAgent,
VENCORD_FILES_DIR
} from "./constants";
import { Settings, State, VencordSettings } from "./settings";
import { Settings, VencordSettings } from "./settings";
import { createSplashWindow } from "./splash";
import { makeLinksOpenExternally } from "./utils/makeLinksOpenExternally";
import { applyDeckKeyboardFix, askToApplySteamLayout, isDeckGameMode } from "./utils/steamOS";
@@ -78,7 +79,8 @@ function initTray(win: BrowserWindow) {
label: "Open",
click() {
win.show();
}
},
enabled: false
},
{
label: "About",
@@ -121,6 +123,14 @@ function initTray(win: BrowserWindow) {
tray.setToolTip("Vesktop");
tray.setContextMenu(trayMenu);
tray.on("click", () => win.show());
win.on("show", () => {
trayMenu.items[0].enabled = false;
});
win.on("hide", () => {
trayMenu.items[0].enabled = true;
});
}
async function clearData(win: BrowserWindow) {
@@ -201,6 +211,7 @@ function initMenuBar(win: BrowserWindow) {
type: "separator"
},
{
label: "Hide Vesktop", // Should probably remove the label, but it says "Hide VencordDesktop" instead of "Hide Vesktop"
role: "hide"
},
{
@@ -258,7 +269,7 @@ 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 { x, y, width, height } = Settings.store.windowBounds ?? {};
const options = {
width: width ?? DEFAULT_WIDTH,
@@ -303,8 +314,8 @@ function getDarwinOptions(): BrowserWindowConstructorOptions {
function initWindowBoundsListeners(win: BrowserWindow) {
const saveState = () => {
State.store.maximized = win.isMaximized();
State.store.minimized = win.isMinimized();
Settings.store.maximized = win.isMaximized();
Settings.store.minimized = win.isMinimized();
};
win.on("maximize", saveState);
@@ -312,7 +323,7 @@ function initWindowBoundsListeners(win: BrowserWindow) {
win.on("unmaximize", saveState);
const saveBounds = () => {
State.store.windowBounds = win.getBounds();
Settings.store.windowBounds = win.getBounds();
};
win.on("resize", saveBounds);
@@ -365,11 +376,11 @@ function createMainWindow() {
removeSettingsListeners();
removeVencordSettingsListeners();
const { staticTitle, transparencyOption, enableMenu, customTitleBar } = Settings.store;
const { staticTitle, transparencyOption, enableMenu, discordWindowsTitleBar } = Settings.store;
const { frameless } = VencordSettings.store;
const noFrame = frameless === true || customTitleBar === true;
const noFrame = frameless === true || (process.platform === "win32" && discordWindowsTitleBar === true);
const win = (mainWin = new BrowserWindow({
show: false,
@@ -386,12 +397,7 @@ function createMainWindow() {
...(transparencyOption &&
transparencyOption !== "none" && {
backgroundColor: "#00000000",
backgroundMaterial: transparencyOption
}),
// Fix transparencyOption for custom discord titlebar
...(customTitleBar &&
transparencyOption &&
transparencyOption !== "none" && {
backgroundMaterial: transparencyOption,
transparent: true
}),
...(staticTitle && { title: "Vesktop" }),
@@ -437,8 +443,8 @@ function createMainWindow() {
const runVencordMain = once(() => require(join(VENCORD_FILES_DIR, "vencordDesktopMain.js")));
export async function createWindows() {
const startMinimized = process.argv.includes("--start-minimized");
const splash = createSplashWindow(startMinimized);
const shouldStartMinimized = Settings.store.startMinimized && autoStart.wasAutoStarted();
const splash = createSplashWindow(shouldStartMinimized);
// SteamOS letterboxes and scales it terribly, so just full screen it
if (isDeckGameMode) splash.setFullScreen(true);
await ensureVencordFiles();
@@ -449,10 +455,7 @@ export async function createWindows() {
mainWin.webContents.on("did-finish-load", () => {
splash.destroy();
if (!startMinimized) {
mainWin!.show();
if (State.store.maximized && !isDeckGameMode) mainWin!.maximize();
}
if (!shouldStartMinimized || isDeckGameMode) mainWin!.show();
if (isDeckGameMode) {
// always use entire display
@@ -460,12 +463,12 @@ export async function createWindows() {
askToApplySteamLayout(mainWin);
}
});
mainWin.once("show", () => {
if (State.store.maximized && !mainWin!.isMaximized() && !isDeckGameMode) {
mainWin!.maximize();
}
});
mainWin.once("show", () => {
if (Settings.store.maximized && !mainWin!.isMaximized() && !isDeckGameMode) {
mainWin!.maximize();
}
});
initArRPC();

View File

@@ -4,15 +4,14 @@
* Copyright (c) 2023 Vendicated and Vencord contributors
*/
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
import { mkdirSync, readFileSync, writeFileSync } from "fs";
import { dirname, join } from "path";
import type { Settings as TSettings, State as TState } from "shared/settings";
import type { Settings as TSettings } from "shared/settings";
import { SettingsStore } from "shared/utils/SettingsStore";
import { DATA_DIR, VENCORD_SETTINGS_FILE } from "./constants";
const SETTINGS_FILE = join(DATA_DIR, "settings.json");
const STATE_FILE = join(DATA_DIR, "state.json");
function loadSettings<T extends object = any>(file: string, name: string) {
let settings = {} as T;
@@ -21,7 +20,7 @@ function loadSettings<T extends object = any>(file: string, name: string) {
try {
settings = JSON.parse(content);
} catch (err) {
console.error(`Failed to parse ${name}.json:`, err);
console.error(`Failed to parse ${name} settings.json:`, err);
}
} catch {}
@@ -34,31 +33,5 @@ function loadSettings<T extends object = any>(file: string, name: string) {
return store;
}
export const Settings = loadSettings<TSettings>(SETTINGS_FILE, "Vesktop settings");
if (Object.hasOwn(Settings.plain, "discordWindowsTitleBar")) {
Settings.plain.customTitleBar = Settings.plain.discordWindowsTitleBar;
delete Settings.plain.discordWindowsTitleBar;
Settings.markAsChanged();
}
export const VencordSettings = loadSettings<any>(VENCORD_SETTINGS_FILE, "Vencord settings");
if (Object.hasOwn(Settings.plain, "firstLaunch") && !existsSync(STATE_FILE)) {
console.warn("legacy state in settings.json detected. migrating to state.json");
const state = {} as TState;
for (const prop of [
"firstLaunch",
"maximized",
"minimized",
"skippedUpdate",
"steamOSLayoutVersion",
"windowBounds"
] as const) {
state[prop] = Settings.plain[prop];
delete Settings.plain[prop];
}
Settings.markAsChanged();
writeFileSync(STATE_FILE, JSON.stringify(state, null, 4));
}
export const State = loadSettings<TState>(STATE_FILE, "Vesktop state");
export const Settings = loadSettings<TSettings>(SETTINGS_FILE, "Vesktop");
export const VencordSettings = loadSettings<any>(VENCORD_SETTINGS_FILE, "Vencord");

View File

@@ -5,14 +5,20 @@
*/
import { ipcMain, IpcMainEvent, IpcMainInvokeEvent, WebFrameMain } from "electron";
import { DISCORD_HOSTNAMES } from "main/constants";
import { IpcEvents } from "shared/IpcEvents";
export function validateSender(frame: WebFrameMain) {
const { hostname, protocol } = new URL(frame.url);
if (protocol === "file:") return;
if (!DISCORD_HOSTNAMES.includes(hostname)) throw new Error("ipc: Disallowed host " + hostname);
switch (hostname) {
case "discord.com":
case "ptb.discord.com":
case "canary.discord.com":
break;
default:
throw new Error("ipc: Disallowed host " + hostname);
}
}
export function handleSync(event: IpcEvents, cb: (e: IpcMainEvent, ...args: any[]) => any) {

View File

@@ -5,67 +5,36 @@
*/
import { BrowserWindow, shell } from "electron";
import { DISCORD_HOSTNAMES } from "main/constants";
import { Settings } from "../settings";
import { createOrFocusPopup, setupPopout } from "./popout";
import { execSteamURL, isDeckGameMode, steamOpenURL } from "./steamOS";
export function handleExternalUrl(url: string, protocol?: string): { action: "deny" | "allow" } {
if (protocol == null) {
try {
protocol = new URL(url).protocol;
} catch {
return { action: "deny" };
}
}
switch (protocol) {
case "http:":
case "https:":
if (Settings.store.openLinksWithElectron) {
return { action: "allow" };
}
// eslint-disable-next-line no-fallthrough
case "mailto:":
case "spotify:":
if (isDeckGameMode) {
steamOpenURL(url);
} else {
shell.openExternal(url);
}
break;
case "steam:":
if (isDeckGameMode) {
execSteamURL(url);
} else {
shell.openExternal(url);
}
break;
}
return { action: "deny" };
}
export function makeLinksOpenExternally(win: BrowserWindow) {
win.webContents.setWindowOpenHandler(({ url, frameName, features }) => {
win.webContents.setWindowOpenHandler(({ url }) => {
switch (url) {
case "about:blank":
case "https://discord.com/popout":
return { action: "allow" };
}
try {
var { protocol, hostname, pathname } = new URL(url);
var { protocol } = new URL(url);
} catch {
return { action: "deny" };
}
if (frameName.startsWith("DISCORD_") && pathname === "/popout" && DISCORD_HOSTNAMES.includes(hostname)) {
return createOrFocusPopup(frameName, features);
switch (protocol) {
case "http:":
case "https:":
if (Settings.store.openLinksWithElectron) {
return { action: "allow" };
}
// eslint-disable-next-line no-fallthrough
case "mailto:":
case "steam:":
case "spotify:":
shell.openExternal(url);
}
if (url === "about:blank" || (frameName === "authorize" && DISCORD_HOSTNAMES.includes(hostname)))
return { action: "allow" };
return handleExternalUrl(url, protocol);
});
win.webContents.on("did-create-window", (win, { frameName }) => {
if (frameName.startsWith("DISCORD_")) setupPopout(win, frameName);
return { action: "deny" };
});
}

View File

@@ -1,112 +0,0 @@
/*
* SPDX-License-Identifier: GPL-3.0
* Vesktop, a desktop app aiming to give you a snappier Discord Experience
* Copyright (c) 2023 Vendicated and Vencord contributors
*/
import { BrowserWindow, BrowserWindowConstructorOptions } from "electron";
import { handleExternalUrl } from "./makeLinksOpenExternally";
const ALLOWED_FEATURES = new Set([
"width",
"height",
"left",
"top",
"resizable",
"movable",
"alwaysOnTop",
"frame",
"transparent",
"hasShadow",
"closable",
"skipTaskbar",
"backgroundColor",
"menubar",
"toolbar",
"location",
"directories",
"titleBarStyle"
]);
const MIN_POPOUT_WIDTH = 320;
const MIN_POPOUT_HEIGHT = 180;
const DEFAULT_POPOUT_OPTIONS: BrowserWindowConstructorOptions = {
title: "Discord Popout",
backgroundColor: "#2f3136",
minWidth: MIN_POPOUT_WIDTH,
minHeight: MIN_POPOUT_HEIGHT,
frame: process.platform === "linux",
titleBarStyle: process.platform === "darwin" ? "hidden" : undefined,
trafficLightPosition:
process.platform === "darwin"
? {
x: 10,
y: 3
}
: undefined,
webPreferences: {
nodeIntegration: false,
contextIsolation: true
}
};
const PopoutWindows = new Map<string, BrowserWindow>();
function focusWindow(window: BrowserWindow) {
window.setAlwaysOnTop(true);
window.focus();
window.setAlwaysOnTop(false);
}
function parseFeatureValue(feature: string) {
if (feature === "yes") return true;
if (feature === "no") return false;
const n = Number(feature);
if (!isNaN(n)) return n;
return feature;
}
function parseWindowFeatures(features: string) {
const keyValuesParsed = features.split(",");
return keyValuesParsed.reduce((features, feature) => {
const [key, value] = feature.split("=");
if (ALLOWED_FEATURES.has(key)) features[key] = parseFeatureValue(value);
return features;
}, {});
}
export function createOrFocusPopup(key: string, features: string) {
const existingWindow = PopoutWindows.get(key);
if (existingWindow) {
focusWindow(existingWindow);
return <const>{ action: "deny" };
}
return <const>{
action: "allow",
overrideBrowserWindowOptions: {
...DEFAULT_POPOUT_OPTIONS,
...parseWindowFeatures(features)
}
};
}
export function setupPopout(win: BrowserWindow, key: string) {
PopoutWindows.set(key, win);
/* win.webContents.on("will-navigate", (evt, url) => {
// maybe prevent if not origin match
})*/
win.webContents.setWindowOpenHandler(({ url }) => handleExternalUrl(url));
win.once("closed", () => {
win.removeAllListeners();
PopoutWindows.delete(key);
});
}

View File

@@ -4,12 +4,15 @@
* Copyright (c) 2023 Vendicated and Vencord contributors
*/
import { exec as callbackExec } from "child_process";
import { BrowserWindow, dialog } from "electron";
import { writeFile } from "fs/promises";
import { join } from "path";
import { sleep } from "shared/utils/sleep";
import { promisify } from "util";
import { MessageBoxChoice } from "../constants";
import { State } from "../settings";
import { Settings } from "../settings";
const exec = promisify(callbackExec);
// Bump this to re-show the prompt
const layoutVersion = 2;
@@ -17,8 +20,6 @@ const layoutVersion = 2;
const layoutId = "3080264545"; // Vesktop Layout v2
const numberRegex = /^[0-9]*$/;
let steamPipeQueue = Promise.resolve();
export const isDeckGameMode = process.env.SteamOS === "1" && process.env.SteamGamepadUI === "1";
export function applyDeckKeyboardFix() {
@@ -41,37 +42,23 @@ function getAppId(): string | null {
return null;
}
export function execSteamURL(url: string) {
// This doesn't allow arbitrary execution despite the weird syntax.
steamPipeQueue = steamPipeQueue.then(() =>
writeFile(
join(process.env.HOME || "/home/deck", ".steam", "steam.pipe"),
// replace ' to prevent argument injection
`'${process.env.HOME}/.local/share/Steam/ubuntu12_32/steam' '-ifrunning' '${url.replaceAll("'", "%27")}'\n`,
"utf-8"
)
);
}
export function steamOpenURL(url: string) {
execSteamURL(`steam://openurl/${url}`);
}
export async function showGamePage() {
const appId = getAppId();
if (!appId) return;
await execSteamURL(`steam://nav/games/details/${appId}`);
async function execSteamURL(url: string): Promise<void> {
await exec(`steam -ifrunning ${url}`);
}
async function showLayout(appId: string) {
execSteamURL(`steam://controllerconfig/${appId}/${layoutId}`);
await execSteamURL(`steam://controllerconfig/${appId}/${layoutId}`);
// because the UI doesn't consistently reload after the data for the config has loaded...
// HOW HAS NOBODY AT VALVE RUN INTO THIS YET
await sleep(100);
await execSteamURL(`steam://controllerconfig/${appId}/${layoutId}`);
}
export async function askToApplySteamLayout(win: BrowserWindow) {
const appId = getAppId();
if (!appId) return;
if (State.store.steamOSLayoutVersion === layoutVersion) return;
const update = Boolean(State.store.steamOSLayoutVersion);
if (Settings.store.steamOSLayoutVersion === layoutVersion) return;
const update = Boolean(Settings.store.steamOSLayoutVersion);
// Touch screen breaks in some menus when native touch mode is enabled on latest SteamOS beta, remove most of the update specific text once that's fixed.
const { response } = await dialog.showMessageBox(win, {
@@ -87,8 +74,8 @@ ${update ? "Click" : "Tap"} no to keep your current layout.`,
type: "question"
});
if (State.store.steamOSLayoutVersion !== layoutVersion) {
State.store.steamOSLayoutVersion = layoutVersion;
if (Settings.store.steamOSLayoutVersion !== layoutVersion) {
Settings.store.steamOSLayoutVersion = layoutVersion;
}
if (response === MessageBoxChoice.Cancel) return;

View File

@@ -51,17 +51,27 @@ ipcMain.handle(IpcEvents.VIRT_MIC_LIST, () => {
: { ok: false, isGlibcxxToOld };
});
ipcMain.handle(IpcEvents.VIRT_MIC_START, (_, targets: string[]) =>
obtainVenmic()?.link({
include: targets.map(target => ({ key: "application.name", value: target })),
exclude: [{ key: "application.process.id", value: getRendererAudioServicePid() }]
})
ipcMain.handle(
IpcEvents.VIRT_MIC_START,
(_, targets: string[]) =>
obtainVenmic()?.link({
props: targets.map(target => ({ key: "application.name", value: target })),
mode: "include"
})
);
ipcMain.handle(IpcEvents.VIRT_MIC_START_SYSTEM, () =>
obtainVenmic()?.link({
exclude: [{ key: "application.process.id", value: getRendererAudioServicePid() }]
})
ipcMain.handle(
IpcEvents.VIRT_MIC_START_SYSTEM,
() =>
obtainVenmic()?.link({
props: [
{
key: "application.process.id",
value: getRendererAudioServicePid()
}
],
mode: "exclude"
})
);
ipcMain.handle(IpcEvents.VIRT_MIC_STOP, () => obtainVenmic()?.unlink());

View File

@@ -71,9 +71,5 @@ export const VesktopNative = {
onActivity(cb: (data: string) => void) {
ipcRenderer.on(IpcEvents.ARRPC_ACTIVITY, (_, data: string) => cb(data));
}
},
clipboard: {
copyImage: (imageBuffer: Uint8Array, imageSrc: string) =>
invoke<void>(IpcEvents.CLIPBOARD_COPY_IMAGE, imageBuffer, imageSrc)
}
};

View File

@@ -10,7 +10,7 @@ import { Margins } from "@vencord/types/utils";
import { Button, Forms, Select, Switch, Text, Toasts, useState } from "@vencord/types/webpack/common";
import { setBadge } from "renderer/appBadge";
import { useSettings } from "renderer/settings";
import { isMac } from "renderer/utils";
import { isMac, isWindows } from "renderer/utils";
import { isTruthy } from "shared/utils/guards";
export default function SettingsUi() {
@@ -21,10 +21,10 @@ export default function SettingsUi() {
const [autoStartEnabled, setAutoStartEnabled] = useState(autostart.isEnabled());
const allSwitches: Array<false | [keyof typeof Settings, string, string, boolean?, (() => boolean)?]> = [
[
"customTitleBar",
isWindows && [
"discordWindowsTitleBar",
"Discord Titlebar",
"Use Discord's custom title bar instead of the native system one. Requires a full restart."
"Use Discord's custom title bar instead of the Windows one. Requires a full restart."
],
!isMac && ["tray", "Tray Icon", "Add a tray icon for Vesktop", true],
!isMac && [
@@ -43,14 +43,14 @@ export default function SettingsUi() {
["staticTitle", "Static Title", 'Makes the window title "Vesktop" instead of changing to the current page'],
["enableMenu", "Enable Menu Bar", "Enables the application menu bar. Press ALT to toggle visibility."],
["disableSmoothScroll", "Disable smooth scrolling", "Disables smooth scrolling in Vesktop", false],
["hardwareAcceleration", "Hardware Acceleration", "Enable hardware acceleration", true],
["splashTheming", "Splash theming", "Adapt the splash window colors to your custom theme", false],
[
"openLinksWithElectron",
"Open Links in app (experimental)",
"Opens links in a new Vesktop window instead of your web browser"
],
["checkUpdates", "Check for updates", "Automatically check for Vesktop updates", true]
["checkUpdates", "Check for updates", "Automatically check for Vesktop updates", true],
["startMinimized", "Start minimized", "Vesktop remains in minimized mode on start", false]
];
const switches = allSwitches.filter(isTruthy);

View File

@@ -6,7 +6,9 @@
import "./hideGarbage.css";
import { isWindows, localStorage } from "./utils";
import { waitFor } from "@vencord/types/webpack";
import { isFirstRun, isWindows, localStorage } from "./utils";
// Make clicking Notifications focus the window
const originalSetOnClick = Object.getOwnPropertyDescriptor(Notification.prototype, "onclick")!.set!;
@@ -20,8 +22,15 @@ Object.defineProperty(Notification.prototype, "onclick", {
configurable: true
});
// Hide "Download Discord Desktop now!!!!" banner
localStorage.setItem("hideNag", "true");
if (isFirstRun) {
// Hide "Download Discord Desktop now!!!!" banner
localStorage.setItem("hideNag", "true");
// Enable Desktop Notifications by default
waitFor("setDesktopType", m => {
m.setDesktopType("all");
});
}
// FIXME: Remove eventually.
// Originally, Vencord always used a Windows user agent. This seems to cause captchas

View File

@@ -1,21 +0,0 @@
/*
* SPDX-License-Identifier: GPL-3.0
* Vesktop, a desktop app aiming to give you a snappier Discord Experience
* Copyright (c) 2023 Vendicated and Vencord contributors
*/
import { addPatch } from "./shared";
addPatch({
patches: [
{
find: '"NotificationSettingsStore',
replacement: {
// FIXME: fix eslint rule
// eslint-disable-next-line no-useless-escape
match: /\.isPlatformEmbedded(?=\?\i\.DesktopNotificationTypes\.ALL)/g,
replace: "$&||true"
}
}
]
});

View File

@@ -5,8 +5,7 @@
*/
// TODO: Possibly auto generate glob if we have more patches in the future
import "./enableNotificationsByDefault";
import "./platformClass";
import "./screenShareAudio";
import "./spellCheck";
import "./platformClass";
import "./windowsTitleBar";
import "./screenShareAudio";

View File

@@ -5,7 +5,7 @@
*/
import { Settings } from "renderer/settings";
import { isMac } from "renderer/utils";
import { isMac, isWindows } from "renderer/utils";
import { addPatch } from "./shared";
@@ -22,8 +22,8 @@ addPatch({
],
getPlatformClass() {
if (Settings.store.customTitleBar) return "platform-win";
if (isMac) return "platform-osx";
if (isWindows && Settings.store.discordWindowsTitleBar) return "platform-win";
return "platform-web";
}
});

View File

@@ -8,7 +8,7 @@ import { Settings } from "renderer/settings";
import { addPatch } from "./shared";
if (Settings.store.customTitleBar)
if (Settings.store.discordWindowsTitleBar)
addPatch({
patches: [
{

View File

@@ -47,7 +47,5 @@ export const enum IpcEvents {
VIRT_MIC_START_SYSTEM = "VCD_VIRT_MIC_START_ALL",
VIRT_MIC_STOP = "VCD_VIRT_MIC_STOP",
ARRPC_ACTIVITY = "VCD_ARRPC_ACTIVITY",
CLIPBOARD_COPY_IMAGE = "VCD_CLIPBOARD_COPY_IMAGE"
ARRPC_ACTIVITY = "VCD_ARRPC_ACTIVITY"
}

View File

@@ -16,29 +16,23 @@ export interface Settings {
staticTitle?: boolean;
enableMenu?: boolean;
disableSmoothScroll?: boolean;
hardwareAcceleration?: boolean;
arRPC?: boolean;
appBadge?: boolean;
discordWindowsTitleBar?: boolean;
startMinimized?: boolean;
maximized?: boolean;
minimized?: boolean;
windowBounds?: Rectangle;
disableMinSize?: boolean;
/** @deprecated use customTitleBar */
discordWindowsTitleBar?: boolean;
customTitleBar?: boolean;
checkUpdates?: boolean;
skippedUpdate?: string;
firstLaunch?: boolean;
splashTheming?: boolean;
splashColor?: string;
splashBackground?: string;
}
export interface State {
maximized?: boolean;
minimized?: boolean;
windowBounds?: Rectangle;
skippedUpdate?: string;
firstLaunch?: boolean;
steamOSLayoutVersion?: number;
}

View File

@@ -144,11 +144,4 @@ export class SettingsStore<T extends object> {
listeners.delete(cb);
if (!listeners.size) this.pathListeners.delete(path as string);
}
/**
* Call all global change listeners
*/
public markAsChanged() {
this.globalListeners.forEach(cb => cb(this.plain, ""));
}
}

View File

@@ -5,7 +5,7 @@
*/
import { app, BrowserWindow, shell } from "electron";
import { Settings, State } from "main/settings";
import { Settings } from "main/settings";
import { handle } from "main/utils/ipcWrappers";
import { makeLinksOpenExternally } from "main/utils/makeLinksOpenExternally";
import { githubGet, ReleaseData } from "main/utils/vencordLoader";
@@ -52,7 +52,7 @@ handle(IpcEvents.UPDATER_DOWNLOAD, () => {
});
handle(IpcEvents.UPDATE_IGNORE, () => {
State.store.skippedUpdate = updateData.latestVersion;
Settings.store.skippedUpdate = updateData.latestVersion;
});
function isOutdated(oldVersion: string, newVersion: string) {
@@ -91,7 +91,7 @@ export async function checkUpdates() {
release: data
};
if (State.store.skippedUpdate !== newVersion && isOutdated(oldVersion, newVersion)) {
if (Settings.store.skippedUpdate !== newVersion && isOutdated(oldVersion, newVersion)) {
openNewUpdateWindow();
}
} catch (e) {

View File

@@ -2,9 +2,8 @@
<link rel="stylesheet" href="./style.css" type="text/css" />
<style>
body {
* {
user-select: none;
-webkit-app-region: drag;
}
.wrapper {