Merge branch 'main' into keybinds

This commit is contained in:
Oleh Polisan 2024-09-05 22:30:09 +03:00 committed by GitHub
commit edd9f715fd
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
36 changed files with 2288 additions and 1230 deletions

View file

@ -13,15 +13,30 @@ 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" 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 - Purely graphical glitches like flickering, scaling issues[^1]
- App crashing / not showing window with mentions of the gpu process in the stacktrace[^1]
- Screenshare not starting, black screening or crashing[^2]
- Vencord related issues: This is the Vesktop repo, not Vencord - 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 - Captchas[^3]
- Issues with opening URLs[^4]
- Issues with Notifications[^4]
- Issues with Input Methods[^4]
- Issues with File Drag and Drop[^5]
- Network Errors[^6]
Linux users: Please only report issues with supported packages (flatpak and any builds from the README / releases). 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, We do not support other packages, like the AUR or Nix packages, so please first make sure your issue is reproducible with official releases,
like [our Flatpak](https://flathub.org/apps/dev.vencord.Vesktop) or [AppImage](https://vencord.dev/download/vesktop/amd64/appimage) like [our Flatpak](https://flathub.org/apps/dev.vencord.Vesktop) or [AppImage](https://vencord.dev/download/vesktop/amd64/appimage)
[^1]: GPU issue. Disable hardware acceleration in Vesktop Settings or run with `--disable-gpu`
[^2]: System issue. You will have to fix it
[^3]: If you are receiving a lot of captchas, it means Discord thinks you might be a bot. Make sure you're not using a VPN/Proxy
[^4]: These things are handled by Chromium / Electron, not us. If they don't work, it's either an issue with your system or a bug with Chromium.
[^5]: You are likely using the Vesktop flatpak and trying to drop a file the flatpak can't access. You can fix this by installing Flatseal and using it to grant Vesktop full access to your files
[^6]: Issue on your end, you have to fix it. Try changing your DNS to [1.1.1.1 (Cloudflare DNS)](https://developers.cloudflare.com/1.1.1.1/setup/)
- type: input - type: input
id: discord id: discord
attributes: attributes:
@ -49,6 +64,15 @@ body:
validations: validations:
required: false required: false
- type: input
id: install-type
attributes:
label: Package Type
description: What kind of Vesktop package are you using? (Setup exe, Portable, Flatpak, AppImage, Deb, etc)
placeholder: Flatpak
validations:
required: true
- type: textarea - type: textarea
id: bug-description id: bug-description
attributes: attributes:
@ -84,7 +108,7 @@ body:
id: debug-logs id: debug-logs
attributes: attributes:
label: Debug Logs label: Debug Logs
description: Run vesktop from the command line. Include the relevant command line output here description: Run vesktop from the command line. Include the relevant command line output here. If there are any lines that seem relevant, try googling them or searching existing issues
value: | value: |
``` ```
Replace this text with your crash-log. Do not remove the backticks Replace this text with your crash-log. Do not remove the backticks

View file

@ -1,7 +1,7 @@
name: 🛠️ Feature Request name: 🛠️ Feature Request
description: Create a feature request for Vesktop description: Request a feature for Vesktop
labels: [bug] labels: [enhancement]
title: "[Bug] <title>" title: "[Feature Request] <title>"
body: body:
- type: markdown - type: markdown
@ -14,6 +14,8 @@ body:
This form is only meant for **Vesktop feature requests**. This form is only meant for **Vesktop feature requests**.
For plugin requests or Vencord feature requests, go [here](https://github.com/Vencord/plugin-requests/issues/new?template=request.yml) instead! For plugin requests or Vencord feature requests, go [here](https://github.com/Vencord/plugin-requests/issues/new?template=request.yml) instead!
**DO NOT** make any icon related requests or you will be blocked.
- type: input - type: input
id: discord id: discord
attributes: attributes:

View file

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

View file

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

View file

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

View file

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

View file

@ -21,15 +21,14 @@ Vesktop is a custom Discord desktop app
If you don't know the difference, pick the Installer. If you don't know the difference, pick the Installer.
- [Installer](https://vencord.dev/download/vesktop/amd64/windows) - [Installer](https://vencord.dev/download/vesktop/universal/windows)
- [Portable](https://vencord.dev/download/vesktop/amd64/windows-portable) - Portable:
- [x64 / amd64](https://vencord.dev/download/vesktop/amd64/windows-portable)
- [arm64](https://vencord.dev/download/vesktop/arm64/windows-portable)
### Mac ### Mac
If you don't know the difference, pick the Intel build. [Vesktop.dmg](https://vencord.dev/download/vesktop/universal/dmg)
- [Intel build (amd64)](https://vencord.dev/download/vesktop/amd64/dmg)
- [Apple Silicon (arm64)](https://vencord.dev/download/vesktop/arm64/dmg)
### Linux ### Linux

View file

@ -28,6 +28,34 @@
</screenshot> </screenshot>
</screenshots> </screenshots>
<releases> <releases>
<release version="1.5.3" date="2024-07-04" type="stable">
<url>https://github.com/Vencord/Vesktop/releases/tag/v1.5.3</url>
<description>
<p>Features</p>
<ul>
<li>added arm64 Windows support</li>
<li>windows &amp; macOS builds are now universal</li>
<li>added option to configure spellcheck languages</li>
<li>will auto-update from now on</li>
<li>updated electron to 31 &amp; Chromium to 126</li>
<li>macOS: Added customized dmg background by @khcrysalis</li>
<li>Windows Portable: store settings in portable folder by @MrGarlic1</li>
<li>linux audioshare: added granular selection, more options, better ui by @Curve</li>
<li>changed default screen-sharing quality to 720p 30 FPS by @Tiagoquix</li>
</ul>
<p>Fixes</p>
<ul>
<li>macOS: Added workaround for making things in draggable area clickable by @HAHALOSAH</li>
<li>fixed Screenshare UI for non-linux systems by @PolisanTheEasyNick</li>
<li>fixed opening on screen that was disconnected by @MrGarlic1</li>
<li>mac: hide native window controls with custom titlebar enabled by @MrGarlic1</li>
<li>fixed some broken patches by @D3SOX</li>
<li>fixed framerate in constraints by @kittykel</li>
<li>fixed some first launch switches not applying</li>
<li>fixed potential sandbox escape via custom vencord location</li>
</ul>
</description>
</release>
<release version="1.5.2" date="2024-05-01" type="stable"> <release version="1.5.2" date="2024-05-01" type="stable">
<url>https://github.com/Vencord/Vesktop/releases/tag/v1.5.2</url> <url>https://github.com/Vencord/Vesktop/releases/tag/v1.5.2</url>
<description> <description>
@ -182,7 +210,7 @@
<url type="vcs-browser">https://github.com/Vencord/Vesktop</url> <url type="vcs-browser">https://github.com/Vencord/Vesktop</url>
<categories> <categories>
<category>InstantMessaging</category> <category>InstantMessaging</category>
<category>AudioVideo</category> <category>Network</category>
</categories> </categories>
<requires> <requires>
<control>pointing</control> <control>pointing</control>

View file

@ -1,8 +1,8 @@
{ {
"name": "vesktop", "name": "vesktop",
"version": "1.5.2", "version": "1.5.3",
"private": true, "private": true,
"description": "", "description": "Vesktop is a custom Discord desktop app",
"keywords": [], "keywords": [],
"homepage": "https://vencord.dev/", "homepage": "https://vencord.dev/",
"license": "GPL-3.0", "license": "GPL-3.0",
@ -24,36 +24,37 @@
"updateMeta": "tsx scripts/utils/updateMeta.mts" "updateMeta": "tsx scripts/utils/updateMeta.mts"
}, },
"dependencies": { "dependencies": {
"arrpc": "github:OpenAsar/arrpc#6960a8fd4d65d566da93dbdb8a7ca474aa0a3c9c" "arrpc": "github:OpenAsar/arrpc#c62ec6a04c8d870530aa6944257fe745f6c59a24",
"electron-updater": "^6.2.1"
}, },
"optionalDependencies": { "optionalDependencies": {
"@vencord/venmic": "^3.5.0" "@vencord/venmic": "^6.1.0"
}, },
"devDependencies": { "devDependencies": {
"@fal-works/esbuild-plugin-global-externals": "^2.1.2", "@fal-works/esbuild-plugin-global-externals": "^2.1.2",
"@types/node": "^20.11.26", "@types/node": "^20.14.11",
"@types/react": "^18.2.0", "@types/react": "^18.3.3",
"@typescript-eslint/eslint-plugin": "^7.2.0", "@typescript-eslint/eslint-plugin": "^7.17.0",
"@typescript-eslint/parser": "^7.2.0", "@typescript-eslint/parser": "^7.17.0",
"@vencord/types": "^1.8.4", "@vencord/types": "^1.8.4",
"dotenv": "^16.4.5", "dotenv": "^16.4.5",
"electron": "^29.1.1", "electron": "^31.2.1",
"electron-builder": "^24.13.3", "electron-builder": "^25.0.1",
"esbuild": "^0.20.1", "esbuild": "^0.20.2",
"eslint": "^8.57.0", "eslint": "^8.57.0",
"eslint-config-prettier": "^9.1.0", "eslint-config-prettier": "^9.1.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.1",
"eslint-plugin-path-alias": "^1.0.0", "eslint-plugin-path-alias": "^1.1.0",
"eslint-plugin-prettier": "^5.1.3", "eslint-plugin-prettier": "^5.2.1",
"eslint-plugin-simple-import-sort": "^12.0.0", "eslint-plugin-simple-import-sort": "^12.1.1",
"eslint-plugin-unused-imports": "^3.1.0", "eslint-plugin-unused-imports": "^3.2.0",
"prettier": "^3.2.5", "prettier": "^3.3.3",
"source-map-support": "^0.5.21", "source-map-support": "^0.5.21",
"tsx": "^4.7.1", "tsx": "^4.16.2",
"type-fest": "^4.12.0", "type-fest": "^4.23.0",
"typescript": "^5.4.2", "typescript": "^5.5.4",
"xml-formatter": "^3.6.2" "xml-formatter": "^3.6.3"
}, },
"packageManager": "pnpm@9.1.0", "packageManager": "pnpm@9.1.0",
"engines": { "engines": {
@ -65,6 +66,7 @@
"productName": "Vesktop", "productName": "Vesktop",
"files": [ "files": [
"!*", "!*",
"!node_modules",
"dist/js", "dist/js",
"static", "static",
"package.json", "package.json",
@ -118,18 +120,19 @@
{ {
"target": "default", "target": "default",
"arch": [ "arch": [
"x64", "universal"
"arm64"
] ]
} }
], ],
"category": "Network", "category": "public.app-category.social-networking",
"darkModeSupport": true,
"extendInfo": { "extendInfo": {
"NSMicrophoneUsageDescription": "This app needs access to the microphone", "NSMicrophoneUsageDescription": "This app needs access to the microphone",
"NSCameraUsageDescription": "This app needs access to the camera", "NSCameraUsageDescription": "This app needs access to the camera",
"com.apple.security.device.audio-input": true, "com.apple.security.device.audio-input": true,
"com.apple.security.device.camera": true "com.apple.security.device.camera": true
} },
"notarize": true
}, },
"dmg": { "dmg": {
"background": "build/background.tiff", "background": "build/background.tiff",
@ -158,12 +161,35 @@
}, },
"win": { "win": {
"target": [ "target": [
"nsis", {
"zip" "target": "nsis",
"arch": [
"x64",
"arm64"
]
},
{
"target": "zip",
"arch": [
"x64",
"arm64"
]
}
] ]
}, },
"publish": { "publish": {
"provider": "github" "provider": "github"
},
"rpm": {
"fpm": [
"--rpm-rpmbuild-define=_build_id_links none"
]
}
},
"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];

File diff suppressed because it is too large Load diff

View file

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

View file

@ -5,11 +5,22 @@
*/ */
import { app } from "electron"; import { app } from "electron";
import { existsSync, readdirSync, renameSync, rmdirSync } from "fs"; import { existsSync, mkdirSync, readdirSync, renameSync, rmdirSync } from "fs";
import { join } from "path"; 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"); 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 // TODO: remove eventually
if (existsSync(LEGACY_DATA_DIR)) { if (existsSync(LEGACY_DATA_DIR)) {
try { try {
@ -26,7 +37,8 @@ if (existsSync(LEGACY_DATA_DIR)) {
console.error("Migration failed", e); 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_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");
@ -36,7 +48,8 @@ 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")).State.store.vencordDir ||
join(SESSION_DATA_DIR, "vencordFiles");
export const USER_AGENT = `Vesktop/${app.getVersion()} (https://github.com/Vencord/Vesktop)`; export const USER_AGENT = `Vesktop/${app.getVersion()} (https://github.com/Vencord/Vesktop)`;
@ -49,10 +62,10 @@ export const DEFAULT_HEIGHT = 720;
export const DISCORD_HOSTNAMES = ["discord.com", "canary.discord.com", "ptb.discord.com"]; export const DISCORD_HOSTNAMES = ["discord.com", "canary.discord.com", "ptb.discord.com"];
const BrowserUserAgents = { const BrowserUserAgents = {
darwin: "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36", darwin: "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36",
linux: "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36", linux: "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36",
windows: windows:
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36"
}; };
export const BrowserUserAgent = BrowserUserAgents[process.platform] || BrowserUserAgents.windows; export const BrowserUserAgent = BrowserUserAgents[process.platform] || BrowserUserAgents.windows;

View file

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

View file

@ -7,7 +7,7 @@
import "./ipc"; import "./ipc";
import { app, BrowserWindow, nativeTheme } from "electron"; import { app, BrowserWindow, nativeTheme } from "electron";
import { checkUpdates } from "updater/main"; import { autoUpdater } from "electron-updater";
import { DATA_DIR } from "./constants"; import { DATA_DIR } from "./constants";
import { createFirstLaunchTour } from "./firstLaunch"; import { createFirstLaunchTour } from "./firstLaunch";
@ -19,6 +19,8 @@ import { isDeckGameMode } from "./utils/steamOS";
if (IS_DEV) { if (IS_DEV) {
require("source-map-support").install(); require("source-map-support").install();
} else {
autoUpdater.checkForUpdatesAndNotify();
} }
// Make the Vencord files use our DATA_DIR // Make the Vencord files use our DATA_DIR
@ -42,7 +44,13 @@ function init() {
// disable renderer backgrounding to prevent the app from unloading when in the background // disable renderer backgrounding to prevent the app from unloading when in the background
// https://github.com/electron/electron/issues/2822 // 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-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 // 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");
@ -50,12 +58,7 @@ function init() {
// 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 // WidgetLayering (Vencord Added): Fix DevTools context menus https://github.com/electron/electron/issues/38790
disabledFeatures.push( disabledFeatures.push("WinRetrieveSuggestionsOnlyOnDemand", "HardwareMediaKeyHandling", "MediaSessionService");
"WinRetrieveSuggestionsOnlyOnDemand",
"HardwareMediaKeyHandling",
"MediaSessionService",
"WidgetLayering"
);
app.commandLine.appendSwitch("enable-features", [...new Set(enabledFeatures)].filter(Boolean).join(",")); app.commandLine.appendSwitch("enable-features", [...new Set(enabledFeatures)].filter(Boolean).join(","));
app.commandLine.appendSwitch("disable-features", [...new Set(disabledFeatures)].filter(Boolean).join(",")); app.commandLine.appendSwitch("disable-features", [...new Set(disabledFeatures)].filter(Boolean).join(","));
@ -73,7 +76,6 @@ function init() {
}); });
app.whenReady().then(async () => { app.whenReady().then(async () => {
checkUpdates();
if (process.platform === "win32") app.setAppUserModelId("dev.vencord.vesktop"); if (process.platform === "win32") app.setAppUserModelId("dev.vencord.vesktop");
registerScreenShareHandler(); registerScreenShareHandler();

View file

@ -19,7 +19,7 @@ import { setBadgeCount } from "./appBadge";
import { autoStart } from "./autoStart"; import { autoStart } from "./autoStart";
import { VENCORD_FILES_DIR, VENCORD_QUICKCSS_FILE, VENCORD_THEMES_DIR } 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, State } from "./settings";
import { handle, handleSync } from "./utils/ipcWrappers"; import { handle, handleSync } from "./utils/ipcWrappers";
import { PopoutWindows } from "./utils/popout"; import { PopoutWindows } from "./utils/popout";
import { isDeckGameMode, showGamePage } from "./utils/steamOS"; import { isDeckGameMode, showGamePage } from "./utils/steamOS";
@ -105,7 +105,14 @@ handle(IpcEvents.SPELLCHECK_ADD_TO_DICTIONARY, (e, word: string) => {
e.sender.session.addWordToSpellCheckerDictionary(word); 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!, { const res = await dialog.showOpenDialog(mainWin!, {
properties: ["openDirectory"] properties: ["openDirectory"]
}); });
@ -114,7 +121,9 @@ handle(IpcEvents.SELECT_VENCORD_DIR, async () => {
const dir = res.filePaths[0]; const dir = res.filePaths[0];
if (!isValidVencordInstall(dir)) return "invalid"; if (!isValidVencordInstall(dir)) return "invalid";
return dir; State.store.vencordDir = dir;
return "ok";
}); });
handle(IpcEvents.SET_BADGE_COUNT, (_, count: number) => setBadgeCount(count)); handle(IpcEvents.SET_BADGE_COUNT, (_, count: number) => setBadgeCount(count));

View file

@ -93,7 +93,7 @@ function initTray(win: BrowserWindow) {
click: createAboutWindow click: createAboutWindow
}, },
{ {
label: "Update Vencord", label: "Repair Vencord",
async click() { async click() {
await downloadVencordFiles(); await downloadVencordFiles();
app.relaunch(); app.relaunch();
@ -110,14 +110,14 @@ function initTray(win: BrowserWindow) {
type: "separator" type: "separator"
}, },
{ {
label: "Relaunch", label: "Restart",
click() { click() {
app.relaunch(); app.relaunch();
app.quit(); app.quit();
} }
}, },
{ {
label: "Quit Vesktop", label: "Quit",
click() { click() {
isQuitting = true; isQuitting = true;
app.quit(); app.quit();
@ -405,7 +405,9 @@ function createMainWindow() {
contextIsolation: true, contextIsolation: true,
devTools: true, devTools: true,
preload: join(__dirname, "preload.js"), 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, icon: ICON_PATH,
frame: !noFrame, frame: !noFrame,
@ -430,6 +432,7 @@ function createMainWindow() {
autoHideMenuBar: enableMenu autoHideMenuBar: enableMenu
})); }));
win.setMenuBarVisibility(false); win.setMenuBarVisibility(false);
if (process.platform === "darwin" && customTitleBar) win.setWindowButtonVisibility(false);
win.on("close", e => { win.on("close", e => {
const useTray = !isDeckGameMode && Settings.store.minimizeToTray !== false && Settings.store.tray !== false; 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) => { session.defaultSession.setPermissionRequestHandler(async (_webContents, permission, callback, details) => {
let granted = true; let granted = true;
if (details.mediaTypes?.includes("audio")) { if ("mediaTypes" in details) {
granted = await systemPreferences.askForMediaAccess("microphone"); if (details.mediaTypes?.includes("audio")) {
} granted &&= await systemPreferences.askForMediaAccess("microphone");
if (details.mediaTypes?.includes("video")) { }
granted &&= await systemPreferences.askForMediaAccess("camera"); if (details.mediaTypes?.includes("video")) {
granted &&= await systemPreferences.askForMediaAccess("camera");
}
} }
callback(granted); callback(granted);

View file

@ -41,14 +41,7 @@ export const VencordSettings = loadSettings<any>(VENCORD_SETTINGS_FILE, "Vencord
if (Object.hasOwn(Settings.plain, "firstLaunch") && !existsSync(STATE_FILE)) { if (Object.hasOwn(Settings.plain, "firstLaunch") && !existsSync(STATE_FILE)) {
console.warn("legacy state in settings.json detected. migrating to state.json"); console.warn("legacy state in settings.json detected. migrating to state.json");
const state = {} as TState; const state = {} as TState;
for (const prop of [ for (const prop of ["firstLaunch", "maximized", "minimized", "steamOSLayoutVersion", "windowBounds"] as const) {
"firstLaunch",
"maximized",
"minimized",
"skippedUpdate",
"steamOSLayoutVersion",
"windowBounds"
] as const) {
state[prop] = Settings.plain[prop]; state[prop] = Settings.plain[prop];
delete Settings.plain[prop]; delete Settings.plain[prop];
} }

View file

@ -4,7 +4,8 @@
* Copyright (c) 2023 Vendicated and Vencord contributors * 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 { join } from "path";
import { USER_AGENT, VENCORD_FILES_DIR } from "../constants"; import { USER_AGENT, VENCORD_FILES_DIR } from "../constants";
@ -56,12 +57,18 @@ export async function downloadVencordFiles() {
); );
} }
export function isValidVencordInstall(dir: string) { const existsAsync = (path: string) =>
return FILES_TO_DOWNLOAD.every(f => existsSync(join(dir, f))); 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() { export async function ensureVencordFiles() {
if (isValidVencordInstall(VENCORD_FILES_DIR)) return; if (await isValidVencordInstall(VENCORD_FILES_DIR)) return;
mkdirSync(VENCORD_FILES_DIR, { recursive: true }); mkdirSync(VENCORD_FILES_DIR, { recursive: true });
await downloadVencordFiles(); await downloadVencordFiles();

View file

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

View file

@ -4,9 +4,9 @@
* Copyright (c) 2023 Vendicated and Vencord contributors * Copyright (c) 2023 Vendicated and Vencord contributors
*/ */
import { Node } from "@vencord/venmic";
import { ipcRenderer } from "electron"; import { ipcRenderer } from "electron";
import type { Settings } from "shared/settings"; import type { Settings } from "shared/settings";
import type { LiteralUnion } from "type-fest";
import { IpcEvents } from "../shared/IpcEvents"; import { IpcEvents } from "../shared/IpcEvents";
import { invoke, sendSync } from "./typedIpc"; import { invoke, sendSync } from "./typedIpc";
@ -33,7 +33,8 @@ export const VesktopNative = {
}, },
fileManager: { fileManager: {
showItemInFolder: (path: string) => invoke<void>(IpcEvents.SHOW_ITEM_IN_FOLDER, path), 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: { settings: {
get: () => sendSync<Settings>(IpcEvents.GET_SETTINGS), get: () => sendSync<Settings>(IpcEvents.GET_SETTINGS),
@ -63,11 +64,10 @@ export const VesktopNative = {
virtmic: { virtmic: {
list: () => list: () =>
invoke< 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), >(IpcEvents.VIRT_MIC_LIST),
start: (targets: string[], workaround?: boolean) => invoke<void>(IpcEvents.VIRT_MIC_START, targets, workaround), start: (include: Node[]) => invoke<void>(IpcEvents.VIRT_MIC_START, include),
startSystem: (workaround?: boolean, onlyDefaultSpeakers?: boolean) => startSystem: (exclude: Node[]) => invoke<void>(IpcEvents.VIRT_MIC_START_SYSTEM, exclude),
invoke<void>(IpcEvents.VIRT_MIC_START_SYSTEM, workaround, onlyDefaultSpeakers),
stop: () => invoke<void>(IpcEvents.VIRT_MIC_STOP) stop: () => invoke<void>(IpcEvents.VIRT_MIC_STOP)
}, },
arrpc: { arrpc: {

View file

@ -6,7 +6,7 @@
import "./screenSharePicker.css"; 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 { findStoreLazy, onceReady } from "@vencord/types/webpack";
import { import {
Button, Button,
@ -19,8 +19,10 @@ import {
UserStore, UserStore,
useState useState
} from "@vencord/types/webpack/common"; } from "@vencord/types/webpack/common";
import { Node } from "@vencord/venmic";
import type { Dispatch, SetStateAction } from "react"; import type { Dispatch, SetStateAction } from "react";
import { addPatch } from "renderer/patches/shared"; import { addPatch } from "renderer/patches/shared";
import { useSettings } from "renderer/settings";
import { isLinux, isWindows } from "renderer/utils"; import { isLinux, isWindows } from "renderer/utils";
const StreamResolutions = ["480", "720", "1080", "1440"] as const; const StreamResolutions = ["480", "720", "1080", "1440"] as const;
@ -31,14 +33,23 @@ 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];
type SpecialSource = "None" | "Entire System";
type AudioSource = SpecialSource | Node;
type AudioSources = SpecialSource | Node[];
interface AudioItem {
name: string;
value: AudioSource;
}
interface StreamSettings { interface StreamSettings {
resolution: StreamResolution; resolution: StreamResolution;
fps: StreamFps; fps: StreamFps;
audio: boolean; audio: boolean;
audioSource?: string;
contentHint?: string; contentHint?: string;
workaround?: boolean; includeSources?: AudioSources;
onlyDefaultSpeakers?: boolean; excludeSources?: AudioSources;
} }
export interface StreamPick extends StreamSettings { export interface StreamPick extends StreamSettings {
@ -118,13 +129,17 @@ export function openScreenSharePicker(screens: Source[], skipPicker: boolean) {
modalProps={props} modalProps={props}
submit={async v => { submit={async v => {
didSubmit = true; didSubmit = true;
if (v.audioSource && v.audioSource !== "None") {
if (v.audioSource === "Entire System") { if (v.includeSources && v.includeSources !== "None") {
await VesktopNative.virtmic.startSystem(v.workaround); if (v.includeSources === "Entire System") {
await VesktopNative.virtmic.startSystem(
!v.excludeSources || isSpecialSource(v.excludeSources) ? [] : v.excludeSources
);
} else { } else {
await VesktopNative.virtmic.start([v.audioSource], v.workaround); await VesktopNative.virtmic.start(v.includeSources);
} }
} }
resolve(v); resolve(v);
}} }}
close={() => { close={() => {
@ -159,6 +174,136 @@ 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,
deviceSelect: v ? false : Settings.audio?.deviceSelect
})
}
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>
<Switch
hideBorder
onChange={value => {
Settings.audio = { ...Settings.audio, deviceSelect: value };
setAudioSources("None");
}}
value={Settings.audio?.deviceSelect ?? false}
disabled={Settings.audio?.ignoreDevices}
note={
<>
Allow to select devices such as microphones. Requires <b>Ignore Devices</b> to be turned
off.
</>
}
>
Device 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({ function StreamSettings({
source, source,
settings, settings,
@ -170,6 +315,8 @@ function StreamSettings({
setSettings: Dispatch<SetStateAction<StreamSettings>>; setSettings: Dispatch<SetStateAction<StreamSettings>>;
skipPicker: boolean; skipPicker: boolean;
}) { }) {
const Settings = useSettings();
const [thumb] = useAwaiter( const [thumb] = useAwaiter(
() => (skipPicker ? Promise.resolve(source.url) : VesktopNative.capturer.getLargeThumbnail(source.id)), () => (skipPicker ? Promise.resolve(source.url) : VesktopNative.capturer.getLargeThumbnail(source.id)),
{ {
@ -178,230 +325,363 @@ function StreamSettings({
} }
); );
const openSettings = () => {
const key = openModal(props => (
<AudioSettingsModal
modalProps={props}
close={() => props.onClose()}
setAudioSources={sources =>
setSettings(s => ({ ...s, includeSources: sources, excludeSources: sources }))
}
/>
));
};
return ( return (
<div className={isLinux ? "vcd-screen-picker-settings-grid" : ""}> <div>
<div> <Forms.FormTitle>What you're streaming</Forms.FormTitle>
<Forms.FormTitle>What you're streaming</Forms.FormTitle> <Card className="vcd-screen-picker-card vcd-screen-picker-preview">
<Card className="vcd-screen-picker-card vcd-screen-picker-preview"> <img
<img src={thumb}
src={thumb} alt=""
alt="" className={isLinux ? "vcd-screen-picker-preview-img-linux" : "vcd-screen-picker-preview-img"}
className={isLinux ? "vcd-screen-picker-preview-img-linux" : "vcd-screen-picker-preview-img"} />
/> <Text variant="text-sm/normal">{source.name}</Text>
<Text variant="text-sm/normal">{source.name}</Text> </Card>
</Card>
<Forms.FormTitle>Stream Settings</Forms.FormTitle> <Forms.FormTitle>Stream Settings</Forms.FormTitle>
<Card className="vcd-screen-picker-card"> <Card className="vcd-screen-picker-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>
<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"> <div className="vcd-screen-picker-radios">
{StreamResolutions.map(res => ( <label
<label className="vcd-screen-picker-radio"
className="vcd-screen-picker-radio" data-checked={settings.contentHint === "motion"}
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"
> >
Stream With Audio <Text variant="text-sm/bold">Prefer Smoothness</Text>
</Switch> <input
)} type="radio"
</section> name="contenthint"
</div> value="motion"
</Card> checked={settings.contentHint === "motion"}
</div> 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 && ( {isLinux && (
<AudioSourcePickerLinux <AudioSourcePickerLinux
audioSource={settings.audioSource} openSettings={openSettings}
workaround={settings.workaround} includeSources={settings.includeSources}
onlyDefaultSpeakers={settings.onlyDefaultSpeakers} excludeSources={settings.excludeSources}
setAudioSource={source => setSettings(s => ({ ...s, audioSource: source }))} deviceSelect={Settings.audio?.deviceSelect}
setWorkaround={value => setSettings(s => ({ ...s, workaround: value }))} granularSelect={Settings.audio?.granularSelect}
setOnlyDefaultSpeakers={value => setSettings(s => ({ ...s, onlyDefaultSpeakers: value }))} setIncludeSources={sources => setSettings(s => ({ ...s, includeSources: sources }))}
setExcludeSources={sources => setSettings(s => ({ ...s, excludeSources: sources }))}
/> />
)} )}
</div> </Card>
</div> </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, deviceSelect?: boolean): AudioItem[] {
if (isSpecialSource(node)) {
return [{ name: node, value: node }];
}
const rtn: AudioItem[] = [];
const mediaClass = node["media.class"];
if (mediaClass?.includes("Video") || mediaClass?.includes("Midi")) {
return rtn;
}
if (!deviceSelect && node["device.id"]) {
return rtn;
}
const name = node["application.name"];
if (name) {
rtn.push({ name: name, value: { "application.name": name } });
}
if (!granularSelect) {
return rtn;
}
const rawName = node["node.name"];
if (!name) {
rtn.push({ name: rawName, value: { "node.name": rawName } });
}
const binary = node["application.process.binary"];
if (!name && binary) {
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;
if (pid) {
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 }
});
}
if (mediaClass) {
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({ function AudioSourcePickerLinux({
audioSource, includeSources,
workaround, excludeSources,
onlyDefaultSpeakers, deviceSelect,
setAudioSource, granularSelect,
setWorkaround, openSettings,
setOnlyDefaultSpeakers setIncludeSources,
setExcludeSources
}: { }: {
audioSource?: string; includeSources?: AudioSources;
workaround?: boolean; excludeSources?: AudioSources;
onlyDefaultSpeakers?: boolean; deviceSelect?: boolean;
setAudioSource(s: string): void; granularSelect?: boolean;
setWorkaround(b: boolean): void; openSettings: () => void;
setOnlyDefaultSpeakers(b: boolean): void; setIncludeSources: (s: AudioSources) => void;
setExcludeSources: (s: AudioSources) => void;
}) { }) {
const [sources, _, loading] = useAwaiter(() => VesktopNative.virtmic.list(), { const [sources, _, loading] = useAwaiter(() => VesktopNative.virtmic.list(), {
fallbackValue: { ok: true, targets: [], hasPipewirePulse: true } fallbackValue: { ok: true, targets: [], hasPipewirePulse: true }
}); });
const allSources = sources.ok ? ["None", "Entire System", ...sources.targets] : null;
const hasPipewirePulse = sources.ok ? sources.hasPipewirePulse : true; const hasPipewirePulse = sources.ok ? sources.hasPipewirePulse : true;
const [ignorePulseWarning, setIgnorePulseWarning] = useState(false); 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, deviceSelect))
.flat()
.filter(uniqueName)
: [];
return ( return (
<> <>
<Forms.FormTitle>Audio Settings</Forms.FormTitle> <div className={includeSources === "Entire System" ? "vcd-screen-picker-quality" : undefined}>
<Card className="vcd-screen-picker-card"> <section>
{loading ? ( <Forms.FormTitle>{loading ? "Loading Sources..." : "Audio Sources"}</Forms.FormTitle>
<Forms.FormTitle>Loading Audio Sources...</Forms.FormTitle> <Select
) : ( options={allSources.map(({ name, value }) => ({
<Forms.FormTitle>Audio Source</Forms.FormTitle> label: name,
)} value: value,
default: name === "None"
{!sources.ok && sources.isGlibCxxOutdated && ( }))}
<Forms.FormText> isSelected={isItemSelected(includeSources)}
Failed to retrieve Audio Sources because your C++ library is too old to run select={updateItems(setIncludeSources, includeSources)}
<a href="https://github.com/Vencord/venmic" target="_blank"> serialize={String}
venmic popoutPosition="top"
</a> closeOnSelect={false}
. See{" "} />
<a href="https://gist.github.com/Vendicated/b655044ffbb16b2716095a448c6d827a" target="_blank"> </section>
this guide {includeSources === "Entire System" && (
</a>{" "} <section>
for possible solutions. <Forms.FormTitle>Exclude Sources</Forms.FormTitle>
</Forms.FormText>
)}
{hasPipewirePulse || ignorePulseWarning ? (
allSources && (
<Select <Select
options={allSources.map(s => ({ label: s, value: s, default: s === "None" }))} options={allSources
isSelected={s => s === audioSource} .filter(x => x.name !== "Entire System")
select={setAudioSource} .map(({ name, value }) => ({
label: name,
value: value,
default: name === "None"
}))}
isSelected={isItemSelected(excludeSources)}
select={updateItems(setExcludeSources, excludeSources)}
serialize={String} serialize={String}
popoutPosition="top"
closeOnSelect={false}
/> />
) </section>
) : (
<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>
)} )}
</div>
<Forms.FormDivider className={Margins.top16 + " " + Margins.bottom16} /> <Button
color={Button.Colors.TRANSPARENT}
<Switch onClick={openSettings}
onChange={setWorkaround} className="vcd-screen-picker-settings-button"
value={workaround ?? false} >
note={ Open Audio Settings
<> </Button>
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>
</> </>
); );
} }
@ -421,10 +701,11 @@ function ModalComponent({
}) { }) {
const [selected, setSelected] = useState<string | undefined>(skipPicker ? screens[0].id : void 0); 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: "720",
fps: "60", fps: "30",
contentHint: "motion", contentHint: "motion",
audio: true audio: true,
includeSources: "None"
}); });
return ( return (
@ -480,7 +761,7 @@ function ModalComponent({
const constraints = { const constraints = {
...track.getConstraints(), ...track.getConstraints(),
frameRate, frameRate: { min: frameRate, ideal: frameRate },
width: { min: 640, ideal: width, max: width }, width: { min: 640, ideal: width, max: width },
height: { min: 480, ideal: height, max: height }, height: { min: 480, ideal: height, max: height },
advanced: [{ width: width, height: height }], advanced: [{ width: width, height: height }],

View file

@ -11,17 +11,6 @@
gap: 1em; 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 { .vcd-screen-picker-card {
flex-grow: 1; flex-grow: 1;
} }
@ -38,7 +27,7 @@
} }
.vcd-screen-picker-selected img { .vcd-screen-picker-selected img {
border: 2px solid var(--brand-experiment); border: 2px solid var(--brand-500);
border-radius: 3px; border-radius: 3px;
} }
@ -49,7 +38,7 @@
} }
.vcd-screen-picker-grid label:hover { .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 { .vcd-screen-picker-preview-img-linux {
width: 100%; width: 60%;
margin-bottom: 0.5em; margin-bottom: 0.5em;
} }
@ -101,8 +90,8 @@
} }
.vcd-screen-picker-radio[data-checked="true"] { .vcd-screen-picker-radio[data-checked="true"] {
background-color: var(--brand-experiment); background-color: var(--brand-500);
border-color: var(--brand-experiment); border-color: var(--brand-500);
} }
.vcd-screen-picker-radio[data-checked="true"] h2 { .vcd-screen-picker-radio[data-checked="true"] h2 {
@ -120,6 +109,11 @@
flex: 1 1 auto; flex: 1 1 auto;
} }
.vcd-screen-picker-settings-button {
margin-left: auto;
margin-top: 0.3rem;
}
.vcd-screen-picker-radios { .vcd-screen-picker-radios {
display: flex; display: flex;
width: 100%; width: 100%;

View file

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

View file

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

View file

@ -9,3 +9,8 @@
scrollbar-width: unset !important; scrollbar-width: unset !important;
scrollbar-color: unset !important; scrollbar-color: unset !important;
} }
/* Workaround for making things in the draggable area clickable again on macOS */
.platform-osx [class*=topic_], .platform-osx [class*=notice_] button {
-webkit-app-region: no-drag;
}

View file

@ -13,7 +13,7 @@ console.log("read if cute :3");
export * as Components from "./components"; export * as Components from "./components";
import { findByPropsLazy, onceReady } from "@vencord/types/webpack"; import { findByPropsLazy, onceReady } from "@vencord/types/webpack";
import { FluxDispatcher } from "@vencord/types/webpack/common"; import { Alerts, FluxDispatcher } from "@vencord/types/webpack/common";
import SettingsUi from "./components/settings/Settings"; import SettingsUi from "./components/settings/Settings";
import { Settings } from "./settings"; import { Settings } from "./settings";
@ -65,3 +65,19 @@ const VoiceActions = findByPropsLazy("toggleSelfMute");
VesktopNative.voice.onToggleSelfMute(() => VoiceActions.toggleSelfMute()); VesktopNative.voice.onToggleSelfMute(() => VoiceActions.toggleSelfMute());
VesktopNative.voice.onToggleSelfDeaf(() => VoiceActions.toggleSelfDeaf()); VesktopNative.voice.onToggleSelfDeaf(() => VoiceActions.toggleSelfDeaf());
// 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: { replacement: {
// FIXME: fix eslint rule // FIXME: fix eslint rule
// eslint-disable-next-line no-useless-escape // eslint-disable-next-line no-useless-escape
match: /\.isPlatformEmbedded(?=\?\i\.DesktopNotificationTypes\.ALL)/g, match: /\.isPlatformEmbedded(?=\?\i\.\i\.ALL)/g,
replace: "$&||true" replace: "$&||true"
} }
} }

View file

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

View file

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

View file

@ -23,6 +23,7 @@ export const enum IpcEvents {
GET_SETTINGS = "VCD_GET_SETTINGS", GET_SETTINGS = "VCD_GET_SETTINGS",
SET_SETTINGS = "VCD_SET_SETTINGS", SET_SETTINGS = "VCD_SET_SETTINGS",
GET_VENCORD_DIR = "VCD_GET_VENCORD_DIR",
SELECT_VENCORD_DIR = "VCD_SELECT_VENCORD_DIR", SELECT_VENCORD_DIR = "VCD_SELECT_VENCORD_DIR",
UPDATER_GET_DATA = "VCD_UPDATER_GET_DATA", UPDATER_GET_DATA = "VCD_UPDATER_GET_DATA",

View file

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

View file

@ -59,6 +59,19 @@ export class SettingsStore<T extends object> {
self.pathListeners.get(setPath)?.forEach(cb => cb(value)); self.pathListeners.get(setPath)?.forEach(cb => cb(value));
return true; 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)
});