Merge branch 'main' into fix/macos-app-region-workaround

This commit is contained in:
vee 2024-07-04 19:38:10 +02:00 committed by GitHub
commit 60902fd91f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
37 changed files with 858 additions and 556 deletions

View file

@ -13,10 +13,11 @@ body:
Make sure both Vesktop and Vencord are fully up to date. You can update Vencord by right-clicking the Vesktop tray icon and pressing "Update Vencord"
Do not report any of the following issues:
**DO NOT REPORT** any of the following issues:
- Purely graphical glitches like flickering, scaling issues, etc: Issue with your gpu. Nothing we can do, update drivers or disable hardware acceleration
- Vencord related issues: This is the Vesktop repo, not Vencord
- Screenshare not starting / black screening on Linux: Issue with your desktop environment, specifically its xdg-desktop-portal
- **SCREENSHARE NOT STARTING** / black screening on Linux: Issue with your desktop environment, specifically its xdg-desktop-portal.
If you're on flatpak, try using native version. If that also doesn't work, you have to fix your systen. Inspect errors and google around.
Linux users: Please only report issues with supported packages (flatpak and any builds from the README / releases).
We do not support other packages, like the AUR or Nix packages, so please first make sure your issue is reproducible with official releases,

View file

@ -1,7 +1,7 @@
name: 🛠️ Feature Request
description: Create a feature request for Vesktop
labels: [bug]
title: "[Bug] <title>"
description: Request a feature for Vesktop
labels: [enhancement]
title: "[Feature Request] <title>"
body:
- type: markdown

View file

@ -11,28 +11,28 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: pnpm/action-setup@v2 # Install pnpm using packageManager key in package.json
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v4 # Install pnpm using packageManager key in package.json
- name: Use Node.js 18.18.2
uses: actions/setup-node@v3
with:
node-version: 18.18.2
- name: Use Node.js 20
uses: actions/setup-node@v4
with:
node-version: 20
- name: Install dependencies
run: pnpm i
- name: Install dependencies
run: pnpm i
- name: Update metainfo
run: pnpm updateMeta
- name: Update metainfo
run: pnpm updateMeta
- name: Commit and merge in changes
run: |
git config user.name "github-actions[bot]"
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
git checkout -b ci/meta-update
git add meta/dev.vencord.Vesktop.metainfo.xml
git commit -m "Insert release changes for ${{ github.event.release.tag_name }}"
git push origin ci/meta-update
gh pr create -B main -H ci/meta-update -t "Metainfo for ${{ github.event.release.tag_name }}" -b "This PR updates the metainfo for release ${{ github.event.release.tag_name }}. @lewisakura @Vendicated"
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Commit and merge in changes
run: |
git config user.name "github-actions[bot]"
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
git checkout -b ci/meta-update
git add meta/dev.vencord.Vesktop.metainfo.xml
git commit -m "Insert release changes for ${{ github.event.release.tag_name }}"
git push origin ci/meta-update
gh pr create -B main -H ci/meta-update -t "Metainfo for ${{ github.event.release.tag_name }}" -b "This PR updates the metainfo for release ${{ github.event.release.tag_name }}. @lewisakura @Vendicated"
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

View file

@ -22,13 +22,13 @@ jobs:
platform: windows
steps:
- uses: actions/checkout@v3
- uses: pnpm/action-setup@v2 # Install pnpm using packageManager key in package.json
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v4 # Install pnpm using packageManager key in package.json
- name: Use Node.js 18.18.2
uses: actions/setup-node@v3
- name: Use Node.js 20
uses: actions/setup-node@v4
with:
node-version: 18.18.2
node-version: 20
cache: "pnpm"
- name: Install dependencies
@ -43,7 +43,7 @@ jobs:
pnpm electron-builder --${{ matrix.platform }} --publish always
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Run Electron Builder
if: ${{ matrix.platform == 'mac' }}
run: |

View file

@ -11,13 +11,13 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: pnpm/action-setup@v2 # Install pnpm using packageManager key in package.json
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v4 # Install pnpm using packageManager key in package.json
- name: Use Node.js 18.18.2
uses: actions/setup-node@v3
- name: Use Node.js 20
uses: actions/setup-node@v4
with:
node-version: 18.18.2
node-version: 20
cache: "pnpm"
- name: Install dependencies

View file

@ -13,10 +13,10 @@ on:
jobs:
winget:
name: Publish winget package
runs-on: ubuntu-latest
runs-on: windows-latest
steps:
- name: Submit package to Winget Community Repo
uses: vedantmgoyal2009/winget-releaser@e68d386d5d6a1cef8cb0fb5e62b77ebcb83e7d58 # v2
uses: vedantmgoyal2009/winget-releaser@4614300d5812e5df91cb02ef0edbece623d5dea8
with:
identifier: Vencord.Vesktop
token: ${{ secrets.WINGET_PAT }}

1
.npmrc
View file

@ -1 +1,2 @@
node-linker=hoisted
package-manager-strict=false

View file

@ -182,7 +182,7 @@
<url type="vcs-browser">https://github.com/Vencord/Vesktop</url>
<categories>
<category>InstantMessaging</category>
<category>AudioVideo</category>
<category>Network</category>
</categories>
<requires>
<control>pointing</control>
@ -208,4 +208,4 @@
<keyword>Privacy</keyword>
<keyword>Mod</keyword>
</keywords>
</component>
</component>

View file

@ -24,10 +24,11 @@
"updateMeta": "tsx scripts/utils/updateMeta.mts"
},
"dependencies": {
"arrpc": "github:OpenAsar/arrpc#6960a8fd4d65d566da93dbdb8a7ca474aa0a3c9c"
"arrpc": "github:OpenAsar/arrpc#c62ec6a04c8d870530aa6944257fe745f6c59a24",
"electron-updater": "^6.2.1"
},
"optionalDependencies": {
"@vencord/venmic": "^3.5.0"
"@vencord/venmic": "^6.1.0"
},
"devDependencies": {
"@fal-works/esbuild-plugin-global-externals": "^2.1.2",
@ -37,7 +38,7 @@
"@typescript-eslint/parser": "^7.2.0",
"@vencord/types": "^1.8.4",
"dotenv": "^16.4.5",
"electron": "^29.1.1",
"electron": "^31.0.1",
"electron-builder": "^24.13.3",
"esbuild": "^0.20.1",
"eslint": "^8.57.0",
@ -65,6 +66,7 @@
"productName": "Vesktop",
"files": [
"!*",
"!node_modules",
"dist/js",
"static",
"package.json",
@ -118,8 +120,7 @@
{
"target": "default",
"arch": [
"x64",
"arm64"
"universal"
]
}
],
@ -158,12 +159,29 @@
},
"win": {
"target": [
"nsis",
"zip"
{
"target": "nsis",
"arch": [
"x64",
"arm64"
]
},
{
"target": "zip",
"arch": [
"x64",
"arm64"
]
}
]
},
"publish": {
"provider": "github"
}
},
"pnpm": {
"patchedDependencies": {
"arrpc@3.4.0": "patches/arrpc@3.4.0.patch"
}
}
}

14
patches/arrpc@3.4.0.patch Normal file
View file

@ -0,0 +1,14 @@
diff --git a/src/process/index.js b/src/process/index.js
index 97ea6514b54dd9c5df588c78f0397d31ab5f882a..c2bdbd6aaa5611bc6ff1d993beeb380b1f5ec575 100644
--- a/src/process/index.js
+++ b/src/process/index.js
@@ -5,8 +5,7 @@ import fs from 'node:fs';
import { dirname, join } from 'path';
import { fileURLToPath } from 'url';
-const __dirname = dirname(fileURLToPath(import.meta.url));
-const DetectableDB = JSON.parse(fs.readFileSync(join(__dirname, 'detectable.json'), 'utf8'));
+const DetectableDB = require('./detectable.json');
import * as Natives from './native/index.js';
const Native = Natives[process.platform];

View file

