Compare commits

...

65 commits

Author SHA1 Message Date
Vendicated
5d675efb64
upgrade to electron33 it probably goes crazy idk what they changed 2024-10-26 13:56:28 +02:00
lewisakura
03c7ad4cc0
ci: remove mentions from meta workflow [skip ci] 2024-10-22 20:22:00 +01:00
δDD
135a369848
fix typo in Settings ui (#924)
Fixed spelling mistake (Miscelleanous → Miscellaneous)
2024-10-22 13:44:49 +00:00
ryanamay
8993b0d520
fix screenshare picker window subtitle alignment (#875)
Co-authored-by: v <vendicated@riseup.net>
2024-10-14 17:40:47 +02:00
adryd
ccff1ac3ef
Enable speech-dispatcher support for TTS on linux (#874) 2024-10-04 18:51:55 +00:00
Tiagoquix
062b536617
Update text in bug report (#873) 2024-09-24 23:55:04 +02:00
Vendicated
d008f90399
update arrpc to 5aadc307cb 2024-09-20 20:27:26 +02:00
Vendicated
4fdf43ea6a
https://tenor.com/view/ah-eto-bleh-27178660 2024-09-20 20:23:57 +02:00
Vendicated
b94379f5bd
workaround electron/electron#43367 2024-09-20 20:16:11 +02:00
Vendicated
37db07807a
upgrade electron to v32 2024-09-20 20:07:17 +02:00
Aiden
4274647c81
update ESLint to v9 (#859) 2024-09-20 20:02:15 +02:00
v
24fbf35542
https://x.com/ArmCordClient/status/1834617546445672703 2024-09-13 23:16:42 +02:00
lewisakura
c8eccc7e9d
ci: add signing certificate password for mac [skip ci] 2024-09-13 22:02:34 +01:00
v
a318f6b407
Update feature-request.yml 2024-08-27 12:59:57 +02:00
donCESAR12345
75354ad8e6
fix(rpm): don't generate build_id links to avoid conflicts (#826)
...with other packages
2024-08-24 00:17:27 +02:00
lewisakura
af9ed58eef
ci: update winget releaser [skip ci] 2024-08-10 10:21:31 +01:00
v
e0453418bd
Update bug_report.yml 2024-08-05 19:18:22 +02:00
lewisakura
22344512ad
feat: notarization (#776) 2024-07-23 15:10:50 +01:00
Noah
9acc6652ff
AudioShare: Even more granular selection, Allow device sharing (#750) 2024-07-12 16:14:18 +02:00
vee
61bbd7f6aa
Update bug_report.yml 2024-07-11 05:31:07 +02:00
github-actions[bot]
f31f06f5c4
meta: Insert release changes for v1.5.3 (#724) 2024-07-04 20:21:38 +02:00
vee
786fe131b8
Update install instructions 2024-07-04 20:21:14 +02:00
Vendicated
35d6f19e80
bump to v1.5.3 2024-07-04 19:49:52 +02:00
Vendicated
8798bbce44
only check updates in production 2024-07-04 19:47:23 +02:00
Vendicated
485eb8eaef
update user agents 2024-07-04 19:43:27 +02:00
HAHALOSAH
1e39ec7d39
macOS: Workaround for making things in draggable area clickable (#582) 2024-07-04 19:38:56 +02:00
Vendicated
5fa4ae9824
fix first launch switches 2024-07-04 19:30:03 +02:00
Vendicated
281885a87e
add auto updater 2024-07-04 19:22:26 +02:00
Vendicated
021aca4089
improve tray strings 2024-07-04 18:51:18 +02:00
Vendicated
f341c5e5bb
specify that it's for security so users don't get annoyed 2024-07-04 18:47:33 +02:00
Vendicated
0d203ac21d
remove silly console.log 2024-07-04 18:41:03 +02:00
Vendicated
1f12d270ec
fix potential sandbox escape via custom vencordDir 2024-07-04 18:40:24 +02:00
kittykel
ec3d83f7ca
fix: specify framerate in constraints (#703) 2024-07-04 18:04:57 +02:00
Vendicated
05014f747a
fix incorrectly packaging node_modules 2024-07-04 17:54:38 +02:00
Vendicated
9c44da0cae
update workflow actions 2024-07-04 17:46:49 +02:00
Lionir
5d5b38b259
meta: Use Network category instead of AudioVideo (#721) 2024-07-04 03:34:17 +02:00
Vendicated
9e3d83a2ee
build: arm64 on windows, universal on mac 2024-06-29 12:36:49 +02:00
Nico
0e49e5e8f6
fix: update switch device and notifications patch (#688) 2024-06-21 16:16:01 +02:00
vee
456d5a61cd
FOR THE LOVE OF THE GODS PLEASE STOP 2024-06-21 03:43:43 +02:00
Tiagoquix
ebd4e6b28e
Make feature request issue template apply appropriate tag (#671) 2024-06-19 19:09:53 +02:00
Tiagoquix
3fe2094814
Change default screen-sharing quality to 720p 30 FPS (#665) 2024-06-18 00:21:55 +02:00
Vendicated
9554902704
update arrpc 2024-06-16 18:42:48 +02:00
Noah
da7f13288f
linux audioshare: add granular selection, more options, better ui (#621) 2024-06-16 18:10:03 +02:00
Vendicated
1a4d173bb4
Upgrade to electron 31 2024-06-16 17:59:46 +02:00
lewisakura
abd1e8c6da
ci: update winget releaser 2024-06-04 16:51:29 +01:00
Vendicated
7fe79b629e
fix(css): brand-experiment is now brand-500 2024-06-01 19:12:29 +02:00
MrGarlic1
d3e8dfa724
hide macos traffic light with custom titlebar (#636)
Co-authored-by: MrGarlic <bsamans@samans.com>
2024-06-01 17:25:42 +02:00
MrGarlic1
3772db9eea
Windows Portable: store settings in portable folder (#592)
Co-authored-by: MrGarlic <bsamans@samans.com>
Co-authored-by: vee <vendicated@riseup.net>
2024-05-29 02:03:01 +02:00
Vendicated
b6c3c8024b
disable background throttling; hopefully fix unloads when in background 2024-05-28 18:11:55 +02:00
Vendicated
6b948668b9
make package manager check warn instead of error on mismatched version 2024-05-23 17:40:57 +02:00
Vendicated
f232cfc3c8
Spellcheck: add language changer & swap position with paste/cut/copy 2024-05-23 17:30:40 +02:00
Vendicated
bca8872db1
bump venmic 2024-05-22 22:54:35 +02:00
Vendicated
463c42330e
bump vencord types 2024-05-17 23:00:06 +02:00
Vendicated
d3b94fc4df
fix react types version 2024-05-17 22:52:05 +02:00
MrGarlic1
a8d72fa665
fix: do not re-position on screen that was disconnected (#598)
Co-authored-by: MrGarlic <bsamans@samans.com>
2024-05-10 21:04:16 +02:00
Jörg Thalheim
69f14ee611
README: update link to new nixos wiki (#594)
This commit updates the the link from the former, unofficial nixos wiki
page to the new https://wiki.nixos.org
ref: NixOS/foundation#113
2024-05-09 13:37:36 +02:00
kaitlynkitty
944a699e83
remove obsolete patch (moved to vencord) (#563) 2024-05-08 15:14:55 +00:00
Nick
d11a9a04ff
chore: update pnpm to latest version (#590) 2024-05-08 03:47:30 +02:00
splatterxl
56b96bded9
simplify mac links in readme (#585) 2024-05-06 20:27:32 +02:00
Oleh Polisan
ac35f81476
fix: Screenshare UI for non-linux systems (#568) 2024-05-05 04:14:08 +02:00
Vendicated
0623a71271
improve issue templates 2024-05-03 14:49:49 +02:00
samara
a66af353f7
macOS: Add customized dmg background (#565) 2024-05-02 23:41:09 +02:00
github-actions[bot]
cb55cf4b74
add Metainfo for v1.5.2 (#557)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2024-05-01 16:57:29 +02:00
Vendicated
e9da30e420
bump to v1.5.2 2024-05-01 16:45:15 +02:00
☆ sam
c52abdc1ee
mac: add entitlements needed for camera/microphone access (#533) 2024-04-26 22:02:19 +02:00
48 changed files with 5468 additions and 3928 deletions

View file

@ -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 Normal file
View file

@ -0,0 +1,18 @@
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

View file

@ -1,58 +0,0 @@
---
name: Bug report
about: Create a report to help us improve
title: ''
labels: bug
assignees: ''
---
<!--
Please do not open issues for the following things. We cannot help you with them:
- "Vesktop.app is damaged" on MacOs ~ Fake issue created by crApple. Google how to fix it https://google.it/search?q=fix+app+is+damaged
- Screenshare does not start / is black ~ This is an issue with your desktop environment, specifically its xdg-desktop-portal
- Purely graphical glitches, like flickering, scaling issues, short whitescreens, etc ~ These are most likely issues with your GPU. try to disable hardware acceleration
- Vencord related issues ~ This is the Vesktop repo, not Vencord
- Getting logged out after restart ~ If you use DevTools, make sure you have NoDevtoolsWarning enabled. Otherwise try reinstalling Vesktop
-->
**Describe the bug**
<!-- A clear and concise description of what the bug is. -->
**To Reproduce**
<!--
Steps to reproduce the behavior:
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
4. See error
-->
**Expected behavior**
<!-- A clear and concise description of what you expected to happen. -->
**Screenshots**
<!-- If applicable, add screenshots to help explain your problem. -->
**Desktop (please complete the following information):**
- OS/Distro: [e.g. Windows / Fedora Linux / MacOs]
- Desktop Environment (linux only): [e.g. gnome, kde, sway]
- Version: [e.g. 22]
**Command line output**
<!-- Run vesktop from the command line. Include the relevant command line output here: -->
```
paste inside these backticks
```
**Additional context**
<!-- Add any other context about the problem here. -->

130
.github/ISSUE_TEMPLATE/bug_report.yml vendored Normal file
View file

@ -0,0 +1,130 @@
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 "Repair Vencord"
**DO NOT REPORT** any of the following issues:
- Purely graphical glitches like flickering, scaling issues[^1]
- App crashing / not showing window with mentions of the gpu process in the stacktrace[^1]
- Screenshare not starting, black screening or crashing[^2]
- Vencord related issues: This is the Vesktop repo, not Vencord
- Captchas[^3]
- Issues with opening URLs[^4]
- Issues with Notifications[^4]
- Issues with Input Methods[^4]
- Issues with File Drag and Drop[^5]
- Network Errors[^6]
Linux users: Please only report issues with supported packages (flatpak and any builds from the README / releases).
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)
[^1]: GPU issue. Disable hardware acceleration in Vesktop Settings or run with `--disable-gpu`
[^2]: System issue. You will have to fix it
[^3]: If you are receiving a lot of captchas, it means Discord thinks you might be a bot. Make sure you're not using a VPN/Proxy
[^4]: These things are handled by Chromium / Electron, not us. If they don't work, it's either an issue with your system or a bug with Chromium.
[^5]: You are likely using the Vesktop flatpak and trying to drop a file the flatpak can't access. You can fix this by installing Flatseal and using it to grant Vesktop full access to your files
[^6]: Issue on your end, you have to fix it. Try changing your DNS to [1.1.1.1 (Cloudflare DNS)](https://developers.cloudflare.com/1.1.1.1/setup/)
- type: input
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: input
id: install-type
attributes:
label: Package Type
description: What kind of Vesktop package are you using? (Setup exe, Portable, Flatpak, AppImage, Deb, etc)
placeholder: Flatpak
validations:
required: true
- type: textarea
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. If there are any lines that seem relevant, try googling them or searching existing issues
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

5
.github/ISSUE_TEMPLATE/config.yml vendored Normal file
View file

@ -0,0 +1,5 @@
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!

View file

@ -1,10 +0,0 @@
---
name: Custom issue template
about: Describe this issue template's purpose here.
title: ''
labels: ''
assignees: ''
---

View file

@ -0,0 +1,74 @@
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!
**DO NOT** make any icon related requests or you will be blocked.
- 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

View file

@ -1,20 +0,0 @@
---
name: Feature request
about: Suggest an idea for this project
title: ''
labels: enhancement
assignees: ''
---
**Is your feature request related to a problem? Please describe.**
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
**Describe the solution you'd like**
A clear and concise description of what you want to happen.
**Describe alternatives you've considered**
A clear and concise description of any alternative solutions or features you've considered.
**Additional context**
Add any other context or screenshots about the feature request here.

View file

@ -11,13 +11,13 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: pnpm/action-setup@v2 # Install pnpm using packageManager key in package.json
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v4 # Install pnpm using packageManager key in package.json
- name: Use Node.js 18.18.2
uses: actions/setup-node@v3
- name: Use Node.js 20
uses: actions/setup-node@v4
with:
node-version: 18.18.2
node-version: 20
- name: Install dependencies
run: pnpm i
@ -33,6 +33,6 @@ jobs:
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"
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 }}."
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

View file

@ -22,13 +22,13 @@ jobs:
platform: windows
steps:
- uses: actions/checkout@v3
- uses: pnpm/action-setup@v2 # Install pnpm using packageManager key in package.json
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v4 # Install pnpm using packageManager key in package.json
- name: Use Node.js 18.18.2
uses: actions/setup-node@v3
- name: Use Node.js 20
uses: actions/setup-node@v4
with:
node-version: 18.18.2
node-version: 20
cache: "pnpm"
- name: Install dependencies
@ -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 }}
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 }}

View file

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

View file

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

1
.npmrc
View file

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

View file

@ -21,15 +21,14 @@ 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 amd64
- [amd64 / x86_64](https://vencord.dev/download/vesktop/amd64/dmg)
- [arm64 / aarch64](https://vencord.dev/download/vesktop/arm64/dmg)
[Vesktop.dmg](https://vencord.dev/download/vesktop/universal/dmg)
### Linux
@ -42,7 +41,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)
@ -53,7 +52,7 @@ If you don't know the difference, pick amd64.
Below you can find unofficial packages created by the community. They are not officially supported by us, so before reporting issues, please first confirm the issue also happens on official builds. When in doubt, consult with their packager first. The flatpak and AppImage should work on any distro that [supports them](https://flatpak.org/setup/), so I recommend you just use those instead!
- Arch Linux: [Vesktop on the Arch user repository](https://aur.archlinux.org/packages?K=vesktop)
- NixOS: https://nixos.wiki/wiki/Discord#Vesktop
- NixOS: https://wiki.nixos.org/wiki/Discord#Vesktop
- Windows - Scoop: https://scoop.sh/#/apps?q=Vesktop
## Building from Source

BIN
build/background.tiff Normal file

Binary file not shown.

View file

@ -0,0 +1,21 @@
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.security.cs.allow-unsigned-executable-memory</key>
<true/>
<key>com.apple.security.cs.allow-jit</key>
<true/>
<key>com.apple.security.network.client</key>
<true/>
<key>com.apple.security.device.audio-input</key>
<true/>
<key>com.apple.security.device.camera</key>
<true/>
<key>com.apple.security.device.bluetooth</key>
<true/>
<key>com.apple.security.cs.allow-dyld-environment-variables</key>
<true/>
<key>com.apple.security.cs.disable-library-validation</key>
<true/>
</dict>
</plist>

102
eslint.config.mjs Normal file
View file

@ -0,0 +1,102 @@
/*
* 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
*/
//@ts-check
import stylistic from "@stylistic/eslint-plugin";
import pathAlias from "eslint-plugin-path-alias";
import header 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: {
header,
stylistic,
importSort,
unusedImports,
pathAlias,
prettier
},
settings: {
"import/resolver": {
alias: {
map: []
}
}
},
languageOptions: {
parser: tseslint.parser,
parserOptions: {
project: true,
tsconfigRootDir: import.meta.dirname
}
},
rules: {
"header/header": [
"error",
{
files: ["scripts/header.txt"]
}
],
// 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"
}
}
);

View file

@ -28,6 +28,48 @@
</screenshot>
</screenshots>
<releases>
<release version="1.5.3" date="2024-07-04" type="stable">
<url>https://github.com/Vencord/Vesktop/releases/tag/v1.5.3</url>
<description>
<p>Features</p>
<ul>
<li>added arm64 Windows support</li>
<li>windows &amp; macOS builds are now universal</li>
<li>added option to configure spellcheck languages</li>
<li>will auto-update from now on</li>
<li>updated electron to 31 &amp; Chromium to 126</li>
<li>macOS: Added customized dmg background by @khcrysalis</li>
<li>Windows Portable: store settings in portable folder by @MrGarlic1</li>
<li>linux audioshare: added granular selection, more options, better ui by @Curve</li>
<li>changed default screen-sharing quality to 720p 30 FPS by @Tiagoquix</li>
</ul>
<p>Fixes</p>
<ul>
<li>macOS: Added workaround for making things in draggable area clickable by @HAHALOSAH</li>
<li>fixed Screenshare UI for non-linux systems by @PolisanTheEasyNick</li>
<li>fixed opening on screen that was disconnected by @MrGarlic1</li>
<li>mac: hide native window controls with custom titlebar enabled by @MrGarlic1</li>
<li>fixed some broken patches by @D3SOX</li>
<li>fixed framerate in constraints by @kittykel</li>
<li>fixed some first launch switches not applying</li>
<li>fixed potential sandbox escape via custom vencord location</li>
</ul>
</description>
</release>
<release version="1.5.2" date="2024-05-01" type="stable">
<url>https://github.com/Vencord/Vesktop/releases/tag/v1.5.2</url>
<description>
<p>What's Changed</p>
<ul>
<li>Fixed scrollbars looking wrong (actually Discord's fault)</li>
<li>Tray: Added left click hide/show feature by @0bCdian</li>
<li>MacOS: Fixed the app not properly requesting microphone permissions by @ssalggnikool</li>
<li>Linux: Various fixed related to audio screenshare by @Curve</li>
<li>Linux: Overhauled &amp; improved screenshare with better framerate by @kaitlynkittyy</li>
<li>Users can now pass --enable/disable-features command line flags by @takase1121</li>
</ul>
</description>
</release>
<release version="1.5.1" date="2024-03-12" type="stable">
<url>https://github.com/Vencord/Vesktop/releases/tag/v1.5.1</url>
<description>
@ -168,7 +210,7 @@
<url type="vcs-browser">https://github.com/Vencord/Vesktop</url>
<categories>
<category>InstantMessaging</category>
<category>AudioVideo</category>
<category>Network</category>
</categories>
<requires>
<control>pointing</control>

View file

@ -1,8 +1,8 @@
{
"name": "vesktop",
"version": "1.5.1",
"version": "1.5.3",
"private": true,
"description": "",
"description": "Vesktop is a custom Discord desktop app",
"keywords": [],
"homepage": "https://vencord.dev/",
"license": "GPL-3.0",
@ -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 .",
@ -24,38 +24,38 @@
"updateMeta": "tsx scripts/utils/updateMeta.mts"
},
"dependencies": {
"arrpc": "github:OpenAsar/arrpc#6960a8fd4d65d566da93dbdb8a7ca474aa0a3c9c"
"arrpc": "github:OpenAsar/arrpc#5aadc307cb9bf4479f0a12364a253b07a77ace22",
"electron-updater": "^6.3.4"
},
"optionalDependencies": {
"@vencord/venmic": "^3.4.2"
"@vencord/venmic": "^6.1.0"
},
"devDependencies": {
"@fal-works/esbuild-plugin-global-externals": "^2.1.2",
"@types/node": "^20.11.26",
"@types/react": "^18.2.65",
"@typescript-eslint/eslint-plugin": "^7.2.0",
"@typescript-eslint/parser": "^7.2.0",
"@vencord/types": "^0.1.2",
"@stylistic/eslint-plugin": "^2.8.0",
"@types/node": "^22.5.5",
"@types/react": "^18.3.8",
"@vencord/types": "^1.8.4",
"dotenv": "^16.4.5",
"electron": "^29.1.1",
"electron-builder": "^24.13.3",
"esbuild": "^0.20.1",
"eslint": "^8.57.0",
"eslint-config-prettier": "^9.1.0",
"electron": "^33.0.2",
"electron-builder": "^25.0.5",
"esbuild": "^0.23.1",
"eslint": "^9.11.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.2.1",
"eslint-plugin-simple-header": "^1.2.1",
"eslint-plugin-simple-import-sort": "^12.1.1",
"eslint-plugin-unused-imports": "^4.1.4",
"prettier": "^3.3.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.1",
"type-fest": "^4.26.1",
"typescript": "^5.6.2",
"typescript-eslint": "^8.6.0",
"xml-formatter": "^3.6.3"
},
"packageManager": "pnpm@8.11.0",
"packageManager": "pnpm@9.1.0",
"engines": {
"node": ">=18",
"pnpm": ">=8"
@ -65,6 +65,7 @@
"productName": "Vesktop",
"files": [
"!*",
"!node_modules",
"dist/js",
"static",
"package.json",
@ -118,18 +119,40 @@
{
"target": "default",
"arch": [
"x64",
"arm64"
"universal"
]
}
],
"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",
"icon": "build/icon.icns",
"iconSize": 105,
"window": {
"width": 512,
"height": 340
},
"contents": [
{
"x": 140,
"y": 160
},
{
"x": 372,
"y": 160,
"type": "link",
"path": "/Applications"
}
]
},
"nsis": {
"include": "build/installer.nsh",
@ -137,12 +160,34 @@
},
"win": {
"target": [
"nsis",
"zip"
{
"target": "nsis",
"arch": [
"x64",
"arm64"
]
},
{
"target": "zip",
"arch": [
"x64",
"arm64"
]
}
]
},
"publish": {
"provider": "github"
},
"rpm": {
"fpm": [
"--rpm-rpmbuild-define=_build_id_links none"
]
}
},
"pnpm": {
"patchedDependencies": {
"arrpc@3.5.0": "patches/arrpc@3.5.0.patch"
}
}
}

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

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

File diff suppressed because it is too large Load diff

View file

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

View file

@ -5,11 +5,22 @@
*/
import { app } from "electron";
import { existsSync, readdirSync, renameSync, rmdirSync } from "fs";
import { join } from "path";
import { existsSync, mkdirSync, readdirSync, renameSync, rmdirSync } from "fs";
import { dirname, join } from "path";
const vesktopDir = dirname(process.execPath);
export const PORTABLE =
process.platform === "win32" &&
!process.execPath.toLowerCase().endsWith("electron.exe") &&
!existsSync(join(vesktopDir, "Uninstall Vesktop.exe"));
const LEGACY_DATA_DIR = join(app.getPath("appData"), "VencordDesktop", "VencordDesktop");
export const DATA_DIR = process.env.VENCORD_USER_DATA_DIR || join(app.getPath("userData"));
export const DATA_DIR =
process.env.VENCORD_USER_DATA_DIR || (PORTABLE ? join(vesktopDir, "Data") : join(app.getPath("userData")));
mkdirSync(DATA_DIR, { recursive: true });
// TODO: remove eventually
if (existsSync(LEGACY_DATA_DIR)) {
try {
@ -26,7 +37,8 @@ if (existsSync(LEGACY_DATA_DIR)) {
console.error("Migration failed", e);
}
}
app.setPath("sessionData", join(DATA_DIR, "sessionData"));
const SESSION_DATA_DIR = join(DATA_DIR, "sessionData");
app.setPath("sessionData", SESSION_DATA_DIR);
export const VENCORD_SETTINGS_DIR = join(DATA_DIR, "settings");
export const VENCORD_QUICKCSS_FILE = join(VENCORD_SETTINGS_DIR, "quickCss.css");
@ -36,7 +48,8 @@ export const VENCORD_THEMES_DIR = join(DATA_DIR, "themes");
// needs to be inline require because of circular dependency
// as otherwise "DATA_DIR" (which is used by ./settings) will be uninitialised
export const VENCORD_FILES_DIR =
(require("./settings") as typeof import("./settings")).Settings.store.vencordDir || join(DATA_DIR, "vencordDist");
(require("./settings") as typeof import("./settings")).State.store.vencordDir ||
join(SESSION_DATA_DIR, "vencordFiles");
export const USER_AGENT = `Vesktop/${app.getVersion()} (https://github.com/Vencord/Vesktop)`;
@ -48,11 +61,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/122.0.0.0 Safari/537.36",
linux: "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36",
windows:
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36"
darwin: `Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) ${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;

View file

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

View file

@ -7,7 +7,7 @@
import "./ipc";
import { app, BrowserWindow, nativeTheme } from "electron";
import { checkUpdates } from "updater/main";
import { autoUpdater } from "electron-updater";
import { DATA_DIR } from "./constants";
import { createFirstLaunchTour } from "./firstLaunch";
@ -19,6 +19,8 @@ import { isDeckGameMode } from "./utils/steamOS";
if (IS_DEV) {
require("source-map-support").install();
} else {
autoUpdater.checkForUpdatesAndNotify();
}
// Make the Vencord files use our DATA_DIR
@ -40,18 +42,26 @@ function init() {
app.commandLine.appendSwitch("disable-smooth-scrolling");
}
// disable renderer backgrounding to prevent the app from unloading when in the background
// https://github.com/electron/electron/issues/2822
// https://github.com/GoogleChrome/chrome-launcher/blob/5a27dd574d47a75fec0fb50f7b774ebf8a9791ba/docs/chrome-flags-for-tools.md#task-throttling
app.commandLine.appendSwitch("disable-renderer-backgrounding");
app.commandLine.appendSwitch("disable-background-timer-throttling");
app.commandLine.appendSwitch("disable-backgrounding-occluded-windows");
if (process.platform === "win32") {
disabledFeatures.push("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",
"WidgetLayering"
);
disabledFeatures.push("WinRetrieveSuggestionsOnlyOnDemand", "HardwareMediaKeyHandling", "MediaSessionService");
// Support TTS on Linux using speech-dispatcher
app.commandLine.appendSwitch("enable-speech-dispatcher");
app.commandLine.appendSwitch("enable-features", [...new Set(enabledFeatures)].filter(Boolean).join(","));
app.commandLine.appendSwitch("disable-features", [...new Set(disabledFeatures)].filter(Boolean).join(","));
@ -69,7 +79,6 @@ function init() {
});
app.whenReady().then(async () => {
checkUpdates();
if (process.platform === "win32") app.setAppUserModelId("dev.vencord.vesktop");
registerScreenShareHandler();

View file

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

View file

@ -12,6 +12,8 @@ import {
Menu,
MenuItemConstructorOptions,
nativeTheme,
screen,
session,
Tray
} from "electron";
import { rm } from "fs/promises";
@ -89,7 +91,7 @@ function initTray(win: BrowserWindow) {
click: createAboutWindow
},
{
label: "Update Vencord",
label: "Repair Vencord",
async click() {
await downloadVencordFiles();
app.relaunch();
@ -106,14 +108,14 @@ function initTray(win: BrowserWindow) {
type: "separator"
},
{
label: "Relaunch",
label: "Restart",
click() {
app.relaunch();
app.quit();
}
},
{
label: "Quit Vesktop",
label: "Quit",
click() {
isQuitting = true;
app.quit();
@ -269,7 +271,9 @@ function getWindowBoundsOptions(): BrowserWindowConstructorOptions {
height: height ?? DEFAULT_HEIGHT
} as BrowserWindowConstructorOptions;
if (x != null && y != null) {
const storedDisplay = screen.getAllDisplays().find(display => display.id === State.store.displayid);
if (x != null && y != null && storedDisplay) {
options.x = x;
options.y = y;
}
@ -317,6 +321,7 @@ function initWindowBoundsListeners(win: BrowserWindow) {
const saveBounds = () => {
State.store.windowBounds = win.getBounds();
State.store.displayid = screen.getDisplayMatching(State.store.windowBounds).id;
};
win.on("resize", saveBounds);
@ -356,12 +361,27 @@ function initSettingsListeners(win: BrowserWindow) {
addSettingsListener("enableMenu", enabled => {
win.setAutoHideMenuBar(enabled ?? false);
});
addSettingsListener("spellCheckLanguages", languages => initSpellCheckLanguages(win, languages));
}
async function initSpellCheckLanguages(win: BrowserWindow, languages?: string[]) {
languages ??= await win.webContents.executeJavaScript("[...new Set(navigator.languages)]").catch(() => []);
if (!languages) return;
const ses = session.defaultSession;
const available = ses.availableSpellCheckerLanguages;
const applicable = languages.filter(l => available.includes(l)).slice(0, 5);
if (applicable.length) ses.setSpellCheckerLanguages(applicable);
}
function initSpellCheck(win: BrowserWindow) {
win.webContents.on("context-menu", (_, data) => {
win.webContents.send(IpcEvents.SPELLCHECK_RESULT, data.misspelledWord, data.dictionarySuggestions);
});
initSpellCheckLanguages(win, Settings.store.spellCheckLanguages);
}
function createMainWindow() {
@ -383,7 +403,9 @@ function createMainWindow() {
contextIsolation: true,
devTools: true,
preload: join(__dirname, "preload.js"),
spellcheck: true
spellcheck: true,
// disable renderer backgrounding to prevent the app from unloading when in the background
backgroundThrottling: false
},
icon: ICON_PATH,
frame: !noFrame,
@ -408,6 +430,7 @@ function createMainWindow() {
autoHideMenuBar: enableMenu
}));
win.setMenuBarVisibility(false);
if (process.platform === "darwin" && customTitleBar) win.setWindowButtonVisibility(false);
win.on("close", e => {
const useTray = !isDeckGameMode && Settings.store.minimizeToTray !== false && Settings.store.tray !== false;
@ -476,5 +499,17 @@ export async function createWindows() {
});
});
// evil hack to fix electron 32 regression that makes devtools always light theme
// https://github.com/electron/electron/issues/43367
// TODO: remove once fixed
mainWin.webContents.on("devtools-opened", () => {
if (!nativeTheme.shouldUseDarkColors) return;
nativeTheme.themeSource = "light";
setTimeout(() => {
nativeTheme.themeSource = "dark";
}, 100);
});
initArRPC();
}

View file

@ -12,12 +12,14 @@ export function registerMediaPermissionsHandler() {
session.defaultSession.setPermissionRequestHandler(async (_webContents, permission, callback, details) => {
let granted = true;
if ("mediaTypes" in details) {
if (details.mediaTypes?.includes("audio")) {
granted = await systemPreferences.askForMediaAccess("microphone");
granted &&= await systemPreferences.askForMediaAccess("microphone");
}
if (details.mediaTypes?.includes("video")) {
granted &&= await systemPreferences.askForMediaAccess("camera");
}
}
callback(granted);
});

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -11,17 +11,6 @@
gap: 1em;
}
.vcd-screen-picker-settings-grid {
gap: 1em;
display: grid;
grid-template-columns: 1fr 1fr;
}
.vcd-screen-picker-settings-grid > div {
display: flex;
flex-direction: column;
}
.vcd-screen-picker-card {
flex-grow: 1;
}
@ -38,21 +27,22 @@
}
.vcd-screen-picker-selected img {
border: 2px solid var(--brand-experiment);
border: 2px solid var(--brand-500);
border-radius: 3px;
}
.vcd-screen-picker-grid label {
overflow: hidden;
padding: 4px 0px;
padding: 8px;
cursor: pointer;
display: grid;
justify-items: center;
}
.vcd-screen-picker-grid label:hover {
outline: 2px solid var(--brand-experiment);
outline: 2px solid var(--brand-500);
}
.vcd-screen-picker-grid div {
white-space: nowrap;
text-overflow: ellipsis;
@ -67,8 +57,13 @@
box-sizing: border-box;
}
.vcd-screen-picker-preview img {
width: 100%;
.vcd-screen-picker-preview-img-linux {
width: 60%;
margin-bottom: 0.5em;
}
.vcd-screen-picker-preview-img {
width: 90%;
margin-bottom: 0.5em;
}
@ -96,8 +91,8 @@
}
.vcd-screen-picker-radio[data-checked="true"] {
background-color: var(--brand-experiment);
border-color: var(--brand-experiment);
background-color: var(--brand-500);
border-color: var(--brand-500);
}
.vcd-screen-picker-radio[data-checked="true"] h2 {
@ -107,7 +102,6 @@
.vcd-screen-picker-quality {
display: flex;
gap: 1em;
margin-bottom: 0.5em;
}
@ -115,6 +109,11 @@
flex: 1 1 auto;
}
.vcd-screen-picker-settings-button {
margin-left: auto;
margin-top: 0.3rem;
}
.vcd-screen-picker-radios {
display: flex;
width: 100%;

View file

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

View file

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

View file

@ -9,3 +9,8 @@
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;
}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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