forked from mirror/Vesktop
Compare commits
2 commits
main
...
mac-entitl
Author | SHA1 | Date | |
---|---|---|---|
|
3b0f21bee8 | ||
|
9aaa38ea2d |
53 changed files with 4084 additions and 6194 deletions
69
.eslintrc.json
Normal file
69
.eslintrc.json
Normal file
|
@ -0,0 +1,69 @@
|
||||||
|
{
|
||||||
|
"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
|
|
58
.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
58
.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
|
@ -0,0 +1,58 @@
|
||||||
|
---
|
||||||
|
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. -->
|
133
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
133
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
|
@ -1,133 +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 "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]
|
|
||||||
- Anything about Screenshare Performance[^7]
|
|
||||||
|
|
||||||
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/)
|
|
||||||
[^7]: Screensharing is managed entirely by Chromium and your System. For optimal performance, make sure you [enable Hardware Acceleration](https://wiki.archlinux.org/title/Chromium#Hardware_video_acceleration).
|
|
||||||
Depending on GPU driver, it might not be supported. We are actively working on making this work out of the box everywhere.
|
|
||||||
|
|
||||||
- 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
5
.github/ISSUE_TEMPLATE/config.yml
vendored
|
@ -1,5 +0,0 @@
|
||||||
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!
|
|
10
.github/ISSUE_TEMPLATE/custom.md
vendored
Normal file
10
.github/ISSUE_TEMPLATE/custom.md
vendored
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
---
|
||||||
|
name: Custom issue template
|
||||||
|
about: Describe this issue template's purpose here.
|
||||||
|
title: ''
|
||||||
|
labels: ''
|
||||||
|
assignees: ''
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
|
74
.github/ISSUE_TEMPLATE/feature-request.yml
vendored
74
.github/ISSUE_TEMPLATE/feature-request.yml
vendored
|
@ -1,74 +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!
|
|
||||||
|
|
||||||
**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
|
|
20
.github/ISSUE_TEMPLATE/feature_request.md
vendored
Normal file
20
.github/ISSUE_TEMPLATE/feature_request.md
vendored
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
---
|
||||||
|
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.
|
12
.github/workflows/meta.yml
vendored
12
.github/workflows/meta.yml
vendored
|
@ -11,13 +11,13 @@ jobs:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v3
|
||||||
- uses: pnpm/action-setup@v4 # Install pnpm using packageManager key in package.json
|
- uses: pnpm/action-setup@v2 # Install pnpm using packageManager key in package.json
|
||||||
|
|
||||||
- name: Use Node.js 20
|
- name: Use Node.js 18.18.2
|
||||||
uses: actions/setup-node@v4
|
uses: actions/setup-node@v3
|
||||||
with:
|
with:
|
||||||
node-version: 20
|
node-version: 18.18.2
|
||||||
|
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: pnpm i
|
run: pnpm i
|
||||||
|
@ -33,6 +33,6 @@ jobs:
|
||||||
git add meta/dev.vencord.Vesktop.metainfo.xml
|
git add meta/dev.vencord.Vesktop.metainfo.xml
|
||||||
git commit -m "Insert release changes for ${{ github.event.release.tag_name }}"
|
git commit -m "Insert release changes for ${{ github.event.release.tag_name }}"
|
||||||
git push origin ci/meta-update
|
git push origin ci/meta-update
|
||||||
gh pr create -B main -H ci/meta-update -t "Metainfo for ${{ github.event.release.tag_name }}" -b "This PR updates the metainfo for release ${{ github.event.release.tag_name }}."
|
gh pr create -B main -H ci/meta-update -t "Metainfo for ${{ github.event.release.tag_name }}" -b "This PR updates the metainfo for release ${{ github.event.release.tag_name }}. @lewisakura @Vendicated"
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
16
.github/workflows/release.yml
vendored
16
.github/workflows/release.yml
vendored
|
@ -22,13 +22,13 @@ jobs:
|
||||||
platform: windows
|
platform: windows
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v3
|
||||||
- uses: pnpm/action-setup@v4 # Install pnpm using packageManager key in package.json
|
- uses: pnpm/action-setup@v2 # Install pnpm using packageManager key in package.json
|
||||||
|
|
||||||
- name: Use Node.js 20
|
- name: Use Node.js 18.18.2
|
||||||
uses: actions/setup-node@v4
|
uses: actions/setup-node@v3
|
||||||
with:
|
with:
|
||||||
node-version: 20
|
node-version: 18.18.2
|
||||||
cache: "pnpm"
|
cache: "pnpm"
|
||||||
|
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
|
@ -47,13 +47,7 @@ jobs:
|
||||||
- name: Run Electron Builder
|
- name: Run Electron Builder
|
||||||
if: ${{ matrix.platform == 'mac' }}
|
if: ${{ matrix.platform == 'mac' }}
|
||||||
run: |
|
run: |
|
||||||
echo "$API_KEY" > apple.p8
|
|
||||||
pnpm electron-builder --${{ matrix.platform }} --publish always
|
pnpm electron-builder --${{ matrix.platform }} --publish always
|
||||||
env:
|
env:
|
||||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
CSC_LINK: ${{ secrets.APPLE_SIGNING_CERT }}
|
CSC_LINK: ${{ secrets.APPLE_SIGNING_CERT }}
|
||||||
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 }}
|
|
||||||
|
|
10
.github/workflows/test.yml
vendored
10
.github/workflows/test.yml
vendored
|
@ -11,13 +11,13 @@ jobs:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v3
|
||||||
- uses: pnpm/action-setup@v4 # Install pnpm using packageManager key in package.json
|
- uses: pnpm/action-setup@v2 # Install pnpm using packageManager key in package.json
|
||||||
|
|
||||||
- name: Use Node.js 20
|
- name: Use Node.js 18.18.2
|
||||||
uses: actions/setup-node@v4
|
uses: actions/setup-node@v3
|
||||||
with:
|
with:
|
||||||
node-version: 20
|
node-version: 18.18.2
|
||||||
cache: "pnpm"
|
cache: "pnpm"
|
||||||
|
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
|
|
4
.github/workflows/winget-submission.yml
vendored
4
.github/workflows/winget-submission.yml
vendored
|
@ -13,10 +13,10 @@ on:
|
||||||
jobs:
|
jobs:
|
||||||
winget:
|
winget:
|
||||||
name: Publish winget package
|
name: Publish winget package
|
||||||
runs-on: windows-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Submit package to Winget Community Repo
|
- name: Submit package to Winget Community Repo
|
||||||
uses: vedantmgoyal2009/winget-releaser@0db4f0a478166abd0fa438c631849f0b8dcfb99f
|
uses: vedantmgoyal2009/winget-releaser@e68d386d5d6a1cef8cb0fb5e62b77ebcb83e7d58 # v2
|
||||||
with:
|
with:
|
||||||
identifier: Vencord.Vesktop
|
identifier: Vencord.Vesktop
|
||||||
token: ${{ secrets.WINGET_PAT }}
|
token: ${{ secrets.WINGET_PAT }}
|
||||||
|
|
1
.npmrc
1
.npmrc
|
@ -1,2 +1 @@
|
||||||
node-linker=hoisted
|
node-linker=hoisted
|
||||||
package-manager-strict=false
|
|
16
README.md
16
README.md
|
@ -21,14 +21,15 @@ Vesktop is a custom Discord desktop app
|
||||||
|
|
||||||
If you don't know the difference, pick the Installer.
|
If you don't know the difference, pick the Installer.
|
||||||
|
|
||||||
- [Installer](https://vencord.dev/download/vesktop/universal/windows)
|
- [Installer](https://vencord.dev/download/vesktop/amd64/windows)
|
||||||
- Portable:
|
- [Portable](https://vencord.dev/download/vesktop/amd64/windows-portable)
|
||||||
- [x64 / amd64](https://vencord.dev/download/vesktop/amd64/windows-portable)
|
|
||||||
- [Arm® 64](https://vencord.dev/download/vesktop/arm64/windows-portable)
|
|
||||||
|
|
||||||
### Mac
|
### Mac
|
||||||
|
|
||||||
[Vesktop.dmg](https://vencord.dev/download/vesktop/universal/dmg)
|
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)
|
||||||
|
|
||||||
### Linux
|
### Linux
|
||||||
|
|
||||||
|
@ -41,7 +42,7 @@ If you don't know the difference, pick amd64.
|
||||||
- [Ubuntu/Debian (.deb)](https://vencord.dev/download/vesktop/amd64/deb)
|
- [Ubuntu/Debian (.deb)](https://vencord.dev/download/vesktop/amd64/deb)
|
||||||
- [Fedora/RHEL (.rpm)](https://vencord.dev/download/vesktop/amd64/rpm)
|
- [Fedora/RHEL (.rpm)](https://vencord.dev/download/vesktop/amd64/rpm)
|
||||||
- [tarball](https://vencord.dev/download/vesktop/amd64/tar)
|
- [tarball](https://vencord.dev/download/vesktop/amd64/tar)
|
||||||
- Arm® 64 / aarch64
|
- arm64 / aarch64
|
||||||
- [AppImage](https://vencord.dev/download/vesktop/arm64/appimage)
|
- [AppImage](https://vencord.dev/download/vesktop/arm64/appimage)
|
||||||
- [Ubuntu/Debian (.deb)](https://vencord.dev/download/vesktop/arm64/deb)
|
- [Ubuntu/Debian (.deb)](https://vencord.dev/download/vesktop/arm64/deb)
|
||||||
- [Fedora/RHEL (.rpm)](https://vencord.dev/download/vesktop/arm64/rpm)
|
- [Fedora/RHEL (.rpm)](https://vencord.dev/download/vesktop/arm64/rpm)
|
||||||
|
@ -52,8 +53,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!
|
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)
|
- Arch Linux: [Vesktop on the Arch user repository](https://aur.archlinux.org/packages?K=vesktop)
|
||||||
- NixOS: https://wiki.nixos.org/wiki/Discord#Vesktop
|
- NixOS: https://nixos.wiki/wiki/Discord#Vesktop
|
||||||
- Slackware: [Vesktop on the SlackBuilds](https://slackbuilds.org/result/?search=vesktop)
|
|
||||||
- Windows - Scoop: https://scoop.sh/#/apps?q=Vesktop
|
- Windows - Scoop: https://scoop.sh/#/apps?q=Vesktop
|
||||||
|
|
||||||
## Building from Source
|
## Building from Source
|
||||||
|
|
Binary file not shown.
|
@ -1,102 +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
|
|
||||||
*/
|
|
||||||
|
|
||||||
//@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"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
|
|
@ -28,61 +28,6 @@
|
||||||
</screenshot>
|
</screenshot>
|
||||||
</screenshots>
|
</screenshots>
|
||||||
<releases>
|
<releases>
|
||||||
<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>
|
|
||||||
<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 & 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">
|
<release version="1.5.1" date="2024-03-12" type="stable">
|
||||||
<url>https://github.com/Vencord/Vesktop/releases/tag/v1.5.1</url>
|
<url>https://github.com/Vencord/Vesktop/releases/tag/v1.5.1</url>
|
||||||
<description>
|
<description>
|
||||||
|
@ -223,7 +168,7 @@
|
||||||
<url type="vcs-browser">https://github.com/Vencord/Vesktop</url>
|
<url type="vcs-browser">https://github.com/Vencord/Vesktop</url>
|
||||||
<categories>
|
<categories>
|
||||||
<category>InstantMessaging</category>
|
<category>InstantMessaging</category>
|
||||||
<category>Network</category>
|
<category>AudioVideo</category>
|
||||||
</categories>
|
</categories>
|
||||||
<requires>
|
<requires>
|
||||||
<control>pointing</control>
|
<control>pointing</control>
|
||||||
|
|
113
package.json
113
package.json
|
@ -1,8 +1,8 @@
|
||||||
{
|
{
|
||||||
"name": "vesktop",
|
"name": "vesktop",
|
||||||
"version": "1.5.4",
|
"version": "1.5.2-alpha.1",
|
||||||
"private": true,
|
"private": true,
|
||||||
"description": "Vesktop is a custom Discord desktop app",
|
"description": "",
|
||||||
"keywords": [],
|
"keywords": [],
|
||||||
"homepage": "https://vencord.dev/",
|
"homepage": "https://vencord.dev/",
|
||||||
"license": "GPL-3.0",
|
"license": "GPL-3.0",
|
||||||
|
@ -13,7 +13,7 @@
|
||||||
"build:dev": "pnpm build --dev",
|
"build:dev": "pnpm build --dev",
|
||||||
"package": "pnpm build && electron-builder",
|
"package": "pnpm build && electron-builder",
|
||||||
"package:dir": "pnpm build && electron-builder --dir",
|
"package:dir": "pnpm build && electron-builder --dir",
|
||||||
"lint": "eslint",
|
"lint": "eslint . --ext .js,.jsx,.ts,.tsx,.mts,.mjs",
|
||||||
"lint:fix": "pnpm lint --fix",
|
"lint:fix": "pnpm lint --fix",
|
||||||
"start": "pnpm build && electron .",
|
"start": "pnpm build && electron .",
|
||||||
"start:dev": "pnpm build:dev && electron .",
|
"start:dev": "pnpm build:dev && electron .",
|
||||||
|
@ -21,43 +21,41 @@
|
||||||
"test": "pnpm lint && pnpm testTypes",
|
"test": "pnpm lint && pnpm testTypes",
|
||||||
"testTypes": "tsc --noEmit",
|
"testTypes": "tsc --noEmit",
|
||||||
"watch": "pnpm build --watch",
|
"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": {
|
"dependencies": {
|
||||||
"arrpc": "github:OpenAsar/arrpc#2234e9c9111f4c42ebcc3aa6a2215bfd979eef77",
|
"arrpc": "github:OpenAsar/arrpc#6960a8fd4d65d566da93dbdb8a7ca474aa0a3c9c"
|
||||||
"electron-updater": "^6.3.9"
|
|
||||||
},
|
},
|
||||||
"optionalDependencies": {
|
"optionalDependencies": {
|
||||||
"@vencord/venmic": "^6.1.0"
|
"@vencord/venmic": "^3.4.2"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@fal-works/esbuild-plugin-global-externals": "^2.1.2",
|
"@fal-works/esbuild-plugin-global-externals": "^2.1.2",
|
||||||
"@stylistic/eslint-plugin": "^2.13.0",
|
"@types/node": "^20.11.26",
|
||||||
"@types/node": "^22.10.7",
|
"@types/react": "^18.2.65",
|
||||||
"@types/react": "^18.3.12",
|
"@typescript-eslint/eslint-plugin": "^7.2.0",
|
||||||
"@vencord/types": "^1.8.4",
|
"@typescript-eslint/parser": "^7.2.0",
|
||||||
"dotenv": "^16.4.7",
|
"@vencord/types": "^0.1.2",
|
||||||
"electron": "^34.0.1",
|
"dotenv": "^16.4.5",
|
||||||
"electron-builder": "^25.1.8",
|
"electron": "^29.1.1",
|
||||||
"esbuild": "^0.24.2",
|
"electron-builder": "^24.13.3",
|
||||||
"eslint": "^9.18.0",
|
"esbuild": "^0.20.1",
|
||||||
|
"eslint": "^8.57.0",
|
||||||
|
"eslint-config-prettier": "^9.1.0",
|
||||||
"eslint-import-resolver-alias": "^1.1.2",
|
"eslint-import-resolver-alias": "^1.1.2",
|
||||||
"eslint-plugin-path-alias": "^2.1.0",
|
"eslint-plugin-license-header": "^0.6.0",
|
||||||
"eslint-plugin-prettier": "^5.2.3",
|
"eslint-plugin-path-alias": "^1.0.0",
|
||||||
"eslint-plugin-simple-header": "^1.2.1",
|
"eslint-plugin-prettier": "^5.1.3",
|
||||||
"eslint-plugin-simple-import-sort": "^12.1.1",
|
"eslint-plugin-simple-import-sort": "^12.0.0",
|
||||||
"eslint-plugin-unused-imports": "^4.1.4",
|
"eslint-plugin-unused-imports": "^3.1.0",
|
||||||
"prettier": "^3.4.2",
|
"prettier": "^3.2.5",
|
||||||
"source-map-support": "^0.5.21",
|
"source-map-support": "^0.5.21",
|
||||||
"tsx": "^4.19.2",
|
"tsx": "^4.7.1",
|
||||||
"type-fest": "^4.33.0",
|
"type-fest": "^4.12.0",
|
||||||
"typescript": "^5.7.3",
|
"typescript": "^5.4.2",
|
||||||
"typescript-eslint": "^8.21.0",
|
"xml-formatter": "^3.6.2"
|
||||||
"xml-formatter": "^3.6.3"
|
|
||||||
},
|
},
|
||||||
"packageManager": "pnpm@9.1.0",
|
"packageManager": "pnpm@8.11.0",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=18",
|
"node": ">=18",
|
||||||
"pnpm": ">=8"
|
"pnpm": ">=8"
|
||||||
|
@ -67,7 +65,6 @@
|
||||||
"productName": "Vesktop",
|
"productName": "Vesktop",
|
||||||
"files": [
|
"files": [
|
||||||
"!*",
|
"!*",
|
||||||
"!node_modules",
|
|
||||||
"dist/js",
|
"dist/js",
|
||||||
"static",
|
"static",
|
||||||
"package.json",
|
"package.json",
|
||||||
|
@ -121,40 +118,18 @@
|
||||||
{
|
{
|
||||||
"target": "default",
|
"target": "default",
|
||||||
"arch": [
|
"arch": [
|
||||||
"universal"
|
"x64",
|
||||||
|
"arm64"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"category": "public.app-category.social-networking",
|
"category": "Network",
|
||||||
"darkModeSupport": true,
|
|
||||||
"extendInfo": {
|
"extendInfo": {
|
||||||
"NSMicrophoneUsageDescription": "This app needs access to the microphone",
|
"NSMicrophoneUsageDescription": "This app needs access to the microphone",
|
||||||
"NSCameraUsageDescription": "This app needs access to the camera",
|
"NSCameraUsageDescription": "This app needs access to the camera",
|
||||||
"com.apple.security.device.audio-input": true,
|
"com.apple.security.device.audio-input": true,
|
||||||
"com.apple.security.device.camera": true
|
"com.apple.security.device.camera": true
|
||||||
},
|
|
||||||
"notarize": true
|
|
||||||
},
|
|
||||||
"dmg": {
|
|
||||||
"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": {
|
"nsis": {
|
||||||
"include": "build/installer.nsh",
|
"include": "build/installer.nsh",
|
||||||
|
@ -162,34 +137,12 @@
|
||||||
},
|
},
|
||||||
"win": {
|
"win": {
|
||||||
"target": [
|
"target": [
|
||||||
{
|
"nsis",
|
||||||
"target": "nsis",
|
"zip"
|
||||||
"arch": [
|
|
||||||
"x64",
|
|
||||||
"arm64"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"target": "zip",
|
|
||||||
"arch": [
|
|
||||||
"x64",
|
|
||||||
"arm64"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"publish": {
|
"publish": {
|
||||||
"provider": "github"
|
"provider": "github"
|
||||||
},
|
|
||||||
"rpm": {
|
|
||||||
"fpm": [
|
|
||||||
"--rpm-rpmbuild-define=_build_id_links none"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"pnpm": {
|
|
||||||
"patchedDependencies": {
|
|
||||||
"arrpc@3.5.0": "patches/arrpc@3.5.0.patch"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,14 +0,0 @@
|
||||||
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];
|
|
7845
pnpm-lock.yaml
generated
7845
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load diff
|
@ -63,6 +63,12 @@ await Promise.all([
|
||||||
outfile: "dist/js/preload.js",
|
outfile: "dist/js/preload.js",
|
||||||
footer: { js: "//# sourceURL=VCDPreload" }
|
footer: { js: "//# sourceURL=VCDPreload" }
|
||||||
}),
|
}),
|
||||||
|
createContext({
|
||||||
|
...NodeCommonOpts,
|
||||||
|
entryPoints: ["src/updater/preload.ts"],
|
||||||
|
outfile: "dist/js/updaterPreload.js",
|
||||||
|
footer: { js: "//# sourceURL=VCDUpdaterPreload" }
|
||||||
|
}),
|
||||||
createContext({
|
createContext({
|
||||||
...CommonOpts,
|
...CommonOpts,
|
||||||
globalName: "Vesktop",
|
globalName: "Vesktop",
|
||||||
|
|
|
@ -5,22 +5,11 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { app } from "electron";
|
import { app } from "electron";
|
||||||
import { existsSync, mkdirSync, readdirSync, renameSync, rmdirSync } from "fs";
|
import { existsSync, readdirSync, renameSync, rmdirSync } from "fs";
|
||||||
import { dirname, join } from "path";
|
import { join } from "path";
|
||||||
|
|
||||||
const vesktopDir = dirname(process.execPath);
|
|
||||||
|
|
||||||
export const PORTABLE =
|
|
||||||
process.platform === "win32" &&
|
|
||||||
!process.execPath.toLowerCase().endsWith("electron.exe") &&
|
|
||||||
!existsSync(join(vesktopDir, "Uninstall Vesktop.exe"));
|
|
||||||
|
|
||||||
const LEGACY_DATA_DIR = join(app.getPath("appData"), "VencordDesktop", "VencordDesktop");
|
const LEGACY_DATA_DIR = join(app.getPath("appData"), "VencordDesktop", "VencordDesktop");
|
||||||
export const DATA_DIR =
|
export const DATA_DIR = process.env.VENCORD_USER_DATA_DIR || join(app.getPath("userData"));
|
||||||
process.env.VENCORD_USER_DATA_DIR || (PORTABLE ? join(vesktopDir, "Data") : join(app.getPath("userData")));
|
|
||||||
|
|
||||||
mkdirSync(DATA_DIR, { recursive: true });
|
|
||||||
|
|
||||||
// TODO: remove eventually
|
// TODO: remove eventually
|
||||||
if (existsSync(LEGACY_DATA_DIR)) {
|
if (existsSync(LEGACY_DATA_DIR)) {
|
||||||
try {
|
try {
|
||||||
|
@ -37,8 +26,7 @@ if (existsSync(LEGACY_DATA_DIR)) {
|
||||||
console.error("Migration failed", e);
|
console.error("Migration failed", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const SESSION_DATA_DIR = join(DATA_DIR, "sessionData");
|
app.setPath("sessionData", join(DATA_DIR, "sessionData"));
|
||||||
app.setPath("sessionData", SESSION_DATA_DIR);
|
|
||||||
|
|
||||||
export const VENCORD_SETTINGS_DIR = join(DATA_DIR, "settings");
|
export const VENCORD_SETTINGS_DIR = join(DATA_DIR, "settings");
|
||||||
export const VENCORD_QUICKCSS_FILE = join(VENCORD_SETTINGS_DIR, "quickCss.css");
|
export const VENCORD_QUICKCSS_FILE = join(VENCORD_SETTINGS_DIR, "quickCss.css");
|
||||||
|
@ -48,8 +36,7 @@ export const VENCORD_THEMES_DIR = join(DATA_DIR, "themes");
|
||||||
// needs to be inline require because of circular dependency
|
// needs to be inline require because of circular dependency
|
||||||
// as otherwise "DATA_DIR" (which is used by ./settings) will be uninitialised
|
// as otherwise "DATA_DIR" (which is used by ./settings) will be uninitialised
|
||||||
export const VENCORD_FILES_DIR =
|
export const VENCORD_FILES_DIR =
|
||||||
(require("./settings") as typeof import("./settings")).State.store.vencordDir ||
|
(require("./settings") as typeof import("./settings")).Settings.store.vencordDir || join(DATA_DIR, "vencordDist");
|
||||||
join(SESSION_DATA_DIR, "vencordFiles");
|
|
||||||
|
|
||||||
export const USER_AGENT = `Vesktop/${app.getVersion()} (https://github.com/Vencord/Vesktop)`;
|
export const USER_AGENT = `Vesktop/${app.getVersion()} (https://github.com/Vencord/Vesktop)`;
|
||||||
|
|
||||||
|
@ -61,11 +48,11 @@ export const DEFAULT_HEIGHT = 720;
|
||||||
|
|
||||||
export const DISCORD_HOSTNAMES = ["discord.com", "canary.discord.com", "ptb.discord.com"];
|
export const DISCORD_HOSTNAMES = ["discord.com", "canary.discord.com", "ptb.discord.com"];
|
||||||
|
|
||||||
const VersionString = `AppleWebKit/537.36 (KHTML, like Gecko) Chrome/${process.versions.chrome.split(".")[0]}.0.0.0 Safari/537.36`;
|
|
||||||
const BrowserUserAgents = {
|
const BrowserUserAgents = {
|
||||||
darwin: `Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) ${VersionString}`,
|
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) ${VersionString}`,
|
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) ${VersionString}`
|
windows:
|
||||||
|
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36"
|
||||||
};
|
};
|
||||||
|
|
||||||
export const BrowserUserAgent = BrowserUserAgents[process.platform] || BrowserUserAgents.windows;
|
export const BrowserUserAgent = BrowserUserAgents[process.platform] || BrowserUserAgents.windows;
|
||||||
|
|
|
@ -18,11 +18,11 @@ import { Settings, State } from "./settings";
|
||||||
import { makeLinksOpenExternally } from "./utils/makeLinksOpenExternally";
|
import { makeLinksOpenExternally } from "./utils/makeLinksOpenExternally";
|
||||||
|
|
||||||
interface Data {
|
interface Data {
|
||||||
|
minimizeToTray: boolean;
|
||||||
discordBranch: "stable" | "canary" | "ptb";
|
discordBranch: "stable" | "canary" | "ptb";
|
||||||
minimizeToTray?: "on";
|
autoStart: boolean;
|
||||||
autoStart?: "on";
|
importSettings: boolean;
|
||||||
importSettings?: "on";
|
richPresence: boolean;
|
||||||
richPresence?: "on";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function createFirstLaunchTour() {
|
export function createFirstLaunchTour() {
|
||||||
|
@ -44,11 +44,10 @@ export function createFirstLaunchTour() {
|
||||||
if (!msg.startsWith("form:")) return;
|
if (!msg.startsWith("form:")) return;
|
||||||
const data = JSON.parse(msg.slice(5)) as Data;
|
const data = JSON.parse(msg.slice(5)) as Data;
|
||||||
|
|
||||||
console.log(data);
|
|
||||||
State.store.firstLaunch = false;
|
State.store.firstLaunch = false;
|
||||||
|
Settings.store.minimizeToTray = data.minimizeToTray;
|
||||||
Settings.store.discordBranch = data.discordBranch;
|
Settings.store.discordBranch = data.discordBranch;
|
||||||
Settings.store.minimizeToTray = !!data.minimizeToTray;
|
Settings.store.arRPC = data.richPresence;
|
||||||
Settings.store.arRPC = !!data.richPresence;
|
|
||||||
|
|
||||||
if (data.autoStart) autoStart.enable();
|
if (data.autoStart) autoStart.enable();
|
||||||
|
|
||||||
|
|
|
@ -7,7 +7,7 @@
|
||||||
import "./ipc";
|
import "./ipc";
|
||||||
|
|
||||||
import { app, BrowserWindow, nativeTheme } from "electron";
|
import { app, BrowserWindow, nativeTheme } from "electron";
|
||||||
import { autoUpdater } from "electron-updater";
|
import { checkUpdates } from "updater/main";
|
||||||
|
|
||||||
import { DATA_DIR } from "./constants";
|
import { DATA_DIR } from "./constants";
|
||||||
import { createFirstLaunchTour } from "./firstLaunch";
|
import { createFirstLaunchTour } from "./firstLaunch";
|
||||||
|
@ -19,8 +19,6 @@ import { isDeckGameMode } from "./utils/steamOS";
|
||||||
|
|
||||||
if (IS_DEV) {
|
if (IS_DEV) {
|
||||||
require("source-map-support").install();
|
require("source-map-support").install();
|
||||||
} else {
|
|
||||||
autoUpdater.checkForUpdatesAndNotify();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Make the Vencord files use our DATA_DIR
|
// Make the Vencord files use our DATA_DIR
|
||||||
|
@ -42,26 +40,18 @@ function init() {
|
||||||
app.commandLine.appendSwitch("disable-smooth-scrolling");
|
app.commandLine.appendSwitch("disable-smooth-scrolling");
|
||||||
}
|
}
|
||||||
|
|
||||||
// disable renderer backgrounding to prevent the app from unloading when in the background
|
|
||||||
// https://github.com/electron/electron/issues/2822
|
|
||||||
// https://github.com/GoogleChrome/chrome-launcher/blob/5a27dd574d47a75fec0fb50f7b774ebf8a9791ba/docs/chrome-flags-for-tools.md#task-throttling
|
|
||||||
app.commandLine.appendSwitch("disable-renderer-backgrounding");
|
|
||||||
app.commandLine.appendSwitch("disable-background-timer-throttling");
|
|
||||||
app.commandLine.appendSwitch("disable-backgrounding-occluded-windows");
|
|
||||||
if (process.platform === "win32") {
|
|
||||||
disabledFeatures.push("CalculateNativeWinOcclusion");
|
|
||||||
}
|
|
||||||
|
|
||||||
// work around chrome 66 disabling autoplay by default
|
// work around chrome 66 disabling autoplay by default
|
||||||
app.commandLine.appendSwitch("autoplay-policy", "no-user-gesture-required");
|
app.commandLine.appendSwitch("autoplay-policy", "no-user-gesture-required");
|
||||||
// WinRetrieveSuggestionsOnlyOnDemand: Work around electron 13 bug w/ async spellchecking on Windows.
|
// WinRetrieveSuggestionsOnlyOnDemand: Work around electron 13 bug w/ async spellchecking on Windows.
|
||||||
// HardwareMediaKeyHandling,MediaSessionService: Prevent Discord from registering as a media service.
|
// HardwareMediaKeyHandling,MediaSessionService: Prevent Discord from registering as a media service.
|
||||||
//
|
//
|
||||||
// WidgetLayering (Vencord Added): Fix DevTools context menus https://github.com/electron/electron/issues/38790
|
// WidgetLayering (Vencord Added): Fix DevTools context menus https://github.com/electron/electron/issues/38790
|
||||||
disabledFeatures.push("WinRetrieveSuggestionsOnlyOnDemand", "HardwareMediaKeyHandling", "MediaSessionService");
|
disabledFeatures.push(
|
||||||
|
"WinRetrieveSuggestionsOnlyOnDemand",
|
||||||
// Support TTS on Linux using speech-dispatcher
|
"HardwareMediaKeyHandling",
|
||||||
app.commandLine.appendSwitch("enable-speech-dispatcher");
|
"MediaSessionService",
|
||||||
|
"WidgetLayering"
|
||||||
|
);
|
||||||
|
|
||||||
app.commandLine.appendSwitch("enable-features", [...new Set(enabledFeatures)].filter(Boolean).join(","));
|
app.commandLine.appendSwitch("enable-features", [...new Set(enabledFeatures)].filter(Boolean).join(","));
|
||||||
app.commandLine.appendSwitch("disable-features", [...new Set(disabledFeatures)].filter(Boolean).join(","));
|
app.commandLine.appendSwitch("disable-features", [...new Set(disabledFeatures)].filter(Boolean).join(","));
|
||||||
|
@ -79,6 +69,7 @@ function init() {
|
||||||
});
|
});
|
||||||
|
|
||||||
app.whenReady().then(async () => {
|
app.whenReady().then(async () => {
|
||||||
|
checkUpdates();
|
||||||
if (process.platform === "win32") app.setAppUserModelId("dev.vencord.vesktop");
|
if (process.platform === "win32") app.setAppUserModelId("dev.vencord.vesktop");
|
||||||
|
|
||||||
registerScreenShareHandler();
|
registerScreenShareHandler();
|
||||||
|
|
|
@ -19,7 +19,7 @@ import { setBadgeCount } from "./appBadge";
|
||||||
import { autoStart } from "./autoStart";
|
import { autoStart } from "./autoStart";
|
||||||
import { VENCORD_FILES_DIR, VENCORD_QUICKCSS_FILE, VENCORD_THEMES_DIR } from "./constants";
|
import { VENCORD_FILES_DIR, VENCORD_QUICKCSS_FILE, VENCORD_THEMES_DIR } from "./constants";
|
||||||
import { mainWin } from "./mainWindow";
|
import { mainWin } from "./mainWindow";
|
||||||
import { Settings, State } from "./settings";
|
import { Settings } from "./settings";
|
||||||
import { handle, handleSync } from "./utils/ipcWrappers";
|
import { handle, handleSync } from "./utils/ipcWrappers";
|
||||||
import { PopoutWindows } from "./utils/popout";
|
import { PopoutWindows } from "./utils/popout";
|
||||||
import { isDeckGameMode, showGamePage } from "./utils/steamOS";
|
import { isDeckGameMode, showGamePage } from "./utils/steamOS";
|
||||||
|
@ -93,8 +93,12 @@ handle(IpcEvents.MAXIMIZE, e => {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
handleSync(IpcEvents.SPELLCHECK_GET_AVAILABLE_LANGUAGES, e => {
|
handle(IpcEvents.SPELLCHECK_SET_LANGUAGES, (_, languages: string[]) => {
|
||||||
e.returnValue = session.defaultSession.availableSpellCheckerLanguages;
|
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);
|
||||||
});
|
});
|
||||||
|
|
||||||
handle(IpcEvents.SPELLCHECK_REPLACE_MISSPELLING, (e, word: string) => {
|
handle(IpcEvents.SPELLCHECK_REPLACE_MISSPELLING, (e, word: string) => {
|
||||||
|
@ -105,14 +109,7 @@ handle(IpcEvents.SPELLCHECK_ADD_TO_DICTIONARY, (e, word: string) => {
|
||||||
e.sender.session.addWordToSpellCheckerDictionary(word);
|
e.sender.session.addWordToSpellCheckerDictionary(word);
|
||||||
});
|
});
|
||||||
|
|
||||||
handleSync(IpcEvents.GET_VENCORD_DIR, e => (e.returnValue = State.store.vencordDir));
|
handle(IpcEvents.SELECT_VENCORD_DIR, async () => {
|
||||||
|
|
||||||
handle(IpcEvents.SELECT_VENCORD_DIR, async (_e, value?: null) => {
|
|
||||||
if (value === null) {
|
|
||||||
delete State.store.vencordDir;
|
|
||||||
return "ok";
|
|
||||||
}
|
|
||||||
|
|
||||||
const res = await dialog.showOpenDialog(mainWin!, {
|
const res = await dialog.showOpenDialog(mainWin!, {
|
||||||
properties: ["openDirectory"]
|
properties: ["openDirectory"]
|
||||||
});
|
});
|
||||||
|
@ -121,9 +118,7 @@ handle(IpcEvents.SELECT_VENCORD_DIR, async (_e, value?: null) => {
|
||||||
const dir = res.filePaths[0];
|
const dir = res.filePaths[0];
|
||||||
if (!isValidVencordInstall(dir)) return "invalid";
|
if (!isValidVencordInstall(dir)) return "invalid";
|
||||||
|
|
||||||
State.store.vencordDir = dir;
|
return dir;
|
||||||
|
|
||||||
return "ok";
|
|
||||||
});
|
});
|
||||||
|
|
||||||
handle(IpcEvents.SET_BADGE_COUNT, (_, count: number) => setBadgeCount(count));
|
handle(IpcEvents.SET_BADGE_COUNT, (_, count: number) => setBadgeCount(count));
|
||||||
|
@ -135,17 +130,6 @@ 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() {
|
function readCss() {
|
||||||
return readFile(VENCORD_QUICKCSS_FILE, "utf-8").catch(() => "");
|
return readFile(VENCORD_QUICKCSS_FILE, "utf-8").catch(() => "");
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,8 +12,6 @@ import {
|
||||||
Menu,
|
Menu,
|
||||||
MenuItemConstructorOptions,
|
MenuItemConstructorOptions,
|
||||||
nativeTheme,
|
nativeTheme,
|
||||||
screen,
|
|
||||||
session,
|
|
||||||
Tray
|
Tray
|
||||||
} from "electron";
|
} from "electron";
|
||||||
import { rm } from "fs/promises";
|
import { rm } from "fs/promises";
|
||||||
|
@ -91,7 +89,7 @@ function initTray(win: BrowserWindow) {
|
||||||
click: createAboutWindow
|
click: createAboutWindow
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: "Repair Vencord",
|
label: "Update Vencord",
|
||||||
async click() {
|
async click() {
|
||||||
await downloadVencordFiles();
|
await downloadVencordFiles();
|
||||||
app.relaunch();
|
app.relaunch();
|
||||||
|
@ -108,14 +106,14 @@ function initTray(win: BrowserWindow) {
|
||||||
type: "separator"
|
type: "separator"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: "Restart",
|
label: "Relaunch",
|
||||||
click() {
|
click() {
|
||||||
app.relaunch();
|
app.relaunch();
|
||||||
app.quit();
|
app.quit();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: "Quit",
|
label: "Quit Vesktop",
|
||||||
click() {
|
click() {
|
||||||
isQuitting = true;
|
isQuitting = true;
|
||||||
app.quit();
|
app.quit();
|
||||||
|
@ -271,9 +269,7 @@ function getWindowBoundsOptions(): BrowserWindowConstructorOptions {
|
||||||
height: height ?? DEFAULT_HEIGHT
|
height: height ?? DEFAULT_HEIGHT
|
||||||
} as BrowserWindowConstructorOptions;
|
} as BrowserWindowConstructorOptions;
|
||||||
|
|
||||||
const storedDisplay = screen.getAllDisplays().find(display => display.id === State.store.displayid);
|
if (x != null && y != null) {
|
||||||
|
|
||||||
if (x != null && y != null && storedDisplay) {
|
|
||||||
options.x = x;
|
options.x = x;
|
||||||
options.y = y;
|
options.y = y;
|
||||||
}
|
}
|
||||||
|
@ -321,7 +317,6 @@ function initWindowBoundsListeners(win: BrowserWindow) {
|
||||||
|
|
||||||
const saveBounds = () => {
|
const saveBounds = () => {
|
||||||
State.store.windowBounds = win.getBounds();
|
State.store.windowBounds = win.getBounds();
|
||||||
State.store.displayid = screen.getDisplayMatching(State.store.windowBounds).id;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
win.on("resize", saveBounds);
|
win.on("resize", saveBounds);
|
||||||
|
@ -361,27 +356,12 @@ function initSettingsListeners(win: BrowserWindow) {
|
||||||
addSettingsListener("enableMenu", enabled => {
|
addSettingsListener("enableMenu", enabled => {
|
||||||
win.setAutoHideMenuBar(enabled ?? false);
|
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) {
|
function initSpellCheck(win: BrowserWindow) {
|
||||||
win.webContents.on("context-menu", (_, data) => {
|
win.webContents.on("context-menu", (_, data) => {
|
||||||
win.webContents.send(IpcEvents.SPELLCHECK_RESULT, data.misspelledWord, data.dictionarySuggestions);
|
win.webContents.send(IpcEvents.SPELLCHECK_RESULT, data.misspelledWord, data.dictionarySuggestions);
|
||||||
});
|
});
|
||||||
|
|
||||||
initSpellCheckLanguages(win, Settings.store.spellCheckLanguages);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function createMainWindow() {
|
function createMainWindow() {
|
||||||
|
@ -403,9 +383,7 @@ function createMainWindow() {
|
||||||
contextIsolation: true,
|
contextIsolation: true,
|
||||||
devTools: true,
|
devTools: true,
|
||||||
preload: join(__dirname, "preload.js"),
|
preload: join(__dirname, "preload.js"),
|
||||||
spellcheck: true,
|
spellcheck: true
|
||||||
// disable renderer backgrounding to prevent the app from unloading when in the background
|
|
||||||
backgroundThrottling: false
|
|
||||||
},
|
},
|
||||||
icon: ICON_PATH,
|
icon: ICON_PATH,
|
||||||
frame: !noFrame,
|
frame: !noFrame,
|
||||||
|
@ -430,7 +408,6 @@ function createMainWindow() {
|
||||||
autoHideMenuBar: enableMenu
|
autoHideMenuBar: enableMenu
|
||||||
}));
|
}));
|
||||||
win.setMenuBarVisibility(false);
|
win.setMenuBarVisibility(false);
|
||||||
if (process.platform === "darwin" && customTitleBar) win.setWindowButtonVisibility(false);
|
|
||||||
|
|
||||||
win.on("close", e => {
|
win.on("close", e => {
|
||||||
const useTray = !isDeckGameMode && Settings.store.minimizeToTray !== false && Settings.store.tray !== false;
|
const useTray = !isDeckGameMode && Settings.store.minimizeToTray !== false && Settings.store.tray !== false;
|
||||||
|
|
|
@ -12,14 +12,12 @@ export function registerMediaPermissionsHandler() {
|
||||||
session.defaultSession.setPermissionRequestHandler(async (_webContents, permission, callback, details) => {
|
session.defaultSession.setPermissionRequestHandler(async (_webContents, permission, callback, details) => {
|
||||||
let granted = true;
|
let granted = true;
|
||||||
|
|
||||||
if ("mediaTypes" in details) {
|
|
||||||
if (details.mediaTypes?.includes("audio")) {
|
if (details.mediaTypes?.includes("audio")) {
|
||||||
granted &&= await systemPreferences.askForMediaAccess("microphone");
|
granted = await systemPreferences.askForMediaAccess("microphone");
|
||||||
}
|
}
|
||||||
if (details.mediaTypes?.includes("video")) {
|
if (details.mediaTypes?.includes("video")) {
|
||||||
granted &&= await systemPreferences.askForMediaAccess("camera");
|
granted &&= await systemPreferences.askForMediaAccess("camera");
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
callback(granted);
|
callback(granted);
|
||||||
});
|
});
|
||||||
|
|
|
@ -49,8 +49,8 @@ export function registerScreenShareHandler() {
|
||||||
if (isWayland) {
|
if (isWayland) {
|
||||||
const video = data[0];
|
const video = data[0];
|
||||||
if (video) {
|
if (video) {
|
||||||
const stream = await request
|
const stream = await request.frame
|
||||||
.frame!.executeJavaScript(
|
.executeJavaScript(
|
||||||
`Vesktop.Components.ScreenShare.openScreenSharePicker(${JSON.stringify([video])},true)`
|
`Vesktop.Components.ScreenShare.openScreenSharePicker(${JSON.stringify([video])},true)`
|
||||||
)
|
)
|
||||||
.catch(() => null);
|
.catch(() => null);
|
||||||
|
@ -61,8 +61,8 @@ export function registerScreenShareHandler() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const choice = await request
|
const choice = await request.frame
|
||||||
.frame!.executeJavaScript(`Vesktop.Components.ScreenShare.openScreenSharePicker(${JSON.stringify(data)})`)
|
.executeJavaScript(`Vesktop.Components.ScreenShare.openScreenSharePicker(${JSON.stringify(data)})`)
|
||||||
.then(e => e as StreamPick)
|
.then(e => e as StreamPick)
|
||||||
.catch(e => {
|
.catch(e => {
|
||||||
console.error("Error during screenshare picker", e);
|
console.error("Error during screenshare picker", e);
|
||||||
|
|
|
@ -35,13 +35,25 @@ function loadSettings<T extends object = any>(file: string, name: string) {
|
||||||
}
|
}
|
||||||
|
|
||||||
export const Settings = loadSettings<TSettings>(SETTINGS_FILE, "Vesktop settings");
|
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");
|
export const VencordSettings = loadSettings<any>(VENCORD_SETTINGS_FILE, "Vencord settings");
|
||||||
|
|
||||||
if (Object.hasOwn(Settings.plain, "firstLaunch") && !existsSync(STATE_FILE)) {
|
if (Object.hasOwn(Settings.plain, "firstLaunch") && !existsSync(STATE_FILE)) {
|
||||||
console.warn("legacy state in settings.json detected. migrating to state.json");
|
console.warn("legacy state in settings.json detected. migrating to state.json");
|
||||||
const state = {} as TState;
|
const state = {} as TState;
|
||||||
for (const prop of ["firstLaunch", "maximized", "minimized", "steamOSLayoutVersion", "windowBounds"] as const) {
|
for (const prop of [
|
||||||
|
"firstLaunch",
|
||||||
|
"maximized",
|
||||||
|
"minimized",
|
||||||
|
"skippedUpdate",
|
||||||
|
"steamOSLayoutVersion",
|
||||||
|
"windowBounds"
|
||||||
|
] as const) {
|
||||||
state[prop] = Settings.plain[prop];
|
state[prop] = Settings.plain[prop];
|
||||||
delete Settings.plain[prop];
|
delete Settings.plain[prop];
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,9 +8,7 @@ import { ipcMain, IpcMainEvent, IpcMainInvokeEvent, WebFrameMain } from "electro
|
||||||
import { DISCORD_HOSTNAMES } from "main/constants";
|
import { DISCORD_HOSTNAMES } from "main/constants";
|
||||||
import { IpcEvents } from "shared/IpcEvents";
|
import { IpcEvents } from "shared/IpcEvents";
|
||||||
|
|
||||||
export function validateSender(frame: WebFrameMain | null) {
|
export function validateSender(frame: WebFrameMain) {
|
||||||
if (!frame) throw new Error("ipc: No sender frame");
|
|
||||||
|
|
||||||
const { hostname, protocol } = new URL(frame.url);
|
const { hostname, protocol } = new URL(frame.url);
|
||||||
if (protocol === "file:") return;
|
if (protocol === "file:") return;
|
||||||
|
|
||||||
|
|
|
@ -4,8 +4,7 @@
|
||||||
* Copyright (c) 2023 Vendicated and Vencord contributors
|
* Copyright (c) 2023 Vendicated and Vencord contributors
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { mkdirSync } from "fs";
|
import { existsSync, mkdirSync } from "fs";
|
||||||
import { access, constants as FsConstants } from "fs/promises";
|
|
||||||
import { join } from "path";
|
import { join } from "path";
|
||||||
|
|
||||||
import { USER_AGENT, VENCORD_FILES_DIR } from "../constants";
|
import { USER_AGENT, VENCORD_FILES_DIR } from "../constants";
|
||||||
|
@ -57,18 +56,12 @@ export async function downloadVencordFiles() {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const existsAsync = (path: string) =>
|
export function isValidVencordInstall(dir: string) {
|
||||||
access(path, FsConstants.F_OK)
|
return FILES_TO_DOWNLOAD.every(f => existsSync(join(dir, f)));
|
||||||
.then(() => true)
|
|
||||||
.catch(() => false);
|
|
||||||
|
|
||||||
export async function isValidVencordInstall(dir: string) {
|
|
||||||
return Promise.all(FILES_TO_DOWNLOAD.map(f => existsAsync(join(dir, f)))).then(arr => !arr.includes(false));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function ensureVencordFiles() {
|
export async function ensureVencordFiles() {
|
||||||
if (await isValidVencordInstall(VENCORD_FILES_DIR)) return;
|
if (isValidVencordInstall(VENCORD_FILES_DIR)) return;
|
||||||
|
|
||||||
mkdirSync(VENCORD_FILES_DIR, { recursive: true });
|
mkdirSync(VENCORD_FILES_DIR, { recursive: true });
|
||||||
|
|
||||||
await downloadVencordFiles();
|
await downloadVencordFiles();
|
||||||
|
|
|
@ -4,13 +4,13 @@
|
||||||
* Copyright (c) 2023 Vendicated and Vencord contributors
|
* Copyright (c) 2023 Vendicated and Vencord contributors
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import type { LinkData, Node, PatchBay as PatchBayType } from "@vencord/venmic";
|
import type { PatchBay as PatchBayType } from "@vencord/venmic";
|
||||||
import { app, ipcMain } from "electron";
|
import { app, ipcMain } from "electron";
|
||||||
import { join } from "path";
|
import { join } from "path";
|
||||||
import { IpcEvents } from "shared/IpcEvents";
|
import { IpcEvents } from "shared/IpcEvents";
|
||||||
import { STATIC_DIR } from "shared/paths";
|
import { STATIC_DIR } from "shared/paths";
|
||||||
|
|
||||||
import { Settings } from "./settings";
|
type LinkData = Parameters<PatchBayType["link"]>[0];
|
||||||
|
|
||||||
let PatchBay: typeof PatchBayType | undefined;
|
let PatchBay: typeof PatchBayType | undefined;
|
||||||
let patchBayInstance: PatchBayType | undefined;
|
let patchBayInstance: PatchBayType | undefined;
|
||||||
|
@ -69,64 +69,47 @@ function getRendererAudioServicePid() {
|
||||||
ipcMain.handle(IpcEvents.VIRT_MIC_LIST, () => {
|
ipcMain.handle(IpcEvents.VIRT_MIC_LIST, () => {
|
||||||
const audioPid = getRendererAudioServicePid();
|
const audioPid = getRendererAudioServicePid();
|
||||||
|
|
||||||
const { granularSelect } = Settings.store.audio ?? {};
|
const list = obtainVenmic()
|
||||||
|
?.list()
|
||||||
|
.filter(s => s["application.process.id"] !== audioPid)
|
||||||
|
.map(s => s["application.name"]);
|
||||||
|
|
||||||
const targets = obtainVenmic()
|
const uniqueTargets = [...new Set(list)];
|
||||||
?.list(granularSelect ? ["node.name"] : undefined)
|
|
||||||
.filter(s => s["application.process.id"] !== audioPid);
|
|
||||||
|
|
||||||
return targets ? { ok: true, targets, hasPipewirePulse } : { ok: false, isGlibCxxOutdated };
|
return list ? { ok: true, targets: uniqueTargets, hasPipewirePulse } : { ok: false, isGlibCxxOutdated };
|
||||||
});
|
});
|
||||||
|
|
||||||
ipcMain.handle(IpcEvents.VIRT_MIC_START, (_, include: Node[]) => {
|
ipcMain.handle(IpcEvents.VIRT_MIC_START, (_, targets: string[], workaround?: boolean) => {
|
||||||
const pid = getRendererAudioServicePid();
|
const pid = getRendererAudioServicePid();
|
||||||
const { ignoreDevices, ignoreInputMedia, ignoreVirtual, workaround } = Settings.store.audio ?? {};
|
|
||||||
|
|
||||||
const data: LinkData = {
|
const data: LinkData = {
|
||||||
include,
|
include: targets.map(target => ({ key: "application.name", value: target })),
|
||||||
exclude: [{ "application.process.id": pid }],
|
exclude: [{ key: "application.process.id", value: pid }]
|
||||||
ignore_devices: ignoreDevices
|
|
||||||
};
|
};
|
||||||
|
|
||||||
if (ignoreInputMedia ?? true) {
|
|
||||||
data.exclude.push({ "media.class": "Stream/Input/Audio" });
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ignoreVirtual) {
|
|
||||||
data.exclude.push({ "node.virtual": "true" });
|
|
||||||
}
|
|
||||||
|
|
||||||
if (workaround) {
|
if (workaround) {
|
||||||
data.workaround = [{ "application.process.id": pid, "media.name": "RecordStream" }];
|
data.workaround = [
|
||||||
|
{ key: "application.process.id", value: pid },
|
||||||
|
{ key: "media.name", value: "RecordStream" }
|
||||||
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
return obtainVenmic()?.link(data);
|
return obtainVenmic()?.link(data);
|
||||||
});
|
});
|
||||||
|
|
||||||
ipcMain.handle(IpcEvents.VIRT_MIC_START_SYSTEM, (_, exclude: Node[]) => {
|
ipcMain.handle(IpcEvents.VIRT_MIC_START_SYSTEM, (_, workaround?: boolean, onlyDefaultSpeakers?: boolean) => {
|
||||||
const pid = getRendererAudioServicePid();
|
const pid = getRendererAudioServicePid();
|
||||||
|
|
||||||
const { workaround, ignoreDevices, ignoreInputMedia, ignoreVirtual, onlySpeakers, onlyDefaultSpeakers } =
|
|
||||||
Settings.store.audio ?? {};
|
|
||||||
|
|
||||||
const data: LinkData = {
|
const data: LinkData = {
|
||||||
include: [],
|
exclude: [{ key: "application.process.id", value: pid }],
|
||||||
exclude: [{ "application.process.id": pid }, ...exclude],
|
|
||||||
only_speakers: onlySpeakers,
|
|
||||||
ignore_devices: ignoreDevices,
|
|
||||||
only_default_speakers: onlyDefaultSpeakers
|
only_default_speakers: onlyDefaultSpeakers
|
||||||
};
|
};
|
||||||
|
|
||||||
if (ignoreInputMedia ?? true) {
|
|
||||||
data.exclude.push({ "media.class": "Stream/Input/Audio" });
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ignoreVirtual) {
|
|
||||||
data.exclude.push({ "node.virtual": "true" });
|
|
||||||
}
|
|
||||||
|
|
||||||
if (workaround) {
|
if (workaround) {
|
||||||
data.workaround = [{ "application.process.id": pid, "media.name": "RecordStream" }];
|
data.workaround = [
|
||||||
|
{ key: "application.process.id", value: pid },
|
||||||
|
{ key: "media.name", value: "RecordStream" }
|
||||||
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
return obtainVenmic()?.link(data);
|
return obtainVenmic()?.link(data);
|
||||||
|
|
|
@ -4,9 +4,9 @@
|
||||||
* Copyright (c) 2023 Vendicated and Vencord contributors
|
* Copyright (c) 2023 Vendicated and Vencord contributors
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Node } from "@vencord/venmic";
|
|
||||||
import { ipcRenderer } from "electron";
|
import { ipcRenderer } from "electron";
|
||||||
import type { Settings } from "shared/settings";
|
import type { Settings } from "shared/settings";
|
||||||
|
import type { LiteralUnion } from "type-fest";
|
||||||
|
|
||||||
import { IpcEvents } from "../shared/IpcEvents";
|
import { IpcEvents } from "../shared/IpcEvents";
|
||||||
import { invoke, sendSync } from "./typedIpc";
|
import { invoke, sendSync } from "./typedIpc";
|
||||||
|
@ -33,15 +33,14 @@ export const VesktopNative = {
|
||||||
},
|
},
|
||||||
fileManager: {
|
fileManager: {
|
||||||
showItemInFolder: (path: string) => invoke<void>(IpcEvents.SHOW_ITEM_IN_FOLDER, path),
|
showItemInFolder: (path: string) => invoke<void>(IpcEvents.SHOW_ITEM_IN_FOLDER, path),
|
||||||
getVencordDir: () => sendSync<string | undefined>(IpcEvents.GET_VENCORD_DIR),
|
selectVencordDir: () => invoke<LiteralUnion<"cancelled" | "invalid", string>>(IpcEvents.SELECT_VENCORD_DIR)
|
||||||
selectVencordDir: (value?: null) => invoke<"cancelled" | "invalid" | "ok">(IpcEvents.SELECT_VENCORD_DIR, value)
|
|
||||||
},
|
},
|
||||||
settings: {
|
settings: {
|
||||||
get: () => sendSync<Settings>(IpcEvents.GET_SETTINGS),
|
get: () => sendSync<Settings>(IpcEvents.GET_SETTINGS),
|
||||||
set: (settings: Settings, path?: string) => invoke<void>(IpcEvents.SET_SETTINGS, settings, path)
|
set: (settings: Settings, path?: string) => invoke<void>(IpcEvents.SET_SETTINGS, settings, path)
|
||||||
},
|
},
|
||||||
spellcheck: {
|
spellcheck: {
|
||||||
getAvailableLanguages: () => sendSync<string[]>(IpcEvents.SPELLCHECK_GET_AVAILABLE_LANGUAGES),
|
setLanguages: (languages: readonly string[]) => invoke<void>(IpcEvents.SPELLCHECK_SET_LANGUAGES, languages),
|
||||||
onSpellcheckResult(cb: SpellCheckerResultCallback) {
|
onSpellcheckResult(cb: SpellCheckerResultCallback) {
|
||||||
spellCheckCallbacks.add(cb);
|
spellCheckCallbacks.add(cb);
|
||||||
},
|
},
|
||||||
|
@ -64,10 +63,11 @@ export const VesktopNative = {
|
||||||
virtmic: {
|
virtmic: {
|
||||||
list: () =>
|
list: () =>
|
||||||
invoke<
|
invoke<
|
||||||
{ ok: false; isGlibCxxOutdated: boolean } | { ok: true; targets: Node[]; hasPipewirePulse: boolean }
|
{ ok: false; isGlibCxxOutdated: boolean } | { ok: true; targets: string[]; hasPipewirePulse: boolean }
|
||||||
>(IpcEvents.VIRT_MIC_LIST),
|
>(IpcEvents.VIRT_MIC_LIST),
|
||||||
start: (include: Node[]) => invoke<void>(IpcEvents.VIRT_MIC_START, include),
|
start: (targets: string[], workaround?: boolean) => invoke<void>(IpcEvents.VIRT_MIC_START, targets, workaround),
|
||||||
startSystem: (exclude: Node[]) => invoke<void>(IpcEvents.VIRT_MIC_START_SYSTEM, exclude),
|
startSystem: (workaround?: boolean, onlyDefaultSpeakers?: boolean) =>
|
||||||
|
invoke<void>(IpcEvents.VIRT_MIC_START_SYSTEM, workaround, onlyDefaultSpeakers),
|
||||||
stop: () => invoke<void>(IpcEvents.VIRT_MIC_STOP)
|
stop: () => invoke<void>(IpcEvents.VIRT_MIC_STOP)
|
||||||
},
|
},
|
||||||
arrpc: {
|
arrpc: {
|
||||||
|
@ -78,9 +78,5 @@ export const VesktopNative = {
|
||||||
clipboard: {
|
clipboard: {
|
||||||
copyImage: (imageBuffer: Uint8Array, imageSrc: string) =>
|
copyImage: (imageBuffer: Uint8Array, imageSrc: string) =>
|
||||||
invoke<void>(IpcEvents.CLIPBOARD_COPY_IMAGE, imageBuffer, imageSrc)
|
invoke<void>(IpcEvents.CLIPBOARD_COPY_IMAGE, imageBuffer, imageSrc)
|
||||||
},
|
|
||||||
debug: {
|
|
||||||
launchGpu: () => invoke<void>(IpcEvents.DEBUG_LAUNCH_GPU),
|
|
||||||
launchWebrtcInternals: () => invoke<void>(IpcEvents.DEBUG_LAUNCH_WEBRTC_INTERNALS)
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -40,3 +40,5 @@ if (IS_DEV) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
// #endregion
|
// #endregion
|
||||||
|
|
||||||
|
VesktopNative.spellcheck.setLanguages(window.navigator.languages);
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
|
|
||||||
import "./screenSharePicker.css";
|
import "./screenSharePicker.css";
|
||||||
|
|
||||||
import { closeModal, Logger, Modals, ModalSize, openModal, useAwaiter } from "@vencord/types/utils";
|
import { closeModal, Logger, Margins, Modals, ModalSize, openModal, useAwaiter } from "@vencord/types/utils";
|
||||||
import { findStoreLazy, onceReady } from "@vencord/types/webpack";
|
import { findStoreLazy, onceReady } from "@vencord/types/webpack";
|
||||||
import {
|
import {
|
||||||
Button,
|
Button,
|
||||||
|
@ -19,39 +19,26 @@ import {
|
||||||
UserStore,
|
UserStore,
|
||||||
useState
|
useState
|
||||||
} from "@vencord/types/webpack/common";
|
} from "@vencord/types/webpack/common";
|
||||||
import { Node } from "@vencord/venmic";
|
|
||||||
import type { Dispatch, SetStateAction } from "react";
|
import type { Dispatch, SetStateAction } from "react";
|
||||||
import { addPatch } from "renderer/patches/shared";
|
import { addPatch } from "renderer/patches/shared";
|
||||||
import { useSettings } from "renderer/settings";
|
import { isLinux, isWindows } from "renderer/utils";
|
||||||
import { classNameFactory, isLinux, isWindows } from "renderer/utils";
|
|
||||||
|
|
||||||
const StreamResolutions = ["480", "720", "1080", "1440", "2160"] as const;
|
const StreamResolutions = ["480", "720", "1080", "1440"] as const;
|
||||||
const StreamFps = ["15", "30", "60"] as const;
|
const StreamFps = ["15", "30", "60"] as const;
|
||||||
|
|
||||||
const cl = classNameFactory("vcd-screen-picker-");
|
|
||||||
|
|
||||||
const MediaEngineStore = findStoreLazy("MediaEngineStore");
|
const MediaEngineStore = findStoreLazy("MediaEngineStore");
|
||||||
|
|
||||||
export type StreamResolution = (typeof StreamResolutions)[number];
|
export type StreamResolution = (typeof StreamResolutions)[number];
|
||||||
export type StreamFps = (typeof StreamFps)[number];
|
export type StreamFps = (typeof StreamFps)[number];
|
||||||
|
|
||||||
type SpecialSource = "None" | "Entire System";
|
|
||||||
|
|
||||||
type AudioSource = SpecialSource | Node;
|
|
||||||
type AudioSources = SpecialSource | Node[];
|
|
||||||
|
|
||||||
interface AudioItem {
|
|
||||||
name: string;
|
|
||||||
value: AudioSource;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface StreamSettings {
|
interface StreamSettings {
|
||||||
resolution: StreamResolution;
|
resolution: StreamResolution;
|
||||||
fps: StreamFps;
|
fps: StreamFps;
|
||||||
audio: boolean;
|
audio: boolean;
|
||||||
|
audioSource?: string;
|
||||||
contentHint?: string;
|
contentHint?: string;
|
||||||
includeSources?: AudioSources;
|
workaround?: boolean;
|
||||||
excludeSources?: AudioSources;
|
onlyDefaultSpeakers?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface StreamPick extends StreamSettings {
|
export interface StreamPick extends StreamSettings {
|
||||||
|
@ -76,6 +63,20 @@ addPatch({
|
||||||
match: /this.localWant=/,
|
match: /this.localWant=/,
|
||||||
replace: "$self.patchStreamQuality(this);$&"
|
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) {
|
patchStreamQuality(opts: any) {
|
||||||
|
@ -131,17 +132,13 @@ export function openScreenSharePicker(screens: Source[], skipPicker: boolean) {
|
||||||
modalProps={props}
|
modalProps={props}
|
||||||
submit={async v => {
|
submit={async v => {
|
||||||
didSubmit = true;
|
didSubmit = true;
|
||||||
|
if (v.audioSource && v.audioSource !== "None") {
|
||||||
if (v.includeSources && v.includeSources !== "None") {
|
if (v.audioSource === "Entire System") {
|
||||||
if (v.includeSources === "Entire System") {
|
await VesktopNative.virtmic.startSystem(v.workaround);
|
||||||
await VesktopNative.virtmic.startSystem(
|
|
||||||
!v.excludeSources || isSpecialSource(v.excludeSources) ? [] : v.excludeSources
|
|
||||||
);
|
|
||||||
} else {
|
} else {
|
||||||
await VesktopNative.virtmic.start(v.includeSources);
|
await VesktopNative.virtmic.start([v.audioSource], v.workaround);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
resolve(v);
|
resolve(v);
|
||||||
}}
|
}}
|
||||||
close={() => {
|
close={() => {
|
||||||
|
@ -163,181 +160,13 @@ export function openScreenSharePicker(screens: Source[], skipPicker: boolean) {
|
||||||
|
|
||||||
function ScreenPicker({ screens, chooseScreen }: { screens: Source[]; chooseScreen: (id: string) => void }) {
|
function ScreenPicker({ screens, chooseScreen }: { screens: Source[]; chooseScreen: (id: string) => void }) {
|
||||||
return (
|
return (
|
||||||
<div className={cl("screen-grid")}>
|
<div className="vcd-screen-picker-grid">
|
||||||
{screens.map(({ id, name, url }) => (
|
{screens.map(({ id, name, url }) => (
|
||||||
<label key={id} className={cl("screen-label")}>
|
<label key={id}>
|
||||||
<input
|
<input type="radio" name="screen" value={id} onChange={() => chooseScreen(id)} />
|
||||||
type="radio"
|
|
||||||
className={cl("screen-radio")}
|
|
||||||
name="screen"
|
|
||||||
value={id}
|
|
||||||
onChange={() => chooseScreen(id)}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<img src={url} alt="" />
|
<img src={url} alt="" />
|
||||||
<Text className={cl("screen-name")} variant="text-sm/normal">
|
<Text variant="text-sm/normal">{name}</Text>
|
||||||
{name}
|
|
||||||
</Text>
|
|
||||||
</label>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
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={cl("header")}>
|
|
||||||
<Forms.FormTitle tag="h2" className={cl("header-title")}>
|
|
||||||
Venmic Settings
|
|
||||||
</Forms.FormTitle>
|
|
||||||
<Modals.ModalCloseButton onClick={close} />
|
|
||||||
</Modals.ModalHeader>
|
|
||||||
<Modals.ModalContent className={cl("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={cl("footer")}>
|
|
||||||
<Button color={Button.Colors.TRANSPARENT} onClick={close}>
|
|
||||||
Back
|
|
||||||
</Button>
|
|
||||||
</Modals.ModalFooter>
|
|
||||||
</Modals.ModalRoot>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function OptionRadio(props: {
|
|
||||||
options: Array<string> | ReadonlyArray<string>;
|
|
||||||
labels?: Array<string>;
|
|
||||||
settingsKey: string;
|
|
||||||
settings: StreamSettings;
|
|
||||||
setSettings: Dispatch<SetStateAction<StreamSettings>>;
|
|
||||||
}) {
|
|
||||||
const { options, setSettings, settings, settingsKey, labels } = 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={() => setSettings(s => ({ ...s, [settingsKey]: option }))}
|
|
||||||
/>
|
|
||||||
</label>
|
</label>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
@ -355,8 +184,6 @@ function StreamSettings({
|
||||||
setSettings: Dispatch<SetStateAction<StreamSettings>>;
|
setSettings: Dispatch<SetStateAction<StreamSettings>>;
|
||||||
skipPicker: boolean;
|
skipPicker: boolean;
|
||||||
}) {
|
}) {
|
||||||
const Settings = useSettings();
|
|
||||||
|
|
||||||
const [thumb] = useAwaiter(
|
const [thumb] = useAwaiter(
|
||||||
() => (skipPicker ? Promise.resolve(source.url) : VesktopNative.capturer.getLargeThumbnail(source.id)),
|
() => (skipPicker ? Promise.resolve(source.url) : VesktopNative.capturer.getLargeThumbnail(source.id)),
|
||||||
{
|
{
|
||||||
|
@ -365,236 +192,164 @@ function StreamSettings({
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
const openSettings = () => {
|
|
||||||
const key = openModal(props => (
|
|
||||||
<AudioSettingsModal
|
|
||||||
modalProps={props}
|
|
||||||
close={() => props.onClose()}
|
|
||||||
setAudioSources={sources =>
|
|
||||||
setSettings(s => ({ ...s, includeSources: sources, excludeSources: sources }))
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
));
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
<div className="vcd-screen-picker-settings-grid">
|
||||||
<div>
|
<div>
|
||||||
<Forms.FormTitle>What you're streaming</Forms.FormTitle>
|
<Forms.FormTitle>What you're streaming</Forms.FormTitle>
|
||||||
<Card className={cl("card", "preview")}>
|
<Card className="vcd-screen-picker-card vcd-screen-picker-preview">
|
||||||
<img src={thumb} alt="" className={cl(isLinux ? "preview-img-linux" : "preview-img")} />
|
<img src={thumb} alt="" />
|
||||||
<Text variant="text-sm/normal">{source.name}</Text>
|
<Text variant="text-sm/normal">{source.name}</Text>
|
||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
<Forms.FormTitle>Stream Settings</Forms.FormTitle>
|
<Forms.FormTitle>Stream Settings</Forms.FormTitle>
|
||||||
|
|
||||||
<Card className={cl("card")}>
|
<Card className="vcd-screen-picker-card">
|
||||||
<div className={cl("quality")}>
|
<div className="vcd-screen-picker-quality">
|
||||||
<section className={cl("quality-section")}>
|
<section>
|
||||||
<Forms.FormTitle>Resolution</Forms.FormTitle>
|
<Forms.FormTitle>Resolution</Forms.FormTitle>
|
||||||
<OptionRadio
|
<div className="vcd-screen-picker-radios">
|
||||||
options={StreamResolutions}
|
{StreamResolutions.map(res => (
|
||||||
settingsKey="resolution"
|
<label
|
||||||
settings={settings}
|
className="vcd-screen-picker-radio"
|
||||||
setSettings={setSettings}
|
data-checked={settings.resolution === res}
|
||||||
|
>
|
||||||
|
<Text variant="text-sm/bold">{res}</Text>
|
||||||
|
<input
|
||||||
|
type="radio"
|
||||||
|
name="resolution"
|
||||||
|
value={res}
|
||||||
|
checked={settings.resolution === res}
|
||||||
|
onChange={() => setSettings(s => ({ ...s, resolution: res }))}
|
||||||
/>
|
/>
|
||||||
|
</label>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section className={cl("quality-section")}>
|
<section>
|
||||||
<Forms.FormTitle>Frame Rate</Forms.FormTitle>
|
<Forms.FormTitle>Frame Rate</Forms.FormTitle>
|
||||||
<OptionRadio
|
<div className="vcd-screen-picker-radios">
|
||||||
options={StreamFps}
|
{StreamFps.map(fps => (
|
||||||
settingsKey="fps"
|
<label className="vcd-screen-picker-radio" data-checked={settings.fps === fps}>
|
||||||
settings={settings}
|
<Text variant="text-sm/bold">{fps}</Text>
|
||||||
setSettings={setSettings}
|
<input
|
||||||
|
type="radio"
|
||||||
|
name="fps"
|
||||||
|
value={fps}
|
||||||
|
checked={settings.fps === fps}
|
||||||
|
onChange={() => setSettings(s => ({ ...s, fps }))}
|
||||||
/>
|
/>
|
||||||
|
</label>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
</section>
|
</section>
|
||||||
</div>
|
</div>
|
||||||
<div className={cl("quality")}>
|
<div className="vcd-screen-picker-quality">
|
||||||
<section className={cl("quality-section")}>
|
<section>
|
||||||
<Forms.FormTitle>Content Type</Forms.FormTitle>
|
<Forms.FormTitle>Content Type</Forms.FormTitle>
|
||||||
<div>
|
<div>
|
||||||
<OptionRadio
|
<div className="vcd-screen-picker-radios">
|
||||||
options={["motion", "detail"]}
|
<label
|
||||||
labels={["Prefer Smoothness", "Prefer Clarity"]}
|
className="vcd-screen-picker-radio"
|
||||||
settingsKey="contentHint"
|
data-checked={settings.contentHint === "motion"}
|
||||||
settings={settings}
|
>
|
||||||
setSettings={setSettings}
|
<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" }))}
|
||||||
/>
|
/>
|
||||||
<div className={cl("hint-description")}>
|
</label>
|
||||||
|
<label
|
||||||
|
className="vcd-screen-picker-radio"
|
||||||
|
data-checked={settings.contentHint === "detail"}
|
||||||
|
>
|
||||||
|
<Text variant="text-sm/bold">Prefer Clarity</Text>
|
||||||
|
<input
|
||||||
|
type="radio"
|
||||||
|
name="contenthint"
|
||||||
|
value="detail"
|
||||||
|
checked={settings.contentHint === "detail"}
|
||||||
|
onChange={() => setSettings(s => ({ ...s, contentHint: "detail" }))}
|
||||||
|
/>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div className="vcd-screen-picker-hint-description">
|
||||||
<p>
|
<p>
|
||||||
Choosing "Prefer Clarity" will result in a significantly lower framerate in exchange
|
Choosing "Prefer Clarity" will result in a significantly lower framerate in
|
||||||
for a much sharper and clearer image.
|
exchange for a much sharper and clearer image.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
</Card>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
{isWindows && (
|
{isWindows && (
|
||||||
<Switch
|
<Switch
|
||||||
value={settings.audio}
|
value={settings.audio}
|
||||||
onChange={checked => setSettings(s => ({ ...s, audio: checked }))}
|
onChange={checked => setSettings(s => ({ ...s, audio: checked }))}
|
||||||
hideBorder
|
hideBorder
|
||||||
className={cl("audio")}
|
className="vcd-screen-picker-audio"
|
||||||
>
|
>
|
||||||
Stream With Audio
|
Stream With Audio
|
||||||
</Switch>
|
</Switch>
|
||||||
)}
|
)}
|
||||||
</section>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{isLinux && (
|
{isLinux && (
|
||||||
<AudioSourcePickerLinux
|
<AudioSourcePickerLinux
|
||||||
openSettings={openSettings}
|
audioSource={settings.audioSource}
|
||||||
includeSources={settings.includeSources}
|
workaround={settings.workaround}
|
||||||
excludeSources={settings.excludeSources}
|
onlyDefaultSpeakers={settings.onlyDefaultSpeakers}
|
||||||
deviceSelect={Settings.audio?.deviceSelect}
|
setAudioSource={source => setSettings(s => ({ ...s, audioSource: source }))}
|
||||||
granularSelect={Settings.audio?.granularSelect}
|
setWorkaround={value => setSettings(s => ({ ...s, workaround: value }))}
|
||||||
setIncludeSources={sources => setSettings(s => ({ ...s, includeSources: sources }))}
|
setOnlyDefaultSpeakers={value => setSettings(s => ({ ...s, onlyDefaultSpeakers: value }))}
|
||||||
setExcludeSources={sources => setSettings(s => ({ ...s, excludeSources: sources }))}
|
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</Card>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function isSpecialSource(value?: AudioSource | AudioSources): value is SpecialSource {
|
|
||||||
return typeof value === "string";
|
|
||||||
}
|
|
||||||
|
|
||||||
function hasMatchingProps(value: Node, other: Node) {
|
|
||||||
return Object.keys(value).every(key => value[key] === other[key]);
|
|
||||||
}
|
|
||||||
|
|
||||||
function mapToAudioItem(node: AudioSource, granularSelect?: boolean, deviceSelect?: boolean): AudioItem[] {
|
|
||||||
if (isSpecialSource(node)) {
|
|
||||||
return [{ name: node, value: node }];
|
|
||||||
}
|
|
||||||
|
|
||||||
const rtn: AudioItem[] = [];
|
|
||||||
|
|
||||||
const mediaClass = node["media.class"];
|
|
||||||
|
|
||||||
if (mediaClass?.includes("Video") || mediaClass?.includes("Midi")) {
|
|
||||||
return rtn;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!deviceSelect && node["device.id"]) {
|
|
||||||
return rtn;
|
|
||||||
}
|
|
||||||
|
|
||||||
const name = node["application.name"];
|
|
||||||
|
|
||||||
if (name) {
|
|
||||||
rtn.push({ name: name, value: { "application.name": name } });
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!granularSelect) {
|
|
||||||
return rtn;
|
|
||||||
}
|
|
||||||
|
|
||||||
const rawName = node["node.name"];
|
|
||||||
|
|
||||||
if (!name) {
|
|
||||||
rtn.push({ name: rawName, value: { "node.name": rawName } });
|
|
||||||
}
|
|
||||||
|
|
||||||
const binary = node["application.process.binary"];
|
|
||||||
|
|
||||||
if (!name && binary) {
|
|
||||||
rtn.push({ name: binary, value: { "application.process.binary": binary } });
|
|
||||||
}
|
|
||||||
|
|
||||||
const pid = node["application.process.id"];
|
|
||||||
|
|
||||||
const first = rtn[0];
|
|
||||||
const firstValues = first.value as Node;
|
|
||||||
|
|
||||||
if (pid) {
|
|
||||||
rtn.push({
|
|
||||||
name: `${first.name} (${pid})`,
|
|
||||||
value: { ...firstValues, "application.process.id": pid }
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const mediaName = node["media.name"];
|
|
||||||
|
|
||||||
if (mediaName) {
|
|
||||||
rtn.push({
|
|
||||||
name: `${first.name} [${mediaName}]`,
|
|
||||||
value: { ...firstValues, "media.name": mediaName }
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (mediaClass) {
|
|
||||||
rtn.push({
|
|
||||||
name: `${first.name} [${mediaClass}]`,
|
|
||||||
value: { ...firstValues, "media.class": mediaClass }
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return rtn;
|
|
||||||
}
|
|
||||||
|
|
||||||
function isItemSelected(sources?: AudioSources) {
|
|
||||||
return (value: AudioSource) => {
|
|
||||||
if (!sources) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isSpecialSource(sources) || isSpecialSource(value)) {
|
|
||||||
return sources === value;
|
|
||||||
}
|
|
||||||
|
|
||||||
return sources.some(source => hasMatchingProps(source, value));
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function updateItems(setSources: (s: AudioSources) => void, sources?: AudioSources) {
|
|
||||||
return (value: AudioSource) => {
|
|
||||||
if (isSpecialSource(value)) {
|
|
||||||
setSources(value);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isSpecialSource(sources)) {
|
|
||||||
setSources([value]);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isItemSelected(sources)(value)) {
|
|
||||||
setSources(sources?.filter(x => !hasMatchingProps(x, value)) ?? "None");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
setSources([...(sources || []), value]);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function AudioSourcePickerLinux({
|
function AudioSourcePickerLinux({
|
||||||
includeSources,
|
audioSource,
|
||||||
excludeSources,
|
workaround,
|
||||||
deviceSelect,
|
onlyDefaultSpeakers,
|
||||||
granularSelect,
|
setAudioSource,
|
||||||
openSettings,
|
setWorkaround,
|
||||||
setIncludeSources,
|
setOnlyDefaultSpeakers
|
||||||
setExcludeSources
|
|
||||||
}: {
|
}: {
|
||||||
includeSources?: AudioSources;
|
audioSource?: string;
|
||||||
excludeSources?: AudioSources;
|
workaround?: boolean;
|
||||||
deviceSelect?: boolean;
|
onlyDefaultSpeakers?: boolean;
|
||||||
granularSelect?: boolean;
|
setAudioSource(s: string): void;
|
||||||
openSettings: () => void;
|
setWorkaround(b: boolean): void;
|
||||||
setIncludeSources: (s: AudioSources) => void;
|
setOnlyDefaultSpeakers(b: boolean): void;
|
||||||
setExcludeSources: (s: AudioSources) => void;
|
|
||||||
}) {
|
}) {
|
||||||
const [sources, _, loading] = useAwaiter(() => VesktopNative.virtmic.list(), {
|
const [sources, _, loading] = useAwaiter(() => VesktopNative.virtmic.list(), {
|
||||||
fallbackValue: { ok: true, targets: [], hasPipewirePulse: true }
|
fallbackValue: { ok: true, targets: [], hasPipewirePulse: true }
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const allSources = sources.ok ? ["None", "Entire System", ...sources.targets] : null;
|
||||||
const hasPipewirePulse = sources.ok ? sources.hasPipewirePulse : true;
|
const hasPipewirePulse = sources.ok ? sources.hasPipewirePulse : true;
|
||||||
|
|
||||||
const [ignorePulseWarning, setIgnorePulseWarning] = useState(false);
|
const [ignorePulseWarning, setIgnorePulseWarning] = useState(false);
|
||||||
|
|
||||||
if (!sources.ok && sources.isGlibCxxOutdated) {
|
|
||||||
return (
|
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>
|
<Forms.FormText>
|
||||||
Failed to retrieve Audio Sources because your C++ library is too old to run
|
Failed to retrieve Audio Sources because your C++ library is too old to run
|
||||||
<a href="https://github.com/Vencord/venmic" target="_blank">
|
<a href="https://github.com/Vencord/venmic" target="_blank">
|
||||||
|
@ -606,77 +361,58 @@ function AudioSourcePickerLinux({
|
||||||
</a>{" "}
|
</a>{" "}
|
||||||
for possible solutions.
|
for possible solutions.
|
||||||
</Forms.FormText>
|
</Forms.FormText>
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!hasPipewirePulse && !ignorePulseWarning) {
|
|
||||||
return (
|
|
||||||
<Text variant="text-sm/normal">
|
|
||||||
Could not find pipewire-pulse. See{" "}
|
|
||||||
<a href="https://gist.github.com/the-spyke/2de98b22ff4f978ebf0650c90e82027e#install" target="_blank">
|
|
||||||
this guide
|
|
||||||
</a>{" "}
|
|
||||||
on how to switch to pipewire. <br />
|
|
||||||
You can still continue, however, please{" "}
|
|
||||||
<b>beware that you can only share audio of apps that are running under pipewire</b>.{" "}
|
|
||||||
<a onClick={() => setIgnorePulseWarning(true)}>I know what I'm doing!</a>
|
|
||||||
</Text>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const specialSources: SpecialSource[] = ["None", "Entire System"] as const;
|
|
||||||
|
|
||||||
const uniqueName = (value: AudioItem, index: number, list: AudioItem[]) =>
|
|
||||||
list.findIndex(x => x.name === value.name) === index;
|
|
||||||
|
|
||||||
const allSources = sources.ok
|
|
||||||
? [...specialSources, ...sources.targets]
|
|
||||||
.map(target => mapToAudioItem(target, granularSelect, deviceSelect))
|
|
||||||
.flat()
|
|
||||||
.filter(uniqueName)
|
|
||||||
: [];
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<div className={cl({ quality: includeSources === "Entire System" })}>
|
|
||||||
<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>
|
|
||||||
)}
|
)}
|
||||||
</div>
|
|
||||||
<Button color={Button.Colors.TRANSPARENT} onClick={openSettings} className={cl("settings-button")}>
|
{hasPipewirePulse || ignorePulseWarning ? (
|
||||||
Open Audio Settings
|
allSources && (
|
||||||
</Button>
|
<Select
|
||||||
|
options={allSources.map(s => ({ label: s, value: s, default: s === "None" }))}
|
||||||
|
isSelected={s => s === audioSource}
|
||||||
|
select={setAudioSource}
|
||||||
|
serialize={String}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
) : (
|
||||||
|
<Text variant="text-sm/normal">
|
||||||
|
Could not find pipewire-pulse. This usually means that you do not run pipewire as your main
|
||||||
|
audio-server. <br />
|
||||||
|
You can still continue, however, please beware that you can only share audio of apps that are
|
||||||
|
running under pipewire.
|
||||||
|
<br />
|
||||||
|
<a onClick={() => setIgnorePulseWarning(true)}>I know what I'm doing</a>
|
||||||
|
</Text>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<Forms.FormDivider className={Margins.top16 + " " + Margins.bottom16} />
|
||||||
|
|
||||||
|
<Switch
|
||||||
|
onChange={setWorkaround}
|
||||||
|
value={workaround ?? false}
|
||||||
|
note={
|
||||||
|
<>
|
||||||
|
Work around an issue that causes the microphone to be shared instead of the correct audio.
|
||||||
|
Only enable if you're experiencing this issue.
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
Microphone Workaround
|
||||||
|
</Switch>
|
||||||
|
|
||||||
|
<Switch
|
||||||
|
hideBorder
|
||||||
|
onChange={setOnlyDefaultSpeakers}
|
||||||
|
disabled={audioSource !== "Entire System"}
|
||||||
|
value={onlyDefaultSpeakers ?? true}
|
||||||
|
note={
|
||||||
|
<>
|
||||||
|
When sharing entire desktop audio, only share apps that play to the default speakers and
|
||||||
|
ignore apps that play to other speakers or devices.
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
Only Default Speakers
|
||||||
|
</Switch>
|
||||||
|
</Card>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -696,20 +432,19 @@ function ModalComponent({
|
||||||
}) {
|
}) {
|
||||||
const [selected, setSelected] = useState<string | undefined>(skipPicker ? screens[0].id : void 0);
|
const [selected, setSelected] = useState<string | undefined>(skipPicker ? screens[0].id : void 0);
|
||||||
const [settings, setSettings] = useState<StreamSettings>({
|
const [settings, setSettings] = useState<StreamSettings>({
|
||||||
resolution: "720",
|
resolution: "1080",
|
||||||
fps: "30",
|
fps: "60",
|
||||||
contentHint: "motion",
|
contentHint: "motion",
|
||||||
audio: true,
|
audio: true
|
||||||
includeSources: "None"
|
|
||||||
});
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Modals.ModalRoot {...modalProps} size={ModalSize.MEDIUM}>
|
<Modals.ModalRoot {...modalProps} size={ModalSize.MEDIUM}>
|
||||||
<Modals.ModalHeader className={cl("header")}>
|
<Modals.ModalHeader className="vcd-screen-picker-header">
|
||||||
<Forms.FormTitle tag="h2">ScreenShare</Forms.FormTitle>
|
<Forms.FormTitle tag="h2">ScreenShare</Forms.FormTitle>
|
||||||
<Modals.ModalCloseButton onClick={close} />
|
<Modals.ModalCloseButton onClick={close} />
|
||||||
</Modals.ModalHeader>
|
</Modals.ModalHeader>
|
||||||
<Modals.ModalContent className={cl("modal")}>
|
<Modals.ModalContent className="vcd-screen-picker-modal">
|
||||||
{!selected ? (
|
{!selected ? (
|
||||||
<ScreenPicker screens={screens} chooseScreen={setSelected} />
|
<ScreenPicker screens={screens} chooseScreen={setSelected} />
|
||||||
) : (
|
) : (
|
||||||
|
@ -721,7 +456,7 @@ function ModalComponent({
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</Modals.ModalContent>
|
</Modals.ModalContent>
|
||||||
<Modals.ModalFooter className={cl("footer")}>
|
<Modals.ModalFooter className="vcd-screen-picker-footer">
|
||||||
<Button
|
<Button
|
||||||
disabled={!selected}
|
disabled={!selected}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
|
@ -756,7 +491,7 @@ function ModalComponent({
|
||||||
|
|
||||||
const constraints = {
|
const constraints = {
|
||||||
...track.getConstraints(),
|
...track.getConstraints(),
|
||||||
frameRate: { min: frameRate, ideal: frameRate },
|
frameRate,
|
||||||
width: { min: 640, ideal: width, max: width },
|
width: { min: 640, ideal: width, max: width },
|
||||||
height: { min: 480, ideal: height, max: height },
|
height: { min: 480, ideal: height, max: height },
|
||||||
advanced: [{ width: width, height: height }],
|
advanced: [{ width: width, height: height }],
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
padding: 1em;
|
padding: 1em;
|
||||||
}
|
}
|
||||||
|
|
||||||
.vcd-screen-picker-header-title {
|
.vcd-screen-picker-header h1 {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -11,36 +11,49 @@
|
||||||
gap: 1em;
|
gap: 1em;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.vcd-screen-picker-settings-grid {
|
||||||
|
gap: 1em;
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1fr 1fr;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vcd-screen-picker-settings-grid > div {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
.vcd-screen-picker-card {
|
.vcd-screen-picker-card {
|
||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.vcd-screen-picker-grid {
|
||||||
/* Screen Grid */
|
|
||||||
.vcd-screen-picker-screen-grid {
|
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: 1fr 1fr;
|
grid-template-columns: 1fr 1fr;
|
||||||
gap: 2em 1em;
|
gap: 2em 1em;
|
||||||
}
|
}
|
||||||
|
|
||||||
.vcd-screen-picker-screen-radio {
|
.vcd-screen-picker-grid input {
|
||||||
appearance: none;
|
appearance: none;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
.vcd-screen-picker-screen-label {
|
.vcd-screen-picker-selected img {
|
||||||
|
border: 2px solid var(--brand-experiment);
|
||||||
|
border-radius: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vcd-screen-picker-grid label {
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
padding: 8px;
|
padding: 4px 0px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
display: grid;
|
|
||||||
justify-items: center;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.vcd-screen-picker-screen-label:hover {
|
.vcd-screen-picker-grid label:hover {
|
||||||
outline: 2px solid var(--brand-500);
|
outline: 2px solid var(--brand-experiment);
|
||||||
}
|
}
|
||||||
|
|
||||||
.vcd-screen-picker-screen-name {
|
|
||||||
|
.vcd-screen-picker-grid div {
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
@ -54,13 +67,8 @@
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
}
|
}
|
||||||
|
|
||||||
.vcd-screen-picker-preview-img-linux {
|
.vcd-screen-picker-preview img {
|
||||||
width: 60%;
|
width: 100%;
|
||||||
margin-bottom: 0.5em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.vcd-screen-picker-preview-img {
|
|
||||||
width: 90%;
|
|
||||||
margin-bottom: 0.5em;
|
margin-bottom: 0.5em;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -72,56 +80,59 @@
|
||||||
margin-bottom: 1em;
|
margin-bottom: 1em;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.vcd-screen-picker-radio input {
|
||||||
/* Option Radios */
|
display: none;
|
||||||
|
|
||||||
.vcd-screen-picker-option-radios {
|
|
||||||
display: flex;
|
|
||||||
width: 100%;
|
|
||||||
border-radius: 3px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.vcd-screen-picker-option-radio {
|
.vcd-screen-picker-radio {
|
||||||
flex: 1 1 auto;
|
|
||||||
text-align: center;
|
|
||||||
background-color: var(--background-secondary);
|
background-color: var(--background-secondary);
|
||||||
border: 1px solid var(--primary-800);
|
border: 1px solid var(--primary-800);
|
||||||
padding: 0.3em;
|
padding: 0.3em;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
.vcd-screen-picker-option-radio:first-child {
|
.vcd-screen-picker-radio h2 {
|
||||||
border-radius: 3px 0 0 3px;
|
margin: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.vcd-screen-picker-option-radio:last-child {
|
.vcd-screen-picker-radio[data-checked="true"] {
|
||||||
border-radius: 0 3px 3px 0;
|
background-color: var(--brand-experiment);
|
||||||
|
border-color: var(--brand-experiment);
|
||||||
}
|
}
|
||||||
|
|
||||||
.vcd-screen-picker-option-input {
|
.vcd-screen-picker-radio[data-checked="true"] h2 {
|
||||||
display: none;
|
color: var(--interactive-active);
|
||||||
}
|
|
||||||
|
|
||||||
.vcd-screen-picker-option-radio[data-checked="true"] {
|
|
||||||
background-color: var(--brand-500);
|
|
||||||
border-color: var(--brand-500);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.vcd-screen-picker-quality {
|
.vcd-screen-picker-quality {
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: 1em;
|
gap: 1em;
|
||||||
|
|
||||||
margin-bottom: 0.5em;
|
margin-bottom: 0.5em;
|
||||||
}
|
}
|
||||||
|
|
||||||
.vcd-screen-picker-quality-section {
|
.vcd-screen-picker-quality section {
|
||||||
flex: 1 1 auto;
|
flex: 1 1 auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
.vcd-screen-picker-settings-button {
|
.vcd-screen-picker-radios {
|
||||||
margin-left: auto;
|
display: flex;
|
||||||
margin-top: 0.3rem;
|
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 {
|
.vcd-screen-picker-audio {
|
||||||
margin-bottom: 0;
|
margin-bottom: 0;
|
||||||
|
|
|
@ -1,120 +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 {
|
|
||||||
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 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();
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<Forms.FormText>
|
|
||||||
Vencord files are loaded from{" "}
|
|
||||||
{vencordDir ? (
|
|
||||||
<a
|
|
||||||
href="about:blank"
|
|
||||||
onClick={e => {
|
|
||||||
e.preventDefault();
|
|
||||||
VesktopNative.fileManager.showItemInFolder(vencordDir!);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{vencordDir}
|
|
||||||
</a>
|
|
||||||
) : (
|
|
||||||
"the default location"
|
|
||||||
)}
|
|
||||||
</Forms.FormText>
|
|
||||||
<div className="vcd-settings-button-grid">
|
|
||||||
<Button
|
|
||||||
size={Button.Sizes.SMALL}
|
|
||||||
onClick={async () => {
|
|
||||||
const choice = await VesktopNative.fileManager.selectVencordDir();
|
|
||||||
switch (choice) {
|
|
||||||
case "cancelled":
|
|
||||||
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:
|
|
||||||
"You did not choose a valid Vencord install. Make sure you're selecting the dist dir!",
|
|
||||||
id: Toasts.genId(),
|
|
||||||
type: Toasts.Type.FAILURE
|
|
||||||
});
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
forceUpdate();
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Change
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
size={Button.Sizes.SMALL}
|
|
||||||
color={Button.Colors.RED}
|
|
||||||
onClick={async () => {
|
|
||||||
await VesktopNative.fileManager.selectVencordDir(null);
|
|
||||||
forceUpdate();
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Reset
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
|
|
@ -12,9 +12,9 @@ import { Settings, useSettings } from "renderer/settings";
|
||||||
import { isMac, isWindows } from "renderer/utils";
|
import { isMac, isWindows } from "renderer/utils";
|
||||||
|
|
||||||
import { AutoStartToggle } from "./AutoStartToggle";
|
import { AutoStartToggle } from "./AutoStartToggle";
|
||||||
import { DeveloperOptionsButton } from "./DeveloperOptions";
|
|
||||||
import { DiscordBranchPicker } from "./DiscordBranchPicker";
|
import { DiscordBranchPicker } from "./DiscordBranchPicker";
|
||||||
import { NotificationBadgeToggle } from "./NotificationBadgeToggle";
|
import { NotificationBadgeToggle } from "./NotificationBadgeToggle";
|
||||||
|
import { VencordLocationPicker } from "./VencordLocationPicker";
|
||||||
import { WindowsTransparencyControls } from "./WindowsTransparencyControls";
|
import { WindowsTransparencyControls } from "./WindowsTransparencyControls";
|
||||||
|
|
||||||
interface BooleanSetting {
|
interface BooleanSetting {
|
||||||
|
@ -102,8 +102,16 @@ const SettingsOptions: Record<string, Array<BooleanSetting | SettingsComponent>>
|
||||||
defaultValue: false
|
defaultValue: false
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
Notifications: [NotificationBadgeToggle],
|
"Notifications & Updates": [
|
||||||
Miscellaneous: [
|
NotificationBadgeToggle,
|
||||||
|
{
|
||||||
|
key: "checkUpdates",
|
||||||
|
title: "Check for updates",
|
||||||
|
description: "Automatically check for Vesktop updates",
|
||||||
|
defaultValue: true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
Miscelleanous: [
|
||||||
{
|
{
|
||||||
key: "arRPC",
|
key: "arRPC",
|
||||||
title: "Rich Presence",
|
title: "Rich Presence",
|
||||||
|
@ -118,7 +126,7 @@ const SettingsOptions: Record<string, Array<BooleanSetting | SettingsComponent>>
|
||||||
defaultValue: false
|
defaultValue: false
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"Developer Options": [DeveloperOptionsButton]
|
"Vencord Location": [VencordLocationPicker]
|
||||||
};
|
};
|
||||||
|
|
||||||
function SettingsSections() {
|
function SettingsSections() {
|
||||||
|
|
62
src/renderer/components/settings/VencordLocationPicker.tsx
Normal file
62
src/renderer/components/settings/VencordLocationPicker.tsx
Normal file
|
@ -0,0 +1,62 @@
|
||||||
|
/*
|
||||||
|
* 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 { Button, Forms, Toasts } from "@vencord/types/webpack/common";
|
||||||
|
|
||||||
|
import { SettingsComponent } from "./Settings";
|
||||||
|
|
||||||
|
export const VencordLocationPicker: SettingsComponent = ({ settings }) => {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Forms.FormText>
|
||||||
|
Vencord files are loaded from{" "}
|
||||||
|
{settings.vencordDir ? (
|
||||||
|
<a
|
||||||
|
href="about:blank"
|
||||||
|
onClick={e => {
|
||||||
|
e.preventDefault();
|
||||||
|
VesktopNative.fileManager.showItemInFolder(settings.vencordDir!);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{settings.vencordDir}
|
||||||
|
</a>
|
||||||
|
) : (
|
||||||
|
"the default location"
|
||||||
|
)}
|
||||||
|
</Forms.FormText>
|
||||||
|
<div className="vcd-location-btns">
|
||||||
|
<Button
|
||||||
|
size={Button.Sizes.SMALL}
|
||||||
|
onClick={async () => {
|
||||||
|
const choice = await VesktopNative.fileManager.selectVencordDir();
|
||||||
|
switch (choice) {
|
||||||
|
case "cancelled":
|
||||||
|
return;
|
||||||
|
case "invalid":
|
||||||
|
Toasts.show({
|
||||||
|
message:
|
||||||
|
"You did not choose a valid Vencord install. Make sure you're selecting the dist dir!",
|
||||||
|
id: Toasts.genId(),
|
||||||
|
type: Toasts.Type.FAILURE
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
settings.vencordDir = choice;
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Change
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
size={Button.Sizes.SMALL}
|
||||||
|
color={Button.Colors.RED}
|
||||||
|
onClick={() => (settings.vencordDir = void 0)}
|
||||||
|
>
|
||||||
|
Reset
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
|
@ -1,4 +1,4 @@
|
||||||
.vcd-settings-button-grid {
|
.vcd-location-btns {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: 1fr 1fr;
|
grid-template-columns: 1fr 1fr;
|
||||||
gap: 0.5em;
|
gap: 0.5em;
|
||||||
|
|
|
@ -1,4 +1,11 @@
|
||||||
/* Workaround for making things in the draggable area clickable again on macOS */
|
/* Download Desktop button in guilds list */
|
||||||
.platform-osx [class*=topic_], .platform-osx [class*=notice_] button {
|
[class^=listItem_]:has([data-list-item-id=guildsnav___app-download-button]),
|
||||||
-webkit-app-region: no-drag;
|
[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;
|
||||||
}
|
}
|
|
@ -12,8 +12,8 @@ import "./themedSplash";
|
||||||
console.log("read if cute :3");
|
console.log("read if cute :3");
|
||||||
|
|
||||||
export * as Components from "./components";
|
export * as Components from "./components";
|
||||||
import { findByPropsLazy, onceReady } from "@vencord/types/webpack";
|
import { findByPropsLazy } from "@vencord/types/webpack";
|
||||||
import { Alerts, FluxDispatcher } from "@vencord/types/webpack/common";
|
import { FluxDispatcher } from "@vencord/types/webpack/common";
|
||||||
|
|
||||||
import SettingsUi from "./components/settings/Settings";
|
import SettingsUi from "./components/settings/Settings";
|
||||||
import { Settings } from "./settings";
|
import { Settings } from "./settings";
|
||||||
|
@ -52,26 +52,8 @@ const arRPC = Vencord.Plugins.plugins["WebRichPresence (arRPC)"] as any as {
|
||||||
handleEvent(e: MessageEvent): void;
|
handleEvent(e: MessageEvent): void;
|
||||||
};
|
};
|
||||||
|
|
||||||
VesktopNative.arrpc.onActivity(async data => {
|
VesktopNative.arrpc.onActivity(data => {
|
||||||
if (!Settings.store.arRPC) return;
|
if (!Settings.store.arRPC) return;
|
||||||
|
|
||||||
await onceReady;
|
|
||||||
|
|
||||||
arRPC.handleEvent(new MessageEvent("message", { data }));
|
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
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
|
@ -13,7 +13,7 @@ addPatch({
|
||||||
replacement: {
|
replacement: {
|
||||||
// FIXME: fix eslint rule
|
// FIXME: fix eslint rule
|
||||||
// eslint-disable-next-line no-useless-escape
|
// eslint-disable-next-line no-useless-escape
|
||||||
match: /\.isPlatformEmbedded(?=\?\i\.\i\.ALL)/g,
|
match: /\.isPlatformEmbedded(?=\?\i\.DesktopNotificationTypes\.ALL)/g,
|
||||||
replace: "$&||true"
|
replace: "$&||true"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,7 +12,7 @@ addPatch({
|
||||||
find: "lastOutputSystemDevice.justChanged",
|
find: "lastOutputSystemDevice.justChanged",
|
||||||
replacement: {
|
replacement: {
|
||||||
// eslint-disable-next-line no-useless-escape
|
// eslint-disable-next-line no-useless-escape
|
||||||
match: /(\i)\.\i\.getState\(\).neverShowModal/,
|
match: /(\i)\.default\.getState\(\).neverShowModal/,
|
||||||
replace: "$& || $self.shouldIgnore($1)"
|
replace: "$& || $self.shouldIgnore($1)"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -36,7 +36,7 @@ if (isLinux) {
|
||||||
|
|
||||||
const constraints = {
|
const constraints = {
|
||||||
...track.getConstraints(),
|
...track.getConstraints(),
|
||||||
frameRate: { min: frameRate, ideal: frameRate },
|
frameRate,
|
||||||
width: { min: 640, ideal: width, max: width },
|
width: { min: 640, ideal: width, max: width },
|
||||||
height: { min: 480, ideal: height, max: height },
|
height: { min: 480, ideal: height, max: height },
|
||||||
advanced: [{ width: width, height: height }],
|
advanced: [{ width: width, height: height }],
|
||||||
|
|
|
@ -6,8 +6,7 @@
|
||||||
|
|
||||||
import { addContextMenuPatch } from "@vencord/types/api/ContextMenu";
|
import { addContextMenuPatch } from "@vencord/types/api/ContextMenu";
|
||||||
import { findStoreLazy } from "@vencord/types/webpack";
|
import { findStoreLazy } from "@vencord/types/webpack";
|
||||||
import { FluxDispatcher, Menu, useMemo, useStateFromStores } from "@vencord/types/webpack/common";
|
import { FluxDispatcher, Menu, useStateFromStores } from "@vencord/types/webpack/common";
|
||||||
import { useSettings } from "renderer/settings";
|
|
||||||
|
|
||||||
import { addPatch } from "./shared";
|
import { addPatch } from "./shared";
|
||||||
|
|
||||||
|
@ -51,16 +50,7 @@ addContextMenuPatch("textarea-context", children => {
|
||||||
const spellCheckEnabled = useStateFromStores([SpellCheckStore], () => SpellCheckStore.isEnabled());
|
const spellCheckEnabled = useStateFromStores([SpellCheckStore], () => SpellCheckStore.isEnabled());
|
||||||
const hasCorrections = Boolean(word && corrections?.length);
|
const hasCorrections = Boolean(word && corrections?.length);
|
||||||
|
|
||||||
const availableLanguages = useMemo(VesktopNative.spellcheck.getAvailableLanguages, []);
|
children.push(
|
||||||
|
|
||||||
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>
|
<Menu.MenuGroup>
|
||||||
{hasCorrections && (
|
{hasCorrections && (
|
||||||
<>
|
<>
|
||||||
|
@ -79,8 +69,6 @@ addContextMenuPatch("textarea-context", children => {
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<Menu.MenuItem id="vcd-spellcheck-settings" label="Spellcheck Settings">
|
|
||||||
<Menu.MenuCheckboxItem
|
<Menu.MenuCheckboxItem
|
||||||
id="vcd-spellcheck-enabled"
|
id="vcd-spellcheck-enabled"
|
||||||
label="Enable Spellcheck"
|
label="Enable Spellcheck"
|
||||||
|
@ -89,29 +77,6 @@ addContextMenuPatch("textarea-context", children => {
|
||||||
FluxDispatcher.dispatch({ type: "SPELLCHECK_TOGGLE" });
|
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>
|
</Menu.MenuGroup>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
|
@ -18,26 +18,3 @@ const { platform } = navigator;
|
||||||
export const isWindows = platform.startsWith("Win");
|
export const isWindows = platform.startsWith("Win");
|
||||||
export const isMac = platform.startsWith("Mac");
|
export const isMac = platform.startsWith("Mac");
|
||||||
export const isLinux = platform.startsWith("Linux");
|
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(" ");
|
|
||||||
};
|
|
||||||
|
|
|
@ -23,14 +23,13 @@ export const enum IpcEvents {
|
||||||
GET_SETTINGS = "VCD_GET_SETTINGS",
|
GET_SETTINGS = "VCD_GET_SETTINGS",
|
||||||
SET_SETTINGS = "VCD_SET_SETTINGS",
|
SET_SETTINGS = "VCD_SET_SETTINGS",
|
||||||
|
|
||||||
GET_VENCORD_DIR = "VCD_GET_VENCORD_DIR",
|
|
||||||
SELECT_VENCORD_DIR = "VCD_SELECT_VENCORD_DIR",
|
SELECT_VENCORD_DIR = "VCD_SELECT_VENCORD_DIR",
|
||||||
|
|
||||||
UPDATER_GET_DATA = "VCD_UPDATER_GET_DATA",
|
UPDATER_GET_DATA = "VCD_UPDATER_GET_DATA",
|
||||||
UPDATER_DOWNLOAD = "VCD_UPDATER_DOWNLOAD",
|
UPDATER_DOWNLOAD = "VCD_UPDATER_DOWNLOAD",
|
||||||
UPDATE_IGNORE = "VCD_UPDATE_IGNORE",
|
UPDATE_IGNORE = "VCD_UPDATE_IGNORE",
|
||||||
|
|
||||||
SPELLCHECK_GET_AVAILABLE_LANGUAGES = "VCD_SPELLCHECK_GET_AVAILABLE_LANGUAGES",
|
SPELLCHECK_SET_LANGUAGES = "VCD_SPELLCHECK_SET_LANGUAGES",
|
||||||
SPELLCHECK_RESULT = "VCD_SPELLCHECK_RESULT",
|
SPELLCHECK_RESULT = "VCD_SPELLCHECK_RESULT",
|
||||||
SPELLCHECK_REPLACE_MISSPELLING = "VCD_SPELLCHECK_REPLACE_MISSPELLING",
|
SPELLCHECK_REPLACE_MISSPELLING = "VCD_SPELLCHECK_REPLACE_MISSPELLING",
|
||||||
SPELLCHECK_ADD_TO_DICTIONARY = "VCD_SPELLCHECK_ADD_TO_DICTIONARY",
|
SPELLCHECK_ADD_TO_DICTIONARY = "VCD_SPELLCHECK_ADD_TO_DICTIONARY",
|
||||||
|
@ -50,8 +49,5 @@ export const enum IpcEvents {
|
||||||
|
|
||||||
ARRPC_ACTIVITY = "VCD_ARRPC_ACTIVITY",
|
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"
|
|
||||||
}
|
}
|
||||||
|
|
25
src/shared/settings.d.ts
vendored
25
src/shared/settings.d.ts
vendored
|
@ -8,6 +8,7 @@ import type { Rectangle } from "electron";
|
||||||
|
|
||||||
export interface Settings {
|
export interface Settings {
|
||||||
discordBranch?: "stable" | "canary" | "ptb";
|
discordBranch?: "stable" | "canary" | "ptb";
|
||||||
|
vencordDir?: string;
|
||||||
transparencyOption?: "none" | "mica" | "tabbed" | "acrylic";
|
transparencyOption?: "none" | "mica" | "tabbed" | "acrylic";
|
||||||
tray?: boolean;
|
tray?: boolean;
|
||||||
minimizeToTray?: boolean;
|
minimizeToTray?: boolean;
|
||||||
|
@ -20,38 +21,24 @@ export interface Settings {
|
||||||
appBadge?: boolean;
|
appBadge?: boolean;
|
||||||
disableMinSize?: boolean;
|
disableMinSize?: boolean;
|
||||||
clickTrayToShowHide?: boolean;
|
clickTrayToShowHide?: boolean;
|
||||||
|
/** @deprecated use customTitleBar */
|
||||||
|
discordWindowsTitleBar?: boolean;
|
||||||
customTitleBar?: boolean;
|
customTitleBar?: boolean;
|
||||||
|
|
||||||
|
checkUpdates?: boolean;
|
||||||
|
|
||||||
splashTheming?: boolean;
|
splashTheming?: boolean;
|
||||||
splashColor?: string;
|
splashColor?: string;
|
||||||
splashBackground?: 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 {
|
export interface State {
|
||||||
maximized?: boolean;
|
maximized?: boolean;
|
||||||
minimized?: boolean;
|
minimized?: boolean;
|
||||||
windowBounds?: Rectangle;
|
windowBounds?: Rectangle;
|
||||||
displayid: int;
|
|
||||||
|
|
||||||
|
skippedUpdate?: string;
|
||||||
firstLaunch?: boolean;
|
firstLaunch?: boolean;
|
||||||
|
|
||||||
steamOSLayoutVersion?: number;
|
steamOSLayoutVersion?: number;
|
||||||
|
|
||||||
vencordDir?: string;
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,11 +26,11 @@ export class SettingsStore<T extends object> {
|
||||||
/**
|
/**
|
||||||
* The store object. Making changes to this object will trigger the applicable change listeners
|
* The store object. Making changes to this object will trigger the applicable change listeners
|
||||||
*/
|
*/
|
||||||
declare public store: T;
|
public declare store: T;
|
||||||
/**
|
/**
|
||||||
* The plain data. Changes to this object will not trigger any change listeners
|
* The plain data. Changes to this object will not trigger any change listeners
|
||||||
*/
|
*/
|
||||||
declare public plain: T;
|
public declare plain: T;
|
||||||
|
|
||||||
public constructor(plain: T) {
|
public constructor(plain: T) {
|
||||||
this.plain = plain;
|
this.plain = plain;
|
||||||
|
@ -59,19 +59,6 @@ export class SettingsStore<T extends object> {
|
||||||
self.pathListeners.get(setPath)?.forEach(cb => cb(value));
|
self.pathListeners.get(setPath)?.forEach(cb => cb(value));
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
},
|
|
||||||
deleteProperty(target, key: string) {
|
|
||||||
if (!(key in target)) return true;
|
|
||||||
|
|
||||||
const res = Reflect.deleteProperty(target, key);
|
|
||||||
if (!res) return false;
|
|
||||||
|
|
||||||
const setPath = `${path}${path && "."}${key}`;
|
|
||||||
|
|
||||||
self.globalListeners.forEach(cb => cb(root, setPath));
|
|
||||||
self.pathListeners.get(setPath)?.forEach(cb => cb(undefined));
|
|
||||||
|
|
||||||
return res;
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
119
src/updater/main.ts
Normal file
119
src/updater/main.ts
Normal file
|
@ -0,0 +1,119 @@
|
||||||
|
/*
|
||||||
|
* 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"));
|
||||||
|
}
|
21
src/updater/preload.ts
Normal file
21
src/updater/preload.ts
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
/*
|
||||||
|
* 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)
|
||||||
|
});
|
|
@ -3,7 +3,6 @@
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
body {
|
body {
|
||||||
background: none;
|
|
||||||
user-select: none;
|
user-select: none;
|
||||||
-webkit-app-region: drag;
|
-webkit-app-region: drag;
|
||||||
}
|
}
|
||||||
|
@ -17,7 +16,6 @@
|
||||||
align-items: center;
|
align-items: center;
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
border: 1px solid var(--fg-semi-trans);
|
border: 1px solid var(--fg-semi-trans);
|
||||||
background: var(--bg);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
p {
|
p {
|
||||||
|
|
Loading…
Add table
Reference in a new issue