@ -4,17 +4,25 @@ settings:
autoInstallPeers: true
excludeLinksFromLockfile: false
patchedDependencies:
arrpc@3.4.0:
hash: biyukfa6dww2wxujy4eyvkhrti
path: patches/arrpc@3.4.0.patch
importers:
.:
dependencies:
arrpc:
specifier: github:OpenAsar/arrpc#6960a8fd4d65d566da93dbdb8a7ca474aa0a3c9c
version: https://codeload.github.com/OpenAsar/arrpc/tar.gz/6960a8fd4d65d566da93dbdb8a7ca474aa0a3c9c
specifier: github:OpenAsar/arrpc#c62ec6a04c8d870530aa6944257fe745f6c59a24
version: https://codeload.github.com/OpenAsar/arrpc/tar.gz/c62ec6a04c8d870530aa6944257fe745f6c59a24(patch_hash=biyukfa6dww2wxujy4eyvkhrti)
electron-updater:
specifier: ^6.2.1
version: 6.2.1
optionalDependencies:
'@vencord/venmic':
specifier: ^3.5.0
version: 3.5.0
specifier: ^6.1.0
version: 6.1.0
devDependencies:
'@fal-works/esbuild-plugin-global-externals':
specifier: ^2.1.2
@ -38,8 +46,8 @@ importers:
specifier: ^16.4.5
version: 16.4.5
electron:
specifier: ^29.1.1
version: 29.1.1
specifier: ^31.0.1
version: 31.0.1
electron-builder:
specifier: ^24.13.3
version: 24.13.3(electron-builder-squirrel-windows@24.13.3(dmg-builder@24.13.3))
@ -424,6 +432,7 @@ packages:
'@humanwhocodes/config-array@0.11.14':
resolution: {integrity: sha512-3T8LkOmg45BV5FICb15QQMsyUSWrQ8AygVfC7ZG32zOalnqrilm018ZVCw0eapXux8FtA33q8PSRSstjee3jSg==}
engines: {node: '>=10.10.0'}
deprecated: Use @eslint/config-array instead
'@humanwhocodes/module-importer@1.0.1':
resolution: {integrity: sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==}
@ -431,6 +440,7 @@ packages:
'@humanwhocodes/object-schema@2.0.2':
resolution: {integrity: sha512-6EwiSjwWYP7pTckG6I5eyFANjPhmPjUX9JRLUSfNPC7FX7zK9gyZAfUEaECL6ALTpGX5AjnBq3C9XmVWPitNpw==}
deprecated: Use @eslint/object-schema instead
'@isaacs/cliui@8.0.2':
resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==}
@ -603,8 +613,8 @@ packages:
'@vencord/types@1.8.4':
resolution: {integrity: sha512-ogLqIOHVO+5zxKUVxAfGIAUZoEfIomrlg6f0cZ/2yd5vBAn1fA9Gi/NASoKfHZuJt8ZcYw329bgn0ah/VufqMg==}
'@vencord/venmic@3.5.0':
resolution: {integrity: sha512-kPvrPcIeMkuqQriuiQAJ9rEBeqGR2nmFBuUtbZRGyiNRF9RDAfWSJYqhHVm6F7wbcqrSZio6FazZuBo0LvjJRw==}
'@vencord/venmic@6.1.0':
resolution: {integrity: sha512-YiCtzml/W8tYbGhu3jm5jfbbEnl2slKKARNK0jO+8qV979k9eFnfIRTxvhMN/SWq1h8ZNJdXVwvXpffQwq0RuA==}
engines: {node: '>=14.15'}
os: [linux]
@ -727,9 +737,9 @@ packages:
resolution: {integrity: sha512-bMxMKAjg13EBSVscxTaYA4mRc5t1UAXa2kXiGTNfZ079HIWXEkKmkgFrh/nJqamaLSrXO5H4WFFkPEaLJWbs3A==}
engines: {node: '>= 0.4'}
arrpc@https://codeload.github.com/OpenAsar/arrpc/tar.gz/6960a8fd4d65d566da93dbdb8a7ca474aa0a3c9c:
resolution: {tarball: https://codeload.github.com/OpenAsar/arrpc/tar.gz/6960a8fd4d65d566da93dbdb8a7ca474aa0a3c9c}
version: 3.3.1
arrpc@https://codeload.github.com/OpenAsar/arrpc/tar.gz/c62ec6a04c8d870530aa6944257fe745f6c59a24:
resolution: {tarball: https://codeload.github.com/OpenAsar/arrpc/tar.gz/c62ec6a04c8d870530aa6944257fe745f6c59a24}
version: 3.4.0
hasBin: true
assert-plus@1.0.0:
@ -1080,8 +1090,11 @@ packages:
electron-publish@24.13.1:
resolution: {integrity: sha512-2ZgdEqJ8e9D17Hwp5LEq5mLQPjqU3lv/IALvgp+4W8VeNhryfGhYEQC/PgDPMrnWUp+l60Ou5SJLsu+k4mhQ8A==}
electron@29.1.1:
resolution: {integrity: sha512-cXN15NgCi7MkzGo5/23ZQbii+0UfhmUiDjACunmzcUofYCjF42XhFbL7JZnwgI0qtBCCeJU8qZNZt9lU91gUFw==}
electron-updater@6.2.1:
resolution: {integrity: sha512-83eKIPW14qwZqUUM6wdsIRwVKZyjmHxQ4/8G+1C6iS5PdDt7b1umYQyj1/qPpH510GmHEQe4q0kCPe3qmb3a0Q==}
electron@31.0.1:
resolution: {integrity: sha512-2eBcp4iqLkTsml6mMq+iqrS5u3kJ/2mpOLP7Mj7lo0uNK3OyfNqRS9z1ArsHjBF2/HV250Te/O9nKrwQRTX/+g==}
engines: {node: '>= 12.20.55'}
hasBin: true
@ -1441,6 +1454,7 @@ packages:
glob@7.2.3:
resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==}
deprecated: Glob versions prior to v9 are no longer supported
global-agent@3.0.0:
resolution: {integrity: sha512-PT6XReJ+D07JvGoxQMkT6qji/jVNfX/h364XHZOWeRzy64sSFr+xJ5OX7LI3b4MPQzdL4H8Y8M0xzPpsVMwA8Q==}
@ -1561,6 +1575,7 @@ packages:
inflight@1.0.6:
resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==}
deprecated: This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.
inherits@2.0.4:
resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==}
@ -1789,9 +1804,15 @@ packages:
lodash.difference@4.5.0:
resolution: {integrity: sha512-dS2j+W26TQ7taQBGN8Lbbq04ssV3emRw4NY58WErlTO29pIqS0HmoT5aJ9+TUQ1N3G+JOZSji4eugsWwGp9yPA==}
lodash.escaperegexp@4.1.2:
resolution: {integrity: sha512-TM9YBvyC84ZxE3rgfefxUWiQKLilstD6k7PTGt6wfbtXF8ixIJLOL3VYyV/z+ZiPLsVxAsKAFVwWlWeb2Y8Yyw==}
lodash.flatten@4.4.0:
resolution: {integrity: sha512-C5N2Z3DgnnKr0LOpv/hKCgKdb7ZZwafIrsesve6lmzvZIRZRGaZ/l6Q8+2W7NaT+ZwO3fFlSCzCzrDCFdJfZ4g==}
lodash.isequal@4.5.0:
resolution: {integrity: sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ==}
lodash.isplainobject@4.0.6:
resolution: {integrity: sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==}
@ -2155,6 +2176,7 @@ packages:
rimraf@3.0.2:
resolution: {integrity: sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==}
deprecated: Rimraf versions prior to v4 are no longer supported
hasBin: true
roarr@2.15.4:
@ -2371,6 +2393,9 @@ packages:
text-table@0.2.0:
resolution: {integrity: sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==}
tiny-typed-emitter@2.1.0:
resolution: {integrity: sha512-qVtvMxeXbVej0cQWKqVSSAHmKZEHAvxdF8HEUBFWts8h+xEo5m/lEiPakuyZ3BnCBjOD8i24kzNOiOLLgsSxhA==}
tmp-promise@3.0.3:
resolution: {integrity: sha512-RwM7MoPojPxsOBYnyd2hy0bxtIlVrihNs9pj5SUvY8Zz1sQcQG2tG1hSr8PDxfgEB8RNKDhqbIlroIarSNDNsQ==}
@ -3031,7 +3056,7 @@ snapshots:
standalone-electron-types: 1.0.0
type-fest: 3.13.1
'@vencord/venmic@3.5.0':
'@vencord/venmic@6.1.0':
dependencies:
cmake-js: 7.3.0
node-addon-api: 8.0.0
@ -3220,7 +3245,7 @@ snapshots:
is-array-buffer: 3.0.4
is-shared-array-buffer: 1.0.3
arrpc@https://codeload.github.com/OpenAsar/arrpc/tar.gz/6960a8fd4d65d566da93dbdb8a7ca474aa0a3c9c:
arrpc@https://codeload.github.com/OpenAsar/arrpc/tar.gz/c62ec6a04c8d870530aa6944257fe745f6c59a24(patch_hash=biyukfa6dww2wxujy4eyvkhrti):
dependencies:
ws: 8.13.0
transitivePeerDependencies:
@ -3651,7 +3676,20 @@ snapshots:
transitivePeerDependencies:
- supports-color
electron@29.1.1:
electron-updater@6.2.1:
dependencies:
builder-util-runtime: 9.2.4
fs-extra: 10.1.0
js-yaml: 4.1.0
lazy-val: 1.0.5
lodash.escaperegexp: 4.1.2
lodash.isequal: 4.5.0
semver: 7.6.0
tiny-typed-emitter: 2.1.0
transitivePeerDependencies:
- supports-color
electron@31.0.1:
dependencies:
'@electron/get': 2.0.3
'@types/node': 20.11.26
@ -4512,8 +4550,12 @@ snapshots:
lodash.difference@4.5.0: {}
lodash.escaperegexp@4.1.2: {}
lodash.flatten@4.4.0: {}
lodash.isequal@4.5.0: {}
lodash.isplainobject@4.0.6: {}
lodash.merge@4.6.2: {}
@ -5125,6 +5167,8 @@ snapshots:
text-table@0.2.0: {}
tiny-typed-emitter@2.1.0: {}
tmp-promise@3.0.3:
dependencies:
tmp: 0.2.3

View file

