29 Commits

Author SHA1 Message Date
V
79cd2791f6 bump to v1.5.0 2024-01-16 02:02:48 +01:00
V
73de0fa535 bump venmic 2024-01-16 01:34:38 +01:00
Vendicated
83e74b98d5 don't disable tray 'Open' item when window is shown (doesnt work correctly) 2024-01-16 01:22:10 +01:00
Vendicated
2e4c834a90 also allow using discord titlebar on non-windows 2024-01-16 01:08:06 +01:00
Vendicated
2aa0b0fa20 prettier update borked lint somehow woah 2024-01-15 19:10:42 +01:00
Vendicated
3ac0ed3d78 config migration: also migrate indexeddb 2024-01-15 19:09:09 +01:00
Vendicated
eddc1de784 bump deps 2024-01-15 19:05:29 +01:00
Vendicated
6483b3a3d9 port popout logic from discord desktop 2024-01-15 18:52:46 +01:00
Vendicated
38f0330eb2 Fix adding connections & popout on ptb/canary 2024-01-15 17:56:33 +01:00
Vendicated
15a49a31e1 add clipboard.copyImage ipc
the web clipboard api only works when focused. so when you try to copy an image but immediately unfocus while its still fetching the image, it will fail. this ipc ensures it will always work even without focus
2024-01-13 19:04:32 +01:00
Lewis Crichton
33eb1af2be ci: mac code signing 2024-01-11 17:50:30 +00:00
V
563ba1eebe Add basic issue templates 2024-01-11 17:23:14 +01:00
Vendicated
62cf02e7b1 make splash window draggable 2024-01-08 02:38:38 +01:00
Redeven
0f0bddbef9 feat: Add start minimized as a launch argument (#316)
Co-authored-by: V <vendicated@riseup.net>
2024-01-07 03:11:00 +01:00
Vendicated
0881143d57 update appId 2024-01-07 02:52:39 +01:00
Vendicated
effd950b2d fully rename app to Vesktop 2024-01-07 02:44:14 +01:00
Vendicated
4074e8d6ac move internal state from settings.json to state.json 2024-01-07 02:26:18 +01:00
Marocco2
779c8fa516 Remove transparent: true for transparencyOption (#266)
Co-authored-by: V <vendicated@riseup.net>
2024-01-07 00:59:29 +00:00
Vendicated
b059516707 SteamOS: always use dark theme
Co-authored-by: AAGaming <aagaming@riseup.net>
2024-01-06 03:20:13 +01:00
Vendicated
dfa9d248d3 bump venmic 2024-01-06 03:18:30 +01:00
Vendicated
18925ad583 bump deps 2024-01-06 02:05:09 +01:00
Vendicated
0b01732293 make hardwareAcceleration setting more intuitive 2024-01-06 02:02:54 +01:00
Nick
a89cd9d2ba add setting to disable hardware acceleration (#315) 2024-01-02 05:36:50 +01:00
V
2f35128acf Revert "feat: Add start as minimized toggle to settings (#248)" (#310)
the current implementation was prematurely merged and is broken. it will be back with a fixed implementaton eventually
2023-12-28 04:25:54 +01:00
V
7565eb39a4 improve method for enabling notifications by default 2023-12-28 04:19:56 +01:00
V
d71d0c1cc4 bump electron to v28 2023-12-28 03:27:38 +01:00
Lewis Crichton
dfc6970756 fix(security): use promise queue for steam pipe (#300)
this prevents an (unlikely) race condition where writing multiple large payloads to the pipe simultaneously could lead to jambled data => argument injection
2023-12-28 01:38:31 +01:00
V
1429815fd1 README: remove outdated section (#303) 2023-12-24 00:15:26 +01:00
AAGaming
aa397d003c SteamOS: add flatpak support using steam.pipe (#283) 2023-12-22 13:56:11 +00:00
33 changed files with 1008 additions and 1049 deletions

View File

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

32
.github/ISSUE_TEMPLATE/bug_report.md vendored Normal file
View File

@@ -0,0 +1,32 @@
---
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.

10
.github/ISSUE_TEMPLATE/custom.md vendored Normal file
View File

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

View File

@@ -0,0 +1,20 @@
---
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,7 +37,16 @@ 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 `vencorddesktop`.
Either download Vesktop-VERSION.AppImage and just run it directly or grab Vesktop-VERSION.tar.gz, extract it somewhere and run `vesktop`.
If other packages are created, feel free to open an issue and we'll link them here.
@@ -68,7 +68,3 @@ 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\VencordDesktop"
WriteRegExpandStr HKCU "${INSTALL_REGISTRY_KEY}" InstallLocation "$LocalAppData\VencordDesktop"
WriteRegExpandStr HKLM "${INSTALL_REGISTRY_KEY}" InstallLocation "$LocalAppData\vesktop"
WriteRegExpandStr HKCU "${INSTALL_REGISTRY_KEY}" InstallLocation "$LocalAppData\vesktop"
SetRegView 32
WriteRegExpandStr HKLM "${INSTALL_REGISTRY_KEY}" InstallLocation "$LocalAppData\VencordDesktop"
WriteRegExpandStr HKCU "${INSTALL_REGISTRY_KEY}" InstallLocation "$LocalAppData\VencordDesktop"
WriteRegExpandStr HKLM "${INSTALL_REGISTRY_KEY}" InstallLocation "$LocalAppData\vesktop"
WriteRegExpandStr HKCU "${INSTALL_REGISTRY_KEY}" InstallLocation "$LocalAppData\vesktop"
!macroend

View File

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

1365
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@@ -10,51 +10,38 @@ 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), // TODO: flatpak
wasAutoStarted: () => process.argv.includes("--autostart"),
isEnabled: () => existsSync(file),
enable() {
if (isFlatpak) {
} else {
const desktopFile = `
const desktopFile = `
[Desktop Entry]
Type=Application
Version=1.0
Name=Vencord
Comment=Vencord autostart script
Exec=${process.execPath} --autostart
Exec=${process.execPath}
Terminal=false
StartupNotify=false
`.trim();
mkdirSync(dir, { recursive: true });
writeFileSync(file, desktopFile);
}
mkdirSync(dir, { recursive: true });
writeFileSync(file, desktopFile);
},
disable: () => {
if (isFlatpak) {
} else {
rmSync(file, { force: true });
}
}
disable: () => 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,9 +5,29 @@
*/
import { app } from "electron";
import { existsSync, readdirSync, renameSync, rmdirSync } from "fs";
import { join } from "path";
export const DATA_DIR = process.env.VENCORD_USER_DATA_DIR || join(app.getPath("userData"), "VencordDesktop");
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 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");
@@ -26,11 +46,13 @@ 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/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",
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",
windows:
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/118.0.0.0 Safari/537.36"
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.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 } from "./settings";
import { Settings, State } 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 } from "electron";
import { app, BrowserWindow, nativeTheme } from "electron";
import { checkUpdates } from "updater/main";
import { DATA_DIR } from "./constants";
@@ -14,7 +14,8 @@ import { createFirstLaunchTour } from "./firstLaunch";
import { createWindows, mainWin } from "./mainWindow";
import { registerMediaPermissionsHandler } from "./mediaPermissions";
import { registerScreenShareHandler } from "./screenShare";
import { Settings } from "./settings";
import { Settings, State } from "./settings";
import { isDeckGameMode } from "./utils/steamOS";
if (IS_DEV) {
require("source-map-support").install();
@@ -24,8 +25,9 @@ if (IS_DEV) {
process.env.VENCORD_USER_DATA_DIR = DATA_DIR;
function init() {
const { disableSmoothScroll } = Settings.store;
const { disableSmoothScroll, hardwareAcceleration } = Settings.store;
if (hardwareAcceleration === false) app.disableHardwareAcceleration();
if (disableSmoothScroll) {
app.commandLine.appendSwitch("disable-smooth-scrolling");
}
@@ -42,6 +44,9 @@ 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) {
@@ -53,7 +58,7 @@ function init() {
app.whenReady().then(async () => {
checkUpdates();
if (process.platform === "win32") app.setAppUserModelId("dev.vencord.desktop");
if (process.platform === "win32") app.setAppUserModelId("dev.vencord.vesktop");
registerScreenShareHandler();
registerMediaPermissionsHandler();
@@ -79,7 +84,7 @@ if (!app.requestSingleInstanceLock({ IS_DEV })) {
}
async function bootstrap() {
if (!Object.hasOwn(Settings.store, "firstLaunch")) {
if (!Object.hasOwn(State.store, "firstLaunch")) {
createFirstLaunchTour();
} else {
createWindows();

View File

@@ -4,10 +4,10 @@
* Copyright (c) 2023 Vendicated and Vencord contributors
*/
if (process.platform === "linux") import("./virtmic");
if (process.platform === "linux") import("./venmic");
import { execFile } from "child_process";
import { app, BrowserWindow, dialog, RelaunchOptions, session, shell } from "electron";
import { app, BrowserWindow, clipboard, dialog, nativeImage, RelaunchOptions, session, shell } from "electron";
import { mkdirSync, readFileSync, watch } from "fs";
import { open, readFile } from "fs/promises";
import { release } from "os";
@@ -21,6 +21,7 @@ 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"));
@@ -47,11 +48,14 @@ handle(IpcEvents.SET_SETTINGS, (_, settings: typeof Settings.store, path?: strin
Settings.setData(settings, path);
});
handle(IpcEvents.RELAUNCH, () => {
handle(IpcEvents.RELAUNCH, async () => {
const options: RelaunchOptions = {
args: process.argv.slice(1).concat(["--relaunch"])
};
if (app.isPackaged && process.env.APPIMAGE) {
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) {
execFile(process.env.APPIMAGE, options.args);
} else {
app.relaunch(options);
@@ -116,6 +120,13 @@ 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,7 +24,6 @@ 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,
@@ -35,7 +34,7 @@ import {
UserAgent,
VENCORD_FILES_DIR
} from "./constants";
import { Settings, VencordSettings } from "./settings";
import { Settings, State, VencordSettings } from "./settings";
import { createSplashWindow } from "./splash";
import { makeLinksOpenExternally } from "./utils/makeLinksOpenExternally";
import { applyDeckKeyboardFix, askToApplySteamLayout, isDeckGameMode } from "./utils/steamOS";
@@ -79,8 +78,7 @@ function initTray(win: BrowserWindow) {
label: "Open",
click() {
win.show();
},
enabled: false
}
},
{
label: "About",
@@ -123,14 +121,6 @@ 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) {
@@ -211,7 +201,6 @@ 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"
},
{
@@ -269,7 +258,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 } = Settings.store.windowBounds ?? {};
const { x, y, width, height } = State.store.windowBounds ?? {};
const options = {
width: width ?? DEFAULT_WIDTH,
@@ -314,8 +303,8 @@ function getDarwinOptions(): BrowserWindowConstructorOptions {
function initWindowBoundsListeners(win: BrowserWindow) {
const saveState = () => {
Settings.store.maximized = win.isMaximized();
Settings.store.minimized = win.isMinimized();
State.store.maximized = win.isMaximized();
State.store.minimized = win.isMinimized();
};
win.on("maximize", saveState);
@@ -323,7 +312,7 @@ function initWindowBoundsListeners(win: BrowserWindow) {
win.on("unmaximize", saveState);
const saveBounds = () => {
Settings.store.windowBounds = win.getBounds();
State.store.windowBounds = win.getBounds();
};
win.on("resize", saveBounds);
@@ -376,11 +365,11 @@ function createMainWindow() {
removeSettingsListeners();
removeVencordSettingsListeners();
const { staticTitle, transparencyOption, enableMenu, discordWindowsTitleBar } = Settings.store;
const { staticTitle, transparencyOption, enableMenu, customTitleBar } = Settings.store;
const { frameless } = VencordSettings.store;
const noFrame = frameless === true || (process.platform === "win32" && discordWindowsTitleBar === true);
const noFrame = frameless === true || customTitleBar === true;
const win = (mainWin = new BrowserWindow({
show: false,
@@ -397,7 +386,12 @@ function createMainWindow() {
...(transparencyOption &&
transparencyOption !== "none" && {
backgroundColor: "#00000000",
backgroundMaterial: transparencyOption,
backgroundMaterial: transparencyOption
}),
// Fix transparencyOption for custom discord titlebar
...(customTitleBar &&
transparencyOption &&
transparencyOption !== "none" && {
transparent: true
}),
...(staticTitle && { title: "Vesktop" }),
@@ -443,8 +437,8 @@ function createMainWindow() {
const runVencordMain = once(() => require(join(VENCORD_FILES_DIR, "vencordDesktopMain.js")));
export async function createWindows() {
const shouldStartMinimized = Settings.store.startMinimized && autoStart.wasAutoStarted();
const splash = createSplashWindow(shouldStartMinimized);
const startMinimized = process.argv.includes("--start-minimized");
const splash = createSplashWindow(startMinimized);
// SteamOS letterboxes and scales it terribly, so just full screen it
if (isDeckGameMode) splash.setFullScreen(true);
await ensureVencordFiles();
@@ -455,7 +449,10 @@ export async function createWindows() {
mainWin.webContents.on("did-finish-load", () => {
splash.destroy();
if (!shouldStartMinimized || isDeckGameMode) mainWin!.show();
if (!startMinimized) {
mainWin!.show();
if (State.store.maximized && !isDeckGameMode) mainWin!.maximize();
}
if (isDeckGameMode) {
// always use entire display
@@ -463,12 +460,12 @@ export async function createWindows() {
askToApplySteamLayout(mainWin);
}
});
mainWin.once("show", () => {
if (Settings.store.maximized && !mainWin!.isMaximized() && !isDeckGameMode) {
mainWin!.maximize();
}
mainWin.once("show", () => {
if (State.store.maximized && !mainWin!.isMaximized() && !isDeckGameMode) {
mainWin!.maximize();
}
});
});
initArRPC();

View File

@@ -4,14 +4,15 @@
* Copyright (c) 2023 Vendicated and Vencord contributors
*/
import { mkdirSync, readFileSync, writeFileSync } from "fs";
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
import { dirname, join } from "path";
import type { Settings as TSettings } from "shared/settings";
import type { Settings as TSettings, State as TState } 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;
@@ -20,7 +21,7 @@ function loadSettings<T extends object = any>(file: string, name: string) {
try {
settings = JSON.parse(content);
} catch (err) {
console.error(`Failed to parse ${name} settings.json:`, err);
console.error(`Failed to parse ${name}.json:`, err);
}
} catch {}
@@ -33,5 +34,31 @@ function loadSettings<T extends object = any>(file: string, name: string) {
return store;
}
export const Settings = loadSettings<TSettings>(SETTINGS_FILE, "Vesktop");
export const VencordSettings = loadSettings<any>(VENCORD_SETTINGS_FILE, "Vencord");
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");

View File

@@ -5,20 +5,14 @@
*/
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;
switch (hostname) {
case "discord.com":
case "ptb.discord.com":
case "canary.discord.com":
break;
default:
throw new Error("ipc: Disallowed host " + hostname);
}
if (!DISCORD_HOSTNAMES.includes(hostname)) throw new Error("ipc: Disallowed host " + hostname);
}
export function handleSync(event: IpcEvents, cb: (e: IpcMainEvent, ...args: any[]) => any) {

View File

@@ -5,36 +5,67 @@
*/
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 }) => {
switch (url) {
case "about:blank":
case "https://discord.com/popout":
return { action: "allow" };
}
win.webContents.setWindowOpenHandler(({ url, frameName, features }) => {
try {
var { protocol } = new URL(url);
var { protocol, hostname, pathname } = new URL(url);
} 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 "steam:":
case "spotify:":
shell.openExternal(url);
if (frameName.startsWith("DISCORD_") && pathname === "/popout" && DISCORD_HOSTNAMES.includes(hostname)) {
return createOrFocusPopup(frameName, features);
}
return { action: "deny" };
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);
});
}

112
src/main/utils/popout.ts Normal file
View File

@@ -0,0 +1,112 @@
/*
* 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,15 +4,12 @@
* Copyright (c) 2023 Vendicated and Vencord contributors
*/
import { exec as callbackExec } from "child_process";
import { BrowserWindow, dialog } from "electron";
import { sleep } from "shared/utils/sleep";
import { promisify } from "util";
import { writeFile } from "fs/promises";
import { join } from "path";
import { MessageBoxChoice } from "../constants";
import { Settings } from "../settings";
const exec = promisify(callbackExec);
import { State } from "../settings";
// Bump this to re-show the prompt
const layoutVersion = 2;
@@ -20,6 +17,8 @@ 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() {
@@ -42,23 +41,37 @@ function getAppId(): string | null {
return null;
}
async function execSteamURL(url: string): Promise<void> {
await exec(`steam -ifrunning ${url}`);
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 showLayout(appId: string) {
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}`);
execSteamURL(`steam://controllerconfig/${appId}/${layoutId}`);
}
export async function askToApplySteamLayout(win: BrowserWindow) {
const appId = getAppId();
if (!appId) return;
if (Settings.store.steamOSLayoutVersion === layoutVersion) return;
const update = Boolean(Settings.store.steamOSLayoutVersion);
if (State.store.steamOSLayoutVersion === layoutVersion) return;
const update = Boolean(State.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, {
@@ -74,8 +87,8 @@ ${update ? "Click" : "Tap"} no to keep your current layout.`,
type: "question"
});
if (Settings.store.steamOSLayoutVersion !== layoutVersion) {
Settings.store.steamOSLayoutVersion = layoutVersion;
if (State.store.steamOSLayoutVersion !== layoutVersion) {
State.store.steamOSLayoutVersion = layoutVersion;
}
if (response === MessageBoxChoice.Cancel) return;

View File

@@ -51,27 +51,17 @@ ipcMain.handle(IpcEvents.VIRT_MIC_LIST, () => {
: { ok: false, isGlibcxxToOld };
});
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, (_, 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_SYSTEM,
() =>
obtainVenmic()?.link({
props: [
{
key: "application.process.id",
value: getRendererAudioServicePid()
}
],
mode: "exclude"
})
ipcMain.handle(IpcEvents.VIRT_MIC_START_SYSTEM, () =>
obtainVenmic()?.link({
exclude: [{ key: "application.process.id", value: getRendererAudioServicePid() }]
})
);
ipcMain.handle(IpcEvents.VIRT_MIC_STOP, () => obtainVenmic()?.unlink());

View File

@@ -71,5 +71,9 @@ 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, isWindows } from "renderer/utils";
import { isMac } 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)?]> = [
isWindows && [
"discordWindowsTitleBar",
[
"customTitleBar",
"Discord Titlebar",
"Use Discord's custom title bar instead of the Windows one. Requires a full restart."
"Use Discord's custom title bar instead of the native system 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],
["startMinimized", "Start minimized", "Vesktop remains in minimized mode on start", false]
["checkUpdates", "Check for updates", "Automatically check for Vesktop updates", true]
];
const switches = allSwitches.filter(isTruthy);

View File

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

View File

@@ -0,0 +1,21 @@
/*
* 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,7 +5,8 @@
*/
// TODO: Possibly auto generate glob if we have more patches in the future
import "./spellCheck";
import "./enableNotificationsByDefault";
import "./platformClass";
import "./windowsTitleBar";
import "./screenShareAudio";
import "./spellCheck";
import "./windowsTitleBar";

View File

@@ -5,7 +5,7 @@
*/
import { Settings } from "renderer/settings";
import { isMac, isWindows } from "renderer/utils";
import { isMac } 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.discordWindowsTitleBar)
if (Settings.store.customTitleBar)
addPatch({
patches: [
{

View File

@@ -47,5 +47,7 @@ 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"
ARRPC_ACTIVITY = "VCD_ARRPC_ACTIVITY",
CLIPBOARD_COPY_IMAGE = "VCD_CLIPBOARD_COPY_IMAGE"
}

View File

@@ -16,23 +16,29 @@ 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,4 +144,11 @@ 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 } from "main/settings";
import { Settings, State } 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, () => {
Settings.store.skippedUpdate = updateData.latestVersion;
State.store.skippedUpdate = updateData.latestVersion;
});
function isOutdated(oldVersion: string, newVersion: string) {
@@ -91,7 +91,7 @@ export async function checkUpdates() {
release: data
};
if (Settings.store.skippedUpdate !== newVersion && isOutdated(oldVersion, newVersion)) {
if (State.store.skippedUpdate !== newVersion && isOutdated(oldVersion, newVersion)) {
openNewUpdateWindow();
}
} catch (e) {

View File

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