Compare commits
99 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
dad306a4b5 | ||
|
|
a52ee1dbb6 | ||
|
|
524f8ff277 | ||
|
|
83104e3625 | ||
|
|
fcb61c8f42 | ||
|
|
48e9aea47e | ||
|
|
6e7d912b95 | ||
|
|
475012cbed | ||
|
|
382dac96f7 | ||
|
|
3295a7d344 | ||
|
|
bb3cec0d13 | ||
|
|
1a673f2318 | ||
|
|
d9a7e81f71 | ||
|
|
73848311b1 | ||
|
|
857779e77b | ||
|
|
fa627b384f | ||
|
|
43a8781492 | ||
|
|
b17fef93ba | ||
|
|
fc16fc5404 | ||
|
|
e760e58ed7 | ||
|
|
3936a0a41e | ||
|
|
c7d830c57c | ||
|
|
7bf05bd907 | ||
|
|
ae20445301 | ||
|
|
33d1ac43e3 | ||
|
|
da51ebb0a7 | ||
|
|
d0399cbde4 | ||
|
|
49784bc1aa | ||
|
|
43f59cefda | ||
|
|
c42c1b7bbd | ||
|
|
765ffc0b57 | ||
|
|
3c87c89b3a | ||
|
|
391ad94b85 | ||
|
|
8f94196646 | ||
|
|
878184cab1 | ||
|
|
156ba6ab7b | ||
|
|
fd91a23188 | ||
|
|
9ca9e78da7 | ||
|
|
a1d5e4dcdc | ||
|
|
da5d0f8f19 | ||
|
|
d7bc56660b | ||
|
|
d39c54b3ee | ||
|
|
670c62267e | ||
|
|
8938fe27b2 | ||
|
|
e6c1a03c59 | ||
|
|
68930a1f50 | ||
|
|
3b76c30db2 | ||
|
|
523f657b3b | ||
|
|
d75ab4af1c | ||
|
|
7e33780743 | ||
|
|
9905592b24 | ||
|
|
6e1913cafc | ||
|
|
b620e07445 | ||
|
|
9b0503f49d | ||
|
|
c0b79e6e93 | ||
|
|
57cae6f9f1 | ||
|
|
2e72fa6589 | ||
|
|
4ee57da6f3 | ||
|
|
7560727372 | ||
|
|
6ba562c663 | ||
|
|
872b60be1c | ||
|
|
8cd80f4af1 | ||
|
|
7b5e1ed4da | ||
|
|
56442ae1e9 | ||
|
|
c9be618164 | ||
|
|
eddbe27c4d | ||
|
|
00fb658355 | ||
|
|
67a1847cea | ||
|
|
030ffca499 | ||
|
|
53913c07bf | ||
|
|
f6e29231f5 | ||
|
|
7a19c81964 | ||
|
|
5a72491ab0 | ||
|
|
6c4ecc0d64 | ||
|
|
524fc8bc0a | ||
|
|
5b6c1c6d81 | ||
|
|
852410a43b | ||
|
|
5d675efb64 | ||
|
|
03c7ad4cc0 | ||
|
|
135a369848 | ||
|
|
8993b0d520 | ||
|
|
ccff1ac3ef | ||
|
|
062b536617 | ||
|
|
d008f90399 | ||
|
|
4fdf43ea6a | ||
|
|
b94379f5bd | ||
|
|
37db07807a | ||
|
|
4274647c81 | ||
|
|
24fbf35542 | ||
|
|
c8eccc7e9d | ||
|
|
a318f6b407 | ||
|
|
75354ad8e6 | ||
|
|
af9ed58eef | ||
|
|
e0453418bd | ||
|
|
22344512ad | ||
|
|
9acc6652ff | ||
|
|
61bbd7f6aa | ||
|
|
f31f06f5c4 | ||
|
|
786fe131b8 |
@@ -5,4 +5,4 @@
|
||||
# https://github.com/settings/personal-access-tokens/new
|
||||
GITHUB_TOKEN=
|
||||
|
||||
ELECTRON_LAUNCH_FLAGS="--ozone-platform-hint=auto --enable-webrtc-pipewire-capturer --enable-features=WaylandWindowDecorations"
|
||||
ELECTRON_LAUNCH_FLAGS="--enable-source-maps --ozone-platform-hint=auto"
|
||||
@@ -1,69 +0,0 @@
|
||||
{
|
||||
"root": true,
|
||||
"parser": "@typescript-eslint/parser",
|
||||
"ignorePatterns": ["dist", "node_modules"],
|
||||
"plugins": [
|
||||
"@typescript-eslint",
|
||||
"license-header",
|
||||
"simple-import-sort",
|
||||
"unused-imports",
|
||||
"path-alias",
|
||||
"prettier"
|
||||
],
|
||||
"settings": {
|
||||
"import/resolver": {
|
||||
"alias": {
|
||||
"map": []
|
||||
}
|
||||
}
|
||||
},
|
||||
"rules": {
|
||||
"license-header/header": ["error", "scripts/header.txt"],
|
||||
"eqeqeq": ["error", "always", { "null": "ignore" }],
|
||||
"spaced-comment": ["error", "always", { "markers": ["!"] }],
|
||||
"yoda": "error",
|
||||
"prefer-destructuring": [
|
||||
"error",
|
||||
{
|
||||
"VariableDeclarator": { "array": false, "object": true },
|
||||
"AssignmentExpression": { "array": false, "object": false }
|
||||
}
|
||||
],
|
||||
"operator-assignment": ["error", "always"],
|
||||
"no-useless-computed-key": "error",
|
||||
"no-unneeded-ternary": ["error", { "defaultAssignment": false }],
|
||||
"no-invalid-regexp": "error",
|
||||
"no-constant-condition": ["error", { "checkLoops": false }],
|
||||
"no-duplicate-imports": "error",
|
||||
"no-extra-semi": "error",
|
||||
"dot-notation": "error",
|
||||
"no-useless-escape": "error",
|
||||
"no-fallthrough": "error",
|
||||
"for-direction": "error",
|
||||
"no-async-promise-executor": "error",
|
||||
"no-cond-assign": "error",
|
||||
"no-dupe-else-if": "error",
|
||||
"no-duplicate-case": "error",
|
||||
"no-irregular-whitespace": "error",
|
||||
"no-loss-of-precision": "error",
|
||||
"no-misleading-character-class": "error",
|
||||
"no-prototype-builtins": "error",
|
||||
"no-regex-spaces": "error",
|
||||
"no-shadow-restricted-names": "error",
|
||||
"no-unexpected-multiline": "error",
|
||||
"no-unsafe-optional-chaining": "error",
|
||||
"no-useless-backreference": "error",
|
||||
"use-isnan": "error",
|
||||
"prefer-const": "error",
|
||||
"prefer-spread": "error",
|
||||
|
||||
"simple-import-sort/imports": "error",
|
||||
"simple-import-sort/exports": "error",
|
||||
|
||||
"unused-imports/no-unused-imports": "error",
|
||||
|
||||
"path-alias/no-relative": "error",
|
||||
|
||||
"prettier/prettier": "error"
|
||||
}
|
||||
}
|
||||
18
.github/ISSUE_TEMPLATE/blank.yml
vendored
18
.github/ISSUE_TEMPLATE/blank.yml
vendored
@@ -1,18 +0,0 @@
|
||||
name: Blank Issue
|
||||
description: Reserved for developers. Use the bug report or feature request templates instead.
|
||||
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
# READ THIS BEFORE OPENING AN ISSUE
|
||||
|
||||
This form is only meant for Vesktop developers. If you don't know what you're doing,
|
||||
please use the bug report or feature request templates instead.
|
||||
|
||||
- type: textarea
|
||||
id: content
|
||||
attributes:
|
||||
label: Content
|
||||
validations:
|
||||
required: true
|
||||
107
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
107
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
@@ -1,107 +0,0 @@
|
||||
name: 🐛 Bug / Crash Report
|
||||
description: Create a bug or crash report for Vesktop
|
||||
labels: [bug]
|
||||
title: "[Bug] <title>"
|
||||
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
**Thanks 🩷 for taking the time to fill out this bug report! Before proceeding, please read the following**
|
||||
|
||||
Make sure a similar issue doesn't already exist by [searching the existing issues](https://github.com/Vencord/Vesktop/issues?q=is%3Aissue) for keywords!
|
||||
|
||||
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:
|
||||
- Purely graphical glitches like flickering, scaling issues, etc: Issue with your gpu. Nothing we can do, update drivers or disable hardware acceleration
|
||||
- Vencord related issues: This is the Vesktop repo, not Vencord
|
||||
- **SCREENSHARE NOT STARTING** / black screening on Linux: Issue with your desktop environment, specifically its xdg-desktop-portal.
|
||||
If you're on flatpak, try using native version. If that also doesn't work, you have to fix your systen. Inspect errors and google around.
|
||||
|
||||
Linux users: Please only report issues with supported packages (flatpak and any builds from the README / releases).
|
||||
We do not support other packages, like the AUR or Nix packages, so please first make sure your issue is reproducible with official releases,
|
||||
like [our Flatpak](https://flathub.org/apps/dev.vencord.Vesktop) or [AppImage](https://vencord.dev/download/vesktop/amd64/appimage)
|
||||
|
||||
- type: input
|
||||
id: discord
|
||||
attributes:
|
||||
label: Discord Account
|
||||
description: Who on Discord is making this request? Not required but encouraged for easier follow-up
|
||||
placeholder: username#0000
|
||||
validations:
|
||||
required: false
|
||||
|
||||
- type: input
|
||||
id: os
|
||||
attributes:
|
||||
label: Operating System
|
||||
description: What operating system are you using (eg Windows 10, macOS Big Sur, Ubuntu 20.04)?
|
||||
placeholder: Windows 10
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: input
|
||||
id: linux-de
|
||||
attributes:
|
||||
label: Linux Only ~ Desktop Environment
|
||||
description: If you are on Linux, what Desktop environment are you using (eg GNOME, KDE, XFCE)? Are you using Wayland or Xorg?
|
||||
placeholder: Gnome on Wayland
|
||||
validations:
|
||||
required: false
|
||||
|
||||
- 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 Vencord/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: debug-logs
|
||||
attributes:
|
||||
label: Debug Logs
|
||||
description: Run vesktop from the command line. Include the relevant command line output here
|
||||
value: |
|
||||
```
|
||||
Replace this text with your crash-log. Do not remove the backticks
|
||||
```
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: checkboxes
|
||||
id: agreement-check
|
||||
attributes:
|
||||
label: Request Agreement
|
||||
description: We only accept reports for bugs that happen on supported and up to date Vesktop releases
|
||||
options:
|
||||
- label: I have searched the existing issues and found no similar issue
|
||||
required: true
|
||||
- label: I am using the latest Vesktop and Vencord versions
|
||||
required: true
|
||||
- label: This issue occurs on an official release (not just the AUR or Nix packages)
|
||||
required: true
|
||||
2
.github/ISSUE_TEMPLATE/config.yml
vendored
2
.github/ISSUE_TEMPLATE/config.yml
vendored
@@ -2,4 +2,4 @@ blank_issues_enabled: false
|
||||
contact_links:
|
||||
- name: Vencord Support Server
|
||||
url: https://discord.gg/D9uwnFnqmd
|
||||
about: If you need help regarding Vesktop or Vencord, please join our support server!
|
||||
about: "Need Help? Join our support server and ask in the #vesktop-support channel!"
|
||||
|
||||
15
.github/ISSUE_TEMPLATE/dev-issue.yml
vendored
Normal file
15
.github/ISSUE_TEMPLATE/dev-issue.yml
vendored
Normal file
@@ -0,0 +1,15 @@
|
||||
name: Vesktop Developer Issue
|
||||
description: Reserved for Vesktop Developers. Join our support server for support.
|
||||
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||

|
||||
GitHub Issues are for developers, not support. Please use our [support server](https://vencord.dev/discord) if you are not a developer.
|
||||
- type: textarea
|
||||
id: content
|
||||
attributes:
|
||||
label: Content
|
||||
validations:
|
||||
required: true
|
||||
BIN
.github/ISSUE_TEMPLATE/developer-banner.png
vendored
Normal file
BIN
.github/ISSUE_TEMPLATE/developer-banner.png
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 31 KiB |
72
.github/ISSUE_TEMPLATE/feature-request.yml
vendored
72
.github/ISSUE_TEMPLATE/feature-request.yml
vendored
@@ -1,72 +0,0 @@
|
||||
name: 🛠️ Feature Request
|
||||
description: Request a feature for Vesktop
|
||||
labels: [enhancement]
|
||||
title: "[Feature Request] <title>"
|
||||
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
**Thanks 🩷 for taking the time to fill out this request! Before proceeding, please read the following**
|
||||
|
||||
Make sure a similar request doesn't already exist by [searching the existing issues](https://github.com/Vencord/Vesktop/issues?q=is%3Aissue) for keywords!
|
||||
|
||||
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!
|
||||
|
||||
- type: input
|
||||
id: discord
|
||||
attributes:
|
||||
label: Discord Account
|
||||
description: Who on Discord is making this request? Not required but encouraged for easier follow-up
|
||||
placeholder: username#0000
|
||||
validations:
|
||||
required: false
|
||||
|
||||
- type: textarea
|
||||
id: motivation
|
||||
attributes:
|
||||
label: Motivation
|
||||
description: If your feature request related to a problem? Please describe
|
||||
placeholder: I'm always frustrated when ..., I think it would be better if ...
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: solution
|
||||
attributes:
|
||||
label: Solution
|
||||
description: Describe the solution you'd like
|
||||
placeholder: A clear and concise description of what you want to happen.
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: alternatives
|
||||
attributes:
|
||||
label: Alternatives
|
||||
description: Describe alternatives you've considered
|
||||
placeholder: A clear and concise description of any alternative solutions or features you've considered.
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: additional-context
|
||||
attributes:
|
||||
label: Additional context
|
||||
description: Add any other context here. Screenshots or mockups could help greatly
|
||||
validations:
|
||||
required: false
|
||||
|
||||
- type: checkboxes
|
||||
id: agreement-check
|
||||
attributes:
|
||||
label: Request Agreement
|
||||
description: This form is only for Vesktop feature requests. If the following don't apply, re-read the introduction text
|
||||
options:
|
||||
- label: I have searched the existing issues and found no similar issue
|
||||
required: true
|
||||
- label: This is not a plugin request
|
||||
required: true
|
||||
- label: This is not a Vencord feature request
|
||||
required: true
|
||||
12
.github/workflows/meta.yml
vendored
12
.github/workflows/meta.yml
vendored
@@ -6,6 +6,9 @@ on:
|
||||
- published
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
jobs:
|
||||
update:
|
||||
runs-on: ubuntu-latest
|
||||
@@ -29,10 +32,11 @@ jobs:
|
||||
run: |
|
||||
git config user.name "github-actions[bot]"
|
||||
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
|
||||
git checkout -b ci/meta-update
|
||||
|
||||
gh release upload "${{ github.event.release.tag_name }}" 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 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"
|
||||
git commit -m "metainfo: add entry for ${{ github.event.release.tag_name }}"
|
||||
git push
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
10
.github/workflows/release.yml
vendored
10
.github/workflows/release.yml
vendored
@@ -47,7 +47,13 @@ jobs:
|
||||
- name: Run Electron Builder
|
||||
if: ${{ matrix.platform == 'mac' }}
|
||||
run: |
|
||||
echo "$API_KEY" > apple.p8
|
||||
pnpm electron-builder --${{ matrix.platform }} --publish always
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
CSC_LINK: ${{ secrets.APPLE_SIGNING_CERT }}
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
CSC_LINK: ${{ secrets.APPLE_SIGNING_CERT }}
|
||||
CSC_KEY_PASSWORD: ${{ secrets.APPLE_SIGNING_CERT_PASSWORD }}
|
||||
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 }}
|
||||
|
||||
29
.github/workflows/update-vencord-dev.yml
vendored
Normal file
29
.github/workflows/update-vencord-dev.yml
vendored
Normal file
@@ -0,0 +1,29 @@
|
||||
name: Update vencord.dev Vesktop version
|
||||
|
||||
on:
|
||||
release:
|
||||
types:
|
||||
- published
|
||||
|
||||
jobs:
|
||||
update:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Update scripts/_latestVesktopVersion.txt file in vencord.dev repo
|
||||
run: |
|
||||
git config --global user.name "$USERNAME"
|
||||
git config --global user.email "41898282+github-actions[bot]@users.noreply.github.com"
|
||||
|
||||
git clone https://$USERNAME:$API_TOKEN@github.com/$GH_REPO.git repo
|
||||
|
||||
cd repo
|
||||
version="${{ github.event.release.tag_name }}"
|
||||
echo "${version#v}" > scripts/_latestVesktopVersion.txt
|
||||
|
||||
git add scripts/_latestVesktopVersion.txt
|
||||
git commit -m "Update Vesktop version to ${{ github.event.release.tag_name }}"
|
||||
git push https://$USERNAME:$API_TOKEN@github.com/$GH_REPO.git
|
||||
env:
|
||||
API_TOKEN: ${{ secrets.VENCORD_DEV_GITHUB_TOKEN }}
|
||||
GH_REPO: Vencord/vencord.dev
|
||||
USERNAME: GitHub-Actions
|
||||
2
.github/workflows/winget-submission.yml
vendored
2
.github/workflows/winget-submission.yml
vendored
@@ -16,7 +16,7 @@ jobs:
|
||||
runs-on: windows-latest
|
||||
steps:
|
||||
- name: Submit package to Winget Community Repo
|
||||
uses: vedantmgoyal2009/winget-releaser@4614300d5812e5df91cb02ef0edbece623d5dea8
|
||||
uses: vedantmgoyal2009/winget-releaser@0db4f0a478166abd0fa438c631849f0b8dcfb99f
|
||||
with:
|
||||
identifier: Vencord.Vesktop
|
||||
token: ${{ secrets.WINGET_PAT }}
|
||||
|
||||
27
README.md
27
README.md
@@ -21,15 +21,18 @@ Vesktop is a custom Discord desktop app
|
||||
|
||||
If you don't know the difference, pick the Installer.
|
||||
|
||||
- [Installer](https://vencord.dev/download/vesktop/amd64/windows)
|
||||
- [Portable](https://vencord.dev/download/vesktop/amd64/windows-portable)
|
||||
- [Installer](https://vencord.dev/download/vesktop/universal/windows)
|
||||
- Portable:
|
||||
- [x64 / amd64](https://vencord.dev/download/vesktop/amd64/windows-portable)
|
||||
- [Arm® 64](https://vencord.dev/download/vesktop/arm64/windows-portable)
|
||||
|
||||
### Mac
|
||||
|
||||
If you don't know the difference, pick the Intel build.
|
||||
Download the latest [Vesktop.dmg](https://vencord.dev/download/vesktop/universal/dmg) or use [Homebrew](https://brew.sh/)
|
||||
|
||||
- [Intel build (amd64)](https://vencord.dev/download/vesktop/amd64/dmg)
|
||||
- [Apple Silicon (arm64)](https://vencord.dev/download/vesktop/arm64/dmg)
|
||||
```sh
|
||||
brew install vesktop
|
||||
```
|
||||
|
||||
### Linux
|
||||
|
||||
@@ -42,7 +45,7 @@ If you don't know the difference, pick amd64.
|
||||
- [Ubuntu/Debian (.deb)](https://vencord.dev/download/vesktop/amd64/deb)
|
||||
- [Fedora/RHEL (.rpm)](https://vencord.dev/download/vesktop/amd64/rpm)
|
||||
- [tarball](https://vencord.dev/download/vesktop/amd64/tar)
|
||||
- arm64 / aarch64
|
||||
- Arm® 64 / aarch64
|
||||
- [AppImage](https://vencord.dev/download/vesktop/arm64/appimage)
|
||||
- [Ubuntu/Debian (.deb)](https://vencord.dev/download/vesktop/arm64/deb)
|
||||
- [Fedora/RHEL (.rpm)](https://vencord.dev/download/vesktop/arm64/rpm)
|
||||
@@ -54,10 +57,16 @@ Below you can find unofficial packages created by the community. They are not of
|
||||
|
||||
- Arch Linux: [Vesktop on the Arch user repository](https://aur.archlinux.org/packages?K=vesktop)
|
||||
- NixOS: https://wiki.nixos.org/wiki/Discord#Vesktop
|
||||
- Slackware: [Vesktop on the SlackBuilds](https://slackbuilds.org/result/?search=vesktop)
|
||||
- Windows - Scoop: https://scoop.sh/#/apps?q=Vesktop
|
||||
|
||||
## Building from Source
|
||||
|
||||
You need to have the following dependencies installed:
|
||||
- [Git](https://git-scm.com/downloads)
|
||||
- [Node.js](https://nodejs.org/en/download)
|
||||
- pnpm: `npm install --global pnpm`
|
||||
|
||||
Packaging will create builds in the dist/ folder
|
||||
|
||||
```sh
|
||||
@@ -70,10 +79,12 @@ pnpm i
|
||||
# Either run it without packaging
|
||||
pnpm start
|
||||
|
||||
# Or package
|
||||
# Or package (will build packages for your OS)
|
||||
pnpm package
|
||||
# Or only build the pacman target
|
||||
|
||||
# Or only build the Linux Pacman package
|
||||
pnpm package --linux pacman
|
||||
|
||||
# Or package to a directory only
|
||||
pnpm package:dir
|
||||
```
|
||||
|
||||
103
eslint.config.mjs
Normal file
103
eslint.config.mjs
Normal file
@@ -0,0 +1,103 @@
|
||||
/*
|
||||
* Vesktop, a desktop app aiming to give you a snappier Discord Experience
|
||||
* Copyright (c) 2023 Vendicated and Vencord contributors
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
//@ts-check
|
||||
|
||||
import stylistic from "@stylistic/eslint-plugin";
|
||||
import pathAlias from "eslint-plugin-path-alias";
|
||||
import simpleHeader from "eslint-plugin-simple-header";
|
||||
import importSort from "eslint-plugin-simple-import-sort";
|
||||
import unusedImports from "eslint-plugin-unused-imports";
|
||||
import tseslint from "typescript-eslint";
|
||||
import prettier from "eslint-plugin-prettier";
|
||||
|
||||
export default tseslint.config(
|
||||
{ ignores: ["dist"] },
|
||||
|
||||
{
|
||||
files: ["src/**/*.{tsx,ts,mts,mjs,js,jsx}"],
|
||||
plugins: {
|
||||
simpleHeader,
|
||||
stylistic,
|
||||
importSort,
|
||||
unusedImports,
|
||||
pathAlias,
|
||||
prettier
|
||||
},
|
||||
settings: {
|
||||
"import/resolver": {
|
||||
alias: {
|
||||
map: []
|
||||
}
|
||||
}
|
||||
},
|
||||
languageOptions: {
|
||||
parser: tseslint.parser,
|
||||
parserOptions: {
|
||||
project: true,
|
||||
tsconfigRootDir: import.meta.dirname
|
||||
}
|
||||
},
|
||||
rules: {
|
||||
"simpleHeader/header": [
|
||||
"error",
|
||||
{
|
||||
files: ["scripts/header.txt"],
|
||||
templates: { author: [".*", "Vendicated and Vesktop contributors"] }
|
||||
}
|
||||
],
|
||||
|
||||
// ESLint Rules
|
||||
|
||||
yoda: "error",
|
||||
eqeqeq: ["error", "always", { null: "ignore" }],
|
||||
"prefer-destructuring": [
|
||||
"error",
|
||||
{
|
||||
VariableDeclarator: { array: false, object: true },
|
||||
AssignmentExpression: { array: false, object: false }
|
||||
}
|
||||
],
|
||||
"operator-assignment": ["error", "always"],
|
||||
"no-useless-computed-key": "error",
|
||||
"no-unneeded-ternary": ["error", { defaultAssignment: false }],
|
||||
"no-invalid-regexp": "error",
|
||||
"no-constant-condition": ["error", { checkLoops: false }],
|
||||
"no-duplicate-imports": "error",
|
||||
"dot-notation": "error",
|
||||
"no-useless-escape": "error",
|
||||
"no-fallthrough": "error",
|
||||
"for-direction": "error",
|
||||
"no-async-promise-executor": "error",
|
||||
"no-cond-assign": "error",
|
||||
"no-dupe-else-if": "error",
|
||||
"no-duplicate-case": "error",
|
||||
"no-irregular-whitespace": "error",
|
||||
"no-loss-of-precision": "error",
|
||||
"no-misleading-character-class": "error",
|
||||
"no-prototype-builtins": "error",
|
||||
"no-regex-spaces": "error",
|
||||
"no-shadow-restricted-names": "error",
|
||||
"no-unexpected-multiline": "error",
|
||||
"no-unsafe-optional-chaining": "error",
|
||||
"no-useless-backreference": "error",
|
||||
"use-isnan": "error",
|
||||
"prefer-const": "error",
|
||||
"prefer-spread": "error",
|
||||
|
||||
// Styling Rules
|
||||
"stylistic/spaced-comment": ["error", "always", { markers: ["!"] }],
|
||||
"stylistic/no-extra-semi": "error",
|
||||
|
||||
// Plugin Rules
|
||||
"importSort/imports": "error",
|
||||
"importSort/exports": "error",
|
||||
"unusedImports/no-unused-imports": "error",
|
||||
"pathAlias/no-relative": "error",
|
||||
"prettier/prettier": "error"
|
||||
}
|
||||
}
|
||||
);
|
||||
@@ -28,6 +28,76 @@
|
||||
</screenshot>
|
||||
</screenshots>
|
||||
<releases>
|
||||
<release version="1.5.6" date="2025-04-13" type="stable">
|
||||
<url>https://github.com/Vencord/Vesktop/releases/tag/v1.5.6</url>
|
||||
<description>
|
||||
<p>What's Changed</p>
|
||||
<ul>
|
||||
<li>Fixes the new Discord titlebar @Sqaaakoi</li>
|
||||
<li>Fixes Clipboard copy actions not working</li>
|
||||
<li>Should now properly hide the "Download Apps" button again</li>
|
||||
<li>No longer responds to Browser Tab shortcuts like Ctrl+W by @rushiiMachine</li>
|
||||
<li>DevTools keybind should now work properly on Mac @ryanccn</li>
|
||||
<li>Should no longer throw Sandbox errors on Ubuntu</li>
|
||||
</ul>
|
||||
</description>
|
||||
</release>
|
||||
<release version="1.5.5" date="2025-02-06" type="stable">
|
||||
<url>https://github.com/Vencord/Vesktop/releases/tag/v1.5.5</url>
|
||||
<description>
|
||||
<p>What's Changed</p>
|
||||
<ul>
|
||||
<li>Now remembers your previous Screenshare resolution & FPS</li>
|
||||
<li>You can now disable the splash screen in Vesktop Settings</li>
|
||||
<li>Now supports deep links (opening Discord Message Links in Vesktop) by @Covkie</li>
|
||||
<li>Now supports discord:// uri scheme, allowing it to open things like invites from your Browser even while closed by @Covkie</li>
|
||||
<li>Added 4k resolution to screenshare by @makindotcc</li>
|
||||
<li>Fixed some performance issues caused by a recent Discord update</li>
|
||||
<li>Updated Electron to v34 & chromium to v132, bringing new features and fixes</li>
|
||||
</ul>
|
||||
</description>
|
||||
</release>
|
||||
<release version="1.5.4" date="2024-12-05" type="stable">
|
||||
<url>https://github.com/Vencord/Vesktop/releases/tag/v1.5.4</url>
|
||||
<description>
|
||||
<p>What's Changed</p>
|
||||
<ul>
|
||||
<li>Upgraded electron to version 33 which brings many improvements and bug fixes</li>
|
||||
<li>AudioShare: add even more granular selection, Allow device sharing by @Curve</li>
|
||||
<li>Enable speech-dispatcher support for TTS on linux by @adryd325</li>
|
||||
<li>fixed screenshare picker window subtitle alignment by @ryawaa</li>
|
||||
<li>fixed splash corners by @Covkie</li>
|
||||
</ul>
|
||||
</description>
|
||||
</release>
|
||||
<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 & 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 & 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">
|
||||
<url>https://github.com/Vencord/Vesktop/releases/tag/v1.5.2</url>
|
||||
<description>
|
||||
@@ -208,4 +278,4 @@
|
||||
<keyword>Privacy</keyword>
|
||||
<keyword>Mod</keyword>
|
||||
</keywords>
|
||||
</component>
|
||||
</component>
|
||||
99
package.json
99
package.json
@@ -1,11 +1,11 @@
|
||||
{
|
||||
"name": "vesktop",
|
||||
"version": "1.5.3",
|
||||
"version": "1.5.7",
|
||||
"private": true,
|
||||
"description": "",
|
||||
"description": "Vesktop is a custom Discord desktop app",
|
||||
"keywords": [],
|
||||
"homepage": "https://vencord.dev/",
|
||||
"license": "GPL-3.0",
|
||||
"license": "GPL-3.0-or-later",
|
||||
"author": "Vendicated <vendicated@riseup.net>",
|
||||
"main": "dist/js/main.js",
|
||||
"scripts": {
|
||||
@@ -13,7 +13,7 @@
|
||||
"build:dev": "pnpm build --dev",
|
||||
"package": "pnpm build && electron-builder",
|
||||
"package:dir": "pnpm build && electron-builder --dir",
|
||||
"lint": "eslint . --ext .js,.jsx,.ts,.tsx,.mts,.mjs",
|
||||
"lint": "eslint",
|
||||
"lint:fix": "pnpm lint --fix",
|
||||
"start": "pnpm build && electron .",
|
||||
"start:dev": "pnpm build:dev && electron .",
|
||||
@@ -21,42 +21,43 @@
|
||||
"test": "pnpm lint && pnpm testTypes",
|
||||
"testTypes": "tsc --noEmit",
|
||||
"watch": "pnpm build --watch",
|
||||
"updateMeta": "tsx scripts/utils/updateMeta.mts"
|
||||
"updateMeta": "tsx scripts/utils/updateMeta.mts",
|
||||
"updateArrpcDB": "node ./node_modules/arrpc/update_db.js",
|
||||
"postinstall": "pnpm updateArrpcDB"
|
||||
},
|
||||
"dependencies": {
|
||||
"arrpc": "github:OpenAsar/arrpc#c62ec6a04c8d870530aa6944257fe745f6c59a24",
|
||||
"electron-updater": "^6.2.1"
|
||||
"arrpc": "github:OpenAsar/arrpc#2234e9c9111f4c42ebcc3aa6a2215bfd979eef77",
|
||||
"electron-updater": "^6.6.2"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@vencord/venmic": "^6.1.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@fal-works/esbuild-plugin-global-externals": "^2.1.2",
|
||||
"@types/node": "^20.11.26",
|
||||
"@types/react": "^18.2.0",
|
||||
"@typescript-eslint/eslint-plugin": "^7.2.0",
|
||||
"@typescript-eslint/parser": "^7.2.0",
|
||||
"@vencord/types": "^1.8.4",
|
||||
"dotenv": "^16.4.5",
|
||||
"electron": "^31.1.0",
|
||||
"electron-builder": "^24.13.3",
|
||||
"esbuild": "^0.20.1",
|
||||
"eslint": "^8.57.0",
|
||||
"eslint-config-prettier": "^9.1.0",
|
||||
"@stylistic/eslint-plugin": "^4.4.1",
|
||||
"@types/node": "^22.15.30",
|
||||
"@types/react": "18.3.1",
|
||||
"@vencord/types": "^1.11.5",
|
||||
"dotenv": "^16.5.0",
|
||||
"electron": "^36.4.0",
|
||||
"electron-builder": "^26.0.12",
|
||||
"esbuild": "^0.25.5",
|
||||
"eslint": "^9.28.0",
|
||||
"eslint-import-resolver-alias": "^1.1.2",
|
||||
"eslint-plugin-license-header": "^0.6.0",
|
||||
"eslint-plugin-path-alias": "^1.0.0",
|
||||
"eslint-plugin-prettier": "^5.1.3",
|
||||
"eslint-plugin-simple-import-sort": "^12.0.0",
|
||||
"eslint-plugin-unused-imports": "^3.1.0",
|
||||
"prettier": "^3.2.5",
|
||||
"eslint-plugin-path-alias": "^2.1.0",
|
||||
"eslint-plugin-prettier": "^5.4.1",
|
||||
"eslint-plugin-simple-header": "^1.2.2",
|
||||
"eslint-plugin-simple-import-sort": "^12.1.1",
|
||||
"eslint-plugin-unused-imports": "^4.1.4",
|
||||
"prettier": "^3.5.3",
|
||||
"source-map-support": "^0.5.21",
|
||||
"tsx": "^4.7.1",
|
||||
"type-fest": "^4.12.0",
|
||||
"typescript": "^5.4.2",
|
||||
"xml-formatter": "^3.6.2"
|
||||
"tsx": "^4.19.4",
|
||||
"type-fest": "^4.41.0",
|
||||
"typescript": "^5.8.3",
|
||||
"typescript-eslint": "^8.33.1",
|
||||
"xml-formatter": "^3.6.6"
|
||||
},
|
||||
"packageManager": "pnpm@9.1.0",
|
||||
"packageManager": "pnpm@10.7.1",
|
||||
"engines": {
|
||||
"node": ">=18",
|
||||
"pnpm": ">=8"
|
||||
@@ -72,6 +73,12 @@
|
||||
"package.json",
|
||||
"LICENSE"
|
||||
],
|
||||
"protocols": {
|
||||
"name": "Discord",
|
||||
"schemes": [
|
||||
"discord"
|
||||
]
|
||||
},
|
||||
"beforePack": "scripts/build/sandboxFix.js",
|
||||
"linux": {
|
||||
"icon": "build/icon.icns",
|
||||
@@ -108,11 +115,15 @@
|
||||
}
|
||||
],
|
||||
"desktop": {
|
||||
"Name": "Vesktop",
|
||||
"GenericName": "Internet Messenger",
|
||||
"Type": "Application",
|
||||
"Categories": "Network;InstantMessaging;Chat;",
|
||||
"Keywords": "discord;vencord;electron;chat;"
|
||||
"entry": {
|
||||
"Name": "Vesktop",
|
||||
"GenericName": "Internet Messenger",
|
||||
"Type": "Application",
|
||||
"Categories": "Network;InstantMessaging;Chat;",
|
||||
"Keywords": "discord;vencord;electron;chat;",
|
||||
"MimeType": "x-scheme-handler/discord",
|
||||
"StartupWMClass": "vesktop"
|
||||
}
|
||||
}
|
||||
},
|
||||
"mac": {
|
||||
@@ -124,13 +135,15 @@
|
||||
]
|
||||
}
|
||||
],
|
||||
"category": "Network",
|
||||
"category": "public.app-category.social-networking",
|
||||
"darkModeSupport": true,
|
||||
"extendInfo": {
|
||||
"NSMicrophoneUsageDescription": "This app needs access to the microphone",
|
||||
"NSCameraUsageDescription": "This app needs access to the camera",
|
||||
"com.apple.security.device.audio-input": true,
|
||||
"com.apple.security.device.camera": true
|
||||
}
|
||||
},
|
||||
"notarize": true
|
||||
},
|
||||
"dmg": {
|
||||
"background": "build/background.tiff",
|
||||
@@ -177,11 +190,21 @@
|
||||
},
|
||||
"publish": {
|
||||
"provider": "github"
|
||||
},
|
||||
"rpm": {
|
||||
"fpm": [
|
||||
"--rpm-rpmbuild-define=_build_id_links none"
|
||||
]
|
||||
}
|
||||
},
|
||||
"pnpm": {
|
||||
"patchedDependencies": {
|
||||
"arrpc@3.4.0": "patches/arrpc@3.4.0.patch"
|
||||
}
|
||||
"arrpc@3.5.0": "patches/arrpc@3.5.0.patch"
|
||||
},
|
||||
"onlyBuiltDependencies": [
|
||||
"@vencord/venmic",
|
||||
"electron",
|
||||
"esbuild"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,14 +1,27 @@
|
||||
diff --git a/src/process/index.js b/src/process/index.js
|
||||
index 97ea6514b54dd9c5df588c78f0397d31ab5f882a..c2bdbd6aaa5611bc6ff1d993beeb380b1f5ec575 100644
|
||||
index 389b0845256a34b4536d6da99edb00d17f13a6b4..f17a0ac687e9110ebfd33cb91fd2f6250d318643 100644
|
||||
--- a/src/process/index.js
|
||||
+++ b/src/process/index.js
|
||||
@@ -5,8 +5,7 @@ import fs from 'node:fs';
|
||||
@@ -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];
|
||||
3961
pnpm-lock.yaml
generated
3961
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@@ -1,13 +1,14 @@
|
||||
/*
|
||||
* 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
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { BuildContext, BuildOptions, context } from "esbuild";
|
||||
import { copyFile } from "fs/promises";
|
||||
|
||||
import vencordDep from "./vencordDep.mjs";
|
||||
import { includeDirPlugin } from "./includeDirPlugin.mts";
|
||||
|
||||
const isDev = process.argv.includes("--dev");
|
||||
|
||||
@@ -63,6 +64,11 @@ await Promise.all([
|
||||
outfile: "dist/js/preload.js",
|
||||
footer: { js: "//# sourceURL=VCDPreload" }
|
||||
}),
|
||||
createContext({
|
||||
...NodeCommonOpts,
|
||||
entryPoints: ["src/preload/splash.ts"],
|
||||
outfile: "dist/js/splashPreload.js"
|
||||
}),
|
||||
createContext({
|
||||
...CommonOpts,
|
||||
globalName: "Vesktop",
|
||||
@@ -73,7 +79,7 @@ await Promise.all([
|
||||
jsxFactory: "VencordCreateElement",
|
||||
jsxFragment: "VencordFragment",
|
||||
external: ["@vencord/types/*"],
|
||||
plugins: [vencordDep],
|
||||
plugins: [vencordDep, includeDirPlugin("patches", "src/renderer/patches")],
|
||||
footer: { js: "//# sourceURL=VCDRenderer" }
|
||||
})
|
||||
]);
|
||||
|
||||
25
scripts/build/includeDirPlugin.mts
Normal file
25
scripts/build/includeDirPlugin.mts
Normal file
@@ -0,0 +1,25 @@
|
||||
import { Plugin } from "esbuild";
|
||||
import { readdir } from "fs/promises";
|
||||
|
||||
const makeImportAllCode = (files: string[]) =>
|
||||
files.map(f => `require("./${f.replace(/\.[cm]?[tj]sx?$/, "")}")`).join("\n");
|
||||
|
||||
const makeImportDirRecursiveCode = (dir: string) => readdir(dir).then(files => makeImportAllCode(files));
|
||||
|
||||
export function includeDirPlugin(namespace: string, path: string): Plugin {
|
||||
return {
|
||||
name: `include-dir-plugin:${namespace}`,
|
||||
setup(build) {
|
||||
const filter = new RegExp(`^__${namespace}__$`);
|
||||
|
||||
build.onResolve({ filter }, args => ({ path: args.path, namespace }));
|
||||
|
||||
build.onLoad({ filter, namespace }, async args => {
|
||||
return {
|
||||
contents: await makeImportDirRecursiveCode(path),
|
||||
resolveDir: path
|
||||
};
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -1,9 +1,3 @@
|
||||
/*
|
||||
* SPDX-License-Identifier: GPL-3.0
|
||||
* Vesktop, a desktop app aiming to give you a snappier Discord Experience
|
||||
* Copyright (c) 2023 Vendicated and Vencord contributors
|
||||
*/
|
||||
|
||||
export const VencordFragment = /* #__PURE__*/ Symbol.for("react.fragment");
|
||||
export let VencordCreateElement = (...args) =>
|
||||
(VencordCreateElement = Vencord.Webpack.Common.React.createElement)(...args);
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/*
|
||||
* 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
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
// Based on https://github.com/gergof/electron-builder-sandbox-fix/blob/master/lib/index.js
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/*
|
||||
* 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
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { globalExternalsWithRegExp } from "@fal-works/esbuild-plugin-global-externals";
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* 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
|
||||
* Copyright (c) {year} {author}
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/*
|
||||
* 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
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import "./start";
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/*
|
||||
* 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
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { config } from "dotenv";
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/*
|
||||
* 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
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { spawn as spaaawn, SpawnOptions } from "child_process";
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/*
|
||||
* 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
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { promises as fs } from "node:fs";
|
||||
|
||||
4
src/globals.d.ts
vendored
4
src/globals.d.ts
vendored
@@ -1,13 +1,13 @@
|
||||
/*
|
||||
* SPDX-License-Identifier: GPL-3.0
|
||||
* Vesktop, a desktop app aiming to give you a snappier Discord Experience
|
||||
* Copyright (c) 2023 Vendicated and Vencord contributors
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
declare global {
|
||||
export var VesktopNative: typeof import("preload/VesktopNative").VesktopNative;
|
||||
export var Vesktop: typeof import("renderer/index");
|
||||
export var VCDP: any;
|
||||
export var VesktopPatchGlobals: any;
|
||||
|
||||
export var IS_DEV: boolean;
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/*
|
||||
* 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
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { BrowserWindow } from "electron";
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/*
|
||||
* 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
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { app, NativeImage, nativeImage } from "electron";
|
||||
@@ -29,10 +29,10 @@ export function setBadgeCount(count: number) {
|
||||
break;
|
||||
case "darwin":
|
||||
if (count === 0) {
|
||||
app.dock.setBadge("");
|
||||
app.dock!.setBadge("");
|
||||
break;
|
||||
}
|
||||
app.dock.setBadge(count === -1 ? "•" : count.toString());
|
||||
app.dock!.setBadge(count === -1 ? "•" : count.toString());
|
||||
break;
|
||||
case "win32":
|
||||
const [index, description] = getBadgeIndexAndDescription(count);
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
/*
|
||||
* SPDX-License-Identifier: GPL-3.0
|
||||
* Vesktop, a desktop app aiming to give you a snappier Discord Experience
|
||||
* Copyright (c) 2023 Vendicated and Vencord contributors
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import Server from "arrpc";
|
||||
import { IpcEvents } from "shared/IpcEvents";
|
||||
import { IpcCommands } from "shared/IpcEvents";
|
||||
|
||||
import { mainWin } from "./mainWindow";
|
||||
import { sendRendererCommand } from "./ipcCommands";
|
||||
import { Settings } from "./settings";
|
||||
|
||||
let server: any;
|
||||
@@ -19,16 +19,15 @@ export async function initArRPC() {
|
||||
|
||||
try {
|
||||
server = await new Server();
|
||||
server.on("activity", (data: any) => mainWin.webContents.send(IpcEvents.ARRPC_ACTIVITY, JSON.stringify(data)));
|
||||
server.on("invite", (invite: string, callback: (valid: boolean) => void) => {
|
||||
server.on("activity", (data: any) => sendRendererCommand(IpcCommands.RPC_ACTIVITY, JSON.stringify(data)));
|
||||
server.on("invite", async (invite: string, callback: (valid: boolean) => void) => {
|
||||
invite = String(invite);
|
||||
if (!inviteCodeRegex.test(invite)) return callback(false);
|
||||
|
||||
mainWin.webContents
|
||||
// Safety: Result of JSON.stringify should always be safe to equal
|
||||
// Also, just to be super super safe, invite is regex validated above
|
||||
.executeJavaScript(`Vesktop.openInviteModal(${JSON.stringify(invite)})`)
|
||||
.then(callback);
|
||||
await sendRendererCommand(IpcCommands.RPC_INVITE, invite).then(callback);
|
||||
});
|
||||
server.on("link", async (data: any, deepCallback: (valid: boolean) => void) => {
|
||||
await sendRendererCommand(IpcCommands.RPC_DEEP_LINK, data).then(deepCallback);
|
||||
});
|
||||
} catch (e) {
|
||||
console.error("Failed to start arRPC server", e);
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
/*
|
||||
* SPDX-License-Identifier: GPL-3.0
|
||||
* Vesktop, a desktop app aiming to give you a snappier Discord Experience
|
||||
* Copyright (c) 2023 Vendicated and Vencord contributors
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { app } from "electron";
|
||||
import { existsSync, mkdirSync, renameSync, rmSync, writeFileSync } from "fs";
|
||||
import { join } from "path";
|
||||
import { stripIndent } from "shared/utils/text";
|
||||
|
||||
interface AutoStart {
|
||||
isEnabled(): boolean;
|
||||
@@ -31,15 +32,15 @@ function makeAutoStartLinux(): AutoStart {
|
||||
return {
|
||||
isEnabled: () => existsSync(file),
|
||||
enable() {
|
||||
const desktopFile = `
|
||||
[Desktop Entry]
|
||||
Type=Application
|
||||
Name=Vesktop
|
||||
Comment=Vesktop autostart script
|
||||
Exec=${commandLine}
|
||||
StartupNotify=false
|
||||
Terminal=false
|
||||
`.trim();
|
||||
const desktopFile = stripIndent`
|
||||
[Desktop Entry]
|
||||
Type=Application
|
||||
Name=Vesktop
|
||||
Comment=Vesktop autostart script
|
||||
Exec=${commandLine}
|
||||
StartupNotify=false
|
||||
Terminal=false
|
||||
`;
|
||||
|
||||
mkdirSync(dir, { recursive: true });
|
||||
writeFileSync(file, desktopFile);
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
/*
|
||||
* SPDX-License-Identifier: GPL-3.0
|
||||
* Vesktop, a desktop app aiming to give you a snappier Discord Experience
|
||||
* Copyright (c) 2023 Vendicated and Vencord contributors
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { app } from "electron";
|
||||
import { existsSync, mkdirSync, readdirSync, renameSync, rmdirSync } from "fs";
|
||||
import { existsSync, mkdirSync } from "fs";
|
||||
import { dirname, join } from "path";
|
||||
|
||||
const vesktopDir = dirname(process.execPath);
|
||||
@@ -15,28 +15,11 @@ export const PORTABLE =
|
||||
!process.execPath.toLowerCase().endsWith("electron.exe") &&
|
||||
!existsSync(join(vesktopDir, "Uninstall Vesktop.exe"));
|
||||
|
||||
const LEGACY_DATA_DIR = join(app.getPath("appData"), "VencordDesktop", "VencordDesktop");
|
||||
export const DATA_DIR =
|
||||
process.env.VENCORD_USER_DATA_DIR || (PORTABLE ? join(vesktopDir, "Data") : join(app.getPath("userData")));
|
||||
|
||||
mkdirSync(DATA_DIR, { recursive: true });
|
||||
|
||||
// TODO: remove eventually
|
||||
if (existsSync(LEGACY_DATA_DIR)) {
|
||||
try {
|
||||
console.warn("Detected legacy settings dir", LEGACY_DATA_DIR + ".", "migrating to", DATA_DIR);
|
||||
for (const file of readdirSync(LEGACY_DATA_DIR)) {
|
||||
renameSync(join(LEGACY_DATA_DIR, file), join(DATA_DIR, file));
|
||||
}
|
||||
rmdirSync(LEGACY_DATA_DIR);
|
||||
renameSync(
|
||||
join(app.getPath("appData"), "VencordDesktop", "IndexedDB"),
|
||||
join(DATA_DIR, "sessionData", "IndexedDB")
|
||||
);
|
||||
} catch (e) {
|
||||
console.error("Migration failed", e);
|
||||
}
|
||||
}
|
||||
const SESSION_DATA_DIR = join(DATA_DIR, "sessionData");
|
||||
app.setPath("sessionData", SESSION_DATA_DIR);
|
||||
|
||||
@@ -61,11 +44,11 @@ export const DEFAULT_HEIGHT = 720;
|
||||
|
||||
export const DISCORD_HOSTNAMES = ["discord.com", "canary.discord.com", "ptb.discord.com"];
|
||||
|
||||
const VersionString = `AppleWebKit/537.36 (KHTML, like Gecko) Chrome/${process.versions.chrome.split(".")[0]}.0.0.0 Safari/537.36`;
|
||||
const BrowserUserAgents = {
|
||||
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/126.0.0.0 Safari/537.36",
|
||||
windows:
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36"
|
||||
darwin: `Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) ${VersionString}`,
|
||||
linux: `Mozilla/5.0 (X11; Linux x86_64) ${VersionString}`,
|
||||
windows: `Mozilla/5.0 (Windows NT 10.0; Win64; x64) ${VersionString}`
|
||||
};
|
||||
|
||||
export const BrowserUserAgent = BrowserUserAgents[process.platform] || BrowserUserAgents.windows;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/*
|
||||
* 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
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { app } from "electron";
|
||||
@@ -28,6 +28,7 @@ interface Data {
|
||||
export function createFirstLaunchTour() {
|
||||
const win = new BrowserWindow({
|
||||
...SplashProps,
|
||||
transparent: false,
|
||||
frame: true,
|
||||
autoHideMenuBar: true,
|
||||
height: 470,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/*
|
||||
* 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
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import "./ipc";
|
||||
@@ -17,25 +17,42 @@ import { registerScreenShareHandler } from "./screenShare";
|
||||
import { Settings, State } from "./settings";
|
||||
import { isDeckGameMode } from "./utils/steamOS";
|
||||
|
||||
if (IS_DEV) {
|
||||
require("source-map-support").install();
|
||||
} else {
|
||||
if (!IS_DEV) {
|
||||
autoUpdater.checkForUpdatesAndNotify();
|
||||
}
|
||||
|
||||
console.log("Vesktop v" + app.getVersion());
|
||||
|
||||
// Make the Vencord files use our DATA_DIR
|
||||
process.env.VENCORD_USER_DATA_DIR = DATA_DIR;
|
||||
|
||||
const isLinux = process.platform === "linux";
|
||||
|
||||
export let enableHardwareAcceleration = true;
|
||||
|
||||
function init() {
|
||||
const { disableSmoothScroll, hardwareAcceleration } = Settings.store;
|
||||
app.setAsDefaultProtocolClient("discord");
|
||||
|
||||
const enabledFeatures = app.commandLine.getSwitchValue("enable-features").split(",");
|
||||
const disabledFeatures = app.commandLine.getSwitchValue("disable-features").split(",");
|
||||
const { disableSmoothScroll, hardwareAcceleration, hardwareVideoAcceleration } = Settings.store;
|
||||
|
||||
if (hardwareAcceleration === false) {
|
||||
const enabledFeatures = new Set(app.commandLine.getSwitchValue("enable-features").split(","));
|
||||
const disabledFeatures = new Set(app.commandLine.getSwitchValue("disable-features").split(","));
|
||||
app.commandLine.removeSwitch("enable-features");
|
||||
app.commandLine.removeSwitch("disable-features");
|
||||
|
||||
if (hardwareAcceleration === false || process.argv.includes("--disable-gpu")) {
|
||||
enableHardwareAcceleration = false;
|
||||
app.disableHardwareAcceleration();
|
||||
} else {
|
||||
enabledFeatures.push("VaapiVideoDecodeLinuxGL", "VaapiVideoEncoder", "VaapiVideoDecoder");
|
||||
if (hardwareVideoAcceleration) {
|
||||
enabledFeatures.add("AcceleratedVideoEncoder");
|
||||
enabledFeatures.add("AcceleratedVideoDecoder");
|
||||
|
||||
if (isLinux) {
|
||||
enabledFeatures.add("AcceleratedVideoDecodeLinuxGL");
|
||||
enabledFeatures.add("AcceleratedVideoDecodeLinuxZeroCopyGL");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (disableSmoothScroll) {
|
||||
@@ -49,19 +66,42 @@ function init() {
|
||||
app.commandLine.appendSwitch("disable-background-timer-throttling");
|
||||
app.commandLine.appendSwitch("disable-backgrounding-occluded-windows");
|
||||
if (process.platform === "win32") {
|
||||
disabledFeatures.push("CalculateNativeWinOcclusion");
|
||||
disabledFeatures.add("CalculateNativeWinOcclusion");
|
||||
}
|
||||
|
||||
// work around chrome 66 disabling autoplay by default
|
||||
app.commandLine.appendSwitch("autoplay-policy", "no-user-gesture-required");
|
||||
// WinRetrieveSuggestionsOnlyOnDemand: Work around electron 13 bug w/ async spellchecking on Windows.
|
||||
// HardwareMediaKeyHandling,MediaSessionService: Prevent Discord from registering as a media service.
|
||||
//
|
||||
// WidgetLayering (Vencord Added): Fix DevTools context menus https://github.com/electron/electron/issues/38790
|
||||
disabledFeatures.push("WinRetrieveSuggestionsOnlyOnDemand", "HardwareMediaKeyHandling", "MediaSessionService");
|
||||
|
||||
app.commandLine.appendSwitch("enable-features", [...new Set(enabledFeatures)].filter(Boolean).join(","));
|
||||
app.commandLine.appendSwitch("disable-features", [...new Set(disabledFeatures)].filter(Boolean).join(","));
|
||||
// WinRetrieveSuggestionsOnlyOnDemand: Work around electron 13 bug w/ async spellchecking on Windows.
|
||||
// HardwareMediaKeyHandling, MediaSessionService: Prevent Discord from registering as a media service.
|
||||
disabledFeatures.add("WinRetrieveSuggestionsOnlyOnDemand");
|
||||
disabledFeatures.add("HardwareMediaKeyHandling");
|
||||
disabledFeatures.add("MediaSessionService");
|
||||
|
||||
if (isLinux) {
|
||||
// Support TTS on Linux using https://wiki.archlinux.org/title/Speech_dispatcher
|
||||
app.commandLine.appendSwitch("enable-speech-dispatcher");
|
||||
|
||||
// Work around Gtk-ERROR: GTK 2/3 symbols detected. Using GTK 2/3 and GTK 4 in the same process is not supported
|
||||
// https://github.com/electron/electron/issues/46538
|
||||
// TODO: Remove this when upstream fixes it
|
||||
app.commandLine.appendSwitch("gtk-version", "3");
|
||||
}
|
||||
|
||||
disabledFeatures.forEach(feat => enabledFeatures.delete(feat));
|
||||
|
||||
const enabledFeaturesArray = enabledFeatures.values().filter(Boolean).toArray();
|
||||
const disabledFeaturesArray = disabledFeatures.values().filter(Boolean).toArray();
|
||||
|
||||
if (enabledFeaturesArray.length) {
|
||||
app.commandLine.appendSwitch("enable-features", enabledFeaturesArray.join(","));
|
||||
console.log("Enabled Chromium features:", enabledFeaturesArray.join(", "));
|
||||
}
|
||||
|
||||
if (disabledFeaturesArray.length) {
|
||||
app.commandLine.appendSwitch("disable-features", disabledFeaturesArray.join(","));
|
||||
console.log("Disabled Chromium features:", disabledFeaturesArray.join(", "));
|
||||
}
|
||||
|
||||
// In the Flatpak on SteamOS the theme is detected as light, but SteamOS only has a dark mode, so we just override it
|
||||
if (isDeckGameMode) nativeTheme.themeSource = "dark";
|
||||
@@ -109,6 +149,12 @@ async function bootstrap() {
|
||||
}
|
||||
}
|
||||
|
||||
// MacOS only event
|
||||
export let darwinURL: string | undefined;
|
||||
app.on("open-url", (_, url) => {
|
||||
darwinURL = url;
|
||||
});
|
||||
|
||||
app.on("window-all-closed", () => {
|
||||
if (process.platform !== "darwin") app.quit();
|
||||
});
|
||||
|
||||
@@ -1,15 +1,26 @@
|
||||
/*
|
||||
* 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
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
if (process.platform === "linux") import("./venmic");
|
||||
|
||||
import { execFile } from "child_process";
|
||||
import { app, BrowserWindow, clipboard, dialog, nativeImage, RelaunchOptions, session, shell } from "electron";
|
||||
import {
|
||||
app,
|
||||
BrowserWindow,
|
||||
clipboard,
|
||||
dialog,
|
||||
IpcMainInvokeEvent,
|
||||
nativeImage,
|
||||
RelaunchOptions,
|
||||
session,
|
||||
shell
|
||||
} from "electron";
|
||||
import { mkdirSync, readFileSync, watch } from "fs";
|
||||
import { open, readFile } from "fs/promises";
|
||||
import { enableHardwareAcceleration } from "main";
|
||||
import { release } from "os";
|
||||
import { join } from "path";
|
||||
import { debounce } from "shared/utils/debounce";
|
||||
@@ -35,6 +46,7 @@ handleSync(IpcEvents.GET_RENDERER_CSS_FILE, () => join(__dirname, "renderer.css"
|
||||
|
||||
handleSync(IpcEvents.GET_SETTINGS, () => Settings.plain);
|
||||
handleSync(IpcEvents.GET_VERSION, () => app.getVersion());
|
||||
handleSync(IpcEvents.GET_ENABLE_HARDWARE_ACCELERATION, () => enableHardwareAcceleration);
|
||||
|
||||
handleSync(
|
||||
IpcEvents.SUPPORTS_WINDOWS_TRANSPARENCY,
|
||||
@@ -68,28 +80,29 @@ handle(IpcEvents.SHOW_ITEM_IN_FOLDER, (_, path) => {
|
||||
shell.showItemInFolder(path);
|
||||
});
|
||||
|
||||
function getWindow(e: IpcMainInvokeEvent, key?: string) {
|
||||
return key ? PopoutWindows.get(key)! : (BrowserWindow.fromWebContents(e.sender) ?? mainWin);
|
||||
}
|
||||
|
||||
handle(IpcEvents.FOCUS, () => {
|
||||
mainWin.show();
|
||||
mainWin.setSkipTaskbar(false);
|
||||
});
|
||||
|
||||
handle(IpcEvents.CLOSE, (e, key?: string) => {
|
||||
const popout = PopoutWindows.get(key!);
|
||||
if (popout) return popout.close();
|
||||
|
||||
const win = BrowserWindow.fromWebContents(e.sender) ?? e.sender;
|
||||
win.close();
|
||||
getWindow(e, key).close();
|
||||
});
|
||||
|
||||
handle(IpcEvents.MINIMIZE, e => {
|
||||
mainWin.minimize();
|
||||
handle(IpcEvents.MINIMIZE, (e, key?: string) => {
|
||||
getWindow(e, key).minimize();
|
||||
});
|
||||
|
||||
handle(IpcEvents.MAXIMIZE, e => {
|
||||
if (mainWin.isMaximized()) {
|
||||
mainWin.unmaximize();
|
||||
handle(IpcEvents.MAXIMIZE, (e, key?: string) => {
|
||||
const win = getWindow(e, key);
|
||||
if (win.isMaximized()) {
|
||||
win.unmaximize();
|
||||
} else {
|
||||
mainWin.maximize();
|
||||
win.maximize();
|
||||
}
|
||||
});
|
||||
|
||||
@@ -135,6 +148,17 @@ handle(IpcEvents.CLIPBOARD_COPY_IMAGE, async (_, buf: ArrayBuffer, src: string)
|
||||
});
|
||||
});
|
||||
|
||||
function openDebugPage(page: string) {
|
||||
const win = new BrowserWindow({
|
||||
autoHideMenuBar: true
|
||||
});
|
||||
|
||||
win.loadURL(page);
|
||||
}
|
||||
|
||||
handle(IpcEvents.DEBUG_LAUNCH_GPU, () => openDebugPage("chrome://gpu"));
|
||||
handle(IpcEvents.DEBUG_LAUNCH_WEBRTC_INTERNALS, () => openDebugPage("chrome://webrtc-internals"));
|
||||
|
||||
function readCss() {
|
||||
return readFile(VENCORD_QUICKCSS_FILE, "utf-8").catch(() => "");
|
||||
}
|
||||
|
||||
61
src/main/ipcCommands.ts
Normal file
61
src/main/ipcCommands.ts
Normal file
@@ -0,0 +1,61 @@
|
||||
/*
|
||||
* Vesktop, a desktop app aiming to give you a snappier Discord Experience
|
||||
* Copyright (c) 2025 Vendicated and Vencord contributors
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { randomUUID } from "crypto";
|
||||
import { ipcMain } from "electron";
|
||||
import { IpcEvents } from "shared/IpcEvents";
|
||||
|
||||
import { mainWin } from "./mainWindow";
|
||||
|
||||
const resolvers = new Map<string, Record<"resolve" | "reject", (data: any) => void>>();
|
||||
|
||||
export interface IpcMessage {
|
||||
nonce: string;
|
||||
message: string;
|
||||
data?: any;
|
||||
}
|
||||
|
||||
export interface IpcResponse {
|
||||
nonce: string;
|
||||
ok: boolean;
|
||||
data?: any;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a message to the renderer process and waits for a response.
|
||||
* `data` must be serializable as it will be sent over IPC.
|
||||
*
|
||||
* You must add a handler for the message in the renderer process.
|
||||
*/
|
||||
export function sendRendererCommand<T = any>(message: string, data?: any) {
|
||||
if (mainWin.isDestroyed()) {
|
||||
console.warn("Main window is destroyed, cannot send IPC command:", message);
|
||||
return Promise.reject(new Error("Main window is destroyed"));
|
||||
}
|
||||
|
||||
const nonce = randomUUID();
|
||||
|
||||
const promise = new Promise<T>((resolve, reject) => {
|
||||
resolvers.set(nonce, { resolve, reject });
|
||||
});
|
||||
|
||||
mainWin.webContents.send(IpcEvents.IPC_COMMAND, { nonce, message, data });
|
||||
|
||||
return promise;
|
||||
}
|
||||
|
||||
ipcMain.on(IpcEvents.IPC_COMMAND, (_event, { nonce, ok, data }: IpcResponse) => {
|
||||
const resolver = resolvers.get(nonce);
|
||||
if (!resolver) throw new Error(`Unknown message: ${nonce}`);
|
||||
|
||||
if (ok) {
|
||||
resolver.resolve(data);
|
||||
} else {
|
||||
resolver.reject(data);
|
||||
}
|
||||
|
||||
resolvers.delete(nonce);
|
||||
});
|
||||
@@ -1,7 +1,7 @@
|
||||
/*
|
||||
* 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
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import {
|
||||
@@ -16,9 +16,10 @@ import {
|
||||
session,
|
||||
Tray
|
||||
} from "electron";
|
||||
import { EventEmitter } from "events";
|
||||
import { rm } from "fs/promises";
|
||||
import { join } from "path";
|
||||
import { IpcEvents } from "shared/IpcEvents";
|
||||
import { IpcCommands, IpcEvents } from "shared/IpcEvents";
|
||||
import { isTruthy } from "shared/utils/guards";
|
||||
import { once } from "shared/utils/once";
|
||||
import type { SettingsStore } from "shared/utils/SettingsStore";
|
||||
@@ -36,8 +37,10 @@ import {
|
||||
MIN_WIDTH,
|
||||
VENCORD_FILES_DIR
|
||||
} from "./constants";
|
||||
import { darwinURL } from "./index";
|
||||
import { sendRendererCommand } from "./ipcCommands";
|
||||
import { Settings, State, VencordSettings } from "./settings";
|
||||
import { createSplashWindow } from "./splash";
|
||||
import { createSplashWindow, updateSplashMessage } from "./splash";
|
||||
import { makeLinksOpenExternally } from "./utils/makeLinksOpenExternally";
|
||||
import { applyDeckKeyboardFix, askToApplySteamLayout, isDeckGameMode } from "./utils/steamOS";
|
||||
import { downloadVencordFiles, ensureVencordFiles } from "./utils/vencordLoader";
|
||||
@@ -198,9 +201,7 @@ function initMenuBar(win: BrowserWindow) {
|
||||
label: "Settings",
|
||||
accelerator: "CmdOrCtrl+,",
|
||||
async click() {
|
||||
mainWin.webContents.executeJavaScript(
|
||||
"Vencord.Webpack.Common.SettingsRouter.open('My Account')"
|
||||
);
|
||||
sendRendererCommand(IpcCommands.NAVIGATE_SETTINGS);
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -245,7 +246,7 @@ function initMenuBar(win: BrowserWindow) {
|
||||
}
|
||||
] satisfies MenuItemList;
|
||||
|
||||
const menu = Menu.buildFromTemplate([
|
||||
const menuItems = [
|
||||
{
|
||||
label: "Vesktop",
|
||||
role: "appMenu",
|
||||
@@ -254,8 +255,10 @@ function initMenuBar(win: BrowserWindow) {
|
||||
{ role: "fileMenu" },
|
||||
{ role: "editMenu" },
|
||||
{ role: "viewMenu" },
|
||||
{ role: "windowMenu" }
|
||||
]);
|
||||
isDarwin && { role: "windowMenu" }
|
||||
] satisfies MenuItemList;
|
||||
|
||||
const menu = Menu.buildFromTemplate(menuItems.filter(isTruthy));
|
||||
|
||||
Menu.setApplicationMenu(menu);
|
||||
}
|
||||
@@ -271,7 +274,7 @@ function getWindowBoundsOptions(): BrowserWindowConstructorOptions {
|
||||
height: height ?? DEFAULT_HEIGHT
|
||||
} as BrowserWindowConstructorOptions;
|
||||
|
||||
const storedDisplay = screen.getAllDisplays().find(display => display.id === State.store.displayid);
|
||||
const storedDisplay = screen.getAllDisplays().find(display => display.id === State.store.displayId);
|
||||
|
||||
if (x != null && y != null && storedDisplay) {
|
||||
options.x = x;
|
||||
@@ -299,7 +302,7 @@ function getDarwinOptions(): BrowserWindowConstructorOptions {
|
||||
options.vibrancy = "sidebar";
|
||||
options.backgroundColor = "#ffffff00";
|
||||
} else {
|
||||
if (splashTheming) {
|
||||
if (splashTheming !== false) {
|
||||
options.backgroundColor = splashBackground;
|
||||
} else {
|
||||
options.backgroundColor = nativeTheme.shouldUseDarkColors ? "#313338" : "#ffffff";
|
||||
@@ -321,7 +324,7 @@ function initWindowBoundsListeners(win: BrowserWindow) {
|
||||
|
||||
const saveBounds = () => {
|
||||
State.store.windowBounds = win.getBounds();
|
||||
State.store.displayid = screen.getDisplayMatching(State.store.windowBounds).id;
|
||||
State.store.displayId = screen.getDisplayMatching(State.store.windowBounds).id;
|
||||
};
|
||||
|
||||
win.on("resize", saveBounds);
|
||||
@@ -333,6 +336,7 @@ function initSettingsListeners(win: BrowserWindow) {
|
||||
if (enable) initTray(win);
|
||||
else tray?.destroy();
|
||||
});
|
||||
|
||||
addSettingsListener("disableMinSize", disable => {
|
||||
if (disable) {
|
||||
// 0 no work
|
||||
@@ -366,7 +370,7 @@ function initSettingsListeners(win: BrowserWindow) {
|
||||
}
|
||||
|
||||
async function initSpellCheckLanguages(win: BrowserWindow, languages?: string[]) {
|
||||
languages ??= await win.webContents.executeJavaScript("[...new Set(navigator.languages)]").catch(() => []);
|
||||
languages ??= await sendRendererCommand(IpcCommands.GET_LANGUAGES);
|
||||
if (!languages) return;
|
||||
|
||||
const ses = session.defaultSession;
|
||||
@@ -384,19 +388,38 @@ function initSpellCheck(win: BrowserWindow) {
|
||||
initSpellCheckLanguages(win, Settings.store.spellCheckLanguages);
|
||||
}
|
||||
|
||||
function initStaticTitle(win: BrowserWindow) {
|
||||
const listener = (e: { preventDefault: Function }) => e.preventDefault();
|
||||
|
||||
if (Settings.store.staticTitle) win.on("page-title-updated", listener);
|
||||
|
||||
addSettingsListener("staticTitle", enabled => {
|
||||
if (enabled) {
|
||||
win.setTitle("Vesktop");
|
||||
win.on("page-title-updated", listener);
|
||||
} else {
|
||||
win.off("page-title-updated", listener);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function createMainWindow() {
|
||||
// Clear up previous settings listeners
|
||||
removeSettingsListeners();
|
||||
removeVencordSettingsListeners();
|
||||
|
||||
const { staticTitle, transparencyOption, enableMenu, customTitleBar } = Settings.store;
|
||||
const { staticTitle, transparencyOption, enableMenu, customTitleBar, splashTheming, splashBackground } =
|
||||
Settings.store;
|
||||
|
||||
const { frameless, transparent } = VencordSettings.store;
|
||||
|
||||
const noFrame = frameless === true || customTitleBar === true;
|
||||
const backgroundColor =
|
||||
splashTheming !== false ? splashBackground : nativeTheme.shouldUseDarkColors ? "#313338" : "#ffffff";
|
||||
|
||||
const win = (mainWin = new BrowserWindow({
|
||||
show: false,
|
||||
show: Settings.store.enableSplashScreen === false,
|
||||
backgroundColor,
|
||||
webPreferences: {
|
||||
nodeIntegration: false,
|
||||
sandbox: false,
|
||||
@@ -444,44 +467,67 @@ function createMainWindow() {
|
||||
return false;
|
||||
});
|
||||
|
||||
if (Settings.store.staticTitle) win.on("page-title-updated", e => e.preventDefault());
|
||||
|
||||
initWindowBoundsListeners(win);
|
||||
if (!isDeckGameMode && (Settings.store.tray ?? true) && process.platform !== "darwin") initTray(win);
|
||||
initMenuBar(win);
|
||||
makeLinksOpenExternally(win);
|
||||
initSettingsListeners(win);
|
||||
initSpellCheck(win);
|
||||
initStaticTitle(win);
|
||||
|
||||
win.webContents.setUserAgent(BrowserUserAgent);
|
||||
|
||||
const subdomain =
|
||||
Settings.store.discordBranch === "canary" || Settings.store.discordBranch === "ptb"
|
||||
? `${Settings.store.discordBranch}.`
|
||||
: "";
|
||||
|
||||
win.loadURL(`https://${subdomain}discord.com/app`);
|
||||
// if the open-url event is fired (in index.ts) while starting up, darwinURL will be set. If not fall back to checking the process args (which Windows and Linux use for URI calling.)
|
||||
// win.webContents.session.clearCache().then(() => {
|
||||
loadUrl(darwinURL || process.argv.find(arg => arg.startsWith("discord://")));
|
||||
// });
|
||||
|
||||
return win;
|
||||
}
|
||||
|
||||
const runVencordMain = once(() => require(join(VENCORD_FILES_DIR, "vencordDesktopMain.js")));
|
||||
|
||||
const loadEvents = new EventEmitter();
|
||||
|
||||
export function loadUrl(uri: string | undefined) {
|
||||
const branch = Settings.store.discordBranch;
|
||||
const subdomain = branch === "canary" || branch === "ptb" ? `${branch}.` : "";
|
||||
|
||||
// we do not rely on 'did-finish-load' because it fires even if loadURL fails which triggers early detruction of the splash
|
||||
mainWin
|
||||
.loadURL(`https://${subdomain}discord.com/${uri ? new URL(uri).pathname.slice(1) || "app" : "app"}`)
|
||||
.then(() => loadEvents.emit("app-loaded"))
|
||||
.catch(error => retryUrl(error.url, error.code));
|
||||
}
|
||||
|
||||
const retryDelay = 1000;
|
||||
function retryUrl(url: string, description: string) {
|
||||
console.log(`retrying in ${retryDelay}ms`);
|
||||
updateSplashMessage(`Failed to load Discord: ${description}`);
|
||||
setTimeout(() => loadUrl(url), retryDelay);
|
||||
}
|
||||
|
||||
export async function createWindows() {
|
||||
const startMinimized = process.argv.includes("--start-minimized");
|
||||
const splash = createSplashWindow(startMinimized);
|
||||
// SteamOS letterboxes and scales it terribly, so just full screen it
|
||||
if (isDeckGameMode) splash.setFullScreen(true);
|
||||
|
||||
let splash: BrowserWindow | undefined;
|
||||
if (Settings.store.enableSplashScreen !== false) {
|
||||
splash = createSplashWindow(startMinimized);
|
||||
|
||||
// SteamOS letterboxes and scales it terribly, so just full screen it
|
||||
if (isDeckGameMode) splash.setFullScreen(true);
|
||||
}
|
||||
|
||||
await ensureVencordFiles();
|
||||
runVencordMain();
|
||||
|
||||
mainWin = createMainWindow();
|
||||
|
||||
mainWin.webContents.on("did-finish-load", () => {
|
||||
splash.destroy();
|
||||
loadEvents.on("app-loaded", () => {
|
||||
splash?.destroy();
|
||||
|
||||
if (!startMinimized) {
|
||||
mainWin!.show();
|
||||
if (splash) mainWin!.show();
|
||||
if (State.store.maximized && !isDeckGameMode) mainWin!.maximize();
|
||||
}
|
||||
|
||||
@@ -499,5 +545,15 @@ export async function createWindows() {
|
||||
});
|
||||
});
|
||||
|
||||
mainWin.webContents.on("did-navigate", (_, url: string, responseCode: number) => {
|
||||
updateSplashMessage(""); // clear the splash message
|
||||
|
||||
// check url to ensure app doesn't loop
|
||||
if (responseCode >= 300 && new URL(url).pathname !== `/app`) {
|
||||
loadUrl(undefined);
|
||||
console.warn(`'did-navigate': Caught bad page response: ${responseCode}, redirecting to main app`);
|
||||
}
|
||||
});
|
||||
|
||||
initArRPC();
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/*
|
||||
* 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
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { session, systemPreferences } from "electron";
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
/*
|
||||
* 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
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { desktopCapturer, session, Streams } from "electron";
|
||||
import type { StreamPick } from "renderer/components/ScreenSharePicker";
|
||||
import { IpcEvents } from "shared/IpcEvents";
|
||||
import { IpcCommands, IpcEvents } from "shared/IpcEvents";
|
||||
|
||||
import { sendRendererCommand } from "./ipcCommands";
|
||||
import { handle } from "./utils/ipcWrappers";
|
||||
|
||||
const isWayland =
|
||||
@@ -49,11 +50,11 @@ export function registerScreenShareHandler() {
|
||||
if (isWayland) {
|
||||
const video = data[0];
|
||||
if (video) {
|
||||
const stream = await request.frame
|
||||
.executeJavaScript(
|
||||
`Vesktop.Components.ScreenShare.openScreenSharePicker(${JSON.stringify([video])},true)`
|
||||
)
|
||||
.catch(() => null);
|
||||
const stream = await sendRendererCommand<StreamPick>(IpcCommands.SCREEN_SHARE_PICKER, {
|
||||
screens: [video],
|
||||
skipPicker: true
|
||||
}).catch(() => null);
|
||||
|
||||
if (stream === null) return callback({});
|
||||
}
|
||||
|
||||
@@ -61,13 +62,13 @@ export function registerScreenShareHandler() {
|
||||
return;
|
||||
}
|
||||
|
||||
const choice = await request.frame
|
||||
.executeJavaScript(`Vesktop.Components.ScreenShare.openScreenSharePicker(${JSON.stringify(data)})`)
|
||||
.then(e => e as StreamPick)
|
||||
.catch(e => {
|
||||
console.error("Error during screenshare picker", e);
|
||||
return null;
|
||||
});
|
||||
const choice = await sendRendererCommand<StreamPick>(IpcCommands.SCREEN_SHARE_PICKER, {
|
||||
screens: data,
|
||||
skipPicker: false
|
||||
}).catch(e => {
|
||||
console.error("Error during screenshare picker", e);
|
||||
return null;
|
||||
});
|
||||
|
||||
if (!choice) return callback({});
|
||||
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
/*
|
||||
* 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
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
|
||||
import { mkdirSync, readFileSync, writeFileSync } from "fs";
|
||||
import { dirname, join } from "path";
|
||||
import type { Settings as TSettings, State as TState } from "shared/settings";
|
||||
import { SettingsStore } from "shared/utils/SettingsStore";
|
||||
@@ -35,18 +35,5 @@ function loadSettings<T extends object = any>(file: string, name: string) {
|
||||
}
|
||||
|
||||
export const Settings = loadSettings<TSettings>(SETTINGS_FILE, "Vesktop settings");
|
||||
|
||||
export const VencordSettings = loadSettings<any>(VENCORD_SETTINGS_FILE, "Vencord settings");
|
||||
|
||||
if (Object.hasOwn(Settings.plain, "firstLaunch") && !existsSync(STATE_FILE)) {
|
||||
console.warn("legacy state in settings.json detected. migrating to state.json");
|
||||
const state = {} as TState;
|
||||
for (const prop of ["firstLaunch", "maximized", "minimized", "steamOSLayoutVersion", "windowBounds"] as const) {
|
||||
state[prop] = Settings.plain[prop];
|
||||
delete Settings.plain[prop];
|
||||
}
|
||||
Settings.markAsChanged();
|
||||
writeFileSync(STATE_FILE, JSON.stringify(state, null, 4));
|
||||
}
|
||||
|
||||
export const State = loadSettings<TState>(STATE_FILE, "Vesktop state");
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/*
|
||||
* 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
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { BrowserWindow } from "electron";
|
||||
@@ -11,18 +11,23 @@ import { ICON_PATH, VIEW_DIR } from "shared/paths";
|
||||
|
||||
import { Settings } from "./settings";
|
||||
|
||||
let splash: BrowserWindow | undefined;
|
||||
|
||||
export function createSplashWindow(startMinimized = false) {
|
||||
const splash = new BrowserWindow({
|
||||
splash = new BrowserWindow({
|
||||
...SplashProps,
|
||||
icon: ICON_PATH,
|
||||
show: !startMinimized
|
||||
show: !startMinimized,
|
||||
webPreferences: {
|
||||
preload: join(__dirname, "splashPreload.js")
|
||||
}
|
||||
});
|
||||
|
||||
splash.loadFile(join(VIEW_DIR, "splash.html"));
|
||||
|
||||
const { splashBackground, splashColor, splashTheming } = Settings.store;
|
||||
|
||||
if (splashTheming) {
|
||||
if (splashTheming !== false) {
|
||||
if (splashColor) {
|
||||
const semiTransparentSplashColor = splashColor.replace("rgb(", "rgba(").replace(")", ", 0.2)");
|
||||
|
||||
@@ -37,3 +42,7 @@ export function createSplashWindow(startMinimized = false) {
|
||||
|
||||
return splash;
|
||||
}
|
||||
|
||||
export function updateSplashMessage(message: string) {
|
||||
if (splash && !splash.isDestroyed()) splash.webContents.send("update-splash-message", message);
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/*
|
||||
* 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
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { createWriteStream } from "fs";
|
||||
|
||||
@@ -1,30 +1,40 @@
|
||||
/*
|
||||
* 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
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { ipcMain, IpcMainEvent, IpcMainInvokeEvent, WebFrameMain } from "electron";
|
||||
import { DISCORD_HOSTNAMES } from "main/constants";
|
||||
import { IpcEvents } from "shared/IpcEvents";
|
||||
|
||||
export function validateSender(frame: WebFrameMain) {
|
||||
const { hostname, protocol } = new URL(frame.url);
|
||||
export function validateSender(frame: WebFrameMain | null, event: string) {
|
||||
if (!frame) throw new Error(`ipc[${event}]: No sender frame`);
|
||||
if (!frame.url) return;
|
||||
|
||||
try {
|
||||
var { hostname, protocol } = new URL(frame.url);
|
||||
} catch (e) {
|
||||
throw new Error(`ipc[${event}]: Invalid URL ${frame.url}`);
|
||||
}
|
||||
|
||||
if (protocol === "file:") return;
|
||||
|
||||
if (!DISCORD_HOSTNAMES.includes(hostname)) throw new Error("ipc: Disallowed host " + hostname);
|
||||
if (!DISCORD_HOSTNAMES.includes(hostname)) {
|
||||
throw new Error(`ipc[${event}]: Disallowed hostname ${hostname}`);
|
||||
}
|
||||
}
|
||||
|
||||
export function handleSync(event: IpcEvents, cb: (e: IpcMainEvent, ...args: any[]) => any) {
|
||||
ipcMain.on(event, (e, ...args) => {
|
||||
validateSender(e.senderFrame);
|
||||
validateSender(e.senderFrame, event);
|
||||
e.returnValue = cb(e, ...args);
|
||||
});
|
||||
}
|
||||
|
||||
export function handle(event: IpcEvents, cb: (e: IpcMainInvokeEvent, ...args: any[]) => any) {
|
||||
ipcMain.handle(event, (e, ...args) => {
|
||||
validateSender(e.senderFrame);
|
||||
validateSender(e.senderFrame, event);
|
||||
return cb(e, ...args);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/*
|
||||
* 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
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { BrowserWindow, shell } from "electron";
|
||||
@@ -50,7 +50,7 @@ export function handleExternalUrl(url: string, protocol?: string): { action: "de
|
||||
export function makeLinksOpenExternally(win: BrowserWindow) {
|
||||
win.webContents.setWindowOpenHandler(({ url, frameName, features }) => {
|
||||
try {
|
||||
var { protocol, hostname, pathname } = new URL(url);
|
||||
var { protocol, hostname, pathname, searchParams } = new URL(url);
|
||||
} catch {
|
||||
return { action: "deny" };
|
||||
}
|
||||
@@ -59,8 +59,10 @@ export function makeLinksOpenExternally(win: BrowserWindow) {
|
||||
return createOrFocusPopup(frameName, features);
|
||||
}
|
||||
|
||||
if (url === "about:blank" || (frameName === "authorize" && DISCORD_HOSTNAMES.includes(hostname)))
|
||||
return { action: "allow" };
|
||||
if (url === "about:blank") return { action: "allow" };
|
||||
|
||||
// Drop the static temp page Discord web loads for the connections popout
|
||||
if (frameName === "authorize" && searchParams.get("loading") === "true") return { action: "deny" };
|
||||
|
||||
return handleExternalUrl(url, protocol);
|
||||
});
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/*
|
||||
* 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
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { BrowserWindow, BrowserWindowConstructorOptions } from "electron";
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/*
|
||||
* 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
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { BrowserWindow, dialog } from "electron";
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
/*
|
||||
* SPDX-License-Identifier: GPL-3.0
|
||||
* Vesktop, a desktop app aiming to give you a snappier Discord Experience
|
||||
* Copyright (c) 2023 Vendicated and Vencord contributors
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { mkdirSync } from "fs";
|
||||
import { access, constants as FsConstants } from "fs/promises";
|
||||
import { access, constants as FsConstants, writeFile } from "fs/promises";
|
||||
import { join } from "path";
|
||||
|
||||
import { USER_AGENT, VENCORD_FILES_DIR } from "../constants";
|
||||
@@ -63,7 +63,8 @@ const existsAsync = (path: string) =>
|
||||
.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));
|
||||
const results = await Promise.all(["package.json", ...FILES_TO_DOWNLOAD].map(f => existsAsync(join(dir, f))));
|
||||
return !results.includes(false);
|
||||
}
|
||||
|
||||
export async function ensureVencordFiles() {
|
||||
@@ -71,5 +72,5 @@ export async function ensureVencordFiles() {
|
||||
|
||||
mkdirSync(VENCORD_FILES_DIR, { recursive: true });
|
||||
|
||||
await downloadVencordFiles();
|
||||
await Promise.all([downloadVencordFiles(), writeFile(join(VENCORD_FILES_DIR, "package.json"), "{}")]);
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/*
|
||||
* 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
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import type { LinkData, Node, PatchBay as PatchBayType } from "@vencord/venmic";
|
||||
@@ -72,7 +72,7 @@ ipcMain.handle(IpcEvents.VIRT_MIC_LIST, () => {
|
||||
const { granularSelect } = Settings.store.audio ?? {};
|
||||
|
||||
const targets = obtainVenmic()
|
||||
?.list(granularSelect ? ["application.process.id"] : undefined)
|
||||
?.list(granularSelect ? ["node.name"] : undefined)
|
||||
.filter(s => s["application.process.id"] !== audioPid);
|
||||
|
||||
return targets ? { ok: true, targets, hasPipewirePulse } : { ok: false, isGlibCxxOutdated };
|
||||
|
||||
10
src/module.d.ts
vendored
Normal file
10
src/module.d.ts
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
/*
|
||||
* Vesktop, a desktop app aiming to give you a snappier Discord Experience
|
||||
* Copyright (c) 2025 Vendicated and Vesktop contributors
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
declare module "__patches__" {
|
||||
const never: never;
|
||||
export default never;
|
||||
}
|
||||
@@ -1,11 +1,12 @@
|
||||
/*
|
||||
* 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
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { Node } from "@vencord/venmic";
|
||||
import { ipcRenderer } from "electron";
|
||||
import { IpcMessage, IpcResponse } from "main/ipcCommands";
|
||||
import type { Settings } from "shared/settings";
|
||||
|
||||
import { IpcEvents } from "../shared/IpcEvents";
|
||||
@@ -24,7 +25,8 @@ export const VesktopNative = {
|
||||
relaunch: () => invoke<void>(IpcEvents.RELAUNCH),
|
||||
getVersion: () => sendSync<void>(IpcEvents.GET_VERSION),
|
||||
setBadgeCount: (count: number) => invoke<void>(IpcEvents.SET_BADGE_COUNT, count),
|
||||
supportsWindowsTransparency: () => sendSync<boolean>(IpcEvents.SUPPORTS_WINDOWS_TRANSPARENCY)
|
||||
supportsWindowsTransparency: () => sendSync<boolean>(IpcEvents.SUPPORTS_WINDOWS_TRANSPARENCY),
|
||||
getEnableHardwareAcceleration: () => sendSync<boolean>(IpcEvents.GET_ENABLE_HARDWARE_ACCELERATION)
|
||||
},
|
||||
autostart: {
|
||||
isEnabled: () => sendSync<boolean>(IpcEvents.AUTOSTART_ENABLED),
|
||||
@@ -54,8 +56,8 @@ export const VesktopNative = {
|
||||
win: {
|
||||
focus: () => invoke<void>(IpcEvents.FOCUS),
|
||||
close: (key?: string) => invoke<void>(IpcEvents.CLOSE, key),
|
||||
minimize: () => invoke<void>(IpcEvents.MINIMIZE),
|
||||
maximize: () => invoke<void>(IpcEvents.MAXIMIZE)
|
||||
minimize: (key?: string) => invoke<void>(IpcEvents.MINIMIZE, key),
|
||||
maximize: (key?: string) => invoke<void>(IpcEvents.MAXIMIZE, key)
|
||||
},
|
||||
capturer: {
|
||||
getLargeThumbnail: (id: string) => invoke<string>(IpcEvents.CAPTURER_GET_LARGE_THUMBNAIL, id)
|
||||
@@ -70,13 +72,18 @@ export const VesktopNative = {
|
||||
startSystem: (exclude: Node[]) => invoke<void>(IpcEvents.VIRT_MIC_START_SYSTEM, exclude),
|
||||
stop: () => invoke<void>(IpcEvents.VIRT_MIC_STOP)
|
||||
},
|
||||
arrpc: {
|
||||
onActivity(cb: (data: string) => void) {
|
||||
ipcRenderer.on(IpcEvents.ARRPC_ACTIVITY, (_, data: string) => cb(data));
|
||||
}
|
||||
},
|
||||
clipboard: {
|
||||
copyImage: (imageBuffer: Uint8Array, imageSrc: string) =>
|
||||
invoke<void>(IpcEvents.CLIPBOARD_COPY_IMAGE, imageBuffer, imageSrc)
|
||||
},
|
||||
debug: {
|
||||
launchGpu: () => invoke<void>(IpcEvents.DEBUG_LAUNCH_GPU),
|
||||
launchWebrtcInternals: () => invoke<void>(IpcEvents.DEBUG_LAUNCH_WEBRTC_INTERNALS)
|
||||
},
|
||||
commands: {
|
||||
onCommand(cb: (message: IpcMessage) => void) {
|
||||
ipcRenderer.on(IpcEvents.IPC_COMMAND, (_, message) => cb(message));
|
||||
},
|
||||
respond: (response: IpcResponse) => ipcRenderer.send(IpcEvents.IPC_COMMAND, response)
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/*
|
||||
* 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
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { contextBridge, ipcRenderer, webFrame } from "electron";
|
||||
|
||||
13
src/preload/splash.ts
Normal file
13
src/preload/splash.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
/*
|
||||
* Vesktop, a desktop app aiming to give you a snappier Discord Experience
|
||||
* Copyright (c) 2025 Vendicated and Vesktop contributors
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { contextBridge, ipcRenderer } from "electron";
|
||||
|
||||
contextBridge.exposeInMainWorld("VesktopSplashNative", {
|
||||
onUpdateMessage(callback: (message: string) => void) {
|
||||
ipcRenderer.on("update-splash-message", (_, message: string) => callback(message));
|
||||
}
|
||||
});
|
||||
@@ -1,7 +1,7 @@
|
||||
/*
|
||||
* 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
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { ipcRenderer } from "electron";
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
/*
|
||||
* SPDX-License-Identifier: GPL-3.0
|
||||
* Vesktop, a desktop app aiming to give you a snappier Discord Experience
|
||||
* Copyright (c) 2023 Vendicated and Vencord contributors
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { filters, waitFor } from "@vencord/types/webpack";
|
||||
import { RelationshipStore } from "@vencord/types/webpack/common";
|
||||
|
||||
import { VesktopLogger } from "./logger";
|
||||
import { Settings } from "./settings";
|
||||
|
||||
let GuildReadStateStore: any;
|
||||
@@ -26,7 +27,7 @@ export function setBadge() {
|
||||
|
||||
VesktopNative.app.setBadgeCount(totalCount);
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
VesktopLogger.error("Failed to update badge count", e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
68
src/renderer/arrpc.ts
Normal file
68
src/renderer/arrpc.ts
Normal file
@@ -0,0 +1,68 @@
|
||||
/*
|
||||
* Vesktop, a desktop app aiming to give you a snappier Discord Experience
|
||||
* Copyright (c) 2025 Vendicated and Vencord contributors
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { Logger } from "@vencord/types/utils";
|
||||
import { findLazy, findStoreLazy, onceReady } from "@vencord/types/webpack";
|
||||
import { FluxDispatcher, InviteActions } from "@vencord/types/webpack/common";
|
||||
import { IpcCommands } from "shared/IpcEvents";
|
||||
|
||||
import { onIpcCommand } from "./ipcCommands";
|
||||
import { Settings } from "./settings";
|
||||
|
||||
const logger = new Logger("VesktopRPC", "#5865f2");
|
||||
const StreamerModeStore = findStoreLazy("StreamerModeStore");
|
||||
|
||||
const arRPC = Vencord.Plugins.plugins["WebRichPresence (arRPC)"] as any as {
|
||||
handleEvent(e: MessageEvent): void;
|
||||
};
|
||||
|
||||
onIpcCommand(IpcCommands.RPC_ACTIVITY, async jsonData => {
|
||||
if (!Settings.store.arRPC) return;
|
||||
|
||||
await onceReady;
|
||||
|
||||
const data = JSON.parse(jsonData);
|
||||
|
||||
if (data.socketId === "STREAMERMODE" && StreamerModeStore.autoToggle) {
|
||||
FluxDispatcher.dispatch({
|
||||
type: "STREAMER_MODE_UPDATE",
|
||||
key: "enabled",
|
||||
value: data.activity?.application_id === "STREAMERMODE"
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
arRPC.handleEvent(new MessageEvent("message", { data: jsonData }));
|
||||
});
|
||||
|
||||
onIpcCommand(IpcCommands.RPC_INVITE, async code => {
|
||||
const { invite } = await InviteActions.resolveInvite(code, "Desktop Modal");
|
||||
if (!invite) return false;
|
||||
|
||||
VesktopNative.win.focus();
|
||||
|
||||
FluxDispatcher.dispatch({
|
||||
type: "INVITE_MODAL_OPEN",
|
||||
invite,
|
||||
code,
|
||||
context: "APP"
|
||||
});
|
||||
|
||||
return true;
|
||||
});
|
||||
|
||||
const { DEEP_LINK } = findLazy(m => m.DEEP_LINK?.handler);
|
||||
|
||||
onIpcCommand(IpcCommands.RPC_DEEP_LINK, async data => {
|
||||
logger.debug("Opening deep link:", data);
|
||||
try {
|
||||
DEEP_LINK.handler({ args: data });
|
||||
return true;
|
||||
} catch (err) {
|
||||
logger.error("Failed to open deep link:", err);
|
||||
return false;
|
||||
}
|
||||
});
|
||||
9
src/renderer/common.ts
Normal file
9
src/renderer/common.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
/*
|
||||
* Vesktop, a desktop app aiming to give you a snappier Discord Experience
|
||||
* Copyright (c) 2025 Vendicated and Vesktop contributors
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { findStoreLazy } from "@vencord/types/webpack";
|
||||
|
||||
export const MediaEngineStore = findStoreLazy("MediaEngineStore");
|
||||
@@ -1,13 +1,13 @@
|
||||
/*
|
||||
* SPDX-License-Identifier: GPL-3.0
|
||||
* Vesktop, a desktop app aiming to give you a snappier Discord Experience
|
||||
* Copyright (c) 2023 Vendicated and Vencord contributors
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import "./screenSharePicker.css";
|
||||
|
||||
import { closeModal, Logger, Modals, ModalSize, openModal, useAwaiter } from "@vencord/types/utils";
|
||||
import { findStoreLazy, onceReady } from "@vencord/types/webpack";
|
||||
import { onceReady } from "@vencord/types/webpack";
|
||||
import {
|
||||
Button,
|
||||
Card,
|
||||
@@ -21,14 +21,15 @@ import {
|
||||
} from "@vencord/types/webpack/common";
|
||||
import { Node } from "@vencord/venmic";
|
||||
import type { Dispatch, SetStateAction } from "react";
|
||||
import { MediaEngineStore } from "renderer/common";
|
||||
import { addPatch } from "renderer/patches/shared";
|
||||
import { useSettings } from "renderer/settings";
|
||||
import { isLinux, isWindows } from "renderer/utils";
|
||||
import { State, useSettings, useVesktopState } from "renderer/settings";
|
||||
import { classNameFactory, isLinux, isWindows } from "renderer/utils";
|
||||
|
||||
const StreamResolutions = ["480", "720", "1080", "1440"] as const;
|
||||
const StreamResolutions = ["480", "720", "1080", "1440", "2160"] as const;
|
||||
const StreamFps = ["15", "30", "60"] as const;
|
||||
|
||||
const MediaEngineStore = findStoreLazy("MediaEngineStore");
|
||||
const cl = classNameFactory("vcd-screen-picker-");
|
||||
|
||||
export type StreamResolution = (typeof StreamResolutions)[number];
|
||||
export type StreamFps = (typeof StreamFps)[number];
|
||||
@@ -44,8 +45,6 @@ interface AudioItem {
|
||||
}
|
||||
|
||||
interface StreamSettings {
|
||||
resolution: StreamResolution;
|
||||
fps: StreamFps;
|
||||
audio: boolean;
|
||||
contentHint?: string;
|
||||
includeSources?: AudioSources;
|
||||
@@ -77,10 +76,11 @@ addPatch({
|
||||
}
|
||||
],
|
||||
patchStreamQuality(opts: any) {
|
||||
if (!currentSettings) return;
|
||||
const { screenshareQuality } = State.store;
|
||||
if (!screenshareQuality) return;
|
||||
|
||||
const framerate = Number(currentSettings.fps);
|
||||
const height = Number(currentSettings.resolution);
|
||||
const framerate = Number(screenshareQuality.frameRate);
|
||||
const height = Number(screenshareQuality.resolution);
|
||||
const width = Math.round(height * (16 / 9));
|
||||
|
||||
Object.assign(opts, {
|
||||
@@ -161,13 +161,21 @@ export function openScreenSharePicker(screens: Source[], skipPicker: boolean) {
|
||||
|
||||
function ScreenPicker({ screens, chooseScreen }: { screens: Source[]; chooseScreen: (id: string) => void }) {
|
||||
return (
|
||||
<div className="vcd-screen-picker-grid">
|
||||
<div className={cl("screen-grid")}>
|
||||
{screens.map(({ id, name, url }) => (
|
||||
<label key={id}>
|
||||
<input type="radio" name="screen" value={id} onChange={() => chooseScreen(id)} />
|
||||
<label key={id} className={cl("screen-label")}>
|
||||
<input
|
||||
type="radio"
|
||||
className={cl("screen-radio")}
|
||||
name="screen"
|
||||
value={id}
|
||||
onChange={() => chooseScreen(id)}
|
||||
/>
|
||||
|
||||
<img src={url} alt="" />
|
||||
<Text variant="text-sm/normal">{name}</Text>
|
||||
<Text className={cl("screen-name")} variant="text-sm/normal">
|
||||
{name}
|
||||
</Text>
|
||||
</label>
|
||||
))}
|
||||
</div>
|
||||
@@ -187,11 +195,13 @@ function AudioSettingsModal({
|
||||
|
||||
return (
|
||||
<Modals.ModalRoot {...modalProps} size={ModalSize.MEDIUM}>
|
||||
<Modals.ModalHeader className="vcd-screen-picker-header">
|
||||
<Forms.FormTitle tag="h2">Venmic Settings</Forms.FormTitle>
|
||||
<Modals.ModalHeader className={cl("header")}>
|
||||
<Forms.FormTitle tag="h2" className={cl("header-title")}>
|
||||
Venmic Settings
|
||||
</Forms.FormTitle>
|
||||
<Modals.ModalCloseButton onClick={close} />
|
||||
</Modals.ModalHeader>
|
||||
<Modals.ModalContent className="vcd-screen-picker-modal">
|
||||
<Modals.ModalContent className={cl("modal")}>
|
||||
<Switch
|
||||
hideBorder
|
||||
onChange={v => (Settings.audio = { ...Settings.audio, workaround: v })}
|
||||
@@ -254,7 +264,13 @@ function AudioSettingsModal({
|
||||
</Switch>
|
||||
<Switch
|
||||
hideBorder
|
||||
onChange={v => (Settings.audio = { ...Settings.audio, ignoreDevices: v })}
|
||||
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.</>}
|
||||
>
|
||||
@@ -271,8 +287,25 @@ function AudioSettingsModal({
|
||||
>
|
||||
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">
|
||||
<Modals.ModalFooter className={cl("footer")}>
|
||||
<Button color={Button.Colors.TRANSPARENT} onClick={close}>
|
||||
Back
|
||||
</Button>
|
||||
@@ -281,7 +314,35 @@ function AudioSettingsModal({
|
||||
);
|
||||
}
|
||||
|
||||
function StreamSettings({
|
||||
function OptionRadio<Settings extends object, Key extends keyof Settings>(props: {
|
||||
options: Array<string> | ReadonlyArray<string>;
|
||||
labels?: Array<string>;
|
||||
settings: Settings;
|
||||
settingsKey: Key;
|
||||
onChange: (option: string) => void;
|
||||
}) {
|
||||
const { options, settings, settingsKey, labels, onChange } = props;
|
||||
|
||||
return (
|
||||
<div className={cl("option-radios")}>
|
||||
{(options as string[]).map((option, idx) => (
|
||||
<label className={cl("option-radio")} data-checked={settings[settingsKey] === option} key={option}>
|
||||
<Text variant="text-sm/bold">{labels?.[idx] ?? option}</Text>
|
||||
<input
|
||||
className={cl("option-input")}
|
||||
type="radio"
|
||||
name="fps"
|
||||
value={option}
|
||||
checked={settings[settingsKey] === option}
|
||||
onChange={() => onChange(option)}
|
||||
/>
|
||||
</label>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function StreamSettingsUi({
|
||||
source,
|
||||
settings,
|
||||
setSettings,
|
||||
@@ -293,6 +354,7 @@ function StreamSettings({
|
||||
skipPicker: boolean;
|
||||
}) {
|
||||
const Settings = useSettings();
|
||||
const qualitySettings = State.store.screenshareQuality!;
|
||||
|
||||
const [thumb] = useAwaiter(
|
||||
() => (skipPicker ? Promise.resolve(source.url) : VesktopNative.capturer.getLargeThumbnail(source.id)),
|
||||
@@ -317,88 +379,47 @@ function StreamSettings({
|
||||
return (
|
||||
<div>
|
||||
<Forms.FormTitle>What you're streaming</Forms.FormTitle>
|
||||
<Card className="vcd-screen-picker-card vcd-screen-picker-preview">
|
||||
<img
|
||||
src={thumb}
|
||||
alt=""
|
||||
className={isLinux ? "vcd-screen-picker-preview-img-linux" : "vcd-screen-picker-preview-img"}
|
||||
/>
|
||||
<Card className={cl("card", "preview")}>
|
||||
<img src={thumb} alt="" className={cl(isLinux ? "preview-img-linux" : "preview-img")} />
|
||||
<Text variant="text-sm/normal">{source.name}</Text>
|
||||
</Card>
|
||||
|
||||
<Forms.FormTitle>Stream Settings</Forms.FormTitle>
|
||||
|
||||
<Card className="vcd-screen-picker-card">
|
||||
<div className="vcd-screen-picker-quality">
|
||||
<section>
|
||||
<Card className={cl("card")}>
|
||||
<div className={cl("quality")}>
|
||||
<section className={cl("quality-section")}>
|
||||
<Forms.FormTitle>Resolution</Forms.FormTitle>
|
||||
<div className="vcd-screen-picker-radios">
|
||||
{StreamResolutions.map(res => (
|
||||
<label className="vcd-screen-picker-radio" data-checked={settings.resolution === res}>
|
||||
<Text variant="text-sm/bold">{res}</Text>
|
||||
<input
|
||||
type="radio"
|
||||
name="resolution"
|
||||
value={res}
|
||||
checked={settings.resolution === res}
|
||||
onChange={() => setSettings(s => ({ ...s, resolution: res }))}
|
||||
/>
|
||||
</label>
|
||||
))}
|
||||
</div>
|
||||
<OptionRadio
|
||||
options={StreamResolutions}
|
||||
settings={qualitySettings}
|
||||
settingsKey="resolution"
|
||||
onChange={value => (qualitySettings.resolution = value)}
|
||||
/>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<section className={cl("quality-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>
|
||||
<OptionRadio
|
||||
options={StreamFps}
|
||||
settings={qualitySettings}
|
||||
settingsKey="frameRate"
|
||||
onChange={value => (qualitySettings.frameRate = value)}
|
||||
/>
|
||||
</section>
|
||||
</div>
|
||||
<div className="vcd-screen-picker-quality">
|
||||
<section>
|
||||
<div className={cl("quality")}>
|
||||
<section className={cl("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">
|
||||
<OptionRadio
|
||||
options={["motion", "detail"]}
|
||||
labels={["Prefer Smoothness", "Prefer Clarity"]}
|
||||
settings={settings}
|
||||
settingsKey="contentHint"
|
||||
onChange={option => setSettings(s => ({ ...s, contentHint: option }))}
|
||||
/>
|
||||
<div className={cl("hint-description")}>
|
||||
<p>
|
||||
Choosing "Prefer Clarity" will result in a significantly lower framerate in exchange
|
||||
for a much sharper and clearer image.
|
||||
@@ -410,7 +431,7 @@ function StreamSettings({
|
||||
value={settings.audio}
|
||||
onChange={checked => setSettings(s => ({ ...s, audio: checked }))}
|
||||
hideBorder
|
||||
className="vcd-screen-picker-audio"
|
||||
className={cl("audio")}
|
||||
>
|
||||
Stream With Audio
|
||||
</Switch>
|
||||
@@ -423,6 +444,7 @@ function StreamSettings({
|
||||
openSettings={openSettings}
|
||||
includeSources={settings.includeSources}
|
||||
excludeSources={settings.excludeSources}
|
||||
deviceSelect={Settings.audio?.deviceSelect}
|
||||
granularSelect={Settings.audio?.granularSelect}
|
||||
setIncludeSources={sources => setSettings(s => ({ ...s, includeSources: sources }))}
|
||||
setExcludeSources={sources => setSettings(s => ({ ...s, excludeSources: sources }))}
|
||||
@@ -441,13 +463,23 @@ function hasMatchingProps(value: Node, other: Node) {
|
||||
return Object.keys(value).every(key => value[key] === other[key]);
|
||||
}
|
||||
|
||||
function mapToAudioItem(node: AudioSource, granularSelect?: boolean): AudioItem[] {
|
||||
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) {
|
||||
@@ -458,9 +490,15 @@ function mapToAudioItem(node: AudioSource, granularSelect?: boolean): AudioItem[
|
||||
return rtn;
|
||||
}
|
||||
|
||||
const binary = node["application.process.binary"];
|
||||
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 } });
|
||||
}
|
||||
|
||||
@@ -469,10 +507,12 @@ function mapToAudioItem(node: AudioSource, granularSelect?: boolean): AudioItem[
|
||||
const first = rtn[0];
|
||||
const firstValues = first.value as Node;
|
||||
|
||||
rtn.push({
|
||||
name: `${first.name} (${pid})`,
|
||||
value: { ...firstValues, "application.process.id": pid }
|
||||
});
|
||||
if (pid) {
|
||||
rtn.push({
|
||||
name: `${first.name} (${pid})`,
|
||||
value: { ...firstValues, "application.process.id": pid }
|
||||
});
|
||||
}
|
||||
|
||||
const mediaName = node["media.name"];
|
||||
|
||||
@@ -483,17 +523,13 @@ function mapToAudioItem(node: AudioSource, granularSelect?: boolean): AudioItem[
|
||||
});
|
||||
}
|
||||
|
||||
const mediaClass = node["media.class"];
|
||||
|
||||
if (!mediaClass) {
|
||||
return rtn;
|
||||
if (mediaClass) {
|
||||
rtn.push({
|
||||
name: `${first.name} [${mediaClass}]`,
|
||||
value: { ...firstValues, "media.class": mediaClass }
|
||||
});
|
||||
}
|
||||
|
||||
rtn.push({
|
||||
name: `${first.name} [${mediaClass}]`,
|
||||
value: { ...firstValues, "media.class": mediaClass }
|
||||
});
|
||||
|
||||
return rtn;
|
||||
}
|
||||
|
||||
@@ -535,6 +571,7 @@ function updateItems(setSources: (s: AudioSources) => void, sources?: AudioSourc
|
||||
function AudioSourcePickerLinux({
|
||||
includeSources,
|
||||
excludeSources,
|
||||
deviceSelect,
|
||||
granularSelect,
|
||||
openSettings,
|
||||
setIncludeSources,
|
||||
@@ -542,6 +579,7 @@ function AudioSourcePickerLinux({
|
||||
}: {
|
||||
includeSources?: AudioSources;
|
||||
excludeSources?: AudioSources;
|
||||
deviceSelect?: boolean;
|
||||
granularSelect?: boolean;
|
||||
openSettings: () => void;
|
||||
setIncludeSources: (s: AudioSources) => void;
|
||||
@@ -592,14 +630,14 @@ function AudioSourcePickerLinux({
|
||||
|
||||
const allSources = sources.ok
|
||||
? [...specialSources, ...sources.targets]
|
||||
.map(target => mapToAudioItem(target, granularSelect))
|
||||
.map(target => mapToAudioItem(target, granularSelect, deviceSelect))
|
||||
.flat()
|
||||
.filter(uniqueName)
|
||||
: [];
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className={includeSources === "Entire System" ? "vcd-screen-picker-quality" : undefined}>
|
||||
<div className={cl({ quality: includeSources === "Entire System" })}>
|
||||
<section>
|
||||
<Forms.FormTitle>{loading ? "Loading Sources..." : "Audio Sources"}</Forms.FormTitle>
|
||||
<Select
|
||||
@@ -635,11 +673,7 @@ function AudioSourcePickerLinux({
|
||||
</section>
|
||||
)}
|
||||
</div>
|
||||
<Button
|
||||
color={Button.Colors.TRANSPARENT}
|
||||
onClick={openSettings}
|
||||
className="vcd-screen-picker-settings-button"
|
||||
>
|
||||
<Button color={Button.Colors.TRANSPARENT} onClick={openSettings} className={cl("settings-button")}>
|
||||
Open Audio Settings
|
||||
</Button>
|
||||
</>
|
||||
@@ -661,24 +695,26 @@ function ModalComponent({
|
||||
}) {
|
||||
const [selected, setSelected] = useState<string | undefined>(skipPicker ? screens[0].id : void 0);
|
||||
const [settings, setSettings] = useState<StreamSettings>({
|
||||
resolution: "720",
|
||||
fps: "30",
|
||||
contentHint: "motion",
|
||||
audio: true,
|
||||
includeSources: "None"
|
||||
});
|
||||
const qualitySettings = (useVesktopState().screenshareQuality ??= {
|
||||
resolution: "720",
|
||||
frameRate: "30"
|
||||
});
|
||||
|
||||
return (
|
||||
<Modals.ModalRoot {...modalProps} size={ModalSize.MEDIUM}>
|
||||
<Modals.ModalHeader className="vcd-screen-picker-header">
|
||||
<Modals.ModalHeader className={cl("header")}>
|
||||
<Forms.FormTitle tag="h2">ScreenShare</Forms.FormTitle>
|
||||
<Modals.ModalCloseButton onClick={close} />
|
||||
</Modals.ModalHeader>
|
||||
<Modals.ModalContent className="vcd-screen-picker-modal">
|
||||
<Modals.ModalContent className={cl("modal")}>
|
||||
{!selected ? (
|
||||
<ScreenPicker screens={screens} chooseScreen={setSelected} />
|
||||
) : (
|
||||
<StreamSettings
|
||||
<StreamSettingsUi
|
||||
source={screens.find(s => s.id === selected)!}
|
||||
settings={settings}
|
||||
setSettings={setSettings}
|
||||
@@ -686,14 +722,14 @@ function ModalComponent({
|
||||
/>
|
||||
)}
|
||||
</Modals.ModalContent>
|
||||
<Modals.ModalFooter className="vcd-screen-picker-footer">
|
||||
<Modals.ModalFooter className={cl("footer")}>
|
||||
<Button
|
||||
disabled={!selected}
|
||||
onClick={() => {
|
||||
currentSettings = settings;
|
||||
try {
|
||||
const frameRate = Number(settings.fps);
|
||||
const height = Number(settings.resolution);
|
||||
const frameRate = Number(qualitySettings.frameRate);
|
||||
const height = Number(qualitySettings.resolution);
|
||||
const width = Math.round(height * (16 / 9));
|
||||
|
||||
const conn = [...MediaEngineStore.getMediaEngine().connections].find(
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/*
|
||||
* 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
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
export * as ScreenShare from "./ScreenSharePicker";
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
padding: 1em;
|
||||
}
|
||||
|
||||
.vcd-screen-picker-header h1 {
|
||||
.vcd-screen-picker-header-title {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
@@ -15,34 +15,32 @@
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.vcd-screen-picker-grid {
|
||||
|
||||
/* Screen Grid */
|
||||
.vcd-screen-picker-screen-grid {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 2em 1em;
|
||||
}
|
||||
|
||||
.vcd-screen-picker-grid input {
|
||||
.vcd-screen-picker-screen-radio {
|
||||
appearance: none;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.vcd-screen-picker-selected img {
|
||||
border: 2px solid var(--brand-500);
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
.vcd-screen-picker-grid label {
|
||||
.vcd-screen-picker-screen-label {
|
||||
overflow: hidden;
|
||||
padding: 4px 0px;
|
||||
padding: 8px;
|
||||
cursor: pointer;
|
||||
display: grid;
|
||||
justify-items: center;
|
||||
}
|
||||
|
||||
.vcd-screen-picker-grid label:hover {
|
||||
.vcd-screen-picker-screen-label:hover {
|
||||
outline: 2px solid var(--brand-500);
|
||||
}
|
||||
|
||||
|
||||
.vcd-screen-picker-grid div {
|
||||
.vcd-screen-picker-screen-name {
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
@@ -74,38 +72,48 @@
|
||||
margin-bottom: 1em;
|
||||
}
|
||||
|
||||
.vcd-screen-picker-radio input {
|
||||
display: none;
|
||||
|
||||
/* Option Radios */
|
||||
|
||||
.vcd-screen-picker-option-radios {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
.vcd-screen-picker-radio {
|
||||
.vcd-screen-picker-option-radio {
|
||||
flex: 1 1 auto;
|
||||
text-align: center;
|
||||
background-color: var(--background-secondary);
|
||||
border: 1px solid var(--primary-800);
|
||||
padding: 0.3em;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.vcd-screen-picker-radio h2 {
|
||||
margin: 0;
|
||||
.vcd-screen-picker-option-radio:first-child {
|
||||
border-radius: 3px 0 0 3px;
|
||||
}
|
||||
|
||||
.vcd-screen-picker-radio[data-checked="true"] {
|
||||
.vcd-screen-picker-option-radio:last-child {
|
||||
border-radius: 0 3px 3px 0;
|
||||
}
|
||||
|
||||
.vcd-screen-picker-option-input {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.vcd-screen-picker-option-radio[data-checked="true"] {
|
||||
background-color: var(--brand-500);
|
||||
border-color: var(--brand-500);
|
||||
}
|
||||
|
||||
.vcd-screen-picker-radio[data-checked="true"] h2 {
|
||||
color: var(--interactive-active);
|
||||
}
|
||||
|
||||
.vcd-screen-picker-quality {
|
||||
display: flex;
|
||||
gap: 1em;
|
||||
|
||||
margin-bottom: 0.5em;
|
||||
}
|
||||
|
||||
.vcd-screen-picker-quality section {
|
||||
.vcd-screen-picker-quality-section {
|
||||
flex: 1 1 auto;
|
||||
}
|
||||
|
||||
@@ -114,24 +122,6 @@
|
||||
margin-top: 0.3rem;
|
||||
}
|
||||
|
||||
.vcd-screen-picker-radios {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
.vcd-screen-picker-radios label {
|
||||
flex: 1 1 auto;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.vcd-screen-picker-radios label:first-child {
|
||||
border-radius: 3px 0 0 3px;
|
||||
}
|
||||
|
||||
.vcd-screen-picker-radios label:last-child {
|
||||
border-radius: 0 3px 3px 0;
|
||||
}
|
||||
|
||||
.vcd-screen-picker-audio {
|
||||
margin-bottom: 0;
|
||||
|
||||
@@ -1,18 +1,19 @@
|
||||
/*
|
||||
* 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
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { Switch, useState } from "@vencord/types/webpack/common";
|
||||
import { useState } from "@vencord/types/webpack/common";
|
||||
|
||||
import { SettingsComponent } from "./Settings";
|
||||
import { VesktopSettingsSwitch } from "./VesktopSettingsSwitch";
|
||||
|
||||
export const AutoStartToggle: SettingsComponent = () => {
|
||||
const [autoStartEnabled, setAutoStartEnabled] = useState(VesktopNative.autostart.isEnabled());
|
||||
|
||||
return (
|
||||
<Switch
|
||||
<VesktopSettingsSwitch
|
||||
value={autoStartEnabled}
|
||||
onChange={async v => {
|
||||
await VesktopNative.autostart[v ? "enable" : "disable"]();
|
||||
@@ -21,6 +22,6 @@ export const AutoStartToggle: SettingsComponent = () => {
|
||||
note="Automatically start Vesktop on computer start-up"
|
||||
>
|
||||
Start With System
|
||||
</Switch>
|
||||
</VesktopSettingsSwitch>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,15 +1,59 @@
|
||||
/*
|
||||
* 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
|
||||
* Copyright (c) 2025 Vendicated and Vencord contributors
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { useForceUpdater } from "@vencord/types/utils";
|
||||
import { Button, Forms, Toasts } from "@vencord/types/webpack/common";
|
||||
import {
|
||||
Margins,
|
||||
ModalCloseButton,
|
||||
ModalContent,
|
||||
ModalHeader,
|
||||
ModalRoot,
|
||||
ModalSize,
|
||||
openModal,
|
||||
useForceUpdater
|
||||
} from "@vencord/types/utils";
|
||||
import { Button, Forms, Text, Toasts } from "@vencord/types/webpack/common";
|
||||
import { Settings } from "shared/settings";
|
||||
|
||||
import { SettingsComponent } from "./Settings";
|
||||
|
||||
export const VencordLocationPicker: SettingsComponent = ({ settings }) => {
|
||||
export const DeveloperOptionsButton: SettingsComponent = ({ settings }) => {
|
||||
return <Button onClick={() => openDeveloperOptionsModal(settings)}>Open Developer Settings</Button>;
|
||||
};
|
||||
|
||||
function openDeveloperOptionsModal(settings: Settings) {
|
||||
openModal(props => (
|
||||
<ModalRoot {...props} size={ModalSize.MEDIUM}>
|
||||
<ModalHeader>
|
||||
<Text variant="heading-lg/semibold" style={{ flexGrow: 1 }}>
|
||||
Vesktop Developer Options
|
||||
</Text>
|
||||
<ModalCloseButton onClick={props.onClose} />
|
||||
</ModalHeader>
|
||||
|
||||
<ModalContent>
|
||||
<div style={{ padding: "1em 0" }}>
|
||||
<Forms.FormTitle tag="h5">Vencord Location</Forms.FormTitle>
|
||||
<VencordLocationPicker settings={settings} />
|
||||
|
||||
<Forms.FormTitle tag="h5" className={Margins.top16}>
|
||||
Debugging
|
||||
</Forms.FormTitle>
|
||||
<div className="vcd-settings-button-grid">
|
||||
<Button onClick={() => VesktopNative.debug.launchGpu()}>Open chrome://gpu</Button>
|
||||
<Button onClick={() => VesktopNative.debug.launchWebrtcInternals()}>
|
||||
Open chrome://webrtc-internals
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</ModalContent>
|
||||
</ModalRoot>
|
||||
));
|
||||
}
|
||||
|
||||
const VencordLocationPicker: SettingsComponent = ({ settings }) => {
|
||||
const forceUpdate = useForceUpdater();
|
||||
const vencordDir = VesktopNative.fileManager.getVencordDir();
|
||||
|
||||
@@ -31,7 +75,7 @@ export const VencordLocationPicker: SettingsComponent = ({ settings }) => {
|
||||
"the default location"
|
||||
)}
|
||||
</Forms.FormText>
|
||||
<div className="vcd-location-btns">
|
||||
<div className="vcd-settings-button-grid">
|
||||
<Button
|
||||
size={Button.Sizes.SMALL}
|
||||
onClick={async () => {
|
||||
@@ -1,7 +1,7 @@
|
||||
/*
|
||||
* 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
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { Select } from "@vencord/types/webpack/common";
|
||||
|
||||
@@ -1,17 +1,17 @@
|
||||
/*
|
||||
* 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
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { Switch } from "@vencord/types/webpack/common";
|
||||
import { setBadge } from "renderer/appBadge";
|
||||
|
||||
import { SettingsComponent } from "./Settings";
|
||||
import { VesktopSettingsSwitch } from "./VesktopSettingsSwitch";
|
||||
|
||||
export const NotificationBadgeToggle: SettingsComponent = ({ settings }) => {
|
||||
return (
|
||||
<Switch
|
||||
<VesktopSettingsSwitch
|
||||
value={settings.appBadge ?? true}
|
||||
onChange={v => {
|
||||
settings.appBadge = v;
|
||||
@@ -21,6 +21,6 @@ export const NotificationBadgeToggle: SettingsComponent = ({ settings }) => {
|
||||
note="Show mention badge on the app icon"
|
||||
>
|
||||
Notification Badge
|
||||
</Switch>
|
||||
</VesktopSettingsSwitch>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,20 +1,22 @@
|
||||
/*
|
||||
* 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
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import "./settings.css";
|
||||
|
||||
import { Forms, Switch, Text } from "@vencord/types/webpack/common";
|
||||
import { ErrorBoundary } from "@vencord/types/components";
|
||||
import { Forms, Text } from "@vencord/types/webpack/common";
|
||||
import { ComponentType } from "react";
|
||||
import { Settings, useSettings } from "renderer/settings";
|
||||
import { isMac, isWindows } from "renderer/utils";
|
||||
|
||||
import { AutoStartToggle } from "./AutoStartToggle";
|
||||
import { DeveloperOptionsButton } from "./DeveloperOptions";
|
||||
import { DiscordBranchPicker } from "./DiscordBranchPicker";
|
||||
import { NotificationBadgeToggle } from "./NotificationBadgeToggle";
|
||||
import { VencordLocationPicker } from "./VencordLocationPicker";
|
||||
import { VesktopSettingsSwitch } from "./VesktopSettingsSwitch";
|
||||
import { WindowsTransparencyControls } from "./WindowsTransparencyControls";
|
||||
|
||||
interface BooleanSetting {
|
||||
@@ -37,6 +39,14 @@ const SettingsOptions: Record<string, Array<BooleanSetting | SettingsComponent>>
|
||||
title: "Hardware Acceleration",
|
||||
description: "Enable hardware acceleration",
|
||||
defaultValue: true
|
||||
},
|
||||
{
|
||||
key: "hardwareVideoAcceleration",
|
||||
title: "Video Hardware Acceleration",
|
||||
description:
|
||||
"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
|
||||
}
|
||||
],
|
||||
"User Interface": [
|
||||
@@ -59,11 +69,18 @@ const SettingsOptions: Record<string, Array<BooleanSetting | SettingsComponent>>
|
||||
defaultValue: false,
|
||||
disabled: () => Settings.store.customTitleBar ?? isWindows
|
||||
},
|
||||
{
|
||||
key: "enableSplashScreen",
|
||||
title: "Enable Splash Screen",
|
||||
description:
|
||||
"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",
|
||||
title: "Splash theming",
|
||||
description: "Adapt the splash window colors to your custom theme",
|
||||
defaultValue: false
|
||||
defaultValue: true
|
||||
},
|
||||
WindowsTransparencyControls
|
||||
],
|
||||
@@ -103,7 +120,7 @@ const SettingsOptions: Record<string, Array<BooleanSetting | SettingsComponent>>
|
||||
}
|
||||
],
|
||||
Notifications: [NotificationBadgeToggle],
|
||||
Miscelleanous: [
|
||||
Miscellaneous: [
|
||||
{
|
||||
key: "arRPC",
|
||||
title: "Rich Presence",
|
||||
@@ -118,51 +135,61 @@ const SettingsOptions: Record<string, Array<BooleanSetting | SettingsComponent>>
|
||||
defaultValue: false
|
||||
}
|
||||
],
|
||||
"Vencord Location": [VencordLocationPicker]
|
||||
"Developer Options": [DeveloperOptionsButton]
|
||||
};
|
||||
|
||||
function SettingsSections() {
|
||||
const Settings = useSettings();
|
||||
|
||||
const sections = Object.entries(SettingsOptions).map(([title, settings]) => (
|
||||
<Forms.FormSection
|
||||
title={title}
|
||||
key={title}
|
||||
className="vcd-settings-section"
|
||||
titleClassName="vcd-settings-title"
|
||||
>
|
||||
{settings.map(Setting => {
|
||||
if (typeof Setting === "function") return <Setting settings={Settings} />;
|
||||
const sections = Object.entries(SettingsOptions).map(([title, settings], i, arr) => (
|
||||
<div key={title} className="vcd-settings-category">
|
||||
<Text variant="heading-lg/semibold" color="header-primary" className="vcd-settings-category-title">
|
||||
{title}
|
||||
</Text>
|
||||
|
||||
const { defaultValue, title, description, key, disabled, invisible } = Setting;
|
||||
if (invisible?.()) return null;
|
||||
<div className="vcd-settings-category-content">
|
||||
{settings.map(Setting => {
|
||||
if (typeof Setting === "function") return <Setting settings={Settings} />;
|
||||
|
||||
return (
|
||||
<Switch
|
||||
value={Settings[key as any] ?? defaultValue}
|
||||
onChange={v => (Settings[key as any] = v)}
|
||||
note={description}
|
||||
disabled={disabled?.()}
|
||||
key={key}
|
||||
>
|
||||
{title}
|
||||
</Switch>
|
||||
);
|
||||
})}
|
||||
</Forms.FormSection>
|
||||
const { defaultValue, title, description, key, disabled, invisible } = Setting;
|
||||
if (invisible?.()) return null;
|
||||
|
||||
return (
|
||||
<VesktopSettingsSwitch
|
||||
value={Settings[key as any] ?? defaultValue}
|
||||
onChange={v => (Settings[key as any] = v)}
|
||||
note={description}
|
||||
disabled={disabled?.()}
|
||||
key={key}
|
||||
>
|
||||
{title}
|
||||
</VesktopSettingsSwitch>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
|
||||
{i < arr.length - 1 && <Forms.FormDivider className="vcd-settings-category-divider" />}
|
||||
</div>
|
||||
));
|
||||
|
||||
return <>{sections}</>;
|
||||
}
|
||||
|
||||
export default function SettingsUi() {
|
||||
return (
|
||||
<Forms.FormSection>
|
||||
<Text variant="heading-lg/semibold" style={{ color: "var(--header-primary)" }} tag="h2">
|
||||
Vesktop Settings
|
||||
</Text>
|
||||
|
||||
<SettingsSections />
|
||||
</Forms.FormSection>
|
||||
);
|
||||
}
|
||||
export default ErrorBoundary.wrap(
|
||||
function SettingsUI() {
|
||||
return (
|
||||
<Forms.FormSection>
|
||||
{/* FIXME: Outdated type */}
|
||||
{/* @ts-expect-error Outdated type */}
|
||||
<Text variant="heading-xl/semibold" color="header-primary" className="vcd-settings-title">
|
||||
Vesktop Settings
|
||||
</Text>
|
||||
<SettingsSections />
|
||||
</Forms.FormSection>
|
||||
);
|
||||
},
|
||||
{
|
||||
message:
|
||||
"Failed to render the Vesktop Settings tab. If this issue persists, try to right click the Vesktop tray icon, then click 'Repair Vencord'. And make sure your Vesktop is up to date."
|
||||
}
|
||||
);
|
||||
|
||||
16
src/renderer/components/settings/VesktopSettingsSwitch.tsx
Normal file
16
src/renderer/components/settings/VesktopSettingsSwitch.tsx
Normal file
@@ -0,0 +1,16 @@
|
||||
/*
|
||||
* Vesktop, a desktop app aiming to give you a snappier Discord Experience
|
||||
* Copyright (c) 2025 Vendicated and Vesktop contributors
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { Switch } from "@vencord/types/webpack/common";
|
||||
import { ComponentProps } from "react";
|
||||
|
||||
export function VesktopSettingsSwitch(props: ComponentProps<typeof Switch>) {
|
||||
return (
|
||||
<Switch {...props} hideBorder className="vcd-settings-switch">
|
||||
{props.children}
|
||||
</Switch>
|
||||
);
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
/*
|
||||
* 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
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { Margins } from "@vencord/types/utils";
|
||||
@@ -13,8 +13,8 @@ export const WindowsTransparencyControls: SettingsComponent = ({ settings }) =>
|
||||
if (!VesktopNative.app.supportsWindowsTransparency()) return null;
|
||||
|
||||
return (
|
||||
<>
|
||||
<Forms.FormTitle className={Margins.top16 + " " + Margins.bottom8}>Transparency Options</Forms.FormTitle>
|
||||
<div>
|
||||
<Forms.FormTitle className={Margins.bottom8}>Transparency Options</Forms.FormTitle>
|
||||
<Forms.FormText className={Margins.bottom8}>
|
||||
Requires a full restart. You will need a theme that supports transparency for this to work.
|
||||
</Forms.FormText>
|
||||
@@ -42,8 +42,6 @@ export const WindowsTransparencyControls: SettingsComponent = ({ settings }) =>
|
||||
isSelected={v => v === settings.transparencyOption}
|
||||
serialize={s => s}
|
||||
/>
|
||||
|
||||
<Forms.FormDivider className={Margins.top16 + " " + Margins.bottom16} />
|
||||
</>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,14 +1,34 @@
|
||||
.vcd-location-btns {
|
||||
.vcd-settings-button-grid {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 0.5em;
|
||||
margin-top: 0.5em;
|
||||
}
|
||||
|
||||
.vcd-settings-section {
|
||||
margin-top: 1.5rem;
|
||||
.vcd-settings-title {
|
||||
margin-bottom: 32px;
|
||||
}
|
||||
|
||||
.vcd-settings-title {
|
||||
margin-bottom: 0.5rem;
|
||||
.vcd-settings-category {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.vcd-settings-category-title {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.vcd-settings-category-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 24px;
|
||||
}
|
||||
|
||||
.vcd-settings-category-divider {
|
||||
margin-top: 32px;
|
||||
margin-bottom: 32px;
|
||||
}
|
||||
|
||||
.vcd-settings-switch {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
/* Download Desktop button in guilds list */
|
||||
[class^=listItem_]:has([data-list-item-id=guildsnav___app-download-button]),
|
||||
[class^=listItem_]:has(+ [class^=listItem_] [data-list-item-id=guildsnav___app-download-button]) {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* FIXME: remove this once Discord fixes their css to not explode scrollbars on chromium >=121 */
|
||||
* {
|
||||
scrollbar-width: 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;
|
||||
}
|
||||
@@ -1,12 +1,10 @@
|
||||
/*
|
||||
* 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
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import "./fixes.css";
|
||||
|
||||
import { isWindows, localStorage } from "./utils";
|
||||
import { localStorage } from "./utils";
|
||||
|
||||
// Make clicking Notifications focus the window
|
||||
const originalSetOnClick = Object.getOwnPropertyDescriptor(Notification.prototype, "onclick")!.set!;
|
||||
@@ -22,14 +20,3 @@ Object.defineProperty(Notification.prototype, "onclick", {
|
||||
|
||||
// Hide "Download Discord Desktop now!!!!" banner
|
||||
localStorage.setItem("hideNag", "true");
|
||||
|
||||
// FIXME: Remove eventually.
|
||||
// Originally, Vencord always used a Windows user agent. This seems to cause captchas
|
||||
// Now, we use a platform specific UA - HOWEVER, discord FOR SOME REASON????? caches
|
||||
// device props in localStorage. This code fixes their cache to properly update the platform in SuperProps
|
||||
if (!isWindows)
|
||||
try {
|
||||
const deviceProperties = localStorage.getItem("deviceProperties");
|
||||
if (deviceProperties && JSON.parse(deviceProperties).os === "Windows")
|
||||
localStorage.removeItem("deviceProperties");
|
||||
} catch {}
|
||||
|
||||
@@ -1,45 +1,29 @@
|
||||
/*
|
||||
* SPDX-License-Identifier: GPL-3.0
|
||||
* Vesktop, a desktop app aiming to give you a snappier Discord Experience
|
||||
* Copyright (c) 2023 Vendicated and Vencord contributors
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import "./fixes";
|
||||
import "./appBadge";
|
||||
import "./patches";
|
||||
import "./themedSplash";
|
||||
|
||||
console.log("read if cute :3");
|
||||
import "./ipcCommands";
|
||||
import "./appBadge";
|
||||
import "./fixes";
|
||||
import "./arrpc";
|
||||
import "__patches__"; // auto generated by the build script
|
||||
|
||||
export * as Components from "./components";
|
||||
import { findByPropsLazy, onceReady } from "@vencord/types/webpack";
|
||||
import { Alerts, FluxDispatcher } from "@vencord/types/webpack/common";
|
||||
|
||||
import SettingsUi from "./components/settings/Settings";
|
||||
import { VesktopLogger } from "./logger";
|
||||
import { Settings } from "./settings";
|
||||
export { Settings };
|
||||
|
||||
const InviteActions = findByPropsLazy("resolveInvite");
|
||||
import type SettingsPlugin from "@vencord/types/plugins/_core/settings";
|
||||
|
||||
export async function openInviteModal(code: string) {
|
||||
const { invite } = await InviteActions.resolveInvite(code, "Desktop Modal");
|
||||
if (!invite) return false;
|
||||
VesktopLogger.log("read if cute :3");
|
||||
VesktopLogger.log("Vesktop v" + VesktopNative.app.getVersion());
|
||||
|
||||
VesktopNative.win.focus();
|
||||
|
||||
FluxDispatcher.dispatch({
|
||||
type: "INVITE_MODAL_OPEN",
|
||||
invite,
|
||||
code,
|
||||
context: "APP"
|
||||
});
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
const customSettingsSections = (
|
||||
Vencord.Plugins.plugins.Settings as any as { customSections: ((ID: Record<string, unknown>) => any)[] }
|
||||
).customSections;
|
||||
const customSettingsSections = (Vencord.Plugins.plugins.Settings as any as typeof SettingsPlugin).customSections;
|
||||
|
||||
customSettingsSections.push(() => ({
|
||||
section: "Vesktop",
|
||||
@@ -47,31 +31,3 @@ customSettingsSections.push(() => ({
|
||||
element: SettingsUi,
|
||||
className: "vc-vesktop-settings"
|
||||
}));
|
||||
|
||||
const arRPC = Vencord.Plugins.plugins["WebRichPresence (arRPC)"] as any as {
|
||||
handleEvent(e: MessageEvent): void;
|
||||
};
|
||||
|
||||
VesktopNative.arrpc.onActivity(async data => {
|
||||
if (!Settings.store.arRPC) return;
|
||||
|
||||
await onceReady;
|
||||
|
||||
arRPC.handleEvent(new MessageEvent("message", { data }));
|
||||
});
|
||||
|
||||
// TODO: remove soon
|
||||
const vencordDir = "vencordDir" as keyof typeof Settings.store;
|
||||
if (Settings.store[vencordDir]) {
|
||||
onceReady.then(() =>
|
||||
setTimeout(
|
||||
() =>
|
||||
Alerts.show({
|
||||
title: "Custom Vencord Location",
|
||||
body: "Due to security hardening changes in Vesktop, your custom Vencord location had to be reset. Please configure it again in the settings.",
|
||||
onConfirm: () => delete Settings.store[vencordDir]
|
||||
}),
|
||||
5000
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
54
src/renderer/ipcCommands.ts
Normal file
54
src/renderer/ipcCommands.ts
Normal file
@@ -0,0 +1,54 @@
|
||||
/*
|
||||
* Vesktop, a desktop app aiming to give you a snappier Discord Experience
|
||||
* Copyright (c) 2025 Vendicated and Vencord contributors
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { SettingsRouter } from "@vencord/types/webpack/common";
|
||||
import { IpcCommands } from "shared/IpcEvents";
|
||||
|
||||
import { openScreenSharePicker } from "./components/ScreenSharePicker";
|
||||
|
||||
type IpcCommandHandler = (data: any) => any;
|
||||
|
||||
const handlers = new Map<string, IpcCommandHandler>();
|
||||
|
||||
function respond(nonce: string, ok: boolean, data: any) {
|
||||
VesktopNative.commands.respond({ nonce, ok, data });
|
||||
}
|
||||
|
||||
VesktopNative.commands.onCommand(async ({ message, nonce, data }) => {
|
||||
const handler = handlers.get(message);
|
||||
if (!handler) {
|
||||
return respond(nonce, false, `No handler for message: ${message}`);
|
||||
}
|
||||
|
||||
try {
|
||||
const result = await handler(data);
|
||||
respond(nonce, true, result);
|
||||
} catch (err) {
|
||||
respond(nonce, false, String(err));
|
||||
}
|
||||
});
|
||||
|
||||
export function onIpcCommand(channel: string, handler: IpcCommandHandler) {
|
||||
if (handlers.has(channel)) {
|
||||
throw new Error(`Handler for message ${channel} already exists`);
|
||||
}
|
||||
|
||||
handlers.set(channel, handler);
|
||||
}
|
||||
|
||||
export function offIpcCommand(channel: string) {
|
||||
handlers.delete(channel);
|
||||
}
|
||||
|
||||
/* Generic Handlers */
|
||||
|
||||
onIpcCommand(IpcCommands.NAVIGATE_SETTINGS, () => {
|
||||
SettingsRouter.open("My Account");
|
||||
});
|
||||
|
||||
onIpcCommand(IpcCommands.GET_LANGUAGES, () => navigator.languages);
|
||||
|
||||
onIpcCommand(IpcCommands.SCREEN_SHARE_PICKER, data => openScreenSharePicker(data.screens, data.skipPicker));
|
||||
9
src/renderer/logger.ts
Normal file
9
src/renderer/logger.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
/*
|
||||
* Vesktop, a desktop app aiming to give you a snappier Discord Experience
|
||||
* Copyright (c) 2025 Vendicated and Vencord contributors
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { Logger } from "@vencord/types/utils";
|
||||
|
||||
export const VesktopLogger = new Logger("Vesktop", "#d3869b");
|
||||
19
src/renderer/patches/allowDevToolsKeybind.ts
Normal file
19
src/renderer/patches/allowDevToolsKeybind.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
/*
|
||||
* Vesktop, a desktop app aiming to give you a snappier Discord Experience
|
||||
* Copyright (c) 2025 Vendicated and Vesktop contributors
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { addPatch } from "./shared";
|
||||
|
||||
addPatch({
|
||||
patches: [
|
||||
{
|
||||
find: '"mod+alt+i"',
|
||||
replacement: {
|
||||
match: /"discord\.com"===location\.host/,
|
||||
replace: "false"
|
||||
}
|
||||
}
|
||||
]
|
||||
});
|
||||
@@ -1,7 +1,7 @@
|
||||
/*
|
||||
* 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
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { addPatch } from "./shared";
|
||||
|
||||
51
src/renderer/patches/fixAutoGainToggle.ts
Normal file
51
src/renderer/patches/fixAutoGainToggle.ts
Normal file
@@ -0,0 +1,51 @@
|
||||
/*
|
||||
* Vesktop, a desktop app aiming to give you a snappier Discord Experience
|
||||
* Copyright (c) 2025 Vendicated and Vesktop contributors
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { Logger } from "@vencord/types/utils";
|
||||
import { MediaEngineStore } from "renderer/common";
|
||||
|
||||
const logger = new Logger("FixAutoGain");
|
||||
|
||||
function fixTrackConstraints(constraint: MediaTrackConstraints) {
|
||||
const target = constraint.advanced?.find(opt => Object.hasOwn(opt, "autoGainControl")) ?? constraint;
|
||||
|
||||
target.autoGainControl = MediaEngineStore.getAutomaticGainControl();
|
||||
}
|
||||
|
||||
function fixStreamConstraints(constraints: MediaStreamConstraints | undefined) {
|
||||
if (!constraints?.audio) return;
|
||||
|
||||
if (typeof constraints.audio !== "object") {
|
||||
constraints.audio = {};
|
||||
}
|
||||
|
||||
fixTrackConstraints(constraints.audio);
|
||||
}
|
||||
|
||||
const originalGetUserMedia = navigator.mediaDevices.getUserMedia;
|
||||
navigator.mediaDevices.getUserMedia = function (constraints) {
|
||||
try {
|
||||
fixStreamConstraints(constraints);
|
||||
logger.debug("Fixed getUserMedia constraints", constraints);
|
||||
} catch (e) {
|
||||
logger.error("Failed to fix getUserMedia constraints", e);
|
||||
}
|
||||
|
||||
return originalGetUserMedia.call(this, constraints);
|
||||
};
|
||||
|
||||
const originalApplyConstraints = MediaStreamTrack.prototype.applyConstraints;
|
||||
MediaStreamTrack.prototype.applyConstraints = function (constraints) {
|
||||
if (constraints) {
|
||||
try {
|
||||
fixTrackConstraints(constraints);
|
||||
logger.debug("Fixed applyConstraints constraints", constraints);
|
||||
} catch (e) {
|
||||
logger.error("Failed to fix applyConstraints constraints", e);
|
||||
}
|
||||
}
|
||||
return originalApplyConstraints.call(this, constraints);
|
||||
};
|
||||
19
src/renderer/patches/hideDownloadAppsButton.ts
Normal file
19
src/renderer/patches/hideDownloadAppsButton.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
/*
|
||||
* Vesktop, a desktop app aiming to give you a snappier Discord Experience
|
||||
* Copyright (c) 2025 Vendicated and Vencord contributors
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { addPatch } from "./shared";
|
||||
|
||||
addPatch({
|
||||
patches: [
|
||||
{
|
||||
find: '"app-download-button"',
|
||||
replacement: {
|
||||
match: /return(?=.{0,50}id:"app-download-button")/,
|
||||
replace: "return null;return"
|
||||
}
|
||||
}
|
||||
]
|
||||
});
|
||||
@@ -1,7 +1,7 @@
|
||||
/*
|
||||
* 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
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { addPatch } from "./shared";
|
||||
@@ -13,12 +13,12 @@ addPatch({
|
||||
replacement: {
|
||||
// eslint-disable-next-line no-useless-escape
|
||||
match: /(\i)\.\i\.getState\(\).neverShowModal/,
|
||||
replace: "$& || $self.shouldIgnore($1)"
|
||||
replace: "$& || $self.shouldIgnoreDevice($1)"
|
||||
}
|
||||
}
|
||||
],
|
||||
|
||||
shouldIgnore(state: any) {
|
||||
shouldIgnoreDevice(state: any) {
|
||||
return Object.keys(state?.default?.lastDeviceConnected ?? {})?.[0] === "vencord-screen-share";
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/*
|
||||
* 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
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { addPatch } from "./shared";
|
||||
|
||||
@@ -1,14 +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
|
||||
*/
|
||||
|
||||
// TODO: Possibly auto generate glob if we have more patches in the future
|
||||
import "./enableNotificationsByDefault";
|
||||
import "./platformClass";
|
||||
import "./hideSwitchDevice";
|
||||
import "./hideVenmicInput";
|
||||
import "./screenShareFixes";
|
||||
import "./spellCheck";
|
||||
import "./windowsTitleBar";
|
||||
@@ -1,7 +1,7 @@
|
||||
/*
|
||||
* 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
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { Settings } from "renderer/settings";
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
/*
|
||||
* 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
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { Logger } from "@vencord/types/utils";
|
||||
import { currentSettings } from "renderer/components/ScreenSharePicker";
|
||||
import { State } from "renderer/settings";
|
||||
import { isLinux } from "renderer/utils";
|
||||
|
||||
const logger = new Logger("VesktopStreamFixes");
|
||||
@@ -27,8 +28,8 @@ if (isLinux) {
|
||||
const stream = await original.call(this, opts);
|
||||
const id = await getVirtmic();
|
||||
|
||||
const frameRate = Number(currentSettings?.fps);
|
||||
const height = Number(currentSettings?.resolution);
|
||||
const frameRate = Number(State.store.screenshareQuality?.frameRate ?? 30);
|
||||
const height = Number(State.store.screenshareQuality?.resolution ?? 720);
|
||||
const width = Math.round(height * (16 / 9));
|
||||
const track = stream.getVideoTracks()[0];
|
||||
|
||||
@@ -58,10 +59,15 @@ if (isLinux) {
|
||||
},
|
||||
autoGainControl: false,
|
||||
echoCancellation: false,
|
||||
noiseSuppression: false
|
||||
noiseSuppression: false,
|
||||
channelCount: 2,
|
||||
sampleRate: 48000,
|
||||
sampleSize: 16
|
||||
}
|
||||
});
|
||||
audio.getAudioTracks().forEach(t => stream.addTrack(t));
|
||||
|
||||
stream.getAudioTracks().forEach(t => stream.removeTrack(t));
|
||||
stream.addTrack(audio.getAudioTracks()[0]);
|
||||
}
|
||||
|
||||
return stream;
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
/*
|
||||
* 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
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { Patch } from "@vencord/types/utils/types";
|
||||
|
||||
window.VCDP = {};
|
||||
window.VesktopPatchGlobals = {};
|
||||
|
||||
interface PatchData {
|
||||
patches: Omit<Patch, "plugin">[];
|
||||
@@ -16,15 +16,9 @@ interface PatchData {
|
||||
export function addPatch<P extends PatchData>(p: P) {
|
||||
const { patches, ...globals } = p;
|
||||
|
||||
for (const patch of patches as Patch[]) {
|
||||
if (!Array.isArray(patch.replacement)) patch.replacement = [patch.replacement];
|
||||
for (const r of patch.replacement) {
|
||||
if (typeof r.replace === "string") r.replace = r.replace.replaceAll("$self", "VCDP");
|
||||
}
|
||||
|
||||
patch.plugin = "Vesktop";
|
||||
Vencord.Plugins.patches.push(patch);
|
||||
for (const patch of patches) {
|
||||
Vencord.Plugins.addPatch(patch, "Vesktop", "VesktopPatchGlobals");
|
||||
}
|
||||
|
||||
Object.assign(VCDP, globals);
|
||||
Object.assign(VesktopPatchGlobals, globals);
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/*
|
||||
* 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
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { addContextMenuPatch } from "@vencord/types/api/ContextMenu";
|
||||
|
||||
21
src/renderer/patches/streamerMode.ts
Normal file
21
src/renderer/patches/streamerMode.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
/*
|
||||
* Vesktop, a desktop app aiming to give you a snappier Discord Experience
|
||||
* Copyright (c) 2025 Vendicated and Vencord contributors
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { addPatch } from "./shared";
|
||||
|
||||
addPatch({
|
||||
patches: [
|
||||
{
|
||||
find: ".STREAMER_MODE_ENABLE,",
|
||||
replacement: {
|
||||
// remove if (platformEmbedded) check from streamer mode toggle
|
||||
// eslint-disable-next-line no-useless-escape
|
||||
match: /if\(\i\.\i\)(?=return.{0,200}?"autoToggle")/g,
|
||||
replace: ""
|
||||
}
|
||||
}
|
||||
]
|
||||
});
|
||||
32
src/renderer/patches/windowMethods.tsx
Normal file
32
src/renderer/patches/windowMethods.tsx
Normal file
@@ -0,0 +1,32 @@
|
||||
/*
|
||||
* Vesktop, a desktop app aiming to give you a snappier Discord Experience
|
||||
* Copyright (c) 2025 Vendicated and Vencord contributors
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { addPatch } from "./shared";
|
||||
|
||||
addPatch({
|
||||
patches: [
|
||||
{
|
||||
find: ",setSystemTrayApplications",
|
||||
replacement: [
|
||||
{
|
||||
// eslint-disable-next-line no-useless-escape
|
||||
match: /\i\.window\.(close|minimize|maximize)/g,
|
||||
replace: `VesktopNative.win.$1`
|
||||
},
|
||||
{
|
||||
// TODO: Fix eslint rule
|
||||
// eslint-disable-next-line no-useless-escape
|
||||
match: /(focus(\(\i\)){).{0,150}?\.focus\(\i,\i\)/,
|
||||
replace: "$1VesktopNative.win.focus$2"
|
||||
},
|
||||
{
|
||||
match: /,getEnableHardwareAcceleration/,
|
||||
replace: "$&:VesktopNative.app.getEnableHardwareAcceleration,_oldGetEnableHardwareAcceleration"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
});
|
||||
@@ -1,7 +1,7 @@
|
||||
/*
|
||||
* 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
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { Settings } from "renderer/settings";
|
||||
@@ -19,11 +19,25 @@ if (Settings.store.customTitleBar)
|
||||
// eslint-disable-next-line no-useless-escape
|
||||
match: /case \i\.\i\.WINDOWS:/,
|
||||
replace: 'case "WEB":'
|
||||
}
|
||||
]
|
||||
},
|
||||
// Visual Refresh
|
||||
{
|
||||
find: ".systemBar,",
|
||||
replacement: [
|
||||
{
|
||||
// TODO: Fix eslint rule
|
||||
// eslint-disable-next-line no-useless-escape
|
||||
match: /\i===\i\.PlatformTypes\.WINDOWS/g,
|
||||
replace: "true"
|
||||
},
|
||||
...["close", "minimize", "maximize"].map(op => ({
|
||||
match: new RegExp(String.raw`\i\.\i\.${op}\b`),
|
||||
replace: `VesktopNative.win.${op}`
|
||||
}))
|
||||
{
|
||||
// TODO: Fix eslint rule
|
||||
// eslint-disable-next-line no-useless-escape
|
||||
match: /\i===\i\.PlatformTypes\.WEB/g,
|
||||
replace: "false"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
|
||||
@@ -1,12 +1,15 @@
|
||||
/*
|
||||
* 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
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { useEffect, useReducer } from "@vencord/types/webpack/common";
|
||||
import { SettingsStore } from "shared/utils/SettingsStore";
|
||||
|
||||
import { VesktopLogger } from "./logger";
|
||||
import { localStorage } from "./utils";
|
||||
|
||||
export const Settings = new SettingsStore(VesktopNative.settings.get());
|
||||
Settings.addGlobalChangeListener((o, p) => VesktopNative.settings.set(o, p));
|
||||
|
||||
@@ -28,3 +31,38 @@ export function getValueAndOnChange(key: keyof typeof Settings.store) {
|
||||
onChange: (value: any) => (Settings.store[key] = value)
|
||||
};
|
||||
}
|
||||
|
||||
interface TState {
|
||||
screenshareQuality?: {
|
||||
resolution: string;
|
||||
frameRate: string;
|
||||
};
|
||||
}
|
||||
|
||||
const stateKey = "VesktopState";
|
||||
|
||||
const currentState: TState = (() => {
|
||||
const stored = localStorage.getItem(stateKey);
|
||||
if (!stored) return {};
|
||||
try {
|
||||
return JSON.parse(stored);
|
||||
} catch (e) {
|
||||
VesktopLogger.error("Failed to parse stored state", e);
|
||||
return {};
|
||||
}
|
||||
})();
|
||||
|
||||
export const State = new SettingsStore<TState>(currentState);
|
||||
State.addGlobalChangeListener((o, p) => localStorage.setItem(stateKey, JSON.stringify(o)));
|
||||
|
||||
export function useVesktopState() {
|
||||
const [, update] = useReducer(x => x + 1, 0);
|
||||
|
||||
useEffect(() => {
|
||||
State.addGlobalChangeListener(update);
|
||||
|
||||
return () => State.removeGlobalChangeListener(update);
|
||||
}, []);
|
||||
|
||||
return State.store;
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/*
|
||||
* 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
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { Settings } from "./settings";
|
||||
@@ -10,15 +10,49 @@ function isValidColor(color: CSSStyleValue | undefined): color is CSSUnparsedVal
|
||||
return color instanceof CSSUnparsedValue && typeof color[0] === "string" && CSS.supports("color", color[0]);
|
||||
}
|
||||
|
||||
// https://gist.github.com/earthbound19/e7fe15fdf8ca3ef814750a61bc75b5ce
|
||||
function clamp(value: number, min: number, max: number) {
|
||||
return Math.max(Math.min(value, max), min);
|
||||
}
|
||||
const linearToGamma = (c: number) => (c >= 0.0031308 ? 1.055 * Math.pow(c, 1 / 2.4) - 0.055 : 12.92 * c);
|
||||
|
||||
function oklabToSRGB({ L, a, b }: { L: number; a: number; b: number }) {
|
||||
let l = L + a * +0.3963377774 + b * +0.2158037573;
|
||||
let m = L + a * -0.1055613458 + b * -0.0638541728;
|
||||
let s = L + a * -0.0894841775 + b * -1.291485548;
|
||||
l **= 3;
|
||||
m **= 3;
|
||||
s **= 3;
|
||||
let R = l * +4.0767416621 + m * -3.3077115913 + s * +0.2309699292;
|
||||
let G = l * -1.2684380046 + m * +2.6097574011 + s * -0.3413193965;
|
||||
let B = l * -0.0041960863 + m * -0.7034186147 + s * +1.707614701;
|
||||
R = 255 * linearToGamma(R);
|
||||
G = 255 * linearToGamma(G);
|
||||
B = 255 * linearToGamma(B);
|
||||
R = Math.round(clamp(R, 0, 255));
|
||||
G = Math.round(clamp(G, 0, 255));
|
||||
B = Math.round(clamp(B, 0, 255));
|
||||
|
||||
return `rgb(${R}, ${G}, ${B})`;
|
||||
}
|
||||
|
||||
function resolveColor(color: string) {
|
||||
const span = document.createElement("span");
|
||||
span.style.color = color;
|
||||
span.style.display = "none";
|
||||
|
||||
document.body.append(span);
|
||||
const rgbColor = getComputedStyle(span).color;
|
||||
let rgbColor = getComputedStyle(span).color;
|
||||
span.remove();
|
||||
|
||||
if (rgbColor.startsWith("oklab(")) {
|
||||
// scam
|
||||
const [_, L, a, b] = rgbColor.match(/oklab\((.+?)[, ]+(.+?)[, ]+(.+?)\)/) ?? [];
|
||||
if (L && a && b) {
|
||||
rgbColor = oklabToSRGB({ L: parseFloat(L), a: parseFloat(a), b: parseFloat(b) });
|
||||
}
|
||||
}
|
||||
|
||||
return rgbColor;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
/*
|
||||
* 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
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
// Discord deletes this from the window so we need to capture it in a variable
|
||||
export const { localStorage } = window;
|
||||
|
||||
export const isFirstRun = (() => {
|
||||
@@ -18,3 +19,26 @@ const { platform } = navigator;
|
||||
export const isWindows = platform.startsWith("Win");
|
||||
export const isMac = platform.startsWith("Mac");
|
||||
export const isLinux = platform.startsWith("Linux");
|
||||
|
||||
type ClassNameFactoryArg = string | string[] | Record<string, unknown> | false | null | undefined | 0 | "";
|
||||
/**
|
||||
* @param prefix The prefix to add to each class, defaults to `""`
|
||||
* @returns A classname generator function
|
||||
* @example
|
||||
* const cl = classNameFactory("plugin-");
|
||||
*
|
||||
* cl("base", ["item", "editable"], { selected: null, disabled: true })
|
||||
* // => "plugin-base plugin-item plugin-editable plugin-disabled"
|
||||
*/
|
||||
export const classNameFactory =
|
||||
(prefix: string = "") =>
|
||||
(...args: ClassNameFactoryArg[]) => {
|
||||
const classNames = new Set<string>();
|
||||
for (const arg of args) {
|
||||
if (arg && typeof arg === "string") classNames.add(arg);
|
||||
else if (Array.isArray(arg)) arg.forEach(name => classNames.add(name));
|
||||
else if (arg && typeof arg === "object")
|
||||
Object.entries(arg).forEach(([name, value]) => value && classNames.add(name));
|
||||
}
|
||||
return Array.from(classNames, name => prefix + name).join(" ");
|
||||
};
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/*
|
||||
* 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
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
export const enum IpcEvents {
|
||||
@@ -12,6 +12,7 @@ export const enum IpcEvents {
|
||||
|
||||
GET_VERSION = "VCD_GET_VERSION",
|
||||
SUPPORTS_WINDOWS_TRANSPARENCY = "VCD_SUPPORTS_WINDOWS_TRANSPARENCY",
|
||||
GET_ENABLE_HARDWARE_ACCELERATION = "VCD_GET_ENABLE_HARDWARE_ACCELERATION",
|
||||
|
||||
RELAUNCH = "VCD_RELAUNCH",
|
||||
CLOSE = "VCD_CLOSE",
|
||||
@@ -50,5 +51,22 @@ export const enum IpcEvents {
|
||||
|
||||
ARRPC_ACTIVITY = "VCD_ARRPC_ACTIVITY",
|
||||
|
||||
CLIPBOARD_COPY_IMAGE = "VCD_CLIPBOARD_COPY_IMAGE"
|
||||
CLIPBOARD_COPY_IMAGE = "VCD_CLIPBOARD_COPY_IMAGE",
|
||||
|
||||
DEBUG_LAUNCH_GPU = "VCD_DEBUG_LAUNCH_GPU",
|
||||
DEBUG_LAUNCH_WEBRTC_INTERNALS = "VCD_DEBUG_LAUNCH_WEBRTC",
|
||||
|
||||
IPC_COMMAND = "VCD_IPC_COMMAND"
|
||||
}
|
||||
|
||||
export const enum IpcCommands {
|
||||
RPC_ACTIVITY = "rpc:activity",
|
||||
RPC_INVITE = "rpc:invite",
|
||||
RPC_DEEP_LINK = "rpc:link",
|
||||
|
||||
NAVIGATE_SETTINGS = "navigate:settings",
|
||||
|
||||
GET_LANGUAGES = "navigator.languages",
|
||||
|
||||
SCREEN_SHARE_PICKER = "screenshare:picker"
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/*
|
||||
* 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
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import type { BrowserWindowConstructorOptions } from "electron";
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/*
|
||||
* 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
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { join } from "path";
|
||||
|
||||
8
src/shared/settings.d.ts
vendored
8
src/shared/settings.d.ts
vendored
@@ -1,7 +1,7 @@
|
||||
/*
|
||||
* 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
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import type { Rectangle } from "electron";
|
||||
@@ -16,12 +16,14 @@ export interface Settings {
|
||||
enableMenu?: boolean;
|
||||
disableSmoothScroll?: boolean;
|
||||
hardwareAcceleration?: boolean;
|
||||
hardwareVideoAcceleration?: boolean;
|
||||
arRPC?: boolean;
|
||||
appBadge?: boolean;
|
||||
disableMinSize?: boolean;
|
||||
clickTrayToShowHide?: boolean;
|
||||
customTitleBar?: boolean;
|
||||
|
||||
enableSplashScreen?: boolean;
|
||||
splashTheming?: boolean;
|
||||
splashColor?: string;
|
||||
splashBackground?: string;
|
||||
@@ -30,6 +32,8 @@ export interface Settings {
|
||||
|
||||
audio?: {
|
||||
workaround?: boolean;
|
||||
|
||||
deviceSelect?: boolean;
|
||||
granularSelect?: boolean;
|
||||
|
||||
ignoreVirtual?: boolean;
|
||||
@@ -45,7 +49,7 @@ export interface State {
|
||||
maximized?: boolean;
|
||||
minimized?: boolean;
|
||||
windowBounds?: Rectangle;
|
||||
displayid: int;
|
||||
displayId: int;
|
||||
|
||||
firstLaunch?: boolean;
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/*
|
||||
* 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
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { LiteralUnion } from "type-fest";
|
||||
@@ -26,11 +26,11 @@ export class SettingsStore<T extends object> {
|
||||
/**
|
||||
* The store object. Making changes to this object will trigger the applicable change listeners
|
||||
*/
|
||||
public declare store: T;
|
||||
declare public store: T;
|
||||
/**
|
||||
* The plain data. Changes to this object will not trigger any change listeners
|
||||
*/
|
||||
public declare plain: T;
|
||||
declare public plain: T;
|
||||
|
||||
public constructor(plain: T) {
|
||||
this.plain = plain;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/*
|
||||
* 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
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/*
|
||||
* 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
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
export function isTruthy<T>(item: T): item is Exclude<T, 0 | "" | false | null | undefined> {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/*
|
||||
* 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
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/*
|
||||
* 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
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
export function sleep(ms: number): Promise<void> {
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user