@ -63,12 +63,6 @@ await Promise.all([
outfile: "dist/js/preload.js",
footer: { js: "//# sourceURL=VCDPreload" }
}),
createContext({
...NodeCommonOpts,
entryPoints: ["src/updater/preload.ts"],
outfile: "dist/js/updaterPreload.js",
footer: { js: "//# sourceURL=VCDUpdaterPreload" }
}),
createContext({
...CommonOpts,
globalName: "Vesktop",

View file

@ -5,11 +5,22 @@
*/
import { app } from "electron";
import { existsSync, readdirSync, renameSync, rmdirSync } from "fs";
import { join } from "path";
import { existsSync, mkdirSync, readdirSync, renameSync, rmdirSync } from "fs";
import { dirname, join } from "path";
const vesktopDir = dirname(process.execPath);
export const PORTABLE =
process.platform === "win32" &&
!process.execPath.toLowerCase().endsWith("electron.exe") &&
!existsSync(join(vesktopDir, "Uninstall Vesktop.exe"));
const LEGACY_DATA_DIR = join(app.getPath("appData"), "VencordDesktop", "VencordDesktop");
export const DATA_DIR = process.env.VENCORD_USER_DATA_DIR || join(app.getPath("userData"));
export const DATA_DIR =
process.env.VENCORD_USER_DATA_DIR || (PORTABLE ? join(vesktopDir, "Data") : join(app.getPath("userData")));
mkdirSync(DATA_DIR, { recursive: true });
// TODO: remove eventually
if (existsSync(LEGACY_DATA_DIR)) {
try {
@ -26,7 +37,8 @@ if (existsSync(LEGACY_DATA_DIR)) {
console.error("Migration failed", e);
}
}
app.setPath("sessionData", join(DATA_DIR, "sessionData"));
const SESSION_DATA_DIR = join(DATA_DIR, "sessionData");
app.setPath("sessionData", SESSION_DATA_DIR);
export const VENCORD_SETTINGS_DIR = join(DATA_DIR, "settings");
export const VENCORD_QUICKCSS_FILE = join(VENCORD_SETTINGS_DIR, "quickCss.css");
@ -36,7 +48,8 @@ export const VENCORD_THEMES_DIR = join(DATA_DIR, "themes");
// needs to be inline require because of circular dependency
// as otherwise "DATA_DIR" (which is used by ./settings) will be uninitialised
export const VENCORD_FILES_DIR =
(require("./settings") as typeof import("./settings")).Settings.store.vencordDir || join(DATA_DIR, "vencordDist");
(require("./settings") as typeof import("./settings")).State.store.vencordDir ||
join(SESSION_DATA_DIR, "vencordFiles");
export const USER_AGENT = `Vesktop/${app.getVersion()} (https://github.com/Vencord/Vesktop)`;

View file

@ -18,11 +18,11 @@ import { Settings, State } from "./settings";
import { makeLinksOpenExternally } from "./utils/makeLinksOpenExternally";
interface Data {
minimizeToTray: boolean;
discordBranch: "stable" | "canary" | "ptb";
autoStart: boolean;
importSettings: boolean;
richPresence: boolean;
minimizeToTray?: "on";
autoStart?: "on";
importSettings?: "on";
richPresence?: "on";
}
export function createFirstLaunchTour() {
@ -44,10 +44,11 @@ export function createFirstLaunchTour() {
if (!msg.startsWith("form:")) return;
const data = JSON.parse(msg.slice(5)) as Data;
console.log(data);
State.store.firstLaunch = false;
Settings.store.minimizeToTray = data.minimizeToTray;
Settings.store.discordBranch = data.discordBranch;
Settings.store.arRPC = data.richPresence;
Settings.store.minimizeToTray = !!data.minimizeToTray;
Settings.store.arRPC = !!data.richPresence;
if (data.autoStart) autoStart.enable();

View file

@ -7,7 +7,7 @@
import "./ipc";
import { app, BrowserWindow, nativeTheme } from "electron";
import { checkUpdates } from "updater/main";
import { autoUpdater } from "electron-updater";
import { DATA_DIR } from "./constants";
import { createFirstLaunchTour } from "./firstLaunch";
@ -21,6 +21,8 @@ if (IS_DEV) {
require("source-map-support").install();
}
autoUpdater.checkForUpdatesAndNotify();
// Make the Vencord files use our DATA_DIR
process.env.VENCORD_USER_DATA_DIR = DATA_DIR;
@ -42,7 +44,13 @@ function init() {
// disable renderer backgrounding to prevent the app from unloading when in the background
// https://github.com/electron/electron/issues/2822
// https://github.com/GoogleChrome/chrome-launcher/blob/5a27dd574d47a75fec0fb50f7b774ebf8a9791ba/docs/chrome-flags-for-tools.md#task-throttling
app.commandLine.appendSwitch("disable-renderer-backgrounding");
app.commandLine.appendSwitch("disable-background-timer-throttling");
app.commandLine.appendSwitch("disable-backgrounding-occluded-windows");
if (process.platform === "win32") {
disabledFeatures.push("CalculateNativeWinOcclusion");
}
// work around chrome 66 disabling autoplay by default
app.commandLine.appendSwitch("autoplay-policy", "no-user-gesture-required");
@ -50,12 +58,7 @@ function init() {
// HardwareMediaKeyHandling,MediaSessionService: Prevent Discord from registering as a media service.
//
// WidgetLayering (Vencord Added): Fix DevTools context menus https://github.com/electron/electron/issues/38790
disabledFeatures.push(
"WinRetrieveSuggestionsOnlyOnDemand",
"HardwareMediaKeyHandling",
"MediaSessionService",
"WidgetLayering"
);
disabledFeatures.push("WinRetrieveSuggestionsOnlyOnDemand", "HardwareMediaKeyHandling", "MediaSessionService");
app.commandLine.appendSwitch("enable-features", [...new Set(enabledFeatures)].filter(Boolean).join(","));
app.commandLine.appendSwitch("disable-features", [...new Set(disabledFeatures)].filter(Boolean).join(","));
@ -73,7 +76,6 @@ function init() {
});
app.whenReady().then(async () => {
checkUpdates();
if (process.platform === "win32") app.setAppUserModelId("dev.vencord.vesktop");
registerScreenShareHandler();

View file

@ -19,7 +19,7 @@ import { setBadgeCount } from "./appBadge";
import { autoStart } from "./autoStart";
import { VENCORD_FILES_DIR, VENCORD_QUICKCSS_FILE, VENCORD_THEMES_DIR } from "./constants";
import { mainWin } from "./mainWindow";
import { Settings } from "./settings";
import { Settings, State } from "./settings";
import { handle, handleSync } from "./utils/ipcWrappers";
import { PopoutWindows } from "./utils/popout";
import { isDeckGameMode, showGamePage } from "./utils/steamOS";
@ -93,12 +93,8 @@ handle(IpcEvents.MAXIMIZE, e => {
}
});
handle(IpcEvents.SPELLCHECK_SET_LANGUAGES, (_, languages: string[]) => {
const ses = session.defaultSession;
const available = ses.availableSpellCheckerLanguages;
const applicable = languages.filter(l => available.includes(l)).slice(0, 3);
if (applicable.length) ses.setSpellCheckerLanguages(applicable);
handleSync(IpcEvents.SPELLCHECK_GET_AVAILABLE_LANGUAGES, e => {
e.returnValue = session.defaultSession.availableSpellCheckerLanguages;
});
handle(IpcEvents.SPELLCHECK_REPLACE_MISSPELLING, (e, word: string) => {
@ -109,7 +105,14 @@ handle(IpcEvents.SPELLCHECK_ADD_TO_DICTIONARY, (e, word: string) => {
e.sender.session.addWordToSpellCheckerDictionary(word);
});
handle(IpcEvents.SELECT_VENCORD_DIR, async () => {
handleSync(IpcEvents.GET_VENCORD_DIR, e => (e.returnValue = State.store.vencordDir));
handle(IpcEvents.SELECT_VENCORD_DIR, async (_e, value?: null) => {
if (value === null) {
delete State.store.vencordDir;
return "ok";
}
const res = await dialog.showOpenDialog(mainWin!, {
properties: ["openDirectory"]
});
@ -118,7 +121,9 @@ handle(IpcEvents.SELECT_VENCORD_DIR, async () => {
const dir = res.filePaths[0];
if (!isValidVencordInstall(dir)) return "invalid";
return dir;
State.store.vencordDir = dir;
return "ok";
});
handle(IpcEvents.SET_BADGE_COUNT, (_, count: number) => setBadgeCount(count));

View file

@ -13,6 +13,7 @@ import {
MenuItemConstructorOptions,
nativeTheme,
screen,
session,
Tray
} from "electron";
import { rm } from "fs/promises";
@ -90,7 +91,7 @@ function initTray(win: BrowserWindow) {
click: createAboutWindow
},
{
label: "Update Vencord",
label: "Repair Vencord",
async click() {
await downloadVencordFiles();
app.relaunch();
@ -107,14 +108,14 @@ function initTray(win: BrowserWindow) {
type: "separator"
},
{
label: "Relaunch",
label: "Restart",
click() {
app.relaunch();
app.quit();
}
},
{
label: "Quit Vesktop",
label: "Quit",
click() {
isQuitting = true;
app.quit();
@ -360,12 +361,27 @@ function initSettingsListeners(win: BrowserWindow) {
addSettingsListener("enableMenu", enabled => {
win.setAutoHideMenuBar(enabled ?? false);
});
addSettingsListener("spellCheckLanguages", languages => initSpellCheckLanguages(win, languages));
}
async function initSpellCheckLanguages(win: BrowserWindow, languages?: string[]) {
languages ??= await win.webContents.executeJavaScript("[...new Set(navigator.languages)]").catch(() => []);
if (!languages) return;
const ses = session.defaultSession;
const available = ses.availableSpellCheckerLanguages;
const applicable = languages.filter(l => available.includes(l)).slice(0, 5);
if (applicable.length) ses.setSpellCheckerLanguages(applicable);
}
function initSpellCheck(win: BrowserWindow) {
win.webContents.on("context-menu", (_, data) => {
win.webContents.send(IpcEvents.SPELLCHECK_RESULT, data.misspelledWord, data.dictionarySuggestions);
});
initSpellCheckLanguages(win, Settings.store.spellCheckLanguages);
}
function createMainWindow() {
@ -387,7 +403,9 @@ function createMainWindow() {
contextIsolation: true,
devTools: true,
preload: join(__dirname, "preload.js"),
spellcheck: true
spellcheck: true,
// disable renderer backgrounding to prevent the app from unloading when in the background
backgroundThrottling: false
},
icon: ICON_PATH,
frame: !noFrame,
@ -412,6 +430,7 @@ function createMainWindow() {
autoHideMenuBar: enableMenu
}));
win.setMenuBarVisibility(false);
if (process.platform === "darwin" && customTitleBar) win.setWindowButtonVisibility(false);
win.on("close", e => {
const useTray = !isDeckGameMode && Settings.store.minimizeToTray !== false && Settings.store.tray !== false;

View file

@ -12,11 +12,13 @@ export function registerMediaPermissionsHandler() {
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");
if ("mediaTypes" in details) {
if (details.mediaTypes?.includes("audio")) {
granted &&= await systemPreferences.askForMediaAccess("microphone");
}
if (details.mediaTypes?.includes("video")) {
granted &&= await systemPreferences.askForMediaAccess("camera");
}
}
callback(granted);

View file

@ -35,25 +35,13 @@ function loadSettings<T extends object = any>(file: string, name: string) {
}
export const Settings = loadSettings<TSettings>(SETTINGS_FILE, "Vesktop settings");
if (Object.hasOwn(Settings.plain, "discordWindowsTitleBar")) {
Settings.plain.customTitleBar = Settings.plain.discordWindowsTitleBar;
delete Settings.plain.discordWindowsTitleBar;
Settings.markAsChanged();
}
export const VencordSettings = loadSettings<any>(VENCORD_SETTINGS_FILE, "Vencord settings");
if (Object.hasOwn(Settings.plain, "firstLaunch") && !existsSync(STATE_FILE)) {
console.warn("legacy state in settings.json detected. migrating to state.json");
const state = {} as TState;
for (const prop of [
"firstLaunch",
"maximized",
"minimized",
"skippedUpdate",
"steamOSLayoutVersion",
"windowBounds"
] as const) {
for (const prop of ["firstLaunch", "maximized", "minimized", "steamOSLayoutVersion", "windowBounds"] as const) {
state[prop] = Settings.plain[prop];
delete Settings.plain[prop];
}

View file

@ -4,7 +4,8 @@
* Copyright (c) 2023 Vendicated and Vencord contributors
*/
import { existsSync, mkdirSync } from "fs";
import { mkdirSync } from "fs";
import { access, constants as FsConstants } from "fs/promises";
import { join } from "path";
import { USER_AGENT, VENCORD_FILES_DIR } from "../constants";
@ -56,12 +57,18 @@ export async function downloadVencordFiles() {
);
}
export function isValidVencordInstall(dir: string) {
return FILES_TO_DOWNLOAD.every(f => existsSync(join(dir, f)));
const existsAsync = (path: string) =>
access(path, FsConstants.F_OK)
.then(() => true)
.catch(() => false);
export async function isValidVencordInstall(dir: string) {
return Promise.all(FILES_TO_DOWNLOAD.map(f => existsAsync(join(dir, f)))).then(arr => !arr.includes(false));
}
export async function ensureVencordFiles() {
if (isValidVencordInstall(VENCORD_FILES_DIR)) return;
if (await isValidVencordInstall(VENCORD_FILES_DIR)) return;
mkdirSync(VENCORD_FILES_DIR, { recursive: true });
await downloadVencordFiles();

View file

@ -4,13 +4,13 @@
* Copyright (c) 2023 Vendicated and Vencord contributors
*/
import type { PatchBay as PatchBayType } from "@vencord/venmic";
import type { LinkData, Node, PatchBay as PatchBayType } from "@vencord/venmic";
import { app, ipcMain } from "electron";
import { join } from "path";
import { IpcEvents } from "shared/IpcEvents";
import { STATIC_DIR } from "shared/paths";
type LinkData = Parameters<PatchBayType["link"]>[0];
import { Settings } from "./settings";
let PatchBay: typeof PatchBayType | undefined;
let patchBayInstance: PatchBayType | undefined;
@ -69,47 +69,64 @@ function getRendererAudioServicePid() {
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"]);
const { granularSelect } = Settings.store.audio ?? {};
const uniqueTargets = [...new Set(list)];
const targets = obtainVenmic()
?.list(granularSelect ? ["application.process.id"] : undefined)
.filter(s => s["application.process.id"] !== audioPid);
return list ? { ok: true, targets: uniqueTargets, hasPipewirePulse } : { ok: false, isGlibCxxOutdated };
return targets ? { ok: true, targets, hasPipewirePulse } : { ok: false, isGlibCxxOutdated };
});
ipcMain.handle(IpcEvents.VIRT_MIC_START, (_, targets: string[], workaround?: boolean) => {
ipcMain.handle(IpcEvents.VIRT_MIC_START, (_, include: Node[]) => {
const pid = getRendererAudioServicePid();
const { ignoreDevices, ignoreInputMedia, ignoreVirtual, workaround } = Settings.store.audio ?? {};
const data: LinkData = {
include: targets.map(target => ({ key: "application.name", value: target })),
exclude: [{ key: "application.process.id", value: pid }]
include,
exclude: [{ "application.process.id": pid }],
ignore_devices: ignoreDevices
};
if (ignoreInputMedia ?? true) {
data.exclude.push({ "media.class": "Stream/Input/Audio" });
}
if (ignoreVirtual) {
data.exclude.push({ "node.virtual": "true" });
}
if (workaround) {
data.workaround = [
{ key: "application.process.id", value: pid },
{ key: "media.name", value: "RecordStream" }
];
data.workaround = [{ "application.process.id": pid, "media.name": "RecordStream" }];
}
return obtainVenmic()?.link(data);
});
ipcMain.handle(IpcEvents.VIRT_MIC_START_SYSTEM, (_, workaround?: boolean, onlyDefaultSpeakers?: boolean) => {
ipcMain.handle(IpcEvents.VIRT_MIC_START_SYSTEM, (_, exclude: Node[]) => {
const pid = getRendererAudioServicePid();
const { workaround, ignoreDevices, ignoreInputMedia, ignoreVirtual, onlySpeakers, onlyDefaultSpeakers } =
Settings.store.audio ?? {};
const data: LinkData = {
exclude: [{ key: "application.process.id", value: pid }],
include: [],
exclude: [{ "application.process.id": pid }, ...exclude],
only_speakers: onlySpeakers,
ignore_devices: ignoreDevices,
only_default_speakers: onlyDefaultSpeakers
};
if (ignoreInputMedia ?? true) {
data.exclude.push({ "media.class": "Stream/Input/Audio" });
}
if (ignoreVirtual) {
data.exclude.push({ "node.virtual": "true" });
}
if (workaround) {
data.workaround = [
{ key: "application.process.id", value: pid },
{ key: "media.name", value: "RecordStream" }
];
data.workaround = [{ "application.process.id": pid, "media.name": "RecordStream" }];
}
return obtainVenmic()?.link(data);

View file

@ -4,9 +4,9 @@
* Copyright (c) 2023 Vendicated and Vencord contributors
*/
import { Node } from "@vencord/venmic";
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";
@ -33,14 +33,15 @@ export const VesktopNative = {
},
fileManager: {
showItemInFolder: (path: string) => invoke<void>(IpcEvents.SHOW_ITEM_IN_FOLDER, path),
selectVencordDir: () => invoke<LiteralUnion<"cancelled" | "invalid", string>>(IpcEvents.SELECT_VENCORD_DIR)
getVencordDir: () => sendSync<string | undefined>(IpcEvents.GET_VENCORD_DIR),
selectVencordDir: (value?: null) => invoke<"cancelled" | "invalid" | "ok">(IpcEvents.SELECT_VENCORD_DIR, value)
},
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),
getAvailableLanguages: () => sendSync<string[]>(IpcEvents.SPELLCHECK_GET_AVAILABLE_LANGUAGES),
onSpellcheckResult(cb: SpellCheckerResultCallback) {
spellCheckCallbacks.add(cb);
},
@ -63,11 +64,10 @@ export const VesktopNative = {
virtmic: {
list: () =>
invoke<
{ ok: false; isGlibCxxOutdated: boolean } | { ok: true; targets: string[]; hasPipewirePulse: boolean }
{ ok: false; isGlibCxxOutdated: boolean } | { ok: true; targets: Node[]; hasPipewirePulse: boolean }
>(IpcEvents.VIRT_MIC_LIST),
start: (targets: string[], workaround?: boolean) => invoke<void>(IpcEvents.VIRT_MIC_START, targets, workaround),
startSystem: (workaround?: boolean, onlyDefaultSpeakers?: boolean) =>
invoke<void>(IpcEvents.VIRT_MIC_START_SYSTEM, workaround, onlyDefaultSpeakers),
start: (include: Node[]) => invoke<void>(IpcEvents.VIRT_MIC_START, include),
startSystem: (exclude: Node[]) => invoke<void>(IpcEvents.VIRT_MIC_START_SYSTEM, exclude),
stop: () => invoke<void>(IpcEvents.VIRT_MIC_STOP)
},
arrpc: {

View file

@ -40,5 +40,3 @@ if (IS_DEV) {
});
}
// #endregion
VesktopNative.spellcheck.setLanguages(window.navigator.languages);

View file

@ -6,7 +6,7 @@
import "./screenSharePicker.css";
import { closeModal, Logger, Margins, Modals, ModalSize, openModal, useAwaiter } from "@vencord/types/utils";
import { closeModal, Logger, Modals, ModalSize, openModal, useAwaiter } from "@vencord/types/utils";
import { findStoreLazy, onceReady } from "@vencord/types/webpack";
import {
Button,
@ -19,8 +19,10 @@ import {
UserStore,
useState
} from "@vencord/types/webpack/common";
import { Node } from "@vencord/venmic";
import type { Dispatch, SetStateAction } from "react";
import { addPatch } from "renderer/patches/shared";
import { useSettings } from "renderer/settings";
import { isLinux, isWindows } from "renderer/utils";
const StreamResolutions = ["480", "720", "1080", "1440"] as const;
@ -31,14 +33,23 @@ const MediaEngineStore = findStoreLazy("MediaEngineStore");
export type StreamResolution = (typeof StreamResolutions)[number];
export type StreamFps = (typeof StreamFps)[number];
type SpecialSource = "None" | "Entire System";
type AudioSource = SpecialSource | Node;
type AudioSources = SpecialSource | Node[];
interface AudioItem {
name: string;
value: AudioSource;
}
interface StreamSettings {
resolution: StreamResolution;
fps: StreamFps;
audio: boolean;
audioSource?: string;
contentHint?: string;
workaround?: boolean;
onlyDefaultSpeakers?: boolean;
includeSources?: AudioSources;
excludeSources?: AudioSources;
}
export interface StreamPick extends StreamSettings {
@ -118,13 +129,17 @@ export function openScreenSharePicker(screens: Source[], skipPicker: boolean) {
modalProps={props}
submit={async v => {
didSubmit = true;
if (v.audioSource && v.audioSource !== "None") {
if (v.audioSource === "Entire System") {
await VesktopNative.virtmic.startSystem(v.workaround);
if (v.includeSources && v.includeSources !== "None") {
if (v.includeSources === "Entire System") {
await VesktopNative.virtmic.startSystem(
!v.excludeSources || isSpecialSource(v.excludeSources) ? [] : v.excludeSources
);
} else {
await VesktopNative.virtmic.start([v.audioSource], v.workaround);
await VesktopNative.virtmic.start(v.includeSources);
}
}
resolve(v);
}}
close={() => {
@ -159,6 +174,113 @@ function ScreenPicker({ screens, chooseScreen }: { screens: Source[]; chooseScre
);
}
function AudioSettingsModal({
modalProps,
close,
setAudioSources
}: {
modalProps: any;
close: () => void;
setAudioSources: (s: AudioSources) => void;
}) {
const Settings = useSettings();
return (
<Modals.ModalRoot {...modalProps} size={ModalSize.MEDIUM}>
<Modals.ModalHeader className="vcd-screen-picker-header">
<Forms.FormTitle tag="h2">Venmic Settings</Forms.FormTitle>
<Modals.ModalCloseButton onClick={close} />
</Modals.ModalHeader>
<Modals.ModalContent className="vcd-screen-picker-modal">
<Switch
hideBorder
onChange={v => (Settings.audio = { ...Settings.audio, workaround: v })}
value={Settings.audio?.workaround ?? false}
note={
<>
Work around an issue that causes the microphone to be shared instead of the correct audio.
Only enable if you're experiencing this issue.
</>
}
>
Microphone Workaround
</Switch>
<Switch
hideBorder
onChange={v => (Settings.audio = { ...Settings.audio, onlySpeakers: v })}
value={Settings.audio?.onlySpeakers ?? true}
note={
<>
When sharing entire desktop audio, only share apps that play to a speaker. You may want to
disable this when using "mix bussing".
</>
}
>
Only Speakers
</Switch>
<Switch
hideBorder
onChange={v => (Settings.audio = { ...Settings.audio, onlyDefaultSpeakers: v })}
value={Settings.audio?.onlyDefaultSpeakers ?? true}
note={
<>
When sharing entire desktop audio, only share apps that play to the <b>default</b> speakers.
You may want to disable this when using "mix bussing".
</>
}
>
Only Default Speakers
</Switch>
<Switch
hideBorder
onChange={v => (Settings.audio = { ...Settings.audio, ignoreInputMedia: v })}
value={Settings.audio?.ignoreInputMedia ?? true}
note={<>Exclude nodes that are intended to capture audio.</>}
>
Ignore Inputs
</Switch>
<Switch
hideBorder
onChange={v => (Settings.audio = { ...Settings.audio, ignoreVirtual: v })}
value={Settings.audio?.ignoreVirtual ?? false}
note={
<>
Exclude virtual nodes, such as nodes belonging to loopbacks. This might be useful when using
"mix bussing".
</>
}
>
Ignore Virtual
</Switch>
<Switch
hideBorder
onChange={v => (Settings.audio = { ...Settings.audio, ignoreDevices: v })}
value={Settings.audio?.ignoreDevices ?? true}
note={<>Exclude device nodes, such as nodes belonging to microphones or speakers.</>}
>
Ignore Devices
</Switch>
<Switch
hideBorder
onChange={value => {
Settings.audio = { ...Settings.audio, granularSelect: value };
setAudioSources("None");
}}
value={Settings.audio?.granularSelect ?? false}
note={<>Allow to select applications more granularly.</>}
>
Granular Selection
</Switch>
</Modals.ModalContent>
<Modals.ModalFooter className="vcd-screen-picker-footer">
<Button color={Button.Colors.TRANSPARENT} onClick={close}>
Back
</Button>
</Modals.ModalFooter>
</Modals.ModalRoot>
);
}
function StreamSettings({
source,
settings,
@ -170,6 +292,8 @@ function StreamSettings({
setSettings: Dispatch<SetStateAction<StreamSettings>>;
skipPicker: boolean;
}) {
const Settings = useSettings();
const [thumb] = useAwaiter(
() => (skipPicker ? Promise.resolve(source.url) : VesktopNative.capturer.getLargeThumbnail(source.id)),
{
@ -178,230 +302,346 @@ function StreamSettings({
}
);
const openSettings = () => {
const key = openModal(props => (
<AudioSettingsModal
modalProps={props}
close={() => props.onClose()}
setAudioSources={sources =>
setSettings(s => ({ ...s, includeSources: sources, excludeSources: sources }))
}
/>
));
};
return (
<div className={isLinux ? "vcd-screen-picker-settings-grid" : ""}>
<div>
<Forms.FormTitle>What you're streaming</Forms.FormTitle>
<Card className="vcd-screen-picker-card vcd-screen-picker-preview">
<img
src={thumb}
alt=""
className={isLinux ? "vcd-screen-picker-preview-img-linux" : "vcd-screen-picker-preview-img"}
/>
<Text variant="text-sm/normal">{source.name}</Text>
</Card>
<div>
<Forms.FormTitle>What you're streaming</Forms.FormTitle>
<Card className="vcd-screen-picker-card vcd-screen-picker-preview">
<img
src={thumb}
alt=""
className={isLinux ? "vcd-screen-picker-preview-img-linux" : "vcd-screen-picker-preview-img"}
/>
<Text variant="text-sm/normal">{source.name}</Text>
</Card>
<Forms.FormTitle>Stream Settings</Forms.FormTitle>
<Forms.FormTitle>Stream Settings</Forms.FormTitle>
<Card className="vcd-screen-picker-card">
<div className="vcd-screen-picker-quality">
<section>
<Forms.FormTitle>Resolution</Forms.FormTitle>
<Card className="vcd-screen-picker-card">
<div className="vcd-screen-picker-quality">
<section>
<Forms.FormTitle>Resolution</Forms.FormTitle>
<div className="vcd-screen-picker-radios">
{StreamResolutions.map(res => (
<label className="vcd-screen-picker-radio" data-checked={settings.resolution === res}>
<Text variant="text-sm/bold">{res}</Text>
<input
type="radio"
name="resolution"
value={res}
checked={settings.resolution === res}
onChange={() => setSettings(s => ({ ...s, resolution: res }))}
/>
</label>
))}
</div>
</section>
<section>
<Forms.FormTitle>Frame Rate</Forms.FormTitle>
<div className="vcd-screen-picker-radios">
{StreamFps.map(fps => (
<label className="vcd-screen-picker-radio" data-checked={settings.fps === fps}>
<Text variant="text-sm/bold">{fps}</Text>
<input
type="radio"
name="fps"
value={fps}
checked={settings.fps === fps}
onChange={() => setSettings(s => ({ ...s, fps }))}
/>
</label>
))}
</div>
</section>
</div>
<div className="vcd-screen-picker-quality">
<section>
<Forms.FormTitle>Content Type</Forms.FormTitle>
<div>
<div className="vcd-screen-picker-radios">
{StreamResolutions.map(res => (
<label
className="vcd-screen-picker-radio"
data-checked={settings.resolution === res}
>
<Text variant="text-sm/bold">{res}</Text>
<input
type="radio"
name="resolution"
value={res}
checked={settings.resolution === res}
onChange={() => setSettings(s => ({ ...s, resolution: res }))}
/>
</label>
))}
</div>
</section>
<section>
<Forms.FormTitle>Frame Rate</Forms.FormTitle>
<div className="vcd-screen-picker-radios">
{StreamFps.map(fps => (
<label className="vcd-screen-picker-radio" data-checked={settings.fps === fps}>
<Text variant="text-sm/bold">{fps}</Text>
<input
type="radio"
name="fps"
value={fps}
checked={settings.fps === fps}
onChange={() => setSettings(s => ({ ...s, fps }))}
/>
</label>
))}
</div>
</section>
</div>
<div className="vcd-screen-picker-quality">
<section>
<Forms.FormTitle>Content Type</Forms.FormTitle>
<div>
<div className="vcd-screen-picker-radios">
<label
className="vcd-screen-picker-radio"
data-checked={settings.contentHint === "motion"}
>
<Text variant="text-sm/bold">Prefer Smoothness</Text>
<input
type="radio"
name="contenthint"
value="motion"
checked={settings.contentHint === "motion"}
onChange={() => setSettings(s => ({ ...s, contentHint: "motion" }))}
/>
</label>
<label
className="vcd-screen-picker-radio"
data-checked={settings.contentHint === "detail"}
>
<Text variant="text-sm/bold">Prefer Clarity</Text>
<input
type="radio"
name="contenthint"
value="detail"
checked={settings.contentHint === "detail"}
onChange={() => setSettings(s => ({ ...s, contentHint: "detail" }))}
/>
</label>
</div>
<div className="vcd-screen-picker-hint-description">
<p>
Choosing "Prefer Clarity" will result in a significantly lower framerate in
exchange for a much sharper and clearer image.
</p>
</div>
</div>
{isWindows && (
<Switch
value={settings.audio}
onChange={checked => setSettings(s => ({ ...s, audio: checked }))}
hideBorder
className="vcd-screen-picker-audio"
<label
className="vcd-screen-picker-radio"
data-checked={settings.contentHint === "motion"}
>
Stream With Audio
</Switch>
)}
</section>
</div>
</Card>
</div>
<Text variant="text-sm/bold">Prefer Smoothness</Text>
<input
type="radio"
name="contenthint"
value="motion"
checked={settings.contentHint === "motion"}
onChange={() => setSettings(s => ({ ...s, contentHint: "motion" }))}
/>
</label>
<label
className="vcd-screen-picker-radio"
data-checked={settings.contentHint === "detail"}
>
<Text variant="text-sm/bold">Prefer Clarity</Text>
<input
type="radio"
name="contenthint"
value="detail"
checked={settings.contentHint === "detail"}
onChange={() => setSettings(s => ({ ...s, contentHint: "detail" }))}
/>
</label>
</div>
<div className="vcd-screen-picker-hint-description">
<p>
Choosing "Prefer Clarity" will result in a significantly lower framerate in exchange
for a much sharper and clearer image.
</p>
</div>
</div>
{isWindows && (
<Switch
value={settings.audio}
onChange={checked => setSettings(s => ({ ...s, audio: checked }))}
hideBorder
className="vcd-screen-picker-audio"
>
Stream With Audio
</Switch>
)}
</section>
</div>
<div>
{isLinux && (
<AudioSourcePickerLinux
audioSource={settings.audioSource}
workaround={settings.workaround}
onlyDefaultSpeakers={settings.onlyDefaultSpeakers}
setAudioSource={source => setSettings(s => ({ ...s, audioSource: source }))}
setWorkaround={value => setSettings(s => ({ ...s, workaround: value }))}
setOnlyDefaultSpeakers={value => setSettings(s => ({ ...s, onlyDefaultSpeakers: value }))}
openSettings={openSettings}
includeSources={settings.includeSources}
excludeSources={settings.excludeSources}
granularSelect={Settings.audio?.granularSelect}
setIncludeSources={sources => setSettings(s => ({ ...s, includeSources: sources }))}
setExcludeSources={sources => setSettings(s => ({ ...s, excludeSources: sources }))}
/>
)}
</div>
</Card>
</div>
);
}
function isSpecialSource(value?: AudioSource | AudioSources): value is SpecialSource {
return typeof value === "string";
}
function hasMatchingProps(value: Node, other: Node) {
return Object.keys(value).every(key => value[key] === other[key]);
}
function mapToAudioItem(node: AudioSource, granularSelect?: boolean): AudioItem[] {
if (isSpecialSource(node)) {
return [{ name: node, value: node }];
}
const rtn: AudioItem[] = [];
const name = node["application.name"];
if (name) {
rtn.push({ name: name, value: { "application.name": name } });
}
if (!granularSelect) {
return rtn;
}
const binary = node["application.process.binary"];
if (!name) {
rtn.push({ name: binary, value: { "application.process.binary": binary } });
}
const pid = node["application.process.id"];
const first = rtn[0];
const firstValues = first.value as Node;
rtn.push({
name: `${first.name} (${pid})`,
value: { ...firstValues, "application.process.id": pid }
});
const mediaName = node["media.name"];
if (mediaName) {
rtn.push({
name: `${first.name} [${mediaName}]`,
value: { ...firstValues, "media.name": mediaName }
});
}
const mediaClass = node["media.class"];
if (!mediaClass) {
return rtn;
}
rtn.push({
name: `${first.name} [${mediaClass}]`,
value: { ...firstValues, "media.class": mediaClass }
});
return rtn;
}
function isItemSelected(sources?: AudioSources) {
return (value: AudioSource) => {
if (!sources) {
return false;
}
if (isSpecialSource(sources) || isSpecialSource(value)) {
return sources === value;
}
return sources.some(source => hasMatchingProps(source, value));
};
}
function updateItems(setSources: (s: AudioSources) => void, sources?: AudioSources) {
return (value: AudioSource) => {
if (isSpecialSource(value)) {
setSources(value);
return;
}
if (isSpecialSource(sources)) {
setSources([value]);
return;
}
if (isItemSelected(sources)(value)) {
setSources(sources?.filter(x => !hasMatchingProps(x, value)) ?? "None");
return;
}
setSources([...(sources || []), value]);
};
}
function AudioSourcePickerLinux({
audioSource,
workaround,
onlyDefaultSpeakers,
setAudioSource,
setWorkaround,
setOnlyDefaultSpeakers
includeSources,
excludeSources,
granularSelect,
openSettings,
setIncludeSources,
setExcludeSources
}: {
audioSource?: string;
workaround?: boolean;
onlyDefaultSpeakers?: boolean;
setAudioSource(s: string): void;
setWorkaround(b: boolean): void;
setOnlyDefaultSpeakers(b: boolean): void;
includeSources?: AudioSources;
excludeSources?: AudioSources;
granularSelect?: boolean;
openSettings: () => void;
setIncludeSources: (s: AudioSources) => void;
setExcludeSources: (s: AudioSources) => void;
}) {
const [sources, _, loading] = useAwaiter(() => VesktopNative.virtmic.list(), {
fallbackValue: { ok: true, targets: [], hasPipewirePulse: true }
});
const allSources = sources.ok ? ["None", "Entire System", ...sources.targets] : null;
const hasPipewirePulse = sources.ok ? sources.hasPipewirePulse : true;
const [ignorePulseWarning, setIgnorePulseWarning] = useState(false);
if (!sources.ok && sources.isGlibCxxOutdated) {
return (
<Forms.FormText>
Failed to retrieve Audio Sources because your C++ library is too old to run
<a href="https://github.com/Vencord/venmic" target="_blank">
venmic
</a>
. See{" "}
<a href="https://gist.github.com/Vendicated/b655044ffbb16b2716095a448c6d827a" target="_blank">
this guide
</a>{" "}
for possible solutions.
</Forms.FormText>
);
}
if (!hasPipewirePulse && !ignorePulseWarning) {
return (
<Text variant="text-sm/normal">
Could not find pipewire-pulse. See{" "}
<a href="https://gist.github.com/the-spyke/2de98b22ff4f978ebf0650c90e82027e#install" target="_blank">
this guide
</a>{" "}
on how to switch to pipewire. <br />
You can still continue, however, please{" "}
<b>beware that you can only share audio of apps that are running under pipewire</b>.{" "}
<a onClick={() => setIgnorePulseWarning(true)}>I know what I'm doing!</a>
</Text>
);
}
const specialSources: SpecialSource[] = ["None", "Entire System"] as const;
const uniqueName = (value: AudioItem, index: number, list: AudioItem[]) =>
list.findIndex(x => x.name === value.name) === index;
const allSources = sources.ok
? [...specialSources, ...sources.targets]
.map(target => mapToAudioItem(target, granularSelect))
.flat()
.filter(uniqueName)
: [];
return (
<>
<Forms.FormTitle>Audio Settings</Forms.FormTitle>
<Card className="vcd-screen-picker-card">
{loading ? (
<Forms.FormTitle>Loading Audio Sources...</Forms.FormTitle>
) : (
<Forms.FormTitle>Audio Source</Forms.FormTitle>
)}
{!sources.ok && sources.isGlibCxxOutdated && (
<Forms.FormText>
Failed to retrieve Audio Sources because your C++ library is too old to run
<a href="https://github.com/Vencord/venmic" target="_blank">
venmic
</a>
. See{" "}
<a href="https://gist.github.com/Vendicated/b655044ffbb16b2716095a448c6d827a" target="_blank">
this guide
</a>{" "}
for possible solutions.
</Forms.FormText>
)}
{hasPipewirePulse || ignorePulseWarning ? (
allSources && (
<div className={includeSources === "Entire System" ? "vcd-screen-picker-quality" : undefined}>
<section>
<Forms.FormTitle>{loading ? "Loading Sources..." : "Audio Sources"}</Forms.FormTitle>
<Select
options={allSources.map(({ name, value }) => ({
label: name,
value: value,
default: name === "None"
}))}
isSelected={isItemSelected(includeSources)}
select={updateItems(setIncludeSources, includeSources)}
serialize={String}
popoutPosition="top"
closeOnSelect={false}
/>
</section>
{includeSources === "Entire System" && (
<section>
<Forms.FormTitle>Exclude Sources</Forms.FormTitle>
<Select
options={allSources.map(s => ({ label: s, value: s, default: s === "None" }))}
isSelected={s => s === audioSource}
select={setAudioSource}
options={allSources
.filter(x => x.name !== "Entire System")
.map(({ name, value }) => ({
label: name,
value: value,
default: name === "None"
}))}
isSelected={isItemSelected(excludeSources)}
select={updateItems(setExcludeSources, excludeSources)}
serialize={String}
popoutPosition="top"
closeOnSelect={false}
/>
)
) : (
<Text variant="text-sm/normal">
Could not find pipewire-pulse. This usually means that you do not run pipewire as your main
audio-server. <br />
You can still continue, however, please beware that you can only share audio of apps that are
running under pipewire.
<br />
<a onClick={() => setIgnorePulseWarning(true)}>I know what I'm doing</a>
</Text>
</section>
)}
<Forms.FormDivider className={Margins.top16 + " " + Margins.bottom16} />
<Switch
onChange={setWorkaround}
value={workaround ?? false}
note={
<>
Work around an issue that causes the microphone to be shared instead of the correct audio.
Only enable if you're experiencing this issue.
</>
}
>
Microphone Workaround
</Switch>
<Switch
hideBorder
onChange={setOnlyDefaultSpeakers}
disabled={audioSource !== "Entire System"}
value={onlyDefaultSpeakers ?? true}
note={
<>
When sharing entire desktop audio, only share apps that play to the default speakers and
ignore apps that play to other speakers or devices.
</>
}
>
Only Default Speakers
</Switch>
</Card>
</div>
<Button
color={Button.Colors.TRANSPARENT}
onClick={openSettings}
className="vcd-screen-picker-settings-button"
>
Open Audio Settings
</Button>
</>
);
}
@ -421,10 +661,11 @@ function ModalComponent({
}) {
const [selected, setSelected] = useState<string | undefined>(skipPicker ? screens[0].id : void 0);
const [settings, setSettings] = useState<StreamSettings>({
resolution: "1080",
fps: "60",
resolution: "720",
fps: "30",
contentHint: "motion",
audio: true
audio: true,
includeSources: "None"
});
return (
@ -480,7 +721,7 @@ function ModalComponent({
const constraints = {
...track.getConstraints(),
frameRate,
frameRate: { min: frameRate, ideal: frameRate },
width: { min: 640, ideal: width, max: width },
height: { min: 480, ideal: height, max: height },
advanced: [{ width: width, height: height }],

View file

@ -11,17 +11,6 @@
gap: 1em;
}
.vcd-screen-picker-settings-grid {
gap: 1em;
display: grid;
grid-template-columns: 1fr 1fr;
}
.vcd-screen-picker-settings-grid>div {
display: flex;
flex-direction: column;
}
.vcd-screen-picker-card {
flex-grow: 1;
}
@ -38,7 +27,7 @@
}
.vcd-screen-picker-selected img {
border: 2px solid var(--brand-experiment);
border: 2px solid var(--brand-500);
border-radius: 3px;
}
@ -49,7 +38,7 @@
}
.vcd-screen-picker-grid label:hover {
outline: 2px solid var(--brand-experiment);
outline: 2px solid var(--brand-500);
}
@ -68,7 +57,7 @@
}
.vcd-screen-picker-preview-img-linux {
width: 100%;
width: 60%;
margin-bottom: 0.5em;
}
@ -101,8 +90,8 @@
}
.vcd-screen-picker-radio[data-checked="true"] {
background-color: var(--brand-experiment);
border-color: var(--brand-experiment);
background-color: var(--brand-500);
border-color: var(--brand-500);
}
.vcd-screen-picker-radio[data-checked="true"] h2 {
@ -120,6 +109,11 @@
flex: 1 1 auto;
}
.vcd-screen-picker-settings-button {
margin-left: auto;
margin-top: 0.3rem;
}
.vcd-screen-picker-radios {
display: flex;
width: 100%;
@ -148,4 +142,4 @@
font-size: 14px;
line-height: 20px;
font-weight: 400;
}
}

View file

@ -102,15 +102,7 @@ const SettingsOptions: Record<string, Array<BooleanSetting | SettingsComponent>>
defaultValue: false
}
],
"Notifications & Updates": [
NotificationBadgeToggle,
{
key: "checkUpdates",
title: "Check for updates",
description: "Automatically check for Vesktop updates",
defaultValue: true
}
],
Notifications: [NotificationBadgeToggle],
Miscelleanous: [
{
key: "arRPC",

View file

@ -4,24 +4,28 @@
* Copyright (c) 2023 Vendicated and Vencord contributors
*/
import { useForceUpdater } from "@vencord/types/utils";
import { Button, Forms, Toasts } from "@vencord/types/webpack/common";
import { SettingsComponent } from "./Settings";
export const VencordLocationPicker: SettingsComponent = ({ settings }) => {
const forceUpdate = useForceUpdater();
const vencordDir = VesktopNative.fileManager.getVencordDir();
return (
<>
<Forms.FormText>
Vencord files are loaded from{" "}
{settings.vencordDir ? (
{vencordDir ? (
<a
href="about:blank"
onClick={e => {
e.preventDefault();
VesktopNative.fileManager.showItemInFolder(settings.vencordDir!);
VesktopNative.fileManager.showItemInFolder(vencordDir!);
}}
>
{settings.vencordDir}
{vencordDir}
</a>
) : (
"the default location"
@ -34,7 +38,14 @@ export const VencordLocationPicker: SettingsComponent = ({ settings }) => {
const choice = await VesktopNative.fileManager.selectVencordDir();
switch (choice) {
case "cancelled":
return;
break;
case "ok":
Toasts.show({
message: "Vencord install changed. Fully restart Vesktop to apply.",
id: Toasts.genId(),
type: Toasts.Type.SUCCESS
});
break;
case "invalid":
Toasts.show({
message:
@ -42,9 +53,9 @@ export const VencordLocationPicker: SettingsComponent = ({ settings }) => {
id: Toasts.genId(),
type: Toasts.Type.FAILURE
});
return;
break;
}
settings.vencordDir = choice;
forceUpdate();
}}
>
Change
@ -52,7 +63,10 @@ export const VencordLocationPicker: SettingsComponent = ({ settings }) => {
<Button
size={Button.Sizes.SMALL}
color={Button.Colors.RED}
onClick={() => (settings.vencordDir = void 0)}
onClick={async () => {
await VesktopNative.fileManager.selectVencordDir(null);
forceUpdate();
}}
>
Reset
</Button>

View file

@ -12,8 +12,8 @@ import "./themedSplash";
console.log("read if cute :3");
export * as Components from "./components";
import { findByPropsLazy } from "@vencord/types/webpack";
import { FluxDispatcher } from "@vencord/types/webpack/common";
import { findByPropsLazy, onceReady } from "@vencord/types/webpack";
import { Alerts, FluxDispatcher } from "@vencord/types/webpack/common";
import SettingsUi from "./components/settings/Settings";
import { Settings } from "./settings";
@ -52,8 +52,26 @@ const arRPC = Vencord.Plugins.plugins["WebRichPresence (arRPC)"] as any as {
handleEvent(e: MessageEvent): void;
};
VesktopNative.arrpc.onActivity(data => {
VesktopNative.arrpc.onActivity(async data => {
if (!Settings.store.arRPC) return;
await onceReady;
arRPC.handleEvent(new MessageEvent("message", { data }));
});
// TODO: remove soon
const vencordDir = "vencordDir" as keyof typeof Settings.store;
if (Settings.store[vencordDir]) {
onceReady.then(() =>
setTimeout(
() =>
Alerts.show({
title: "Custom Vencord Location",
body: "Due to security hardening changes in Vesktop, your custom Vencord location had to be reset. Please configure it again in the settings.",
onConfirm: () => delete Settings.store[vencordDir]
}),
5000
)
);
}

View file

@ -13,7 +13,7 @@ addPatch({
replacement: {
// FIXME: fix eslint rule
// eslint-disable-next-line no-useless-escape
match: /\.isPlatformEmbedded(?=\?\i\.DesktopNotificationTypes\.ALL)/g,
match: /\.isPlatformEmbedded(?=\?\i\.\i\.ALL)/g,
replace: "$&||true"
}
}

View file

@ -12,7 +12,7 @@ addPatch({
find: "lastOutputSystemDevice.justChanged",
replacement: {
// eslint-disable-next-line no-useless-escape
match: /(\i)\.default\.getState\(\).neverShowModal/,
match: /(\i)\.\i\.getState\(\).neverShowModal/,
replace: "$& || $self.shouldIgnore($1)"
}
}

View file

@ -36,7 +36,7 @@ if (isLinux) {
const constraints = {
...track.getConstraints(),
frameRate,
frameRate: { min: frameRate, ideal: frameRate },
width: { min: 640, ideal: width, max: width },
height: { min: 480, ideal: height, max: height },
advanced: [{ width: width, height: height }],

View file

@ -6,7 +6,8 @@
import { addContextMenuPatch } from "@vencord/types/api/ContextMenu";
import { findStoreLazy } from "@vencord/types/webpack";
import { FluxDispatcher, Menu, useStateFromStores } from "@vencord/types/webpack/common";
import { FluxDispatcher, Menu, useMemo, useStateFromStores } from "@vencord/types/webpack/common";
import { useSettings } from "renderer/settings";
import { addPatch } from "./shared";
@ -50,7 +51,16 @@ addContextMenuPatch("textarea-context", children => {
const spellCheckEnabled = useStateFromStores([SpellCheckStore], () => SpellCheckStore.isEnabled());
const hasCorrections = Boolean(word && corrections?.length);
children.push(
const availableLanguages = useMemo(VesktopNative.spellcheck.getAvailableLanguages, []);
const settings = useSettings();
const spellCheckLanguages = (settings.spellCheckLanguages ??= [...new Set(navigator.languages)]);
const pasteSectionIndex = children.findIndex(c => c?.props?.children?.some(c => c?.props?.id === "paste"));
children.splice(
pasteSectionIndex === -1 ? children.length : pasteSectionIndex,
0,
<Menu.MenuGroup>
{hasCorrections && (
<>
@ -69,14 +79,39 @@ addContextMenuPatch("textarea-context", children => {
/>
</>
)}
<Menu.MenuCheckboxItem
id="vcd-spellcheck-enabled"
label="Enable Spellcheck"
checked={spellCheckEnabled}
action={() => {
FluxDispatcher.dispatch({ type: "SPELLCHECK_TOGGLE" });
}}
/>
<Menu.MenuItem id="vcd-spellcheck-settings" label="Spellcheck Settings">
<Menu.MenuCheckboxItem
id="vcd-spellcheck-enabled"
label="Enable Spellcheck"
checked={spellCheckEnabled}
action={() => {
FluxDispatcher.dispatch({ type: "SPELLCHECK_TOGGLE" });
}}
/>
<Menu.MenuItem id="vcd-spellcheck-languages" label="Languages" disabled={!spellCheckEnabled}>
{availableLanguages.map(lang => {
const isEnabled = spellCheckLanguages.includes(lang);
return (
<Menu.MenuCheckboxItem
id={"vcd-spellcheck-lang-" + lang}
label={lang}
checked={isEnabled}
disabled={!isEnabled && spellCheckLanguages.length >= 5}
action={() => {
const newSpellCheckLanguages = spellCheckLanguages.filter(l => l !== lang);
if (newSpellCheckLanguages.length === spellCheckLanguages.length) {
newSpellCheckLanguages.push(lang);
}
settings.spellCheckLanguages = newSpellCheckLanguages;
}}
/>
);
})}
</Menu.MenuItem>
</Menu.MenuItem>
</Menu.MenuGroup>
);
});

View file

@ -23,13 +23,14 @@ export const enum IpcEvents {
GET_SETTINGS = "VCD_GET_SETTINGS",
SET_SETTINGS = "VCD_SET_SETTINGS",
GET_VENCORD_DIR = "VCD_GET_VENCORD_DIR",
SELECT_VENCORD_DIR = "VCD_SELECT_VENCORD_DIR",
UPDATER_GET_DATA = "VCD_UPDATER_GET_DATA",
UPDATER_DOWNLOAD = "VCD_UPDATER_DOWNLOAD",
UPDATE_IGNORE = "VCD_UPDATE_IGNORE",
SPELLCHECK_SET_LANGUAGES = "VCD_SPELLCHECK_SET_LANGUAGES",
SPELLCHECK_GET_AVAILABLE_LANGUAGES = "VCD_SPELLCHECK_GET_AVAILABLE_LANGUAGES",
SPELLCHECK_RESULT = "VCD_SPELLCHECK_RESULT",
SPELLCHECK_REPLACE_MISSPELLING = "VCD_SPELLCHECK_REPLACE_MISSPELLING",
SPELLCHECK_ADD_TO_DICTIONARY = "VCD_SPELLCHECK_ADD_TO_DICTIONARY",

View file

@ -8,7 +8,6 @@ import type { Rectangle } from "electron";
export interface Settings {
discordBranch?: "stable" | "canary" | "ptb";
vencordDir?: string;
transparencyOption?: "none" | "mica" | "tabbed" | "acrylic";
tray?: boolean;
minimizeToTray?: boolean;
@ -21,15 +20,25 @@ export interface Settings {
appBadge?: boolean;
disableMinSize?: boolean;
clickTrayToShowHide?: boolean;
/** @deprecated use customTitleBar */
discordWindowsTitleBar?: boolean;
customTitleBar?: boolean;
checkUpdates?: boolean;
splashTheming?: boolean;
splashColor?: string;
splashBackground?: string;
spellCheckLanguages?: string[];
audio?: {
workaround?: boolean;
granularSelect?: boolean;
ignoreVirtual?: boolean;
ignoreDevices?: boolean;
ignoreInputMedia?: boolean;
onlySpeakers?: boolean;
onlyDefaultSpeakers?: boolean;
};
}
export interface State {
@ -38,8 +47,9 @@ export interface State {
windowBounds?: Rectangle;
displayid: int;
skippedUpdate?: string;
firstLaunch?: boolean;
steamOSLayoutVersion?: number;
vencordDir?: string;
}

View file

@ -59,6 +59,19 @@ export class SettingsStore<T extends object> {
self.pathListeners.get(setPath)?.forEach(cb => cb(value));
return true;
},
deleteProperty(target, key: string) {
if (!(key in target)) return true;
const res = Reflect.deleteProperty(target, key);
if (!res) return false;
const setPath = `${path}${path && "."}${key}`;
self.globalListeners.forEach(cb => cb(root, setPath));
self.pathListeners.get(setPath)?.forEach(cb => cb(undefined));
return res;
}
});
}

View file

@ -1,119 +0,0 @@
/*
* SPDX-License-Identifier: GPL-3.0
* Vesktop, a desktop app aiming to give you a snappier Discord Experience
* Copyright (c) 2023 Vendicated and Vencord contributors
*/
import { app, BrowserWindow, shell } from "electron";
import { Settings, State } from "main/settings";
import { handle } from "main/utils/ipcWrappers";
import { makeLinksOpenExternally } from "main/utils/makeLinksOpenExternally";
import { githubGet, ReleaseData } from "main/utils/vencordLoader";
import { join } from "path";
import { IpcEvents } from "shared/IpcEvents";
import { ICON_PATH, VIEW_DIR } from "shared/paths";
export interface UpdateData {
currentVersion: string;
latestVersion: string;
release: ReleaseData;
}
let updateData: UpdateData;
handle(IpcEvents.UPDATER_GET_DATA, () => updateData);
handle(IpcEvents.UPDATER_DOWNLOAD, () => {
const portable = !!process.env.PORTABLE_EXECUTABLE_FILE;
const { assets } = updateData.release;
const url = (() => {
switch (process.platform) {
case "win32":
return assets.find(a => {
if (!a.name.endsWith(".exe")) return false;
const isSetup = a.name.includes("Setup");
return portable ? !isSetup : isSetup;
})!.browser_download_url;
case "darwin":
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":
return updateData.release.html_url;
default:
throw new Error(`Unsupported platform: ${process.platform}`);
}
})();
shell.openExternal(url);
});
handle(IpcEvents.UPDATE_IGNORE, () => {
State.store.skippedUpdate = updateData.latestVersion;
});
function isOutdated(oldVersion: string, newVersion: string) {
const oldParts = oldVersion.split(".");
const newParts = newVersion.split(".");
if (oldParts.length !== newParts.length)
throw new Error(`Incompatible version strings (old: ${oldVersion}, new: ${newVersion})`);
for (let i = 0; i < oldParts.length; i++) {
const oldPart = Number(oldParts[i]);
const newPart = Number(newParts[i]);
if (isNaN(oldPart) || isNaN(newPart))
throw new Error(`Invalid version string (old: ${oldVersion}, new: ${newVersion})`);
if (oldPart < newPart) return true;
if (oldPart > newPart) return false;
}
return false;
}
export async function checkUpdates() {
if (Settings.store.checkUpdates === false) return;
try {
const raw = await githubGet("/repos/Vencord/Vesktop/releases/latest");
const data: ReleaseData = await raw.json();
const oldVersion = app.getVersion();
const newVersion = data.tag_name.replace(/^v/, "");
updateData = {
currentVersion: oldVersion,
latestVersion: newVersion,
release: data
};
if (State.store.skippedUpdate !== newVersion && isOutdated(oldVersion, newVersion)) {
openNewUpdateWindow();
}
} catch (e) {
console.error("AppUpdater: Failed to check for updates\n", e);
}
}
function openNewUpdateWindow() {
const win = new BrowserWindow({
width: 500,
autoHideMenuBar: true,
alwaysOnTop: true,
webPreferences: {
preload: join(__dirname, "updaterPreload.js"),
nodeIntegration: false,
contextIsolation: true,
sandbox: true
},
icon: ICON_PATH
});
makeLinksOpenExternally(win);
win.loadFile(join(VIEW_DIR, "updater.html"));
}

View file

@ -1,21 +0,0 @@
/*
* SPDX-License-Identifier: GPL-3.0
* Vesktop, a desktop app aiming to give you a snappier Discord Experience
* Copyright (c) 2023 Vendicated and Vencord contributors
*/
import { contextBridge } from "electron";
import { invoke } from "preload/typedIpc";
import { IpcEvents } from "shared/IpcEvents";
import type { UpdateData } from "./main";
contextBridge.exposeInMainWorld("Updater", {
getData: () => invoke<UpdateData>(IpcEvents.UPDATER_GET_DATA),
download: () => {
invoke<void>(IpcEvents.UPDATER_DOWNLOAD);
invoke<void>(IpcEvents.CLOSE);
},
ignore: () => invoke<void>(IpcEvents.UPDATE_IGNORE),
close: () => invoke<void>(IpcEvents.CLOSE)
});