94 Commits

Author SHA1 Message Date
Vendicated
cc62903b9c bump to v0.4.2 2023-10-27 00:43:02 +02:00
Vendicated
b17370cc7b add arm64 venmic binary 2023-10-27 00:42:50 +02:00
Vendicated
886d02f7c3 screenaudio: show better error if glibcxx too old 2023-10-27 00:27:35 +02:00
Nico
0cad71f6ae fix: adjust hiding download button to new discord update (#176) 2023-10-26 23:23:31 +02:00
Vendicated
b5eac15b42 Use correct OS in U-Agent ~ ~should fix captchas 2023-10-26 22:07:35 +02:00
rini
19c3112d52 fix spellcheck patch (#169) 2023-10-25 20:57:08 +02:00
Vendicated
1c308d0e2c Bump to v0.4.1 2023-10-25 00:32:06 +02:00
Vendicated
7f6db5eeda Linux ScreenAudio: Remove Duplicates 2023-10-25 00:31:53 +02:00
Vendicated
10b38e5b41 make updater window close button close proper window 2023-10-25 00:30:42 +02:00
wearr
28282d1d76 Use local shiggy gif for splash (#157)
Co-authored-by: wearrrrr <contact@wearr.dev>
Co-authored-by: V <vendicated@riseup.net>
2023-10-25 00:25:17 +02:00
MiMillieuh
e1512b72f4 Fix : VM class for AppImage has broken Dekstop integrations (#160) 2023-10-25 00:24:49 +02:00
AAGaming
46a2314173 fix turnary soup in main window options (#164) 2023-10-25 00:24:37 +02:00
Vendicated
e6dc026708 Fix for latest canary 2023-10-24 23:12:22 +02:00
Lewis Crichton
cac307d1fc ci: pin wgr to new version (#163) 2023-10-23 17:44:54 +00:00
Ryan Cao
4b8f374856 feat: allow disabling updates check (#155)
Co-authored-by: V <vendicated@riseup.net>
2023-10-22 17:10:25 +02:00
Jack
e6cc11fc0e Build arm64 Linux packages (#151) 2023-10-22 16:53:58 +02:00
Vendicated
43ca479fc8 bump to v0.4.0 2023-10-21 22:27:50 +02:00
Vendicated
7b0f64a9fc remove legacy vencorddesktop aliases 2023-10-21 22:25:16 +02:00
V
573a953a2f add linux audio screensharing (#130)
Co-authored-by: V <vendicated@riseup.net>
Co-authored-by: kaitlynkitty <87152313+kaitlynkittyy@users.noreply.github.com>
Co-authored-by: Curve <fynnbwdt@gmail.com>
2023-10-21 22:15:55 +02:00
Ryan Cao
841cdcf672 feat: add splash window theming (#52)
Co-authored-by: V <vendicated@riseup.net>
2023-10-14 03:04:44 +00:00
Vendicated
0d93e08e99 arrpc: migrate from websocket + plugin to electron ipc 2023-10-13 04:02:10 +02:00
Tornike Khintibidze
c445c6194f MacOs: properly request microphone and camera permission (#121)
Co-authored-by: V <vendicated@riseup.net>
2023-10-12 04:24:16 +02:00
Nick
e29d293855 README: hyperlink Vencord (#143) 2023-10-11 17:24:39 +02:00
Vendicated
e13a4eacb1 bump deps 2023-10-11 17:21:32 +02:00
Vendicated
c070761f9d Bump electron to v27 2023-10-11 17:19:45 +02:00
Lewis Crichton
ae86c28247 ci: force fork user to be bot account (#141) 2023-10-07 16:19:42 +00:00
Lewis Crichton
9c95956c96 ci: allow manual submissions (#140) 2023-10-07 18:10:07 +02:00
Vendicated
45f56c63a0 bump to v0.3.3 2023-09-30 22:45:22 +02:00
Tornike Khintibidze
89af4316d3 Optimize the menu bar for macOS (#120)
Co-authored-by: V <vendicated@riseup.net>
2023-09-30 20:43:27 +00:00
Cynthia Foxwell
a9bfb857ae Bump Electron to 25.8.4 for CVE-2023-5217 (#134) 2023-09-30 19:38:32 +00:00
Vendicated
b9e411ac90 fix deb/rpm icon 2023-09-28 02:31:42 +02:00
Zyrouge
670de01938 fix: appimage not relaunching (#128) 2023-09-27 04:52:00 +02:00
Lewis Crichton
ef064eba3d ci: switch to vedantmgoyal2009/winget-releaser (#103)
Co-authored-by: V <vendicated@riseup.net>
2023-09-26 00:00:17 +02:00
V
d5f63da939 bump to v0.3.2 2023-09-25 22:11:53 +02:00
Pierre
2aadc61af9 Downgrade electron to v25.8.2 (#126) 2023-09-25 22:09:46 +02:00
Zyrouge
061fec44af fix icon missing in some windows (#124)
Co-authored-by: V <vendicated@riseup.net>
2023-09-25 01:19:54 +00:00
V
b876f450c3 bump arrpc 2023-09-25 03:17:50 +02:00
V
5f5febda9d bump electron for important security fixes 2023-09-25 03:12:51 +02:00
Lewis Crichton
94ba59afb5 fix: add correct WMClass to desktop file (#108)
resolves #96
2023-08-31 22:52:12 +02:00
V
566737017c ci: publish as draft 2023-08-27 22:40:37 +02:00
Lewis Crichton
196ee4e42c ci: winget (#101) 2023-08-27 22:27:23 +02:00
V
9bb02f8581 security: make ipc only allow discord origins 2023-08-25 15:32:06 +02:00
V
c76d7195a5 Bump to 0.3.0 2023-08-16 02:05:17 +02:00
V
50a103710d Add Vesktop section to Settings customSections 2023-08-16 02:02:23 +02:00
DaBluLite
e6e66e775c Fix platformClass: change "platform-windows" to "platform-win" (#94) 2023-08-14 13:55:47 +00:00
V
a5ec031a2f Fix windows titlebar on canary 2023-08-12 04:01:10 +02:00
V
4dceadbbd2 Bump to 0.2.9 (for real this time) 2023-08-12 03:15:37 +02:00
V
6ee920ff2c Show error toast when selecting invalid venchord dir 2023-08-12 03:15:21 +02:00
V
28ad4a6f73 Improve valid vencord install checks 2023-08-12 03:13:07 +02:00
V
c5ac3e64a6 Bump to 0.2.9 2023-08-12 03:09:38 +02:00
Justice Almanzar
6dc26aea6a vecord: download proper vesktop specific preload & renderer css (#90)
Co-authored-by: V <vendicated@riseup.net>
2023-08-12 03:09:14 +02:00
Hugo C
9003b94f85 mac: fix dock notification badge (#88) 2023-08-08 13:08:32 +02:00
V
f7b7931847 only pass partial info 2023-08-07 00:48:23 +02:00
V
b87bcaefe9 Wayland: Skip our screenshare screen picker 2023-08-07 00:39:38 +02:00
V
3108de7c79 Port Discord's windows title bar (#86) 2023-08-07 00:23:27 +02:00
Lewis Crichton
57006e7e52 feat: portable zip (#85) 2023-08-06 02:05:48 +02:00
Nickyux
8f1ea1f440 Update Settings Text to Vesktop (#84) 2023-08-05 17:18:12 +00:00
TymanWasTaken
26b6fb13d4 Update pnpm in package.json (#79)
Co-authored-by: V <vendicated@riseup.net>
2023-08-04 20:36:28 +02:00
V
e1971f55a0 bump arrpc; fixes false positives 2023-08-04 20:21:49 +02:00
V
ba2618878e add vencord themes watcher 2023-08-04 19:39:33 +02:00
V
87595deae7 Fix static title 2023-08-02 23:59:02 +02:00
V
6ab7030fdf Bump to v0.2.8 2023-08-02 23:26:12 +02:00
V
5ba84e8a0d but here's the bumper 2023-08-02 23:26:00 +02:00
V
61f9559984 Add Enable Menu setting 2023-07-28 21:54:17 +02:00
V
5fa9264bdb mac: Hide tray related settings 2023-07-28 21:45:22 +02:00
V
c9f0920f71 Bump arRPC 2023-07-28 21:39:50 +02:00
V
f502d04b5f Bump dependencies 2023-07-28 21:38:17 +02:00
Ryan Cao
08090e3764 feat: use standardized icon for macOS (#48)
Co-authored-by: V <vendicated@riseup.net>
2023-07-27 01:03:29 +00:00
Hugo C
a95f7f8fbd Remove macOS tray icon (#68)
Co-authored-by: V <vendicated@riseup.net>
2023-07-27 00:20:15 +00:00
Ryan Cao
9d62cc9437 fix: macOS updater URL for different architectures (#69)
Co-authored-by: V <vendicated@riseup.net>
2023-07-27 02:16:17 +02:00
V
2b4f7a07d2 Fix tray logic 2023-07-27 00:46:25 +02:00
V
be9642eff1 Bump to v0.2.7 2023-07-26 02:28:23 +02:00
Ryan Cao
d884b7d3d6 feat: add "Reset Vesktop" option to menu & tray (#53)
Co-authored-by: V <vendicated@riseup.net>
2023-07-14 00:14:48 +00:00
Ryan Cao
6d35be79b8 feat: use inset title bar style on macOS (#47)
Co-authored-by: V <vendicated@riseup.net>
2023-07-14 00:07:48 +00:00
Ryan Cao
70ca06eb16 fix: remove duplicate quit items on macOS (#49)
Co-authored-by: V <vendicated@riseup.net>
2023-07-13 17:07:19 +00:00
V
210ddbae06 Rename to Vesktop (#57) 2023-07-13 19:03:13 +02:00
V
3ee57831b9 Fix maximise button being wrongly disabled 2023-07-12 18:56:50 +02:00
Ryan Cao
72f83c3ac4 feat: add hide menu bar items on macOS (#50)
Co-authored-by: V <vendicated@riseup.net>
2023-07-11 21:35:15 +02:00
Ryan Cao
13d87dc85e fix: improve app hiding functionality (#51) 2023-07-11 20:57:10 +02:00
Flag
0415cb77f7 feat: implement transparency for Windows (#46) 2023-07-11 20:45:30 +02:00
V
b4da701080 Bump to 0.2.6 2023-07-05 00:49:43 +02:00
V
ed2361ff22 Don't show Menubar on alt press
Closes #45
2023-07-03 22:25:11 +02:00
V
c587e93c56 oop oop 2023-07-03 22:22:35 +02:00
V
6ba0896a6e Implement Screenshare Quality & fps settings 2023-07-03 22:12:26 +02:00
V
843b57e03e Support invite modal launch from web 2023-06-30 18:57:28 +02:00
V
49fb4c68b6 Updater Popup: Add Changelog; Make about page prettier 2023-06-26 01:42:51 +02:00
V
477ecbb4ba Cleanup 2023-06-26 00:41:52 +02:00
V
4abce9d084 Fix some Context Menus being broken by regression 2023-06-26 00:05:08 +02:00
V
7f54858b27 Add SpellCheck toggle in textarea context menu 2023-06-25 16:48:46 +02:00
V
be176fab71 Update README.md (#42) 2023-06-25 04:55:06 +02:00
V
e60f04bb79 Fix DevTools context menu 2023-06-25 04:20:26 +02:00
V
17f1c4ff6f Bump to v0.2.4 2023-06-25 03:45:44 +02:00
V
a7ded71404 Add SpellCheck suggestions 2023-06-25 03:44:19 +02:00
V
31799ccfb0 Add Notification badge 2023-06-23 17:20:54 +02:00
85 changed files with 3283 additions and 1245 deletions

View File

@@ -4,3 +4,5 @@
# all permissions at the defaults (public repos read only, 0 permissions): # all permissions at the defaults (public repos read only, 0 permissions):
# https://github.com/settings/personal-access-tokens/new # https://github.com/settings/personal-access-tokens/new
GITHUB_TOKEN= GITHUB_TOKEN=
ELECTRON_LAUNCH_FLAGS="--ozone-platform-hint=auto --enable-webrtc-pipewire-capturer --enable-features=WaylandWindowDecorations"

25
.github/workflows/winget-submission.yml vendored Normal file
View File

@@ -0,0 +1,25 @@
name: Submit to Winget Community Repo
on:
release:
types: [published]
workflow_dispatch:
inputs:
tag:
type: string
description: The release tag to submit
required: true
jobs:
winget:
name: Publish winget package
runs-on: ubuntu-latest
steps:
- name: Submit package to Winget Community Repo
uses: vedantmgoyal2009/winget-releaser@e68d386d5d6a1cef8cb0fb5e62b77ebcb83e7d58 # v2
with:
identifier: Vencord.Vesktop
token: ${{ secrets.WINGET_PAT }}
installers-regex: '\.exe$'
release-tag: ${{ inputs.tag || github.event.release.tag_name }}
fork-user: shiggybot

3
.gitignore vendored
View File

@@ -1,3 +1,6 @@
dist dist
node_modules node_modules
.env .env
.DS_Store
.idea/
.pnpm-store/

View File

@@ -20,5 +20,6 @@
}, },
"[jsonc]": { "[jsonc]": {
"editor.defaultFormatter": "esbenp.prettier-vscode" "editor.defaultFormatter": "esbenp.prettier-vscode"
} },
"cSpell.words": ["Vesktop"]
} }

View File

@@ -1,8 +1,6 @@
# Vencord Desktop # Vesktop
Vencord Desktop is a cross platform desktop app aiming to give you a snappier Discord experience with Vencord pre-installed Vesktop is a cross platform desktop app aiming to give you a snappier Discord experience with [Vencord](https://github.com/Vendicated/Vencord) pre-installed
Vencord Desktop is currently in beta
**Not yet supported**: **Not yet supported**:
- Global Keybinds - Global Keybinds
@@ -15,11 +13,11 @@ Bug reports, feature requests & contributions are highly appreciated!!
### Windows ### Windows
Download and run Vencord-Desktop-Setup-VERSION.exe from [releases](https://github.com/Vencord/Desktop/releases/latest) Download and run Vesktop-Setup-VERSION.exe from [releases](https://github.com/Vencord/Vesktop/releases/latest)
### Mac ### Mac
Download and run Vencord-Desktop-VERSION.dmg from [releases](https://github.com/Vencord/Desktop/releases/latest) Download and run Vesktop-VERSION.dmg from [releases](https://github.com/Vencord/Vesktop/releases/latest)
### Linux ### Linux
@@ -29,15 +27,15 @@ Install [vencord-desktop-git](https://aur.archlinux.org/packages/vencord-desktop
#### Ubuntu/Debian based #### Ubuntu/Debian based
Download Vencord-Desktop-VERSION.deb from [releases](https://github.com/Vencord/Desktop/releases/latest) Download Vesktop-VERSION.deb from [releases](https://github.com/Vencord/Vesktop/releases/latest)
#### Fedora/RHEL based #### Fedora/RHEL based
Download Vencord-Desktop-VERSION.rpm from [releases](https://github.com/Vencord/Desktop/releases/latest) Download Vesktop-VERSION.rpm from [releases](https://github.com/Vencord/Vesktop/releases/latest)
#### Other #### Other
Either download Vencord-Desktop-VERSION.AppImage and just run it directly or grab Vencord-Desktop-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 `vencorddesktop`.
A flatpak is planned, if you want packages for other repos, feel free to create them and they can be linked as unofficial here A flatpak is planned, if you want packages for other repos, feel free to create them and they can be linked as unofficial here
@@ -46,8 +44,8 @@ A flatpak is planned, if you want packages for other repos, feel free to create
Packaging will create builds in the dist/ folder. You can then install them like mentioned above or distribute them Packaging will create builds in the dist/ folder. You can then install them like mentioned above or distribute them
```sh ```sh
git clone https://github.com/Vencord/Desktop git clone https://github.com/Vencord/Vesktop
cd Desktop cd Vesktop
# Install Dependencies # Install Dependencies
pnpm i pnpm i

BIN
build/icon.icns Normal file

Binary file not shown.

View File

@@ -1,6 +1,6 @@
{ {
"name": "VencordDesktop", "name": "VencordDesktop",
"version": "0.2.3", "version": "0.4.2",
"private": true, "private": true,
"description": "", "description": "",
"keywords": [], "keywords": [],
@@ -23,41 +23,44 @@
"watch": "pnpm build --watch" "watch": "pnpm build --watch"
}, },
"dependencies": { "dependencies": {
"arrpc": "github:OpenAsar/arrpc#3eb5d36a5e9295d3aeafc49975df5d399eb627fd" "arrpc": "github:OpenAsar/arrpc#89f4da610ccfac93f461826a446a17cd3b23953d"
},
"optionalDependencies": {
"@vencord/venmic": "^1.6.0"
}, },
"devDependencies": { "devDependencies": {
"@fal-works/esbuild-plugin-global-externals": "^2.1.2", "@fal-works/esbuild-plugin-global-externals": "^2.1.2",
"@types/node": "^18.15.11", "@types/node": "^20.8.4",
"@types/react": "^18.0.33", "@types/react": "^18.2.28",
"@typescript-eslint/eslint-plugin": "^5.57.1", "@typescript-eslint/eslint-plugin": "^6.7.5",
"@typescript-eslint/parser": "^5.57.1", "@typescript-eslint/parser": "^6.7.5",
"@vencord/types": "^0.1.2", "@vencord/types": "^0.1.2",
"dotenv": "^16.0.3", "dotenv": "^16.3.1",
"electron": "^25.2.0", "electron": "^27.0.0",
"electron-builder": "^23.6.0", "electron-builder": "^24.6.4",
"esbuild": "^0.17.14", "esbuild": "^0.19.4",
"eslint": "^8.38.0", "eslint": "^8.51.0",
"eslint-config-prettier": "^8.8.0", "eslint-config-prettier": "^9.0.0",
"eslint-import-resolver-alias": "^1.1.2", "eslint-import-resolver-alias": "^1.1.2",
"eslint-plugin-license-header": "^0.6.0", "eslint-plugin-license-header": "^0.6.0",
"eslint-plugin-path-alias": "^1.0.0", "eslint-plugin-path-alias": "^1.0.0",
"eslint-plugin-prettier": "^4.2.1", "eslint-plugin-prettier": "^5.0.1",
"eslint-plugin-simple-import-sort": "^10.0.0", "eslint-plugin-simple-import-sort": "^10.0.0",
"eslint-plugin-unused-imports": "^2.0.0", "eslint-plugin-unused-imports": "^3.0.0",
"prettier": "^2.8.7", "prettier": "^3.0.3",
"source-map-support": "^0.5.21", "source-map-support": "^0.5.21",
"tsx": "^3.12.6", "tsx": "^3.13.0",
"type-fest": "^3.8.0", "type-fest": "^4.4.0",
"typescript": "^5.0.2" "typescript": "^5.2.2"
}, },
"packageManager": "pnpm@8.1.1", "packageManager": "pnpm@8.6.11",
"engines": { "engines": {
"node": ">=18", "node": ">=18",
"pnpm": ">=8" "pnpm": ">=8"
}, },
"build": { "build": {
"appId": "dev.vencord.desktop", "appId": "dev.vencord.desktop",
"productName": "Vencord Desktop", "productName": "Vesktop",
"files": [ "files": [
"!*", "!*",
"dist/js", "dist/js",
@@ -66,20 +69,47 @@
"LICENSE" "LICENSE"
], ],
"linux": { "linux": {
"icon": "build/icon.icns",
"category": "Network", "category": "Network",
"maintainer": "vendicated+vencord-desktop@riseup.net", "maintainer": "vendicated+vesktop@riseup.net",
"target": [ "target": [
"deb", {
"tar.gz", "target": "deb",
"rpm", "arch": [
"AppImage" "x64",
"arm64"
]
},
{
"target": "tar.gz",
"arch": [
"x64",
"arm64"
]
},
{
"target": "rpm",
"arch": [
"x64",
"arm64"
]
},
{
"target": "AppImage",
"arch": [
"x64",
"arm64"
]
}
], ],
"desktop": { "desktop": {
"Name": "Vencord Desktop", "Name": "Vesktop",
"GenericName": "Internet Messenger", "GenericName": "Internet Messenger",
"Type": "Application", "Type": "Application",
"Categories": "Network;InstantMessaging;Chat;", "Categories": "Network;InstantMessaging;Chat;",
"Keywords": "discord;vencord;electron;chat;" "Keywords": "discord;vencord;electron;chat;",
"WMClass": "VencordDesktop",
"StartupWMClass": "VencordDesktop"
} }
}, },
"mac": { "mac": {
@@ -92,7 +122,13 @@
] ]
} }
], ],
"category": "Network" "category": "Network",
"extendInfo": {
"NSMicrophoneUsageDescription": "This app needs access to the microphone",
"NSCameraUsageDescription": "This app needs access to the camera",
"com.apple.security.device.audio-input": true,
"com.apple.security.device.camera": true
}
}, },
"nsis": { "nsis": {
"include": "build/installer.nsh", "include": "build/installer.nsh",
@@ -101,12 +137,11 @@
"win": { "win": {
"target": [ "target": [
"nsis", "nsis",
"portable" "zip"
] ]
}, },
"publish": { "publish": {
"provider": "github", "provider": "github"
"releaseType": "release"
} }
} }
} }

2384
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,10 +1,11 @@
/* /*
* SPDX-License-Identifier: GPL-3.0 * SPDX-License-Identifier: GPL-3.0
* Vencord Desktop, a desktop app aiming to give you a snappier Discord Experience * Vesktop, a desktop app aiming to give you a snappier Discord Experience
* Copyright (c) 2023 Vendicated and Vencord contributors * Copyright (c) 2023 Vendicated and Vencord contributors
*/ */
import { BuildContext, BuildOptions, context } from "esbuild"; import { BuildContext, BuildOptions, context } from "esbuild";
import { copyFile } from "fs/promises";
import vencordDep from "./vencordDep.mjs"; import vencordDep from "./vencordDep.mjs";
@@ -33,7 +34,23 @@ async function createContext(options: BuildOptions) {
contexts.push(await context(options)); contexts.push(await context(options));
} }
async function copyVenmic() {
if (process.platform !== "linux") return;
return Promise.all([
copyFile(
"./node_modules/@vencord/venmic/prebuilds/venmic-addon-linux-x64/node-napi-v7.node",
"./static/dist/venmic-x64.node"
),
copyFile(
"./node_modules/@vencord/venmic/prebuilds/venmic-addon-linux-arm64/node-napi-v7.node",
"./static/dist/venmic-arm64.node"
)
]).catch(() => console.warn("Failed to copy venmic. Building without venmic support"));
}
await Promise.all([ await Promise.all([
copyVenmic(),
createContext({ createContext({
...NodeCommonOpts, ...NodeCommonOpts,
entryPoints: ["src/main/index.ts"], entryPoints: ["src/main/index.ts"],
@@ -54,7 +71,7 @@ await Promise.all([
}), }),
createContext({ createContext({
...CommonOpts, ...CommonOpts,
globalName: "VencordDesktop", globalName: "Vesktop",
entryPoints: ["src/renderer/index.ts"], entryPoints: ["src/renderer/index.ts"],
outfile: "dist/js/renderer.js", outfile: "dist/js/renderer.js",
format: "iife", format: "iife",

View File

@@ -1,6 +1,6 @@
/* /*
* SPDX-License-Identifier: GPL-3.0 * SPDX-License-Identifier: GPL-3.0
* Vencord Desktop, a desktop app aiming to give you a snappier Discord Experience * Vesktop, a desktop app aiming to give you a snappier Discord Experience
* Copyright (c) 2023 Vendicated and Vencord contributors * Copyright (c) 2023 Vendicated and Vencord contributors
*/ */

View File

@@ -1,6 +1,6 @@
/* /*
* SPDX-License-Identifier: GPL-3.0 * SPDX-License-Identifier: GPL-3.0
* Vencord Desktop, a desktop app aiming to give you a snappier Discord Experience * Vesktop, a desktop app aiming to give you a snappier Discord Experience
* Copyright (c) 2023 Vendicated and Vencord contributors * Copyright (c) 2023 Vendicated and Vencord contributors
*/ */

View File

@@ -1,5 +1,5 @@
/* /*
* SPDX-License-Identifier: GPL-3.0 * SPDX-License-Identifier: GPL-3.0
* Vencord Desktop, a desktop app aiming to give you a snappier Discord Experience * Vesktop, a desktop app aiming to give you a snappier Discord Experience
* Copyright (c) 2023 Vendicated and Vencord contributors * Copyright (c) 2023 Vendicated and Vencord contributors
*/ */

View File

@@ -1,6 +1,6 @@
/* /*
* SPDX-License-Identifier: GPL-3.0 * SPDX-License-Identifier: GPL-3.0
* Vencord Desktop, a desktop app aiming to give you a snappier Discord Experience * Vesktop, a desktop app aiming to give you a snappier Discord Experience
* Copyright (c) 2023 Vendicated and Vencord contributors * Copyright (c) 2023 Vendicated and Vencord contributors
*/ */
@@ -8,4 +8,4 @@ import "./utils/dotenv";
import { spawnNodeModuleBin } from "./utils/spawn.mjs"; import { spawnNodeModuleBin } from "./utils/spawn.mjs";
spawnNodeModuleBin("electron", ["."]); spawnNodeModuleBin("electron", [".", ...(process.env.ELECTRON_LAUNCH_FLAGS?.split(" ") ?? [])]);

View File

@@ -1,6 +1,6 @@
/* /*
* SPDX-License-Identifier: GPL-3.0 * SPDX-License-Identifier: GPL-3.0
* Vencord Desktop, a desktop app aiming to give you a snappier Discord Experience * Vesktop, a desktop app aiming to give you a snappier Discord Experience
* Copyright (c) 2023 Vendicated and Vencord contributors * Copyright (c) 2023 Vendicated and Vencord contributors
*/ */

View File

@@ -1,6 +1,6 @@
/* /*
* SPDX-License-Identifier: GPL-3.0 * SPDX-License-Identifier: GPL-3.0
* Vencord Desktop, a desktop app aiming to give you a snappier Discord Experience * Vesktop, a desktop app aiming to give you a snappier Discord Experience
* Copyright (c) 2023 Vendicated and Vencord contributors * Copyright (c) 2023 Vendicated and Vencord contributors
*/ */

View File

@@ -1,6 +1,6 @@
/* /*
* SPDX-License-Identifier: GPL-3.0 * SPDX-License-Identifier: GPL-3.0
* Vencord Desktop, a desktop app aiming to give you a snappier Discord Experience * Vesktop, a desktop app aiming to give you a snappier Discord Experience
* Copyright (c) 2023 Vendicated and Vencord contributors * Copyright (c) 2023 Vendicated and Vencord contributors
*/ */

8
src/globals.d.ts vendored
View File

@@ -1,13 +1,13 @@
/* /*
* SPDX-License-Identifier: GPL-3.0 * SPDX-License-Identifier: GPL-3.0
* Vencord Desktop, a desktop app aiming to give you a snappier Discord Experience * Vesktop, a desktop app aiming to give you a snappier Discord Experience
* Copyright (c) 2023 Vendicated and Vencord contributors * Copyright (c) 2023 Vendicated and Vencord contributors
*/ */
declare global { declare global {
export var VencordDesktopNative: typeof import("preload/VencordDesktopNative").VencordDesktopNative; export var VesktopNative: typeof import("preload/VesktopNative").VesktopNative;
export var VencordDesktop: typeof import("renderer/index"); export var Vesktop: typeof import("renderer/index");
export var vcdLS: typeof localStorage; export var VCDP: any;
export var IS_DEV: boolean; export var IS_DEV: boolean;
} }

View File

@@ -1,13 +1,12 @@
/* /*
* SPDX-License-Identifier: GPL-3.0 * SPDX-License-Identifier: GPL-3.0
* Vencord Desktop, a desktop app aiming to give you a snappier Discord Experience * Vesktop, a desktop app aiming to give you a snappier Discord Experience
* Copyright (c) 2023 Vendicated and Vencord contributors * Copyright (c) 2023 Vendicated and Vencord contributors
*/ */
import { app, BrowserWindow } from "electron"; import { BrowserWindow } from "electron";
import { readFileSync } from "fs";
import { join } from "path"; import { join } from "path";
import { ICON_PATH, STATIC_DIR } from "shared/paths"; import { ICON_PATH, VIEW_DIR } from "shared/paths";
import { makeLinksOpenExternally } from "./utils/makeLinksOpenExternally"; import { makeLinksOpenExternally } from "./utils/makeLinksOpenExternally";
@@ -15,14 +14,15 @@ export function createAboutWindow() {
const about = new BrowserWindow({ const about = new BrowserWindow({
center: true, center: true,
autoHideMenuBar: true, autoHideMenuBar: true,
icon: ICON_PATH icon: ICON_PATH,
webPreferences: {
preload: join(__dirname, "updaterPreload.js")
}
}); });
makeLinksOpenExternally(about); makeLinksOpenExternally(about);
const html = readFileSync(join(STATIC_DIR, "about.html"), "utf-8").replaceAll("%VERSION%", app.getVersion()); about.loadFile(join(VIEW_DIR, "about.html"));
about.loadURL("data:text/html;charset=utf-8," + html);
return about; return about;
} }

56
src/main/appBadge.ts Normal file
View File

@@ -0,0 +1,56 @@
/*
* 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 { app, NativeImage, nativeImage } from "electron";
import { join } from "path";
import { BADGE_DIR } from "shared/paths";
const imgCache = new Map<number, NativeImage>();
function loadBadge(index: number) {
const cached = imgCache.get(index);
if (cached) return cached;
const img = nativeImage.createFromPath(join(BADGE_DIR, `${index}.ico`));
imgCache.set(index, img);
return img;
}
let lastIndex: null | number = -1;
export function setBadgeCount(count: number) {
switch (process.platform) {
case "linux":
if (count === -1) count = 0;
app.setBadgeCount(count);
break;
case "darwin":
if (count === 0) {
app.dock.setBadge("");
break;
}
app.dock.setBadge(count === -1 ? "•" : count.toString());
break;
case "win32":
const [index, description] = getBadgeIndexAndDescription(count);
if (lastIndex === index) break;
lastIndex = index;
// circular import shenanigans
const { mainWin } = require("./mainWindow") as typeof import("./mainWindow");
mainWin.setOverlayIcon(index === null ? null : loadBadge(index), description);
break;
}
}
function getBadgeIndexAndDescription(count: number): [number | null, string] {
if (count === -1) return [11, "Unread Messages"];
if (count === 0) return [null, "No Notifications"];
const index = Math.max(1, Math.min(count, 10));
return [index, `${index} Notification`];
}

View File

@@ -1,21 +1,38 @@
/* /*
* SPDX-License-Identifier: GPL-3.0 * SPDX-License-Identifier: GPL-3.0
* Vencord Desktop, a desktop app aiming to give you a snappier Discord Experience * Vesktop, a desktop app aiming to give you a snappier Discord Experience
* Copyright (c) 2023 Vendicated and Vencord contributors * Copyright (c) 2023 Vendicated and Vencord contributors
*/ */
import Server from "arrpc"; import Server from "arrpc";
import { send as sendToBridge } from "arrpc/src/bridge"; import { IpcEvents } from "shared/IpcEvents";
import { mainWin } from "./mainWindow";
import { Settings } from "./settings"; import { Settings } from "./settings";
let server: any; let server: any;
const inviteCodeRegex = /^(\w|-)+$/;
export async function initArRPC() { export async function initArRPC() {
if (server || !Settings.store.arRPC) return; if (server || !Settings.store.arRPC) return;
server = await new Server(); try {
server.on("activity", sendToBridge); server = await new Server();
server.on("activity", (data: any) => mainWin.webContents.send(IpcEvents.ARRPC_ACTIVITY, JSON.stringify(data)));
server.on("invite", (invite: string, callback: (valid: boolean) => void) => {
invite = String(invite);
if (!inviteCodeRegex.test(invite)) return callback(false);
mainWin.webContents
// Safety: Result of JSON.stringify should always be safe to equal
// Also, just to be super super safe, invite is regex validated above
.executeJavaScript(`Vesktop.openInviteModal(${JSON.stringify(invite)})`)
.then(callback);
});
} catch (e) {
console.error("Failed to start arRPC server", e);
}
} }
Settings.addChangeListener("arRPC", initArRPC); Settings.addChangeListener("arRPC", initArRPC);

View File

@@ -1,6 +1,6 @@
/* /*
* SPDX-License-Identifier: GPL-3.0 * SPDX-License-Identifier: GPL-3.0
* Vencord Desktop, a desktop app aiming to give you a snappier Discord Experience * Vesktop, a desktop app aiming to give you a snappier Discord Experience
* Copyright (c) 2023 Vendicated and Vencord contributors * Copyright (c) 2023 Vendicated and Vencord contributors
*/ */

View File

@@ -1,6 +1,6 @@
/* /*
* SPDX-License-Identifier: GPL-3.0 * SPDX-License-Identifier: GPL-3.0
* Vencord Desktop, a desktop app aiming to give you a snappier Discord Experience * Vesktop, a desktop app aiming to give you a snappier Discord Experience
* Copyright (c) 2023 Vendicated and Vencord contributors * Copyright (c) 2023 Vendicated and Vencord contributors
*/ */
@@ -11,16 +11,26 @@ export const DATA_DIR = process.env.VENCORD_USER_DATA_DIR || join(app.getPath("u
export const VENCORD_SETTINGS_DIR = join(DATA_DIR, "settings"); export const VENCORD_SETTINGS_DIR = join(DATA_DIR, "settings");
export const VENCORD_QUICKCSS_FILE = join(VENCORD_SETTINGS_DIR, "quickCss.css"); 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_SETTINGS_FILE = join(VENCORD_SETTINGS_DIR, "settings.json");
export const VENCORD_THEMES_DIR = join(DATA_DIR, "themes");
// needs to be inline require because of circular dependency // needs to be inline require because of circular dependency
// as otherwise "DATA_DIR" (which is used by ./settings) will be uninitialised // as otherwise "DATA_DIR" (which is used by ./settings) will be uninitialised
export const VENCORD_FILES_DIR = export const VENCORD_FILES_DIR =
(require("./settings") as typeof import("./settings")).Settings.store.vencordDir || join(DATA_DIR, "vencordDist"); (require("./settings") as typeof import("./settings")).Settings.store.vencordDir || join(DATA_DIR, "vencordDist");
export const USER_AGENT = `VencordDesktop/${app.getVersion()} (https://github.com/Vencord/Desktop)`; export const USER_AGENT = `Vesktop/${app.getVersion()} (https://github.com/Vencord/Vesktop)`;
// dimensions shamelessly stolen from Discord Desktop :3 // dimensions shamelessly stolen from Discord Desktop :3
export const MIN_WIDTH = 940; export const MIN_WIDTH = 940;
export const MIN_HEIGHT = 500; export const MIN_HEIGHT = 500;
export const DEFAULT_WIDTH = 1280; export const DEFAULT_WIDTH = 1280;
export const DEFAULT_HEIGHT = 720; export const DEFAULT_HEIGHT = 720;
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",
windows:
"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

@@ -1,6 +1,6 @@
/* /*
* SPDX-License-Identifier: GPL-3.0 * SPDX-License-Identifier: GPL-3.0
* Vencord Desktop, a desktop app aiming to give you a snappier Discord Experience * Vesktop, a desktop app aiming to give you a snappier Discord Experience
* Copyright (c) 2023 Vendicated and Vencord contributors * Copyright (c) 2023 Vendicated and Vencord contributors
*/ */
@@ -9,12 +9,13 @@ import { BrowserWindow } from "electron/main";
import { copyFileSync, mkdirSync, readdirSync } from "fs"; import { copyFileSync, mkdirSync, readdirSync } from "fs";
import { join } from "path"; import { join } from "path";
import { SplashProps } from "shared/browserWinProperties"; import { SplashProps } from "shared/browserWinProperties";
import { STATIC_DIR } from "shared/paths"; import { ICON_PATH, VIEW_DIR } from "shared/paths";
import { autoStart } from "./autoStart"; import { autoStart } from "./autoStart";
import { DATA_DIR } from "./constants"; import { DATA_DIR } from "./constants";
import { createWindows } from "./mainWindow"; import { createWindows } from "./mainWindow";
import { Settings } from "./settings"; import { Settings } from "./settings";
import { makeLinksOpenExternally } from "./utils/makeLinksOpenExternally";
interface Data { interface Data {
minimizeToTray: boolean; minimizeToTray: boolean;
@@ -30,10 +31,13 @@ export function createFirstLaunchTour() {
frame: true, frame: true,
autoHideMenuBar: true, autoHideMenuBar: true,
height: 470, height: 470,
width: 550 width: 550,
icon: ICON_PATH
}); });
win.loadFile(join(STATIC_DIR, "first-launch.html")); makeLinksOpenExternally(win);
win.loadFile(join(VIEW_DIR, "first-launch.html"));
win.webContents.addListener("console-message", (_e, _l, msg) => { win.webContents.addListener("console-message", (_e, _l, msg) => {
if (msg === "cancel") return app.exit(); if (msg === "cancel") return app.exit();

View File

@@ -1,6 +1,6 @@
/* /*
* SPDX-License-Identifier: GPL-3.0 * SPDX-License-Identifier: GPL-3.0
* Vencord Desktop, a desktop app aiming to give you a snappier Discord Experience * Vesktop, a desktop app aiming to give you a snappier Discord Experience
* Copyright (c) 2023 Vendicated and Vencord contributors * Copyright (c) 2023 Vendicated and Vencord contributors
*/ */
@@ -9,10 +9,10 @@ import "./ipc";
import { app, BrowserWindow } from "electron"; import { app, BrowserWindow } from "electron";
import { checkUpdates } from "updater/main"; import { checkUpdates } from "updater/main";
import { ICON_PATH } from "../shared/paths";
import { DATA_DIR } from "./constants"; import { DATA_DIR } from "./constants";
import { createFirstLaunchTour } from "./firstLaunch"; import { createFirstLaunchTour } from "./firstLaunch";
import { createWindows, mainWin } from "./mainWindow"; import { createWindows, mainWin } from "./mainWindow";
import { registerMediaPermissionsHandler } from "./mediaPermissions";
import { registerScreenShareHandler } from "./screenShare"; import { registerScreenShareHandler } from "./screenShare";
import { Settings } from "./settings"; import { Settings } from "./settings";
@@ -24,20 +24,18 @@ if (IS_DEV) {
process.env.VENCORD_USER_DATA_DIR = DATA_DIR; process.env.VENCORD_USER_DATA_DIR = DATA_DIR;
function init() { function init() {
// <-- BEGIN COPY PASTED FROM DISCORD -->
// work around chrome 66 disabling autoplay by default // work around chrome 66 disabling autoplay by default
app.commandLine.appendSwitch("autoplay-policy", "no-user-gesture-required"); app.commandLine.appendSwitch("autoplay-policy", "no-user-gesture-required");
// WinRetrieveSuggestionsOnlyOnDemand: Work around electron 13 bug w/ async spellchecking on Windows. // WinRetrieveSuggestionsOnlyOnDemand: Work around electron 13 bug w/ async spellchecking on Windows.
// HardwareMediaKeyHandling,MediaSessionService: Prevent Discord from registering as a media service. // HardwareMediaKeyHandling,MediaSessionService: Prevent Discord from registering as a media service.
//
// WidgetLayering (Vencord Added): Fix DevTools context menus https://github.com/electron/electron/issues/38790
app.commandLine.appendSwitch( app.commandLine.appendSwitch(
"disable-features", "disable-features",
"WinRetrieveSuggestionsOnlyOnDemand,HardwareMediaKeyHandling,MediaSessionService" "WinRetrieveSuggestionsOnlyOnDemand,HardwareMediaKeyHandling,MediaSessionService,WidgetLayering"
); );
// <-- END COPY PASTED FROM DISCORD -->
app.on("second-instance", (_event, _cmdLine, _cwd, data: any) => { app.on("second-instance", (_event, _cmdLine, _cwd, data: any) => {
if (data.IS_DEV) app.quit(); if (data.IS_DEV) app.quit();
else if (mainWin) { else if (mainWin) {
@@ -50,9 +48,10 @@ function init() {
app.whenReady().then(async () => { app.whenReady().then(async () => {
checkUpdates(); checkUpdates();
if (process.platform === "win32") app.setAppUserModelId("dev.vencord.desktop"); if (process.platform === "win32") app.setAppUserModelId("dev.vencord.desktop");
else if (process.platform === "darwin") app.dock.setIcon(ICON_PATH);
registerScreenShareHandler(); registerScreenShareHandler();
registerMediaPermissionsHandler();
bootstrap(); bootstrap();
app.on("activate", () => { app.on("activate", () => {
@@ -63,10 +62,10 @@ function init() {
if (!app.requestSingleInstanceLock({ IS_DEV })) { if (!app.requestSingleInstanceLock({ IS_DEV })) {
if (IS_DEV) { if (IS_DEV) {
console.log("Vencord Desktop is already running. Quitting previous instance..."); console.log("Vesktop is already running. Quitting previous instance...");
init(); init();
} else { } else {
console.log("Vencord Desktop is already running. Quitting..."); console.log("Vesktop is already running. Quitting...");
app.quit(); app.quit();
} }
} else { } else {

View File

@@ -1,73 +1,92 @@
/* /*
* SPDX-License-Identifier: GPL-3.0 * SPDX-License-Identifier: GPL-3.0
* Vencord Desktop, a desktop app aiming to give you a snappier Discord Experience * Vesktop, a desktop app aiming to give you a snappier Discord Experience
* Copyright (c) 2023 Vendicated and Vencord contributors * Copyright (c) 2023 Vendicated and Vencord contributors
*/ */
import { app, dialog, ipcMain, session, shell } from "electron"; if (process.platform === "linux") import("./virtmic");
import { existsSync, readFileSync, watch } from "fs";
import { execFile } from "child_process";
import { app, BrowserWindow, dialog, RelaunchOptions, session, shell } from "electron";
import { mkdirSync, readFileSync, watch } from "fs";
import { open, readFile } from "fs/promises"; import { open, readFile } from "fs/promises";
import { release } from "os";
import { join } from "path"; import { join } from "path";
import { debounce } from "shared/utils/debounce"; import { debounce } from "shared/utils/debounce";
import { IpcEvents } from "../shared/IpcEvents"; import { IpcEvents } from "../shared/IpcEvents";
import { setBadgeCount } from "./appBadge";
import { autoStart } from "./autoStart"; import { autoStart } from "./autoStart";
import { VENCORD_FILES_DIR, VENCORD_QUICKCSS_FILE } from "./constants"; import { VENCORD_FILES_DIR, VENCORD_QUICKCSS_FILE, VENCORD_THEMES_DIR } from "./constants";
import { mainWin } from "./mainWindow"; import { mainWin } from "./mainWindow";
import { Settings } from "./settings"; import { Settings } from "./settings";
import { handle, handleSync } from "./utils/ipcWrappers";
import { isValidVencordInstall } from "./utils/vencordLoader";
ipcMain.on(IpcEvents.GET_VENCORD_PRELOAD_FILE, e => { handleSync(IpcEvents.GET_VENCORD_PRELOAD_FILE, () => join(VENCORD_FILES_DIR, "vencordDesktopPreload.js"));
e.returnValue = join(VENCORD_FILES_DIR, "preload.js"); handleSync(IpcEvents.GET_VENCORD_RENDERER_SCRIPT, () =>
}); readFileSync(join(VENCORD_FILES_DIR, "vencordDesktopRenderer.js"), "utf-8")
);
ipcMain.on(IpcEvents.GET_VENCORD_RENDERER_SCRIPT, e => { handleSync(IpcEvents.GET_RENDERER_SCRIPT, () => readFileSync(join(__dirname, "renderer.js"), "utf-8"));
e.returnValue = readFileSync(join(VENCORD_FILES_DIR, "vencordDesktopRenderer.js"), "utf-8"); handleSync(IpcEvents.GET_RENDERER_CSS_FILE, () => join(__dirname, "renderer.css"));
});
ipcMain.on(IpcEvents.GET_RENDERER_SCRIPT, e => { handleSync(IpcEvents.GET_SETTINGS, () => Settings.plain);
e.returnValue = readFileSync(join(__dirname, "renderer.js"), "utf-8"); handleSync(IpcEvents.GET_VERSION, () => app.getVersion());
});
ipcMain.on(IpcEvents.GET_RENDERER_CSS_FILE, e => { handleSync(
e.returnValue = join(__dirname, "renderer.css"); IpcEvents.SUPPORTS_WINDOWS_TRANSPARENCY,
}); () => process.platform === "win32" && Number(release().split(".").pop()) >= 22621
);
ipcMain.on(IpcEvents.GET_SETTINGS, e => { handleSync(IpcEvents.AUTOSTART_ENABLED, () => autoStart.isEnabled());
e.returnValue = Settings.plain; handle(IpcEvents.ENABLE_AUTOSTART, autoStart.enable);
}); handle(IpcEvents.DISABLE_AUTOSTART, autoStart.disable);
ipcMain.on(IpcEvents.GET_VERSION, e => { handle(IpcEvents.SET_SETTINGS, (_, settings: typeof Settings.store, path?: string) => {
e.returnValue = app.getVersion();
});
ipcMain.on(IpcEvents.AUTOSTART_ENABLED, e => {
e.returnValue = autoStart.isEnabled();
});
ipcMain.handle(IpcEvents.ENABLE_AUTOSTART, autoStart.enable);
ipcMain.handle(IpcEvents.DISABLE_AUTOSTART, autoStart.disable);
ipcMain.handle(IpcEvents.SET_SETTINGS, (_, settings: typeof Settings.store, path?: string) => {
Settings.setData(settings, path); Settings.setData(settings, path);
}); });
ipcMain.handle(IpcEvents.RELAUNCH, () => { handle(IpcEvents.RELAUNCH, () => {
app.relaunch(); const options: RelaunchOptions = {
args: process.argv.slice(1).concat(["--relaunch"])
};
if (app.isPackaged && process.env.APPIMAGE) {
execFile(process.env.APPIMAGE, options.args);
} else {
app.relaunch(options);
}
app.exit(); app.exit();
}); });
ipcMain.handle(IpcEvents.SHOW_ITEM_IN_FOLDER, (_, path) => { handle(IpcEvents.SHOW_ITEM_IN_FOLDER, (_, path) => {
shell.showItemInFolder(path); shell.showItemInFolder(path);
}); });
ipcMain.handle(IpcEvents.FOCUS, e => { handle(IpcEvents.FOCUS, () => {
e.sender.focus(); if (process.platform === "win32") mainWin.minimize(); // Windows is weird
mainWin.restore();
mainWin.show();
}); });
ipcMain.handle(IpcEvents.CLOSE, e => { handle(IpcEvents.CLOSE, e => {
e.sender.close(); (BrowserWindow.fromWebContents(e.sender) ?? e.sender).close();
}); });
ipcMain.handle(IpcEvents.SPELLCHECK_SET_LANGUAGES, (_, languages: string[]) => { handle(IpcEvents.MINIMIZE, e => {
mainWin.minimize();
});
handle(IpcEvents.MAXIMIZE, e => {
if (mainWin.isMaximized()) {
mainWin.unmaximize();
} else {
mainWin.maximize();
}
});
handle(IpcEvents.SPELLCHECK_SET_LANGUAGES, (_, languages: string[]) => {
const ses = session.defaultSession; const ses = session.defaultSession;
const available = ses.availableSpellCheckerLanguages; const available = ses.availableSpellCheckerLanguages;
@@ -75,20 +94,28 @@ ipcMain.handle(IpcEvents.SPELLCHECK_SET_LANGUAGES, (_, languages: string[]) => {
if (applicable.length) ses.setSpellCheckerLanguages(applicable); if (applicable.length) ses.setSpellCheckerLanguages(applicable);
}); });
ipcMain.handle(IpcEvents.SELECT_VENCORD_DIR, async () => { handle(IpcEvents.SPELLCHECK_REPLACE_MISSPELLING, (e, word: string) => {
e.sender.replaceMisspelling(word);
});
handle(IpcEvents.SPELLCHECK_ADD_TO_DICTIONARY, (e, word: string) => {
e.sender.session.addWordToSpellCheckerDictionary(word);
});
handle(IpcEvents.SELECT_VENCORD_DIR, async () => {
const res = await dialog.showOpenDialog(mainWin!, { const res = await dialog.showOpenDialog(mainWin!, {
properties: ["openDirectory"] properties: ["openDirectory"]
}); });
if (!res.filePaths.length) return "cancelled"; if (!res.filePaths.length) return "cancelled";
const dir = res.filePaths[0]; const dir = res.filePaths[0];
for (const file of ["vencordDesktopMain.js", "preload.js", "vencordDesktopRenderer.js", "renderer.css"]) { if (!isValidVencordInstall(dir)) return "invalid";
if (!existsSync(join(dir, file))) return "invalid";
}
return dir; return dir;
}); });
handle(IpcEvents.SET_BADGE_COUNT, (_, count: number) => setBadgeCount(count));
function readCss() { function readCss() {
return readFile(VENCORD_QUICKCSS_FILE, "utf-8").catch(() => ""); return readFile(VENCORD_QUICKCSS_FILE, "utf-8").catch(() => "");
} }
@@ -103,3 +130,12 @@ open(VENCORD_QUICKCSS_FILE, "a+").then(fd => {
}, 50) }, 50)
); );
}); });
mkdirSync(VENCORD_THEMES_DIR, { recursive: true });
watch(
VENCORD_THEMES_DIR,
{ persistent: false },
debounce(() => {
mainWin?.webContents.postMessage("VencordThemeUpdate", void 0);
})
);

View File

@@ -1,17 +1,38 @@
/* /*
* SPDX-License-Identifier: GPL-3.0 * SPDX-License-Identifier: GPL-3.0
* Vencord Desktop, a desktop app aiming to give you a snappier Discord Experience * Vesktop, a desktop app aiming to give you a snappier Discord Experience
* Copyright (c) 2023 Vendicated and Vencord contributors * Copyright (c) 2023 Vendicated and Vencord contributors
*/ */
import { app, BrowserWindow, BrowserWindowConstructorOptions, Menu, Tray } from "electron"; import {
app,
BrowserWindow,
BrowserWindowConstructorOptions,
dialog,
Menu,
MenuItemConstructorOptions,
nativeTheme,
Tray
} from "electron";
import { rm } from "fs/promises";
import { join } from "path"; import { join } from "path";
import { IpcEvents } from "shared/IpcEvents";
import { isTruthy } from "shared/utils/guards";
import { once } from "shared/utils/once"; import { once } from "shared/utils/once";
import type { SettingsStore } from "shared/utils/SettingsStore";
import { ICON_PATH } from "../shared/paths"; import { ICON_PATH } from "../shared/paths";
import { createAboutWindow } from "./about"; import { createAboutWindow } from "./about";
import { initArRPC } from "./arrpc"; import { initArRPC } from "./arrpc";
import { DEFAULT_HEIGHT, DEFAULT_WIDTH, MIN_HEIGHT, MIN_WIDTH, VENCORD_FILES_DIR } from "./constants"; import {
DATA_DIR,
DEFAULT_HEIGHT,
DEFAULT_WIDTH,
MIN_HEIGHT,
MIN_WIDTH,
UserAgent,
VENCORD_FILES_DIR
} from "./constants";
import { Settings, VencordSettings } from "./settings"; import { Settings, VencordSettings } from "./settings";
import { createSplashWindow } from "./splash"; import { createSplashWindow } from "./splash";
import { makeLinksOpenExternally } from "./utils/makeLinksOpenExternally"; import { makeLinksOpenExternally } from "./utils/makeLinksOpenExternally";
@@ -26,6 +47,27 @@ app.on("before-quit", () => {
export let mainWin: BrowserWindow; export let mainWin: BrowserWindow;
function makeSettingsListenerHelpers<O extends object>(o: SettingsStore<O>) {
const listeners = new Map<(data: any) => void, PropertyKey>();
const addListener: typeof o.addChangeListener = (path, cb) => {
listeners.set(cb, path);
o.addChangeListener(path, cb);
};
const removeAllListeners = () => {
for (const [listener, path] of listeners) {
o.removeChangeListener(path as any, listener);
}
listeners.clear();
};
return [addListener, removeAllListeners] as const;
}
const [addSettingsListener, removeSettingsListeners] = makeSettingsListenerHelpers(Settings);
const [addVencordSettingsListener, removeVencordSettingsListeners] = makeSettingsListenerHelpers(VencordSettings);
function initTray(win: BrowserWindow) { function initTray(win: BrowserWindow) {
const trayMenu = Menu.buildFromTemplate([ const trayMenu = Menu.buildFromTemplate([
{ {
@@ -47,6 +89,12 @@ function initTray(win: BrowserWindow) {
app.quit(); app.quit();
} }
}, },
{
label: "Reset Vesktop",
async click() {
await clearData(win);
}
},
{ {
type: "separator" type: "separator"
}, },
@@ -58,7 +106,7 @@ function initTray(win: BrowserWindow) {
} }
}, },
{ {
label: "Quit Vencord Desktop", label: "Quit Vesktop",
click() { click() {
isQuitting = true; isQuitting = true;
app.quit(); app.quit();
@@ -67,7 +115,7 @@ function initTray(win: BrowserWindow) {
]); ]);
tray = new Tray(ICON_PATH); tray = new Tray(ICON_PATH);
tray.setToolTip("Vencord Desktop"); tray.setToolTip("Vesktop");
tray.setContextMenu(trayMenu); tray.setContextMenu(trayMenu);
tray.on("click", () => win.show()); tray.on("click", () => win.show());
@@ -80,62 +128,133 @@ function initTray(win: BrowserWindow) {
}); });
} }
const enum MessageBoxChoice {
Default,
Cancel
}
async function clearData(win: BrowserWindow) {
const { response } = await dialog.showMessageBox(win, {
message: "Are you sure you want to reset Vesktop?",
detail: "This will log you out, clear caches and reset all your settings!\n\nVesktop will automatically restart after this operation.",
buttons: ["Yes", "No"],
cancelId: MessageBoxChoice.Cancel,
defaultId: MessageBoxChoice.Default,
type: "warning"
});
if (response === MessageBoxChoice.Cancel) return;
win.close();
await win.webContents.session.clearStorageData();
await win.webContents.session.clearCache();
await win.webContents.session.clearCodeCaches({});
await rm(DATA_DIR, { force: true, recursive: true });
app.relaunch();
app.quit();
}
type MenuItemList = Array<MenuItemConstructorOptions | false>;
function initMenuBar(win: BrowserWindow) { function initMenuBar(win: BrowserWindow) {
const isWindows = process.platform === "win32"; const isWindows = process.platform === "win32";
const isDarwin = process.platform === "darwin";
const wantCtrlQ = !isWindows || VencordSettings.store.winCtrlQ; const wantCtrlQ = !isWindows || VencordSettings.store.winCtrlQ;
const subMenu = [
{
label: "About Vesktop",
click: createAboutWindow
},
{
label: "Force Update Vencord",
async click() {
await downloadVencordFiles();
app.relaunch();
app.quit();
},
toolTip: "Vesktop will automatically restart after this operation"
},
{
label: "Reset Vesktop",
async click() {
await clearData(win);
},
toolTip: "Vesktop will automatically restart after this operation"
},
{
label: "Relaunch",
accelerator: "CmdOrCtrl+Shift+R",
click() {
app.relaunch();
app.quit();
}
},
...(!isDarwin
? []
: ([
{
type: "separator"
},
{
label: "Settings",
accelerator: "CmdOrCtrl+,",
async click() {
mainWin.webContents.executeJavaScript(
"Vencord.Webpack.Common.SettingsRouter.open('My Account')"
);
}
},
{
type: "separator"
},
{
label: "Hide Vesktop", // Should probably remove the label, but it says "Hide VencordDesktop" instead of "Hide Vesktop"
role: "hide"
},
{
role: "hideOthers"
},
{
role: "unhide"
},
{
type: "separator"
}
] satisfies MenuItemList)),
{
label: "Quit",
accelerator: wantCtrlQ ? "CmdOrCtrl+Q" : void 0,
visible: !isWindows,
role: "quit",
click() {
app.quit();
}
},
isWindows && {
label: "Quit",
accelerator: "Alt+F4",
role: "quit",
click() {
app.quit();
}
},
// See https://github.com/electron/electron/issues/14742 and https://github.com/electron/electron/issues/5256
{
label: "Zoom in (hidden, hack for Qwertz and others)",
accelerator: "CmdOrCtrl+=",
role: "zoomIn",
visible: false
}
] satisfies MenuItemList;
const menu = Menu.buildFromTemplate([ const menu = Menu.buildFromTemplate([
{ {
label: "Vencord Desktop", label: "Vesktop",
role: "appMenu", role: "appMenu",
submenu: [ submenu: subMenu.filter(isTruthy)
{
label: "About Vencord Desktop",
click: createAboutWindow
},
{
label: "Force Update Vencord",
async click() {
await downloadVencordFiles();
app.relaunch();
app.quit();
},
toolTip: "Vencord Desktop will automatically restart after this operation"
},
{
label: "Relaunch",
accelerator: "CmdOrCtrl+Shift+R",
click() {
app.relaunch();
app.quit();
}
},
{
label: "Quit",
accelerator: wantCtrlQ ? "CmdOrCtrl+Q" : void 0,
visible: !isWindows,
role: "quit",
click() {
app.quit();
}
},
{
label: "Quit",
accelerator: isWindows ? "Alt+F4" : void 0,
visible: isWindows,
role: "quit",
click() {
app.quit();
}
},
// See https://github.com/electron/electron/issues/14742 and https://github.com/electron/electron/issues/5256
{
label: "Zoom in (hidden, hack for Qwertz and others)",
accelerator: "CmdOrCtrl+=",
role: "zoomIn",
visible: false
}
]
}, },
{ role: "fileMenu" }, { role: "fileMenu" },
{ role: "editMenu" }, { role: "editMenu" },
@@ -167,6 +286,28 @@ function getWindowBoundsOptions(): BrowserWindowConstructorOptions {
return options; return options;
} }
function getDarwinOptions(): BrowserWindowConstructorOptions {
const options = {
titleBarStyle: "hiddenInset"
} as BrowserWindowConstructorOptions;
const { splashTheming, splashBackground } = Settings.store;
const { macosTranslucency } = VencordSettings.store;
if (macosTranslucency) {
options.vibrancy = "sidebar";
options.backgroundColor = "#ffffff00";
} else {
if (splashTheming) {
options.backgroundColor = splashBackground;
} else {
options.backgroundColor = nativeTheme.shouldUseDarkColors ? "#313338" : "#ffffff";
}
}
return options;
}
function initWindowBoundsListeners(win: BrowserWindow) { function initWindowBoundsListeners(win: BrowserWindow) {
const saveState = () => { const saveState = () => {
Settings.store.maximized = win.isMaximized(); Settings.store.maximized = win.isMaximized();
@@ -186,11 +327,11 @@ function initWindowBoundsListeners(win: BrowserWindow) {
} }
function initSettingsListeners(win: BrowserWindow) { function initSettingsListeners(win: BrowserWindow) {
Settings.addChangeListener("tray", enable => { addSettingsListener("tray", enable => {
if (enable) initTray(win); if (enable) initTray(win);
else tray?.destroy(); else tray?.destroy();
}); });
Settings.addChangeListener("disableMinSize", disable => { addSettingsListener("disableMinSize", disable => {
if (disable) { if (disable) {
// 0 no work // 0 no work
win.setMinimumSize(1, 1); win.setMinimumSize(1, 1);
@@ -205,7 +346,7 @@ function initSettingsListeners(win: BrowserWindow) {
} }
}); });
VencordSettings.addChangeListener("macosTranslucency", enabled => { addVencordSettingsListener("macosTranslucency", enabled => {
if (enabled) { if (enabled) {
win.setVibrancy("sidebar"); win.setVibrancy("sidebar");
win.setBackgroundColor("#ffffff00"); win.setBackgroundColor("#ffffff00");
@@ -214,36 +355,62 @@ function initSettingsListeners(win: BrowserWindow) {
win.setBackgroundColor("#ffffff"); win.setBackgroundColor("#ffffff");
} }
}); });
addSettingsListener("enableMenu", enabled => {
win.setAutoHideMenuBar(enabled ?? false);
});
}
function initSpellCheck(win: BrowserWindow) {
win.webContents.on("context-menu", (_, data) => {
win.webContents.send(IpcEvents.SPELLCHECK_RESULT, data.misspelledWord, data.dictionarySuggestions);
});
} }
function createMainWindow() { function createMainWindow() {
// Clear up previous settings listeners
removeSettingsListeners();
removeVencordSettingsListeners();
const { staticTitle, transparencyOption, enableMenu, discordWindowsTitleBar } = Settings.store;
const { frameless } = VencordSettings.store;
const noFrame = frameless === true || (process.platform === "win32" && discordWindowsTitleBar === true);
const win = (mainWin = new BrowserWindow({ const win = (mainWin = new BrowserWindow({
show: false, show: false,
autoHideMenuBar: true,
webPreferences: { webPreferences: {
nodeIntegration: false, nodeIntegration: false,
sandbox: false, sandbox: false,
contextIsolation: true, contextIsolation: true,
devTools: true, devTools: true,
preload: join(__dirname, "preload.js") preload: join(__dirname, "preload.js"),
spellcheck: true
}, },
icon: ICON_PATH, icon: ICON_PATH,
frame: VencordSettings.store.frameless !== true, frame: !noFrame,
...(Settings.store.staticTitle ? { title: "Vencord" } : {}), ...(transparencyOption &&
...(VencordSettings.store.macosTranslucency transparencyOption !== "none" && {
? { backgroundColor: "#00000000",
vibrancy: "sidebar", backgroundMaterial: transparencyOption,
backgroundColor: "#ffffff00" transparent: true
} }),
: {}), ...(staticTitle && { title: "Vesktop" }),
...getWindowBoundsOptions() ...(process.platform === "darwin" && getDarwinOptions()),
...getWindowBoundsOptions(),
autoHideMenuBar: enableMenu
})); }));
win.setMenuBarVisibility(false);
win.on("close", e => { win.on("close", e => {
if (isQuitting || Settings.store.minimizeToTray === false || Settings.store.tray === false) return; const useTray = Settings.store.minimizeToTray !== false && Settings.store.tray !== false;
if (isQuitting || (process.platform !== "darwin" && !useTray)) return;
e.preventDefault(); e.preventDefault();
win.hide();
if (process.platform === "darwin") app.hide();
else win.hide();
return false; return false;
}); });
@@ -251,14 +418,13 @@ function createMainWindow() {
if (Settings.store.staticTitle) win.on("page-title-updated", e => e.preventDefault()); if (Settings.store.staticTitle) win.on("page-title-updated", e => e.preventDefault());
initWindowBoundsListeners(win); initWindowBoundsListeners(win);
if (Settings.store.tray ?? true) initTray(win); if ((Settings.store.tray ?? true) && process.platform !== "darwin") initTray(win);
initMenuBar(win); initMenuBar(win);
makeLinksOpenExternally(win); makeLinksOpenExternally(win);
initSettingsListeners(win); initSettingsListeners(win);
initSpellCheck(win);
win.webContents.setUserAgent( win.webContents.setUserAgent(UserAgent);
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Safari/537.36"
);
const subdomain = const subdomain =
Settings.store.discordBranch === "canary" || Settings.store.discordBranch === "ptb" Settings.store.discordBranch === "canary" || Settings.store.discordBranch === "ptb"

View File

@@ -0,0 +1,24 @@
/*
* 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 { session, systemPreferences } from "electron";
export function registerMediaPermissionsHandler() {
if (process.platform !== "darwin") return;
session.defaultSession.setPermissionRequestHandler(async (_webContents, permission, callback, details) => {
let granted = true;
if (details.mediaTypes?.includes("audio")) {
granted = await systemPreferences.askForMediaAccess("microphone");
}
if (details.mediaTypes?.includes("video")) {
granted &&= await systemPreferences.askForMediaAccess("camera");
}
callback(granted);
});
}

View File

@@ -1,15 +1,20 @@
/* /*
* SPDX-License-Identifier: GPL-3.0 * SPDX-License-Identifier: GPL-3.0
* Vencord Desktop, a desktop app aiming to give you a snappier Discord Experience * Vesktop, a desktop app aiming to give you a snappier Discord Experience
* Copyright (c) 2023 Vendicated and Vencord contributors * Copyright (c) 2023 Vendicated and Vencord contributors
*/ */
import { desktopCapturer, ipcMain, session, Streams } from "electron"; import { desktopCapturer, session, Streams } from "electron";
import type { StreamPick } from "renderer/components/ScreenSharePicker"; import type { StreamPick } from "renderer/components/ScreenSharePicker";
import { IpcEvents } from "shared/IpcEvents"; import { IpcEvents } from "shared/IpcEvents";
import { handle } from "./utils/ipcWrappers";
const isWayland =
process.platform === "linux" && (process.env.XDG_SESSION_TYPE === "wayland" || !!process.env.WAYLAND_DISPLAY);
export function registerScreenShareHandler() { export function registerScreenShareHandler() {
ipcMain.handle(IpcEvents.CAPTURER_GET_LARGE_THUMBNAIL, async (_, id: string) => { handle(IpcEvents.CAPTURER_GET_LARGE_THUMBNAIL, async (_, id: string) => {
const sources = await desktopCapturer.getSources({ const sources = await desktopCapturer.getSources({
types: ["window", "screen"], types: ["window", "screen"],
thumbnailSize: { thumbnailSize: {
@@ -21,13 +26,19 @@ export function registerScreenShareHandler() {
}); });
session.defaultSession.setDisplayMediaRequestHandler(async (request, callback) => { session.defaultSession.setDisplayMediaRequestHandler(async (request, callback) => {
const sources = await desktopCapturer.getSources({ // request full resolution on wayland right away because we always only end up with one result anyway
types: ["window", "screen"], const width = isWayland ? 1920 : 176;
thumbnailSize: { const sources = await desktopCapturer
width: 176, .getSources({
height: 99 types: ["window", "screen"],
} thumbnailSize: {
}); width,
height: width * (9 / 16)
}
})
.catch(err => console.error("Error during screenshare picker", err));
if (!sources) return callback({});
const data = sources.map(({ id, name, thumbnail }) => ({ const data = sources.map(({ id, name, thumbnail }) => ({
id, id,
@@ -35,10 +46,28 @@ export function registerScreenShareHandler() {
url: thumbnail.toDataURL() url: thumbnail.toDataURL()
})); }));
if (isWayland) {
const video = data[0];
if (video) {
const stream = await request.frame
.executeJavaScript(
`Vesktop.Components.ScreenShare.openScreenSharePicker(${JSON.stringify([video])},true)`
)
.catch(() => null);
if (stream === null) return callback({});
}
callback(video ? { video: sources[0] } : {});
return;
}
const choice = await request.frame const choice = await request.frame
.executeJavaScript(`VencordDesktop.Components.ScreenShare.openScreenSharePicker(${JSON.stringify(data)})`) .executeJavaScript(`Vesktop.Components.ScreenShare.openScreenSharePicker(${JSON.stringify(data)})`)
.then(e => e as StreamPick) .then(e => e as StreamPick)
.catch(() => null); .catch(e => {
console.error("Error during screenshare picker", e);
return null;
});
if (!choice) return callback({}); if (!choice) return callback({});

View File

@@ -1,6 +1,6 @@
/* /*
* SPDX-License-Identifier: GPL-3.0 * SPDX-License-Identifier: GPL-3.0
* Vencord Desktop, a desktop app aiming to give you a snappier Discord Experience * Vesktop, a desktop app aiming to give you a snappier Discord Experience
* Copyright (c) 2023 Vendicated and Vencord contributors * Copyright (c) 2023 Vendicated and Vencord contributors
*/ */
@@ -33,5 +33,5 @@ function loadSettings<T extends object = any>(file: string, name: string) {
return store; return store;
} }
export const Settings = loadSettings<TSettings>(SETTINGS_FILE, "Vencord Desktop"); export const Settings = loadSettings<TSettings>(SETTINGS_FILE, "Vesktop");
export const VencordSettings = loadSettings<any>(VENCORD_SETTINGS_FILE, "Vencord"); export const VencordSettings = loadSettings<any>(VENCORD_SETTINGS_FILE, "Vencord");

View File

@@ -1,18 +1,38 @@
/* /*
* SPDX-License-Identifier: GPL-3.0 * SPDX-License-Identifier: GPL-3.0
* Vencord Desktop, a desktop app aiming to give you a snappier Discord Experience * Vesktop, a desktop app aiming to give you a snappier Discord Experience
* Copyright (c) 2023 Vendicated and Vencord contributors * Copyright (c) 2023 Vendicated and Vencord contributors
*/ */
import { BrowserWindow } from "electron"; import { BrowserWindow } from "electron";
import { join } from "path"; import { join } from "path";
import { SplashProps } from "shared/browserWinProperties"; import { SplashProps } from "shared/browserWinProperties";
import { STATIC_DIR } from "shared/paths"; import { ICON_PATH, VIEW_DIR } from "shared/paths";
import { Settings } from "./settings";
export function createSplashWindow() { export function createSplashWindow() {
const splash = new BrowserWindow(SplashProps); const splash = new BrowserWindow({
...SplashProps,
icon: ICON_PATH
});
splash.loadFile(join(STATIC_DIR, "splash.html")); splash.loadFile(join(VIEW_DIR, "splash.html"));
const { splashBackground, splashColor, splashTheming } = Settings.store;
if (splashTheming) {
if (splashColor) {
const semiTransparentSplashColor = splashColor.replace("rgb(", "rgba(").replace(")", ", 0.2)");
splash.webContents.insertCSS(`body { --fg: ${splashColor} !important }`);
splash.webContents.insertCSS(`body { --fg-semi-trans: ${semiTransparentSplashColor} !important }`);
}
if (splashBackground) {
splash.webContents.insertCSS(`body { --bg: ${splashBackground} !important }`);
}
}
return splash; return splash;
} }

View File

@@ -1,6 +1,6 @@
/* /*
* SPDX-License-Identifier: GPL-3.0 * SPDX-License-Identifier: GPL-3.0
* Vencord Desktop, a desktop app aiming to give you a snappier Discord Experience * Vesktop, a desktop app aiming to give you a snappier Discord Experience
* Copyright (c) 2023 Vendicated and Vencord contributors * Copyright (c) 2023 Vendicated and Vencord contributors
*/ */

View File

@@ -0,0 +1,36 @@
/*
* 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 { ipcMain, IpcMainEvent, IpcMainInvokeEvent, WebFrameMain } from "electron";
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);
}
}
export function handleSync(event: IpcEvents, cb: (e: IpcMainEvent, ...args: any[]) => any) {
ipcMain.on(event, (e, ...args) => {
validateSender(e.senderFrame);
e.returnValue = cb(e, ...args);
});
}
export function handle(event: IpcEvents, cb: (e: IpcMainInvokeEvent, ...args: any[]) => any) {
ipcMain.handle(event, (e, ...args) => {
validateSender(e.senderFrame);
return cb(e, ...args);
});
}

View File

@@ -1,6 +1,6 @@
/* /*
* SPDX-License-Identifier: GPL-3.0 * SPDX-License-Identifier: GPL-3.0
* Vencord Desktop, a desktop app aiming to give you a snappier Discord Experience * Vesktop, a desktop app aiming to give you a snappier Discord Experience
* Copyright (c) 2023 Vendicated and Vencord contributors * Copyright (c) 2023 Vendicated and Vencord contributors
*/ */

View File

@@ -1,6 +1,6 @@
/* /*
* SPDX-License-Identifier: GPL-3.0 * SPDX-License-Identifier: GPL-3.0
* Vencord Desktop, a desktop app aiming to give you a snappier Discord Experience * Vesktop, a desktop app aiming to give you a snappier Discord Experience
* Copyright (c) 2023 Vendicated and Vencord contributors * Copyright (c) 2023 Vendicated and Vencord contributors
*/ */
@@ -13,7 +13,12 @@ import { downloadFile, simpleGet } from "./http";
const API_BASE = "https://api.github.com"; const API_BASE = "https://api.github.com";
const FILES_TO_DOWNLOAD = ["vencordDesktopMain.js", "preload.js", "vencordDesktopRenderer.js", "renderer.css"]; export const FILES_TO_DOWNLOAD = [
"vencordDesktopMain.js",
"vencordDesktopPreload.js",
"vencordDesktopRenderer.js",
"vencordDesktopRenderer.css"
];
export interface ReleaseData { export interface ReleaseData {
name: string; name: string;
@@ -50,8 +55,12 @@ export async function downloadVencordFiles() {
); );
} }
export function isValidVencordInstall(dir: string) {
return FILES_TO_DOWNLOAD.every(f => existsSync(join(dir, f)));
}
export async function ensureVencordFiles() { export async function ensureVencordFiles() {
if (existsSync(join(VENCORD_FILES_DIR, "vencordDesktopMain.js"))) return; if (isValidVencordInstall(VENCORD_FILES_DIR)) return;
mkdirSync(VENCORD_FILES_DIR, { recursive: true }); mkdirSync(VENCORD_FILES_DIR, { recursive: true });
await downloadVencordFiles(); await downloadVencordFiles();

74
src/main/virtmic.ts Normal file
View File

@@ -0,0 +1,74 @@
/*
* 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 { app, ipcMain } from "electron";
import { join } from "path";
import { IpcEvents } from "shared/IpcEvents";
import { STATIC_DIR } from "shared/paths";
let initialized = false;
let patchBay: import("@vencord/venmic").PatchBay | undefined;
let isGlibcxxToOld = false;
function getRendererAudioServicePid() {
return (
app
.getAppMetrics()
.find(proc => proc.name === "Audio Service")
?.pid?.toString() ?? "owo"
);
}
function obtainVenmic() {
if (!initialized) {
initialized = true;
try {
const { PatchBay } = require(
join(STATIC_DIR, `dist/venmic-${process.arch}.node`)
) as typeof import("@vencord/venmic");
patchBay = new PatchBay();
} catch (e: any) {
console.error("Failed to initialise venmic. Make sure you're using pipewire", e);
isGlibcxxToOld = (e?.stack || e?.message || "").toLowerCase().includes("glibc");
}
}
return patchBay;
}
ipcMain.handle(IpcEvents.VIRT_MIC_LIST, () => {
const audioPid = getRendererAudioServicePid();
const list = obtainVenmic()
?.list()
.filter(s => s["application.process.id"] !== audioPid)
.map(s => s["application.name"]);
return list
? { ok: true, targets: [...new Set(list)] } // Remove duplicates
: { ok: false, isGlibcxxToOld };
});
ipcMain.handle(
IpcEvents.VIRT_MIC_START,
(_, target: string) =>
obtainVenmic()?.link({
key: "application.name",
value: target,
mode: "include"
})
);
ipcMain.handle(
IpcEvents.VIRT_MIC_START_SYSTEM,
() =>
obtainVenmic()?.link({
key: "application.process.id",
value: getRendererAudioServicePid(),
mode: "exclude"
})
);
ipcMain.handle(IpcEvents.VIRT_MIC_STOP, () => obtainVenmic()?.unlink());

View File

@@ -1,41 +0,0 @@
/*
* SPDX-License-Identifier: GPL-3.0
* Vencord Desktop, a desktop app aiming to give you a snappier Discord Experience
* Copyright (c) 2023 Vendicated and Vencord contributors
*/
import type { Settings } from "shared/settings";
import type { LiteralUnion } from "type-fest";
import { IpcEvents } from "../shared/IpcEvents";
import { invoke, sendSync } from "./typedIpcs";
export const VencordDesktopNative = {
app: {
relaunch: () => invoke<void>(IpcEvents.RELAUNCH),
getVersion: () => sendSync<void>(IpcEvents.GET_VERSION)
},
autostart: {
isEnabled: () => sendSync<boolean>(IpcEvents.AUTOSTART_ENABLED),
enable: () => invoke<void>(IpcEvents.ENABLE_AUTOSTART),
disable: () => invoke<void>(IpcEvents.DISABLE_AUTOSTART)
},
fileManager: {
showItemInFolder: (path: string) => invoke<void>(IpcEvents.SHOW_ITEM_IN_FOLDER, path),
selectVencordDir: () => invoke<LiteralUnion<"cancelled" | "invalid", string>>(IpcEvents.SELECT_VENCORD_DIR)
},
settings: {
get: () => sendSync<Settings>(IpcEvents.GET_SETTINGS),
set: (settings: Settings, path?: string) => invoke<void>(IpcEvents.SET_SETTINGS, settings, path)
},
spellcheck: {
setLanguages: (languages: readonly string[]) => invoke<void>(IpcEvents.SPELLCHECK_SET_LANGUAGES, languages)
// todo: perhaps add ways to learn words
},
win: {
focus: () => invoke<void>(IpcEvents.FOCUS)
},
capturer: {
getLargeThumbnail: (id: string) => invoke<string>(IpcEvents.CAPTURER_GET_LARGE_THUMBNAIL, id)
}
};

View File

@@ -0,0 +1,75 @@
/*
* 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 { ipcRenderer } from "electron";
import type { Settings } from "shared/settings";
import type { LiteralUnion } from "type-fest";
import { IpcEvents } from "../shared/IpcEvents";
import { invoke, sendSync } from "./typedIpc";
type SpellCheckerResultCallback = (word: string, suggestions: string[]) => void;
const spellCheckCallbacks = new Set<SpellCheckerResultCallback>();
ipcRenderer.on(IpcEvents.SPELLCHECK_RESULT, (_, w: string, s: string[]) => {
spellCheckCallbacks.forEach(cb => cb(w, s));
});
export const VesktopNative = {
app: {
relaunch: () => invoke<void>(IpcEvents.RELAUNCH),
getVersion: () => sendSync<void>(IpcEvents.GET_VERSION),
setBadgeCount: (count: number) => invoke<void>(IpcEvents.SET_BADGE_COUNT, count),
supportsWindowsTransparency: () => sendSync<boolean>(IpcEvents.SUPPORTS_WINDOWS_TRANSPARENCY)
},
autostart: {
isEnabled: () => sendSync<boolean>(IpcEvents.AUTOSTART_ENABLED),
enable: () => invoke<void>(IpcEvents.ENABLE_AUTOSTART),
disable: () => invoke<void>(IpcEvents.DISABLE_AUTOSTART)
},
fileManager: {
showItemInFolder: (path: string) => invoke<void>(IpcEvents.SHOW_ITEM_IN_FOLDER, path),
selectVencordDir: () => invoke<LiteralUnion<"cancelled" | "invalid", string>>(IpcEvents.SELECT_VENCORD_DIR)
},
settings: {
get: () => sendSync<Settings>(IpcEvents.GET_SETTINGS),
set: (settings: Settings, path?: string) => invoke<void>(IpcEvents.SET_SETTINGS, settings, path)
},
spellcheck: {
setLanguages: (languages: readonly string[]) => invoke<void>(IpcEvents.SPELLCHECK_SET_LANGUAGES, languages),
onSpellcheckResult(cb: SpellCheckerResultCallback) {
spellCheckCallbacks.add(cb);
},
offSpellcheckResult(cb: SpellCheckerResultCallback) {
spellCheckCallbacks.delete(cb);
},
replaceMisspelling: (word: string) => invoke<void>(IpcEvents.SPELLCHECK_REPLACE_MISSPELLING, word),
addToDictionary: (word: string) => invoke<void>(IpcEvents.SPELLCHECK_ADD_TO_DICTIONARY, word)
},
win: {
focus: () => invoke<void>(IpcEvents.FOCUS),
close: () => invoke<void>(IpcEvents.CLOSE),
minimize: () => invoke<void>(IpcEvents.MINIMIZE),
maximize: () => invoke<void>(IpcEvents.MAXIMIZE)
},
capturer: {
getLargeThumbnail: (id: string) => invoke<string>(IpcEvents.CAPTURER_GET_LARGE_THUMBNAIL, id)
},
/** only available on Linux. */
virtmic: {
list: () =>
invoke<{ ok: false; isGlibcxxToOld: boolean } | { ok: true; targets: string[] }>(IpcEvents.VIRT_MIC_LIST),
start: (target: string) => invoke<void>(IpcEvents.VIRT_MIC_START, target),
startSystem: () => invoke<void>(IpcEvents.VIRT_MIC_START_SYSTEM),
stop: () => invoke<void>(IpcEvents.VIRT_MIC_STOP)
},
arrpc: {
onActivity(cb: (data: string) => void) {
ipcRenderer.on(IpcEvents.ARRPC_ACTIVITY, (_, data: string) => cb(data));
}
}
};

View File

@@ -1,6 +1,6 @@
/* /*
* SPDX-License-Identifier: GPL-3.0 * SPDX-License-Identifier: GPL-3.0
* Vencord Desktop, a desktop app aiming to give you a snappier Discord Experience * Vesktop, a desktop app aiming to give you a snappier Discord Experience
* Copyright (c) 2023 Vendicated and Vencord contributors * Copyright (c) 2023 Vendicated and Vencord contributors
*/ */
@@ -8,9 +8,9 @@ import { contextBridge, ipcRenderer, webFrame } from "electron";
import { readFileSync, watch } from "fs"; import { readFileSync, watch } from "fs";
import { IpcEvents } from "../shared/IpcEvents"; import { IpcEvents } from "../shared/IpcEvents";
import { VencordDesktopNative } from "./VencordDesktopNative"; import { VesktopNative } from "./VesktopNative";
contextBridge.exposeInMainWorld("VencordDesktopNative", VencordDesktopNative); contextBridge.exposeInMainWorld("VesktopNative", VesktopNative);
require(ipcRenderer.sendSync(IpcEvents.GET_VENCORD_PRELOAD_FILE)); require(ipcRenderer.sendSync(IpcEvents.GET_VENCORD_PRELOAD_FILE));
@@ -41,4 +41,4 @@ if (IS_DEV) {
} }
// #endregion // #endregion
VencordDesktopNative.spellcheck.setLanguages(window.navigator.languages); VesktopNative.spellcheck.setLanguages(window.navigator.languages);

View File

@@ -1,6 +1,6 @@
/* /*
* SPDX-License-Identifier: GPL-3.0 * SPDX-License-Identifier: GPL-3.0
* Vencord Desktop, a desktop app aiming to give you a snappier Discord Experience * Vesktop, a desktop app aiming to give you a snappier Discord Experience
* Copyright (c) 2023 Vendicated and Vencord contributors * Copyright (c) 2023 Vendicated and Vencord contributors
*/ */

47
src/renderer/appBadge.ts Normal file
View File

@@ -0,0 +1,47 @@
/*
* 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 { filters, waitFor } from "@vencord/types/webpack";
import { RelationshipStore } from "@vencord/types/webpack/common";
import { Settings } from "./settings";
let GuildReadStateStore: any;
let NotificationSettingsStore: any;
export function setBadge() {
if (Settings.store.appBadge === false) return;
try {
const mentionCount = GuildReadStateStore.getTotalMentionCount();
const pendingRequests = RelationshipStore.getPendingCount();
const hasUnread = GuildReadStateStore.hasAnyUnread();
const disableUnreadBadge = NotificationSettingsStore.getDisableUnreadBadge();
let totalCount = mentionCount + pendingRequests;
if (!totalCount && hasUnread && !disableUnreadBadge) totalCount = -1;
VesktopNative.app.setBadgeCount(totalCount);
} catch (e) {
console.error(e);
}
}
let toFind = 3;
function waitForAndSubscribeToStore(name: string, cb?: (m: any) => void) {
waitFor(filters.byStoreName(name), store => {
cb?.(store);
store.addChangeListener(setBadge);
toFind--;
if (toFind === 0) setBadge();
});
}
waitForAndSubscribeToStore("GuildReadStateStore", store => (GuildReadStateStore = store));
waitForAndSubscribeToStore("NotificationSettingsStore", store => (NotificationSettingsStore = store));
waitForAndSubscribeToStore("RelationshipStore");

View File

@@ -1,21 +1,32 @@
/* /*
* SPDX-License-Identifier: GPL-3.0 * SPDX-License-Identifier: GPL-3.0
* Vencord Desktop, a desktop app aiming to give you a snappier Discord Experience * Vesktop, a desktop app aiming to give you a snappier Discord Experience
* Copyright (c) 2023 Vendicated and Vencord contributors * Copyright (c) 2023 Vendicated and Vencord contributors
*/ */
import "./screenSharePicker.css"; import "./screenSharePicker.css";
import { classes, closeModal, Margins, Modals, openModal, useAwaiter } from "@vencord/types/utils"; import { closeModal, Modals, openModal, useAwaiter } from "@vencord/types/utils";
import { findByPropsLazy } from "@vencord/types/webpack"; import { findStoreLazy, onceReady } from "@vencord/types/webpack";
import { Button, Card, Forms, Switch, Text, useState } from "@vencord/types/webpack/common"; import {
Button,
Card,
FluxDispatcher,
Forms,
Select,
Switch,
Text,
UserStore,
useState
} from "@vencord/types/webpack/common";
import type { Dispatch, SetStateAction } from "react"; import type { Dispatch, SetStateAction } from "react";
import { isWindows } from "renderer/utils"; import { addPatch } from "renderer/patches/shared";
import { isLinux, isWindows } from "renderer/utils";
const StreamResolutions = ["720", "1080", "1440", "Source"] as const; const StreamResolutions = ["480", "720", "1080", "1440"] as const;
const StreamFps = ["15", "30", "60"] as const; const StreamFps = ["15", "30", "60"] as const;
const WarningIconClasses = findByPropsLazy("warning", "error", "container"); const MediaEngineStore = findStoreLazy("MediaEngineStore");
export type StreamResolution = (typeof StreamResolutions)[number]; export type StreamResolution = (typeof StreamResolutions)[number];
export type StreamFps = (typeof StreamFps)[number]; export type StreamFps = (typeof StreamFps)[number];
@@ -24,6 +35,7 @@ interface StreamSettings {
resolution: StreamResolution; resolution: StreamResolution;
fps: StreamFps; fps: StreamFps;
audio: boolean; audio: boolean;
audioSource?: string;
} }
export interface StreamPick extends StreamSettings { export interface StreamPick extends StreamSettings {
@@ -36,18 +48,74 @@ interface Source {
url: string; url: string;
} }
export function openScreenSharePicker(screens: Source[]) { let currentSettings: StreamSettings | null = null;
addPatch({
patches: [
{
find: "this.localWant=",
replacement: {
match: /this.localWant=/,
replace: "$self.patchStreamQuality(this);$&"
}
}
],
patchStreamQuality(opts: any) {
if (!currentSettings) return;
const framerate = Number(currentSettings.fps);
const height = Number(currentSettings.resolution);
const width = Math.round(height * (16 / 9));
Object.assign(opts, {
bitrateMin: 500000,
bitrateMax: 8000000,
bitrateTarget: 600000
});
Object.assign(opts.capture, {
framerate,
width,
height,
pixelCount: height * width
});
}
});
if (isLinux) {
onceReady.then(() => {
FluxDispatcher.subscribe("VOICE_STATE_UPDATES", e => {
for (const state of e.voiceStates) {
if (state.userId === UserStore.getCurrentUser().id && state.oldChannelId && !state.channelId)
VesktopNative.virtmic.stop();
}
});
});
}
export function openScreenSharePicker(screens: Source[], skipPicker: boolean) {
let didSubmit = false;
return new Promise<StreamPick>((resolve, reject) => { return new Promise<StreamPick>((resolve, reject) => {
const key = openModal( const key = openModal(
props => ( props => (
<ModalComponent <ModalComponent
screens={screens} screens={screens}
modalProps={props} modalProps={props}
submit={resolve} submit={async v => {
didSubmit = true;
if (v.audioSource && v.audioSource !== "None") {
if (v.audioSource === "Entire System") {
await VesktopNative.virtmic.startSystem();
} else {
await VesktopNative.virtmic.start(v.audioSource);
}
}
resolve(v);
}}
close={() => { close={() => {
props.onClose(); props.onClose();
reject("Aborted"); if (!didSubmit) reject("Aborted");
}} }}
skipPicker={skipPicker}
/> />
), ),
{ {
@@ -78,16 +146,21 @@ function ScreenPicker({ screens, chooseScreen }: { screens: Source[]; chooseScre
function StreamSettings({ function StreamSettings({
source, source,
settings, settings,
setSettings setSettings,
skipPicker
}: { }: {
source: Source; source: Source;
settings: StreamSettings; settings: StreamSettings;
setSettings: Dispatch<SetStateAction<StreamSettings>>; setSettings: Dispatch<SetStateAction<StreamSettings>>;
skipPicker: boolean;
}) { }) {
const [thumb] = useAwaiter(() => VencordDesktopNative.capturer.getLargeThumbnail(source.id), { const [thumb] = useAwaiter(
fallbackValue: source.url, () => (skipPicker ? Promise.resolve(source.url) : VesktopNative.capturer.getLargeThumbnail(source.id)),
deps: [source.id] {
}); fallbackValue: source.url,
deps: [source.id]
}
);
return ( return (
<div> <div>
@@ -100,12 +173,6 @@ function StreamSettings({
<Forms.FormTitle>Stream Settings</Forms.FormTitle> <Forms.FormTitle>Stream Settings</Forms.FormTitle>
<Card className="vcd-screen-picker-card"> <Card className="vcd-screen-picker-card">
<Card className={classes(WarningIconClasses.container, WarningIconClasses.warning, Margins.bottom8)}>
<Forms.FormText>
Resolution and Frame Rate aren't implemented for now. Locked to 720p 30fps
</Forms.FormText>
</Card>
<div className="vcd-screen-picker-quality"> <div className="vcd-screen-picker-quality">
<section> <section>
<Forms.FormTitle>Resolution</Forms.FormTitle> <Forms.FormTitle>Resolution</Forms.FormTitle>
@@ -154,23 +221,76 @@ function StreamSettings({
Stream With Audio Stream With Audio
</Switch> </Switch>
)} )}
{isLinux && (
<AudioSourcePickerLinux
audioSource={settings.audioSource}
setAudioSource={source => setSettings(s => ({ ...s, audioSource: source }))}
/>
)}
</Card> </Card>
</div> </div>
); );
} }
function AudioSourcePickerLinux({
audioSource,
setAudioSource
}: {
audioSource?: string;
setAudioSource(s: string): void;
}) {
const [sources, _, loading] = useAwaiter(() => VesktopNative.virtmic.list(), {
fallbackValue: { ok: true, targets: [] }
});
const allSources = sources.ok ? ["None", "Entire System", ...sources.targets] : null;
return (
<section>
<Forms.FormTitle>Audio</Forms.FormTitle>
{loading && <Forms.FormTitle>Loading Audio sources...</Forms.FormTitle>}
{!sources.ok &&
(sources.isGlibcxxToOld ? (
<Forms.FormText>
Failed to retrieve Audio Sources because your c++ library is too old to run venmic. If you would
like to stream with Audio, see{" "}
<a href="https://gist.github.com/Vendicated/b655044ffbb16b2716095a448c6d827a" target="_blank">
this guide
</a>
</Forms.FormText>
) : (
<Forms.FormText>
Failed to retrieve Audio Sources. If you would like to stream with Audio, make sure you're using
Pipewire, not Pulseaudio
</Forms.FormText>
))}
{allSources && (
<Select
options={allSources.map(s => ({ label: s, value: s, default: s === "None" }))}
isSelected={s => s === audioSource}
select={setAudioSource}
serialize={String}
/>
)}
</section>
);
}
function ModalComponent({ function ModalComponent({
screens, screens,
modalProps, modalProps,
submit, submit,
close close,
skipPicker
}: { }: {
screens: Source[]; screens: Source[];
modalProps: any; modalProps: any;
submit: (data: StreamPick) => void; submit: (data: StreamPick) => void;
close: () => void; close: () => void;
skipPicker: boolean;
}) { }) {
const [selected, setSelected] = useState<string>(); const [selected, setSelected] = useState<string | undefined>(skipPicker ? screens[0].id : void 0);
const [settings, setSettings] = useState<StreamSettings>({ const [settings, setSettings] = useState<StreamSettings>({
resolution: "1080", resolution: "1080",
fps: "60", fps: "60",
@@ -192,6 +312,7 @@ function ModalComponent({
source={screens.find(s => s.id === selected)!} source={screens.find(s => s.id === selected)!}
settings={settings} settings={settings}
setSettings={setSettings} setSettings={setSettings}
skipPicker={skipPicker}
/> />
)} )}
</Modals.ModalContent> </Modals.ModalContent>
@@ -200,17 +321,38 @@ function ModalComponent({
<Button <Button
disabled={!selected} disabled={!selected}
onClick={() => { onClick={() => {
currentSettings = settings;
// If there are 2 connections, the second one is the existing stream.
// In that case, we patch its quality
const conn = [...MediaEngineStore.getMediaEngine().connections][1];
if (conn && conn.videoStreamParameters.length > 0) {
const height = Number(settings.resolution);
const width = Math.round(height * (16 / 9));
Object.assign(conn.videoStreamParameters[0], {
maxFrameRate: Number(settings.fps),
maxPixelCount: width * height,
maxBitrate: 8000000,
maxResolution: {
type: "fixed",
width,
height
}
});
}
submit({ submit({
id: selected!, id: selected!,
...settings ...settings
}); });
close(); close();
}} }}
> >
Go Live Go Live
</Button> </Button>
{selected ? ( {selected && !skipPicker ? (
<Button color={Button.Colors.TRANSPARENT} onClick={() => setSelected(void 0)}> <Button color={Button.Colors.TRANSPARENT} onClick={() => setSelected(void 0)}>
Back Back
</Button> </Button>

View File

@@ -1,27 +1,36 @@
/* /*
* SPDX-License-Identifier: GPL-3.0 * SPDX-License-Identifier: GPL-3.0
* Vencord Desktop, a desktop app aiming to give you a snappier Discord Experience * Vesktop, a desktop app aiming to give you a snappier Discord Experience
* Copyright (c) 2023 Vendicated and Vencord contributors * Copyright (c) 2023 Vendicated and Vencord contributors
*/ */
import "./settings.css"; import "./settings.css";
import { Margins } from "@vencord/types/utils"; import { Margins } from "@vencord/types/utils";
import { Button, Forms, Select, Switch, Text, useState } from "@vencord/types/webpack/common"; import { Button, Forms, Select, Switch, Text, Toasts, useState } from "@vencord/types/webpack/common";
import { setBadge } from "renderer/appBadge";
import { useSettings } from "renderer/settings"; import { useSettings } from "renderer/settings";
import { isMac, isWindows } from "renderer/utils";
import { isTruthy } from "shared/utils/guards";
export default function SettingsUi() { export default function SettingsUi() {
const Settings = useSettings(); const Settings = useSettings();
const supportsWindowsTransparency = VesktopNative.app.supportsWindowsTransparency();
const { autostart } = VencordDesktopNative; const { autostart } = VesktopNative;
const [autoStartEnabled, setAutoStartEnabled] = useState(autostart.isEnabled()); const [autoStartEnabled, setAutoStartEnabled] = useState(autostart.isEnabled());
const switches: [keyof typeof Settings, string, string, boolean?, (() => boolean)?][] = [ const allSwitches: Array<false | [keyof typeof Settings, string, string, boolean?, (() => boolean)?]> = [
["tray", "Tray Icon", "Add a tray icon for Vencord Desktop", true], isWindows && [
[ "discordWindowsTitleBar",
"Discord Titlebar",
"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 && [
"minimizeToTray", "minimizeToTray",
"Minimize to tray", "Minimize to tray",
"Hitting X will make Vencord Desktop minimize to the tray instead of closing", "Hitting X will make Vesktop minimize to the tray instead of closing",
true, true,
() => Settings.tray ?? true () => Settings.tray ?? true
], ],
@@ -31,21 +40,26 @@ export default function SettingsUi() {
"Disable minimum window size", "Disable minimum window size",
"Allows you to make the window as small as your heart desires" "Allows you to make the window as small as your heart desires"
], ],
["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."],
["splashTheming", "Splash theming", "Adapt the splash window colors to your custom theme", false],
[ [
"openLinksWithElectron", "openLinksWithElectron",
"Open Links in app (experimental)", "Open Links in app (experimental)",
"Opens links in a new Vencord Desktop window instead of your web browser" "Opens links in a new Vesktop window instead of your web browser"
], ],
["staticTitle", "Static Title", 'Makes the window title "Vencord" instead of changing to the current page'] ["checkUpdates", "Check for updates", "Automatically check for Vesktop updates", true]
]; ];
const switches = allSwitches.filter(isTruthy);
return ( return (
<Forms.FormSection> <Forms.FormSection>
<Text variant="heading-lg/semibold" style={{ color: "var(--header-primary)" }} tag="h2"> <Text variant="heading-lg/semibold" style={{ color: "var(--header-primary)" }} tag="h2">
Vencord Desktop Settings Vesktop Settings
</Text> </Text>
<Forms.FormTitle className={Margins.top16}>Discord Branch</Forms.FormTitle> <Forms.FormTitle className={Margins.top16 + " " + Margins.bottom8}>Discord Branch</Forms.FormTitle>
<Select <Select
placeholder="Stable" placeholder="Stable"
options={[ options={[
@@ -67,11 +81,23 @@ export default function SettingsUi() {
await autostart[v ? "enable" : "disable"](); await autostart[v ? "enable" : "disable"]();
setAutoStartEnabled(v); setAutoStartEnabled(v);
}} }}
note="Automatically start Vencord Desktop on computer start-up" note="Automatically start Vesktop on computer start-up"
> >
Start With System Start With System
</Switch> </Switch>
<Switch
value={Settings.appBadge ?? true}
onChange={v => {
Settings.appBadge = v;
if (v) setBadge();
else VesktopNative.app.setBadgeCount(0);
}}
note="Show mention badge on the app icon"
>
Notification Badge
</Switch>
{switches.map(([key, text, note, def, predicate]) => ( {switches.map(([key, text, note, def, predicate]) => (
<Switch <Switch
value={(Settings[key as any] ?? def ?? false) && predicate?.() !== false} value={(Settings[key as any] ?? def ?? false) && predicate?.() !== false}
@@ -84,6 +110,43 @@ export default function SettingsUi() {
</Switch> </Switch>
))} ))}
{supportsWindowsTransparency && (
<>
<Forms.FormTitle className={Margins.top16 + " " + Margins.bottom8}>
Transparency Options
</Forms.FormTitle>
<Forms.FormText className={Margins.bottom8}>
Requires a full restart. You will need a theme that supports transparency for this to work.
</Forms.FormText>
<Select
placeholder="None"
options={[
{
label: "None",
value: "none",
default: true
},
{
label: "Mica (incorporates system theme + desktop wallpaper to paint the background)",
value: "mica"
},
{ label: "Tabbed (variant of Mica with stronger background tinting)", value: "tabbed" },
{
label: "Acrylic (blurs the window behind Vesktop for a translucent background)",
value: "acrylic"
}
]}
closeOnSelect={true}
select={v => (Settings.transparencyOption = v)}
isSelected={v => v === Settings.transparencyOption}
serialize={s => s}
/>
<Forms.FormDivider className={Margins.top16 + " " + Margins.bottom16} />
</>
)}
<Forms.FormTitle>Vencord Location</Forms.FormTitle> <Forms.FormTitle>Vencord Location</Forms.FormTitle>
<Forms.FormText> <Forms.FormText>
Vencord files are loaded from{" "} Vencord files are loaded from{" "}
@@ -92,7 +155,7 @@ export default function SettingsUi() {
href="about:blank" href="about:blank"
onClick={e => { onClick={e => {
e.preventDefault(); e.preventDefault();
VencordDesktopNative.fileManager.showItemInFolder(Settings.vencordDir!); VesktopNative.fileManager.showItemInFolder(Settings.vencordDir!);
}} }}
> >
{Settings.vencordDir} {Settings.vencordDir}
@@ -105,11 +168,17 @@ export default function SettingsUi() {
<Button <Button
size={Button.Sizes.SMALL} size={Button.Sizes.SMALL}
onClick={async () => { onClick={async () => {
const choice = await VencordDesktopNative.fileManager.selectVencordDir(); const choice = await VesktopNative.fileManager.selectVencordDir();
switch (choice) { switch (choice) {
case "cancelled": case "cancelled":
return;
case "invalid": case "invalid":
// TODO Toasts.show({
message:
"You did not choose a valid Vencord install. Make sure you're selecting the dist dir!",
id: Toasts.genId(),
type: Toasts.Type.FAILURE
});
return; return;
} }
Settings.vencordDir = choice; Settings.vencordDir = choice;

View File

@@ -1,6 +1,6 @@
/* /*
* SPDX-License-Identifier: GPL-3.0 * SPDX-License-Identifier: GPL-3.0
* Vencord Desktop, a desktop app aiming to give you a snappier Discord Experience * Vesktop, a desktop app aiming to give you a snappier Discord Experience
* Copyright (c) 2023 Vendicated and Vencord contributors * Copyright (c) 2023 Vendicated and Vencord contributors
*/ */

View File

@@ -94,8 +94,6 @@
gap: 1em; gap: 1em;
margin-bottom: 0.5em; margin-bottom: 0.5em;
opacity: 0.3;
} }
.vcd-screen-picker-quality section { .vcd-screen-picker-quality section {

View File

@@ -1,6 +1,6 @@
/* /*
* SPDX-License-Identifier: GPL-3.0 * SPDX-License-Identifier: GPL-3.0
* Vencord Desktop, a desktop app aiming to give you a snappier Discord Experience * Vesktop, a desktop app aiming to give you a snappier Discord Experience
* Copyright (c) 2023 Vendicated and Vencord contributors * Copyright (c) 2023 Vendicated and Vencord contributors
*/ */
@@ -8,7 +8,7 @@ import "./hideGarbage.css";
import { waitFor } from "@vencord/types/webpack"; import { waitFor } from "@vencord/types/webpack";
import { isFirstRun, localStorage } from "./utils"; import { isFirstRun, isWindows, localStorage } from "./utils";
// Make clicking Notifications focus the window // Make clicking Notifications focus the window
const originalSetOnClick = Object.getOwnPropertyDescriptor(Notification.prototype, "onclick")!.set!; const originalSetOnClick = Object.getOwnPropertyDescriptor(Notification.prototype, "onclick")!.set!;
@@ -16,18 +16,29 @@ Object.defineProperty(Notification.prototype, "onclick", {
set(onClick) { set(onClick) {
originalSetOnClick.call(this, function (this: unknown) { originalSetOnClick.call(this, function (this: unknown) {
onClick.apply(this, arguments); onClick.apply(this, arguments);
VencordDesktopNative.win.focus(); VesktopNative.win.focus();
}); });
}, },
configurable: true configurable: true
}); });
// Enable Desktop Notifications by default
if (isFirstRun) { if (isFirstRun) {
// Hide "Download Discord Desktop now!!!!" banner // Hide "Download Discord Desktop now!!!!" banner
localStorage.setItem("hideNag", "true"); localStorage.setItem("hideNag", "true");
// Enable Desktop Notifications by default
waitFor("setDesktopType", m => { waitFor("setDesktopType", m => {
m.setDesktopType("all"); m.setDesktopType("all");
}); });
} }
// FIXME: Remove eventually.
// Originally, Vencord always used a Windows user agent. This seems to cause captchas
// Now, we use a platform specific UA - HOWEVER, discord FOR SOME REASON????? caches
// device props in localStorage. This code fixes their cache to properly update the platform in SuperProps
if (!isWindows)
try {
const deviceProperties = localStorage.getItem("deviceProperties");
if (deviceProperties && JSON.parse(deviceProperties).os === "Windows")
localStorage.removeItem("deviceProperties");
} catch {}

View File

@@ -1,5 +1,5 @@
/* Download Desktop button in guilds list */ /* Download Desktop button in guilds list */
[class|=listItem]:has([data-list-item-id=guildsnav___app-download-button]), [class^=listItem_]:has([data-list-item-id=guildsnav___app-download-button]),
[class|=listItem]:has(+ [class|=listItem] [data-list-item-id=guildsnav___app-download-button]) { [class^=listItem_]:has(+ [class^=listItem_] [data-list-item-id=guildsnav___app-download-button]) {
display: none; display: none;
} }

View File

@@ -1,25 +1,59 @@
/* /*
* SPDX-License-Identifier: GPL-3.0 * SPDX-License-Identifier: GPL-3.0
* Vencord Desktop, a desktop app aiming to give you a snappier Discord Experience * Vesktop, a desktop app aiming to give you a snappier Discord Experience
* Copyright (c) 2023 Vendicated and Vencord contributors * Copyright (c) 2023 Vendicated and Vencord contributors
*/ */
import "./fixes"; import "./fixes";
import "./appBadge";
import "./patches";
import "./themedSplash";
console.log("read if cute :3"); console.log("read if cute :3");
export * as Components from "./components"; export * as Components from "./components";
import { findByPropsLazy } from "@vencord/types/webpack";
import { FluxDispatcher } from "@vencord/types/webpack/common";
import SettingsUi from "./components/Settings";
import { Settings } from "./settings"; import { Settings } from "./settings";
export { Settings }; export { Settings };
const arRPC = Vencord.Plugins.plugins["WebRichPresence (arRPC)"]; const InviteActions = findByPropsLazy("resolveInvite");
arRPC.required = !!Settings.store.arRPC; export async function openInviteModal(code: string) {
const { invite } = await InviteActions.resolveInvite(code, "Desktop Modal");
if (!invite) return false;
Settings.addChangeListener("arRPC", v => { VesktopNative.win.focus();
arRPC.required = !!v;
if (v && !arRPC.started) Vencord.Plugins.startPlugin(arRPC); FluxDispatcher.dispatch({
else if (arRPC.started) { type: "INVITE_MODAL_OPEN",
Vencord.Plugins.stopPlugin(arRPC); invite,
} code,
context: "APP"
});
return true;
}
const customSettingsSections = (
Vencord.Plugins.plugins.Settings as any as { customSections: ((ID: Record<string, unknown>) => any)[] }
).customSections;
customSettingsSections.push(() => ({
section: "Vesktop",
label: "Vesktop Settings",
element: SettingsUi,
className: "vc-vesktop-settings"
}));
const arRPC = Vencord.Plugins.plugins["WebRichPresence (arRPC)"] as any as {
handleEvent(e: MessageEvent): void;
};
VesktopNative.arrpc.onActivity(data => {
if (!Settings.store.arRPC) return;
arRPC.handleEvent(new MessageEvent("message", { data }));
}); });

View File

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

View File

@@ -0,0 +1,29 @@
/*
* 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 { Settings } from "renderer/settings";
import { isMac, isWindows } from "renderer/utils";
import { addPatch } from "./shared";
addPatch({
patches: [
{
find: "platform-web",
replacement: {
// eslint-disable-next-line no-useless-escape
match: /(?<=" platform-overlay"\):)\i/,
replace: "$self.getPlatformClass()"
}
}
],
getPlatformClass() {
if (isMac) return "platform-osx";
if (isWindows && Settings.store.discordWindowsTitleBar) return "platform-win";
return "platform-web";
}
});

View File

@@ -0,0 +1,42 @@
/*
* 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 { isLinux } from "renderer/utils";
if (isLinux) {
const original = navigator.mediaDevices.getDisplayMedia;
async function getVirtmic() {
try {
const devices = await navigator.mediaDevices.enumerateDevices();
const audioDevice = devices.find(({ label }) => label === "vencord-screen-share");
return audioDevice?.deviceId;
} catch (error) {
return null;
}
}
navigator.mediaDevices.getDisplayMedia = async function (opts) {
const stream = await original.call(this, opts);
const id = await getVirtmic();
if (id) {
const audio = await navigator.mediaDevices.getUserMedia({
audio: {
deviceId: {
exact: id
},
autoGainControl: false,
echoCancellation: false,
noiseSuppression: false
}
});
audio.getAudioTracks().forEach(t => stream.addTrack(t));
}
return stream;
};
}

View File

@@ -0,0 +1,30 @@
/*
* 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 { Patch } from "@vencord/types/utils/types";
window.VCDP = {};
interface PatchData {
patches: Omit<Patch, "plugin">[];
[key: string]: any;
}
export function addPatch<P extends PatchData>(p: P) {
const { patches, ...globals } = p;
for (const patch of patches as Patch[]) {
if (!Array.isArray(patch.replacement)) patch.replacement = [patch.replacement];
for (const r of patch.replacement) {
if (typeof r.replace === "string") r.replace = r.replace.replaceAll("$self", "VCDP");
}
patch.plugin = "Vesktop";
Vencord.Plugins.patches.push(patch);
}
Object.assign(VCDP, globals);
}

View File

@@ -0,0 +1,83 @@
/*
* 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 { addContextMenuPatch } from "@vencord/types/api/ContextMenu";
import { findStoreLazy } from "@vencord/types/webpack";
import { ContextMenu, FluxDispatcher, Menu } from "@vencord/types/webpack/common";
import { addPatch } from "./shared";
let word: string;
let corrections: string[];
const SpellCheckStore = findStoreLazy("SpellcheckStore");
// Make spellcheck suggestions work
addPatch({
patches: [
{
find: ".enableSpellCheck)",
replacement: {
// if (isDesktop) { DiscordNative.onSpellcheck(openMenu(props)) } else { e.preventDefault(); openMenu(props) }
match: /else (.{1,3})\.preventDefault\(\),(.{1,3}\(.{1,3}\))(?<=:(.{1,3})\.enableSpellCheck\).+?)/,
// ... else { $self.onSlateContext(() => openMenu(props)) }
replace: "else {$self.onSlateContext($1, $3?.enableSpellCheck, () => $2)}"
}
}
],
onSlateContext(e: MouseEvent, hasSpellcheck: boolean | undefined, openMenu: () => void) {
if (!hasSpellcheck) {
e.preventDefault();
openMenu();
return;
}
const cb = (w: string, c: string[]) => {
VesktopNative.spellcheck.offSpellcheckResult(cb);
word = w;
corrections = c;
openMenu();
};
VesktopNative.spellcheck.onSpellcheckResult(cb);
}
});
addContextMenuPatch("textarea-context", children => () => {
const hasCorrections = Boolean(word && corrections?.length);
children.push(
<Menu.MenuGroup>
{hasCorrections && (
<>
{corrections.map(c => (
<Menu.MenuItem
id={"vcd-spellcheck-suggestion-" + c}
label={c}
action={() => VesktopNative.spellcheck.replaceMisspelling(c)}
/>
))}
<Menu.MenuSeparator />
<Menu.MenuItem
id="vcd-spellcheck-learn"
label={`Add ${word} to dictionary`}
action={() => VesktopNative.spellcheck.addToDictionary(word)}
/>
</>
)}
<Menu.MenuCheckboxItem
id="vcd-spellcheck-enabled"
label="Enable Spellcheck"
checked={SpellCheckStore.isEnabled()}
action={() => {
FluxDispatcher.dispatch({ type: "SPELLCHECK_TOGGLE" });
// Haven't found a good way to update state, so just close for now 🤷‍♀️
ContextMenu.close();
}}
/>
</Menu.MenuGroup>
);
});

View File

@@ -0,0 +1,30 @@
/*
* 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 { Settings } from "renderer/settings";
import { addPatch } from "./shared";
if (Settings.store.discordWindowsTitleBar)
addPatch({
patches: [
{
find: ".wordmarkWindows",
replacement: [
{
// TODO: Fix eslint rule
// eslint-disable-next-line no-useless-escape
match: /case \i\.\i\.WINDOWS:/,
replace: 'case "WEB":'
},
...["close", "minimize", "maximize"].map(op => ({
match: new RegExp(String.raw`\i\.\i\.${op}\b`),
replace: `VesktopNative.win.${op}`
}))
]
}
]
});

View File

@@ -1,14 +1,14 @@
/* /*
* SPDX-License-Identifier: GPL-3.0 * SPDX-License-Identifier: GPL-3.0
* Vencord Desktop, a desktop app aiming to give you a snappier Discord Experience * Vesktop, a desktop app aiming to give you a snappier Discord Experience
* Copyright (c) 2023 Vendicated and Vencord contributors * Copyright (c) 2023 Vendicated and Vencord contributors
*/ */
import { useEffect, useReducer } from "@vencord/types/webpack/common"; import { useEffect, useReducer } from "@vencord/types/webpack/common";
import { SettingsStore } from "shared/utils/SettingsStore"; import { SettingsStore } from "shared/utils/SettingsStore";
export const Settings = new SettingsStore(VencordDesktopNative.settings.get()); export const Settings = new SettingsStore(VesktopNative.settings.get());
Settings.addGlobalChangeListener((o, p) => VencordDesktopNative.settings.set(o, p)); Settings.addGlobalChangeListener((o, p) => VesktopNative.settings.set(o, p));
export function useSettings() { export function useSettings() {
const [, update] = useReducer(x => x + 1, 0); const [, update] = useReducer(x => x + 1, 0);

View File

@@ -0,0 +1,46 @@
/*
* 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 { Settings } from "./settings";
function isValidColor(color: CSSStyleValue | undefined): color is CSSUnparsedValue & { [0]: string } {
return color instanceof CSSUnparsedValue && typeof color[0] === "string" && CSS.supports("color", color[0]);
}
function resolveColor(color: string) {
const span = document.createElement("span");
span.style.color = color;
span.style.display = "none";
document.body.append(span);
const rgbColor = getComputedStyle(span).color;
span.remove();
return rgbColor;
}
const updateSplashColors = () => {
const bodyStyles = document.body.computedStyleMap();
const color = bodyStyles.get("--text-normal");
const backgroundColor = bodyStyles.get("--background-primary");
if (isValidColor(color)) {
Settings.store.splashColor = resolveColor(color[0]);
}
if (isValidColor(backgroundColor)) {
Settings.store.splashBackground = resolveColor(backgroundColor[0]);
}
};
if (document.readyState === "complete") {
updateSplashColors();
} else {
window.addEventListener("load", updateSplashColors);
}
window.addEventListener("beforeunload", updateSplashColors);

View File

@@ -1,10 +1,10 @@
/* /*
* SPDX-License-Identifier: GPL-3.0 * SPDX-License-Identifier: GPL-3.0
* Vencord Desktop, a desktop app aiming to give you a snappier Discord Experience * Vesktop, a desktop app aiming to give you a snappier Discord Experience
* Copyright (c) 2023 Vendicated and Vencord contributors * Copyright (c) 2023 Vendicated and Vencord contributors
*/ */
export const localStorage = (window.vcdLS = window.localStorage); export const { localStorage } = window;
export const isFirstRun = (() => { export const isFirstRun = (() => {
const key = "VCD_FIRST_RUN"; const key = "VCD_FIRST_RUN";
@@ -16,3 +16,5 @@ export const isFirstRun = (() => {
const { platform } = navigator; const { platform } = navigator;
export const isWindows = platform.startsWith("Win"); export const isWindows = platform.startsWith("Win");
export const isMac = platform.startsWith("Mac");
export const isLinux = platform.startsWith("Linux");

View File

@@ -1,6 +1,6 @@
/* /*
* SPDX-License-Identifier: GPL-3.0 * SPDX-License-Identifier: GPL-3.0
* Vencord Desktop, a desktop app aiming to give you a snappier Discord Experience * Vesktop, a desktop app aiming to give you a snappier Discord Experience
* Copyright (c) 2023 Vendicated and Vencord contributors * Copyright (c) 2023 Vendicated and Vencord contributors
*/ */
@@ -11,10 +11,13 @@ export const enum IpcEvents {
GET_RENDERER_CSS_FILE = "VCD_GET_RENDERER_CSS_FILE", GET_RENDERER_CSS_FILE = "VCD_GET_RENDERER_CSS_FILE",
GET_VERSION = "VCD_GET_VERSION", GET_VERSION = "VCD_GET_VERSION",
SUPPORTS_WINDOWS_TRANSPARENCY = "VCD_SUPPORTS_WINDOWS_TRANSPARENCY",
RELAUNCH = "VCD_RELAUNCH", RELAUNCH = "VCD_RELAUNCH",
CLOSE = "VCD_CLOSE", CLOSE = "VCD_CLOSE",
FOCUS = "VCD_FOCUS", FOCUS = "VCD_FOCUS",
MINIMIZE = "VCD_MINIMIZE",
MAXIMIZE = "VCD_MAXIMIZE",
SHOW_ITEM_IN_FOLDER = "VCD_SHOW_ITEM_IN_FOLDER", SHOW_ITEM_IN_FOLDER = "VCD_SHOW_ITEM_IN_FOLDER",
GET_SETTINGS = "VCD_GET_SETTINGS", GET_SETTINGS = "VCD_GET_SETTINGS",
@@ -27,10 +30,22 @@ export const enum IpcEvents {
UPDATE_IGNORE = "VCD_UPDATE_IGNORE", UPDATE_IGNORE = "VCD_UPDATE_IGNORE",
SPELLCHECK_SET_LANGUAGES = "VCD_SPELLCHECK_SET_LANGUAGES", SPELLCHECK_SET_LANGUAGES = "VCD_SPELLCHECK_SET_LANGUAGES",
SPELLCHECK_RESULT = "VCD_SPELLCHECK_RESULT",
SPELLCHECK_REPLACE_MISSPELLING = "VCD_SPELLCHECK_REPLACE_MISSPELLING",
SPELLCHECK_ADD_TO_DICTIONARY = "VCD_SPELLCHECK_ADD_TO_DICTIONARY",
SET_BADGE_COUNT = "VCD_SET_BADGE_COUNT",
CAPTURER_GET_LARGE_THUMBNAIL = "VCD_CAPTURER_GET_LARGE_THUMBNAIL", CAPTURER_GET_LARGE_THUMBNAIL = "VCD_CAPTURER_GET_LARGE_THUMBNAIL",
AUTOSTART_ENABLED = "VCD_AUTOSTART_ENABLED", AUTOSTART_ENABLED = "VCD_AUTOSTART_ENABLED",
ENABLE_AUTOSTART = "VCD_ENABLE_AUTOSTART", ENABLE_AUTOSTART = "VCD_ENABLE_AUTOSTART",
DISABLE_AUTOSTART = "VCD_DISABLE_AUTOSTART" DISABLE_AUTOSTART = "VCD_DISABLE_AUTOSTART",
VIRT_MIC_LIST = "VCD_VIRT_MIC_LIST",
VIRT_MIC_START = "VCD_VIRT_MIC_START",
VIRT_MIC_START_SYSTEM = "VCD_VIRT_MIC_START_ALL",
VIRT_MIC_STOP = "VCD_VIRT_MIC_STOP",
ARRPC_ACTIVITY = "VCD_ARRPC_ACTIVITY"
} }

View File

@@ -1,6 +1,6 @@
/* /*
* SPDX-License-Identifier: GPL-3.0 * SPDX-License-Identifier: GPL-3.0
* Vencord Desktop, a desktop app aiming to give you a snappier Discord Experience * Vesktop, a desktop app aiming to give you a snappier Discord Experience
* Copyright (c) 2023 Vendicated and Vencord contributors * Copyright (c) 2023 Vendicated and Vencord contributors
*/ */

View File

@@ -1,10 +1,12 @@
/* /*
* SPDX-License-Identifier: GPL-3.0 * SPDX-License-Identifier: GPL-3.0
* Vencord Desktop, a desktop app aiming to give you a snappier Discord Experience * Vesktop, a desktop app aiming to give you a snappier Discord Experience
* Copyright (c) 2023 Vendicated and Vencord contributors * Copyright (c) 2023 Vendicated and Vencord contributors
*/ */
import { join } from "path"; import { join } from "path";
export const STATIC_DIR = /* @__PURE__ */ join(__dirname, "..", "..", "static"); export const STATIC_DIR = /* @__PURE__ */ join(__dirname, "..", "..", "static");
export const VIEW_DIR = /* @__PURE__ */ join(STATIC_DIR, "views");
export const BADGE_DIR = /* @__PURE__ */ join(STATIC_DIR, "badges");
export const ICON_PATH = /* @__PURE__ */ join(STATIC_DIR, "icon.png"); export const ICON_PATH = /* @__PURE__ */ join(STATIC_DIR, "icon.png");

View File

@@ -1,24 +1,34 @@
/* /*
* SPDX-License-Identifier: GPL-3.0 * SPDX-License-Identifier: GPL-3.0
* Vencord Desktop, a desktop app aiming to give you a snappier Discord Experience * Vesktop, a desktop app aiming to give you a snappier Discord Experience
* Copyright (c) 2023 Vendicated and Vencord contributors * Copyright (c) 2023 Vendicated and Vencord contributors
*/ */
import type { Rectangle } from "electron"; import type { Rectangle } from "electron";
export interface Settings { export interface Settings {
discordBranch?: "stable" | "canary" | "ptb";
vencordDir?: string;
transparencyOption?: "none" | "mica" | "tabbed" | "acrylic";
tray?: boolean;
minimizeToTray?: boolean;
openLinksWithElectron?: boolean;
staticTitle?: boolean;
enableMenu?: boolean;
arRPC?: boolean;
appBadge?: boolean;
discordWindowsTitleBar?: boolean;
maximized?: boolean; maximized?: boolean;
minimized?: boolean; minimized?: boolean;
windowBounds?: Rectangle; windowBounds?: Rectangle;
discordBranch?: "stable" | "canary" | "ptb";
openLinksWithElectron?: boolean;
vencordDir?: string;
disableMinSize?: boolean; disableMinSize?: boolean;
tray?: boolean;
minimizeToTray?: boolean;
skippedUpdate?: string;
staticTitle?: boolean;
arRPC?: boolean;
checkUpdates?: boolean;
skippedUpdate?: string;
firstLaunch?: boolean; firstLaunch?: boolean;
splashTheming?: boolean;
splashColor?: string;
splashBackground?: string;
} }

View File

@@ -1,6 +1,6 @@
/* /*
* SPDX-License-Identifier: GPL-3.0 * SPDX-License-Identifier: GPL-3.0
* Vencord Desktop, a desktop app aiming to give you a snappier Discord Experience * Vesktop, a desktop app aiming to give you a snappier Discord Experience
* Copyright (c) 2023 Vendicated and Vencord contributors * Copyright (c) 2023 Vendicated and Vencord contributors
*/ */

View File

@@ -1,6 +1,6 @@
/* /*
* SPDX-License-Identifier: GPL-3.0 * SPDX-License-Identifier: GPL-3.0
* Vencord Desktop, a desktop app aiming to give you a snappier Discord Experience * Vesktop, a desktop app aiming to give you a snappier Discord Experience
* Copyright (c) 2023 Vendicated and Vencord contributors * Copyright (c) 2023 Vendicated and Vencord contributors
*/ */

View File

@@ -0,0 +1,13 @@
/*
* 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
*/
export function isTruthy<T>(item: T): item is Exclude<T, 0 | "" | false | null | undefined> {
return Boolean(item);
}
export function isNonNullish<T>(item: T): item is Exclude<T, null | undefined> {
return item != null;
}

View File

@@ -1,23 +0,0 @@
/*
* SPDX-License-Identifier: GPL-3.0
* Vencord Desktop, a desktop app aiming to give you a snappier Discord Experience
* Copyright (c) 2023 Vendicated and Vencord contributors
*/
type Func = (...args: any[]) => any;
export function monkeyPatch<O extends object>(
object: O,
key: keyof O,
replacement: (original: Func, ...args: any[]) => any
): void {
const original = object[key] as Func;
const replacer = (object[key] = function (this: unknown, ...args: any[]) {
return replacement.call(this, original, ...args);
} as any);
Object.defineProperties(replacer, Object.getOwnPropertyDescriptors(original));
replacer.toString = () => original.toString();
replacer.$$original = original;
}

View File

@@ -1,6 +1,6 @@
/* /*
* SPDX-License-Identifier: GPL-3.0 * SPDX-License-Identifier: GPL-3.0
* Vencord Desktop, a desktop app aiming to give you a snappier Discord Experience * Vesktop, a desktop app aiming to give you a snappier Discord Experience
* Copyright (c) 2023 Vendicated and Vencord contributors * Copyright (c) 2023 Vendicated and Vencord contributors
*/ */

View File

@@ -1,16 +1,17 @@
/* /*
* SPDX-License-Identifier: GPL-3.0 * SPDX-License-Identifier: GPL-3.0
* Vencord Desktop, a desktop app aiming to give you a snappier Discord Experience * Vesktop, a desktop app aiming to give you a snappier Discord Experience
* Copyright (c) 2023 Vendicated and Vencord contributors * Copyright (c) 2023 Vendicated and Vencord contributors
*/ */
import { app, BrowserWindow, ipcMain, shell } from "electron"; import { app, BrowserWindow, shell } from "electron";
import { Settings } 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"; import { githubGet, ReleaseData } from "main/utils/vencordLoader";
import { join } from "path"; import { join } from "path";
import { SplashProps } from "shared/browserWinProperties";
import { IpcEvents } from "shared/IpcEvents"; import { IpcEvents } from "shared/IpcEvents";
import { STATIC_DIR } from "shared/paths"; import { ICON_PATH, VIEW_DIR } from "shared/paths";
export interface UpdateData { export interface UpdateData {
currentVersion: string; currentVersion: string;
@@ -20,8 +21,8 @@ export interface UpdateData {
let updateData: UpdateData; let updateData: UpdateData;
ipcMain.handle(IpcEvents.UPDATER_GET_DATA, () => updateData); handle(IpcEvents.UPDATER_GET_DATA, () => updateData);
ipcMain.handle(IpcEvents.UPDATER_DOWNLOAD, () => { handle(IpcEvents.UPDATER_DOWNLOAD, () => {
const portable = !!process.env.PORTABLE_EXECUTABLE_FILE; const portable = !!process.env.PORTABLE_EXECUTABLE_FILE;
const { assets } = updateData.release; const { assets } = updateData.release;
@@ -35,7 +36,11 @@ ipcMain.handle(IpcEvents.UPDATER_DOWNLOAD, () => {
return portable ? !isSetup : isSetup; return portable ? !isSetup : isSetup;
})!.browser_download_url; })!.browser_download_url;
case "darwin": case "darwin":
return assets.find(a => a.name.endsWith(".dmg"))!.browser_download_url; return assets.find(a =>
process.arch === "arm64"
? a.name.endsWith("-arm64-mac.zip")
: a.name.endsWith("-mac.zip") && !a.name.includes("arm64")
)!.browser_download_url;
case "linux": case "linux":
return updateData.release.html_url; return updateData.release.html_url;
default: default:
@@ -46,7 +51,7 @@ ipcMain.handle(IpcEvents.UPDATER_DOWNLOAD, () => {
shell.openExternal(url); shell.openExternal(url);
}); });
ipcMain.handle(IpcEvents.UPDATE_IGNORE, () => { handle(IpcEvents.UPDATE_IGNORE, () => {
Settings.store.skippedUpdate = updateData.latestVersion; Settings.store.skippedUpdate = updateData.latestVersion;
}); });
@@ -72,20 +77,21 @@ function isOutdated(oldVersion: string, newVersion: string) {
} }
export async function checkUpdates() { export async function checkUpdates() {
// if (IS_DEV) return; if (Settings.store.checkUpdates === false) return;
try { try {
const raw = await githubGet("/repos/Vencord/Desktop/releases/latest"); const raw = await githubGet("/repos/Vencord/Vesktop/releases/latest");
const data = JSON.parse(raw.toString("utf-8")) as ReleaseData; const data = JSON.parse(raw.toString("utf-8")) as ReleaseData;
const oldVersion = app.getVersion(); const oldVersion = app.getVersion();
const newVersion = data.tag_name.replace(/^v/, ""); const newVersion = data.tag_name.replace(/^v/, "");
if (Settings.store.skippedUpdate !== newVersion && isOutdated(oldVersion, newVersion)) { updateData = {
updateData = { currentVersion: oldVersion,
currentVersion: oldVersion, latestVersion: newVersion,
latestVersion: newVersion, release: data
release: data };
};
if (Settings.store.skippedUpdate !== newVersion && isOutdated(oldVersion, newVersion)) {
openNewUpdateWindow(); openNewUpdateWindow();
} }
} catch (e) { } catch (e) {
@@ -95,11 +101,19 @@ export async function checkUpdates() {
function openNewUpdateWindow() { function openNewUpdateWindow() {
const win = new BrowserWindow({ const win = new BrowserWindow({
...SplashProps, width: 500,
autoHideMenuBar: true,
alwaysOnTop: true,
webPreferences: { webPreferences: {
preload: join(__dirname, "updaterPreload.js") preload: join(__dirname, "updaterPreload.js"),
} nodeIntegration: false,
contextIsolation: true,
sandbox: true
},
icon: ICON_PATH
}); });
win.loadFile(join(STATIC_DIR, "updater.html")); makeLinksOpenExternally(win);
win.loadFile(join(VIEW_DIR, "updater.html"));
} }

View File

@@ -1,11 +1,11 @@
/* /*
* SPDX-License-Identifier: GPL-3.0 * SPDX-License-Identifier: GPL-3.0
* Vencord Desktop, a desktop app aiming to give you a snappier Discord Experience * Vesktop, a desktop app aiming to give you a snappier Discord Experience
* Copyright (c) 2023 Vendicated and Vencord contributors * Copyright (c) 2023 Vendicated and Vencord contributors
*/ */
import { contextBridge } from "electron"; import { contextBridge } from "electron";
import { invoke } from "preload/typedIpcs"; import { invoke } from "preload/typedIpc";
import { IpcEvents } from "shared/IpcEvents"; import { IpcEvents } from "shared/IpcEvents";
import type { UpdateData } from "./main"; import type { UpdateData } from "./main";

BIN
static/badges/1.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

BIN
static/badges/10.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

BIN
static/badges/11.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

BIN
static/badges/2.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

BIN
static/badges/3.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

BIN
static/badges/4.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

BIN
static/badges/5.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

BIN
static/badges/6.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

BIN
static/badges/7.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

BIN
static/badges/8.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

BIN
static/badges/9.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

2
static/dist/.gitignore vendored Normal file
View File

@@ -0,0 +1,2 @@
*
!.gitignore

BIN
static/shiggy.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

View File

@@ -1,9 +1,9 @@
<head> <head>
<link rel="stylesheet" href="./style.css" type="text/css" />
<style> <style>
body { body {
padding: 2em; padding: 2em;
font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, Ubuntu, Cantarell,
"Open Sans", "Helvetica Neue", sans-serif;
} }
h1 { h1 {
@@ -13,10 +13,10 @@
</head> </head>
<body> <body>
<h1>Vencord Desktop %VERSION%</h1> <h1 id="title">Vesktop</h1>
<p> <p>
Vencord Desktop is a free/libre cross platform desktop app aiming to give you a snappier Discord experience with Vesktop is a free/libre cross platform desktop app aiming to give you a snappier Discord experience with Vencord
Vencord pre-installed pre-installed
</p> </p>
<section> <section>
@@ -26,17 +26,17 @@
<a href="https://vencord.dev" target="_blank">Vencord Website</a> <a href="https://vencord.dev" target="_blank">Vencord Website</a>
</li> </li>
<li> <li>
<a href="https://github.com/Vencord/Desktop" target="_blank">Source Code</a> <a href="https://github.com/Vencord/Vesktop" target="_blank">Source Code</a>
</li> </li>
<li> <li>
<a href="https://github.com/Vencord/Desktop/issues" target="_blank">Report bugs / Request features</a> <a href="https://github.com/Vencord/Vesktop/issues" target="_blank">Report bugs / Request features</a>
</li> </li>
</ul> </ul>
</section> </section>
<section> <section>
<h2>Acknowledgements</h2> <h2>Acknowledgements</h2>
<p>These awesome libraries empower Vencord Desktop</p> <p>These awesome libraries empower Vesktop</p>
<ul> <ul>
<li> <li>
<a href="https://github.com/electron/electron" target="_blank">Electron</a> <a href="https://github.com/electron/electron" target="_blank">Electron</a>
@@ -51,12 +51,25 @@
<a href="https://github.com/OpenAsar/arrpc" target="_blank">arrpc</a> <a href="https://github.com/OpenAsar/arrpc" target="_blank">arrpc</a>
- An open implementation of Discord's Rich Presence server - An open implementation of Discord's Rich Presence server
</li> </li>
<li>
<a href="https://github.com/Soundux/rohrkabel" target="_blank">rohrkabel</a>
- A C++ RAII Pipewire-API Wrapper
</li>
<li> <li>
And many And many
<a href="https://github.com/Vencord/Desktop/blob/main/pnpm-lock.yaml" target="_blank" <a href="https://github.com/Vencord/Vesktop/blob/main/pnpm-lock.yaml" target="_blank"
>more awesome open source libraries</a >more awesome open source libraries</a
> >
</li> </li>
</ul> </ul>
</section> </section>
</body> </body>
<script type="module">
const data = await Updater.getData();
if (data.currentVersion) {
const title = document.getElementById("title");
title.textContent += ` v${data.currentVersion}`;
}
</script>

View File

@@ -1,29 +1,10 @@
<head> <head>
<link rel="stylesheet" href="./style.css" type="text/css" />
<style> <style>
:root {
--bg: white;
--fg: black;
--fg-secondary: #313338;
--fg-semi-trans: rgb(0 0 0 / 0.2);
--link: #006ce7;
}
@media (prefers-color-scheme: dark) {
:root {
--bg: hsl(223 6.7% 20.6%);
--fg: white;
--fg-secondary: #b5bac1;
--fg-semi-trans: rgb(255 255 255 / 0.2);
--link: #00a8fc;
}
}
body { body {
height: 100vh; height: 100vh;
font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, Ubuntu, Cantarell,
"Open Sans", "Helvetica Neue", sans-serif;
margin: 0;
padding: 1.5em; padding: 1.5em;
padding-bottom: 1em; padding-bottom: 1em;
@@ -34,13 +15,9 @@
box-sizing: border-box; box-sizing: border-box;
} }
body,
select { select {
background: var(--bg); background: var(--bg);
color: var(--fg); color: var(--fg);
}
select {
padding: 0.3em; padding: 0.3em;
margin: -0.3em; margin: -0.3em;
border-radius: 6px; border-radius: 6px;
@@ -54,10 +31,6 @@
margin: 1em 0 2em; margin: 1em 0 2em;
} }
a {
color: var(--link);
}
form { form {
display: grid; display: grid;
gap: 1em; gap: 1em;
@@ -131,7 +104,7 @@
</head> </head>
<body> <body>
<h1>Welcome to Vencord Desktop</h1> <h1>Welcome to Vesktop</h1>
<p>Let's customise your experience!</p> <p>Let's customise your experience!</p>
<form> <form>
@@ -147,7 +120,7 @@
<label> <label>
<div> <div>
<h2>Start with System</h2> <h2>Start with System</h2>
<span>Automatically open Vencord Desktop when your computer starts</span> <span>Automatically open Vesktop when your computer starts</span>
</div> </div>
<input type="checkbox" name="autoStart" /> <input type="checkbox" name="autoStart" />
</label> </label>
@@ -155,7 +128,10 @@
<label> <label>
<div> <div>
<h2>Rich Presence</h2> <h2>Rich Presence</h2>
<span>Enable Rich presence (game activity) via arRPC</span> <span
>Enable Rich presence (game activity) via
<a href="https://github.com/OpenAsar/arrpc" target="_blank">arRPC</a></span
>
</div> </div>
<input type="checkbox" name="richPresence" checked /> <input type="checkbox" name="richPresence" checked />
</label> </label>

View File

@@ -1,16 +1,11 @@
<head> <head>
<link rel="stylesheet" href="./style.css" type="text/css" />
<style> <style>
* { * {
user-select: none; user-select: none;
} }
body {
font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, Ubuntu, Cantarell,
"Open Sans", "Helvetica Neue", sans-serif;
margin: 0;
padding: 0;
}
.wrapper { .wrapper {
box-sizing: border-box; box-sizing: border-box;
height: 100%; height: 100%;
@@ -18,13 +13,11 @@
flex-direction: column; flex-direction: column;
justify-content: center; justify-content: center;
align-items: center; align-items: center;
background-color: hsl(223 6.7% 20.6%);
border-radius: 8px; border-radius: 8px;
border: 1px solid hsl(220 6.5% 18%); border: 1px solid var(--fg-semi-trans);
} }
p { p {
color: rgb(219, 222, 225);
text-align: center; text-align: center;
} }
@@ -39,10 +32,10 @@
<div class="wrapper"> <div class="wrapper">
<img <img
draggable="false" draggable="false"
src="https://cdn.discordapp.com/emojis/1024751291504791654.gif?size=512" src="../shiggy.gif"
alt="shiggy" alt="shiggy"
role="presentation" role="presentation"
/> />
<p>Loading Vencord Desktop...</p> <p>Loading Vesktop...</p>
</div> </div>
</body> </body>

30
static/views/style.css Normal file
View File

@@ -0,0 +1,30 @@
:root {
--bg: white;
--fg: black;
--fg-secondary: #313338;
--fg-semi-trans: rgb(0 0 0 / 0.2);
--link: #006ce7;
}
@media (prefers-color-scheme: dark) {
:root {
--bg: hsl(223 6.7% 20.6%);
--fg: white;
--fg-secondary: #b5bac1;
--fg-semi-trans: rgb(255 255 255 / 0.2);
--link: #00a8fc;
}
}
body {
font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, Ubuntu, Cantarell,
"Open Sans", "Helvetica Neue", sans-serif;
margin: 0;
padding: 0;
background: var(--bg);
color: var(--fg);
}
a {
color: var(--link);
}

View File

@@ -1,22 +1,13 @@
<head> <head>
<style> <link rel="stylesheet" href="./style.css" type="text/css" />
body {
font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, Ubuntu, Cantarell,
"Open Sans", "Helvetica Neue", sans-serif;
margin: 0;
padding: 0;
color: rgb(219, 222, 225);
}
<style>
.wrapper { .wrapper {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
justify-content: space-between; justify-content: space-between;
box-sizing: border-box; box-sizing: border-box;
height: 100%; min-height: 100%;
background-color: #313338;
border-radius: 8px;
border: 1px solid #248046;
padding: 1em; padding: 1em;
} }
@@ -34,7 +25,7 @@
button { button {
cursor: pointer; cursor: pointer;
padding: 0.5em; padding: 0.5em;
color: white; color: var(--fg);
border: none; border: none;
border-radius: 3px; border-radius: 3px;
font-weight: bold; font-weight: bold;
@@ -60,12 +51,15 @@
<div class="wrapper"> <div class="wrapper">
<section> <section>
<h1>Update Available</h1> <h1>Update Available</h1>
<p>There's a new update for Vencord Desktop! Update now to get new fixes and features!</p> <p>There's a new update for Vesktop! Update now to get new fixes and features!</p>
<p> <p>
Current: <span id="current"></span> Current: <span id="current"></span>
<br /> <br />
Latest: <span id="latest"></span> Latest: <span id="latest"></span>
</p> </p>
<h2>Changelog</h2>
<p id="changelog">Loading...</p>
</section> </section>
<section> <section>
@@ -113,3 +107,17 @@
}); });
} }
</script> </script>
<script type="module">
import { micromark } from "https://esm.sh/micromark@3?bundle";
import { gfm, gfmHtml } from "https://esm.sh/micromark-extension-gfm@2?bundle";
const changelog = (await Updater.getData()).release.body;
if (changelog)
document.getElementById("changelog").innerHTML = micromark(changelog, {
extensions: [gfm()],
htmlExtensions: [gfmHtml()]
})
.replace(/h1>/g, "h3>")
.replace(/<a /g, '<a target="_blank" ');
</script>