Merge branch 'main' into global-shortcuts
# Conflicts: # src/renderer/components/settings/Settings.tsx
This commit is contained in:
@@ -0,0 +1,93 @@
|
|||||||
|
name: Bug/Crash Report
|
||||||
|
description: Create a bug or crash report for Vesktop.
|
||||||
|
labels: [bug]
|
||||||
|
title: "[Bug] <title>"
|
||||||
|
|
||||||
|
body:
|
||||||
|
- type: markdown
|
||||||
|
attributes:
|
||||||
|
value: |
|
||||||
|
GitHub Issues are for development, not support!
|
||||||
|
|
||||||
|
Vesktop and Vencord are two separate things. This issue tracker is only for Vesktop. If your Vesktop crashes, it is very likely an issue with Vencord, not Vesktop.
|
||||||
|
|
||||||
|
If you are experiencing any of the following issues, go to our [Discord server](https://discord.gg/D9uwnFnqmd) for support:
|
||||||
|
- Vesktop doesn't work or crashes
|
||||||
|
- A Vencord plugin doesn't work anymore
|
||||||
|
- You are having issues installing Vesktop
|
||||||
|
- You want to ask a question about Vesktop, Vencord or one of its plugins
|
||||||
|
|
||||||
|
- type: input
|
||||||
|
id: vesktop-version
|
||||||
|
attributes:
|
||||||
|
label: Vesktop Version
|
||||||
|
placeholder: e.g. 1.6.7
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
|
||||||
|
- type: input
|
||||||
|
id: os
|
||||||
|
attributes:
|
||||||
|
label: Operating System
|
||||||
|
placeholder: e.g. Windows 11, macOS Tahoe, Fedora Linux 44 (GNOME), Arch Linux (Hyprland)
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
|
||||||
|
- type: input
|
||||||
|
id: install-method
|
||||||
|
attributes:
|
||||||
|
label: Install Method
|
||||||
|
placeholder: e.g. Setup exe, dmg, Flatpak, rpm, vesktop-git AUR
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
|
||||||
|
- type: textarea
|
||||||
|
id: bug-description
|
||||||
|
attributes:
|
||||||
|
label: What happens when the bug or crash occurs?
|
||||||
|
description: Where does this bug or crash occur, when does it occur, etc.
|
||||||
|
placeholder: The bug/crash happens sometimes when I do ..., causing this to not work/the app to crash. I think it happens because of ...
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
|
||||||
|
- type: textarea
|
||||||
|
id: expected-behaviour
|
||||||
|
attributes:
|
||||||
|
label: What is the expected behaviour?
|
||||||
|
description: Simply detail what the expected behaviour is.
|
||||||
|
placeholder: I expect Vesktop/Discord to open the ... page instead of ..., it prevents me from doing ...
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
|
||||||
|
- type: textarea
|
||||||
|
id: steps-to-take
|
||||||
|
attributes:
|
||||||
|
label: How do you recreate this bug or crash?
|
||||||
|
description: Give us a list of steps in order to recreate the bug or crash.
|
||||||
|
placeholder: |
|
||||||
|
1. Do ...
|
||||||
|
2. Then ...
|
||||||
|
3. Do this ..., ... and then ...
|
||||||
|
4. Observe "the bug" or "the crash"
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
|
||||||
|
- type: textarea
|
||||||
|
id: crash-log
|
||||||
|
attributes:
|
||||||
|
label: Errors
|
||||||
|
render: text
|
||||||
|
description: Open the Developer Console with `Ctrl+Shift+i` / `Cmd+Option+i`. Then look for any red errors (Ignore network errors like Failed to load resource) and paste them. Run Vesktop from the command line to get console output and also paste that here
|
||||||
|
validations:
|
||||||
|
required: false
|
||||||
|
|
||||||
|
- type: checkboxes
|
||||||
|
id: agreement-check
|
||||||
|
attributes:
|
||||||
|
label: Request Agreement
|
||||||
|
description: We only accept reports for bugs that happen on official builds on Discord Stable. Canary and PTB are Development branches and may be unstable
|
||||||
|
options:
|
||||||
|
- label: I've searched existing issues and this hasn't been reported before
|
||||||
|
- label: I installed Vesktop via an official method (download from vesktop.dev or from source), not via something like Nix packages
|
||||||
|
- label: This is not a support request
|
||||||
|
- label: I am willing to submit a fix for this myself
|
||||||
@@ -1,5 +1,11 @@
|
|||||||
blank_issues_enabled: false
|
blank_issues_enabled: false
|
||||||
contact_links:
|
contact_links:
|
||||||
- name: Vencord Support Server
|
- name: 💬 Discord Community
|
||||||
url: https://discord.gg/D9uwnFnqmd
|
url: https://discord.gg/D9uwnFnqmd
|
||||||
about: "Need Help? Join our support server and ask in the #vesktop-support channel!"
|
about: For plugin requests, support questions, and general help - join our Discord server
|
||||||
|
- name: 🔌 Plugin Requests
|
||||||
|
url: https://discord.gg/D9uwnFnqmd
|
||||||
|
about: Want a new plugin? Request it in our Discord community, not here
|
||||||
|
- name: ❓ Support & Questions
|
||||||
|
url: https://discord.gg/D9uwnFnqmd
|
||||||
|
about: Need help? Ask in our Discord server for faster responses
|
||||||
|
|||||||
@@ -1,20 +0,0 @@
|
|||||||
name: Vesktop Developer Issue
|
|
||||||
description: Reserved for Vesktop Developers. Join our support server for support.
|
|
||||||
|
|
||||||
body:
|
|
||||||
- type: markdown
|
|
||||||
attributes:
|
|
||||||
value: |
|
|
||||||
# This form is reserved for Vesktop Developers. Do not open an issue.
|
|
||||||
|
|
||||||
Instead, use the [#vesktop-support channel](https://discord.com/channels/1015060230222131221/1345457031426871417) on our [Discord server](https://vencord.dev/discord) for help and reporting issues.
|
|
||||||
|
|
||||||
Your issue will be closed immediately with no comment and you will be blocked if you ignore this.
|
|
||||||
|
|
||||||
This is because 99% of issues are not actually bugs, but rather user or system issues and it adds a lot of noise to our development process.
|
|
||||||
- type: textarea
|
|
||||||
id: content
|
|
||||||
attributes:
|
|
||||||
label: Content
|
|
||||||
validations:
|
|
||||||
required: true
|
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
name: Feature Request
|
||||||
|
description: Have an idea for a new feature? Request it here! No plugin requests, go to our Discord server for that
|
||||||
|
labels: [enhancement]
|
||||||
|
title: "[Feature] <title>"
|
||||||
|
|
||||||
|
body:
|
||||||
|
- type: markdown
|
||||||
|
attributes:
|
||||||
|
value: |
|
||||||
|
This form is not for any of the following:
|
||||||
|
- Plugin requests — Go to our [Discord server](https://discord.gg/D9uwnFnqmd) for that
|
||||||
|
- Feature requests for plugins — Go to our [Discord server](https://discord.gg/D9uwnFnqmd) for that
|
||||||
|
|
||||||
|
- type: textarea
|
||||||
|
id: feature-description
|
||||||
|
attributes:
|
||||||
|
label: A clear and concise description of the feature you'd like to see
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
|
||||||
|
- type: textarea
|
||||||
|
id: implementation-notes
|
||||||
|
attributes:
|
||||||
|
label: Implementation Notes
|
||||||
|
description: If you have ideas about how this could be implemented technically, share them here
|
||||||
|
validations:
|
||||||
|
required: false
|
||||||
|
|
||||||
|
- type: textarea
|
||||||
|
id: additional-context
|
||||||
|
attributes:
|
||||||
|
label: Additional context
|
||||||
|
description: Screenshots, mockups, examples from other apps, etc.
|
||||||
|
validations:
|
||||||
|
required: false
|
||||||
|
|
||||||
|
- type: checkboxes
|
||||||
|
id: agreement-check
|
||||||
|
attributes:
|
||||||
|
label: Request Agreement
|
||||||
|
options:
|
||||||
|
- label: I've searched existing issues and this hasn't been suggested before
|
||||||
|
- label: This is a **core modification feature**, not a plugin request (those go in Discord)
|
||||||
|
- label: This feature would benefit most users, not just a specific use case
|
||||||
|
- label: I am willing to work on this myself
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
<!--
|
||||||
|
We do not accept PRs that were created by AI. You will be permanently blocked with no further warning if you submit AI generated PRs.
|
||||||
|
Also do not use AI in communication, it makes me ill
|
||||||
|
-->
|
||||||
|
|
||||||
|
## Describe your Changes
|
||||||
|
|
||||||
|
## Screenshots (if applicable)
|
||||||
|
|
||||||
|
## Checklist before submitting
|
||||||
|
<!-- Hint: [x] this is how to check boxes -->
|
||||||
|
- [ ] This pull request was written by me, and not an AI agent
|
||||||
+20
-12
@@ -1,9 +1,6 @@
|
|||||||
name: Update metainfo on release
|
name: Upload release metainfo
|
||||||
|
|
||||||
on:
|
on:
|
||||||
release:
|
|
||||||
types:
|
|
||||||
- published
|
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
|
|
||||||
permissions:
|
permissions:
|
||||||
@@ -15,10 +12,22 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v7
|
- uses: actions/checkout@v7
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
|
||||||
|
- name: Get latest tag
|
||||||
|
id: latest_tag
|
||||||
|
run: |
|
||||||
|
tag=$(git tag --sort=-creatordate | head -n1)
|
||||||
|
echo "tag=$tag" >> "$GITHUB_OUTPUT"
|
||||||
|
|
||||||
|
- name: Checkout latest tag
|
||||||
|
run: git checkout "${{ steps.latest_tag.outputs.tag }}"
|
||||||
|
|
||||||
- uses: pnpm/action-setup@v6 # Install pnpm using packageManager key in package.json
|
- uses: pnpm/action-setup@v6 # Install pnpm using packageManager key in package.json
|
||||||
|
|
||||||
- name: Use Node.js 24
|
- name: Use Node.js 24
|
||||||
uses: actions/setup-node@v6
|
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
|
||||||
with:
|
with:
|
||||||
node-version: 24
|
node-version: 24
|
||||||
|
|
||||||
@@ -27,12 +36,11 @@ jobs:
|
|||||||
|
|
||||||
- name: Update metainfo
|
- name: Update metainfo
|
||||||
run: pnpm generateMeta
|
run: pnpm generateMeta
|
||||||
|
env:
|
||||||
- name: Commit and merge in changes
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
run: |
|
|
||||||
git config user.name "github-actions[bot]"
|
- name: Upload metainfo to release
|
||||||
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
|
run: |
|
||||||
|
gh release upload "${{ steps.latest_tag.outputs.tag }}" dist/dev.vencord.Vesktop.metainfo.xml
|
||||||
gh release upload "${{ github.event.release.tag_name }}" dist/dev.vencord.Vesktop.metainfo.xml
|
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ jobs:
|
|||||||
- uses: pnpm/action-setup@v6 # Install pnpm using packageManager key in package.json
|
- uses: pnpm/action-setup@v6 # Install pnpm using packageManager key in package.json
|
||||||
|
|
||||||
- name: Use Node.js 24
|
- name: Use Node.js 24
|
||||||
uses: actions/setup-node@v6
|
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
|
||||||
with:
|
with:
|
||||||
node-version: 24
|
node-version: 24
|
||||||
cache: "pnpm"
|
cache: "pnpm"
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ jobs:
|
|||||||
- uses: pnpm/action-setup@v6 # Install pnpm using packageManager key in package.json
|
- uses: pnpm/action-setup@v6 # Install pnpm using packageManager key in package.json
|
||||||
|
|
||||||
- name: Use Node.js 24
|
- name: Use Node.js 24
|
||||||
uses: actions/setup-node@v6
|
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
|
||||||
with:
|
with:
|
||||||
node-version: 24
|
node-version: 24
|
||||||
cache: "pnpm"
|
cache: "pnpm"
|
||||||
|
|||||||
+14
-16
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "vesktop",
|
"name": "vesktop",
|
||||||
"version": "1.6.5",
|
"version": "1.6.7",
|
||||||
"private": true,
|
"private": true,
|
||||||
"description": "Vesktop is a custom Discord desktop app",
|
"description": "Vesktop is a custom Discord desktop app",
|
||||||
"keywords": [],
|
"keywords": [],
|
||||||
@@ -22,42 +22,40 @@
|
|||||||
"test": "pnpm lint && pnpm testTypes",
|
"test": "pnpm lint && pnpm testTypes",
|
||||||
"testTypes": "tsc --noEmit",
|
"testTypes": "tsc --noEmit",
|
||||||
"watch": "pnpm build --watch",
|
"watch": "pnpm build --watch",
|
||||||
"generateMeta": "tsx scripts/utils/generateMeta.mts",
|
"generateMeta": "tsx scripts/utils/generateMeta.mts"
|
||||||
"updateArrpcDB": "node ./node_modules/arrpc/update_db.js",
|
|
||||||
"postinstall": "pnpm updateArrpcDB"
|
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"arrpc": "github:OpenAsar/arrpc#2234e9c9111f4c42ebcc3aa6a2215bfd979eef77",
|
"arrpc": "github:Vencord/arrpc#82ece7442564d725d8202a8bb9bed5903ee85026",
|
||||||
"electron-updater": "6.6.2"
|
"electron-updater": "6.6.2"
|
||||||
},
|
},
|
||||||
"optionalDependencies": {
|
"optionalDependencies": {
|
||||||
"@vencord/venmic": "^6.1.0"
|
"@vencord/venmic": "^7.2.3"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@fal-works/esbuild-plugin-global-externals": "^2.1.2",
|
"@fal-works/esbuild-plugin-global-externals": "^2.1.2",
|
||||||
"@stylistic/eslint-plugin": "^5.10.0",
|
"@stylistic/eslint-plugin": "^5.10.0",
|
||||||
"@types/node": "^26.0.1",
|
"@types/node": "^26.2.0",
|
||||||
"@types/react": "19.2.9",
|
"@types/react": "19.2.9",
|
||||||
"@vencord/types": "^1.14.1",
|
"@vencord/types": "^1.15.1",
|
||||||
"dotenv": "^17.3.1",
|
"dotenv": "^17.3.1",
|
||||||
"electron": "^43.0.0",
|
"electron": "43.2.0",
|
||||||
"electron-builder": "^26.15.3",
|
"electron-builder": "^26.15.3",
|
||||||
"esbuild": "^0.28.1",
|
"esbuild": "^0.28.2",
|
||||||
"eslint": "^10.6.0",
|
"eslint": "^10.8.1",
|
||||||
"eslint-import-resolver-alias": "^1.1.2",
|
"eslint-import-resolver-alias": "^1.1.2",
|
||||||
"eslint-plugin-path-alias": "^2.1.0",
|
"eslint-plugin-path-alias": "^2.1.0",
|
||||||
"eslint-plugin-prettier": "^5.5.6",
|
"eslint-plugin-prettier": "^5.5.6",
|
||||||
"eslint-plugin-react": "^7.37.5",
|
"eslint-plugin-react": "^7.37.5",
|
||||||
"eslint-plugin-simple-header": "^1.2.2",
|
"eslint-plugin-simple-header": "^1.2.2",
|
||||||
"eslint-plugin-simple-import-sort": "^13.0.0",
|
"eslint-plugin-simple-import-sort": "^14.0.0",
|
||||||
"eslint-plugin-unused-imports": "^4.4.1",
|
"eslint-plugin-unused-imports": "^4.4.1",
|
||||||
"libvesktop": "link:packages/libvesktop",
|
"libvesktop": "link:packages/libvesktop",
|
||||||
"prettier": "^3.9.4",
|
"prettier": "^3.9.6",
|
||||||
"source-map-support": "^0.5.21",
|
"source-map-support": "^0.5.21",
|
||||||
"tsx": "^4.22.4",
|
"tsx": "^4.23.12",
|
||||||
"type-fest": "^5.7.0",
|
"type-fest": "^5.8.0",
|
||||||
"typescript": "^6.0.3",
|
"typescript": "^6.0.3",
|
||||||
"typescript-eslint": "^8.62.1",
|
"typescript-eslint": "^8.67.0",
|
||||||
"xml-formatter": "^3.6.7"
|
"xml-formatter": "^3.6.7"
|
||||||
},
|
},
|
||||||
"packageManager": "pnpm@11.9.0",
|
"packageManager": "pnpm@11.9.0",
|
||||||
|
|||||||
@@ -1,27 +0,0 @@
|
|||||||
diff --git a/src/process/index.js b/src/process/index.js
|
|
||||||
index 389b0845256a34b4536d6da99edb00d17f13a6b4..f17a0ac687e9110ebfd33cb91fd2f6250d318643 100644
|
|
||||||
--- a/src/process/index.js
|
|
||||||
+++ b/src/process/index.js
|
|
||||||
@@ -5,8 +5,20 @@ 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');
|
|
||||||
+DetectableDB.push(
|
|
||||||
+ {
|
|
||||||
+ aliases: ["Obs"],
|
|
||||||
+ executables: [
|
|
||||||
+ { is_launcher: false, name: "obs", os: "linux" },
|
|
||||||
+ { is_launcher: false, name: "obs.exe", os: "win32" },
|
|
||||||
+ { is_launcher: false, name: "obs.app", os: "darwin" }
|
|
||||||
+ ],
|
|
||||||
+ hook: true,
|
|
||||||
+ id: "STREAMERMODE",
|
|
||||||
+ name: "OBS"
|
|
||||||
+ }
|
|
||||||
+);
|
|
||||||
|
|
||||||
import * as Natives from './native/index.js';
|
|
||||||
const Native = Natives[process.platform];
|
|
||||||
Generated
+482
-465
File diff suppressed because it is too large
Load Diff
+1
-1
@@ -1,5 +1,4 @@
|
|||||||
patchedDependencies:
|
patchedDependencies:
|
||||||
arrpc@3.5.0: patches/arrpc@3.5.0.patch
|
|
||||||
electron-updater: patches/electron-updater.patch
|
electron-updater: patches/electron-updater.patch
|
||||||
nodeLinker: hoisted
|
nodeLinker: hoisted
|
||||||
allowBuilds:
|
allowBuilds:
|
||||||
@@ -14,3 +13,4 @@ packages:
|
|||||||
- packages/*
|
- packages/*
|
||||||
minimumReleaseAgeExclude:
|
minimumReleaseAgeExclude:
|
||||||
- electron
|
- electron
|
||||||
|
- '@vencord/*'
|
||||||
|
|||||||
@@ -46,7 +46,8 @@ function generateDescription(description: string, descriptionNode: Element) {
|
|||||||
const releases = await fetch("https://api.github.com/repos/Vencord/Vesktop/releases", {
|
const releases = await fetch("https://api.github.com/repos/Vencord/Vesktop/releases", {
|
||||||
headers: {
|
headers: {
|
||||||
Accept: "application/vnd.github+json",
|
Accept: "application/vnd.github+json",
|
||||||
"X-Github-Api-Version": "2022-11-28"
|
"X-Github-Api-Version": "2022-11-28",
|
||||||
|
Authorization: `Bearer ${process.env.GITHUB_TOKEN}`
|
||||||
}
|
}
|
||||||
}).then(res => res.json());
|
}).then(res => res.json());
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,8 @@
|
|||||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { resolve } from "path";
|
import { SESSION_DATA_DIR } from "main/constants";
|
||||||
|
import { join, resolve } from "path";
|
||||||
import { IpcCommands } from "shared/IpcEvents";
|
import { IpcCommands } from "shared/IpcEvents";
|
||||||
import { MessageChannel, Worker } from "worker_threads";
|
import { MessageChannel, Worker } from "worker_threads";
|
||||||
|
|
||||||
@@ -19,6 +20,8 @@ const inviteCodeRegex = /^(\w|-)+$/;
|
|||||||
export async function initArRPC() {
|
export async function initArRPC() {
|
||||||
if (worker || !Settings.store.arRPC) return;
|
if (worker || !Settings.store.arRPC) return;
|
||||||
|
|
||||||
|
process.env.ARRPC_DETECTABLE_CACHE_PATH = join(SESSION_DATA_DIR, "arrpc-detectable-db.json");
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const { port1: hostPort, port2: workerPort } = new MessageChannel();
|
const { port1: hostPort, port2: workerPort } = new MessageChannel();
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -149,7 +149,7 @@ handle(IpcEvents.SELECT_VENCORD_DIR, async (_e, value?: null) => {
|
|||||||
if (!res.filePaths.length) return "cancelled";
|
if (!res.filePaths.length) return "cancelled";
|
||||||
|
|
||||||
const dir = res.filePaths[0];
|
const dir = res.filePaths[0];
|
||||||
if (!isValidVencordInstall(dir)) return "invalid";
|
if (!(await isValidVencordInstall(dir))) return "invalid";
|
||||||
|
|
||||||
State.store.vencordDir = dir;
|
State.store.vencordDir = dir;
|
||||||
|
|
||||||
|
|||||||
+4
-19
@@ -46,7 +46,7 @@ function init() {
|
|||||||
app.commandLine.removeSwitch("enable-features");
|
app.commandLine.removeSwitch("enable-features");
|
||||||
app.commandLine.removeSwitch("disable-features");
|
app.commandLine.removeSwitch("disable-features");
|
||||||
|
|
||||||
if (hardwareAcceleration === false || process.argv.includes("--disable-gpu")) {
|
if (!hardwareAcceleration || process.argv.includes("--disable-gpu")) {
|
||||||
enableHardwareAcceleration = false;
|
enableHardwareAcceleration = false;
|
||||||
app.disableHardwareAcceleration();
|
app.disableHardwareAcceleration();
|
||||||
} else {
|
} else {
|
||||||
@@ -65,29 +65,14 @@ function init() {
|
|||||||
app.commandLine.appendSwitch("disable-smooth-scrolling");
|
app.commandLine.appendSwitch("disable-smooth-scrolling");
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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.add("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");
|
||||||
|
|
||||||
// WinRetrieveSuggestionsOnlyOnDemand: Work around electron 13 bug w/ async spellchecking on Windows.
|
// Prevent Discord from registering as a media service.
|
||||||
// HardwareMediaKeyHandling, MediaSessionService: Prevent Discord from registering as a media service.
|
|
||||||
disabledFeatures.add("WinRetrieveSuggestionsOnlyOnDemand");
|
|
||||||
disabledFeatures.add("HardwareMediaKeyHandling");
|
disabledFeatures.add("HardwareMediaKeyHandling");
|
||||||
disabledFeatures.add("MediaSessionService");
|
disabledFeatures.add("MediaSessionService");
|
||||||
|
|
||||||
if (isLinux) {
|
if (isLinux) {
|
||||||
// Support TTS on Linux using https://wiki.archlinux.org/title/Speech_dispatcher
|
|
||||||
app.commandLine.appendSwitch("enable-speech-dispatcher");
|
|
||||||
|
|
||||||
// This is needed to fix washed out colours - https://github.com/electron/electron/issues/49566
|
// This is needed to fix washed out colours - https://github.com/electron/electron/issues/49566
|
||||||
// Supposed to be fixed already according to comments there, but it's just not lol, I can repro on Electron 43.0.0
|
// Supposed to be fixed already according to comments there, but it's just not lol, I can repro on Electron 43.0.0
|
||||||
// when moving the window from my main monitor (HDR - not sure if this is relevant lol) to second monitor (SDR) and back
|
// when moving the window from my main monitor (HDR - not sure if this is relevant lol) to second monitor (SDR) and back
|
||||||
@@ -99,8 +84,8 @@ function init() {
|
|||||||
|
|
||||||
disabledFeatures.forEach(feat => enabledFeatures.delete(feat));
|
disabledFeatures.forEach(feat => enabledFeatures.delete(feat));
|
||||||
|
|
||||||
const enabledFeaturesArray = enabledFeatures.values().filter(Boolean).toArray();
|
const enabledFeaturesArray = [...enabledFeatures].filter(Boolean);
|
||||||
const disabledFeaturesArray = disabledFeatures.values().filter(Boolean).toArray();
|
const disabledFeaturesArray = [...disabledFeatures].filter(Boolean);
|
||||||
|
|
||||||
if (enabledFeaturesArray.length) {
|
if (enabledFeaturesArray.length) {
|
||||||
app.commandLine.appendSwitch("enable-features", enabledFeaturesArray.join(","));
|
app.commandLine.appendSwitch("enable-features", enabledFeaturesArray.join(","));
|
||||||
|
|||||||
+40
-12
@@ -156,6 +156,25 @@ function initMenuBar(win: BrowserWindow) {
|
|||||||
accelerator: "CmdOrCtrl+=",
|
accelerator: "CmdOrCtrl+=",
|
||||||
role: "zoomIn",
|
role: "zoomIn",
|
||||||
visible: false
|
visible: false
|
||||||
|
},
|
||||||
|
// numpad zooms
|
||||||
|
{
|
||||||
|
label: "Zoom in (hidden)",
|
||||||
|
accelerator: "CmdOrCtrl+numadd",
|
||||||
|
role: "zoomIn",
|
||||||
|
visible: false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Zoom out (hidden)",
|
||||||
|
accelerator: "CmdOrCtrl+numsub",
|
||||||
|
role: "zoomOut",
|
||||||
|
visible: false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Reset Zoom (hidden)",
|
||||||
|
accelerator: "CmdOrCtrl+num0",
|
||||||
|
role: "resetZoom",
|
||||||
|
visible: false
|
||||||
}
|
}
|
||||||
] satisfies MenuItemList;
|
] satisfies MenuItemList;
|
||||||
|
|
||||||
@@ -312,17 +331,24 @@ function getWindowBoundsOptions(): BrowserWindowConstructorOptions {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function buildBrowserWindowOptions(): BrowserWindowConstructorOptions {
|
function buildBrowserWindowOptions(): BrowserWindowConstructorOptions {
|
||||||
const { staticTitle, transparencyOption, enableMenu, customTitleBar, splashTheming, splashBackground } =
|
const {
|
||||||
Settings.store;
|
staticTitle,
|
||||||
|
transparencyOption,
|
||||||
|
enableMenu,
|
||||||
|
enableShadow,
|
||||||
|
enableRoundedCorners,
|
||||||
|
nativeTitleBar,
|
||||||
|
splashTheming,
|
||||||
|
splashBackground
|
||||||
|
} = Settings.store;
|
||||||
|
|
||||||
const { frameless, transparent, macosVibrancyStyle } = VencordSettings.store;
|
const { transparent, macosVibrancyStyle } = VencordSettings.store;
|
||||||
|
|
||||||
const noFrame = frameless === true || customTitleBar === true;
|
const frameless = !nativeTitleBar;
|
||||||
const backgroundColor =
|
const backgroundColor = splashTheming ? splashBackground : nativeTheme.shouldUseDarkColors ? "#313338" : "#ffffff";
|
||||||
splashTheming !== false ? splashBackground : nativeTheme.shouldUseDarkColors ? "#313338" : "#ffffff";
|
|
||||||
|
|
||||||
const options: BrowserWindowConstructorOptions = {
|
const options: BrowserWindowConstructorOptions = {
|
||||||
show: Settings.store.enableSplashScreen === false && !CommandLine.values["start-minimized"],
|
show: !Settings.store.enableSplashScreen && !CommandLine.values["start-minimized"],
|
||||||
backgroundColor,
|
backgroundColor,
|
||||||
webPreferences: {
|
webPreferences: {
|
||||||
nodeIntegration: false,
|
nodeIntegration: false,
|
||||||
@@ -334,8 +360,10 @@ function buildBrowserWindowOptions(): BrowserWindowConstructorOptions {
|
|||||||
// 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
|
||||||
backgroundThrottling: false
|
backgroundThrottling: false
|
||||||
},
|
},
|
||||||
frame: !noFrame,
|
frame: !frameless,
|
||||||
autoHideMenuBar: enableMenu,
|
autoHideMenuBar: enableMenu,
|
||||||
|
hasShadow: enableShadow,
|
||||||
|
roundedCorners: enableRoundedCorners,
|
||||||
...getWindowBoundsOptions()
|
...getWindowBoundsOptions()
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -348,7 +376,7 @@ function buildBrowserWindowOptions(): BrowserWindowConstructorOptions {
|
|||||||
options.backgroundColor = "#00000000";
|
options.backgroundColor = "#00000000";
|
||||||
options.backgroundMaterial = transparencyOption;
|
options.backgroundMaterial = transparencyOption;
|
||||||
|
|
||||||
if (customTitleBar) {
|
if (frameless) {
|
||||||
options.transparent = true;
|
options.transparent = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -378,10 +406,10 @@ function createMainWindow() {
|
|||||||
const win = (mainWin = new BrowserWindow(buildBrowserWindowOptions()));
|
const win = (mainWin = new BrowserWindow(buildBrowserWindowOptions()));
|
||||||
|
|
||||||
win.setMenuBarVisibility(false);
|
win.setMenuBarVisibility(false);
|
||||||
if (process.platform === "darwin" && Settings.store.customTitleBar) win.setWindowButtonVisibility(false);
|
if (process.platform === "darwin" && Settings.store.nativeTitleBar) 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 && Settings.store.tray;
|
||||||
if (isQuitting || (process.platform !== "darwin" && !useTray)) return;
|
if (isQuitting || (process.platform !== "darwin" && !useTray)) return;
|
||||||
|
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
@@ -441,7 +469,7 @@ export async function createWindows() {
|
|||||||
const startMinimized = CommandLine.values["start-minimized"];
|
const startMinimized = CommandLine.values["start-minimized"];
|
||||||
|
|
||||||
let splash: BrowserWindow | undefined;
|
let splash: BrowserWindow | undefined;
|
||||||
if (Settings.store.enableSplashScreen !== false) {
|
if (Settings.store.enableSplashScreen) {
|
||||||
splash = createSplashWindow(startMinimized);
|
splash = createSplashWindow(startMinimized);
|
||||||
|
|
||||||
// SteamOS letterboxes and scales it terribly, so just full screen it
|
// SteamOS letterboxes and scales it terribly, so just full screen it
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { desktopCapturer, session, Streams } from "electron";
|
import { desktopCapturer, session, Streams } from "electron";
|
||||||
|
import { release } from "os";
|
||||||
import type { StreamPick } from "renderer/components/ScreenSharePicker";
|
import type { StreamPick } from "renderer/components/ScreenSharePicker";
|
||||||
import { IpcCommands, IpcEvents } from "shared/IpcEvents";
|
import { IpcCommands, IpcEvents } from "shared/IpcEvents";
|
||||||
|
|
||||||
@@ -14,6 +15,8 @@ import { handle } from "./utils/ipcWrappers";
|
|||||||
const isWayland =
|
const isWayland =
|
||||||
process.platform === "linux" && (process.env.XDG_SESSION_TYPE === "wayland" || !!process.env.WAYLAND_DISPLAY);
|
process.platform === "linux" && (process.env.XDG_SESSION_TYPE === "wayland" || !!process.env.WAYLAND_DISPLAY);
|
||||||
|
|
||||||
|
const supportsLoopbackWithoutChrome = process.platform === "win32" && Number(release().split(".").pop()) >= 19045;
|
||||||
|
|
||||||
export function registerScreenShareHandler() {
|
export function registerScreenShareHandler() {
|
||||||
handle(IpcEvents.CAPTURER_GET_LARGE_THUMBNAIL, async (_, id: string) => {
|
handle(IpcEvents.CAPTURER_GET_LARGE_THUMBNAIL, async (_, id: string) => {
|
||||||
const sources = await desktopCapturer.getSources({
|
const sources = await desktopCapturer.getSources({
|
||||||
@@ -78,7 +81,10 @@ export function registerScreenShareHandler() {
|
|||||||
const streams: Streams = {
|
const streams: Streams = {
|
||||||
video: source
|
video: source
|
||||||
};
|
};
|
||||||
if (choice.audio && process.platform === "win32") streams.audio = "loopback";
|
if (choice.audio && process.platform === "win32") {
|
||||||
|
// @ts-expect-error loopbackWithoutChrome is real but not documented
|
||||||
|
streams.audio = supportsLoopbackWithoutChrome ? "loopbackWithoutChrome" : "loopback";
|
||||||
|
}
|
||||||
|
|
||||||
callback(streams);
|
callback(streams);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -7,15 +7,17 @@
|
|||||||
import { type Settings as TVencordSettings } from "@vencord/types/Vencord";
|
import { type Settings as TVencordSettings } from "@vencord/types/Vencord";
|
||||||
import { mkdirSync, readFileSync, writeFileSync } from "fs";
|
import { mkdirSync, readFileSync, writeFileSync } from "fs";
|
||||||
import { dirname, join } from "path";
|
import { dirname, join } from "path";
|
||||||
|
import { DefaultVesktopSettings } from "shared/defaultSettings";
|
||||||
import type { Settings as TSettings, State as TState } from "shared/settings";
|
import type { Settings as TSettings, State as TState } from "shared/settings";
|
||||||
import { SettingsStore } from "shared/utils/SettingsStore";
|
import { SettingsStore } from "shared/utils/SettingsStore";
|
||||||
|
|
||||||
import { DATA_DIR, VENCORD_SETTINGS_FILE } from "./constants";
|
import { DATA_DIR, VENCORD_SETTINGS_FILE } from "./constants";
|
||||||
|
import { mergeDefaults } from "./utils/mergeDefaults";
|
||||||
|
|
||||||
const SETTINGS_FILE = join(DATA_DIR, "settings.json");
|
const SETTINGS_FILE = join(DATA_DIR, "settings.json");
|
||||||
const STATE_FILE = join(DATA_DIR, "state.json");
|
const STATE_FILE = join(DATA_DIR, "state.json");
|
||||||
|
|
||||||
function loadSettings<T extends object = any>(file: string, name: string) {
|
function loadSettings<T extends object = any>(file: string, name: string, defaults?: T) {
|
||||||
let settings = {} as T;
|
let settings = {} as T;
|
||||||
try {
|
try {
|
||||||
const content = readFileSync(file, "utf8");
|
const content = readFileSync(file, "utf8");
|
||||||
@@ -26,6 +28,10 @@ function loadSettings<T extends object = any>(file: string, name: string) {
|
|||||||
}
|
}
|
||||||
} catch {}
|
} catch {}
|
||||||
|
|
||||||
|
if (defaults) {
|
||||||
|
mergeDefaults(settings, defaults);
|
||||||
|
}
|
||||||
|
|
||||||
const store = new SettingsStore(settings);
|
const store = new SettingsStore(settings);
|
||||||
store.addGlobalChangeListener(o => {
|
store.addGlobalChangeListener(o => {
|
||||||
try {
|
try {
|
||||||
@@ -39,6 +45,6 @@ function loadSettings<T extends object = any>(file: string, name: string) {
|
|||||||
return store;
|
return store;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const Settings = loadSettings<TSettings>(SETTINGS_FILE, "Vesktop settings");
|
export const Settings = loadSettings<TSettings>(SETTINGS_FILE, "Vesktop settings", DefaultVesktopSettings);
|
||||||
export const VencordSettings = loadSettings<TVencordSettings>(VENCORD_SETTINGS_FILE, "Vencord settings");
|
export const VencordSettings = loadSettings<TVencordSettings>(VENCORD_SETTINGS_FILE, "Vencord settings");
|
||||||
export const State = loadSettings<TState>(STATE_FILE, "Vesktop state");
|
export const State = loadSettings<TState>(STATE_FILE, "Vesktop state");
|
||||||
|
|||||||
+1
-1
@@ -26,7 +26,7 @@ export function createSplashWindow(startMinimized = false) {
|
|||||||
|
|
||||||
const { splashBackground, splashColor, splashTheming, splashPixelated } = Settings.store;
|
const { splashBackground, splashColor, splashTheming, splashPixelated } = Settings.store;
|
||||||
|
|
||||||
if (splashTheming !== false) {
|
if (splashTheming) {
|
||||||
if (splashColor) {
|
if (splashColor) {
|
||||||
const semiTransparentSplashColor = splashColor.replace("rgb(", "rgba(").replace(")", ", 0.2)");
|
const semiTransparentSplashColor = splashColor.replace("rgb(", "rgba(").replace(")", ", 0.2)");
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,24 @@
|
|||||||
|
/*
|
||||||
|
* Vesktop, a desktop app aiming to give you a snappier Discord Experience
|
||||||
|
* Copyright (c) 2026 Vendicated and Vesktop contributors
|
||||||
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Recursively merges defaults into an object and returns the same object
|
||||||
|
* @param obj Object
|
||||||
|
* @param defaults Defaults
|
||||||
|
* @returns obj
|
||||||
|
*/
|
||||||
|
export function mergeDefaults<T>(obj: T, defaults: T): T {
|
||||||
|
for (const key in defaults) {
|
||||||
|
const v = defaults[key];
|
||||||
|
if (typeof v === "object" && !Array.isArray(v) && v !== null) {
|
||||||
|
obj[key] ??= {} as any;
|
||||||
|
mergeDefaults(obj[key], v);
|
||||||
|
} else {
|
||||||
|
obj[key] ??= v;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return obj;
|
||||||
|
}
|
||||||
@@ -37,7 +37,7 @@ const DEFAULT_POPOUT_OPTIONS: BrowserWindowConstructorOptions = {
|
|||||||
backgroundColor: "#2f3136",
|
backgroundColor: "#2f3136",
|
||||||
minWidth: MIN_POPOUT_WIDTH,
|
minWidth: MIN_POPOUT_WIDTH,
|
||||||
minHeight: MIN_POPOUT_HEIGHT,
|
minHeight: MIN_POPOUT_HEIGHT,
|
||||||
frame: Settings.store.customTitleBar !== true,
|
frame: Settings.store.nativeTitleBar,
|
||||||
titleBarStyle: process.platform === "darwin" ? "hidden" : undefined,
|
titleBarStyle: process.platform === "darwin" ? "hidden" : undefined,
|
||||||
trafficLightPosition:
|
trafficLightPosition:
|
||||||
process.platform === "darwin"
|
process.platform === "darwin"
|
||||||
|
|||||||
@@ -64,7 +64,7 @@ const existsAsync = (path: string) =>
|
|||||||
.catch(() => false);
|
.catch(() => false);
|
||||||
|
|
||||||
export async function isValidVencordInstall(dir: string) {
|
export async function isValidVencordInstall(dir: string) {
|
||||||
const results = await Promise.all(["package.json", ...FILES_TO_DOWNLOAD].map(f => existsAsync(join(dir, f))));
|
const results = await Promise.all(FILES_TO_DOWNLOAD.map(f => existsAsync(join(dir, f))));
|
||||||
return !results.includes(false);
|
return !results.includes(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+38
-48
@@ -66,70 +66,60 @@ function getRendererAudioServicePid() {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function buildLinkData({ include, exclude }: { include?: Node[]; exclude?: Node[] }) {
|
||||||
|
const pid = getRendererAudioServicePid();
|
||||||
|
|
||||||
|
const { mute, ignoreDevices, ignoreInputMedia, ignoreVirtual, workaround, onlySpeakers, onlyDefaultSpeakers } =
|
||||||
|
Settings.store.audio ?? {};
|
||||||
|
|
||||||
|
const data: LinkData = {
|
||||||
|
mute: mute ?? true,
|
||||||
|
include: include ?? [],
|
||||||
|
exclude: exclude ?? [],
|
||||||
|
only_speakers: onlySpeakers,
|
||||||
|
ignore_devices: ignoreDevices,
|
||||||
|
only_default_speakers: onlyDefaultSpeakers
|
||||||
|
};
|
||||||
|
|
||||||
|
data.exclude.push({ "application.process.id": pid });
|
||||||
|
|
||||||
|
if (ignoreInputMedia ?? true) {
|
||||||
|
data.exclude.push({ "media.class": "Stream/Input/Audio" });
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ignoreVirtual) {
|
||||||
|
data.exclude.push({ "node.virtual": "true" });
|
||||||
|
}
|
||||||
|
|
||||||
|
if (workaround) {
|
||||||
|
data.workaround = [{ "application.process.id": pid, "media.name": "RecordStream" }];
|
||||||
|
}
|
||||||
|
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
ipcMain.handle(IpcEvents.VIRT_MIC_LIST, () => {
|
ipcMain.handle(IpcEvents.VIRT_MIC_LIST, () => {
|
||||||
const audioPid = getRendererAudioServicePid();
|
const audioPid = getRendererAudioServicePid();
|
||||||
|
|
||||||
const { granularSelect } = Settings.store.audio ?? {};
|
const { granularSelect } = Settings.store.audio ?? {};
|
||||||
|
|
||||||
const targets = obtainVenmic()
|
const targets = obtainVenmic()
|
||||||
?.list(granularSelect ? ["node.name"] : undefined)
|
?.list(granularSelect ? [] : ["application.name", "node.name"])
|
||||||
.filter(s => s["application.process.id"] !== audioPid);
|
.filter(s => s["application.process.id"] !== audioPid);
|
||||||
|
|
||||||
return targets ? { ok: true, targets, hasPipewirePulse } : { ok: false, isGlibCxxOutdated };
|
return targets ? { ok: true, targets, hasPipewirePulse } : { ok: false, isGlibCxxOutdated };
|
||||||
});
|
});
|
||||||
|
|
||||||
ipcMain.handle(IpcEvents.VIRT_MIC_START, (_, include: Node[]) => {
|
ipcMain.handle(IpcEvents.VIRT_MIC_START, (_, include: Node[]) => {
|
||||||
const pid = getRendererAudioServicePid();
|
return obtainVenmic()?.link(buildLinkData({ include }));
|
||||||
const { ignoreDevices, ignoreInputMedia, ignoreVirtual, workaround } = Settings.store.audio ?? {};
|
|
||||||
|
|
||||||
const data: LinkData = {
|
|
||||||
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 = [{ "application.process.id": pid, "media.name": "RecordStream" }];
|
|
||||||
}
|
|
||||||
|
|
||||||
return obtainVenmic()?.link(data);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
ipcMain.handle(IpcEvents.VIRT_MIC_START_SYSTEM, (_, exclude: Node[]) => {
|
ipcMain.handle(IpcEvents.VIRT_MIC_START_SYSTEM, (_, exclude: Node[]) => {
|
||||||
const pid = getRendererAudioServicePid();
|
return obtainVenmic()?.link(buildLinkData({ exclude }));
|
||||||
|
});
|
||||||
|
|
||||||
const { workaround, ignoreDevices, ignoreInputMedia, ignoreVirtual, onlySpeakers, onlyDefaultSpeakers } =
|
ipcMain.handle(IpcEvents.VIRT_MIC_UNMUTE, () => {
|
||||||
Settings.store.audio ?? {};
|
return obtainVenmic()?.unmute();
|
||||||
|
|
||||||
const data: LinkData = {
|
|
||||||
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 = [{ "application.process.id": pid, "media.name": "RecordStream" }];
|
|
||||||
}
|
|
||||||
|
|
||||||
return obtainVenmic()?.link(data);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
ipcMain.handle(IpcEvents.VIRT_MIC_STOP, () => obtainVenmic()?.unlink());
|
ipcMain.handle(IpcEvents.VIRT_MIC_STOP, () => obtainVenmic()?.unlink());
|
||||||
|
|||||||
@@ -23,3 +23,16 @@ app.whenReady().then(() => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
protocol.registerSchemesAsPrivileged([
|
||||||
|
{
|
||||||
|
scheme: "vesktop",
|
||||||
|
privileges: {
|
||||||
|
standard: true,
|
||||||
|
secure: true,
|
||||||
|
supportFetchAPI: true,
|
||||||
|
corsEnabled: true,
|
||||||
|
stream: true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]);
|
||||||
|
|||||||
@@ -92,6 +92,7 @@ export const VesktopNative = {
|
|||||||
>(IpcEvents.VIRT_MIC_LIST),
|
>(IpcEvents.VIRT_MIC_LIST),
|
||||||
start: (include: Node[]) => invoke<void>(IpcEvents.VIRT_MIC_START, include),
|
start: (include: Node[]) => invoke<void>(IpcEvents.VIRT_MIC_START, include),
|
||||||
startSystem: (exclude: Node[]) => invoke<void>(IpcEvents.VIRT_MIC_START_SYSTEM, exclude),
|
startSystem: (exclude: Node[]) => invoke<void>(IpcEvents.VIRT_MIC_START_SYSTEM, exclude),
|
||||||
|
unmute: () => invoke<void>(IpcEvents.VIRT_MIC_UNMUTE),
|
||||||
stop: () => invoke<void>(IpcEvents.VIRT_MIC_STOP)
|
stop: () => invoke<void>(IpcEvents.VIRT_MIC_STOP)
|
||||||
},
|
},
|
||||||
clipboard: {
|
clipboard: {
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ let GuildReadStateStore: any;
|
|||||||
let NotificationSettingsStore: any;
|
let NotificationSettingsStore: any;
|
||||||
|
|
||||||
export function setBadge() {
|
export function setBadge() {
|
||||||
if (Settings.store.appBadge === false) return;
|
if (!Settings.store.appBadge) return;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const mentionCount = GuildReadStateStore.getTotalMentionCount();
|
const mentionCount = GuildReadStateStore.getTotalMentionCount();
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ import "./screenSharePicker.css";
|
|||||||
|
|
||||||
import { classNameFactory } from "@vencord/types/api/Styles";
|
import { classNameFactory } from "@vencord/types/api/Styles";
|
||||||
import {
|
import {
|
||||||
BaseText,
|
|
||||||
Button,
|
Button,
|
||||||
Card,
|
Card,
|
||||||
CogWheel,
|
CogWheel,
|
||||||
@@ -20,18 +19,18 @@ import {
|
|||||||
RestartIcon,
|
RestartIcon,
|
||||||
Span
|
Span
|
||||||
} from "@vencord/types/components";
|
} from "@vencord/types/components";
|
||||||
|
import { Logger, useAwaiter, useForceUpdater } from "@vencord/types/utils";
|
||||||
|
import { onceReady } from "@vencord/types/webpack";
|
||||||
import {
|
import {
|
||||||
closeModal,
|
closeModal,
|
||||||
Logger,
|
FluxDispatcher,
|
||||||
ModalCloseButton,
|
MediaEngineStore,
|
||||||
Modals,
|
Modal,
|
||||||
ModalSize,
|
|
||||||
openModal,
|
openModal,
|
||||||
useAwaiter,
|
Select,
|
||||||
useForceUpdater
|
UserStore,
|
||||||
} from "@vencord/types/utils";
|
useState
|
||||||
import { onceReady } from "@vencord/types/webpack";
|
} from "@vencord/types/webpack/common";
|
||||||
import { FluxDispatcher, MediaEngineStore, Select, UserStore, useState } from "@vencord/types/webpack/common";
|
|
||||||
import { Node } from "@vencord/venmic";
|
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";
|
||||||
@@ -122,15 +121,25 @@ addPatch({
|
|||||||
|
|
||||||
if (isLinux) {
|
if (isLinux) {
|
||||||
onceReady.then(() => {
|
onceReady.then(() => {
|
||||||
FluxDispatcher.subscribe("STREAM_CLOSE", ({ streamKey }: { streamKey: string }) => {
|
const ownsStream = (streamKey: string) => {
|
||||||
const owner = streamKey.split(":").at(-1);
|
return streamKey.split(":").at(-1) === UserStore.getCurrentUser().id;
|
||||||
|
};
|
||||||
|
|
||||||
if (owner !== UserStore.getCurrentUser().id) {
|
FluxDispatcher.subscribe("STREAM_CLOSE", ({ streamKey }: { streamKey: string }) => {
|
||||||
|
if (!ownsStream(streamKey)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
VesktopNative.virtmic.stop();
|
VesktopNative.virtmic.stop();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
FluxDispatcher.subscribe("STREAM_UPDATE", ({ streamKey }: { streamKey: string }) => {
|
||||||
|
if (!ownsStream(streamKey)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
VesktopNative.virtmic.unmute();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -210,15 +219,20 @@ function AudioSettingsModal({
|
|||||||
const Settings = useSettings();
|
const Settings = useSettings();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Modals.ModalRoot {...modalProps} size={ModalSize.MEDIUM}>
|
<Modal
|
||||||
<Modals.ModalHeader className={cl("header")}>
|
{...modalProps}
|
||||||
<BaseText size="lg" weight="semibold" tag="h3" style={{ flexGrow: 1 }}>
|
size="lg"
|
||||||
Audio Settings
|
title="Audio Settings"
|
||||||
</BaseText>
|
actions={[{ text: "Back", variant: "secondary", onClick: close }]}
|
||||||
<ModalCloseButton onClick={close} />
|
>
|
||||||
</Modals.ModalHeader>
|
<div className={cl("venmic-settings")}>
|
||||||
|
<FormSwitch
|
||||||
<Modals.ModalContent className={cl("modal", "venmic-settings")}>
|
title="Initial Mute"
|
||||||
|
description="Fix an initial audio spike caused by chromium."
|
||||||
|
hideBorder
|
||||||
|
onChange={v => (Settings.audio = { ...Settings.audio, mute: v })}
|
||||||
|
value={Settings.audio?.mute ?? true}
|
||||||
|
/>
|
||||||
<FormSwitch
|
<FormSwitch
|
||||||
title="Microphone Workaround"
|
title="Microphone Workaround"
|
||||||
description="Work around an issue that causes the microphone to be shared instead of the correct audio. Only enable if you're experiencing this issue."
|
description="Work around an issue that causes the microphone to be shared instead of the correct audio. Only enable if you're experiencing this issue."
|
||||||
@@ -302,13 +316,8 @@ function AudioSettingsModal({
|
|||||||
value={Settings.audio?.deviceSelect ?? false}
|
value={Settings.audio?.deviceSelect ?? false}
|
||||||
disabled={Settings.audio?.ignoreDevices}
|
disabled={Settings.audio?.ignoreDevices}
|
||||||
/>
|
/>
|
||||||
</Modals.ModalContent>
|
</div>
|
||||||
<Modals.ModalFooter className={cl("footer")}>
|
</Modal>
|
||||||
<Button variant="secondary" onClick={close}>
|
|
||||||
Back
|
|
||||||
</Button>
|
|
||||||
</Modals.ModalFooter>
|
|
||||||
</Modals.ModalRoot>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -455,78 +464,61 @@ function isSpecialSource(value?: AudioSource | AudioSources): value is SpecialSo
|
|||||||
return typeof value === "string";
|
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[] {
|
function mapToAudioItem(node: AudioSource, granularSelect?: boolean, deviceSelect?: boolean): AudioItem[] {
|
||||||
if (isSpecialSource(node)) {
|
if (isSpecialSource(node)) {
|
||||||
return [{ name: node, value: node }];
|
return [{ name: node, value: node }];
|
||||||
}
|
}
|
||||||
|
|
||||||
const rtn: AudioItem[] = [];
|
|
||||||
|
|
||||||
const mediaClass = node["media.class"];
|
const mediaClass = node["media.class"];
|
||||||
|
|
||||||
if (mediaClass?.includes("Video") || mediaClass?.includes("Midi")) {
|
if (mediaClass?.includes("Video") || mediaClass?.includes("Midi")) {
|
||||||
return rtn;
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!deviceSelect && node["device.id"]) {
|
if (!deviceSelect && node["device.id"]) {
|
||||||
return rtn;
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
const name = node["application.name"];
|
const preferred = ["application.name", "node.description", "node.name", "application.process.binary"] as const;
|
||||||
|
const prop = preferred.find(prop => node[prop]);
|
||||||
|
|
||||||
if (name) {
|
if (!prop) {
|
||||||
rtn.push({ name: name, value: { "application.name": name } });
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const name = node[prop]!;
|
||||||
|
const items: AudioItem[] = [{ name: name, value: { [prop]: name } }];
|
||||||
|
|
||||||
if (!granularSelect) {
|
if (!granularSelect) {
|
||||||
return rtn;
|
return items;
|
||||||
}
|
}
|
||||||
|
|
||||||
const rawName = node["node.name"];
|
let granularName = name;
|
||||||
|
const granularProps: Node = { [prop]: name };
|
||||||
|
|
||||||
if (!name) {
|
const append = (name: string, brackets: string) => {
|
||||||
rtn.push({ name: rawName, value: { "node.name": rawName } });
|
const value = node[name];
|
||||||
|
|
||||||
|
if (!value) {
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const binary = node["application.process.binary"];
|
granularName += ` ${brackets[0]}${value}${brackets[1]}`;
|
||||||
|
granularProps[name] = value;
|
||||||
|
};
|
||||||
|
|
||||||
if (!name && binary) {
|
append("application.process.id", "<>");
|
||||||
rtn.push({ name: binary, value: { "application.process.binary": binary } });
|
append("application.process.binary", "()");
|
||||||
|
append("media.name", "[]");
|
||||||
|
|
||||||
|
items.push({ name: granularName, value: granularProps });
|
||||||
|
|
||||||
|
return items;
|
||||||
}
|
}
|
||||||
|
|
||||||
const pid = node["application.process.id"];
|
function hasMatchingProps(value: Node, other: Node) {
|
||||||
|
const keys = Object.keys(value);
|
||||||
const first = rtn[0];
|
return keys.length === Object.keys(other).length && keys.every(key => value[key] === other[key]);
|
||||||
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) {
|
function isItemSelected(sources?: AudioSources) {
|
||||||
@@ -655,9 +647,10 @@ function AudioSourcePickerLinux({
|
|||||||
}))}
|
}))}
|
||||||
isSelected={isItemSelected(includeSources)}
|
isSelected={isItemSelected(includeSources)}
|
||||||
select={updateItems(setIncludeSources, includeSources)}
|
select={updateItems(setIncludeSources, includeSources)}
|
||||||
serialize={String}
|
serialize={JSON.stringify}
|
||||||
popoutPosition="top"
|
popoutPosition="top"
|
||||||
closeOnSelect={false}
|
closeOnSelect={false}
|
||||||
|
renderOptionLabel={o => o.label}
|
||||||
/>
|
/>
|
||||||
</SimpleErrorBoundary>
|
</SimpleErrorBoundary>
|
||||||
</section>
|
</section>
|
||||||
@@ -675,9 +668,10 @@ function AudioSourcePickerLinux({
|
|||||||
}))}
|
}))}
|
||||||
isSelected={isItemSelected(excludeSources)}
|
isSelected={isItemSelected(excludeSources)}
|
||||||
select={updateItems(setExcludeSources, excludeSources)}
|
select={updateItems(setExcludeSources, excludeSources)}
|
||||||
serialize={String}
|
serialize={JSON.stringify}
|
||||||
popoutPosition="top"
|
popoutPosition="top"
|
||||||
closeOnSelect={false}
|
closeOnSelect={false}
|
||||||
|
renderOptionLabel={o => o.label}
|
||||||
/>
|
/>
|
||||||
</SimpleErrorBoundary>
|
</SimpleErrorBoundary>
|
||||||
</section>
|
</section>
|
||||||
@@ -721,30 +715,7 @@ function ModalComponent({
|
|||||||
frameRate: "30"
|
frameRate: "30"
|
||||||
});
|
});
|
||||||
|
|
||||||
return (
|
function handleGoLive() {
|
||||||
<Modals.ModalRoot {...modalProps} size={ModalSize.MEDIUM}>
|
|
||||||
<Modals.ModalHeader className={cl("header")}>
|
|
||||||
<BaseText size="lg" weight="semibold" tag="h3" style={{ flexGrow: 1 }}>
|
|
||||||
Screen Share Picker
|
|
||||||
</BaseText>
|
|
||||||
<ModalCloseButton onClick={close} />
|
|
||||||
</Modals.ModalHeader>
|
|
||||||
<Modals.ModalContent className={cl("modal")}>
|
|
||||||
{!selected ? (
|
|
||||||
<ScreenPicker screens={screens} chooseScreen={setSelected} />
|
|
||||||
) : (
|
|
||||||
<StreamSettingsUi
|
|
||||||
source={screens.find(s => s.id === selected)!}
|
|
||||||
settings={settings}
|
|
||||||
setSettings={setSettings}
|
|
||||||
skipPicker={skipPicker}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</Modals.ModalContent>
|
|
||||||
<Modals.ModalFooter className={cl("footer")}>
|
|
||||||
<Button
|
|
||||||
disabled={!selected}
|
|
||||||
onClick={() => {
|
|
||||||
currentSettings = settings;
|
currentSettings = settings;
|
||||||
try {
|
try {
|
||||||
const frameRate = Number(qualitySettings.frameRate);
|
const frameRate = Number(qualitySettings.frameRate);
|
||||||
@@ -788,10 +759,7 @@ function ModalComponent({
|
|||||||
try {
|
try {
|
||||||
await track.applyConstraints(constraints);
|
await track.applyConstraints(constraints);
|
||||||
|
|
||||||
logger.info(
|
logger.info("Applied constraints successfully. New constraints:", track.getConstraints());
|
||||||
"Applied constraints successfully. New constraints:",
|
|
||||||
track.getConstraints()
|
|
||||||
);
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
logger.error("Failed to apply constraints.", e);
|
logger.error("Failed to apply constraints.", e);
|
||||||
}
|
}
|
||||||
@@ -801,21 +769,37 @@ function ModalComponent({
|
|||||||
}
|
}
|
||||||
|
|
||||||
close();
|
close();
|
||||||
}}
|
}
|
||||||
>
|
|
||||||
Go Live
|
|
||||||
</Button>
|
|
||||||
|
|
||||||
{selected && !skipPicker ? (
|
const showGoBack = selected && !skipPicker;
|
||||||
<Button variant="secondary" onClick={() => setSelected(void 0)}>
|
return (
|
||||||
Back
|
<Modal
|
||||||
</Button>
|
{...modalProps}
|
||||||
|
size="lg"
|
||||||
|
title="Screen Share Picker"
|
||||||
|
actions={[
|
||||||
|
{
|
||||||
|
text: showGoBack ? "Back" : "Cancel",
|
||||||
|
variant: "secondary",
|
||||||
|
onClick: () => (showGoBack ? setSelected(void 0) : close())
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: "Go Live",
|
||||||
|
disabled: !selected,
|
||||||
|
onClick: handleGoLive
|
||||||
|
}
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
{!selected ? (
|
||||||
|
<ScreenPicker screens={screens} chooseScreen={setSelected} />
|
||||||
) : (
|
) : (
|
||||||
<Button variant="secondary" onClick={close}>
|
<StreamSettingsUi
|
||||||
Cancel
|
source={screens.find(s => s.id === selected)!}
|
||||||
</Button>
|
settings={settings}
|
||||||
|
setSettings={setSettings}
|
||||||
|
skipPicker={skipPicker}
|
||||||
|
/>
|
||||||
)}
|
)}
|
||||||
</Modals.ModalFooter>
|
</Modal>
|
||||||
</Modals.ModalRoot>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,3 @@
|
|||||||
.vcd-screen-picker-modal {
|
|
||||||
padding: 1em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.vcd-screen-picker-header-close-button {
|
.vcd-screen-picker-header-close-button {
|
||||||
margin-left: auto;
|
margin-left: auto;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ export const AutoStartToggle: SettingsComponent = ({ settings }) => {
|
|||||||
<VesktopSettingsSwitch
|
<VesktopSettingsSwitch
|
||||||
title="Auto Start Minimized"
|
title="Auto Start Minimized"
|
||||||
description={"Start Vesktop minimized when starting with system"}
|
description={"Start Vesktop minimized when starting with system"}
|
||||||
value={settings.autoStartMinimized ?? false}
|
value={settings.autoStartMinimized}
|
||||||
onChange={v => (settings.autoStartMinimized = v)}
|
onChange={v => (settings.autoStartMinimized = v)}
|
||||||
disabled={!autoStartEnabled}
|
disabled={!autoStartEnabled}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -4,18 +4,9 @@
|
|||||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { BaseText, Button, Heading, Paragraph, TextButton } from "@vencord/types/components";
|
import { Button, Heading, Paragraph, TextButton } from "@vencord/types/components";
|
||||||
import {
|
import { Margins, useForceUpdater } from "@vencord/types/utils";
|
||||||
Margins,
|
import { Modal, openModal, Toasts } from "@vencord/types/webpack/common";
|
||||||
ModalCloseButton,
|
|
||||||
ModalContent,
|
|
||||||
ModalHeader,
|
|
||||||
ModalRoot,
|
|
||||||
ModalSize,
|
|
||||||
openModal,
|
|
||||||
useForceUpdater
|
|
||||||
} from "@vencord/types/utils";
|
|
||||||
import { Toasts } from "@vencord/types/webpack/common";
|
|
||||||
import { Settings } from "shared/settings";
|
import { Settings } from "shared/settings";
|
||||||
|
|
||||||
import { cl, SettingsComponent } from "./Settings";
|
import { cl, SettingsComponent } from "./Settings";
|
||||||
@@ -26,20 +17,11 @@ export const DeveloperOptionsButton: SettingsComponent = ({ settings }) => {
|
|||||||
|
|
||||||
function openDeveloperOptionsModal(settings: Settings) {
|
function openDeveloperOptionsModal(settings: Settings) {
|
||||||
openModal(props => (
|
openModal(props => (
|
||||||
<ModalRoot {...props} size={ModalSize.MEDIUM}>
|
<Modal {...props} size="lg" title="Vesktop Developer Options">
|
||||||
<ModalHeader>
|
<Heading tag="h4">Vencord Location</Heading>
|
||||||
<BaseText size="lg" weight="semibold" tag="h3" style={{ flexGrow: 1 }}>
|
|
||||||
Vesktop Developer Options
|
|
||||||
</BaseText>
|
|
||||||
<ModalCloseButton onClick={props.onClose} />
|
|
||||||
</ModalHeader>
|
|
||||||
|
|
||||||
<ModalContent>
|
|
||||||
<div style={{ padding: "1em 0" }}>
|
|
||||||
<Heading tag="h5">Vencord Location</Heading>
|
|
||||||
<VencordLocationPicker settings={settings} />
|
<VencordLocationPicker settings={settings} />
|
||||||
|
|
||||||
<Heading tag="h5" className={Margins.top16}>
|
<Heading tag="h4" className={Margins.top16}>
|
||||||
Debugging
|
Debugging
|
||||||
</Heading>
|
</Heading>
|
||||||
<div className={cl("button-grid")}>
|
<div className={cl("button-grid")}>
|
||||||
@@ -48,9 +30,7 @@ function openDeveloperOptionsModal(settings: Settings) {
|
|||||||
Open chrome://webrtc-internals
|
Open chrome://webrtc-internals
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</Modal>
|
||||||
</ModalContent>
|
|
||||||
</ModalRoot>
|
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ export const NotificationBadgeToggle: SettingsComponent = ({ settings }) => {
|
|||||||
<VesktopSettingsSwitch
|
<VesktopSettingsSwitch
|
||||||
title="Notification Badge"
|
title="Notification Badge"
|
||||||
description="Show mention badge on the app icon"
|
description="Show mention badge on the app icon"
|
||||||
value={settings.appBadge ?? true}
|
value={settings.appBadge}
|
||||||
onChange={v => {
|
onChange={v => {
|
||||||
settings.appBadge = v;
|
settings.appBadge = v;
|
||||||
if (v) setBadge();
|
if (v) setBadge();
|
||||||
|
|||||||
@@ -11,8 +11,8 @@ import { BaseText, Divider, ErrorBoundary } from "@vencord/types/components";
|
|||||||
import { ComponentType } from "react";
|
import { ComponentType } from "react";
|
||||||
import { WebRTCIPHandlingPolicyPicker } from "renderer/components/settings/WebRTCIPHandlingPolicyPicker";
|
import { WebRTCIPHandlingPolicyPicker } from "renderer/components/settings/WebRTCIPHandlingPolicyPicker";
|
||||||
import { KeybindsButton } from "renderer/globalShortcuts/ShortcutSettings";
|
import { KeybindsButton } from "renderer/globalShortcuts/ShortcutSettings";
|
||||||
import { Settings, useSettings } from "renderer/settings";
|
import { getValueAndOnChange, Settings, useSettings } from "renderer/settings";
|
||||||
import { isMac, isWindows } from "renderer/utils";
|
import { isMac } from "renderer/utils";
|
||||||
|
|
||||||
import { AutoStartToggle } from "./AutoStartToggle";
|
import { AutoStartToggle } from "./AutoStartToggle";
|
||||||
import { DeveloperOptionsButton } from "./DeveloperOptions";
|
import { DeveloperOptionsButton } from "./DeveloperOptions";
|
||||||
@@ -27,7 +27,6 @@ interface BooleanSetting {
|
|||||||
key: keyof typeof Settings.store;
|
key: keyof typeof Settings.store;
|
||||||
title: string;
|
title: string;
|
||||||
description: string;
|
description: string;
|
||||||
defaultValue: boolean;
|
|
||||||
disabled?(): boolean;
|
disabled?(): boolean;
|
||||||
invisible?(): boolean;
|
invisible?(): boolean;
|
||||||
}
|
}
|
||||||
@@ -43,50 +42,55 @@ const SettingsOptions: Record<string, Array<BooleanSetting | SettingsComponent>>
|
|||||||
{
|
{
|
||||||
key: "hardwareAcceleration",
|
key: "hardwareAcceleration",
|
||||||
title: "Hardware Acceleration",
|
title: "Hardware Acceleration",
|
||||||
description: "Enable hardware acceleration",
|
description: "Enable hardware acceleration"
|
||||||
defaultValue: true
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: "hardwareVideoAcceleration",
|
key: "hardwareVideoAcceleration",
|
||||||
title: "Video Hardware Acceleration",
|
title: "Video Hardware Acceleration",
|
||||||
description:
|
description:
|
||||||
"Enable hardware video acceleration. This can improve performance of screenshare and video playback, but may cause graphical glitches and infinitely loading streams.",
|
"Enable hardware video acceleration. This can improve performance of screenshare and video playback, but may cause graphical glitches and infinitely loading streams.",
|
||||||
defaultValue: false,
|
disabled: () => !Settings.store.hardwareAcceleration
|
||||||
disabled: () => Settings.store.hardwareAcceleration === false
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"User Interface": [
|
"User Interface": [
|
||||||
{
|
{
|
||||||
key: "customTitleBar",
|
key: "nativeTitleBar",
|
||||||
title: "Discord Titlebar",
|
title: "Native Titlebar",
|
||||||
description: "Use Discord's custom title bar instead of the native system one. Requires a full restart.",
|
description: "Enable the system titlebar in addition to Discord's custom one. Requires a full restart."
|
||||||
defaultValue: isWindows
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: "staticTitle",
|
key: "staticTitle",
|
||||||
title: "Static Title",
|
title: "Static Title",
|
||||||
description: 'Makes the window title "Vesktop" instead of changing to the current page',
|
description: 'Makes the window title "Vesktop" instead of changing to the current page'
|
||||||
defaultValue: false
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: "enableMenu",
|
key: "enableMenu",
|
||||||
title: "Enable Menu Bar",
|
title: "Enable Menu Bar",
|
||||||
description: "Enables the application menu bar. Press ALT to toggle visibility.",
|
description: "Enables the application menu bar. Press ALT to toggle visibility.",
|
||||||
defaultValue: false,
|
disabled: () => !Settings.store.nativeTitleBar
|
||||||
disabled: () => Settings.store.customTitleBar ?? isWindows
|
},
|
||||||
|
{
|
||||||
|
key: "enableShadow",
|
||||||
|
title: "Enable Window Shadow",
|
||||||
|
description: "Enables the window shadow. Requires a full restart.",
|
||||||
|
disabled: () => Settings.store.nativeTitleBar
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "enableRoundedCorners",
|
||||||
|
title: "Enable Rounded Corners",
|
||||||
|
description: "Enables rounded corners. Requires a full restart.",
|
||||||
|
disabled: () => Settings.store.nativeTitleBar
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: "enableSplashScreen",
|
key: "enableSplashScreen",
|
||||||
title: "Enable Splash Screen",
|
title: "Enable Splash Screen",
|
||||||
description:
|
description:
|
||||||
"Shows a small splash screen while Vesktop is loading. Disabling this option will show the main window earlier while it's still loading.",
|
"Shows a small splash screen while Vesktop is loading. Disabling this option will show the main window earlier while it's still loading."
|
||||||
defaultValue: true
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: "splashTheming",
|
key: "splashTheming",
|
||||||
title: "Splash theming",
|
title: "Splash theming",
|
||||||
description: "Adapt the splash window colors to your custom theme",
|
description: "Adapt the splash window colors to your custom theme"
|
||||||
defaultValue: true
|
|
||||||
},
|
},
|
||||||
WindowsTransparencyControls,
|
WindowsTransparencyControls,
|
||||||
UserAssetsButton
|
UserAssetsButton
|
||||||
@@ -96,34 +100,29 @@ const SettingsOptions: Record<string, Array<BooleanSetting | SettingsComponent>>
|
|||||||
key: "tray",
|
key: "tray",
|
||||||
title: "Tray Icon",
|
title: "Tray Icon",
|
||||||
description: "Add a tray icon for Vesktop",
|
description: "Add a tray icon for Vesktop",
|
||||||
defaultValue: true,
|
|
||||||
invisible: () => isMac
|
invisible: () => isMac
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: "minimizeToTray",
|
key: "minimizeToTray",
|
||||||
title: "Minimize to tray",
|
title: "Minimize to tray",
|
||||||
description: "Hitting X will make Vesktop minimize to the tray instead of closing",
|
description: "Hitting X will make Vesktop minimize to the tray instead of closing",
|
||||||
defaultValue: true,
|
|
||||||
invisible: () => isMac,
|
invisible: () => isMac,
|
||||||
disabled: () => Settings.store.tray === false
|
disabled: () => !Settings.store.tray
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: "clickTrayToShowHide",
|
key: "clickTrayToShowHide",
|
||||||
title: "Hide/Show on tray click",
|
title: "Hide/Show on tray click",
|
||||||
description: "Left clicking tray icon will toggle the vesktop window visibility.",
|
description: "Left clicking tray icon will toggle the vesktop window visibility."
|
||||||
defaultValue: false
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: "disableMinSize",
|
key: "disableMinSize",
|
||||||
title: "Disable minimum window size",
|
title: "Disable minimum window size",
|
||||||
description: "Allows you to make the window as small as your heart desires",
|
description: "Allows you to make the window as small as your heart desires"
|
||||||
defaultValue: false
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: "disableSmoothScroll",
|
key: "disableSmoothScroll",
|
||||||
title: "Disable smooth scrolling",
|
title: "Disable smooth scrolling",
|
||||||
description: "Disables smooth scrolling",
|
description: "Disables smooth scrolling"
|
||||||
defaultValue: false
|
|
||||||
},
|
},
|
||||||
KeybindsButton
|
KeybindsButton
|
||||||
],
|
],
|
||||||
@@ -132,23 +131,20 @@ const SettingsOptions: Record<string, Array<BooleanSetting | SettingsComponent>>
|
|||||||
{
|
{
|
||||||
key: "enableTaskbarFlashing",
|
key: "enableTaskbarFlashing",
|
||||||
title: "Enable Taskbar Flashing",
|
title: "Enable Taskbar Flashing",
|
||||||
description: "Flashes the app in your taskbar when you have new notifications.",
|
description: "Flashes the app in your taskbar when you have new notifications."
|
||||||
defaultValue: false
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
Miscellaneous: [
|
Miscellaneous: [
|
||||||
{
|
{
|
||||||
key: "arRPC",
|
key: "arRPC",
|
||||||
title: "Rich Presence",
|
title: "Rich Presence",
|
||||||
description: "Enables Rich Presence via arRPC",
|
description: "Enables Rich Presence via arRPC"
|
||||||
defaultValue: false
|
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
key: "openLinksWithElectron",
|
key: "openLinksWithElectron",
|
||||||
title: "Open Links in app (experimental)",
|
title: "Open Links in app (experimental)",
|
||||||
description: "Opens links in a new Vesktop window instead of your web browser",
|
description: "Opens links in a new Vesktop window instead of your web browser"
|
||||||
defaultValue: false
|
|
||||||
},
|
},
|
||||||
|
|
||||||
WebRTCIPHandlingPolicyPicker
|
WebRTCIPHandlingPolicyPicker
|
||||||
@@ -170,16 +166,15 @@ function SettingsSections() {
|
|||||||
{settings.map((Setting, i) => {
|
{settings.map((Setting, i) => {
|
||||||
if (typeof Setting === "function") return <Setting key={`Custom-${i}`} settings={Settings} />;
|
if (typeof Setting === "function") return <Setting key={`Custom-${i}`} settings={Settings} />;
|
||||||
|
|
||||||
const { defaultValue, title, description, key, disabled, invisible } = Setting;
|
const { title, description, key, disabled, invisible } = Setting;
|
||||||
if (invisible?.()) return null;
|
if (invisible?.()) return null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<VesktopSettingsSwitch
|
<VesktopSettingsSwitch
|
||||||
title={title}
|
title={title}
|
||||||
description={description}
|
description={description}
|
||||||
value={Settings[key as any] ?? defaultValue}
|
|
||||||
onChange={v => (Settings[key as any] = v)}
|
|
||||||
disabled={disabled?.()}
|
disabled={disabled?.()}
|
||||||
|
{...getValueAndOnChange(key)}
|
||||||
key={key}
|
key={key}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -7,18 +7,8 @@
|
|||||||
import "./UserAssets.css";
|
import "./UserAssets.css";
|
||||||
|
|
||||||
import { BaseText, Button, FormSwitch } from "@vencord/types/components";
|
import { BaseText, Button, FormSwitch } from "@vencord/types/components";
|
||||||
import {
|
import { Margins, wordsFromCamel, wordsToTitle } from "@vencord/types/utils";
|
||||||
Margins,
|
import { Modal, openModal, showToast, useState } from "@vencord/types/webpack/common";
|
||||||
ModalCloseButton,
|
|
||||||
ModalContent,
|
|
||||||
ModalHeader,
|
|
||||||
ModalRoot,
|
|
||||||
ModalSize,
|
|
||||||
openModal,
|
|
||||||
wordsFromCamel,
|
|
||||||
wordsToTitle
|
|
||||||
} from "@vencord/types/utils";
|
|
||||||
import { showToast, useState } from "@vencord/types/webpack/common";
|
|
||||||
import { UserAssetType } from "main/userAssets";
|
import { UserAssetType } from "main/userAssets";
|
||||||
import { useSettings } from "renderer/settings";
|
import { useSettings } from "renderer/settings";
|
||||||
|
|
||||||
@@ -32,22 +22,13 @@ export const UserAssetsButton: SettingsComponent = () => {
|
|||||||
|
|
||||||
function openAssetsModal() {
|
function openAssetsModal() {
|
||||||
openModal(props => (
|
openModal(props => (
|
||||||
<ModalRoot {...props} size={ModalSize.MEDIUM}>
|
<Modal {...props} size="lg" title="User Assets">
|
||||||
<ModalHeader>
|
|
||||||
<BaseText size="lg" weight="semibold" tag="h3" style={{ flexGrow: 1 }}>
|
|
||||||
User Assets
|
|
||||||
</BaseText>
|
|
||||||
<ModalCloseButton onClick={props.onClose} />
|
|
||||||
</ModalHeader>
|
|
||||||
|
|
||||||
<ModalContent>
|
|
||||||
<div className="vcd-user-assets">
|
<div className="vcd-user-assets">
|
||||||
{CUSTOMIZABLE_ASSETS.map(asset => (
|
{CUSTOMIZABLE_ASSETS.map(asset => (
|
||||||
<Asset key={asset} asset={asset} />
|
<Asset key={asset} asset={asset} />
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
</ModalContent>
|
</Modal>
|
||||||
</ModalRoot>
|
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -93,7 +74,7 @@ function Asset({ asset }: { asset: UserAssetType }) {
|
|||||||
{isSplash && (
|
{isSplash && (
|
||||||
<FormSwitch
|
<FormSwitch
|
||||||
title="Nearest-Neighbor Scaling (for pixel art)"
|
title="Nearest-Neighbor Scaling (for pixel art)"
|
||||||
value={settings.splashPixelated ?? false}
|
value={settings.splashPixelated}
|
||||||
onChange={val => (settings.splashPixelated = val)}
|
onChange={val => (settings.splashPixelated = val)}
|
||||||
className={Margins.top16}
|
className={Margins.top16}
|
||||||
hideBorder
|
hideBorder
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ export const WebRTCIPHandlingPolicyPicker: SettingsComponent = ({ settings }) =>
|
|||||||
]}
|
]}
|
||||||
closeOnSelect={true}
|
closeOnSelect={true}
|
||||||
select={v => (settings.webRTCIPHandlingPolicy = v)}
|
select={v => (settings.webRTCIPHandlingPolicy = v)}
|
||||||
isSelected={v => v === (settings.webRTCIPHandlingPolicy ?? "default")}
|
isSelected={v => v === settings.webRTCIPHandlingPolicy}
|
||||||
serialize={s => s}
|
serialize={s => s}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -7,17 +7,8 @@
|
|||||||
import "./ShortcutSettings.css";
|
import "./ShortcutSettings.css";
|
||||||
|
|
||||||
import { BaseText, Button, Card, DeleteIcon, Switch } from "@vencord/types/components";
|
import { BaseText, Button, Card, DeleteIcon, Switch } from "@vencord/types/components";
|
||||||
import {
|
import { classNameFactory, identity, openModal } from "@vencord/types/utils";
|
||||||
classNameFactory,
|
import { Modal, React, Select, useState } from "@vencord/types/webpack/common";
|
||||||
identity,
|
|
||||||
ModalCloseButton,
|
|
||||||
ModalContent,
|
|
||||||
ModalHeader,
|
|
||||||
ModalRoot,
|
|
||||||
ModalSize,
|
|
||||||
openModal
|
|
||||||
} from "@vencord/types/utils";
|
|
||||||
import { React, Select, useState } from "@vencord/types/webpack/common";
|
|
||||||
import { SettingsComponent } from "renderer/components/settings/Settings";
|
import { SettingsComponent } from "renderer/components/settings/Settings";
|
||||||
import { onIpcCommand } from "renderer/ipcCommands";
|
import { onIpcCommand } from "renderer/ipcCommands";
|
||||||
import { reactiveValue } from "renderer/reactiveState";
|
import { reactiveValue } from "renderer/reactiveState";
|
||||||
@@ -181,18 +172,9 @@ export function KeyBindSettings() {
|
|||||||
|
|
||||||
export function openKeybindsModal() {
|
export function openKeybindsModal() {
|
||||||
openModal(props => (
|
openModal(props => (
|
||||||
<ModalRoot {...props} size={ModalSize.LARGE}>
|
<Modal {...props} size="lg" title="Keybinds">
|
||||||
<ModalHeader>
|
|
||||||
<BaseText size="lg" weight="semibold" tag="h3" style={{ flexGrow: 1 }}>
|
|
||||||
Keybinds
|
|
||||||
</BaseText>
|
|
||||||
<ModalCloseButton onClick={props.onClose} />
|
|
||||||
</ModalHeader>
|
|
||||||
|
|
||||||
<ModalContent>
|
|
||||||
<KeyBindSettings />
|
<KeyBindSettings />
|
||||||
</ModalContent>
|
</Modal>
|
||||||
</ModalRoot>
|
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -21,8 +21,7 @@ addPatch({
|
|||||||
],
|
],
|
||||||
|
|
||||||
getPlatformClass() {
|
getPlatformClass() {
|
||||||
if (Settings.store.customTitleBar) return "platform-win";
|
if (Settings.store.nativeTitleBar) return "platform-web";
|
||||||
if (isMac) return "platform-osx";
|
return isMac ? "platform-osx" : "platform-win";
|
||||||
return "platform-web";
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import { Settings } from "renderer/settings";
|
|||||||
|
|
||||||
import { addPatch } from "./shared";
|
import { addPatch } from "./shared";
|
||||||
|
|
||||||
if (Settings.store.customTitleBar)
|
if (!Settings.store.nativeTitleBar)
|
||||||
addPatch({
|
addPatch({
|
||||||
patches: [
|
patches: [
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -25,10 +25,12 @@ export function useSettings() {
|
|||||||
return Settings.store;
|
return Settings.store;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getValueAndOnChange(key: keyof typeof Settings.store) {
|
export function getValueAndOnChange<K extends keyof typeof Settings.store, V extends (typeof Settings.store)[K]>(
|
||||||
|
key: K
|
||||||
|
) {
|
||||||
return {
|
return {
|
||||||
value: Settings.store[key] as any,
|
value: Settings.store[key] as V,
|
||||||
onChange: (value: any) => (Settings.store[key] = value)
|
onChange: (value: V) => (Settings.store[key] = value)
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -49,6 +49,7 @@ export const enum IpcEvents {
|
|||||||
VIRT_MIC_LIST = "VCD_VIRT_MIC_LIST",
|
VIRT_MIC_LIST = "VCD_VIRT_MIC_LIST",
|
||||||
VIRT_MIC_START = "VCD_VIRT_MIC_START",
|
VIRT_MIC_START = "VCD_VIRT_MIC_START",
|
||||||
VIRT_MIC_START_SYSTEM = "VCD_VIRT_MIC_START_ALL",
|
VIRT_MIC_START_SYSTEM = "VCD_VIRT_MIC_START_ALL",
|
||||||
|
VIRT_MIC_UNMUTE = "VCD_VIRT_MIC_UNMUTE",
|
||||||
VIRT_MIC_STOP = "VCD_VIRT_MIC_STOP",
|
VIRT_MIC_STOP = "VCD_VIRT_MIC_STOP",
|
||||||
|
|
||||||
CLIPBOARD_COPY_IMAGE = "VCD_CLIPBOARD_COPY_IMAGE",
|
CLIPBOARD_COPY_IMAGE = "VCD_CLIPBOARD_COPY_IMAGE",
|
||||||
|
|||||||
@@ -0,0 +1,33 @@
|
|||||||
|
/*
|
||||||
|
* Vesktop, a desktop app aiming to give you a snappier Discord Experience
|
||||||
|
* Copyright (c) 2026 Vendicated and Vesktop contributors
|
||||||
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { Settings } from "./settings";
|
||||||
|
|
||||||
|
export const DefaultVesktopSettings: Settings = {
|
||||||
|
discordBranch: "stable",
|
||||||
|
hardwareAcceleration: true,
|
||||||
|
hardwareVideoAcceleration: false,
|
||||||
|
nativeTitleBar: false,
|
||||||
|
staticTitle: false,
|
||||||
|
enableMenu: false,
|
||||||
|
enableShadow: true,
|
||||||
|
enableRoundedCorners: true,
|
||||||
|
enableSplashScreen: true,
|
||||||
|
splashTheming: true,
|
||||||
|
tray: true,
|
||||||
|
minimizeToTray: true,
|
||||||
|
clickTrayToShowHide: false,
|
||||||
|
disableMinSize: false,
|
||||||
|
disableSmoothScroll: false,
|
||||||
|
enableTaskbarFlashing: false,
|
||||||
|
arRPC: true,
|
||||||
|
openLinksWithElectron: false,
|
||||||
|
autoStartMinimized: false,
|
||||||
|
splashPixelated: false,
|
||||||
|
webRTCIPHandlingPolicy: "default",
|
||||||
|
appBadge: true,
|
||||||
|
transparencyOption: "none"
|
||||||
|
};
|
||||||
Vendored
+24
-21
@@ -8,34 +8,36 @@ import type { Rectangle } from "electron";
|
|||||||
import { KeyBind } from "renderer/globalShortcuts/ShortcutSettings";
|
import { KeyBind } from "renderer/globalShortcuts/ShortcutSettings";
|
||||||
|
|
||||||
export interface Settings {
|
export interface Settings {
|
||||||
discordBranch?: "stable" | "canary" | "ptb";
|
discordBranch: "stable" | "canary" | "ptb";
|
||||||
transparencyOption?: "none" | "mica" | "tabbed" | "acrylic";
|
transparencyOption: "none" | "mica" | "tabbed" | "acrylic";
|
||||||
webRTCIPHandlingPolicy?:
|
webRTCIPHandlingPolicy:
|
||||||
| "default"
|
| "default"
|
||||||
| "default_public_interface_only"
|
| "default_public_interface_only"
|
||||||
| "default_public_and_private_interfaces"
|
| "default_public_and_private_interfaces"
|
||||||
| "disable_non_proxied_udp";
|
| "disable_non_proxied_udp";
|
||||||
tray?: boolean;
|
tray: boolean;
|
||||||
minimizeToTray?: boolean;
|
minimizeToTray: boolean;
|
||||||
autoStartMinimized?: boolean;
|
autoStartMinimized: boolean;
|
||||||
openLinksWithElectron?: boolean;
|
openLinksWithElectron: boolean;
|
||||||
staticTitle?: boolean;
|
staticTitle: boolean;
|
||||||
enableMenu?: boolean;
|
enableMenu: boolean;
|
||||||
disableSmoothScroll?: boolean;
|
enableShadow: boolean;
|
||||||
hardwareAcceleration?: boolean;
|
enableRoundedCorners: boolean;
|
||||||
hardwareVideoAcceleration?: boolean;
|
disableSmoothScroll: boolean;
|
||||||
arRPC?: boolean;
|
hardwareAcceleration: boolean;
|
||||||
appBadge?: boolean;
|
hardwareVideoAcceleration: boolean;
|
||||||
enableTaskbarFlashing?: boolean;
|
arRPC: boolean;
|
||||||
disableMinSize?: boolean;
|
appBadge: boolean;
|
||||||
clickTrayToShowHide?: boolean;
|
enableTaskbarFlashing: boolean;
|
||||||
customTitleBar?: boolean;
|
disableMinSize: boolean;
|
||||||
|
clickTrayToShowHide: boolean;
|
||||||
|
nativeTitleBar: boolean;
|
||||||
|
|
||||||
enableSplashScreen?: boolean;
|
enableSplashScreen: boolean;
|
||||||
splashTheming?: boolean;
|
splashTheming: boolean;
|
||||||
|
splashPixelated: boolean;
|
||||||
splashColor?: string;
|
splashColor?: string;
|
||||||
splashBackground?: string;
|
splashBackground?: string;
|
||||||
splashPixelated?: boolean;
|
|
||||||
|
|
||||||
spellCheckLanguages?: string[];
|
spellCheckLanguages?: string[];
|
||||||
|
|
||||||
@@ -49,6 +51,7 @@ export interface Settings {
|
|||||||
ignoreDevices?: boolean;
|
ignoreDevices?: boolean;
|
||||||
ignoreInputMedia?: boolean;
|
ignoreInputMedia?: boolean;
|
||||||
|
|
||||||
|
mute?: boolean;
|
||||||
onlySpeakers?: boolean;
|
onlySpeakers?: boolean;
|
||||||
onlyDefaultSpeakers?: boolean;
|
onlyDefaultSpeakers?: boolean;
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user