Compare commits
280 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
02907d3248 | ||
|
|
c82cc7a963 | ||
|
|
6a43e135d0 | ||
|
|
d232797889 | ||
|
|
fa23c630cb | ||
|
|
9f0af48355 | ||
|
|
eb3dae897d | ||
|
|
d005dd5ebd | ||
|
|
6aeacaaf21 | ||
|
|
40d9cba2f0 | ||
|
|
5734a1d33c | ||
|
|
8cc34e217c | ||
|
|
a55b1f0250 | ||
|
|
e79635f15e | ||
|
|
6e2da1d294 | ||
|
|
0d9ca2270c | ||
|
|
497c251d72 | ||
|
|
b221882c5b | ||
|
|
0ee194698d | ||
|
|
27293d4ae9 | ||
|
|
432e54ace5 | ||
|
|
4a2d12f273 | ||
|
|
3498e39cbb | ||
|
|
3278b16923 | ||
|
|
fde447bc1d | ||
|
|
4baf6de472 | ||
|
|
8aa91b4f01 | ||
|
|
099845deb7 | ||
|
|
3982e122a7 | ||
|
|
8d3c9390ae | ||
|
|
236fc806de | ||
|
|
2dcebeca79 | ||
|
|
e6589eacfc | ||
|
|
29d1c73d81 | ||
|
|
26906b9776 | ||
|
|
36c67aa54a | ||
|
|
3cd4e94762 | ||
|
|
6950e0b03a | ||
|
|
f98309c7b7 | ||
|
|
a2dade9140 | ||
|
|
83ad4970e5 | ||
|
|
f123f5fc3c | ||
|
|
0a856b26da | ||
|
|
dad306a4b5 | ||
|
|
a52ee1dbb6 | ||
|
|
524f8ff277 | ||
|
|
83104e3625 | ||
|
|
fcb61c8f42 | ||
|
|
48e9aea47e | ||
|
|
6e7d912b95 | ||
|
|
475012cbed | ||
|
|
382dac96f7 | ||
|
|
3295a7d344 | ||
|
|
bb3cec0d13 | ||
|
|
1a673f2318 | ||
|
|
d9a7e81f71 | ||
|
|
73848311b1 | ||
|
|
857779e77b | ||
|
|
fa627b384f | ||
|
|
43a8781492 | ||
|
|
b17fef93ba | ||
|
|
fc16fc5404 | ||
|
|
e760e58ed7 | ||
|
|
3936a0a41e | ||
|
|
c7d830c57c | ||
|
|
7bf05bd907 | ||
|
|
ae20445301 | ||
|
|
33d1ac43e3 | ||
|
|
da51ebb0a7 | ||
|
|
d0399cbde4 | ||
|
|
49784bc1aa | ||
|
|
43f59cefda | ||
|
|
c42c1b7bbd | ||
|
|
765ffc0b57 | ||
|
|
3c87c89b3a | ||
|
|
391ad94b85 | ||
|
|
8f94196646 | ||
|
|
878184cab1 | ||
|
|
156ba6ab7b | ||
|
|
fd91a23188 | ||
|
|
9ca9e78da7 | ||
|
|
a1d5e4dcdc | ||
|
|
da5d0f8f19 | ||
|
|
d7bc56660b | ||
|
|
d39c54b3ee | ||
|
|
670c62267e | ||
|
|
8938fe27b2 | ||
|
|
e6c1a03c59 | ||
|
|
68930a1f50 | ||
|
|
3b76c30db2 | ||
|
|
523f657b3b | ||
|
|
d75ab4af1c | ||
|
|
7e33780743 | ||
|
|
9905592b24 | ||
|
|
6e1913cafc | ||
|
|
b620e07445 | ||
|
|
9b0503f49d | ||
|
|
c0b79e6e93 | ||
|
|
57cae6f9f1 | ||
|
|
2e72fa6589 | ||
|
|
4ee57da6f3 | ||
|
|
7560727372 | ||
|
|
6ba562c663 | ||
|
|
872b60be1c | ||
|
|
8cd80f4af1 | ||
|
|
7b5e1ed4da | ||
|
|
56442ae1e9 | ||
|
|
c9be618164 | ||
|
|
eddbe27c4d | ||
|
|
00fb658355 | ||
|
|
67a1847cea | ||
|
|
030ffca499 | ||
|
|
53913c07bf | ||
|
|
f6e29231f5 | ||
|
|
7a19c81964 | ||
|
|
5a72491ab0 | ||
|
|
6c4ecc0d64 | ||
|
|
524fc8bc0a | ||
|
|
5b6c1c6d81 | ||
|
|
852410a43b | ||
|
|
5d675efb64 | ||
|
|
03c7ad4cc0 | ||
|
|
135a369848 | ||
|
|
8993b0d520 | ||
|
|
ccff1ac3ef | ||
|
|
062b536617 | ||
|
|
d008f90399 | ||
|
|
4fdf43ea6a | ||
|
|
b94379f5bd | ||
|
|
37db07807a | ||
|
|
4274647c81 | ||
|
|
24fbf35542 | ||
|
|
c8eccc7e9d | ||
|
|
a318f6b407 | ||
|
|
75354ad8e6 | ||
|
|
af9ed58eef | ||
|
|
e0453418bd | ||
|
|
22344512ad | ||
|
|
9acc6652ff | ||
|
|
61bbd7f6aa | ||
|
|
f31f06f5c4 | ||
|
|
786fe131b8 | ||
|
|
35d6f19e80 | ||
|
|
8798bbce44 | ||
|
|
485eb8eaef | ||
|
|
1e39ec7d39 | ||
|
|
5fa4ae9824 | ||
|
|
281885a87e | ||
|
|
021aca4089 | ||
|
|
f341c5e5bb | ||
|
|
0d203ac21d | ||
|
|
1f12d270ec | ||
|
|
ec3d83f7ca | ||
|
|
05014f747a | ||
|
|
9c44da0cae | ||
|
|
5d5b38b259 | ||
|
|
9e3d83a2ee | ||
|
|
0e49e5e8f6 | ||
|
|
456d5a61cd | ||
|
|
ebd4e6b28e | ||
|
|
3fe2094814 | ||
|
|
9554902704 | ||
|
|
da7f13288f | ||
|
|
1a4d173bb4 | ||
|
|
abd1e8c6da | ||
|
|
7fe79b629e | ||
|
|
d3e8dfa724 | ||
|
|
3772db9eea | ||
|
|
b6c3c8024b | ||
|
|
6b948668b9 | ||
|
|
f232cfc3c8 | ||
|
|
bca8872db1 | ||
|
|
463c42330e | ||
|
|
d3b94fc4df | ||
|
|
a8d72fa665 | ||
|
|
69f14ee611 | ||
|
|
944a699e83 | ||
|
|
d11a9a04ff | ||
|
|
56b96bded9 | ||
|
|
ac35f81476 | ||
|
|
0623a71271 | ||
|
|
a66af353f7 | ||
|
|
cb55cf4b74 | ||
|
|
e9da30e420 | ||
|
|
c52abdc1ee | ||
|
|
55ca2f3091 | ||
|
|
0beb74fad0 | ||
|
|
2733727a40 | ||
|
|
ab9e8579ee | ||
|
|
617ef0fa19 | ||
|
|
2649598361 | ||
|
|
8eaa5206b9 | ||
|
|
df05d12fb2 | ||
|
|
8c6941b8e9 | ||
|
|
6ed59332d1 | ||
|
|
4abae9c708 | ||
|
|
b09f035ea9 | ||
|
|
44627b4cd4 | ||
|
|
97267ef89a | ||
|
|
132adcb733 | ||
|
|
738fa588a6 | ||
|
|
e63cff7a52 | ||
|
|
777a0e83fe | ||
|
|
612d35c96f | ||
|
|
0acd3e160a | ||
|
|
6993b2a7d4 | ||
|
|
2bd8ca96df | ||
|
|
4d82a6f41d | ||
|
|
cb33f1834b | ||
|
|
fb40f4b42d | ||
|
|
808eb56327 | ||
|
|
b636b65e55 | ||
|
|
7d30dcdb47 | ||
|
|
4f1615ecb3 | ||
|
|
463cd6dc46 | ||
|
|
8c007476c3 | ||
|
|
b20c77734c | ||
|
|
79cd2791f6 | ||
|
|
73de0fa535 | ||
|
|
83e74b98d5 | ||
|
|
2e4c834a90 | ||
|
|
2aa0b0fa20 | ||
|
|
3ac0ed3d78 | ||
|
|
eddc1de784 | ||
|
|
6483b3a3d9 | ||
|
|
38f0330eb2 | ||
|
|
15a49a31e1 | ||
|
|
33eb1af2be | ||
|
|
563ba1eebe | ||
|
|
62cf02e7b1 | ||
|
|
0f0bddbef9 | ||
|
|
0881143d57 | ||
|
|
effd950b2d | ||
|
|
4074e8d6ac | ||
|
|
779c8fa516 | ||
|
|
b059516707 | ||
|
|
dfa9d248d3 | ||
|
|
18925ad583 | ||
|
|
0b01732293 | ||
|
|
a89cd9d2ba | ||
|
|
2f35128acf | ||
|
|
7565eb39a4 | ||
|
|
d71d0c1cc4 | ||
|
|
dfc6970756 | ||
|
|
1429815fd1 | ||
|
|
aa397d003c | ||
|
|
40b952d8bf | ||
|
|
4974848a56 | ||
|
|
623fa5d709 | ||
|
|
fc33050496 | ||
|
|
e02acda6fa | ||
|
|
f11a72d4b5 | ||
|
|
49cd558fa1 | ||
|
|
378a1f7464 | ||
|
|
35d2dd6505 | ||
|
|
017c2c847f | ||
|
|
de2b4b7dd8 | ||
|
|
5dc5741771 | ||
|
|
971c490c41 | ||
|
|
0f804f9f6e | ||
|
|
fdf454722f | ||
|
|
208158f4d1 | ||
|
|
032b94e81d | ||
|
|
4988163744 | ||
|
|
ee7e71b9fb | ||
|
|
7efa54687a | ||
|
|
2917ff25e5 | ||
|
|
e3839c35b7 | ||
|
|
c39678d733 | ||
|
|
06dea79c74 | ||
|
|
96b0652a06 | ||
|
|
49e0411be6 | ||
|
|
94819e6f16 | ||
|
|
a232af06ed | ||
|
|
b24535483e | ||
|
|
9521c287b6 | ||
|
|
55b9edec39 | ||
|
|
1e9c70eed9 | ||
|
|
3262e083fa | ||
|
|
ec1c719553 |
@@ -5,4 +5,4 @@
|
||||
# https://github.com/settings/personal-access-tokens/new
|
||||
GITHUB_TOKEN=
|
||||
|
||||
ELECTRON_LAUNCH_FLAGS="--ozone-platform-hint=auto --enable-webrtc-pipewire-capturer --enable-features=WaylandWindowDecorations"
|
||||
ELECTRON_LAUNCH_FLAGS="--enable-source-maps --ozone-platform-hint=auto"
|
||||
@@ -1,63 +0,0 @@
|
||||
{
|
||||
"root": true,
|
||||
"parser": "@typescript-eslint/parser",
|
||||
"ignorePatterns": ["dist", "node_modules"],
|
||||
"plugins": [
|
||||
"@typescript-eslint",
|
||||
"license-header",
|
||||
"simple-import-sort",
|
||||
"unused-imports",
|
||||
"path-alias",
|
||||
"prettier"
|
||||
],
|
||||
"settings": {
|
||||
"import/resolver": {
|
||||
"alias": {
|
||||
"map": []
|
||||
}
|
||||
}
|
||||
},
|
||||
"rules": {
|
||||
"license-header/header": ["error", "scripts/header.txt"],
|
||||
"eqeqeq": ["error", "always", { "null": "ignore" }],
|
||||
"spaced-comment": ["error", "always", { "markers": ["!"] }],
|
||||
"yoda": "error",
|
||||
"prefer-destructuring": ["error", { "object": true, "array": 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"
|
||||
}
|
||||
}
|
||||
5
.github/ISSUE_TEMPLATE/config.yml
vendored
Normal file
5
.github/ISSUE_TEMPLATE/config.yml
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
blank_issues_enabled: false
|
||||
contact_links:
|
||||
- name: Vencord Support Server
|
||||
url: https://discord.gg/D9uwnFnqmd
|
||||
about: "Need Help? Join our support server and ask in the #vesktop-support channel!"
|
||||
20
.github/ISSUE_TEMPLATE/dev-issue.yml
vendored
Normal file
20
.github/ISSUE_TEMPLATE/dev-issue.yml
vendored
Normal file
@@ -0,0 +1,20 @@
|
||||
name: Vesktop Developer Issue
|
||||
description: Reserved for Vesktop Developers. Join our support server for support.
|
||||
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
# This form is reserved for Vesktop Developers. Do not open an issue.
|
||||
|
||||
Instead, use the [#vesktop-support channel](https://discord.com/channels/1015060230222131221/1345457031426871417) on our [Discord server](https://vencord.dev/discord) for help and reporting issues.
|
||||
|
||||
Your issue will be closed immediately with no comment and you will be blocked if you ignore this.
|
||||
|
||||
This is because 99% of issues are not actually bugs, but rather user or system issues and it adds a lot of noise to our development process.
|
||||
- type: textarea
|
||||
id: content
|
||||
attributes:
|
||||
label: Content
|
||||
validations:
|
||||
required: true
|
||||
38
.github/workflows/meta.yml
vendored
Normal file
38
.github/workflows/meta.yml
vendored
Normal file
@@ -0,0 +1,38 @@
|
||||
name: Update metainfo on release
|
||||
|
||||
on:
|
||||
release:
|
||||
types:
|
||||
- published
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
jobs:
|
||||
update:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: pnpm/action-setup@v4 # Install pnpm using packageManager key in package.json
|
||||
|
||||
- name: Use Node.js 20
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 20
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm i
|
||||
|
||||
- name: Update metainfo
|
||||
run: pnpm generateMeta
|
||||
|
||||
- name: Commit and merge in changes
|
||||
run: |
|
||||
git config user.name "github-actions[bot]"
|
||||
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
|
||||
|
||||
gh release upload "${{ github.event.release.tag_name }}" dist/dev.vencord.Vesktop.metainfo.xml
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
47
.github/workflows/release.yml
vendored
47
.github/workflows/release.yml
vendored
@@ -4,6 +4,7 @@ on:
|
||||
push:
|
||||
tags:
|
||||
- v*
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
release:
|
||||
@@ -12,17 +13,47 @@ jobs:
|
||||
strategy:
|
||||
matrix:
|
||||
os: [macos-latest, ubuntu-latest, windows-latest]
|
||||
include:
|
||||
- os: macos-latest
|
||||
platform: mac
|
||||
- os: ubuntu-latest
|
||||
platform: linux
|
||||
- os: windows-latest
|
||||
platform: windows
|
||||
|
||||
steps:
|
||||
- name: Check out Git repository
|
||||
uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
- uses: pnpm/action-setup@v4 # Install pnpm using packageManager key in package.json
|
||||
|
||||
- uses: actions/setup-node@v3
|
||||
- name: Use Node.js 20
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 18
|
||||
node-version: 20
|
||||
cache: "pnpm"
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm install --frozen-lockfile
|
||||
|
||||
- name: Build
|
||||
run: pnpm build
|
||||
|
||||
- name: Run Electron Builder
|
||||
uses: samuelmeuli/action-electron-builder@e4b12cd06ddf023422f1ac4e39632bd76f6e6928
|
||||
with:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
RELEASE: true
|
||||
if: ${{ matrix.platform != 'mac' }}
|
||||
run: |
|
||||
pnpm electron-builder --${{ matrix.platform }} --publish always
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Run Electron Builder
|
||||
if: ${{ matrix.platform == 'mac' }}
|
||||
run: |
|
||||
echo "$API_KEY" > apple.p8
|
||||
pnpm electron-builder --${{ matrix.platform }} --publish always
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
CSC_LINK: ${{ secrets.APPLE_SIGNING_CERT }}
|
||||
CSC_KEY_PASSWORD: ${{ secrets.APPLE_SIGNING_CERT_PASSWORD }}
|
||||
API_KEY: ${{ secrets.APPLE_API_KEY }}
|
||||
APPLE_API_KEY: apple.p8
|
||||
APPLE_API_KEY_ID: ${{ secrets.APPLE_API_KEY_ID }}
|
||||
APPLE_API_ISSUER: ${{ secrets.APPLE_API_ISSUER }}
|
||||
|
||||
10
.github/workflows/test.yml
vendored
10
.github/workflows/test.yml
vendored
@@ -11,13 +11,13 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: pnpm/action-setup@v2 # Install pnpm using packageManager key in package.json
|
||||
- uses: actions/checkout@v4
|
||||
- uses: pnpm/action-setup@v4 # Install pnpm using packageManager key in package.json
|
||||
|
||||
- name: Use Node.js 18
|
||||
uses: actions/setup-node@v3
|
||||
- name: Use Node.js 20
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 18
|
||||
node-version: 20
|
||||
cache: "pnpm"
|
||||
|
||||
- name: Install dependencies
|
||||
|
||||
29
.github/workflows/update-vencord-dev.yml
vendored
Normal file
29
.github/workflows/update-vencord-dev.yml
vendored
Normal file
@@ -0,0 +1,29 @@
|
||||
name: Update vencord.dev Vesktop version
|
||||
|
||||
on:
|
||||
release:
|
||||
types:
|
||||
- published
|
||||
|
||||
jobs:
|
||||
update:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Update scripts/_latestVesktopVersion.txt file in vencord.dev repo
|
||||
run: |
|
||||
git config --global user.name "$USERNAME"
|
||||
git config --global user.email "41898282+github-actions[bot]@users.noreply.github.com"
|
||||
|
||||
git clone https://$USERNAME:$API_TOKEN@github.com/$GH_REPO.git repo
|
||||
|
||||
cd repo
|
||||
version="${{ github.event.release.tag_name }}"
|
||||
echo "${version#v}" > scripts/_latestVesktopVersion.txt
|
||||
|
||||
git add scripts/_latestVesktopVersion.txt
|
||||
git commit -m "Update Vesktop version to ${{ github.event.release.tag_name }}"
|
||||
git push https://$USERNAME:$API_TOKEN@github.com/$GH_REPO.git
|
||||
env:
|
||||
API_TOKEN: ${{ secrets.VENCORD_DEV_GITHUB_TOKEN }}
|
||||
GH_REPO: Vencord/vencord.dev
|
||||
USERNAME: GitHub-Actions
|
||||
4
.github/workflows/winget-submission.yml
vendored
4
.github/workflows/winget-submission.yml
vendored
@@ -13,10 +13,10 @@ on:
|
||||
jobs:
|
||||
winget:
|
||||
name: Publish winget package
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: windows-latest
|
||||
steps:
|
||||
- name: Submit package to Winget Community Repo
|
||||
uses: vedantmgoyal2009/winget-releaser@e68d386d5d6a1cef8cb0fb5e62b77ebcb83e7d58 # v2
|
||||
uses: vedantmgoyal2009/winget-releaser@0db4f0a478166abd0fa438c631849f0b8dcfb99f
|
||||
with:
|
||||
identifier: Vencord.Vesktop
|
||||
token: ${{ secrets.WINGET_PAT }}
|
||||
|
||||
50
.vscode/settings.json
vendored
50
.vscode/settings.json
vendored
@@ -1,25 +1,25 @@
|
||||
{
|
||||
"editor.formatOnSave": true,
|
||||
"editor.codeActionsOnSave": {
|
||||
"source.fixAll.eslint": true
|
||||
},
|
||||
"[typescript]": {
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||
},
|
||||
"[javascript]": {
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||
},
|
||||
"[typescriptreact]": {
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||
},
|
||||
"[javascriptreact]": {
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||
},
|
||||
"[json]": {
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||
},
|
||||
"[jsonc]": {
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||
},
|
||||
"cSpell.words": ["Vesktop"]
|
||||
}
|
||||
{
|
||||
"editor.formatOnSave": true,
|
||||
"editor.codeActionsOnSave": {
|
||||
"source.fixAll.eslint": "explicit"
|
||||
},
|
||||
"[typescript]": {
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||
},
|
||||
"[javascript]": {
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||
},
|
||||
"[typescriptreact]": {
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||
},
|
||||
"[javascriptreact]": {
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||
},
|
||||
"[json]": {
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||
},
|
||||
"[jsonc]": {
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||
},
|
||||
"cSpell.words": ["Vesktop"]
|
||||
}
|
||||
|
||||
68
README.md
68
README.md
@@ -1,47 +1,32 @@
|
||||
# Vesktop
|
||||
|
||||
Vesktop is a cross platform desktop app aiming to give you a snappier Discord experience with [Vencord](https://github.com/Vendicated/Vencord) pre-installed
|
||||
Vesktop is a custom Discord desktop app
|
||||
|
||||
**Main features**:
|
||||
- Vencord preinstalled
|
||||
- Much more lightweight and faster than the official Discord app
|
||||
- Linux Screenshare with sound & wayland
|
||||
- Much better privacy, since Discord has no access to your system
|
||||
|
||||
**Not yet supported**:
|
||||
- Global Keybinds
|
||||
- see the [Roadmap](https://github.com/Vencord/Vesktop/issues/324)
|
||||
|
||||
Bug reports, feature requests & contributions are highly appreciated!!
|
||||
|
||||

|
||||

|
||||

|
||||
|
||||
## Installing
|
||||
|
||||
### Windows
|
||||
Visit https://vesktop.dev/install
|
||||
|
||||
Download and run Vesktop-Setup-VERSION.exe from [releases](https://github.com/Vencord/Vesktop/releases/latest)
|
||||
## Building from Source
|
||||
|
||||
### Mac
|
||||
You need to have the following dependencies installed:
|
||||
- [Git](https://git-scm.com/downloads)
|
||||
- [Node.js](https://nodejs.org/en/download)
|
||||
- pnpm: `npm install --global pnpm`
|
||||
|
||||
Download and run Vesktop-VERSION.dmg from [releases](https://github.com/Vencord/Vesktop/releases/latest)
|
||||
|
||||
### Linux
|
||||
|
||||
#### Arch based
|
||||
|
||||
Install [vencord-desktop-git](https://aur.archlinux.org/packages/vencord-desktop-git) from the AUR using your favourite AUR helper, for example [yay](https://github.com/Jguer/yay)
|
||||
|
||||
#### Ubuntu/Debian based
|
||||
|
||||
Download Vesktop-VERSION.deb from [releases](https://github.com/Vencord/Vesktop/releases/latest)
|
||||
|
||||
#### Fedora/RHEL based
|
||||
|
||||
Download Vesktop-VERSION.rpm from [releases](https://github.com/Vencord/Vesktop/releases/latest)
|
||||
|
||||
#### Other
|
||||
|
||||
Either download Vesktop-VERSION.AppImage and just run it directly or grab Vesktop-VERSION.tar.gz, extract it somewhere and run `vencorddesktop`.
|
||||
|
||||
A flatpak is planned, if you want packages for other repos, feel free to create them and they can be linked as unofficial here
|
||||
|
||||
## Building
|
||||
|
||||
Packaging will create builds in the dist/ folder. You can then install them like mentioned above or distribute them
|
||||
Packaging will create builds in the dist/ folder
|
||||
|
||||
```sh
|
||||
git clone https://github.com/Vencord/Vesktop
|
||||
@@ -53,18 +38,23 @@ pnpm i
|
||||
# Either run it without packaging
|
||||
pnpm start
|
||||
|
||||
# Or package
|
||||
# Or package (will build packages for your OS)
|
||||
pnpm package
|
||||
# Or only build the pacman target
|
||||
|
||||
# Or only build the Linux Pacman package
|
||||
pnpm package --linux pacman
|
||||
|
||||
# Or package to a directory only
|
||||
pnpm package:dir
|
||||
```
|
||||
|
||||
## Motivation
|
||||
## Building LibVesktop from Source
|
||||
|
||||
The official Discord Desktop app is very resource heavy compared to Discord in your Browser. There are multiple alternative Electron apps (ArmCord, WebCord, probably more) that prove how much of a performance gain you can gain by using a custom app. ArmCord already supports Vencord but makes it pretty limited for us. Making our own standalone app gives us much more control.
|
||||
This is a small C++ helper library Vesktop uses on Linux to emit D-Bus events. By default, prebuilt binaries for x64 and arm64 are used.
|
||||
|
||||
This is just a random idea I (V) got, and might not actually ever be finished heh
|
||||
|
||||
Gluon also seems very attractive for this because of how lightweight it can be and because unlike electron, streaming just works out of the box like in any chromium browser. However, at the time of writing this, it still lacks some features necessary to make it work (synchronous ipc or a way to get node process variables into the onLoad function for instance, plus onLoad seems to load a little too late sometimes)
|
||||
If you want to build it from source:
|
||||
1. Install build dependencies:
|
||||
- Debian/Ubuntu: `apt install build-essential python3 curl pkg-config libglib2.0-dev`
|
||||
- Fedora: `dnf install @c-development @development-tools python3 curl pkgconf-pkg-config glib2-devel`
|
||||
2. Run `pnpm buildLibVesktop`
|
||||
3. From now on, building Vesktop will use your own build
|
||||
BIN
build/Assets.car
Normal file
BIN
build/Assets.car
Normal file
Binary file not shown.
BIN
build/background.tiff
Normal file
BIN
build/background.tiff
Normal file
Binary file not shown.
21
build/entitlements.mac.plist
Normal file
21
build/entitlements.mac.plist
Normal file
@@ -0,0 +1,21 @@
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>com.apple.security.cs.allow-unsigned-executable-memory</key>
|
||||
<true/>
|
||||
<key>com.apple.security.cs.allow-jit</key>
|
||||
<true/>
|
||||
<key>com.apple.security.network.client</key>
|
||||
<true/>
|
||||
<key>com.apple.security.device.audio-input</key>
|
||||
<true/>
|
||||
<key>com.apple.security.device.camera</key>
|
||||
<true/>
|
||||
<key>com.apple.security.device.bluetooth</key>
|
||||
<true/>
|
||||
<key>com.apple.security.cs.allow-dyld-environment-variables</key>
|
||||
<true/>
|
||||
<key>com.apple.security.cs.disable-library-validation</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</plist>
|
||||
BIN
build/icon.icns
BIN
build/icon.icns
Binary file not shown.
BIN
build/icon.ico
Normal file
BIN
build/icon.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 364 KiB |
BIN
build/icon.png
BIN
build/icon.png
Binary file not shown.
|
Before Width: | Height: | Size: 9.5 KiB |
1
build/icon.svg
Normal file
1
build/icon.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="1024" height="1024" style="isolation:isolate" viewBox="0 0 1024 1024"><path fill="#eb7396" d="M336.23 886.35c56.07 31.62 150.18 51.4 247.12 37.3C816.1 889.78 977.57 673.33 943.7 440.57 909.85 207.83 693.41 46.36 460.65 80.22 227.9 114.08 66.43 330.53 100.3 563.28c6.86 47.2 21.23 91.46 29.65 108.01 6.67 13.1 9.03 35.28 5.28 49.5l-26.8 101.43c-16.75 63.41 21.03 100.93 84.32 83.73l94.59-25.7c14.18-3.86 36.1-1.12 48.9 6.1z" style="display:inline;isolation:isolate"/><g style="display:inline;isolation:isolate"><g style="isolation:isolate"><path d="M494.586 109.162c-3.861.14-7.743.508-11.621 1.004-72.122 6.505-117.149 63.067-128.176 116.693l-17.812 86.508-35.555-76.785v-.002c-23.455-50.637-77.415-90.65-141.998-81.877-32.285 4.386-60.608 22.22-76.123 48.385-13.722 23.14-16.564 52.439-7.66 78.943v1.608l157.394 336.037c24.863 53.102 81.404 87.116 143.43 78.681 61.475-8.36 109.47-52.362 123.914-110.074l91.158-364.25c7.385-29.508 1.643-63.11-20.129-86.328-19.05-20.315-47.417-29.61-76.822-28.543" style="baseline-shift:baseline;display:inline;overflow:visible;opacity:1;vector-effect:none;fill:#1d2021;stop-color:#000;stop-opacity:1"/><path fill="#fff" d="m118.49 274.1 153.35 327.4c16 34.18 58.517 59.883 98.852 54.398s78.958-41.418 88.118-78.018l91.16-364.25c9.16-36.6-14.593-67.398-62.253-60.918-51.41 4.189-83.357 45.818-90.957 82.778l-37.84 183.78c-3.42 16.62-11.98 17.61-19.1 2.22l-77.28-166.9c-15.86-34.24-55.539-63.108-97.349-57.428S102.48 239.92 118.49 274.09Z" style="display:inline;fill:#ededed;fill-opacity:1"/><path d="M701.543 396.742a266.4 266.4 0 0 0-100.73 17.746l-.004.002h-.002a271.8 271.8 0 0 0-81.118 48.942 275 275 0 0 0-59.822 74.15v.002a277 277 0 0 0-31.572 93.527v.006l-.002.006c-1.984 13.48-3.023 27.136-3.024 40.893v.014a275.13 275.13 0 0 0 64.01 176.617l.004.004.002.004a269.5 269.5 0 0 0 72.846 61.008l.02.011.02.012a266.5 266.5 0 0 0 93.054 32.19l.017.002a266.3 266.3 0 0 0 114.906-8.061l.02-.006.02-.006a270.3 270.3 0 0 0 67.706-30.816l.004-.002c34.575-21.867 52.68-62.732 45.645-103.032-7.032-40.29-37.897-72.6-77.82-81.47a101.46 101.46 0 0 0-76.338 13.314l-.024.016-.023.015a67 67 0 0 1-16.83 7.67 63.3 63.3 0 0 1-17.957 2.6h-.081a63.86 63.86 0 0 1-31.726-8.37 67 67 0 0 1-17.951-15.042l-.006-.008a72.44 72.44 0 0 1-16.838-46.524v-.016a76.6 76.6 0 0 1 9.367-36.642 72.5 72.5 0 0 1 15.688-19.395l.012-.012.011-.011a69.3 69.3 0 0 1 20.617-12.455 63.97 63.97 0 0 1 59.061 6.896 68.5 68.5 0 0 1 19.91 21.201h.002v.002c28.529 47.605 91.489 63.35 139.068 34.78l.01-.006.01-.006c47.508-28.562 63.227-91.41 34.76-138.975l-.013-.025-.016-.026a271.8 271.8 0 0 0-79.111-84.105l-.026-.02-.027-.017a266.5 266.5 0 0 0-111.506-43.725 266 266 0 0 0-34.223-2.857" style="baseline-shift:baseline;display:inline;overflow:visible;opacity:1;vector-effect:none;fill:#ededed;stop-color:#000;stop-opacity:1"/><path fill="#1d2021" d="M751.65 766.94a59.8 59.8 0 0 1 45.03-7.85c23.607 5.237 41.724 24.197 45.882 48.018 4.157 23.821-6.465 47.797-26.902 60.722a228.7 228.7 0 0 1-57.33 26.1 224.7 224.7 0 0 1-96.97 6.8 224.9 224.9 0 0 1-78.56-27.17 227.9 227.9 0 0 1-61.6-51.59 233.5 233.5 0 0 1-54.33-149.94c0-11.67.88-23.3 2.58-34.85a235.4 235.4 0 0 1 26.83-79.48 233.4 233.4 0 0 1 50.78-62.94 230.2 230.2 0 0 1 68.7-41.45 224.8 224.8 0 0 1 113.91-12.56c33.75 5 65.94 17.62 94.1 36.9a230.15 230.15 0 0 1 67 71.23c16.936 28.299 7.765 64.967-20.5 81.96-28.294 16.99-65.005 7.81-81.97-20.5a110.1 110.1 0 0 0-32.08-34.14 105.7 105.7 0 0 0-97.52-11.4 110.9 110.9 0 0 0-33.05 19.96 114 114 0 0 0-24.79 30.69 118.2 118.2 0 0 0-14.51 56.66 114.07 114.07 0 0 0 26.51 73.24 108.6 108.6 0 0 0 29.24 24.5 105.5 105.5 0 0 0 52.45 13.85c10.08 0 20.12-1.45 29.8-4.32a108.6 108.6 0 0 0 27.3-12.44" style="display:inline"/></g></g></svg>
|
||||
|
After Width: | Height: | Size: 3.7 KiB |
@@ -1,8 +1,8 @@
|
||||
!macro preInit
|
||||
SetRegView 64
|
||||
WriteRegExpandStr HKLM "${INSTALL_REGISTRY_KEY}" InstallLocation "$LocalAppData\VencordDesktop"
|
||||
WriteRegExpandStr HKCU "${INSTALL_REGISTRY_KEY}" InstallLocation "$LocalAppData\VencordDesktop"
|
||||
WriteRegExpandStr HKLM "${INSTALL_REGISTRY_KEY}" InstallLocation "$LocalAppData\vesktop"
|
||||
WriteRegExpandStr HKCU "${INSTALL_REGISTRY_KEY}" InstallLocation "$LocalAppData\vesktop"
|
||||
SetRegView 32
|
||||
WriteRegExpandStr HKLM "${INSTALL_REGISTRY_KEY}" InstallLocation "$LocalAppData\VencordDesktop"
|
||||
WriteRegExpandStr HKCU "${INSTALL_REGISTRY_KEY}" InstallLocation "$LocalAppData\VencordDesktop"
|
||||
WriteRegExpandStr HKLM "${INSTALL_REGISTRY_KEY}" InstallLocation "$LocalAppData\vesktop"
|
||||
WriteRegExpandStr HKCU "${INSTALL_REGISTRY_KEY}" InstallLocation "$LocalAppData\vesktop"
|
||||
!macroend
|
||||
|
||||
103
eslint.config.mjs
Normal file
103
eslint.config.mjs
Normal file
@@ -0,0 +1,103 @@
|
||||
/*
|
||||
* Vesktop, a desktop app aiming to give you a snappier Discord Experience
|
||||
* Copyright (c) 2023 Vendicated and Vencord contributors
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
//@ts-check
|
||||
|
||||
import stylistic from "@stylistic/eslint-plugin";
|
||||
import pathAlias from "eslint-plugin-path-alias";
|
||||
import simpleHeader from "eslint-plugin-simple-header";
|
||||
import importSort from "eslint-plugin-simple-import-sort";
|
||||
import unusedImports from "eslint-plugin-unused-imports";
|
||||
import tseslint from "typescript-eslint";
|
||||
import prettier from "eslint-plugin-prettier";
|
||||
|
||||
export default tseslint.config(
|
||||
{ ignores: ["dist"] },
|
||||
|
||||
{
|
||||
files: ["src/**/*.{tsx,ts,mts,mjs,js,jsx}"],
|
||||
plugins: {
|
||||
simpleHeader,
|
||||
stylistic,
|
||||
importSort,
|
||||
unusedImports,
|
||||
pathAlias,
|
||||
prettier
|
||||
},
|
||||
settings: {
|
||||
"import/resolver": {
|
||||
alias: {
|
||||
map: []
|
||||
}
|
||||
}
|
||||
},
|
||||
languageOptions: {
|
||||
parser: tseslint.parser,
|
||||
parserOptions: {
|
||||
project: true,
|
||||
tsconfigRootDir: import.meta.dirname
|
||||
}
|
||||
},
|
||||
rules: {
|
||||
"simpleHeader/header": [
|
||||
"error",
|
||||
{
|
||||
files: ["scripts/header.txt"],
|
||||
templates: { author: [".*", "Vendicated and Vesktop contributors"] }
|
||||
}
|
||||
],
|
||||
|
||||
// ESLint Rules
|
||||
|
||||
yoda: "error",
|
||||
eqeqeq: ["error", "always", { null: "ignore" }],
|
||||
"prefer-destructuring": [
|
||||
"error",
|
||||
{
|
||||
VariableDeclarator: { array: false, object: true },
|
||||
AssignmentExpression: { array: false, object: false }
|
||||
}
|
||||
],
|
||||
"operator-assignment": ["error", "always"],
|
||||
"no-useless-computed-key": "error",
|
||||
"no-unneeded-ternary": ["error", { defaultAssignment: false }],
|
||||
"no-invalid-regexp": "error",
|
||||
"no-constant-condition": ["error", { checkLoops: false }],
|
||||
"no-duplicate-imports": "error",
|
||||
"dot-notation": "error",
|
||||
"no-useless-escape": "error",
|
||||
"no-fallthrough": "error",
|
||||
"for-direction": "error",
|
||||
"no-async-promise-executor": "error",
|
||||
"no-cond-assign": "error",
|
||||
"no-dupe-else-if": "error",
|
||||
"no-duplicate-case": "error",
|
||||
"no-irregular-whitespace": "error",
|
||||
"no-loss-of-precision": "error",
|
||||
"no-misleading-character-class": "error",
|
||||
"no-prototype-builtins": "error",
|
||||
"no-regex-spaces": "error",
|
||||
"no-shadow-restricted-names": "error",
|
||||
"no-unexpected-multiline": "error",
|
||||
"no-unsafe-optional-chaining": "error",
|
||||
"no-useless-backreference": "error",
|
||||
"use-isnan": "error",
|
||||
"prefer-const": "error",
|
||||
"prefer-spread": "error",
|
||||
|
||||
// Styling Rules
|
||||
"stylistic/spaced-comment": ["error", "always", { markers: ["!"] }],
|
||||
"stylistic/no-extra-semi": "error",
|
||||
|
||||
// Plugin Rules
|
||||
"importSort/imports": "error",
|
||||
"importSort/exports": "error",
|
||||
"unusedImports/no-unused-imports": "error",
|
||||
"pathAlias/no-relative": "error",
|
||||
"prettier/prettier": "error"
|
||||
}
|
||||
}
|
||||
);
|
||||
160
package.json
160
package.json
@@ -1,75 +1,91 @@
|
||||
{
|
||||
"name": "VencordDesktop",
|
||||
"version": "0.4.2",
|
||||
"name": "vesktop",
|
||||
"version": "1.6.0",
|
||||
"private": true,
|
||||
"description": "",
|
||||
"description": "Vesktop is a custom Discord desktop app",
|
||||
"keywords": [],
|
||||
"homepage": "https://vencord.dev/",
|
||||
"license": "GPL-3.0",
|
||||
"license": "GPL-3.0-or-later",
|
||||
"author": "Vendicated <vendicated@riseup.net>",
|
||||
"main": "dist/js/main.js",
|
||||
"scripts": {
|
||||
"build": "tsx scripts/build/build.mts",
|
||||
"build:dev": "pnpm build --dev",
|
||||
"buildLibVesktop": "pnpm -C packages/libvesktop install && pnpm -C packages/libvesktop run build",
|
||||
"package": "pnpm build && electron-builder",
|
||||
"package:dir": "pnpm build && electron-builder --dir",
|
||||
"lint": "eslint . --ext .js,.jsx,.ts,.tsx,.mts,.mjs",
|
||||
"lint": "eslint",
|
||||
"lint:fix": "pnpm lint --fix",
|
||||
"start": "pnpm build && electron .",
|
||||
"start:dev": "pnpm build:dev && electron .",
|
||||
"start:watch": "pnpm build:dev && tsx scripts/startWatch.mts",
|
||||
"test": "pnpm lint && pnpm testTypes",
|
||||
"testTypes": "tsc --noEmit",
|
||||
"watch": "pnpm build --watch"
|
||||
"watch": "pnpm build --watch",
|
||||
"generateMeta": "tsx scripts/utils/generateMeta.mts",
|
||||
"updateArrpcDB": "node ./node_modules/arrpc/update_db.js",
|
||||
"postinstall": "pnpm updateArrpcDB"
|
||||
},
|
||||
"dependencies": {
|
||||
"arrpc": "github:OpenAsar/arrpc#89f4da610ccfac93f461826a446a17cd3b23953d"
|
||||
"arrpc": "github:OpenAsar/arrpc#2234e9c9111f4c42ebcc3aa6a2215bfd979eef77",
|
||||
"electron-updater": "^6.6.2"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@vencord/venmic": "^1.6.0"
|
||||
"@vencord/venmic": "^6.1.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@fal-works/esbuild-plugin-global-externals": "^2.1.2",
|
||||
"@types/node": "^20.8.4",
|
||||
"@types/react": "^18.2.28",
|
||||
"@typescript-eslint/eslint-plugin": "^6.7.5",
|
||||
"@typescript-eslint/parser": "^6.7.5",
|
||||
"@vencord/types": "^0.1.2",
|
||||
"dotenv": "^16.3.1",
|
||||
"electron": "^27.0.0",
|
||||
"electron-builder": "^24.6.4",
|
||||
"esbuild": "^0.19.4",
|
||||
"eslint": "^8.51.0",
|
||||
"eslint-config-prettier": "^9.0.0",
|
||||
"@stylistic/eslint-plugin": "^5.4.0",
|
||||
"@types/node": "^24.7.0",
|
||||
"@types/react": "19.2.1",
|
||||
"@vencord/types": "^1.13.2",
|
||||
"dotenv": "^17.2.3",
|
||||
"electron": "^38.0.0",
|
||||
"electron-builder": "^26.0.12",
|
||||
"esbuild": "^0.25.10",
|
||||
"eslint": "^9.37.0",
|
||||
"eslint-import-resolver-alias": "^1.1.2",
|
||||
"eslint-plugin-license-header": "^0.6.0",
|
||||
"eslint-plugin-path-alias": "^1.0.0",
|
||||
"eslint-plugin-prettier": "^5.0.1",
|
||||
"eslint-plugin-simple-import-sort": "^10.0.0",
|
||||
"eslint-plugin-unused-imports": "^3.0.0",
|
||||
"prettier": "^3.0.3",
|
||||
"eslint-plugin-path-alias": "^2.1.0",
|
||||
"eslint-plugin-prettier": "^5.5.4",
|
||||
"eslint-plugin-simple-header": "^1.2.2",
|
||||
"eslint-plugin-simple-import-sort": "^12.1.1",
|
||||
"eslint-plugin-unused-imports": "^4.2.0",
|
||||
"libvesktop": "link:packages/libvesktop",
|
||||
"prettier": "^3.6.2",
|
||||
"source-map-support": "^0.5.21",
|
||||
"tsx": "^3.13.0",
|
||||
"type-fest": "^4.4.0",
|
||||
"typescript": "^5.2.2"
|
||||
"tsx": "^4.20.6",
|
||||
"type-fest": "^5.0.1",
|
||||
"typescript": "^5.9.3",
|
||||
"typescript-eslint": "^8.46.0",
|
||||
"xml-formatter": "^3.6.7"
|
||||
},
|
||||
"packageManager": "pnpm@8.6.11",
|
||||
"packageManager": "pnpm@10.7.1",
|
||||
"engines": {
|
||||
"node": ">=18",
|
||||
"pnpm": ">=8"
|
||||
},
|
||||
"build": {
|
||||
"appId": "dev.vencord.desktop",
|
||||
"appId": "dev.vencord.vesktop",
|
||||
"productName": "Vesktop",
|
||||
"executableName": "vesktop",
|
||||
"files": [
|
||||
"!*",
|
||||
"!node_modules",
|
||||
"dist/js",
|
||||
"static",
|
||||
"package.json",
|
||||
"LICENSE"
|
||||
],
|
||||
"protocols": {
|
||||
"name": "Discord",
|
||||
"schemes": [
|
||||
"discord"
|
||||
]
|
||||
},
|
||||
"beforePack": "scripts/build/beforePack.mjs",
|
||||
"afterPack": "scripts/build/afterPack.mjs",
|
||||
"linux": {
|
||||
"icon": "build/icon.icns",
|
||||
"icon": "build/icon.svg",
|
||||
"category": "Network",
|
||||
"maintainer": "vendicated+vesktop@riseup.net",
|
||||
"target": [
|
||||
@@ -103,13 +119,15 @@
|
||||
}
|
||||
],
|
||||
"desktop": {
|
||||
"Name": "Vesktop",
|
||||
"GenericName": "Internet Messenger",
|
||||
"Type": "Application",
|
||||
"Categories": "Network;InstantMessaging;Chat;",
|
||||
"Keywords": "discord;vencord;electron;chat;",
|
||||
"WMClass": "VencordDesktop",
|
||||
"StartupWMClass": "VencordDesktop"
|
||||
"entry": {
|
||||
"Name": "Vesktop",
|
||||
"GenericName": "Internet Messenger",
|
||||
"Type": "Application",
|
||||
"Categories": "Network;InstantMessaging;Chat;",
|
||||
"Keywords": "discord;vencord;electron;chat;",
|
||||
"MimeType": "x-scheme-handler/discord",
|
||||
"StartupWMClass": "vesktop"
|
||||
}
|
||||
}
|
||||
},
|
||||
"mac": {
|
||||
@@ -117,31 +135,83 @@
|
||||
{
|
||||
"target": "default",
|
||||
"arch": [
|
||||
"x64",
|
||||
"arm64"
|
||||
"universal"
|
||||
]
|
||||
}
|
||||
],
|
||||
"category": "Network",
|
||||
"category": "public.app-category.social-networking",
|
||||
"darkModeSupport": true,
|
||||
"extendInfo": {
|
||||
"NSMicrophoneUsageDescription": "This app needs access to the microphone",
|
||||
"NSCameraUsageDescription": "This app needs access to the camera",
|
||||
"com.apple.security.device.audio-input": true,
|
||||
"com.apple.security.device.camera": true
|
||||
}
|
||||
"com.apple.security.device.camera": true,
|
||||
"CFBundleIconName": "Icon"
|
||||
},
|
||||
"notarize": true
|
||||
},
|
||||
"dmg": {
|
||||
"background": "build/background.tiff",
|
||||
"icon": "build/icon.icns",
|
||||
"iconSize": 105,
|
||||
"window": {
|
||||
"width": 512,
|
||||
"height": 340
|
||||
},
|
||||
"contents": [
|
||||
{
|
||||
"x": 140,
|
||||
"y": 160
|
||||
},
|
||||
{
|
||||
"x": 372,
|
||||
"y": 160,
|
||||
"type": "link",
|
||||
"path": "/Applications"
|
||||
}
|
||||
]
|
||||
},
|
||||
"nsis": {
|
||||
"include": "build/installer.nsh",
|
||||
"oneClick": false
|
||||
},
|
||||
"win": {
|
||||
"icon": "build/icon.ico",
|
||||
"target": [
|
||||
"nsis",
|
||||
"zip"
|
||||
{
|
||||
"target": "nsis",
|
||||
"arch": [
|
||||
"x64",
|
||||
"arm64"
|
||||
]
|
||||
},
|
||||
{
|
||||
"target": "zip",
|
||||
"arch": [
|
||||
"x64",
|
||||
"arm64"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"publish": {
|
||||
"provider": "github"
|
||||
},
|
||||
"rpm": {
|
||||
"fpm": [
|
||||
"--rpm-rpmbuild-define=_build_id_links none"
|
||||
]
|
||||
}
|
||||
},
|
||||
"pnpm": {
|
||||
"patchedDependencies": {
|
||||
"arrpc@3.5.0": "patches/arrpc@3.5.0.patch",
|
||||
"electron-updater": "patches/electron-updater.patch"
|
||||
},
|
||||
"onlyBuiltDependencies": [
|
||||
"@vencord/venmic",
|
||||
"electron",
|
||||
"esbuild"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
1
packages/libvesktop/.gitignore
vendored
Normal file
1
packages/libvesktop/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
build
|
||||
19
packages/libvesktop/Dockerfile
Normal file
19
packages/libvesktop/Dockerfile
Normal file
@@ -0,0 +1,19 @@
|
||||
# Dockerfile for building both x64 and arm64 on an old distro for maximum compatibility.
|
||||
|
||||
# ubuntu20 is dead but debian11 is still supported for now
|
||||
FROM debian:11
|
||||
ENV DEBIAN_FRONTEND=noninteractive
|
||||
|
||||
RUN dpkg --add-architecture arm64
|
||||
|
||||
RUN apt-get update && apt-get install -y \
|
||||
build-essential python3 curl pkg-config \
|
||||
g++-aarch64-linux-gnu libglib2.0-dev:amd64 libglib2.0-dev:arm64 \
|
||||
ca-certificates \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
RUN curl -fsSL https://deb.nodesource.com/setup_22.x | bash - \
|
||||
&& apt-get update && apt-get install -y nodejs \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
WORKDIR /src
|
||||
19
packages/libvesktop/binding.gyp
Normal file
19
packages/libvesktop/binding.gyp
Normal file
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"targets": [
|
||||
{
|
||||
"target_name": "libvesktop",
|
||||
"sources": [ "src/libvesktop.cc" ],
|
||||
"include_dirs": [
|
||||
"<!@(node -p \"require('node-addon-api').include\")"
|
||||
],
|
||||
"cflags_cc": [
|
||||
"<!(pkg-config --cflags glib-2.0 gio-2.0)",
|
||||
"-O3"
|
||||
],
|
||||
"libraries": [
|
||||
"<!@(pkg-config --libs-only-l --libs-only-other glib-2.0 gio-2.0)"
|
||||
],
|
||||
"cflags_cc!": ["-fno-exceptions"],
|
||||
}
|
||||
]
|
||||
}
|
||||
17
packages/libvesktop/build.sh
Executable file
17
packages/libvesktop/build.sh
Executable file
@@ -0,0 +1,17 @@
|
||||
#!/bin/sh
|
||||
set -e
|
||||
|
||||
docker build -t libvesktop-builder -f Dockerfile .
|
||||
|
||||
docker run --rm -v "$PWD":/src -w /src libvesktop-builder bash -c "
|
||||
set -e
|
||||
|
||||
echo '=== Building x64 ==='
|
||||
npx node-gyp rebuild --arch=x64
|
||||
mv build/Release/vesktop.node prebuilds/vesktop-x64.node
|
||||
|
||||
echo '=== Building arm64 ==='
|
||||
export CXX=aarch64-linux-gnu-g++
|
||||
npx node-gyp rebuild --arch=arm64
|
||||
mv build/Release/vesktop.node prebuilds/vesktop-arm64.node
|
||||
"
|
||||
3
packages/libvesktop/index.d.ts
vendored
Normal file
3
packages/libvesktop/index.d.ts
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
export function getAccentColor(): number | null;
|
||||
export function requestBackground(autoStart: boolean, commandLine: string[]): boolean;
|
||||
export function updateUnityLauncherCount(count: number): boolean;
|
||||
14
packages/libvesktop/package.json
Normal file
14
packages/libvesktop/package.json
Normal file
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"name": "libvesktop",
|
||||
"main": "build/Release/vesktop.node",
|
||||
"types": "index.d.ts",
|
||||
"devDependencies": {
|
||||
"node-addon-api": "^8.5.0",
|
||||
"node-gyp": "^11.4.2"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "node-gyp configure build",
|
||||
"clean": "node-gyp clean",
|
||||
"test": "npm run build && node test.js"
|
||||
}
|
||||
}
|
||||
730
packages/libvesktop/pnpm-lock.yaml
generated
Normal file
730
packages/libvesktop/pnpm-lock.yaml
generated
Normal file
@@ -0,0 +1,730 @@
|
||||
lockfileVersion: '9.0'
|
||||
|
||||
settings:
|
||||
autoInstallPeers: true
|
||||
excludeLinksFromLockfile: false
|
||||
|
||||
importers:
|
||||
|
||||
.:
|
||||
devDependencies:
|
||||
node-addon-api:
|
||||
specifier: ^8.5.0
|
||||
version: 8.5.0
|
||||
node-gyp:
|
||||
specifier: ^11.4.2
|
||||
version: 11.4.2
|
||||
|
||||
packages:
|
||||
|
||||
'@isaacs/cliui@8.0.2':
|
||||
resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==}
|
||||
engines: {node: '>=12'}
|
||||
|
||||
'@isaacs/fs-minipass@4.0.1':
|
||||
resolution: {integrity: sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==}
|
||||
engines: {node: '>=18.0.0'}
|
||||
|
||||
'@npmcli/agent@3.0.0':
|
||||
resolution: {integrity: sha512-S79NdEgDQd/NGCay6TCoVzXSj74skRZIKJcpJjC5lOq34SZzyI6MqtiiWoiVWoVrTcGjNeC4ipbh1VIHlpfF5Q==}
|
||||
engines: {node: ^18.17.0 || >=20.5.0}
|
||||
|
||||
'@npmcli/fs@4.0.0':
|
||||
resolution: {integrity: sha512-/xGlezI6xfGO9NwuJlnwz/K14qD1kCSAGtacBHnGzeAIuJGazcp45KP5NuyARXoKb7cwulAGWVsbeSxdG/cb0Q==}
|
||||
engines: {node: ^18.17.0 || >=20.5.0}
|
||||
|
||||
'@pkgjs/parseargs@0.11.0':
|
||||
resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==}
|
||||
engines: {node: '>=14'}
|
||||
|
||||
abbrev@3.0.1:
|
||||
resolution: {integrity: sha512-AO2ac6pjRB3SJmGJo+v5/aK6Omggp6fsLrs6wN9bd35ulu4cCwaAU9+7ZhXjeqHVkaHThLuzH0nZr0YpCDhygg==}
|
||||
engines: {node: ^18.17.0 || >=20.5.0}
|
||||
|
||||
agent-base@7.1.4:
|
||||
resolution: {integrity: sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==}
|
||||
engines: {node: '>= 14'}
|
||||
|
||||
ansi-regex@5.0.1:
|
||||
resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==}
|
||||
engines: {node: '>=8'}
|
||||
|
||||
ansi-regex@6.2.2:
|
||||
resolution: {integrity: sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==}
|
||||
engines: {node: '>=12'}
|
||||
|
||||
ansi-styles@4.3.0:
|
||||
resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==}
|
||||
engines: {node: '>=8'}
|
||||
|
||||
ansi-styles@6.2.3:
|
||||
resolution: {integrity: sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==}
|
||||
engines: {node: '>=12'}
|
||||
|
||||
balanced-match@1.0.2:
|
||||
resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==}
|
||||
|
||||
brace-expansion@2.0.2:
|
||||
resolution: {integrity: sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==}
|
||||
|
||||
cacache@19.0.1:
|
||||
resolution: {integrity: sha512-hdsUxulXCi5STId78vRVYEtDAjq99ICAUktLTeTYsLoTE6Z8dS0c8pWNCxwdrk9YfJeobDZc2Y186hD/5ZQgFQ==}
|
||||
engines: {node: ^18.17.0 || >=20.5.0}
|
||||
|
||||
chownr@3.0.0:
|
||||
resolution: {integrity: sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
color-convert@2.0.1:
|
||||
resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==}
|
||||
engines: {node: '>=7.0.0'}
|
||||
|
||||
color-name@1.1.4:
|
||||
resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==}
|
||||
|
||||
cross-spawn@7.0.6:
|
||||
resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==}
|
||||
engines: {node: '>= 8'}
|
||||
|
||||
debug@4.4.3:
|
||||
resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==}
|
||||
engines: {node: '>=6.0'}
|
||||
peerDependencies:
|
||||
supports-color: '*'
|
||||
peerDependenciesMeta:
|
||||
supports-color:
|
||||
optional: true
|
||||
|
||||
eastasianwidth@0.2.0:
|
||||
resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==}
|
||||
|
||||
emoji-regex@8.0.0:
|
||||
resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==}
|
||||
|
||||
emoji-regex@9.2.2:
|
||||
resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==}
|
||||
|
||||
encoding@0.1.13:
|
||||
resolution: {integrity: sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==}
|
||||
|
||||
env-paths@2.2.1:
|
||||
resolution: {integrity: sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==}
|
||||
engines: {node: '>=6'}
|
||||
|
||||
err-code@2.0.3:
|
||||
resolution: {integrity: sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA==}
|
||||
|
||||
exponential-backoff@3.1.2:
|
||||
resolution: {integrity: sha512-8QxYTVXUkuy7fIIoitQkPwGonB8F3Zj8eEO8Sqg9Zv/bkI7RJAzowee4gr81Hak/dUTpA2Z7VfQgoijjPNlUZA==}
|
||||
|
||||
fdir@6.5.0:
|
||||
resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==}
|
||||
engines: {node: '>=12.0.0'}
|
||||
peerDependencies:
|
||||
picomatch: ^3 || ^4
|
||||
peerDependenciesMeta:
|
||||
picomatch:
|
||||
optional: true
|
||||
|
||||
foreground-child@3.3.1:
|
||||
resolution: {integrity: sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==}
|
||||
engines: {node: '>=14'}
|
||||
|
||||
fs-minipass@3.0.3:
|
||||
resolution: {integrity: sha512-XUBA9XClHbnJWSfBzjkm6RvPsyg3sryZt06BEQoXcF7EK/xpGaQYJgQKDJSUH5SGZ76Y7pFx1QBnXz09rU5Fbw==}
|
||||
engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0}
|
||||
|
||||
glob@10.4.5:
|
||||
resolution: {integrity: sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==}
|
||||
hasBin: true
|
||||
|
||||
graceful-fs@4.2.11:
|
||||
resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==}
|
||||
|
||||
http-cache-semantics@4.2.0:
|
||||
resolution: {integrity: sha512-dTxcvPXqPvXBQpq5dUr6mEMJX4oIEFv6bwom3FDwKRDsuIjjJGANqhBuoAn9c1RQJIdAKav33ED65E2ys+87QQ==}
|
||||
|
||||
http-proxy-agent@7.0.2:
|
||||
resolution: {integrity: sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==}
|
||||
engines: {node: '>= 14'}
|
||||
|
||||
https-proxy-agent@7.0.6:
|
||||
resolution: {integrity: sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==}
|
||||
engines: {node: '>= 14'}
|
||||
|
||||
iconv-lite@0.6.3:
|
||||
resolution: {integrity: sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
|
||||
imurmurhash@0.1.4:
|
||||
resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==}
|
||||
engines: {node: '>=0.8.19'}
|
||||
|
||||
ip-address@10.0.1:
|
||||
resolution: {integrity: sha512-NWv9YLW4PoW2B7xtzaS3NCot75m6nK7Icdv0o3lfMceJVRfSoQwqD4wEH5rLwoKJwUiZ/rfpiVBhnaF0FK4HoA==}
|
||||
engines: {node: '>= 12'}
|
||||
|
||||
is-fullwidth-code-point@3.0.0:
|
||||
resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==}
|
||||
engines: {node: '>=8'}
|
||||
|
||||
isexe@2.0.0:
|
||||
resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==}
|
||||
|
||||
isexe@3.1.1:
|
||||
resolution: {integrity: sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ==}
|
||||
engines: {node: '>=16'}
|
||||
|
||||
jackspeak@3.4.3:
|
||||
resolution: {integrity: sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==}
|
||||
|
||||
lru-cache@10.4.3:
|
||||
resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==}
|
||||
|
||||
make-fetch-happen@14.0.3:
|
||||
resolution: {integrity: sha512-QMjGbFTP0blj97EeidG5hk/QhKQ3T4ICckQGLgz38QF7Vgbk6e6FTARN8KhKxyBbWn8R0HU+bnw8aSoFPD4qtQ==}
|
||||
engines: {node: ^18.17.0 || >=20.5.0}
|
||||
|
||||
minimatch@9.0.5:
|
||||
resolution: {integrity: sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==}
|
||||
engines: {node: '>=16 || 14 >=14.17'}
|
||||
|
||||
minipass-collect@2.0.1:
|
||||
resolution: {integrity: sha512-D7V8PO9oaz7PWGLbCACuI1qEOsq7UKfLotx/C0Aet43fCUB/wfQ7DYeq2oR/svFJGYDHPr38SHATeaj/ZoKHKw==}
|
||||
engines: {node: '>=16 || 14 >=14.17'}
|
||||
|
||||
minipass-fetch@4.0.1:
|
||||
resolution: {integrity: sha512-j7U11C5HXigVuutxebFadoYBbd7VSdZWggSe64NVdvWNBqGAiXPL2QVCehjmw7lY1oF9gOllYbORh+hiNgfPgQ==}
|
||||
engines: {node: ^18.17.0 || >=20.5.0}
|
||||
|
||||
minipass-flush@1.0.5:
|
||||
resolution: {integrity: sha512-JmQSYYpPUqX5Jyn1mXaRwOda1uQ8HP5KAT/oDSLCzt1BYRhQU0/hDtsB1ufZfEEzMZ9aAVmsBw8+FWsIXlClWw==}
|
||||
engines: {node: '>= 8'}
|
||||
|
||||
minipass-pipeline@1.2.4:
|
||||
resolution: {integrity: sha512-xuIq7cIOt09RPRJ19gdi4b+RiNvDFYe5JH+ggNvBqGqpQXcru3PcRmOZuHBKWK1Txf9+cQ+HMVN4d6z46LZP7A==}
|
||||
engines: {node: '>=8'}
|
||||
|
||||
minipass-sized@1.0.3:
|
||||
resolution: {integrity: sha512-MbkQQ2CTiBMlA2Dm/5cY+9SWFEN8pzzOXi6rlM5Xxq0Yqbda5ZQy9sU75a673FE9ZK0Zsbr6Y5iP6u9nktfg2g==}
|
||||
engines: {node: '>=8'}
|
||||
|
||||
minipass@3.3.6:
|
||||
resolution: {integrity: sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==}
|
||||
engines: {node: '>=8'}
|
||||
|
||||
minipass@7.1.2:
|
||||
resolution: {integrity: sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==}
|
||||
engines: {node: '>=16 || 14 >=14.17'}
|
||||
|
||||
minizlib@3.1.0:
|
||||
resolution: {integrity: sha512-KZxYo1BUkWD2TVFLr0MQoM8vUUigWD3LlD83a/75BqC+4qE0Hb1Vo5v1FgcfaNXvfXzr+5EhQ6ing/CaBijTlw==}
|
||||
engines: {node: '>= 18'}
|
||||
|
||||
ms@2.1.3:
|
||||
resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==}
|
||||
|
||||
negotiator@1.0.0:
|
||||
resolution: {integrity: sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==}
|
||||
engines: {node: '>= 0.6'}
|
||||
|
||||
node-addon-api@8.5.0:
|
||||
resolution: {integrity: sha512-/bRZty2mXUIFY/xU5HLvveNHlswNJej+RnxBjOMkidWfwZzgTbPG1E3K5TOxRLOR+5hX7bSofy8yf1hZevMS8A==}
|
||||
engines: {node: ^18 || ^20 || >= 21}
|
||||
|
||||
node-gyp@11.4.2:
|
||||
resolution: {integrity: sha512-3gD+6zsrLQH7DyYOUIutaauuXrcyxeTPyQuZQCQoNPZMHMMS5m4y0xclNpvYzoK3VNzuyxT6eF4mkIL4WSZ1eQ==}
|
||||
engines: {node: ^18.17.0 || >=20.5.0}
|
||||
hasBin: true
|
||||
|
||||
nopt@8.1.0:
|
||||
resolution: {integrity: sha512-ieGu42u/Qsa4TFktmaKEwM6MQH0pOWnaB3htzh0JRtx84+Mebc0cbZYN5bC+6WTZ4+77xrL9Pn5m7CV6VIkV7A==}
|
||||
engines: {node: ^18.17.0 || >=20.5.0}
|
||||
hasBin: true
|
||||
|
||||
p-map@7.0.3:
|
||||
resolution: {integrity: sha512-VkndIv2fIB99swvQoA65bm+fsmt6UNdGeIB0oxBs+WhAhdh08QA04JXpI7rbB9r08/nkbysKoya9rtDERYOYMA==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
package-json-from-dist@1.0.1:
|
||||
resolution: {integrity: sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==}
|
||||
|
||||
path-key@3.1.1:
|
||||
resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==}
|
||||
engines: {node: '>=8'}
|
||||
|
||||
path-scurry@1.11.1:
|
||||
resolution: {integrity: sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==}
|
||||
engines: {node: '>=16 || 14 >=14.18'}
|
||||
|
||||
picomatch@4.0.3:
|
||||
resolution: {integrity: sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==}
|
||||
engines: {node: '>=12'}
|
||||
|
||||
proc-log@5.0.0:
|
||||
resolution: {integrity: sha512-Azwzvl90HaF0aCz1JrDdXQykFakSSNPaPoiZ9fm5qJIMHioDZEi7OAdRwSm6rSoPtY3Qutnm3L7ogmg3dc+wbQ==}
|
||||
engines: {node: ^18.17.0 || >=20.5.0}
|
||||
|
||||
promise-retry@2.0.1:
|
||||
resolution: {integrity: sha512-y+WKFlBR8BGXnsNlIHFGPZmyDf3DFMoLhaflAnyZgV6rG6xu+JwesTo2Q9R6XwYmtmwAFCkAk3e35jEdoeh/3g==}
|
||||
engines: {node: '>=10'}
|
||||
|
||||
retry@0.12.0:
|
||||
resolution: {integrity: sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==}
|
||||
engines: {node: '>= 4'}
|
||||
|
||||
safer-buffer@2.1.2:
|
||||
resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==}
|
||||
|
||||
semver@7.7.2:
|
||||
resolution: {integrity: sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==}
|
||||
engines: {node: '>=10'}
|
||||
hasBin: true
|
||||
|
||||
shebang-command@2.0.0:
|
||||
resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==}
|
||||
engines: {node: '>=8'}
|
||||
|
||||
shebang-regex@3.0.0:
|
||||
resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==}
|
||||
engines: {node: '>=8'}
|
||||
|
||||
signal-exit@4.1.0:
|
||||
resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==}
|
||||
engines: {node: '>=14'}
|
||||
|
||||
smart-buffer@4.2.0:
|
||||
resolution: {integrity: sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==}
|
||||
engines: {node: '>= 6.0.0', npm: '>= 3.0.0'}
|
||||
|
||||
socks-proxy-agent@8.0.5:
|
||||
resolution: {integrity: sha512-HehCEsotFqbPW9sJ8WVYB6UbmIMv7kUUORIF2Nncq4VQvBfNBLibW9YZR5dlYCSUhwcD628pRllm7n+E+YTzJw==}
|
||||
engines: {node: '>= 14'}
|
||||
|
||||
socks@2.8.7:
|
||||
resolution: {integrity: sha512-HLpt+uLy/pxB+bum/9DzAgiKS8CX1EvbWxI4zlmgGCExImLdiad2iCwXT5Z4c9c3Eq8rP2318mPW2c+QbtjK8A==}
|
||||
engines: {node: '>= 10.0.0', npm: '>= 3.0.0'}
|
||||
|
||||
ssri@12.0.0:
|
||||
resolution: {integrity: sha512-S7iGNosepx9RadX82oimUkvr0Ct7IjJbEbs4mJcTxst8um95J3sDYU1RBEOvdu6oL1Wek2ODI5i4MAw+dZ6cAQ==}
|
||||
engines: {node: ^18.17.0 || >=20.5.0}
|
||||
|
||||
string-width@4.2.3:
|
||||
resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==}
|
||||
engines: {node: '>=8'}
|
||||
|
||||
string-width@5.1.2:
|
||||
resolution: {integrity: sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==}
|
||||
engines: {node: '>=12'}
|
||||
|
||||
strip-ansi@6.0.1:
|
||||
resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==}
|
||||
engines: {node: '>=8'}
|
||||
|
||||
strip-ansi@7.1.2:
|
||||
resolution: {integrity: sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==}
|
||||
engines: {node: '>=12'}
|
||||
|
||||
tar@7.5.1:
|
||||
resolution: {integrity: sha512-nlGpxf+hv0v7GkWBK2V9spgactGOp0qvfWRxUMjqHyzrt3SgwE48DIv/FhqPHJYLHpgW1opq3nERbz5Anq7n1g==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
tinyglobby@0.2.15:
|
||||
resolution: {integrity: sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==}
|
||||
engines: {node: '>=12.0.0'}
|
||||
|
||||
unique-filename@4.0.0:
|
||||
resolution: {integrity: sha512-XSnEewXmQ+veP7xX2dS5Q4yZAvO40cBN2MWkJ7D/6sW4Dg6wYBNwM1Vrnz1FhH5AdeLIlUXRI9e28z1YZi71NQ==}
|
||||
engines: {node: ^18.17.0 || >=20.5.0}
|
||||
|
||||
unique-slug@5.0.0:
|
||||
resolution: {integrity: sha512-9OdaqO5kwqR+1kVgHAhsp5vPNU0hnxRa26rBFNfNgM7M6pNtgzeBn3s/xbyCQL3dcjzOatcef6UUHpB/6MaETg==}
|
||||
engines: {node: ^18.17.0 || >=20.5.0}
|
||||
|
||||
which@2.0.2:
|
||||
resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==}
|
||||
engines: {node: '>= 8'}
|
||||
hasBin: true
|
||||
|
||||
which@5.0.0:
|
||||
resolution: {integrity: sha512-JEdGzHwwkrbWoGOlIHqQ5gtprKGOenpDHpxE9zVR1bWbOtYRyPPHMe9FaP6x61CmNaTThSkb0DAJte5jD+DmzQ==}
|
||||
engines: {node: ^18.17.0 || >=20.5.0}
|
||||
hasBin: true
|
||||
|
||||
wrap-ansi@7.0.0:
|
||||
resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==}
|
||||
engines: {node: '>=10'}
|
||||
|
||||
wrap-ansi@8.1.0:
|
||||
resolution: {integrity: sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==}
|
||||
engines: {node: '>=12'}
|
||||
|
||||
yallist@4.0.0:
|
||||
resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==}
|
||||
|
||||
yallist@5.0.0:
|
||||
resolution: {integrity: sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
snapshots:
|
||||
|
||||
'@isaacs/cliui@8.0.2':
|
||||
dependencies:
|
||||
string-width: 5.1.2
|
||||
string-width-cjs: string-width@4.2.3
|
||||
strip-ansi: 7.1.2
|
||||
strip-ansi-cjs: strip-ansi@6.0.1
|
||||
wrap-ansi: 8.1.0
|
||||
wrap-ansi-cjs: wrap-ansi@7.0.0
|
||||
|
||||
'@isaacs/fs-minipass@4.0.1':
|
||||
dependencies:
|
||||
minipass: 7.1.2
|
||||
|
||||
'@npmcli/agent@3.0.0':
|
||||
dependencies:
|
||||
agent-base: 7.1.4
|
||||
http-proxy-agent: 7.0.2
|
||||
https-proxy-agent: 7.0.6
|
||||
lru-cache: 10.4.3
|
||||
socks-proxy-agent: 8.0.5
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
'@npmcli/fs@4.0.0':
|
||||
dependencies:
|
||||
semver: 7.7.2
|
||||
|
||||
'@pkgjs/parseargs@0.11.0':
|
||||
optional: true
|
||||
|
||||
abbrev@3.0.1: {}
|
||||
|
||||
agent-base@7.1.4: {}
|
||||
|
||||
ansi-regex@5.0.1: {}
|
||||
|
||||
ansi-regex@6.2.2: {}
|
||||
|
||||
ansi-styles@4.3.0:
|
||||
dependencies:
|
||||
color-convert: 2.0.1
|
||||
|
||||
ansi-styles@6.2.3: {}
|
||||
|
||||
balanced-match@1.0.2: {}
|
||||
|
||||
brace-expansion@2.0.2:
|
||||
dependencies:
|
||||
balanced-match: 1.0.2
|
||||
|
||||
cacache@19.0.1:
|
||||
dependencies:
|
||||
'@npmcli/fs': 4.0.0
|
||||
fs-minipass: 3.0.3
|
||||
glob: 10.4.5
|
||||
lru-cache: 10.4.3
|
||||
minipass: 7.1.2
|
||||
minipass-collect: 2.0.1
|
||||
minipass-flush: 1.0.5
|
||||
minipass-pipeline: 1.2.4
|
||||
p-map: 7.0.3
|
||||
ssri: 12.0.0
|
||||
tar: 7.5.1
|
||||
unique-filename: 4.0.0
|
||||
|
||||
chownr@3.0.0: {}
|
||||
|
||||
color-convert@2.0.1:
|
||||
dependencies:
|
||||
color-name: 1.1.4
|
||||
|
||||
color-name@1.1.4: {}
|
||||
|
||||
cross-spawn@7.0.6:
|
||||
dependencies:
|
||||
path-key: 3.1.1
|
||||
shebang-command: 2.0.0
|
||||
which: 2.0.2
|
||||
|
||||
debug@4.4.3:
|
||||
dependencies:
|
||||
ms: 2.1.3
|
||||
|
||||
eastasianwidth@0.2.0: {}
|
||||
|
||||
emoji-regex@8.0.0: {}
|
||||
|
||||
emoji-regex@9.2.2: {}
|
||||
|
||||
encoding@0.1.13:
|
||||
dependencies:
|
||||
iconv-lite: 0.6.3
|
||||
optional: true
|
||||
|
||||
env-paths@2.2.1: {}
|
||||
|
||||
err-code@2.0.3: {}
|
||||
|
||||
exponential-backoff@3.1.2: {}
|
||||
|
||||
fdir@6.5.0(picomatch@4.0.3):
|
||||
optionalDependencies:
|
||||
picomatch: 4.0.3
|
||||
|
||||
foreground-child@3.3.1:
|
||||
dependencies:
|
||||
cross-spawn: 7.0.6
|
||||
signal-exit: 4.1.0
|
||||
|
||||
fs-minipass@3.0.3:
|
||||
dependencies:
|
||||
minipass: 7.1.2
|
||||
|
||||
glob@10.4.5:
|
||||
dependencies:
|
||||
foreground-child: 3.3.1
|
||||
jackspeak: 3.4.3
|
||||
minimatch: 9.0.5
|
||||
minipass: 7.1.2
|
||||
package-json-from-dist: 1.0.1
|
||||
path-scurry: 1.11.1
|
||||
|
||||
graceful-fs@4.2.11: {}
|
||||
|
||||
http-cache-semantics@4.2.0: {}
|
||||
|
||||
http-proxy-agent@7.0.2:
|
||||
dependencies:
|
||||
agent-base: 7.1.4
|
||||
debug: 4.4.3
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
https-proxy-agent@7.0.6:
|
||||
dependencies:
|
||||
agent-base: 7.1.4
|
||||
debug: 4.4.3
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
iconv-lite@0.6.3:
|
||||
dependencies:
|
||||
safer-buffer: 2.1.2
|
||||
optional: true
|
||||
|
||||
imurmurhash@0.1.4: {}
|
||||
|
||||
ip-address@10.0.1: {}
|
||||
|
||||
is-fullwidth-code-point@3.0.0: {}
|
||||
|
||||
isexe@2.0.0: {}
|
||||
|
||||
isexe@3.1.1: {}
|
||||
|
||||
jackspeak@3.4.3:
|
||||
dependencies:
|
||||
'@isaacs/cliui': 8.0.2
|
||||
optionalDependencies:
|
||||
'@pkgjs/parseargs': 0.11.0
|
||||
|
||||
lru-cache@10.4.3: {}
|
||||
|
||||
make-fetch-happen@14.0.3:
|
||||
dependencies:
|
||||
'@npmcli/agent': 3.0.0
|
||||
cacache: 19.0.1
|
||||
http-cache-semantics: 4.2.0
|
||||
minipass: 7.1.2
|
||||
minipass-fetch: 4.0.1
|
||||
minipass-flush: 1.0.5
|
||||
minipass-pipeline: 1.2.4
|
||||
negotiator: 1.0.0
|
||||
proc-log: 5.0.0
|
||||
promise-retry: 2.0.1
|
||||
ssri: 12.0.0
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
minimatch@9.0.5:
|
||||
dependencies:
|
||||
brace-expansion: 2.0.2
|
||||
|
||||
minipass-collect@2.0.1:
|
||||
dependencies:
|
||||
minipass: 7.1.2
|
||||
|
||||
minipass-fetch@4.0.1:
|
||||
dependencies:
|
||||
minipass: 7.1.2
|
||||
minipass-sized: 1.0.3
|
||||
minizlib: 3.1.0
|
||||
optionalDependencies:
|
||||
encoding: 0.1.13
|
||||
|
||||
minipass-flush@1.0.5:
|
||||
dependencies:
|
||||
minipass: 3.3.6
|
||||
|
||||
minipass-pipeline@1.2.4:
|
||||
dependencies:
|
||||
minipass: 3.3.6
|
||||
|
||||
minipass-sized@1.0.3:
|
||||
dependencies:
|
||||
minipass: 3.3.6
|
||||
|
||||
minipass@3.3.6:
|
||||
dependencies:
|
||||
yallist: 4.0.0
|
||||
|
||||
minipass@7.1.2: {}
|
||||
|
||||
minizlib@3.1.0:
|
||||
dependencies:
|
||||
minipass: 7.1.2
|
||||
|
||||
ms@2.1.3: {}
|
||||
|
||||
negotiator@1.0.0: {}
|
||||
|
||||
node-addon-api@8.5.0: {}
|
||||
|
||||
node-gyp@11.4.2:
|
||||
dependencies:
|
||||
env-paths: 2.2.1
|
||||
exponential-backoff: 3.1.2
|
||||
graceful-fs: 4.2.11
|
||||
make-fetch-happen: 14.0.3
|
||||
nopt: 8.1.0
|
||||
proc-log: 5.0.0
|
||||
semver: 7.7.2
|
||||
tar: 7.5.1
|
||||
tinyglobby: 0.2.15
|
||||
which: 5.0.0
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
nopt@8.1.0:
|
||||
dependencies:
|
||||
abbrev: 3.0.1
|
||||
|
||||
p-map@7.0.3: {}
|
||||
|
||||
package-json-from-dist@1.0.1: {}
|
||||
|
||||
path-key@3.1.1: {}
|
||||
|
||||
path-scurry@1.11.1:
|
||||
dependencies:
|
||||
lru-cache: 10.4.3
|
||||
minipass: 7.1.2
|
||||
|
||||
picomatch@4.0.3: {}
|
||||
|
||||
proc-log@5.0.0: {}
|
||||
|
||||
promise-retry@2.0.1:
|
||||
dependencies:
|
||||
err-code: 2.0.3
|
||||
retry: 0.12.0
|
||||
|
||||
retry@0.12.0: {}
|
||||
|
||||
safer-buffer@2.1.2:
|
||||
optional: true
|
||||
|
||||
semver@7.7.2: {}
|
||||
|
||||
shebang-command@2.0.0:
|
||||
dependencies:
|
||||
shebang-regex: 3.0.0
|
||||
|
||||
shebang-regex@3.0.0: {}
|
||||
|
||||
signal-exit@4.1.0: {}
|
||||
|
||||
smart-buffer@4.2.0: {}
|
||||
|
||||
socks-proxy-agent@8.0.5:
|
||||
dependencies:
|
||||
agent-base: 7.1.4
|
||||
debug: 4.4.3
|
||||
socks: 2.8.7
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
socks@2.8.7:
|
||||
dependencies:
|
||||
ip-address: 10.0.1
|
||||
smart-buffer: 4.2.0
|
||||
|
||||
ssri@12.0.0:
|
||||
dependencies:
|
||||
minipass: 7.1.2
|
||||
|
||||
string-width@4.2.3:
|
||||
dependencies:
|
||||
emoji-regex: 8.0.0
|
||||
is-fullwidth-code-point: 3.0.0
|
||||
strip-ansi: 6.0.1
|
||||
|
||||
string-width@5.1.2:
|
||||
dependencies:
|
||||
eastasianwidth: 0.2.0
|
||||
emoji-regex: 9.2.2
|
||||
strip-ansi: 7.1.2
|
||||
|
||||
strip-ansi@6.0.1:
|
||||
dependencies:
|
||||
ansi-regex: 5.0.1
|
||||
|
||||
strip-ansi@7.1.2:
|
||||
dependencies:
|
||||
ansi-regex: 6.2.2
|
||||
|
||||
tar@7.5.1:
|
||||
dependencies:
|
||||
'@isaacs/fs-minipass': 4.0.1
|
||||
chownr: 3.0.0
|
||||
minipass: 7.1.2
|
||||
minizlib: 3.1.0
|
||||
yallist: 5.0.0
|
||||
|
||||
tinyglobby@0.2.15:
|
||||
dependencies:
|
||||
fdir: 6.5.0(picomatch@4.0.3)
|
||||
picomatch: 4.0.3
|
||||
|
||||
unique-filename@4.0.0:
|
||||
dependencies:
|
||||
unique-slug: 5.0.0
|
||||
|
||||
unique-slug@5.0.0:
|
||||
dependencies:
|
||||
imurmurhash: 0.1.4
|
||||
|
||||
which@2.0.2:
|
||||
dependencies:
|
||||
isexe: 2.0.0
|
||||
|
||||
which@5.0.0:
|
||||
dependencies:
|
||||
isexe: 3.1.1
|
||||
|
||||
wrap-ansi@7.0.0:
|
||||
dependencies:
|
||||
ansi-styles: 4.3.0
|
||||
string-width: 4.2.3
|
||||
strip-ansi: 6.0.1
|
||||
|
||||
wrap-ansi@8.1.0:
|
||||
dependencies:
|
||||
ansi-styles: 6.2.3
|
||||
string-width: 5.1.2
|
||||
strip-ansi: 7.1.2
|
||||
|
||||
yallist@4.0.0: {}
|
||||
|
||||
yallist@5.0.0: {}
|
||||
BIN
packages/libvesktop/prebuilds/vesktop-arm64.node
Executable file
BIN
packages/libvesktop/prebuilds/vesktop-arm64.node
Executable file
Binary file not shown.
BIN
packages/libvesktop/prebuilds/vesktop-x64.node
Executable file
BIN
packages/libvesktop/prebuilds/vesktop-x64.node
Executable file
Binary file not shown.
269
packages/libvesktop/src/libvesktop.cc
Normal file
269
packages/libvesktop/src/libvesktop.cc
Normal file
@@ -0,0 +1,269 @@
|
||||
#include <gio/gio.h>
|
||||
#include <cstdlib>
|
||||
#include <cstdint>
|
||||
#include <iostream>
|
||||
#include <napi.h>
|
||||
#include <optional>
|
||||
#include <cmath>
|
||||
#include <memory>
|
||||
|
||||
template <typename T>
|
||||
struct GObjectDeleter
|
||||
{
|
||||
void operator()(T *obj) const
|
||||
{
|
||||
if (obj)
|
||||
g_object_unref(obj);
|
||||
}
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
using GObjectPtr = std::unique_ptr<T, GObjectDeleter<T>>;
|
||||
|
||||
struct GVariantDeleter
|
||||
{
|
||||
void operator()(GVariant *variant) const
|
||||
{
|
||||
if (variant)
|
||||
g_variant_unref(variant);
|
||||
}
|
||||
};
|
||||
|
||||
using GVariantPtr = std::unique_ptr<GVariant, GVariantDeleter>;
|
||||
|
||||
struct GErrorDeleter
|
||||
{
|
||||
void operator()(GError *error) const
|
||||
{
|
||||
if (error)
|
||||
g_error_free(error);
|
||||
}
|
||||
};
|
||||
|
||||
using GErrorPtr = std::unique_ptr<GError, GErrorDeleter>;
|
||||
|
||||
bool update_launcher_count(int count)
|
||||
{
|
||||
GError *error = nullptr;
|
||||
|
||||
const char *chromeDesktop = std::getenv("CHROME_DESKTOP");
|
||||
std::string desktop_id = std::string("application://") + (chromeDesktop ? chromeDesktop : "vesktop.desktop");
|
||||
|
||||
GObjectPtr<GDBusConnection> bus(g_bus_get_sync(G_BUS_TYPE_SESSION, nullptr, &error));
|
||||
if (!bus)
|
||||
{
|
||||
GErrorPtr error_ptr(error);
|
||||
std::cerr << "[libvesktop::update_launcher_count] Failed to connect to session bus: "
|
||||
<< (error_ptr ? error_ptr->message : "unknown error") << std::endl;
|
||||
return false;
|
||||
}
|
||||
|
||||
GVariantBuilder builder;
|
||||
g_variant_builder_init(&builder, G_VARIANT_TYPE("a{sv}"));
|
||||
g_variant_builder_add(&builder, "{sv}", "count", g_variant_new_int64(count));
|
||||
g_variant_builder_add(&builder, "{sv}", "count-visible", g_variant_new_boolean(count != 0));
|
||||
|
||||
gboolean result = g_dbus_connection_emit_signal(
|
||||
bus.get(),
|
||||
nullptr,
|
||||
"/",
|
||||
"com.canonical.Unity.LauncherEntry",
|
||||
"Update",
|
||||
g_variant_new("(sa{sv})", desktop_id.c_str(), &builder),
|
||||
&error);
|
||||
|
||||
if (!result || error)
|
||||
{
|
||||
GErrorPtr error_ptr(error);
|
||||
std::cerr << "[libvesktop::update_launcher_count] Failed to emit Update signal: "
|
||||
<< (error_ptr ? error_ptr->message : "unknown error") << std::endl;
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
std::optional<int32_t> get_accent_color()
|
||||
{
|
||||
GError *error = nullptr;
|
||||
|
||||
GObjectPtr<GDBusConnection> bus(g_bus_get_sync(G_BUS_TYPE_SESSION, nullptr, &error));
|
||||
if (!bus)
|
||||
{
|
||||
GErrorPtr error_ptr(error);
|
||||
std::cerr << "[libvesktop::get_accent_color] Failed to connect to session bus: "
|
||||
<< (error_ptr ? error_ptr->message : "unknown error") << std::endl;
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
GVariantPtr reply(g_dbus_connection_call_sync(
|
||||
bus.get(),
|
||||
"org.freedesktop.portal.Desktop",
|
||||
"/org/freedesktop/portal/desktop",
|
||||
"org.freedesktop.portal.Settings",
|
||||
"Read",
|
||||
g_variant_new("(ss)", "org.freedesktop.appearance", "accent-color"),
|
||||
nullptr,
|
||||
G_DBUS_CALL_FLAGS_NONE,
|
||||
5000,
|
||||
nullptr,
|
||||
&error));
|
||||
|
||||
if (!reply)
|
||||
{
|
||||
GErrorPtr error_ptr(error);
|
||||
std::cerr << "[libvesktop::get_accent_color] Failed to call Read: "
|
||||
<< (error_ptr ? error_ptr->message : "unknown error") << std::endl;
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
GVariant *inner_raw = nullptr;
|
||||
g_variant_get(reply.get(), "(v)", &inner_raw);
|
||||
if (!inner_raw)
|
||||
{
|
||||
std::cerr << "[libvesktop::get_accent_color] Inner variant is null" << std::endl;
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
GVariantPtr inner(inner_raw);
|
||||
|
||||
// Unwrap nested variants
|
||||
while (g_variant_is_of_type(inner.get(), G_VARIANT_TYPE_VARIANT))
|
||||
{
|
||||
GVariant *next = g_variant_get_variant(inner.get());
|
||||
inner.reset(next);
|
||||
}
|
||||
|
||||
if (!g_variant_is_of_type(inner.get(), G_VARIANT_TYPE_TUPLE) ||
|
||||
g_variant_n_children(inner.get()) < 3)
|
||||
{
|
||||
std::cerr << "[libvesktop::get_accent_color] Inner variant is not a tuple of 3 doubles" << std::endl;
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
double r = 0.0, g = 0.0, b = 0.0;
|
||||
g_variant_get(inner.get(), "(ddd)", &r, &g, &b);
|
||||
|
||||
bool discard = false;
|
||||
auto toInt = [&discard](double v) -> int
|
||||
{
|
||||
if (!std::isfinite(v) || v < 0.0 || v > 1.0)
|
||||
{
|
||||
discard = true;
|
||||
return 0;
|
||||
}
|
||||
|
||||
return static_cast<int>(std::round(v * 255.0));
|
||||
};
|
||||
|
||||
int32_t rgb = (toInt(r) << 16) | (toInt(g) << 8) | toInt(b);
|
||||
if (discard)
|
||||
return std::nullopt;
|
||||
|
||||
return rgb;
|
||||
}
|
||||
|
||||
bool request_background(bool autostart, const std::vector<std::string> &commandline)
|
||||
{
|
||||
GError *error = nullptr;
|
||||
|
||||
GObjectPtr<GDBusConnection> bus(g_bus_get_sync(G_BUS_TYPE_SESSION, nullptr, &error));
|
||||
if (!bus)
|
||||
{
|
||||
GErrorPtr error_ptr(error);
|
||||
std::cerr << "[libvesktop::request_background] Failed to connect to session bus: "
|
||||
<< (error_ptr ? error_ptr->message : "unknown error") << std::endl;
|
||||
return false;
|
||||
}
|
||||
|
||||
GVariantBuilder builder;
|
||||
g_variant_builder_init(&builder, G_VARIANT_TYPE("a{sv}"));
|
||||
g_variant_builder_add(&builder, "{sv}", "autostart", g_variant_new_boolean(autostart));
|
||||
|
||||
if (!commandline.empty())
|
||||
{
|
||||
GVariantBuilder cmd_builder;
|
||||
g_variant_builder_init(&cmd_builder, G_VARIANT_TYPE("as"));
|
||||
for (const auto &s : commandline)
|
||||
g_variant_builder_add(&cmd_builder, "s", s.c_str());
|
||||
g_variant_builder_add(&builder, "{sv}", "commandline", g_variant_builder_end(&cmd_builder));
|
||||
}
|
||||
|
||||
GVariantPtr reply(g_dbus_connection_call_sync(
|
||||
bus.get(),
|
||||
"org.freedesktop.portal.Desktop",
|
||||
"/org/freedesktop/portal/desktop",
|
||||
"org.freedesktop.portal.Background",
|
||||
"RequestBackground",
|
||||
g_variant_new("(sa{sv})", "", &builder),
|
||||
nullptr,
|
||||
G_DBUS_CALL_FLAGS_NONE,
|
||||
5000,
|
||||
nullptr,
|
||||
&error));
|
||||
|
||||
if (!reply)
|
||||
{
|
||||
GErrorPtr error_ptr(error);
|
||||
std::cerr << "[libvesktop::request_background] Failed to call RequestBackground: "
|
||||
<< (error_ptr ? error_ptr->message : "unknown error") << std::endl;
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
Napi::Value updateUnityLauncherCount(Napi::CallbackInfo const &info)
|
||||
{
|
||||
if (info.Length() < 1 || !info[0].IsNumber())
|
||||
{
|
||||
Napi::TypeError::New(info.Env(), "Expected (number)").ThrowAsJavaScriptException();
|
||||
return info.Env().Undefined();
|
||||
}
|
||||
|
||||
int count = info[0].As<Napi::Number>().Int32Value();
|
||||
bool success = update_launcher_count(count);
|
||||
return Napi::Boolean::New(info.Env(), success);
|
||||
}
|
||||
|
||||
Napi::Value getAccentColor(const Napi::CallbackInfo &info)
|
||||
{
|
||||
auto color = get_accent_color();
|
||||
if (color)
|
||||
return Napi::Number::New(info.Env(), *color);
|
||||
return info.Env().Null();
|
||||
}
|
||||
|
||||
Napi::Value RequestBackground(const Napi::CallbackInfo &info)
|
||||
{
|
||||
Napi::Env env = info.Env();
|
||||
|
||||
if (info.Length() < 2 || !info[0].IsBoolean() || !info[1].IsArray())
|
||||
{
|
||||
Napi::TypeError::New(env, "Expected (boolean, string[])").ThrowAsJavaScriptException();
|
||||
return env.Null();
|
||||
}
|
||||
|
||||
bool autostart = info[0].As<Napi::Boolean>();
|
||||
Napi::Array arr = info[1].As<Napi::Array>();
|
||||
std::vector<std::string> commandline;
|
||||
for (uint32_t i = 0; i < arr.Length(); i++)
|
||||
{
|
||||
Napi::Value v = arr.Get(i);
|
||||
if (v.IsString())
|
||||
commandline.push_back(v.As<Napi::String>().Utf8Value());
|
||||
}
|
||||
|
||||
bool ok = request_background(autostart, commandline);
|
||||
return Napi::Boolean::New(env, ok);
|
||||
}
|
||||
|
||||
Napi::Object Init(Napi::Env env, Napi::Object exports)
|
||||
{
|
||||
exports.Set("updateUnityLauncherCount", Napi::Function::New(env, updateUnityLauncherCount));
|
||||
exports.Set("getAccentColor", Napi::Function::New(env, getAccentColor));
|
||||
exports.Set("requestBackground", Napi::Function::New(env, RequestBackground));
|
||||
return exports;
|
||||
}
|
||||
|
||||
NODE_API_MODULE(libvesktop, Init)
|
||||
22
packages/libvesktop/test.js
Normal file
22
packages/libvesktop/test.js
Normal file
@@ -0,0 +1,22 @@
|
||||
/**
|
||||
* @type {typeof import(".")}
|
||||
*/
|
||||
const libVesktop = require(".");
|
||||
const test = require("node:test");
|
||||
const assert = require("node:assert/strict");
|
||||
|
||||
test("getAccentColor should return a number", () => {
|
||||
const color = libVesktop.getAccentColor();
|
||||
assert.strictEqual(typeof color, "number");
|
||||
});
|
||||
|
||||
test("updateUnityLauncherCount should return true (success)", () => {
|
||||
assert.strictEqual(libVesktop.updateUnityLauncherCount(5), true);
|
||||
assert.strictEqual(libVesktop.updateUnityLauncherCount(0), true);
|
||||
assert.strictEqual(libVesktop.updateUnityLauncherCount(10), true);
|
||||
});
|
||||
|
||||
test("requestBackground should return true (success)", () => {
|
||||
assert.strictEqual(libVesktop.requestBackground(true, ["bash"]), true);
|
||||
assert.strictEqual(libVesktop.requestBackground(false, []), true);
|
||||
});
|
||||
27
patches/arrpc@3.5.0.patch
Normal file
27
patches/arrpc@3.5.0.patch
Normal file
@@ -0,0 +1,27 @@
|
||||
diff --git a/src/process/index.js b/src/process/index.js
|
||||
index 389b0845256a34b4536d6da99edb00d17f13a6b4..f17a0ac687e9110ebfd33cb91fd2f6250d318643 100644
|
||||
--- a/src/process/index.js
|
||||
+++ b/src/process/index.js
|
||||
@@ -5,8 +5,20 @@ import fs from 'node:fs';
|
||||
import { dirname, join } from 'path';
|
||||
import { fileURLToPath } from 'url';
|
||||
|
||||
-const __dirname = dirname(fileURLToPath(import.meta.url));
|
||||
-const DetectableDB = JSON.parse(fs.readFileSync(join(__dirname, 'detectable.json'), 'utf8'));
|
||||
+const DetectableDB = require('./detectable.json');
|
||||
+DetectableDB.push(
|
||||
+ {
|
||||
+ aliases: ["Obs"],
|
||||
+ executables: [
|
||||
+ { is_launcher: false, name: "obs", os: "linux" },
|
||||
+ { is_launcher: false, name: "obs.exe", os: "win32" },
|
||||
+ { is_launcher: false, name: "obs.app", os: "darwin" }
|
||||
+ ],
|
||||
+ hook: true,
|
||||
+ id: "STREAMERMODE",
|
||||
+ name: "OBS"
|
||||
+ }
|
||||
+);
|
||||
|
||||
import * as Natives from './native/index.js';
|
||||
const Native = Natives[process.platform];
|
||||
16
patches/electron-updater.patch
Normal file
16
patches/electron-updater.patch
Normal file
@@ -0,0 +1,16 @@
|
||||
diff --git a/out/RpmUpdater.js b/out/RpmUpdater.js
|
||||
index 563187bb18cb0bd154dff6620cb62b8c8f534cd6..d91594026c2bac9cc78ef3b1183df3241d7d9624 100644
|
||||
--- a/out/RpmUpdater.js
|
||||
+++ b/out/RpmUpdater.js
|
||||
@@ -32,7 +32,10 @@ class RpmUpdater extends BaseUpdater_1.BaseUpdater {
|
||||
const sudo = this.wrapSudo();
|
||||
// pkexec doesn't want the command to be wrapped in " quotes
|
||||
const wrapper = /pkexec/i.test(sudo) ? "" : `"`;
|
||||
- const packageManager = this.spawnSyncLog("which zypper");
|
||||
+ let packageManager;
|
||||
+ try {
|
||||
+ packageManager = this.spawnSyncLog("which zypper");
|
||||
+ } catch {}
|
||||
const installerPath = this.installerPath;
|
||||
if (installerPath == null) {
|
||||
this.dispatchError(new Error("No valid update available, can't quit and install"));
|
||||
8398
pnpm-lock.yaml
generated
8398
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
24
scripts/build/addAssetsCar.mjs
Normal file
24
scripts/build/addAssetsCar.mjs
Normal file
@@ -0,0 +1,24 @@
|
||||
import { copyFile, readdir } from "fs/promises";
|
||||
|
||||
/**
|
||||
* @param {{
|
||||
* readonly appOutDir: string;
|
||||
* readonly arch: Arch;
|
||||
* readonly electronPlatformName: string;
|
||||
* readonly outDir: string;
|
||||
* readonly packager: PlatformPackager;
|
||||
* readonly targets: Target[];
|
||||
* }} context
|
||||
*/
|
||||
export async function addAssetsCar({ appOutDir }) {
|
||||
if (process.platform !== "darwin") return;
|
||||
|
||||
const appName = (await readdir(appOutDir)).find(item => item.endsWith(".app"));
|
||||
|
||||
if (!appName) {
|
||||
console.warn(`Could not find .app directory in ${appOutDir}. Skipping adding assets.car`);
|
||||
return;
|
||||
}
|
||||
|
||||
await copyFile("build/Assets.car", `${appOutDir}/${appName}/Contents/Resources/Assets.car`);
|
||||
}
|
||||
5
scripts/build/afterPack.mjs
Normal file
5
scripts/build/afterPack.mjs
Normal file
@@ -0,0 +1,5 @@
|
||||
import { addAssetsCar } from "./addAssetsCar.mjs";
|
||||
|
||||
export default async function afterPack(context) {
|
||||
await addAssetsCar(context);
|
||||
}
|
||||
5
scripts/build/beforePack.mjs
Normal file
5
scripts/build/beforePack.mjs
Normal file
@@ -0,0 +1,5 @@
|
||||
import { applyAppImageSandboxFix } from "./sandboxFix.mjs";
|
||||
|
||||
export default async function beforePack() {
|
||||
await applyAppImageSandboxFix();
|
||||
}
|
||||
@@ -1,13 +1,14 @@
|
||||
/*
|
||||
* SPDX-License-Identifier: GPL-3.0
|
||||
* Vesktop, a desktop app aiming to give you a snappier Discord Experience
|
||||
* Copyright (c) 2023 Vendicated and Vencord contributors
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { BuildContext, BuildOptions, context } from "esbuild";
|
||||
import { copyFile } from "fs/promises";
|
||||
|
||||
import vencordDep from "./vencordDep.mjs";
|
||||
import { includeDirPlugin } from "./includeDirPlugin.mts";
|
||||
|
||||
const isDev = process.argv.includes("--dev");
|
||||
|
||||
@@ -24,6 +25,9 @@ const NodeCommonOpts: BuildOptions = {
|
||||
platform: "node",
|
||||
external: ["electron"],
|
||||
target: ["esnext"],
|
||||
loader: {
|
||||
".node": "file"
|
||||
},
|
||||
define: {
|
||||
IS_DEV: JSON.stringify(isDev)
|
||||
}
|
||||
@@ -49,25 +53,58 @@ async function copyVenmic() {
|
||||
]).catch(() => console.warn("Failed to copy venmic. Building without venmic support"));
|
||||
}
|
||||
|
||||
async function copyLibVesktop() {
|
||||
if (process.platform !== "linux") return;
|
||||
|
||||
try {
|
||||
await copyFile(
|
||||
"./packages/libvesktop/build/Release/vesktop.node",
|
||||
`./static/dist/libvesktop-${process.arch}.node`
|
||||
);
|
||||
console.log("Using local libvesktop build");
|
||||
} catch {
|
||||
console.log(
|
||||
"Using prebuilt libvesktop binaries. Run `pnpm buildLibVesktop` and build again to build from source - see README.md for more details"
|
||||
);
|
||||
return Promise.all([
|
||||
copyFile("./packages/libvesktop/prebuilds/vesktop-x64.node", "./static/dist/libvesktop-x64.node"),
|
||||
copyFile("./packages/libvesktop/prebuilds/vesktop-arm64.node", "./static/dist/libvesktop-arm64.node")
|
||||
]).catch(() => console.warn("Failed to copy libvesktop. Building without libvesktop support"));
|
||||
}
|
||||
}
|
||||
|
||||
await Promise.all([
|
||||
copyVenmic(),
|
||||
copyLibVesktop(),
|
||||
createContext({
|
||||
...NodeCommonOpts,
|
||||
entryPoints: ["src/main/index.ts"],
|
||||
outfile: "dist/js/main.js",
|
||||
footer: { js: "//# sourceURL=VCDMain" }
|
||||
footer: { js: "//# sourceURL=VesktopMain" }
|
||||
}),
|
||||
createContext({
|
||||
...NodeCommonOpts,
|
||||
entryPoints: ["src/main/arrpc/worker.ts"],
|
||||
outfile: "dist/js/arRpcWorker.js",
|
||||
footer: { js: "//# sourceURL=VesktopArRpcWorker" }
|
||||
}),
|
||||
createContext({
|
||||
...NodeCommonOpts,
|
||||
entryPoints: ["src/preload/index.ts"],
|
||||
outfile: "dist/js/preload.js",
|
||||
footer: { js: "//# sourceURL=VCDPreload" }
|
||||
footer: { js: "//# sourceURL=VesktopPreload" }
|
||||
}),
|
||||
createContext({
|
||||
...NodeCommonOpts,
|
||||
entryPoints: ["src/updater/preload.ts"],
|
||||
entryPoints: ["src/preload/splash.ts"],
|
||||
outfile: "dist/js/splashPreload.js",
|
||||
footer: { js: "//# sourceURL=VesktopSplashPreload" }
|
||||
}),
|
||||
createContext({
|
||||
...NodeCommonOpts,
|
||||
entryPoints: ["src/preload/updater.ts"],
|
||||
outfile: "dist/js/updaterPreload.js",
|
||||
footer: { js: "//# sourceURL=VCDUpdaterPreload" }
|
||||
footer: { js: "//# sourceURL=VesktopUpdaterPreload" }
|
||||
}),
|
||||
createContext({
|
||||
...CommonOpts,
|
||||
@@ -78,11 +115,9 @@ await Promise.all([
|
||||
inject: ["./scripts/build/injectReact.mjs"],
|
||||
jsxFactory: "VencordCreateElement",
|
||||
jsxFragment: "VencordFragment",
|
||||
// Work around https://github.com/evanw/esbuild/issues/2460
|
||||
tsconfig: "./scripts/build/tsconfig.esbuild.json",
|
||||
external: ["@vencord/types/*"],
|
||||
plugins: [vencordDep],
|
||||
footer: { js: "//# sourceURL=VCDRenderer" }
|
||||
plugins: [vencordDep, includeDirPlugin("patches", "src/renderer/patches")],
|
||||
footer: { js: "//# sourceURL=VesktopRenderer" }
|
||||
})
|
||||
]);
|
||||
|
||||
|
||||
25
scripts/build/includeDirPlugin.mts
Normal file
25
scripts/build/includeDirPlugin.mts
Normal file
@@ -0,0 +1,25 @@
|
||||
import { Plugin } from "esbuild";
|
||||
import { readdir } from "fs/promises";
|
||||
|
||||
const makeImportAllCode = (files: string[]) =>
|
||||
files.map(f => `require("./${f.replace(/\.[cm]?[tj]sx?$/, "")}")`).join("\n");
|
||||
|
||||
const makeImportDirRecursiveCode = (dir: string) => readdir(dir).then(files => makeImportAllCode(files));
|
||||
|
||||
export function includeDirPlugin(namespace: string, path: string): Plugin {
|
||||
return {
|
||||
name: `include-dir-plugin:${namespace}`,
|
||||
setup(build) {
|
||||
const filter = new RegExp(`^__${namespace}__$`);
|
||||
|
||||
build.onResolve({ filter }, args => ({ path: args.path, namespace }));
|
||||
|
||||
build.onLoad({ filter, namespace }, async args => {
|
||||
return {
|
||||
contents: await makeImportDirRecursiveCode(path),
|
||||
resolveDir: path
|
||||
};
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -1,9 +1,3 @@
|
||||
/*
|
||||
* SPDX-License-Identifier: GPL-3.0
|
||||
* Vesktop, a desktop app aiming to give you a snappier Discord Experience
|
||||
* Copyright (c) 2023 Vendicated and Vencord contributors
|
||||
*/
|
||||
|
||||
export const VencordFragment = /* #__PURE__*/ Symbol.for("react.fragment");
|
||||
export let VencordCreateElement = (...args) =>
|
||||
(VencordCreateElement = Vencord.Webpack.Common.React.createElement)(...args);
|
||||
|
||||
75
scripts/build/sandboxFix.mjs
Normal file
75
scripts/build/sandboxFix.mjs
Normal file
@@ -0,0 +1,75 @@
|
||||
/*
|
||||
* Vesktop, a desktop app aiming to give you a snappier Discord Experience
|
||||
* Copyright (c) 2023 Vendicated and Vencord contributors
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
// Based on https://github.com/gergof/electron-builder-sandbox-fix/blob/master/lib/index.js
|
||||
|
||||
import fs from "fs/promises";
|
||||
import path from "path";
|
||||
import AppImageTarget from "app-builder-lib/out/targets/AppImageTarget.js";
|
||||
|
||||
let isApplied = false;
|
||||
|
||||
export async function applyAppImageSandboxFix() {
|
||||
if (process.platform !== "linux") {
|
||||
// this fix is only required on linux
|
||||
return;
|
||||
}
|
||||
|
||||
if (isApplied) return;
|
||||
isApplied = true;
|
||||
|
||||
const oldBuildMethod = AppImageTarget.default.prototype.build;
|
||||
AppImageTarget.default.prototype.build = async function (...args) {
|
||||
console.log("Running AppImage builder hook", args);
|
||||
const oldPath = args[0];
|
||||
const newPath = oldPath + "-appimage-sandbox-fix";
|
||||
// just in case
|
||||
try {
|
||||
await fs.rm(newPath, {
|
||||
recursive: true
|
||||
});
|
||||
} catch {}
|
||||
|
||||
console.log("Copying to apply appimage fix", oldPath, newPath);
|
||||
await fs.cp(oldPath, newPath, {
|
||||
recursive: true
|
||||
});
|
||||
args[0] = newPath;
|
||||
|
||||
const executable = path.join(newPath, this.packager.executableName);
|
||||
|
||||
const loaderScript = `
|
||||
#!/usr/bin/env bash
|
||||
|
||||
SCRIPT_DIR="$( cd "$( dirname "\${BASH_SOURCE[0]}" )" && pwd )"
|
||||
IS_STEAMOS=0
|
||||
|
||||
if [[ "$SteamOS" == "1" && "$SteamGamepadUI" == "1" ]]; then
|
||||
echo "Running Vesktop on SteamOS, disabling sandbox"
|
||||
IS_STEAMOS=1
|
||||
fi
|
||||
|
||||
exec "$SCRIPT_DIR/${this.packager.executableName}.bin" "$([ "$IS_STEAMOS" == 1 ] && echo '--no-sandbox')" "$@"
|
||||
`.trim();
|
||||
|
||||
try {
|
||||
await fs.rename(executable, executable + ".bin");
|
||||
await fs.writeFile(executable, loaderScript);
|
||||
await fs.chmod(executable, 0o755);
|
||||
} catch (e) {
|
||||
console.error("failed to create loder for sandbox fix: " + e.message);
|
||||
throw new Error("Failed to create loader for sandbox fix");
|
||||
}
|
||||
|
||||
const ret = await oldBuildMethod.apply(this, args);
|
||||
|
||||
await fs.rm(newPath, {
|
||||
recursive: true
|
||||
});
|
||||
|
||||
return ret;
|
||||
};
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
// Work around https://github.com/evanw/esbuild/issues/2460
|
||||
{
|
||||
"extends": "../../tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"jsx": "react"
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
/*
|
||||
* SPDX-License-Identifier: GPL-3.0
|
||||
* Vesktop, a desktop app aiming to give you a snappier Discord Experience
|
||||
* Copyright (c) 2023 Vendicated and Vencord contributors
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { globalExternalsWithRegExp } from "@fal-works/esbuild-plugin-global-externals";
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* SPDX-License-Identifier: GPL-3.0
|
||||
* Vesktop, a desktop app aiming to give you a snappier Discord Experience
|
||||
* Copyright (c) 2023 Vendicated and Vencord contributors
|
||||
* Copyright (c) {year} {author}
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
@@ -8,4 +8,4 @@ import "./utils/dotenv";
|
||||
|
||||
import { spawnNodeModuleBin } from "./utils/spawn.mjs";
|
||||
|
||||
spawnNodeModuleBin("electron", [".", ...(process.env.ELECTRON_LAUNCH_FLAGS?.split(" ") ?? [])]);
|
||||
spawnNodeModuleBin("electron", [process.cwd(), ...(process.env.ELECTRON_LAUNCH_FLAGS?.split(" ") ?? [])]);
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/*
|
||||
* SPDX-License-Identifier: GPL-3.0
|
||||
* Vesktop, a desktop app aiming to give you a snappier Discord Experience
|
||||
* Copyright (c) 2023 Vendicated and Vencord contributors
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import "./start";
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/*
|
||||
* SPDX-License-Identifier: GPL-3.0
|
||||
* Vesktop, a desktop app aiming to give you a snappier Discord Experience
|
||||
* Copyright (c) 2023 Vendicated and Vencord contributors
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { config } from "dotenv";
|
||||
|
||||
98
scripts/utils/generateMeta.mts
Normal file
98
scripts/utils/generateMeta.mts
Normal file
@@ -0,0 +1,98 @@
|
||||
/*
|
||||
* Vesktop, a desktop app aiming to give you a snappier Discord Experience
|
||||
* Copyright (c) 2023 Vendicated and Vencord contributors
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { promises as fs } from "node:fs";
|
||||
import { mkdir } from "node:fs/promises";
|
||||
import { DOMParser, XMLSerializer } from "@xmldom/xmldom";
|
||||
import xmlFormat from "xml-formatter";
|
||||
|
||||
function generateDescription(description: string, descriptionNode: Element) {
|
||||
const lines = description.replace(/\r/g, "").split("\n");
|
||||
let currentList: Element | null = null;
|
||||
|
||||
for (let i = 0; i < lines.length; i++) {
|
||||
const line = lines[i];
|
||||
|
||||
if (line.includes("New Contributors")) {
|
||||
// we're done, don't parse any more since the new contributors section is the last one
|
||||
break;
|
||||
}
|
||||
|
||||
if (line.startsWith("## ")) {
|
||||
const pNode = descriptionNode.ownerDocument.createElement("p");
|
||||
pNode.textContent = line.slice(3);
|
||||
descriptionNode.appendChild(pNode);
|
||||
} else if (line.startsWith("* ")) {
|
||||
const liNode = descriptionNode.ownerDocument.createElement("li");
|
||||
liNode.textContent = line.slice(2).split("in https://github.com")[0].trim(); // don't include links to github
|
||||
|
||||
if (!currentList) {
|
||||
currentList = descriptionNode.ownerDocument.createElement("ul");
|
||||
}
|
||||
|
||||
currentList.appendChild(liNode);
|
||||
}
|
||||
|
||||
if (currentList && !lines[i + 1].startsWith("* ")) {
|
||||
descriptionNode.appendChild(currentList);
|
||||
currentList = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const latestReleaseInformation = await fetch("https://api.github.com/repos/Vencord/Vesktop/releases/latest", {
|
||||
headers: {
|
||||
Accept: "application/vnd.github+json",
|
||||
"X-Github-Api-Version": "2022-11-28"
|
||||
}
|
||||
}).then(res => res.json());
|
||||
|
||||
const metaInfo = await fetch(
|
||||
"https://github.com/Vencord/Vesktop/releases/latest/download/dev.vencord.Vesktop.metainfo.xml"
|
||||
).then(res => res.text());
|
||||
|
||||
const parser = new DOMParser().parseFromString(metaInfo, "text/xml");
|
||||
|
||||
const releaseList = parser.getElementsByTagName("releases")[0];
|
||||
|
||||
for (let i = 0; i < releaseList.childNodes.length; i++) {
|
||||
const release = releaseList.childNodes[i] as Element;
|
||||
|
||||
if (release.nodeType === 1 && release.getAttribute("version") === latestReleaseInformation.name) {
|
||||
console.log("Latest release already added, nothing to be done");
|
||||
process.exit(0);
|
||||
}
|
||||
}
|
||||
|
||||
const release = parser.createElement("release");
|
||||
release.setAttribute("version", latestReleaseInformation.name);
|
||||
release.setAttribute("date", latestReleaseInformation.published_at.split("T")[0]);
|
||||
release.setAttribute("type", "stable");
|
||||
|
||||
const releaseUrl = parser.createElement("url");
|
||||
releaseUrl.textContent = latestReleaseInformation.html_url;
|
||||
|
||||
release.appendChild(releaseUrl);
|
||||
|
||||
const description = parser.createElement("description");
|
||||
|
||||
// we're not using a full markdown parser here since we don't have a lot of formatting options to begin with
|
||||
generateDescription(latestReleaseInformation.body, description);
|
||||
|
||||
release.appendChild(description);
|
||||
|
||||
releaseList.insertBefore(release, releaseList.childNodes[0]);
|
||||
|
||||
const output = xmlFormat(new XMLSerializer().serializeToString(parser), {
|
||||
lineSeparator: "\n",
|
||||
collapseContent: true,
|
||||
indentation: " "
|
||||
});
|
||||
|
||||
await mkdir("./dist", { recursive: true });
|
||||
await fs.writeFile("./dist/dev.vencord.Vesktop.metainfo.xml", output, "utf-8");
|
||||
|
||||
console.log("Updated meta information written to ./dist/dev.vencord.Vesktop.metainfo.xml");
|
||||
@@ -1,7 +1,7 @@
|
||||
/*
|
||||
* SPDX-License-Identifier: GPL-3.0
|
||||
* Vesktop, a desktop app aiming to give you a snappier Discord Experience
|
||||
* Copyright (c) 2023 Vendicated and Vencord contributors
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { spawn as spaaawn, SpawnOptions } from "child_process";
|
||||
|
||||
4
src/globals.d.ts
vendored
4
src/globals.d.ts
vendored
@@ -1,13 +1,13 @@
|
||||
/*
|
||||
* SPDX-License-Identifier: GPL-3.0
|
||||
* Vesktop, a desktop app aiming to give you a snappier Discord Experience
|
||||
* Copyright (c) 2023 Vendicated and Vencord contributors
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
declare global {
|
||||
export var VesktopNative: typeof import("preload/VesktopNative").VesktopNative;
|
||||
export var Vesktop: typeof import("renderer/index");
|
||||
export var VCDP: any;
|
||||
export var VesktopPatchGlobals: any;
|
||||
|
||||
export var IS_DEV: boolean;
|
||||
}
|
||||
|
||||
@@ -1,28 +1,32 @@
|
||||
/*
|
||||
* SPDX-License-Identifier: GPL-3.0
|
||||
* Vesktop, a desktop app aiming to give you a snappier Discord Experience
|
||||
* Copyright (c) 2023 Vendicated and Vencord contributors
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { BrowserWindow } from "electron";
|
||||
import { join } from "path";
|
||||
import { ICON_PATH, VIEW_DIR } from "shared/paths";
|
||||
import { app, BrowserWindow } from "electron";
|
||||
|
||||
import { makeLinksOpenExternally } from "./utils/makeLinksOpenExternally";
|
||||
import { loadView } from "./vesktopStatic";
|
||||
|
||||
export async function createAboutWindow() {
|
||||
const height = 750;
|
||||
const width = height * (4 / 3);
|
||||
|
||||
export function createAboutWindow() {
|
||||
const about = new BrowserWindow({
|
||||
center: true,
|
||||
autoHideMenuBar: true,
|
||||
icon: ICON_PATH,
|
||||
webPreferences: {
|
||||
preload: join(__dirname, "updaterPreload.js")
|
||||
}
|
||||
height,
|
||||
width
|
||||
});
|
||||
|
||||
makeLinksOpenExternally(about);
|
||||
|
||||
about.loadFile(join(VIEW_DIR, "about.html"));
|
||||
const data = new URLSearchParams({
|
||||
APP_VERSION: app.getVersion()
|
||||
});
|
||||
|
||||
loadView(about, "about.html", data);
|
||||
|
||||
return about;
|
||||
}
|
||||
|
||||
@@ -1,13 +1,17 @@
|
||||
/*
|
||||
* SPDX-License-Identifier: GPL-3.0
|
||||
* Vesktop, a desktop app aiming to give you a snappier Discord Experience
|
||||
* Copyright (c) 2023 Vendicated and Vencord contributors
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { app, NativeImage, nativeImage } from "electron";
|
||||
import { join } from "path";
|
||||
import { BADGE_DIR } from "shared/paths";
|
||||
|
||||
import { updateUnityLauncherCount } from "./dbus";
|
||||
import { AppEvents } from "./events";
|
||||
import { mainWin } from "./mainWindow";
|
||||
|
||||
const imgCache = new Map<number, NativeImage>();
|
||||
function loadBadge(index: number) {
|
||||
const cached = imgCache.get(index);
|
||||
@@ -21,18 +25,24 @@ function loadBadge(index: number) {
|
||||
|
||||
let lastIndex: null | number = -1;
|
||||
|
||||
/**
|
||||
* -1 = show unread indicator
|
||||
* 0 = clear
|
||||
*/
|
||||
export function setBadgeCount(count: number) {
|
||||
AppEvents.emit("setTrayVariant", count !== 0 ? "trayUnread" : "tray");
|
||||
|
||||
switch (process.platform) {
|
||||
case "linux":
|
||||
if (count === -1) count = 0;
|
||||
app.setBadgeCount(count);
|
||||
updateUnityLauncherCount(count);
|
||||
break;
|
||||
case "darwin":
|
||||
if (count === 0) {
|
||||
app.dock.setBadge("");
|
||||
app.dock!.setBadge("");
|
||||
break;
|
||||
}
|
||||
app.dock.setBadge(count === -1 ? "•" : count.toString());
|
||||
app.dock!.setBadge(count === -1 ? "•" : count.toString());
|
||||
break;
|
||||
case "win32":
|
||||
const [index, description] = getBadgeIndexAndDescription(count);
|
||||
@@ -40,8 +50,6 @@ export function setBadgeCount(count: number) {
|
||||
|
||||
lastIndex = index;
|
||||
|
||||
// circular import shenanigans
|
||||
const { mainWin } = require("./mainWindow") as typeof import("./mainWindow");
|
||||
mainWin.setOverlayIcon(index === null ? null : loadBadge(index), description);
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -1,38 +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 Server from "arrpc";
|
||||
import { IpcEvents } from "shared/IpcEvents";
|
||||
|
||||
import { mainWin } from "./mainWindow";
|
||||
import { Settings } from "./settings";
|
||||
|
||||
let server: any;
|
||||
|
||||
const inviteCodeRegex = /^(\w|-)+$/;
|
||||
|
||||
export async function initArRPC() {
|
||||
if (server || !Settings.store.arRPC) return;
|
||||
|
||||
try {
|
||||
server = await new Server();
|
||||
server.on("activity", (data: any) => mainWin.webContents.send(IpcEvents.ARRPC_ACTIVITY, JSON.stringify(data)));
|
||||
server.on("invite", (invite: string, callback: (valid: boolean) => void) => {
|
||||
invite = String(invite);
|
||||
if (!inviteCodeRegex.test(invite)) return callback(false);
|
||||
|
||||
mainWin.webContents
|
||||
// Safety: Result of JSON.stringify should always be safe to equal
|
||||
// Also, just to be super super safe, invite is regex validated above
|
||||
.executeJavaScript(`Vesktop.openInviteModal(${JSON.stringify(invite)})`)
|
||||
.then(callback);
|
||||
});
|
||||
} catch (e) {
|
||||
console.error("Failed to start arRPC server", e);
|
||||
}
|
||||
}
|
||||
|
||||
Settings.addChangeListener("arRPC", initArRPC);
|
||||
77
src/main/arrpc/index.ts
Normal file
77
src/main/arrpc/index.ts
Normal file
@@ -0,0 +1,77 @@
|
||||
/*
|
||||
* Vesktop, a desktop app aiming to give you a snappier Discord Experience
|
||||
* Copyright (c) 2023 Vendicated and Vencord contributors
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { resolve } from "path";
|
||||
import { IpcCommands } from "shared/IpcEvents";
|
||||
import { MessageChannel, Worker } from "worker_threads";
|
||||
|
||||
import { sendRendererCommand } from "../ipcCommands";
|
||||
import { Settings } from "../settings";
|
||||
import { ArRpcEvent, ArRpcHostEvent } from "./types";
|
||||
|
||||
let worker: Worker;
|
||||
|
||||
const inviteCodeRegex = /^(\w|-)+$/;
|
||||
|
||||
export async function initArRPC() {
|
||||
if (worker || !Settings.store.arRPC) return;
|
||||
|
||||
try {
|
||||
const { port1: hostPort, port2: workerPort } = new MessageChannel();
|
||||
|
||||
worker = new Worker(resolve(__dirname, "./arRpcWorker.js"), {
|
||||
workerData: {
|
||||
workerPort
|
||||
},
|
||||
transferList: [workerPort]
|
||||
});
|
||||
|
||||
hostPort.on("message", async ({ type, nonce, data }: ArRpcEvent) => {
|
||||
switch (type) {
|
||||
case "activity": {
|
||||
sendRendererCommand(IpcCommands.RPC_ACTIVITY, data);
|
||||
break;
|
||||
}
|
||||
|
||||
case "invite": {
|
||||
const invite = String(data);
|
||||
|
||||
const response: ArRpcHostEvent = {
|
||||
type: "ack-invite",
|
||||
nonce,
|
||||
data: false
|
||||
};
|
||||
|
||||
if (!inviteCodeRegex.test(invite)) {
|
||||
return hostPort.postMessage(response);
|
||||
}
|
||||
|
||||
response.data = await sendRendererCommand(IpcCommands.RPC_INVITE, invite).catch(() => false);
|
||||
|
||||
hostPort.postMessage(response);
|
||||
break;
|
||||
}
|
||||
|
||||
case "link": {
|
||||
const response: ArRpcHostEvent = {
|
||||
type: "ack-link",
|
||||
nonce: nonce,
|
||||
data: false
|
||||
};
|
||||
|
||||
response.data = await sendRendererCommand(IpcCommands.RPC_DEEP_LINK, data).catch(() => false);
|
||||
|
||||
hostPort.postMessage(response);
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
} catch (e) {
|
||||
console.error("Failed to start arRPC server", e);
|
||||
}
|
||||
}
|
||||
|
||||
Settings.addChangeListener("arRPC", initArRPC);
|
||||
38
src/main/arrpc/types.ts
Normal file
38
src/main/arrpc/types.ts
Normal file
@@ -0,0 +1,38 @@
|
||||
/*
|
||||
* Vesktop, a desktop app aiming to give you a snappier Discord Experience
|
||||
* Copyright (c) 2025 Vendicated and Vesktop contributors
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
export type ArRpcEvent = ArRpcActivityEvent | ArRpcInviteEvent | ArRpcLinkEvent;
|
||||
export type ArRpcHostEvent = ArRpcHostAckInviteEvent | ArRpcHostAckLinkEvent;
|
||||
|
||||
export interface ArRpcActivityEvent {
|
||||
type: "activity";
|
||||
nonce: string;
|
||||
data: string;
|
||||
}
|
||||
|
||||
export interface ArRpcInviteEvent {
|
||||
type: "invite";
|
||||
nonce: string;
|
||||
data: string;
|
||||
}
|
||||
|
||||
export interface ArRpcLinkEvent {
|
||||
type: "link";
|
||||
nonce: string;
|
||||
data: any;
|
||||
}
|
||||
|
||||
export interface ArRpcHostAckInviteEvent {
|
||||
type: "ack-invite";
|
||||
nonce: string;
|
||||
data: boolean;
|
||||
}
|
||||
|
||||
export interface ArRpcHostAckLinkEvent {
|
||||
type: "ack-link";
|
||||
nonce: string;
|
||||
data: boolean;
|
||||
}
|
||||
73
src/main/arrpc/worker.ts
Normal file
73
src/main/arrpc/worker.ts
Normal file
@@ -0,0 +1,73 @@
|
||||
/*
|
||||
* Vesktop, a desktop app aiming to give you a snappier Discord Experience
|
||||
* Copyright (c) 2025 Vendicated and Vesktop contributors
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import Server from "arrpc";
|
||||
import { randomUUID } from "crypto";
|
||||
import { MessagePort, workerData } from "worker_threads";
|
||||
|
||||
import { ArRpcEvent, ArRpcHostEvent } from "./types";
|
||||
|
||||
let server: any;
|
||||
|
||||
type InviteCallback = (valid: boolean) => void;
|
||||
type LinkCallback = InviteCallback;
|
||||
|
||||
const inviteCallbacks = new Map<string, InviteCallback>();
|
||||
const linkCallbacks = new Map<string, LinkCallback>();
|
||||
|
||||
(async function () {
|
||||
const { workerPort } = workerData as { workerPort: MessagePort };
|
||||
|
||||
server = await new Server();
|
||||
|
||||
server.on("activity", (data: any) => {
|
||||
const event: ArRpcEvent = {
|
||||
type: "activity",
|
||||
data: JSON.stringify(data),
|
||||
nonce: randomUUID()
|
||||
};
|
||||
workerPort.postMessage(event);
|
||||
});
|
||||
|
||||
server.on("invite", (invite: string, callback: InviteCallback) => {
|
||||
const nonce = randomUUID();
|
||||
inviteCallbacks.set(nonce, callback);
|
||||
|
||||
const event: ArRpcEvent = {
|
||||
type: "invite",
|
||||
data: invite,
|
||||
nonce
|
||||
};
|
||||
workerPort.postMessage(event);
|
||||
});
|
||||
|
||||
server.on("link", async (data: any, callback: LinkCallback) => {
|
||||
const nonce = randomUUID();
|
||||
linkCallbacks.set(nonce, callback);
|
||||
|
||||
const event: ArRpcEvent = {
|
||||
type: "link",
|
||||
data,
|
||||
nonce
|
||||
};
|
||||
workerPort.postMessage(event);
|
||||
});
|
||||
|
||||
workerPort.on("message", (e: ArRpcHostEvent) => {
|
||||
switch (e.type) {
|
||||
case "ack-invite": {
|
||||
inviteCallbacks.get(e.nonce)?.(e.data);
|
||||
inviteCallbacks.delete(e.nonce);
|
||||
break;
|
||||
}
|
||||
case "ack-link": {
|
||||
linkCallbacks.get(e.nonce)?.(e.data);
|
||||
linkCallbacks.delete(e.nonce);
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
})();
|
||||
@@ -1,12 +1,18 @@
|
||||
/*
|
||||
* SPDX-License-Identifier: GPL-3.0
|
||||
* Vesktop, a desktop app aiming to give you a snappier Discord Experience
|
||||
* Copyright (c) 2023 Vendicated and Vencord contributors
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { app } from "electron";
|
||||
import { existsSync, mkdirSync, rmSync, writeFileSync } from "fs";
|
||||
import { join } from "path";
|
||||
import { stripIndent } from "shared/utils/text";
|
||||
|
||||
import { IS_FLATPAK } from "./constants";
|
||||
import { requestBackground } from "./dbus";
|
||||
import { Settings, State } from "./settings";
|
||||
import { escapeDesktopFileArgument } from "./utils/desktopFileEscape";
|
||||
|
||||
interface AutoStart {
|
||||
isEnabled(): boolean;
|
||||
@@ -14,24 +20,30 @@ interface AutoStart {
|
||||
disable(): void;
|
||||
}
|
||||
|
||||
function makeAutoStartLinux(): AutoStart {
|
||||
function getEscapedCommandLine() {
|
||||
const args = process.argv.map(escapeDesktopFileArgument);
|
||||
if (Settings.store.autoStartMinimized) args.push("--start-minimized");
|
||||
return args;
|
||||
}
|
||||
|
||||
function makeAutoStartLinuxDesktop(): AutoStart {
|
||||
const configDir = process.env.XDG_CONFIG_HOME || join(process.env.HOME!, ".config");
|
||||
const dir = join(configDir, "autostart");
|
||||
const file = join(dir, "vencord.desktop");
|
||||
const file = join(dir, "vesktop.desktop");
|
||||
|
||||
return {
|
||||
isEnabled: () => existsSync(file),
|
||||
enable() {
|
||||
const desktopFile = `
|
||||
[Desktop Entry]
|
||||
Type=Application
|
||||
Version=1.0
|
||||
Name=Vencord
|
||||
Comment=Vencord autostart script
|
||||
Exec=${process.execPath}
|
||||
Terminal=false
|
||||
StartupNotify=false
|
||||
`.trim();
|
||||
const desktopFile = stripIndent`
|
||||
[Desktop Entry]
|
||||
Type=Application
|
||||
Name=Vesktop
|
||||
Comment=Vesktop autostart script
|
||||
Exec=${getEscapedCommandLine().join(" ")}
|
||||
StartupNotify=false
|
||||
Terminal=false
|
||||
Icon=vesktop
|
||||
`;
|
||||
|
||||
mkdirSync(dir, { recursive: true });
|
||||
writeFileSync(file, desktopFile);
|
||||
@@ -40,10 +52,49 @@ StartupNotify=false
|
||||
};
|
||||
}
|
||||
|
||||
function makeAutoStartLinuxPortal() {
|
||||
return {
|
||||
isEnabled: () => State.store.linuxAutoStartEnabled === true,
|
||||
enable() {
|
||||
const success = requestBackground(true, getEscapedCommandLine());
|
||||
if (success) {
|
||||
State.store.linuxAutoStartEnabled = true;
|
||||
}
|
||||
return success;
|
||||
},
|
||||
disable() {
|
||||
const success = requestBackground(false, []);
|
||||
if (success) {
|
||||
State.store.linuxAutoStartEnabled = false;
|
||||
}
|
||||
return success;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
const autoStartWindowsMac: AutoStart = {
|
||||
isEnabled: () => app.getLoginItemSettings().openAtLogin,
|
||||
enable: () => app.setLoginItemSettings({ openAtLogin: true }),
|
||||
enable: () =>
|
||||
app.setLoginItemSettings({
|
||||
openAtLogin: true,
|
||||
args: Settings.store.autoStartMinimized ? ["--start-minimized"] : []
|
||||
}),
|
||||
disable: () => app.setLoginItemSettings({ openAtLogin: false })
|
||||
};
|
||||
|
||||
export const autoStart = process.platform === "linux" ? makeAutoStartLinux() : autoStartWindowsMac;
|
||||
// The portal call uses the app id by default, which is org.chromium.Chromium, even in packaged Vesktop.
|
||||
// This leads to an autostart entry named "Chromium" instead of "Vesktop".
|
||||
// Thus, only use the portal inside Flatpak, where the app is actually correct.
|
||||
// Maybe there is a way to fix it outside of flatpak, but I couldn't figure it out.
|
||||
export const autoStart =
|
||||
process.platform !== "linux"
|
||||
? autoStartWindowsMac
|
||||
: IS_FLATPAK
|
||||
? makeAutoStartLinuxPortal()
|
||||
: makeAutoStartLinuxDesktop();
|
||||
|
||||
Settings.addChangeListener("autoStartMinimized", () => {
|
||||
if (!autoStart.isEnabled()) return;
|
||||
|
||||
autoStart.enable();
|
||||
});
|
||||
|
||||
137
src/main/cli.ts
Normal file
137
src/main/cli.ts
Normal file
@@ -0,0 +1,137 @@
|
||||
/*
|
||||
* Vesktop, a desktop app aiming to give you a snappier Discord Experience
|
||||
* Copyright (c) 2025 Vendicated and Vesktop contributors
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { app } from "electron";
|
||||
import { stripIndent } from "shared/utils/text";
|
||||
import { parseArgs, ParseArgsOptionDescriptor } from "util";
|
||||
|
||||
type Option = ParseArgsOptionDescriptor & {
|
||||
description: string;
|
||||
hidden?: boolean;
|
||||
options?: string[];
|
||||
argumentName?: string;
|
||||
};
|
||||
|
||||
const options = {
|
||||
"start-minimized": {
|
||||
default: false,
|
||||
type: "boolean",
|
||||
short: "m",
|
||||
description: "Start the application minimized to the system tray"
|
||||
},
|
||||
version: {
|
||||
type: "boolean",
|
||||
short: "v",
|
||||
description: "Print the application version and exit"
|
||||
},
|
||||
help: {
|
||||
type: "boolean",
|
||||
short: "h",
|
||||
description: "Print help information and exit"
|
||||
},
|
||||
"user-agent": {
|
||||
type: "string",
|
||||
argumentName: "ua",
|
||||
description: "Set a custom User-Agent. May trigger anti-spam or break voice chat"
|
||||
},
|
||||
"user-agent-os": {
|
||||
type: "string",
|
||||
description: "Set User-Agent to a specific operating system. May trigger anti-spam or break voice chat",
|
||||
options: ["windows", "linux", "darwin"]
|
||||
}
|
||||
} satisfies Record<string, Option>;
|
||||
|
||||
// only for help display
|
||||
const extraOptions = {
|
||||
"enable-features": {
|
||||
type: "string",
|
||||
description: "Enable specific Chromium features",
|
||||
argumentName: "feature1,feature2,…"
|
||||
},
|
||||
"disable-features": {
|
||||
type: "string",
|
||||
description: "Disable specific Chromium features",
|
||||
argumentName: "feature1,feature2,…"
|
||||
},
|
||||
"ozone-platform": {
|
||||
hidden: process.platform !== "linux",
|
||||
type: "string",
|
||||
description: "Whether to run Vesktop in Wayland or X11 (XWayland)",
|
||||
options: ["x11", "wayland"]
|
||||
}
|
||||
} satisfies Record<string, Option>;
|
||||
|
||||
export const CommandLine = parseArgs({
|
||||
options,
|
||||
strict: false as true, // we manually check later, so cast to true to get better types
|
||||
allowPositionals: true
|
||||
});
|
||||
|
||||
export function checkCommandLineForHelpOrVersion() {
|
||||
const { help, version } = CommandLine.values;
|
||||
|
||||
if (version) {
|
||||
console.log(`Vesktop v${app.getVersion()}`);
|
||||
app.exit(0);
|
||||
}
|
||||
|
||||
if (help) {
|
||||
const base = stripIndent`
|
||||
Vesktop v${app.getVersion()}
|
||||
|
||||
Usage: ${process.execPath} [options] [url]
|
||||
|
||||
Electron Options:
|
||||
See <https://www.electronjs.org/docs/latest/api/command-line-switches#electron-cli-flags>
|
||||
|
||||
Chromium Options:
|
||||
See <https://peter.sh/experiments/chromium-command-line-switches> - only some of them work
|
||||
|
||||
Vesktop Options:
|
||||
`;
|
||||
|
||||
const optionLines = Object.entries(options)
|
||||
.sort(([a], [b]) => a.localeCompare(b))
|
||||
.concat(Object.entries(extraOptions))
|
||||
.filter(([, opt]) => !("hidden" in opt && opt.hidden))
|
||||
.map(([name, opt]) => {
|
||||
const flags = [
|
||||
"short" in opt && `-${opt.short}`,
|
||||
`--${name}`,
|
||||
opt.type !== "boolean" &&
|
||||
("options" in opt ? `<${opt.options.join(" | ")}>` : `<${opt.argumentName ?? opt.type}>`)
|
||||
]
|
||||
.filter(Boolean)
|
||||
.join(" ");
|
||||
|
||||
return [flags, opt.description];
|
||||
});
|
||||
|
||||
const padding = optionLines.reduce((max, [flags]) => Math.max(max, flags.length), 0) + 4;
|
||||
|
||||
const optionsHelp = optionLines
|
||||
.map(([flags, description]) => ` ${flags.padEnd(padding, " ")}${description}`)
|
||||
.join("\n");
|
||||
|
||||
console.log(base + "\n" + optionsHelp);
|
||||
app.exit(0);
|
||||
}
|
||||
|
||||
for (const [name, def] of Object.entries(options)) {
|
||||
const value = CommandLine.values[name];
|
||||
if (value == null) continue;
|
||||
|
||||
if (typeof value !== def.type) {
|
||||
console.error(`Invalid options. Expected ${def.type === "boolean" ? "no" : "an"} argument for --${name}`);
|
||||
app.exit(1);
|
||||
}
|
||||
|
||||
if ("options" in def && !def.options?.includes(value as string)) {
|
||||
console.error(`Invalid value for --${name}: ${value}\nExpected one of: ${def.options.join(", ")}`);
|
||||
app.exit(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,23 +1,35 @@
|
||||
/*
|
||||
* SPDX-License-Identifier: GPL-3.0
|
||||
* Vesktop, a desktop app aiming to give you a snappier Discord Experience
|
||||
* Copyright (c) 2023 Vendicated and Vencord contributors
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { app } from "electron";
|
||||
import { join } from "path";
|
||||
import { existsSync, mkdirSync } from "fs";
|
||||
import { dirname, join } from "path";
|
||||
|
||||
import { CommandLine } from "./cli";
|
||||
|
||||
const vesktopDir = dirname(process.execPath);
|
||||
|
||||
export const PORTABLE =
|
||||
process.platform === "win32" &&
|
||||
!process.execPath.toLowerCase().endsWith("electron.exe") &&
|
||||
!existsSync(join(vesktopDir, "Uninstall Vesktop.exe"));
|
||||
|
||||
export const DATA_DIR =
|
||||
process.env.VENCORD_USER_DATA_DIR || (PORTABLE ? join(vesktopDir, "Data") : join(app.getPath("userData")));
|
||||
|
||||
mkdirSync(DATA_DIR, { recursive: true });
|
||||
|
||||
export const SESSION_DATA_DIR = join(DATA_DIR, "sessionData");
|
||||
app.setPath("sessionData", SESSION_DATA_DIR);
|
||||
|
||||
export const DATA_DIR = process.env.VENCORD_USER_DATA_DIR || join(app.getPath("userData"), "VencordDesktop");
|
||||
export const VENCORD_SETTINGS_DIR = join(DATA_DIR, "settings");
|
||||
export const VENCORD_QUICKCSS_FILE = join(VENCORD_SETTINGS_DIR, "quickCss.css");
|
||||
export const VENCORD_SETTINGS_FILE = join(VENCORD_SETTINGS_DIR, "settings.json");
|
||||
export const VENCORD_THEMES_DIR = join(DATA_DIR, "themes");
|
||||
|
||||
// needs to be inline require because of circular dependency
|
||||
// as otherwise "DATA_DIR" (which is used by ./settings) will be uninitialised
|
||||
export const VENCORD_FILES_DIR =
|
||||
(require("./settings") as typeof import("./settings")).Settings.store.vencordDir || join(DATA_DIR, "vencordDist");
|
||||
|
||||
export const USER_AGENT = `Vesktop/${app.getVersion()} (https://github.com/Vencord/Vesktop)`;
|
||||
|
||||
// dimensions shamelessly stolen from Discord Desktop :3
|
||||
@@ -26,11 +38,23 @@ export const MIN_HEIGHT = 500;
|
||||
export const DEFAULT_WIDTH = 1280;
|
||||
export const DEFAULT_HEIGHT = 720;
|
||||
|
||||
const UserAgents = {
|
||||
darwin: "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/118.0.0.0 Safari/537.36",
|
||||
linux: "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/118.0.0.0 Safari/537.36",
|
||||
windows:
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/118.0.0.0 Safari/537.36"
|
||||
export const DISCORD_HOSTNAMES = ["discord.com", "canary.discord.com", "ptb.discord.com"];
|
||||
|
||||
const VersionString = `AppleWebKit/537.36 (KHTML, like Gecko) Chrome/${process.versions.chrome.split(".")[0]}.0.0.0 Safari/537.36`;
|
||||
const BrowserUserAgents = {
|
||||
darwin: `Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) ${VersionString}`,
|
||||
linux: `Mozilla/5.0 (X11; Linux x86_64) ${VersionString}`,
|
||||
windows: `Mozilla/5.0 (Windows NT 10.0; Win64; x64) ${VersionString}`
|
||||
};
|
||||
|
||||
export const UserAgent = UserAgents[process.platform] || UserAgents.windows;
|
||||
export const BrowserUserAgent =
|
||||
CommandLine.values["user-agent"] ||
|
||||
BrowserUserAgents[CommandLine.values["user-agent-os"] || process.platform] ||
|
||||
BrowserUserAgents.windows;
|
||||
|
||||
export const enum MessageBoxChoice {
|
||||
Default,
|
||||
Cancel
|
||||
}
|
||||
|
||||
export const IS_FLATPAK = process.env.FLATPAK_ID !== undefined;
|
||||
|
||||
40
src/main/dbus.ts
Normal file
40
src/main/dbus.ts
Normal file
@@ -0,0 +1,40 @@
|
||||
/*
|
||||
* Vesktop, a desktop app aiming to give you a snappier Discord Experience
|
||||
* Copyright (c) 2025 Vendicated and Vesktop contributors
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { app } from "electron";
|
||||
import { join } from "path";
|
||||
import { STATIC_DIR } from "shared/paths";
|
||||
|
||||
let libVesktop: typeof import("libvesktop") | null = null;
|
||||
|
||||
function loadLibVesktop() {
|
||||
try {
|
||||
if (!libVesktop) {
|
||||
libVesktop = require(join(STATIC_DIR, `dist/libvesktop-${process.arch}.node`));
|
||||
}
|
||||
} catch (e) {
|
||||
console.error("Failed to load libvesktop:", e);
|
||||
}
|
||||
|
||||
return libVesktop;
|
||||
}
|
||||
|
||||
export function getAccentColor() {
|
||||
return loadLibVesktop()?.getAccentColor() ?? null;
|
||||
}
|
||||
|
||||
export function updateUnityLauncherCount(count: number) {
|
||||
const libVesktop = loadLibVesktop();
|
||||
if (!libVesktop) {
|
||||
return app.setBadgeCount(count);
|
||||
}
|
||||
|
||||
return libVesktop.updateUnityLauncherCount(count);
|
||||
}
|
||||
|
||||
export function requestBackground(autoStart: boolean, commandLine: string[]) {
|
||||
return loadLibVesktop()?.requestBackground(autoStart, commandLine) ?? false;
|
||||
}
|
||||
15
src/main/events.ts
Normal file
15
src/main/events.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
/*
|
||||
* Vesktop, a desktop app aiming to give you a snappier Discord Experience
|
||||
* Copyright (c) 2025 Vendicated and Vesktop contributors
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { EventEmitter } from "events";
|
||||
|
||||
import { UserAssetType } from "./userAssets";
|
||||
|
||||
export const AppEvents = new EventEmitter<{
|
||||
appLoaded: [];
|
||||
userAssetChanged: [UserAssetType];
|
||||
setTrayVariant: ["tray" | "trayUnread"];
|
||||
}>();
|
||||
@@ -1,7 +1,7 @@
|
||||
/*
|
||||
* SPDX-License-Identifier: GPL-3.0
|
||||
* Vesktop, a desktop app aiming to give you a snappier Discord Experience
|
||||
* Copyright (c) 2023 Vendicated and Vencord contributors
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { app } from "electron";
|
||||
@@ -9,45 +9,46 @@ import { BrowserWindow } from "electron/main";
|
||||
import { copyFileSync, mkdirSync, readdirSync } from "fs";
|
||||
import { join } from "path";
|
||||
import { SplashProps } from "shared/browserWinProperties";
|
||||
import { ICON_PATH, VIEW_DIR } from "shared/paths";
|
||||
|
||||
import { autoStart } from "./autoStart";
|
||||
import { DATA_DIR } from "./constants";
|
||||
import { createWindows } from "./mainWindow";
|
||||
import { Settings } from "./settings";
|
||||
import { Settings, State } from "./settings";
|
||||
import { makeLinksOpenExternally } from "./utils/makeLinksOpenExternally";
|
||||
import { loadView } from "./vesktopStatic";
|
||||
|
||||
interface Data {
|
||||
minimizeToTray: boolean;
|
||||
discordBranch: "stable" | "canary" | "ptb";
|
||||
autoStart: boolean;
|
||||
importSettings: boolean;
|
||||
richPresence: boolean;
|
||||
minimizeToTray?: "on";
|
||||
autoStart?: "on";
|
||||
importSettings?: "on";
|
||||
richPresence?: "on";
|
||||
}
|
||||
|
||||
export function createFirstLaunchTour() {
|
||||
const win = new BrowserWindow({
|
||||
...SplashProps,
|
||||
transparent: false,
|
||||
frame: true,
|
||||
autoHideMenuBar: true,
|
||||
height: 470,
|
||||
width: 550,
|
||||
icon: ICON_PATH
|
||||
width: 550
|
||||
});
|
||||
|
||||
makeLinksOpenExternally(win);
|
||||
|
||||
win.loadFile(join(VIEW_DIR, "first-launch.html"));
|
||||
loadView(win, "first-launch.html");
|
||||
win.webContents.addListener("console-message", (_e, _l, msg) => {
|
||||
if (msg === "cancel") return app.exit();
|
||||
|
||||
if (!msg.startsWith("form:")) return;
|
||||
const data = JSON.parse(msg.slice(5)) as Data;
|
||||
|
||||
Settings.store.minimizeToTray = data.minimizeToTray;
|
||||
console.log(data);
|
||||
State.store.firstLaunch = false;
|
||||
Settings.store.discordBranch = data.discordBranch;
|
||||
Settings.store.firstLaunch = false;
|
||||
Settings.store.arRPC = data.richPresence;
|
||||
Settings.store.minimizeToTray = !!data.minimizeToTray;
|
||||
Settings.store.arRPC = !!data.richPresence;
|
||||
|
||||
if (data.autoStart) autoStart.enable();
|
||||
|
||||
|
||||
@@ -1,40 +1,107 @@
|
||||
/*
|
||||
* SPDX-License-Identifier: GPL-3.0
|
||||
* Vesktop, a desktop app aiming to give you a snappier Discord Experience
|
||||
* Copyright (c) 2023 Vendicated and Vencord contributors
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import "./updater";
|
||||
import "./ipc";
|
||||
import "./userAssets";
|
||||
import "./vesktopProtocol";
|
||||
|
||||
import { app, BrowserWindow } from "electron";
|
||||
import { checkUpdates } from "updater/main";
|
||||
import { app, BrowserWindow, nativeTheme } from "electron";
|
||||
|
||||
import { checkCommandLineForHelpOrVersion } from "./cli";
|
||||
import { DATA_DIR } from "./constants";
|
||||
import { createFirstLaunchTour } from "./firstLaunch";
|
||||
import { createWindows, mainWin } from "./mainWindow";
|
||||
import { registerMediaPermissionsHandler } from "./mediaPermissions";
|
||||
import { registerScreenShareHandler } from "./screenShare";
|
||||
import { Settings } from "./settings";
|
||||
import { Settings, State } from "./settings";
|
||||
import { setAsDefaultProtocolClient } from "./utils/setAsDefaultProtocolClient";
|
||||
import { isDeckGameMode } from "./utils/steamOS";
|
||||
|
||||
if (IS_DEV) {
|
||||
require("source-map-support").install();
|
||||
}
|
||||
checkCommandLineForHelpOrVersion();
|
||||
|
||||
console.log("Vesktop v" + app.getVersion());
|
||||
|
||||
// Make the Vencord files use our DATA_DIR
|
||||
process.env.VENCORD_USER_DATA_DIR = DATA_DIR;
|
||||
|
||||
const isLinux = process.platform === "linux";
|
||||
|
||||
export let enableHardwareAcceleration = true;
|
||||
|
||||
function init() {
|
||||
setAsDefaultProtocolClient("discord");
|
||||
|
||||
const { disableSmoothScroll, hardwareAcceleration, hardwareVideoAcceleration } = Settings.store;
|
||||
|
||||
const enabledFeatures = new Set(app.commandLine.getSwitchValue("enable-features").split(","));
|
||||
const disabledFeatures = new Set(app.commandLine.getSwitchValue("disable-features").split(","));
|
||||
app.commandLine.removeSwitch("enable-features");
|
||||
app.commandLine.removeSwitch("disable-features");
|
||||
|
||||
if (hardwareAcceleration === false || process.argv.includes("--disable-gpu")) {
|
||||
enableHardwareAcceleration = false;
|
||||
app.disableHardwareAcceleration();
|
||||
} else {
|
||||
if (hardwareVideoAcceleration) {
|
||||
enabledFeatures.add("AcceleratedVideoEncoder");
|
||||
enabledFeatures.add("AcceleratedVideoDecoder");
|
||||
|
||||
if (isLinux) {
|
||||
enabledFeatures.add("AcceleratedVideoDecodeLinuxGL");
|
||||
enabledFeatures.add("AcceleratedVideoDecodeLinuxZeroCopyGL");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (disableSmoothScroll) {
|
||||
app.commandLine.appendSwitch("disable-smooth-scrolling");
|
||||
}
|
||||
|
||||
// disable renderer backgrounding to prevent the app from unloading when in the background
|
||||
// https://github.com/electron/electron/issues/2822
|
||||
// https://github.com/GoogleChrome/chrome-launcher/blob/5a27dd574d47a75fec0fb50f7b774ebf8a9791ba/docs/chrome-flags-for-tools.md#task-throttling
|
||||
app.commandLine.appendSwitch("disable-renderer-backgrounding");
|
||||
app.commandLine.appendSwitch("disable-background-timer-throttling");
|
||||
app.commandLine.appendSwitch("disable-backgrounding-occluded-windows");
|
||||
if (process.platform === "win32") {
|
||||
disabledFeatures.add("CalculateNativeWinOcclusion");
|
||||
}
|
||||
|
||||
// work around chrome 66 disabling autoplay by default
|
||||
app.commandLine.appendSwitch("autoplay-policy", "no-user-gesture-required");
|
||||
|
||||
// WinRetrieveSuggestionsOnlyOnDemand: Work around electron 13 bug w/ async spellchecking on Windows.
|
||||
// HardwareMediaKeyHandling,MediaSessionService: Prevent Discord from registering as a media service.
|
||||
//
|
||||
// WidgetLayering (Vencord Added): Fix DevTools context menus https://github.com/electron/electron/issues/38790
|
||||
app.commandLine.appendSwitch(
|
||||
"disable-features",
|
||||
"WinRetrieveSuggestionsOnlyOnDemand,HardwareMediaKeyHandling,MediaSessionService,WidgetLayering"
|
||||
);
|
||||
// HardwareMediaKeyHandling, MediaSessionService: Prevent Discord from registering as a media service.
|
||||
disabledFeatures.add("WinRetrieveSuggestionsOnlyOnDemand");
|
||||
disabledFeatures.add("HardwareMediaKeyHandling");
|
||||
disabledFeatures.add("MediaSessionService");
|
||||
|
||||
if (isLinux) {
|
||||
// Support TTS on Linux using https://wiki.archlinux.org/title/Speech_dispatcher
|
||||
app.commandLine.appendSwitch("enable-speech-dispatcher");
|
||||
}
|
||||
|
||||
disabledFeatures.forEach(feat => enabledFeatures.delete(feat));
|
||||
|
||||
const enabledFeaturesArray = enabledFeatures.values().filter(Boolean).toArray();
|
||||
const disabledFeaturesArray = disabledFeatures.values().filter(Boolean).toArray();
|
||||
|
||||
if (enabledFeaturesArray.length) {
|
||||
app.commandLine.appendSwitch("enable-features", enabledFeaturesArray.join(","));
|
||||
console.log("Enabled Chromium features:", enabledFeaturesArray.join(", "));
|
||||
}
|
||||
|
||||
if (disabledFeaturesArray.length) {
|
||||
app.commandLine.appendSwitch("disable-features", disabledFeaturesArray.join(","));
|
||||
console.log("Disabled Chromium features:", disabledFeaturesArray.join(", "));
|
||||
}
|
||||
|
||||
// In the Flatpak on SteamOS the theme is detected as light, but SteamOS only has a dark mode, so we just override it
|
||||
if (isDeckGameMode) nativeTheme.themeSource = "dark";
|
||||
|
||||
app.on("second-instance", (_event, _cmdLine, _cwd, data: any) => {
|
||||
if (data.IS_DEV) app.quit();
|
||||
@@ -46,8 +113,7 @@ function init() {
|
||||
});
|
||||
|
||||
app.whenReady().then(async () => {
|
||||
checkUpdates();
|
||||
if (process.platform === "win32") app.setAppUserModelId("dev.vencord.desktop");
|
||||
if (process.platform === "win32") app.setAppUserModelId("dev.vencord.vesktop");
|
||||
|
||||
registerScreenShareHandler();
|
||||
registerMediaPermissionsHandler();
|
||||
@@ -73,13 +139,19 @@ if (!app.requestSingleInstanceLock({ IS_DEV })) {
|
||||
}
|
||||
|
||||
async function bootstrap() {
|
||||
if (!Object.hasOwn(Settings.store, "firstLaunch")) {
|
||||
if (!Object.hasOwn(State.store, "firstLaunch")) {
|
||||
createFirstLaunchTour();
|
||||
} else {
|
||||
createWindows();
|
||||
}
|
||||
}
|
||||
|
||||
// MacOS only event
|
||||
export let darwinURL: string | undefined;
|
||||
app.on("open-url", (_, url) => {
|
||||
darwinURL = url;
|
||||
});
|
||||
|
||||
app.on("window-all-closed", () => {
|
||||
if (process.platform !== "darwin") app.quit();
|
||||
});
|
||||
|
||||
@@ -1,15 +1,26 @@
|
||||
/*
|
||||
* SPDX-License-Identifier: GPL-3.0
|
||||
* Vesktop, a desktop app aiming to give you a snappier Discord Experience
|
||||
* Copyright (c) 2023 Vendicated and Vencord contributors
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
if (process.platform === "linux") import("./virtmic");
|
||||
if (process.platform === "linux") import("./venmic");
|
||||
|
||||
import { execFile } from "child_process";
|
||||
import { app, BrowserWindow, dialog, RelaunchOptions, session, shell } from "electron";
|
||||
import {
|
||||
app,
|
||||
BrowserWindow,
|
||||
clipboard,
|
||||
dialog,
|
||||
IpcMainInvokeEvent,
|
||||
nativeImage,
|
||||
RelaunchOptions,
|
||||
session,
|
||||
shell
|
||||
} from "electron";
|
||||
import { mkdirSync, readFileSync, watch } from "fs";
|
||||
import { open, readFile } from "fs/promises";
|
||||
import { enableHardwareAcceleration } from "main";
|
||||
import { release } from "os";
|
||||
import { join } from "path";
|
||||
import { debounce } from "shared/utils/debounce";
|
||||
@@ -17,11 +28,14 @@ import { debounce } from "shared/utils/debounce";
|
||||
import { IpcEvents } from "../shared/IpcEvents";
|
||||
import { setBadgeCount } from "./appBadge";
|
||||
import { autoStart } from "./autoStart";
|
||||
import { VENCORD_FILES_DIR, VENCORD_QUICKCSS_FILE, VENCORD_THEMES_DIR } from "./constants";
|
||||
import { VENCORD_QUICKCSS_FILE, VENCORD_THEMES_DIR } from "./constants";
|
||||
import { mainWin } from "./mainWindow";
|
||||
import { Settings } from "./settings";
|
||||
import { Settings, State } from "./settings";
|
||||
import { handle, handleSync } from "./utils/ipcWrappers";
|
||||
import { PopoutWindows } from "./utils/popout";
|
||||
import { isDeckGameMode, showGamePage } from "./utils/steamOS";
|
||||
import { isValidVencordInstall } from "./utils/vencordLoader";
|
||||
import { VENCORD_FILES_DIR } from "./vencordFilesDir";
|
||||
|
||||
handleSync(IpcEvents.GET_VENCORD_PRELOAD_FILE, () => join(VENCORD_FILES_DIR, "vencordDesktopPreload.js"));
|
||||
handleSync(IpcEvents.GET_VENCORD_RENDERER_SCRIPT, () =>
|
||||
@@ -33,6 +47,7 @@ handleSync(IpcEvents.GET_RENDERER_CSS_FILE, () => join(__dirname, "renderer.css"
|
||||
|
||||
handleSync(IpcEvents.GET_SETTINGS, () => Settings.plain);
|
||||
handleSync(IpcEvents.GET_VERSION, () => app.getVersion());
|
||||
handleSync(IpcEvents.GET_ENABLE_HARDWARE_ACCELERATION, () => enableHardwareAcceleration);
|
||||
|
||||
handleSync(
|
||||
IpcEvents.SUPPORTS_WINDOWS_TRANSPARENCY,
|
||||
@@ -47,11 +62,14 @@ handle(IpcEvents.SET_SETTINGS, (_, settings: typeof Settings.store, path?: strin
|
||||
Settings.setData(settings, path);
|
||||
});
|
||||
|
||||
handle(IpcEvents.RELAUNCH, () => {
|
||||
handle(IpcEvents.RELAUNCH, async () => {
|
||||
const options: RelaunchOptions = {
|
||||
args: process.argv.slice(1).concat(["--relaunch"])
|
||||
};
|
||||
if (app.isPackaged && process.env.APPIMAGE) {
|
||||
if (isDeckGameMode) {
|
||||
// We can't properly relaunch when running under gamescope, but we can at least navigate to our page in Steam.
|
||||
await showGamePage();
|
||||
} else if (app.isPackaged && process.env.APPIMAGE) {
|
||||
execFile(process.env.APPIMAGE, options.args);
|
||||
} else {
|
||||
app.relaunch(options);
|
||||
@@ -63,35 +81,34 @@ handle(IpcEvents.SHOW_ITEM_IN_FOLDER, (_, path) => {
|
||||
shell.showItemInFolder(path);
|
||||
});
|
||||
|
||||
function getWindow(e: IpcMainInvokeEvent, key?: string) {
|
||||
return key ? PopoutWindows.get(key)! : (BrowserWindow.fromWebContents(e.sender) ?? mainWin);
|
||||
}
|
||||
|
||||
handle(IpcEvents.FOCUS, () => {
|
||||
if (process.platform === "win32") mainWin.minimize(); // Windows is weird
|
||||
|
||||
mainWin.restore();
|
||||
mainWin.show();
|
||||
mainWin.setSkipTaskbar(false);
|
||||
});
|
||||
|
||||
handle(IpcEvents.CLOSE, e => {
|
||||
(BrowserWindow.fromWebContents(e.sender) ?? e.sender).close();
|
||||
handle(IpcEvents.CLOSE, (e, key?: string) => {
|
||||
getWindow(e, key).close();
|
||||
});
|
||||
|
||||
handle(IpcEvents.MINIMIZE, e => {
|
||||
mainWin.minimize();
|
||||
handle(IpcEvents.MINIMIZE, (e, key?: string) => {
|
||||
getWindow(e, key).minimize();
|
||||
});
|
||||
|
||||
handle(IpcEvents.MAXIMIZE, e => {
|
||||
if (mainWin.isMaximized()) {
|
||||
mainWin.unmaximize();
|
||||
handle(IpcEvents.MAXIMIZE, (e, key?: string) => {
|
||||
const win = getWindow(e, key);
|
||||
if (win.isMaximized()) {
|
||||
win.unmaximize();
|
||||
} else {
|
||||
mainWin.maximize();
|
||||
win.maximize();
|
||||
}
|
||||
});
|
||||
|
||||
handle(IpcEvents.SPELLCHECK_SET_LANGUAGES, (_, languages: string[]) => {
|
||||
const ses = session.defaultSession;
|
||||
|
||||
const available = ses.availableSpellCheckerLanguages;
|
||||
const applicable = languages.filter(l => available.includes(l)).slice(0, 3);
|
||||
if (applicable.length) ses.setSpellCheckerLanguages(applicable);
|
||||
handleSync(IpcEvents.SPELLCHECK_GET_AVAILABLE_LANGUAGES, e => {
|
||||
e.returnValue = session.defaultSession.availableSpellCheckerLanguages;
|
||||
});
|
||||
|
||||
handle(IpcEvents.SPELLCHECK_REPLACE_MISSPELLING, (e, word: string) => {
|
||||
@@ -102,7 +119,14 @@ handle(IpcEvents.SPELLCHECK_ADD_TO_DICTIONARY, (e, word: string) => {
|
||||
e.sender.session.addWordToSpellCheckerDictionary(word);
|
||||
});
|
||||
|
||||
handle(IpcEvents.SELECT_VENCORD_DIR, async () => {
|
||||
handleSync(IpcEvents.GET_VENCORD_DIR, e => (e.returnValue = State.store.vencordDir));
|
||||
|
||||
handle(IpcEvents.SELECT_VENCORD_DIR, async (_e, value?: null) => {
|
||||
if (value === null) {
|
||||
delete State.store.vencordDir;
|
||||
return "ok";
|
||||
}
|
||||
|
||||
const res = await dialog.showOpenDialog(mainWin!, {
|
||||
properties: ["openDirectory"]
|
||||
});
|
||||
@@ -111,11 +135,31 @@ handle(IpcEvents.SELECT_VENCORD_DIR, async () => {
|
||||
const dir = res.filePaths[0];
|
||||
if (!isValidVencordInstall(dir)) return "invalid";
|
||||
|
||||
return dir;
|
||||
State.store.vencordDir = dir;
|
||||
|
||||
return "ok";
|
||||
});
|
||||
|
||||
handle(IpcEvents.SET_BADGE_COUNT, (_, count: number) => setBadgeCount(count));
|
||||
|
||||
handle(IpcEvents.CLIPBOARD_COPY_IMAGE, async (_, buf: ArrayBuffer, src: string) => {
|
||||
clipboard.write({
|
||||
html: `<img src="${src.replaceAll('"', '\\"')}">`,
|
||||
image: nativeImage.createFromBuffer(Buffer.from(buf))
|
||||
});
|
||||
});
|
||||
|
||||
function openDebugPage(page: string) {
|
||||
const win = new BrowserWindow({
|
||||
autoHideMenuBar: true
|
||||
});
|
||||
|
||||
win.loadURL(page);
|
||||
}
|
||||
|
||||
handle(IpcEvents.DEBUG_LAUNCH_GPU, () => openDebugPage("chrome://gpu"));
|
||||
handle(IpcEvents.DEBUG_LAUNCH_WEBRTC_INTERNALS, () => openDebugPage("chrome://webrtc-internals"));
|
||||
|
||||
function readCss() {
|
||||
return readFile(VENCORD_QUICKCSS_FILE, "utf-8").catch(() => "");
|
||||
}
|
||||
|
||||
61
src/main/ipcCommands.ts
Normal file
61
src/main/ipcCommands.ts
Normal file
@@ -0,0 +1,61 @@
|
||||
/*
|
||||
* Vesktop, a desktop app aiming to give you a snappier Discord Experience
|
||||
* Copyright (c) 2025 Vendicated and Vencord contributors
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { randomUUID } from "crypto";
|
||||
import { ipcMain } from "electron";
|
||||
import { IpcEvents } from "shared/IpcEvents";
|
||||
|
||||
import { mainWin } from "./mainWindow";
|
||||
|
||||
const resolvers = new Map<string, Record<"resolve" | "reject", (data: any) => void>>();
|
||||
|
||||
export interface IpcMessage {
|
||||
nonce: string;
|
||||
message: string;
|
||||
data?: any;
|
||||
}
|
||||
|
||||
export interface IpcResponse {
|
||||
nonce: string;
|
||||
ok: boolean;
|
||||
data?: any;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a message to the renderer process and waits for a response.
|
||||
* `data` must be serializable as it will be sent over IPC.
|
||||
*
|
||||
* You must add a handler for the message in the renderer process.
|
||||
*/
|
||||
export function sendRendererCommand<T = any>(message: string, data?: any) {
|
||||
if (mainWin.isDestroyed()) {
|
||||
console.warn("Main window is destroyed, cannot send IPC command:", message);
|
||||
return Promise.reject(new Error("Main window is destroyed"));
|
||||
}
|
||||
|
||||
const nonce = randomUUID();
|
||||
|
||||
const promise = new Promise<T>((resolve, reject) => {
|
||||
resolvers.set(nonce, { resolve, reject });
|
||||
});
|
||||
|
||||
mainWin.webContents.send(IpcEvents.IPC_COMMAND, { nonce, message, data });
|
||||
|
||||
return promise;
|
||||
}
|
||||
|
||||
ipcMain.on(IpcEvents.IPC_COMMAND, (_event, { nonce, ok, data }: IpcResponse) => {
|
||||
const resolver = resolvers.get(nonce);
|
||||
if (!resolver) throw new Error(`Unknown message: ${nonce}`);
|
||||
|
||||
if (ok) {
|
||||
resolver.resolve(data);
|
||||
} else {
|
||||
resolver.reject(data);
|
||||
}
|
||||
|
||||
resolvers.delete(nonce);
|
||||
});
|
||||
@@ -1,45 +1,44 @@
|
||||
/*
|
||||
* SPDX-License-Identifier: GPL-3.0
|
||||
* Vesktop, a desktop app aiming to give you a snappier Discord Experience
|
||||
* Copyright (c) 2023 Vendicated and Vencord contributors
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import {
|
||||
app,
|
||||
BrowserWindow,
|
||||
BrowserWindowConstructorOptions,
|
||||
dialog,
|
||||
Menu,
|
||||
MenuItemConstructorOptions,
|
||||
nativeTheme,
|
||||
Tray
|
||||
screen,
|
||||
session
|
||||
} from "electron";
|
||||
import { rm } from "fs/promises";
|
||||
import { join } from "path";
|
||||
import { IpcEvents } from "shared/IpcEvents";
|
||||
import { IpcCommands, IpcEvents } from "shared/IpcEvents";
|
||||
import { isTruthy } from "shared/utils/guards";
|
||||
import { once } from "shared/utils/once";
|
||||
import type { SettingsStore } from "shared/utils/SettingsStore";
|
||||
|
||||
import { ICON_PATH } from "../shared/paths";
|
||||
import { createAboutWindow } from "./about";
|
||||
import { initArRPC } from "./arrpc";
|
||||
import {
|
||||
DATA_DIR,
|
||||
DEFAULT_HEIGHT,
|
||||
DEFAULT_WIDTH,
|
||||
MIN_HEIGHT,
|
||||
MIN_WIDTH,
|
||||
UserAgent,
|
||||
VENCORD_FILES_DIR
|
||||
} from "./constants";
|
||||
import { Settings, VencordSettings } from "./settings";
|
||||
import { createSplashWindow } from "./splash";
|
||||
import { CommandLine } from "./cli";
|
||||
import { BrowserUserAgent, DEFAULT_HEIGHT, DEFAULT_WIDTH, MIN_HEIGHT, MIN_WIDTH } from "./constants";
|
||||
import { AppEvents } from "./events";
|
||||
import { darwinURL } from "./index";
|
||||
import { sendRendererCommand } from "./ipcCommands";
|
||||
import { Settings, State, VencordSettings } from "./settings";
|
||||
import { createSplashWindow, updateSplashMessage } from "./splash";
|
||||
import { destroyTray, initTray } from "./tray";
|
||||
import { clearData } from "./utils/clearData";
|
||||
import { makeLinksOpenExternally } from "./utils/makeLinksOpenExternally";
|
||||
import { applyDeckKeyboardFix, askToApplySteamLayout, isDeckGameMode } from "./utils/steamOS";
|
||||
import { downloadVencordFiles, ensureVencordFiles } from "./utils/vencordLoader";
|
||||
import { VENCORD_FILES_DIR } from "./vencordFilesDir";
|
||||
|
||||
let isQuitting = false;
|
||||
let tray: Tray;
|
||||
|
||||
applyDeckKeyboardFix();
|
||||
|
||||
app.on("before-quit", () => {
|
||||
isQuitting = true;
|
||||
@@ -68,94 +67,6 @@ function makeSettingsListenerHelpers<O extends object>(o: SettingsStore<O>) {
|
||||
const [addSettingsListener, removeSettingsListeners] = makeSettingsListenerHelpers(Settings);
|
||||
const [addVencordSettingsListener, removeVencordSettingsListeners] = makeSettingsListenerHelpers(VencordSettings);
|
||||
|
||||
function initTray(win: BrowserWindow) {
|
||||
const trayMenu = Menu.buildFromTemplate([
|
||||
{
|
||||
label: "Open",
|
||||
click() {
|
||||
win.show();
|
||||
},
|
||||
enabled: false
|
||||
},
|
||||
{
|
||||
label: "About",
|
||||
click: createAboutWindow
|
||||
},
|
||||
{
|
||||
label: "Update Vencord",
|
||||
async click() {
|
||||
await downloadVencordFiles();
|
||||
app.relaunch();
|
||||
app.quit();
|
||||
}
|
||||
},
|
||||
{
|
||||
label: "Reset Vesktop",
|
||||
async click() {
|
||||
await clearData(win);
|
||||
}
|
||||
},
|
||||
{
|
||||
type: "separator"
|
||||
},
|
||||
{
|
||||
label: "Relaunch",
|
||||
click() {
|
||||
app.relaunch();
|
||||
app.quit();
|
||||
}
|
||||
},
|
||||
{
|
||||
label: "Quit Vesktop",
|
||||
click() {
|
||||
isQuitting = true;
|
||||
app.quit();
|
||||
}
|
||||
}
|
||||
]);
|
||||
|
||||
tray = new Tray(ICON_PATH);
|
||||
tray.setToolTip("Vesktop");
|
||||
tray.setContextMenu(trayMenu);
|
||||
tray.on("click", () => win.show());
|
||||
|
||||
win.on("show", () => {
|
||||
trayMenu.items[0].enabled = false;
|
||||
});
|
||||
|
||||
win.on("hide", () => {
|
||||
trayMenu.items[0].enabled = true;
|
||||
});
|
||||
}
|
||||
|
||||
const enum MessageBoxChoice {
|
||||
Default,
|
||||
Cancel
|
||||
}
|
||||
|
||||
async function clearData(win: BrowserWindow) {
|
||||
const { response } = await dialog.showMessageBox(win, {
|
||||
message: "Are you sure you want to reset Vesktop?",
|
||||
detail: "This will log you out, clear caches and reset all your settings!\n\nVesktop will automatically restart after this operation.",
|
||||
buttons: ["Yes", "No"],
|
||||
cancelId: MessageBoxChoice.Cancel,
|
||||
defaultId: MessageBoxChoice.Default,
|
||||
type: "warning"
|
||||
});
|
||||
|
||||
if (response === MessageBoxChoice.Cancel) return;
|
||||
|
||||
win.close();
|
||||
|
||||
await win.webContents.session.clearStorageData();
|
||||
await win.webContents.session.clearCache();
|
||||
await win.webContents.session.clearCodeCaches({});
|
||||
await rm(DATA_DIR, { force: true, recursive: true });
|
||||
|
||||
app.relaunch();
|
||||
app.quit();
|
||||
}
|
||||
|
||||
type MenuItemList = Array<MenuItemConstructorOptions | false>;
|
||||
|
||||
function initMenuBar(win: BrowserWindow) {
|
||||
@@ -202,16 +113,13 @@ function initMenuBar(win: BrowserWindow) {
|
||||
label: "Settings",
|
||||
accelerator: "CmdOrCtrl+,",
|
||||
async click() {
|
||||
mainWin.webContents.executeJavaScript(
|
||||
"Vencord.Webpack.Common.SettingsRouter.open('My Account')"
|
||||
);
|
||||
sendRendererCommand(IpcCommands.NAVIGATE_SETTINGS);
|
||||
}
|
||||
},
|
||||
{
|
||||
type: "separator"
|
||||
},
|
||||
{
|
||||
label: "Hide Vesktop", // Should probably remove the label, but it says "Hide VencordDesktop" instead of "Hide Vesktop"
|
||||
role: "hide"
|
||||
},
|
||||
{
|
||||
@@ -250,7 +158,7 @@ function initMenuBar(win: BrowserWindow) {
|
||||
}
|
||||
] satisfies MenuItemList;
|
||||
|
||||
const menu = Menu.buildFromTemplate([
|
||||
const menuItems = [
|
||||
{
|
||||
label: "Vesktop",
|
||||
role: "appMenu",
|
||||
@@ -259,21 +167,28 @@ function initMenuBar(win: BrowserWindow) {
|
||||
{ role: "fileMenu" },
|
||||
{ role: "editMenu" },
|
||||
{ role: "viewMenu" },
|
||||
{ role: "windowMenu" }
|
||||
]);
|
||||
isDarwin && { role: "windowMenu" }
|
||||
] satisfies MenuItemList;
|
||||
|
||||
const menu = Menu.buildFromTemplate(menuItems.filter(isTruthy));
|
||||
|
||||
Menu.setApplicationMenu(menu);
|
||||
}
|
||||
|
||||
function getWindowBoundsOptions(): BrowserWindowConstructorOptions {
|
||||
const { x, y, width, height } = Settings.store.windowBounds ?? {};
|
||||
// We want the default window behaivour to apply in game mode since it expects everything to be fullscreen and maximized.
|
||||
if (isDeckGameMode) return {};
|
||||
|
||||
const { x, y, width, height } = State.store.windowBounds ?? {};
|
||||
|
||||
const options = {
|
||||
width: width ?? DEFAULT_WIDTH,
|
||||
height: height ?? DEFAULT_HEIGHT
|
||||
} as BrowserWindowConstructorOptions;
|
||||
|
||||
if (x != null && y != null) {
|
||||
const storedDisplay = screen.getAllDisplays().find(display => display.id === State.store.displayId);
|
||||
|
||||
if (x != null && y != null && storedDisplay) {
|
||||
options.x = x;
|
||||
options.y = y;
|
||||
}
|
||||
@@ -288,7 +203,8 @@ function getWindowBoundsOptions(): BrowserWindowConstructorOptions {
|
||||
|
||||
function getDarwinOptions(): BrowserWindowConstructorOptions {
|
||||
const options = {
|
||||
titleBarStyle: "hiddenInset"
|
||||
titleBarStyle: "hidden",
|
||||
trafficLightPosition: { x: 10, y: 10 }
|
||||
} as BrowserWindowConstructorOptions;
|
||||
|
||||
const { splashTheming, splashBackground } = Settings.store;
|
||||
@@ -298,7 +214,7 @@ function getDarwinOptions(): BrowserWindowConstructorOptions {
|
||||
options.vibrancy = "sidebar";
|
||||
options.backgroundColor = "#ffffff00";
|
||||
} else {
|
||||
if (splashTheming) {
|
||||
if (splashTheming !== false) {
|
||||
options.backgroundColor = splashBackground;
|
||||
} else {
|
||||
options.backgroundColor = nativeTheme.shouldUseDarkColors ? "#313338" : "#ffffff";
|
||||
@@ -310,8 +226,8 @@ function getDarwinOptions(): BrowserWindowConstructorOptions {
|
||||
|
||||
function initWindowBoundsListeners(win: BrowserWindow) {
|
||||
const saveState = () => {
|
||||
Settings.store.maximized = win.isMaximized();
|
||||
Settings.store.minimized = win.isMinimized();
|
||||
State.store.maximized = win.isMaximized();
|
||||
State.store.minimized = win.isMinimized();
|
||||
};
|
||||
|
||||
win.on("maximize", saveState);
|
||||
@@ -319,7 +235,8 @@ function initWindowBoundsListeners(win: BrowserWindow) {
|
||||
win.on("unmaximize", saveState);
|
||||
|
||||
const saveBounds = () => {
|
||||
Settings.store.windowBounds = win.getBounds();
|
||||
State.store.windowBounds = win.getBounds();
|
||||
State.store.displayId = screen.getDisplayMatching(State.store.windowBounds).id;
|
||||
};
|
||||
|
||||
win.on("resize", saveBounds);
|
||||
@@ -328,9 +245,10 @@ function initWindowBoundsListeners(win: BrowserWindow) {
|
||||
|
||||
function initSettingsListeners(win: BrowserWindow) {
|
||||
addSettingsListener("tray", enable => {
|
||||
if (enable) initTray(win);
|
||||
else tray?.destroy();
|
||||
if (enable) initTray(win, q => (isQuitting = q));
|
||||
else destroyTray();
|
||||
});
|
||||
|
||||
addSettingsListener("disableMinSize", disable => {
|
||||
if (disable) {
|
||||
// 0 no work
|
||||
@@ -359,12 +277,51 @@ function initSettingsListeners(win: BrowserWindow) {
|
||||
addSettingsListener("enableMenu", enabled => {
|
||||
win.setAutoHideMenuBar(enabled ?? false);
|
||||
});
|
||||
|
||||
addSettingsListener("spellCheckLanguages", languages => initSpellCheckLanguages(win, languages));
|
||||
}
|
||||
|
||||
async function initSpellCheckLanguages(win: BrowserWindow, languages?: string[]) {
|
||||
languages ??= await sendRendererCommand(IpcCommands.GET_LANGUAGES);
|
||||
if (!languages) return;
|
||||
|
||||
const ses = session.defaultSession;
|
||||
|
||||
const available = ses.availableSpellCheckerLanguages;
|
||||
const applicable = languages.filter(l => available.includes(l)).slice(0, 5);
|
||||
if (applicable.length) ses.setSpellCheckerLanguages(applicable);
|
||||
}
|
||||
|
||||
function initSpellCheck(win: BrowserWindow) {
|
||||
win.webContents.on("context-menu", (_, data) => {
|
||||
win.webContents.send(IpcEvents.SPELLCHECK_RESULT, data.misspelledWord, data.dictionarySuggestions);
|
||||
});
|
||||
|
||||
initSpellCheckLanguages(win, Settings.store.spellCheckLanguages);
|
||||
}
|
||||
|
||||
function initDevtoolsListeners(win: BrowserWindow) {
|
||||
win.webContents.on("devtools-opened", () => {
|
||||
win.webContents.send(IpcEvents.DEVTOOLS_OPENED);
|
||||
});
|
||||
win.webContents.on("devtools-closed", () => {
|
||||
win.webContents.send(IpcEvents.DEVTOOLS_CLOSED);
|
||||
});
|
||||
}
|
||||
|
||||
function initStaticTitle(win: BrowserWindow) {
|
||||
const listener = (e: { preventDefault: Function }) => e.preventDefault();
|
||||
|
||||
if (Settings.store.staticTitle) win.on("page-title-updated", listener);
|
||||
|
||||
addSettingsListener("staticTitle", enabled => {
|
||||
if (enabled) {
|
||||
win.setTitle("Vesktop");
|
||||
win.on("page-title-updated", listener);
|
||||
} else {
|
||||
win.off("page-title-updated", listener);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function createMainWindow() {
|
||||
@@ -372,28 +329,42 @@ function createMainWindow() {
|
||||
removeSettingsListeners();
|
||||
removeVencordSettingsListeners();
|
||||
|
||||
const { staticTitle, transparencyOption, enableMenu, discordWindowsTitleBar } = Settings.store;
|
||||
const { staticTitle, transparencyOption, enableMenu, customTitleBar, splashTheming, splashBackground } =
|
||||
Settings.store;
|
||||
|
||||
const { frameless } = VencordSettings.store;
|
||||
const { frameless, transparent } = VencordSettings.store;
|
||||
|
||||
const noFrame = frameless === true || (process.platform === "win32" && discordWindowsTitleBar === true);
|
||||
const noFrame = frameless === true || customTitleBar === true;
|
||||
const backgroundColor =
|
||||
splashTheming !== false ? splashBackground : nativeTheme.shouldUseDarkColors ? "#313338" : "#ffffff";
|
||||
|
||||
const win = (mainWin = new BrowserWindow({
|
||||
show: false,
|
||||
show: Settings.store.enableSplashScreen === false,
|
||||
backgroundColor,
|
||||
webPreferences: {
|
||||
nodeIntegration: false,
|
||||
sandbox: false,
|
||||
contextIsolation: true,
|
||||
devTools: true,
|
||||
preload: join(__dirname, "preload.js"),
|
||||
spellcheck: true
|
||||
spellcheck: true,
|
||||
// disable renderer backgrounding to prevent the app from unloading when in the background
|
||||
backgroundThrottling: false
|
||||
},
|
||||
icon: ICON_PATH,
|
||||
frame: !noFrame,
|
||||
...(transparent && {
|
||||
transparent: true,
|
||||
backgroundColor: "#00000000"
|
||||
}),
|
||||
...(transparencyOption &&
|
||||
transparencyOption !== "none" && {
|
||||
backgroundColor: "#00000000",
|
||||
backgroundMaterial: transparencyOption,
|
||||
backgroundMaterial: transparencyOption
|
||||
}),
|
||||
// Fix transparencyOption for custom discord titlebar
|
||||
...(customTitleBar &&
|
||||
transparencyOption &&
|
||||
transparencyOption !== "none" && {
|
||||
transparent: true
|
||||
}),
|
||||
...(staticTitle && { title: "Vesktop" }),
|
||||
@@ -402,9 +373,10 @@ function createMainWindow() {
|
||||
autoHideMenuBar: enableMenu
|
||||
}));
|
||||
win.setMenuBarVisibility(false);
|
||||
if (process.platform === "darwin" && customTitleBar) win.setWindowButtonVisibility(false);
|
||||
|
||||
win.on("close", e => {
|
||||
const useTray = Settings.store.minimizeToTray !== false && Settings.store.tray !== false;
|
||||
const useTray = !isDeckGameMode && Settings.store.minimizeToTray !== false && Settings.store.tray !== false;
|
||||
if (isQuitting || (process.platform !== "darwin" && !useTray)) return;
|
||||
|
||||
e.preventDefault();
|
||||
@@ -415,43 +387,92 @@ function createMainWindow() {
|
||||
return false;
|
||||
});
|
||||
|
||||
if (Settings.store.staticTitle) win.on("page-title-updated", e => e.preventDefault());
|
||||
|
||||
initWindowBoundsListeners(win);
|
||||
if ((Settings.store.tray ?? true) && process.platform !== "darwin") initTray(win);
|
||||
if (!isDeckGameMode && (Settings.store.tray ?? true) && process.platform !== "darwin")
|
||||
initTray(win, q => (isQuitting = q));
|
||||
|
||||
initMenuBar(win);
|
||||
makeLinksOpenExternally(win);
|
||||
initSettingsListeners(win);
|
||||
initSpellCheck(win);
|
||||
initDevtoolsListeners(win);
|
||||
initStaticTitle(win);
|
||||
|
||||
win.webContents.setUserAgent(UserAgent);
|
||||
win.webContents.setUserAgent(BrowserUserAgent);
|
||||
|
||||
const subdomain =
|
||||
Settings.store.discordBranch === "canary" || Settings.store.discordBranch === "ptb"
|
||||
? `${Settings.store.discordBranch}.`
|
||||
: "";
|
||||
|
||||
win.loadURL(`https://${subdomain}discord.com/app`);
|
||||
// if the open-url event is fired (in index.ts) while starting up, darwinURL will be set. If not fall back to checking the process args (which Windows and Linux use for URI calling.)
|
||||
// win.webContents.session.clearCache().then(() => {
|
||||
loadUrl(darwinURL || process.argv.find(arg => arg.startsWith("discord://")));
|
||||
// });
|
||||
|
||||
return win;
|
||||
}
|
||||
|
||||
const runVencordMain = once(() => require(join(VENCORD_FILES_DIR, "vencordDesktopMain.js")));
|
||||
|
||||
export function loadUrl(uri: string | undefined) {
|
||||
const branch = Settings.store.discordBranch;
|
||||
const subdomain = branch === "canary" || branch === "ptb" ? `${branch}.` : "";
|
||||
|
||||
// we do not rely on 'did-finish-load' because it fires even if loadURL fails which triggers early detruction of the splash
|
||||
mainWin
|
||||
.loadURL(`https://${subdomain}discord.com/${uri ? new URL(uri).pathname.slice(1) || "app" : "app"}`)
|
||||
.then(() => AppEvents.emit("appLoaded"))
|
||||
.catch(error => retryUrl(error.url, error.code));
|
||||
}
|
||||
|
||||
const retryDelay = 1000;
|
||||
function retryUrl(url: string, description: string) {
|
||||
console.log(`retrying in ${retryDelay}ms`);
|
||||
updateSplashMessage(`Failed to load Discord: ${description}`);
|
||||
setTimeout(() => loadUrl(url), retryDelay);
|
||||
}
|
||||
|
||||
export async function createWindows() {
|
||||
const splash = createSplashWindow();
|
||||
const startMinimized = CommandLine.values["start-minimized"];
|
||||
|
||||
let splash: BrowserWindow | undefined;
|
||||
if (Settings.store.enableSplashScreen !== false) {
|
||||
splash = createSplashWindow(startMinimized);
|
||||
|
||||
// SteamOS letterboxes and scales it terribly, so just full screen it
|
||||
if (isDeckGameMode) splash.setFullScreen(true);
|
||||
}
|
||||
|
||||
await ensureVencordFiles();
|
||||
runVencordMain();
|
||||
|
||||
mainWin = createMainWindow();
|
||||
|
||||
mainWin.once("ready-to-show", () => {
|
||||
splash.destroy();
|
||||
mainWin!.show();
|
||||
AppEvents.on("appLoaded", () => {
|
||||
splash?.destroy();
|
||||
|
||||
if (Settings.store.maximized) {
|
||||
mainWin!.maximize();
|
||||
if (!startMinimized) {
|
||||
if (splash) mainWin!.show();
|
||||
if (State.store.maximized && !isDeckGameMode) mainWin!.maximize();
|
||||
}
|
||||
|
||||
if (isDeckGameMode) {
|
||||
// always use entire display
|
||||
mainWin!.setFullScreen(true);
|
||||
|
||||
askToApplySteamLayout(mainWin);
|
||||
}
|
||||
|
||||
mainWin.once("show", () => {
|
||||
if (State.store.maximized && !mainWin!.isMaximized() && !isDeckGameMode) {
|
||||
mainWin!.maximize();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
mainWin.webContents.on("did-navigate", (_, url: string, responseCode: number) => {
|
||||
updateSplashMessage(""); // clear the splash message
|
||||
|
||||
// check url to ensure app doesn't loop
|
||||
if (responseCode >= 300 && new URL(url).pathname !== `/app`) {
|
||||
loadUrl(undefined);
|
||||
console.warn(`'did-navigate': Caught bad page response: ${responseCode}, redirecting to main app`);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/*
|
||||
* SPDX-License-Identifier: GPL-3.0
|
||||
* Vesktop, a desktop app aiming to give you a snappier Discord Experience
|
||||
* Copyright (c) 2023 Vendicated and Vencord contributors
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { session, systemPreferences } from "electron";
|
||||
@@ -12,11 +12,13 @@ export function registerMediaPermissionsHandler() {
|
||||
session.defaultSession.setPermissionRequestHandler(async (_webContents, permission, callback, details) => {
|
||||
let granted = true;
|
||||
|
||||
if (details.mediaTypes?.includes("audio")) {
|
||||
granted = await systemPreferences.askForMediaAccess("microphone");
|
||||
}
|
||||
if (details.mediaTypes?.includes("video")) {
|
||||
granted &&= await systemPreferences.askForMediaAccess("camera");
|
||||
if ("mediaTypes" in details) {
|
||||
if (details.mediaTypes?.includes("audio")) {
|
||||
granted &&= await systemPreferences.askForMediaAccess("microphone");
|
||||
}
|
||||
if (details.mediaTypes?.includes("video")) {
|
||||
granted &&= await systemPreferences.askForMediaAccess("camera");
|
||||
}
|
||||
}
|
||||
|
||||
callback(granted);
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
/*
|
||||
* SPDX-License-Identifier: GPL-3.0
|
||||
* Vesktop, a desktop app aiming to give you a snappier Discord Experience
|
||||
* Copyright (c) 2023 Vendicated and Vencord contributors
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { desktopCapturer, session, Streams } from "electron";
|
||||
import type { StreamPick } from "renderer/components/ScreenSharePicker";
|
||||
import { IpcEvents } from "shared/IpcEvents";
|
||||
import { IpcCommands, IpcEvents } from "shared/IpcEvents";
|
||||
|
||||
import { sendRendererCommand } from "./ipcCommands";
|
||||
import { handle } from "./utils/ipcWrappers";
|
||||
|
||||
const isWayland =
|
||||
@@ -49,11 +50,11 @@ export function registerScreenShareHandler() {
|
||||
if (isWayland) {
|
||||
const video = data[0];
|
||||
if (video) {
|
||||
const stream = await request.frame
|
||||
.executeJavaScript(
|
||||
`Vesktop.Components.ScreenShare.openScreenSharePicker(${JSON.stringify([video])},true)`
|
||||
)
|
||||
.catch(() => null);
|
||||
const stream = await sendRendererCommand<StreamPick>(IpcCommands.SCREEN_SHARE_PICKER, {
|
||||
screens: [video],
|
||||
skipPicker: true
|
||||
}).catch(() => null);
|
||||
|
||||
if (stream === null) return callback({});
|
||||
}
|
||||
|
||||
@@ -61,13 +62,13 @@ export function registerScreenShareHandler() {
|
||||
return;
|
||||
}
|
||||
|
||||
const choice = await request.frame
|
||||
.executeJavaScript(`Vesktop.Components.ScreenShare.openScreenSharePicker(${JSON.stringify(data)})`)
|
||||
.then(e => e as StreamPick)
|
||||
.catch(e => {
|
||||
console.error("Error during screenshare picker", e);
|
||||
return null;
|
||||
});
|
||||
const choice = await sendRendererCommand<StreamPick>(IpcCommands.SCREEN_SHARE_PICKER, {
|
||||
screens: data,
|
||||
skipPicker: false
|
||||
}).catch(e => {
|
||||
console.error("Error during screenshare picker", e);
|
||||
return null;
|
||||
});
|
||||
|
||||
if (!choice) return callback({});
|
||||
|
||||
|
||||
@@ -1,17 +1,18 @@
|
||||
/*
|
||||
* SPDX-License-Identifier: GPL-3.0
|
||||
* Vesktop, a desktop app aiming to give you a snappier Discord Experience
|
||||
* Copyright (c) 2023 Vendicated and Vencord contributors
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { mkdirSync, readFileSync, writeFileSync } from "fs";
|
||||
import { dirname, join } from "path";
|
||||
import type { Settings as TSettings } from "shared/settings";
|
||||
import type { Settings as TSettings, State as TState } from "shared/settings";
|
||||
import { SettingsStore } from "shared/utils/SettingsStore";
|
||||
|
||||
import { DATA_DIR, VENCORD_SETTINGS_FILE } from "./constants";
|
||||
|
||||
const SETTINGS_FILE = join(DATA_DIR, "settings.json");
|
||||
const STATE_FILE = join(DATA_DIR, "state.json");
|
||||
|
||||
function loadSettings<T extends object = any>(file: string, name: string) {
|
||||
let settings = {} as T;
|
||||
@@ -20,7 +21,7 @@ function loadSettings<T extends object = any>(file: string, name: string) {
|
||||
try {
|
||||
settings = JSON.parse(content);
|
||||
} catch (err) {
|
||||
console.error(`Failed to parse ${name} settings.json:`, err);
|
||||
console.error(`Failed to parse ${name}.json:`, err);
|
||||
}
|
||||
} catch {}
|
||||
|
||||
@@ -33,5 +34,6 @@ function loadSettings<T extends object = any>(file: string, name: string) {
|
||||
return store;
|
||||
}
|
||||
|
||||
export const Settings = loadSettings<TSettings>(SETTINGS_FILE, "Vesktop");
|
||||
export const VencordSettings = loadSettings<any>(VENCORD_SETTINGS_FILE, "Vencord");
|
||||
export const Settings = loadSettings<TSettings>(SETTINGS_FILE, "Vesktop settings");
|
||||
export const VencordSettings = loadSettings<any>(VENCORD_SETTINGS_FILE, "Vencord settings");
|
||||
export const State = loadSettings<TState>(STATE_FILE, "Vesktop state");
|
||||
|
||||
@@ -1,27 +1,32 @@
|
||||
/*
|
||||
* SPDX-License-Identifier: GPL-3.0
|
||||
* Vesktop, a desktop app aiming to give you a snappier Discord Experience
|
||||
* Copyright (c) 2023 Vendicated and Vencord contributors
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { BrowserWindow } from "electron";
|
||||
import { join } from "path";
|
||||
import { SplashProps } from "shared/browserWinProperties";
|
||||
import { ICON_PATH, VIEW_DIR } from "shared/paths";
|
||||
|
||||
import { Settings } from "./settings";
|
||||
import { loadView } from "./vesktopStatic";
|
||||
|
||||
export function createSplashWindow() {
|
||||
const splash = new BrowserWindow({
|
||||
let splash: BrowserWindow | undefined;
|
||||
|
||||
export function createSplashWindow(startMinimized = false) {
|
||||
splash = new BrowserWindow({
|
||||
...SplashProps,
|
||||
icon: ICON_PATH
|
||||
show: !startMinimized,
|
||||
webPreferences: {
|
||||
preload: join(__dirname, "splashPreload.js")
|
||||
}
|
||||
});
|
||||
|
||||
splash.loadFile(join(VIEW_DIR, "splash.html"));
|
||||
loadView(splash, "splash.html");
|
||||
|
||||
const { splashBackground, splashColor, splashTheming } = Settings.store;
|
||||
|
||||
if (splashTheming) {
|
||||
if (splashTheming !== false) {
|
||||
if (splashColor) {
|
||||
const semiTransparentSplashColor = splashColor.replace("rgb(", "rgba(").replace(")", ", 0.2)");
|
||||
|
||||
@@ -36,3 +41,7 @@ export function createSplashWindow() {
|
||||
|
||||
return splash;
|
||||
}
|
||||
|
||||
export function updateSplashMessage(message: string) {
|
||||
if (splash && !splash.isDestroyed()) splash.webContents.send("update-splash-message", message);
|
||||
}
|
||||
|
||||
92
src/main/tray.ts
Normal file
92
src/main/tray.ts
Normal file
@@ -0,0 +1,92 @@
|
||||
/*
|
||||
* Vesktop, a desktop app aiming to give you a snappier Discord Experience
|
||||
* Copyright (c) 2025 Vendicated and Vesktop contributors
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { app, BrowserWindow, Menu, Tray } from "electron";
|
||||
|
||||
import { createAboutWindow } from "./about";
|
||||
import { AppEvents } from "./events";
|
||||
import { Settings } from "./settings";
|
||||
import { resolveAssetPath } from "./userAssets";
|
||||
import { clearData } from "./utils/clearData";
|
||||
import { downloadVencordFiles } from "./utils/vencordLoader";
|
||||
|
||||
let tray: Tray;
|
||||
let trayVariant: "tray" | "trayUnread" = "tray";
|
||||
|
||||
AppEvents.on("userAssetChanged", async asset => {
|
||||
if (tray && (asset === "tray" || asset === "trayUnread")) {
|
||||
tray.setImage(await resolveAssetPath(trayVariant));
|
||||
}
|
||||
});
|
||||
|
||||
AppEvents.on("setTrayVariant", async variant => {
|
||||
if (trayVariant === variant) return;
|
||||
|
||||
trayVariant = variant;
|
||||
if (!tray) return;
|
||||
|
||||
tray.setImage(await resolveAssetPath(trayVariant));
|
||||
});
|
||||
|
||||
export function destroyTray() {
|
||||
tray?.destroy();
|
||||
}
|
||||
|
||||
export async function initTray(win: BrowserWindow, setIsQuitting: (val: boolean) => void) {
|
||||
const onTrayClick = () => {
|
||||
if (Settings.store.clickTrayToShowHide && win.isVisible()) win.hide();
|
||||
else win.show();
|
||||
};
|
||||
|
||||
const trayMenu = Menu.buildFromTemplate([
|
||||
{
|
||||
label: "Open",
|
||||
click() {
|
||||
win.show();
|
||||
}
|
||||
},
|
||||
{
|
||||
label: "About",
|
||||
click: createAboutWindow
|
||||
},
|
||||
{
|
||||
label: "Repair Vencord",
|
||||
async click() {
|
||||
await downloadVencordFiles();
|
||||
app.relaunch();
|
||||
app.quit();
|
||||
}
|
||||
},
|
||||
{
|
||||
label: "Reset Vesktop",
|
||||
async click() {
|
||||
await clearData(win);
|
||||
}
|
||||
},
|
||||
{
|
||||
type: "separator"
|
||||
},
|
||||
{
|
||||
label: "Restart",
|
||||
click() {
|
||||
app.relaunch();
|
||||
app.quit();
|
||||
}
|
||||
},
|
||||
{
|
||||
label: "Quit",
|
||||
click() {
|
||||
setIsQuitting(true);
|
||||
app.quit();
|
||||
}
|
||||
}
|
||||
]);
|
||||
|
||||
tray = new Tray(await resolveAssetPath(trayVariant));
|
||||
tray.setToolTip("Vesktop");
|
||||
tray.setContextMenu(trayMenu);
|
||||
tray.on("click", onTrayClick);
|
||||
}
|
||||
81
src/main/updater.ts
Normal file
81
src/main/updater.ts
Normal file
@@ -0,0 +1,81 @@
|
||||
/*
|
||||
* Vesktop, a desktop app aiming to give you a snappier Discord Experience
|
||||
* Copyright (c) 2025 Vendicated and Vesktop contributors
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { app, BrowserWindow, ipcMain } from "electron";
|
||||
import { autoUpdater, UpdateInfo } from "electron-updater";
|
||||
import { join } from "path";
|
||||
import { IpcEvents, UpdaterIpcEvents } from "shared/IpcEvents";
|
||||
import { Millis } from "shared/utils/millis";
|
||||
|
||||
import { State } from "./settings";
|
||||
import { handle } from "./utils/ipcWrappers";
|
||||
import { makeLinksOpenExternally } from "./utils/makeLinksOpenExternally";
|
||||
import { loadView } from "./vesktopStatic";
|
||||
|
||||
let updaterWindow: BrowserWindow | null = null;
|
||||
|
||||
autoUpdater.on("update-available", update => {
|
||||
if (State.store.updater?.ignoredVersion === update.version) return;
|
||||
if ((State.store.updater?.snoozeUntil ?? 0) > Date.now()) return;
|
||||
|
||||
openUpdater(update);
|
||||
});
|
||||
|
||||
autoUpdater.on("update-downloaded", () => setTimeout(() => autoUpdater.quitAndInstall(), 100));
|
||||
autoUpdater.on("download-progress", p =>
|
||||
updaterWindow?.webContents.send(UpdaterIpcEvents.DOWNLOAD_PROGRESS, p.percent)
|
||||
);
|
||||
autoUpdater.on("error", err => updaterWindow?.webContents.send(UpdaterIpcEvents.ERROR, err.message));
|
||||
|
||||
autoUpdater.autoDownload = false;
|
||||
autoUpdater.autoInstallOnAppQuit = false;
|
||||
autoUpdater.fullChangelog = true;
|
||||
|
||||
const isOutdated = autoUpdater.checkForUpdates().then(res => Boolean(res?.isUpdateAvailable));
|
||||
|
||||
handle(IpcEvents.UPDATER_IS_OUTDATED, () => isOutdated);
|
||||
handle(IpcEvents.UPDATER_OPEN, async () => {
|
||||
const res = await autoUpdater.checkForUpdates();
|
||||
if (res?.isUpdateAvailable && res.updateInfo) openUpdater(res.updateInfo);
|
||||
});
|
||||
|
||||
function openUpdater(update: UpdateInfo) {
|
||||
updaterWindow = new BrowserWindow({
|
||||
title: "Vesktop Updater",
|
||||
autoHideMenuBar: true,
|
||||
webPreferences: {
|
||||
preload: join(__dirname, "updaterPreload.js")
|
||||
},
|
||||
minHeight: 400,
|
||||
minWidth: 750
|
||||
});
|
||||
makeLinksOpenExternally(updaterWindow);
|
||||
|
||||
handle(UpdaterIpcEvents.GET_DATA, () => ({ update, version: app.getVersion() }));
|
||||
handle(UpdaterIpcEvents.INSTALL, async () => {
|
||||
await autoUpdater.downloadUpdate();
|
||||
});
|
||||
handle(UpdaterIpcEvents.SNOOZE_UPDATE, () => {
|
||||
State.store.updater ??= {};
|
||||
State.store.updater.snoozeUntil = Date.now() + 1 * Millis.DAY;
|
||||
updaterWindow?.close();
|
||||
});
|
||||
handle(UpdaterIpcEvents.IGNORE_UPDATE, () => {
|
||||
State.store.updater ??= {};
|
||||
State.store.updater.ignoredVersion = update.version;
|
||||
updaterWindow?.close();
|
||||
});
|
||||
|
||||
updaterWindow.on("closed", () => {
|
||||
ipcMain.removeHandler(UpdaterIpcEvents.GET_DATA);
|
||||
ipcMain.removeHandler(UpdaterIpcEvents.INSTALL);
|
||||
ipcMain.removeHandler(UpdaterIpcEvents.SNOOZE_UPDATE);
|
||||
ipcMain.removeHandler(UpdaterIpcEvents.IGNORE_UPDATE);
|
||||
updaterWindow = null;
|
||||
});
|
||||
|
||||
loadView(updaterWindow, "updater/index.html");
|
||||
}
|
||||
101
src/main/userAssets.ts
Normal file
101
src/main/userAssets.ts
Normal file
@@ -0,0 +1,101 @@
|
||||
/*
|
||||
* Vesktop, a desktop app aiming to give you a snappier Discord Experience
|
||||
* Copyright (c) 2025 Vendicated and Vesktop contributors
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { app, dialog, net } from "electron";
|
||||
import { copyFile, mkdir, rm } from "fs/promises";
|
||||
import { join } from "path";
|
||||
import { IpcEvents } from "shared/IpcEvents";
|
||||
import { STATIC_DIR } from "shared/paths";
|
||||
import { pathToFileURL } from "url";
|
||||
|
||||
import { DATA_DIR } from "./constants";
|
||||
import { AppEvents } from "./events";
|
||||
import { mainWin } from "./mainWindow";
|
||||
import { fileExistsAsync } from "./utils/fileExists";
|
||||
import { handle } from "./utils/ipcWrappers";
|
||||
|
||||
const CUSTOMIZABLE_ASSETS = ["splash", "tray", "trayUnread"] as const;
|
||||
export type UserAssetType = (typeof CUSTOMIZABLE_ASSETS)[number];
|
||||
|
||||
const DEFAULT_ASSETS: Record<UserAssetType, string> = {
|
||||
splash: "splash.webp",
|
||||
tray: `tray/${process.platform === "darwin" ? "trayTemplate" : "tray"}.png`,
|
||||
trayUnread: "tray/trayUnread.png"
|
||||
};
|
||||
|
||||
const UserAssetFolder = join(DATA_DIR, "userAssets");
|
||||
|
||||
export async function resolveAssetPath(asset: UserAssetType) {
|
||||
if (!CUSTOMIZABLE_ASSETS.includes(asset)) {
|
||||
throw new Error(`Invalid asset: ${asset}`);
|
||||
}
|
||||
|
||||
const assetPath = join(UserAssetFolder, asset);
|
||||
if (await fileExistsAsync(assetPath)) {
|
||||
return assetPath;
|
||||
}
|
||||
|
||||
return join(STATIC_DIR, DEFAULT_ASSETS[asset]);
|
||||
}
|
||||
|
||||
export async function handleVesktopAssetsProtocol(path: string, req: Request) {
|
||||
const asset = path.slice(1);
|
||||
|
||||
// @ts-expect-error dumb types
|
||||
if (!CUSTOMIZABLE_ASSETS.includes(asset)) {
|
||||
return new Response(null, { status: 404 });
|
||||
}
|
||||
|
||||
try {
|
||||
const res = await net.fetch(pathToFileURL(join(UserAssetFolder, asset)).href);
|
||||
if (res.ok) return res;
|
||||
} catch {}
|
||||
|
||||
return net.fetch(pathToFileURL(join(STATIC_DIR, DEFAULT_ASSETS[asset])).href);
|
||||
}
|
||||
|
||||
handle(IpcEvents.CHOOSE_USER_ASSET, async (_event, asset: UserAssetType, value?: null) => {
|
||||
if (!CUSTOMIZABLE_ASSETS.includes(asset)) {
|
||||
throw `Invalid asset: ${asset}`;
|
||||
}
|
||||
|
||||
const assetPath = join(UserAssetFolder, asset);
|
||||
|
||||
if (value === null) {
|
||||
try {
|
||||
await rm(assetPath, { force: true });
|
||||
AppEvents.emit("userAssetChanged", asset);
|
||||
return "ok";
|
||||
} catch (e) {
|
||||
console.error(`Failed to remove user asset ${asset}:`, e);
|
||||
return "failed";
|
||||
}
|
||||
}
|
||||
|
||||
const res = await dialog.showOpenDialog(mainWin, {
|
||||
properties: ["openFile"],
|
||||
title: `Select an image to use as ${asset}`,
|
||||
defaultPath: app.getPath("pictures"),
|
||||
filters: [
|
||||
{
|
||||
name: "Images",
|
||||
extensions: ["png", "jpg", "jpeg", "webp", "gif", "avif", "svg"]
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
if (res.canceled || !res.filePaths.length) return "cancelled";
|
||||
|
||||
try {
|
||||
await mkdir(UserAssetFolder, { recursive: true });
|
||||
await copyFile(res.filePaths[0], assetPath);
|
||||
AppEvents.emit("userAssetChanged", asset);
|
||||
return "ok";
|
||||
} catch (e) {
|
||||
console.error(`Failed to copy user asset ${asset}:`, e);
|
||||
return "failed";
|
||||
}
|
||||
});
|
||||
32
src/main/utils/clearData.ts
Normal file
32
src/main/utils/clearData.ts
Normal file
@@ -0,0 +1,32 @@
|
||||
/*
|
||||
* Vesktop, a desktop app aiming to give you a snappier Discord Experience
|
||||
* Copyright (c) 2025 Vendicated and Vesktop contributors
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { app, BrowserWindow, dialog } from "electron";
|
||||
import { rm } from "fs/promises";
|
||||
import { DATA_DIR, MessageBoxChoice } from "main/constants";
|
||||
|
||||
export async function clearData(win: BrowserWindow) {
|
||||
const { response } = await dialog.showMessageBox(win, {
|
||||
message: "Are you sure you want to reset Vesktop?",
|
||||
detail: "This will log you out, clear caches and reset all your settings!\n\nVesktop will automatically restart after this operation.",
|
||||
buttons: ["Yes", "No"],
|
||||
cancelId: MessageBoxChoice.Cancel,
|
||||
defaultId: MessageBoxChoice.Default,
|
||||
type: "warning"
|
||||
});
|
||||
|
||||
if (response === MessageBoxChoice.Cancel) return;
|
||||
|
||||
win.close();
|
||||
|
||||
await win.webContents.session.clearStorageData();
|
||||
await win.webContents.session.clearCache();
|
||||
await win.webContents.session.clearCodeCaches({});
|
||||
await rm(DATA_DIR, { force: true, recursive: true });
|
||||
|
||||
app.relaunch();
|
||||
app.quit();
|
||||
}
|
||||
56
src/main/utils/desktopFileEscape.ts
Normal file
56
src/main/utils/desktopFileEscape.ts
Normal file
@@ -0,0 +1,56 @@
|
||||
/*
|
||||
* Vesktop, a desktop app aiming to give you a snappier Discord Experience
|
||||
* Copyright (c) 2025 Vendicated and Vesktop contributors
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
// https://specifications.freedesktop.org/desktop-entry-spec/latest/exec-variables.html
|
||||
|
||||
// "If an argument contains a reserved character the argument must be quoted."
|
||||
const desktopFileReservedChars = new Set([
|
||||
" ",
|
||||
"\t",
|
||||
"\n",
|
||||
'"',
|
||||
"'",
|
||||
"\\",
|
||||
">",
|
||||
"<",
|
||||
"~",
|
||||
"|",
|
||||
"&",
|
||||
";",
|
||||
"$",
|
||||
"*",
|
||||
"?",
|
||||
"#",
|
||||
"(",
|
||||
")",
|
||||
"`"
|
||||
]);
|
||||
|
||||
export function escapeDesktopFileArgument(arg: string) {
|
||||
let needsQuoting = false;
|
||||
let out = "";
|
||||
|
||||
for (const c of arg) {
|
||||
if (desktopFileReservedChars.has(c)) {
|
||||
// "Quoting must be done by enclosing the argument between double quotes"
|
||||
needsQuoting = true;
|
||||
// "and escaping the double quote character, backtick character ("`"), dollar sign ("$")
|
||||
// and backslash character ("\") by preceding it with an additional backslash character"
|
||||
if (c === '"' || c === "`" || c === "$" || c === "\\") {
|
||||
out += "\\";
|
||||
}
|
||||
}
|
||||
|
||||
// "Literal percentage characters must be escaped as %%"
|
||||
if (c === "%") {
|
||||
out += "%%";
|
||||
} else {
|
||||
out += c;
|
||||
}
|
||||
}
|
||||
|
||||
return needsQuoting ? `"${out}"` : out;
|
||||
}
|
||||
13
src/main/utils/fileExists.ts
Normal file
13
src/main/utils/fileExists.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
/*
|
||||
* Vesktop, a desktop app aiming to give you a snappier Discord Experience
|
||||
* Copyright (c) 2025 Vendicated and Vesktop contributors
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { access, constants } from "fs/promises";
|
||||
|
||||
export async function fileExistsAsync(path: string) {
|
||||
return await access(path, constants.F_OK)
|
||||
.then(() => true)
|
||||
.catch(() => false);
|
||||
}
|
||||
@@ -1,45 +1,58 @@
|
||||
/*
|
||||
* SPDX-License-Identifier: GPL-3.0
|
||||
* Vesktop, a desktop app aiming to give you a snappier Discord Experience
|
||||
* Copyright (c) 2023 Vendicated and Vencord contributors
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { createWriteStream } from "fs";
|
||||
import type { IncomingMessage } from "http";
|
||||
import { get, RequestOptions } from "https";
|
||||
import { finished } from "stream/promises";
|
||||
import { Readable } from "stream";
|
||||
import { pipeline } from "stream/promises";
|
||||
import { setTimeout } from "timers/promises";
|
||||
|
||||
export async function downloadFile(url: string, file: string, options: RequestOptions = {}) {
|
||||
const res = await simpleReq(url, options);
|
||||
await finished(
|
||||
res.pipe(
|
||||
createWriteStream(file, {
|
||||
autoClose: true
|
||||
})
|
||||
)
|
||||
interface FetchieOptions {
|
||||
retryOnNetworkError?: boolean;
|
||||
}
|
||||
|
||||
export async function downloadFile(url: string, file: string, options: RequestInit = {}, fetchieOpts?: FetchieOptions) {
|
||||
const res = await fetchie(url, options, fetchieOpts);
|
||||
await pipeline(
|
||||
// @ts-expect-error odd type error
|
||||
Readable.fromWeb(res.body!),
|
||||
createWriteStream(file, {
|
||||
autoClose: true
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
export function simpleReq(url: string, options: RequestOptions = {}) {
|
||||
return new Promise<IncomingMessage>((resolve, reject) => {
|
||||
get(url, options, res => {
|
||||
const { statusCode, statusMessage, headers } = res;
|
||||
if (statusCode! >= 400) return void reject(`${statusCode}: ${statusMessage} - ${url}`);
|
||||
if (statusCode! >= 300) return simpleReq(headers.location!, options).then(resolve).catch(reject);
|
||||
const ONE_MINUTE_MS = 1000 * 60;
|
||||
|
||||
resolve(res);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
export async function simpleGet(url: string, options: RequestOptions = {}) {
|
||||
const res = await simpleReq(url, options);
|
||||
|
||||
return new Promise<Buffer>((resolve, reject) => {
|
||||
const chunks = [] as Buffer[];
|
||||
|
||||
res.once("error", reject);
|
||||
res.on("data", chunk => chunks.push(chunk));
|
||||
res.once("end", () => resolve(Buffer.concat(chunks)));
|
||||
});
|
||||
export async function fetchie(url: string, options?: RequestInit, { retryOnNetworkError }: FetchieOptions = {}) {
|
||||
let res: Response | undefined;
|
||||
|
||||
try {
|
||||
res = await fetch(url, options);
|
||||
} catch (err) {
|
||||
if (retryOnNetworkError) {
|
||||
console.error("Failed to fetch", url + ".", "Gonna retry with backoff.");
|
||||
|
||||
for (let tries = 0, delayMs = 500; tries < 20; tries++, delayMs = Math.min(2 * delayMs, ONE_MINUTE_MS)) {
|
||||
await setTimeout(delayMs);
|
||||
try {
|
||||
res = await fetch(url, options);
|
||||
break;
|
||||
} catch {}
|
||||
}
|
||||
}
|
||||
|
||||
if (!res) throw new Error(`Failed to fetch ${url}\n${err}`);
|
||||
}
|
||||
|
||||
if (res.ok) return res;
|
||||
|
||||
let msg = `Got non-OK response for ${url}: ${res.status} ${res.statusText}`;
|
||||
|
||||
const reason = await res.text().catch(() => "");
|
||||
if (reason) msg += `\n${reason}`;
|
||||
|
||||
throw new Error(msg);
|
||||
}
|
||||
|
||||
@@ -1,36 +1,40 @@
|
||||
/*
|
||||
* SPDX-License-Identifier: GPL-3.0
|
||||
* Vesktop, a desktop app aiming to give you a snappier Discord Experience
|
||||
* Copyright (c) 2023 Vendicated and Vencord contributors
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { ipcMain, IpcMainEvent, IpcMainInvokeEvent, WebFrameMain } from "electron";
|
||||
import { IpcEvents } from "shared/IpcEvents";
|
||||
import { DISCORD_HOSTNAMES } from "main/constants";
|
||||
import { IpcEvents, UpdaterIpcEvents } from "shared/IpcEvents";
|
||||
|
||||
export function validateSender(frame: WebFrameMain) {
|
||||
const { hostname, protocol } = new URL(frame.url);
|
||||
if (protocol === "file:") return;
|
||||
export function validateSender(frame: WebFrameMain | null, event: string) {
|
||||
if (!frame) throw new Error(`ipc[${event}]: No sender frame`);
|
||||
if (!frame.url) return;
|
||||
|
||||
switch (hostname) {
|
||||
case "discord.com":
|
||||
case "ptb.discord.com":
|
||||
case "canary.discord.com":
|
||||
break;
|
||||
default:
|
||||
throw new Error("ipc: Disallowed host " + hostname);
|
||||
try {
|
||||
var { hostname, protocol } = new URL(frame.url);
|
||||
} catch (e) {
|
||||
throw new Error(`ipc[${event}]: Invalid URL ${frame.url}`);
|
||||
}
|
||||
|
||||
if (protocol === "file:" || protocol === "vesktop:") return;
|
||||
|
||||
if (!DISCORD_HOSTNAMES.includes(hostname)) {
|
||||
throw new Error(`ipc[${event}]: Disallowed hostname ${hostname}`);
|
||||
}
|
||||
}
|
||||
|
||||
export function handleSync(event: IpcEvents, cb: (e: IpcMainEvent, ...args: any[]) => any) {
|
||||
export function handleSync(event: IpcEvents | UpdaterIpcEvents, cb: (e: IpcMainEvent, ...args: any[]) => any) {
|
||||
ipcMain.on(event, (e, ...args) => {
|
||||
validateSender(e.senderFrame);
|
||||
validateSender(e.senderFrame, event);
|
||||
e.returnValue = cb(e, ...args);
|
||||
});
|
||||
}
|
||||
|
||||
export function handle(event: IpcEvents, cb: (e: IpcMainInvokeEvent, ...args: any[]) => any) {
|
||||
export function handle(event: IpcEvents | UpdaterIpcEvents, cb: (e: IpcMainInvokeEvent, ...args: any[]) => any) {
|
||||
ipcMain.handle(event, (e, ...args) => {
|
||||
validateSender(e.senderFrame);
|
||||
validateSender(e.senderFrame, event);
|
||||
return cb(e, ...args);
|
||||
});
|
||||
}
|
||||
|
||||
16
src/main/utils/isPathInDirectory.ts
Normal file
16
src/main/utils/isPathInDirectory.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
/*
|
||||
* Vesktop, a desktop app aiming to give you a snappier Discord Experience
|
||||
* Copyright (c) 2025 Vendicated and Vesktop contributors
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { resolve, sep } from "path";
|
||||
|
||||
export function isPathInDirectory(filePath: string, directory: string) {
|
||||
const resolvedPath = resolve(filePath);
|
||||
const resolvedDirectory = resolve(directory);
|
||||
|
||||
const normalizedDirectory = resolvedDirectory.endsWith(sep) ? resolvedDirectory : resolvedDirectory + sep;
|
||||
|
||||
return resolvedPath.startsWith(normalizedDirectory) || resolvedPath === resolvedDirectory;
|
||||
}
|
||||
@@ -1,40 +1,73 @@
|
||||
/*
|
||||
* SPDX-License-Identifier: GPL-3.0
|
||||
* Vesktop, a desktop app aiming to give you a snappier Discord Experience
|
||||
* Copyright (c) 2023 Vendicated and Vencord contributors
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { BrowserWindow, shell } from "electron";
|
||||
import { DISCORD_HOSTNAMES } from "main/constants";
|
||||
|
||||
import { Settings } from "../settings";
|
||||
import { createOrFocusPopup, setupPopout } from "./popout";
|
||||
import { execSteamURL, isDeckGameMode, steamOpenURL } from "./steamOS";
|
||||
|
||||
export function handleExternalUrl(url: string, protocol?: string): { action: "deny" | "allow" } {
|
||||
if (protocol == null) {
|
||||
try {
|
||||
protocol = new URL(url).protocol;
|
||||
} catch {
|
||||
return { action: "deny" };
|
||||
}
|
||||
}
|
||||
|
||||
switch (protocol) {
|
||||
case "http:":
|
||||
case "https:":
|
||||
if (Settings.store.openLinksWithElectron) {
|
||||
return { action: "allow" };
|
||||
}
|
||||
// eslint-disable-next-line no-fallthrough
|
||||
case "mailto:":
|
||||
case "spotify:":
|
||||
if (isDeckGameMode) {
|
||||
steamOpenURL(url);
|
||||
} else {
|
||||
shell.openExternal(url);
|
||||
}
|
||||
break;
|
||||
case "steam:":
|
||||
if (isDeckGameMode) {
|
||||
execSteamURL(url);
|
||||
} else {
|
||||
shell.openExternal(url);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
return { action: "deny" };
|
||||
}
|
||||
|
||||
export function makeLinksOpenExternally(win: BrowserWindow) {
|
||||
win.webContents.setWindowOpenHandler(({ url }) => {
|
||||
switch (url) {
|
||||
case "about:blank":
|
||||
case "https://discord.com/popout":
|
||||
return { action: "allow" };
|
||||
}
|
||||
|
||||
win.webContents.setWindowOpenHandler(({ url, frameName, features }) => {
|
||||
try {
|
||||
var { protocol } = new URL(url);
|
||||
var { protocol, hostname, pathname, searchParams } = new URL(url);
|
||||
} catch {
|
||||
return { action: "deny" };
|
||||
}
|
||||
|
||||
switch (protocol) {
|
||||
case "http:":
|
||||
case "https:":
|
||||
if (Settings.store.openLinksWithElectron) {
|
||||
return { action: "allow" };
|
||||
}
|
||||
// eslint-disable-next-line no-fallthrough
|
||||
case "mailto:":
|
||||
case "steam:":
|
||||
case "spotify:":
|
||||
shell.openExternal(url);
|
||||
if (frameName.startsWith("DISCORD_") && pathname === "/popout" && DISCORD_HOSTNAMES.includes(hostname)) {
|
||||
return createOrFocusPopup(frameName, features);
|
||||
}
|
||||
|
||||
return { action: "deny" };
|
||||
if (url === "about:blank") return { action: "allow" };
|
||||
|
||||
// Drop the static temp page Discord web loads for the connections popout
|
||||
if (frameName === "authorize" && searchParams.get("loading") === "true") return { action: "deny" };
|
||||
|
||||
return handleExternalUrl(url, protocol);
|
||||
});
|
||||
|
||||
win.webContents.on("did-create-window", (win, { frameName }) => {
|
||||
if (frameName.startsWith("DISCORD_")) setupPopout(win, frameName);
|
||||
});
|
||||
}
|
||||
|
||||
116
src/main/utils/popout.ts
Normal file
116
src/main/utils/popout.ts
Normal file
@@ -0,0 +1,116 @@
|
||||
/*
|
||||
* Vesktop, a desktop app aiming to give you a snappier Discord Experience
|
||||
* Copyright (c) 2023 Vendicated and Vencord contributors
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { BrowserWindow, BrowserWindowConstructorOptions } from "electron";
|
||||
import { Settings } from "main/settings";
|
||||
|
||||
import { handleExternalUrl } from "./makeLinksOpenExternally";
|
||||
|
||||
const ALLOWED_FEATURES = new Set([
|
||||
"width",
|
||||
"height",
|
||||
"left",
|
||||
"top",
|
||||
"resizable",
|
||||
"movable",
|
||||
"alwaysOnTop",
|
||||
"frame",
|
||||
"transparent",
|
||||
"hasShadow",
|
||||
"closable",
|
||||
"skipTaskbar",
|
||||
"backgroundColor",
|
||||
"menubar",
|
||||
"toolbar",
|
||||
"location",
|
||||
"directories",
|
||||
"titleBarStyle"
|
||||
]);
|
||||
|
||||
const MIN_POPOUT_WIDTH = 320;
|
||||
const MIN_POPOUT_HEIGHT = 180;
|
||||
const DEFAULT_POPOUT_OPTIONS: BrowserWindowConstructorOptions = {
|
||||
title: "Discord Popout",
|
||||
backgroundColor: "#2f3136",
|
||||
minWidth: MIN_POPOUT_WIDTH,
|
||||
minHeight: MIN_POPOUT_HEIGHT,
|
||||
frame: Settings.store.customTitleBar !== true,
|
||||
titleBarStyle: process.platform === "darwin" ? "hidden" : undefined,
|
||||
trafficLightPosition:
|
||||
process.platform === "darwin"
|
||||
? {
|
||||
x: 10,
|
||||
y: 3
|
||||
}
|
||||
: undefined,
|
||||
webPreferences: {
|
||||
nodeIntegration: false,
|
||||
contextIsolation: true
|
||||
},
|
||||
autoHideMenuBar: Settings.store.enableMenu
|
||||
};
|
||||
|
||||
export const PopoutWindows = new Map<string, BrowserWindow>();
|
||||
|
||||
function focusWindow(window: BrowserWindow) {
|
||||
window.setAlwaysOnTop(true);
|
||||
window.focus();
|
||||
window.setAlwaysOnTop(false);
|
||||
}
|
||||
|
||||
function parseFeatureValue(feature: string) {
|
||||
if (feature === "yes") return true;
|
||||
if (feature === "no") return false;
|
||||
|
||||
const n = Number(feature);
|
||||
if (!isNaN(n)) return n;
|
||||
|
||||
return feature;
|
||||
}
|
||||
|
||||
function parseWindowFeatures(features: string) {
|
||||
const keyValuesParsed = features.split(",");
|
||||
|
||||
return keyValuesParsed.reduce((features, feature) => {
|
||||
const [key, value] = feature.split("=");
|
||||
if (ALLOWED_FEATURES.has(key)) features[key] = parseFeatureValue(value);
|
||||
|
||||
return features;
|
||||
}, {});
|
||||
}
|
||||
|
||||
export function createOrFocusPopup(key: string, features: string) {
|
||||
const existingWindow = PopoutWindows.get(key);
|
||||
if (existingWindow) {
|
||||
focusWindow(existingWindow);
|
||||
return <const>{ action: "deny" };
|
||||
}
|
||||
|
||||
return <const>{
|
||||
action: "allow",
|
||||
overrideBrowserWindowOptions: {
|
||||
...DEFAULT_POPOUT_OPTIONS,
|
||||
...parseWindowFeatures(features)
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export function setupPopout(win: BrowserWindow, key: string) {
|
||||
win.setMenuBarVisibility(false);
|
||||
|
||||
PopoutWindows.set(key, win);
|
||||
|
||||
/* win.webContents.on("will-navigate", (evt, url) => {
|
||||
// maybe prevent if not origin match
|
||||
})*/
|
||||
|
||||
win.webContents.setWindowOpenHandler(({ url }) => handleExternalUrl(url));
|
||||
|
||||
win.once("closed", () => {
|
||||
win.removeAllListeners();
|
||||
PopoutWindows.delete(key);
|
||||
});
|
||||
}
|
||||
29
src/main/utils/setAsDefaultProtocolClient.ts
Normal file
29
src/main/utils/setAsDefaultProtocolClient.ts
Normal file
@@ -0,0 +1,29 @@
|
||||
/*
|
||||
* Vesktop, a desktop app aiming to give you a snappier Discord Experience
|
||||
* Copyright (c) 2025 Vendicated and Vesktop contributors
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { execFile } from "child_process";
|
||||
import { app } from "electron";
|
||||
|
||||
export async function setAsDefaultProtocolClient(protocol: string) {
|
||||
if (process.platform !== "linux") {
|
||||
return app.setAsDefaultProtocolClient(protocol);
|
||||
}
|
||||
|
||||
// electron setAsDefaultProtocolClient uses xdg-settings instead of xdg-mime.
|
||||
// xdg-settings had a bug where it would also register the app as a handler for text/html,
|
||||
// aka become your browser. This bug was fixed years ago (xdg-utils 1.2.0) but Ubuntu ships
|
||||
// 7 (YES, SEVEN) years out of date xdg-utils which STILL has the bug.
|
||||
// FIXME: remove this workaround when Ubuntu updates their xdg-utils or electron switches to xdg-mime.
|
||||
|
||||
const { CHROME_DESKTOP } = process.env;
|
||||
if (!CHROME_DESKTOP) return false;
|
||||
|
||||
return new Promise<boolean>(resolve => {
|
||||
execFile("xdg-mime", ["default", CHROME_DESKTOP, `x-scheme-handler/${protocol}`], err => {
|
||||
resolve(err == null);
|
||||
});
|
||||
});
|
||||
}
|
||||
97
src/main/utils/steamOS.ts
Normal file
97
src/main/utils/steamOS.ts
Normal file
@@ -0,0 +1,97 @@
|
||||
/*
|
||||
* Vesktop, a desktop app aiming to give you a snappier Discord Experience
|
||||
* Copyright (c) 2023 Vendicated and Vencord contributors
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { BrowserWindow, dialog } from "electron";
|
||||
import { writeFile } from "fs/promises";
|
||||
import { join } from "path";
|
||||
|
||||
import { MessageBoxChoice } from "../constants";
|
||||
import { State } from "../settings";
|
||||
|
||||
// Bump this to re-show the prompt
|
||||
const layoutVersion = 2;
|
||||
// Get this from "show details" on the profile after exporting as a shared personal layout or using share with community
|
||||
const layoutId = "3080264545"; // Vesktop Layout v2
|
||||
const numberRegex = /^[0-9]*$/;
|
||||
|
||||
let steamPipeQueue = Promise.resolve();
|
||||
|
||||
export const isDeckGameMode = process.env.SteamOS === "1" && process.env.SteamGamepadUI === "1";
|
||||
|
||||
export function applyDeckKeyboardFix() {
|
||||
if (!isDeckGameMode) return;
|
||||
// Prevent constant virtual keyboard spam that eventually crashes Steam.
|
||||
process.env.GTK_IM_MODULE = "None";
|
||||
}
|
||||
|
||||
// For some reason SteamAppId is always 0 for non-steam apps so we do this insanity instead.
|
||||
function getAppId(): string | null {
|
||||
// /home/deck/.local/share/Steam/steamapps/shadercache/APPID/fozmediav1
|
||||
const path = process.env.STEAM_COMPAT_MEDIA_PATH;
|
||||
if (!path) return null;
|
||||
const pathElems = path?.split("/");
|
||||
const appId = pathElems[pathElems.length - 2];
|
||||
if (appId.match(numberRegex)) {
|
||||
console.log(`Got Steam App ID ${appId}`);
|
||||
return appId;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
export function execSteamURL(url: string) {
|
||||
// This doesn't allow arbitrary execution despite the weird syntax.
|
||||
steamPipeQueue = steamPipeQueue.then(() =>
|
||||
writeFile(
|
||||
join(process.env.HOME || "/home/deck", ".steam", "steam.pipe"),
|
||||
// replace ' to prevent argument injection
|
||||
`'${process.env.HOME}/.local/share/Steam/ubuntu12_32/steam' '-ifrunning' '${url.replaceAll("'", "%27")}'\n`,
|
||||
"utf-8"
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
export function steamOpenURL(url: string) {
|
||||
execSteamURL(`steam://openurl/${url}`);
|
||||
}
|
||||
|
||||
export async function showGamePage() {
|
||||
const appId = getAppId();
|
||||
if (!appId) return;
|
||||
await execSteamURL(`steam://nav/games/details/${appId}`);
|
||||
}
|
||||
|
||||
async function showLayout(appId: string) {
|
||||
execSteamURL(`steam://controllerconfig/${appId}/${layoutId}`);
|
||||
}
|
||||
|
||||
export async function askToApplySteamLayout(win: BrowserWindow) {
|
||||
const appId = getAppId();
|
||||
if (!appId) return;
|
||||
if (State.store.steamOSLayoutVersion === layoutVersion) return;
|
||||
const update = Boolean(State.store.steamOSLayoutVersion);
|
||||
|
||||
// Touch screen breaks in some menus when native touch mode is enabled on latest SteamOS beta, remove most of the update specific text once that's fixed.
|
||||
const { response } = await dialog.showMessageBox(win, {
|
||||
message: `${update ? "Update" : "Apply"} Vesktop Steam Input Layout?`,
|
||||
detail: `Would you like to ${update ? "Update" : "Apply"} Vesktop's recommended Steam Deck controller settings?
|
||||
${update ? "Click yes using the touchpad" : "Tap yes"}, then press the X button or tap Apply Layout to confirm.${
|
||||
update ? " Doing so will undo any customizations you have made." : ""
|
||||
}
|
||||
${update ? "Click" : "Tap"} no to keep your current layout.`,
|
||||
buttons: ["Yes", "No"],
|
||||
cancelId: MessageBoxChoice.Cancel,
|
||||
defaultId: MessageBoxChoice.Default,
|
||||
type: "question"
|
||||
});
|
||||
|
||||
if (State.store.steamOSLayoutVersion !== layoutVersion) {
|
||||
State.store.steamOSLayoutVersion = layoutVersion;
|
||||
}
|
||||
|
||||
if (response === MessageBoxChoice.Cancel) return;
|
||||
|
||||
await showLayout(appId);
|
||||
}
|
||||
@@ -1,15 +1,16 @@
|
||||
/*
|
||||
* SPDX-License-Identifier: GPL-3.0
|
||||
* Vesktop, a desktop app aiming to give you a snappier Discord Experience
|
||||
* Copyright (c) 2023 Vendicated and Vencord contributors
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { existsSync, mkdirSync } from "fs";
|
||||
import type { RequestOptions } from "https";
|
||||
import { mkdirSync } from "fs";
|
||||
import { access, constants as FsConstants, writeFile } from "fs/promises";
|
||||
import { VENCORD_FILES_DIR } from "main/vencordFilesDir";
|
||||
import { join } from "path";
|
||||
|
||||
import { USER_AGENT, VENCORD_FILES_DIR } from "../constants";
|
||||
import { downloadFile, simpleGet } from "./http";
|
||||
import { USER_AGENT } from "../constants";
|
||||
import { downloadFile, fetchie } from "./http";
|
||||
|
||||
const API_BASE = "https://api.github.com";
|
||||
|
||||
@@ -31,37 +32,46 @@ export interface ReleaseData {
|
||||
}
|
||||
|
||||
export async function githubGet(endpoint: string) {
|
||||
const opts: RequestOptions = {
|
||||
const opts: RequestInit = {
|
||||
headers: {
|
||||
Accept: "application/vnd.github+json",
|
||||
"User-Agent": USER_AGENT
|
||||
}
|
||||
};
|
||||
|
||||
if (process.env.GITHUB_TOKEN) opts.headers!.Authorization = `Bearer ${process.env.GITHUB_TOKEN}`;
|
||||
if (process.env.GITHUB_TOKEN) (opts.headers! as any).Authorization = `Bearer ${process.env.GITHUB_TOKEN}`;
|
||||
|
||||
return simpleGet(API_BASE + endpoint, opts);
|
||||
return fetchie(API_BASE + endpoint, opts, { retryOnNetworkError: true });
|
||||
}
|
||||
|
||||
export async function downloadVencordFiles() {
|
||||
const release = await githubGet("/repos/Vendicated/Vencord/releases/latest");
|
||||
|
||||
const { assets } = JSON.parse(release.toString("utf-8")) as ReleaseData;
|
||||
const { assets }: ReleaseData = await release.json();
|
||||
|
||||
await Promise.all(
|
||||
assets
|
||||
.filter(({ name }) => FILES_TO_DOWNLOAD.some(f => name.startsWith(f)))
|
||||
.map(({ name, browser_download_url }) => downloadFile(browser_download_url, join(VENCORD_FILES_DIR, name)))
|
||||
.map(({ name, browser_download_url }) =>
|
||||
downloadFile(browser_download_url, join(VENCORD_FILES_DIR, name), {}, { retryOnNetworkError: true })
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
export function isValidVencordInstall(dir: string) {
|
||||
return FILES_TO_DOWNLOAD.every(f => existsSync(join(dir, f)));
|
||||
const existsAsync = (path: string) =>
|
||||
access(path, FsConstants.F_OK)
|
||||
.then(() => true)
|
||||
.catch(() => false);
|
||||
|
||||
export async function isValidVencordInstall(dir: string) {
|
||||
const results = await Promise.all(["package.json", ...FILES_TO_DOWNLOAD].map(f => existsAsync(join(dir, f))));
|
||||
return !results.includes(false);
|
||||
}
|
||||
|
||||
export async function ensureVencordFiles() {
|
||||
if (isValidVencordInstall(VENCORD_FILES_DIR)) return;
|
||||
if (await isValidVencordInstall(VENCORD_FILES_DIR)) return;
|
||||
|
||||
mkdirSync(VENCORD_FILES_DIR, { recursive: true });
|
||||
|
||||
await downloadVencordFiles();
|
||||
await Promise.all([downloadVencordFiles(), writeFile(join(VENCORD_FILES_DIR, "package.json"), "{}")]);
|
||||
}
|
||||
|
||||
13
src/main/vencordFilesDir.ts
Normal file
13
src/main/vencordFilesDir.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
/*
|
||||
* Vesktop, a desktop app aiming to give you a snappier Discord Experience
|
||||
* Copyright (c) 2025 Vendicated and Vesktop contributors
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { join } from "path";
|
||||
|
||||
import { SESSION_DATA_DIR } from "./constants";
|
||||
import { State } from "./settings";
|
||||
|
||||
// this is in a separate file to avoid circular dependencies
|
||||
export const VENCORD_FILES_DIR = State.store.vencordDir || join(SESSION_DATA_DIR, "vencordFiles");
|
||||
135
src/main/venmic.ts
Normal file
135
src/main/venmic.ts
Normal file
@@ -0,0 +1,135 @@
|
||||
/*
|
||||
* Vesktop, a desktop app aiming to give you a snappier Discord Experience
|
||||
* Copyright (c) 2023 Vendicated and Vencord contributors
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import type { LinkData, Node, PatchBay as PatchBayType } from "@vencord/venmic";
|
||||
import { app, ipcMain } from "electron";
|
||||
import { join } from "path";
|
||||
import { IpcEvents } from "shared/IpcEvents";
|
||||
import { STATIC_DIR } from "shared/paths";
|
||||
|
||||
import { Settings } from "./settings";
|
||||
|
||||
let PatchBay: typeof PatchBayType | undefined;
|
||||
let patchBayInstance: PatchBayType | undefined;
|
||||
|
||||
let imported = false;
|
||||
let initialized = false;
|
||||
|
||||
let hasPipewirePulse = false;
|
||||
let isGlibCxxOutdated = false;
|
||||
|
||||
function importVenmic() {
|
||||
if (imported) {
|
||||
return;
|
||||
}
|
||||
|
||||
imported = true;
|
||||
|
||||
try {
|
||||
PatchBay = (require(join(STATIC_DIR, `dist/venmic-${process.arch}.node`)) as typeof import("@vencord/venmic"))
|
||||
.PatchBay;
|
||||
|
||||
hasPipewirePulse = PatchBay.hasPipeWire();
|
||||
} catch (e: any) {
|
||||
console.error("Failed to import venmic", e);
|
||||
isGlibCxxOutdated = (e?.stack || e?.message || "").toLowerCase().includes("glibc");
|
||||
}
|
||||
}
|
||||
|
||||
function obtainVenmic() {
|
||||
if (!imported) {
|
||||
importVenmic();
|
||||
}
|
||||
|
||||
if (PatchBay && !initialized) {
|
||||
initialized = true;
|
||||
|
||||
try {
|
||||
patchBayInstance = new PatchBay();
|
||||
} catch (e: any) {
|
||||
console.error("Failed to instantiate venmic", e);
|
||||
}
|
||||
}
|
||||
|
||||
return patchBayInstance;
|
||||
}
|
||||
|
||||
function getRendererAudioServicePid() {
|
||||
return (
|
||||
app
|
||||
.getAppMetrics()
|
||||
.find(proc => proc.name === "Audio Service")
|
||||
?.pid?.toString() ?? "owo"
|
||||
);
|
||||
}
|
||||
|
||||
ipcMain.handle(IpcEvents.VIRT_MIC_LIST, () => {
|
||||
const audioPid = getRendererAudioServicePid();
|
||||
|
||||
const { granularSelect } = Settings.store.audio ?? {};
|
||||
|
||||
const targets = obtainVenmic()
|
||||
?.list(granularSelect ? ["node.name"] : undefined)
|
||||
.filter(s => s["application.process.id"] !== audioPid);
|
||||
|
||||
return targets ? { ok: true, targets, hasPipewirePulse } : { ok: false, isGlibCxxOutdated };
|
||||
});
|
||||
|
||||
ipcMain.handle(IpcEvents.VIRT_MIC_START, (_, include: Node[]) => {
|
||||
const pid = getRendererAudioServicePid();
|
||||
const { ignoreDevices, ignoreInputMedia, ignoreVirtual, workaround } = Settings.store.audio ?? {};
|
||||
|
||||
const data: LinkData = {
|
||||
include,
|
||||
exclude: [{ "application.process.id": pid }],
|
||||
ignore_devices: ignoreDevices
|
||||
};
|
||||
|
||||
if (ignoreInputMedia ?? true) {
|
||||
data.exclude.push({ "media.class": "Stream/Input/Audio" });
|
||||
}
|
||||
|
||||
if (ignoreVirtual) {
|
||||
data.exclude.push({ "node.virtual": "true" });
|
||||
}
|
||||
|
||||
if (workaround) {
|
||||
data.workaround = [{ "application.process.id": pid, "media.name": "RecordStream" }];
|
||||
}
|
||||
|
||||
return obtainVenmic()?.link(data);
|
||||
});
|
||||
|
||||
ipcMain.handle(IpcEvents.VIRT_MIC_START_SYSTEM, (_, exclude: Node[]) => {
|
||||
const pid = getRendererAudioServicePid();
|
||||
|
||||
const { workaround, ignoreDevices, ignoreInputMedia, ignoreVirtual, onlySpeakers, onlyDefaultSpeakers } =
|
||||
Settings.store.audio ?? {};
|
||||
|
||||
const data: LinkData = {
|
||||
include: [],
|
||||
exclude: [{ "application.process.id": pid }, ...exclude],
|
||||
only_speakers: onlySpeakers,
|
||||
ignore_devices: ignoreDevices,
|
||||
only_default_speakers: onlyDefaultSpeakers
|
||||
};
|
||||
|
||||
if (ignoreInputMedia ?? true) {
|
||||
data.exclude.push({ "media.class": "Stream/Input/Audio" });
|
||||
}
|
||||
|
||||
if (ignoreVirtual) {
|
||||
data.exclude.push({ "node.virtual": "true" });
|
||||
}
|
||||
|
||||
if (workaround) {
|
||||
data.workaround = [{ "application.process.id": pid, "media.name": "RecordStream" }];
|
||||
}
|
||||
|
||||
return obtainVenmic()?.link(data);
|
||||
});
|
||||
|
||||
ipcMain.handle(IpcEvents.VIRT_MIC_STOP, () => obtainVenmic()?.unlink());
|
||||
25
src/main/vesktopProtocol.ts
Normal file
25
src/main/vesktopProtocol.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
/*
|
||||
* Vesktop, a desktop app aiming to give you a snappier Discord Experience
|
||||
* Copyright (c) 2025 Vendicated and Vesktop contributors
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { app, protocol } from "electron";
|
||||
|
||||
import { handleVesktopAssetsProtocol } from "./userAssets";
|
||||
import { handleVesktopStaticProtocol } from "./vesktopStatic";
|
||||
|
||||
app.whenReady().then(() => {
|
||||
protocol.handle("vesktop", async req => {
|
||||
const url = new URL(req.url);
|
||||
|
||||
switch (url.hostname) {
|
||||
case "assets":
|
||||
return handleVesktopAssetsProtocol(url.pathname, req);
|
||||
case "static":
|
||||
return handleVesktopStaticProtocol(url.pathname, req);
|
||||
default:
|
||||
return new Response(null, { status: 404 });
|
||||
}
|
||||
});
|
||||
});
|
||||
31
src/main/vesktopStatic.ts
Normal file
31
src/main/vesktopStatic.ts
Normal file
@@ -0,0 +1,31 @@
|
||||
/*
|
||||
* Vesktop, a desktop app aiming to give you a snappier Discord Experience
|
||||
* Copyright (c) 2025 Vendicated and Vesktop contributors
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { BrowserWindow, net } from "electron";
|
||||
import { join } from "path";
|
||||
import { pathToFileURL } from "url";
|
||||
|
||||
import { isPathInDirectory } from "./utils/isPathInDirectory";
|
||||
|
||||
const STATIC_DIR = join(__dirname, "..", "..", "static");
|
||||
|
||||
export async function handleVesktopStaticProtocol(path: string, req: Request) {
|
||||
const fullPath = join(STATIC_DIR, path);
|
||||
if (!isPathInDirectory(fullPath, STATIC_DIR)) {
|
||||
return new Response(null, { status: 404 });
|
||||
}
|
||||
|
||||
return net.fetch(pathToFileURL(fullPath).href);
|
||||
}
|
||||
|
||||
export function loadView(browserWindow: BrowserWindow, view: string, params?: URLSearchParams) {
|
||||
const url = new URL(`vesktop://static/views/${view}`);
|
||||
if (params) {
|
||||
url.search = params.toString();
|
||||
}
|
||||
|
||||
return browserWindow.loadURL(url.toString());
|
||||
}
|
||||
@@ -1,74 +0,0 @@
|
||||
/*
|
||||
* SPDX-License-Identifier: GPL-3.0
|
||||
* Vesktop, a desktop app aiming to give you a snappier Discord Experience
|
||||
* Copyright (c) 2023 Vendicated and Vencord contributors
|
||||
*/
|
||||
|
||||
import { app, ipcMain } from "electron";
|
||||
import { join } from "path";
|
||||
import { IpcEvents } from "shared/IpcEvents";
|
||||
import { STATIC_DIR } from "shared/paths";
|
||||
|
||||
let initialized = false;
|
||||
let patchBay: import("@vencord/venmic").PatchBay | undefined;
|
||||
let isGlibcxxToOld = false;
|
||||
|
||||
function getRendererAudioServicePid() {
|
||||
return (
|
||||
app
|
||||
.getAppMetrics()
|
||||
.find(proc => proc.name === "Audio Service")
|
||||
?.pid?.toString() ?? "owo"
|
||||
);
|
||||
}
|
||||
|
||||
function obtainVenmic() {
|
||||
if (!initialized) {
|
||||
initialized = true;
|
||||
try {
|
||||
const { PatchBay } = require(
|
||||
join(STATIC_DIR, `dist/venmic-${process.arch}.node`)
|
||||
) as typeof import("@vencord/venmic");
|
||||
patchBay = new PatchBay();
|
||||
} catch (e: any) {
|
||||
console.error("Failed to initialise venmic. Make sure you're using pipewire", e);
|
||||
isGlibcxxToOld = (e?.stack || e?.message || "").toLowerCase().includes("glibc");
|
||||
}
|
||||
}
|
||||
|
||||
return patchBay;
|
||||
}
|
||||
|
||||
ipcMain.handle(IpcEvents.VIRT_MIC_LIST, () => {
|
||||
const audioPid = getRendererAudioServicePid();
|
||||
const list = obtainVenmic()
|
||||
?.list()
|
||||
.filter(s => s["application.process.id"] !== audioPid)
|
||||
.map(s => s["application.name"]);
|
||||
|
||||
return list
|
||||
? { ok: true, targets: [...new Set(list)] } // Remove duplicates
|
||||
: { ok: false, isGlibcxxToOld };
|
||||
});
|
||||
|
||||
ipcMain.handle(
|
||||
IpcEvents.VIRT_MIC_START,
|
||||
(_, target: string) =>
|
||||
obtainVenmic()?.link({
|
||||
key: "application.name",
|
||||
value: target,
|
||||
mode: "include"
|
||||
})
|
||||
);
|
||||
|
||||
ipcMain.handle(
|
||||
IpcEvents.VIRT_MIC_START_SYSTEM,
|
||||
() =>
|
||||
obtainVenmic()?.link({
|
||||
key: "application.process.id",
|
||||
value: getRendererAudioServicePid(),
|
||||
mode: "exclude"
|
||||
})
|
||||
);
|
||||
|
||||
ipcMain.handle(IpcEvents.VIRT_MIC_STOP, () => obtainVenmic()?.unlink());
|
||||
10
src/module.d.ts
vendored
Normal file
10
src/module.d.ts
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
/*
|
||||
* Vesktop, a desktop app aiming to give you a snappier Discord Experience
|
||||
* Copyright (c) 2025 Vendicated and Vesktop contributors
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
declare module "__patches__" {
|
||||
const never: never;
|
||||
export default never;
|
||||
}
|
||||
@@ -1,12 +1,13 @@
|
||||
/*
|
||||
* SPDX-License-Identifier: GPL-3.0
|
||||
* Vesktop, a desktop app aiming to give you a snappier Discord Experience
|
||||
* Copyright (c) 2023 Vendicated and Vencord contributors
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { Node } from "@vencord/venmic";
|
||||
import { ipcRenderer } from "electron";
|
||||
import { IpcMessage, IpcResponse } from "main/ipcCommands";
|
||||
import type { Settings } from "shared/settings";
|
||||
import type { LiteralUnion } from "type-fest";
|
||||
|
||||
import { IpcEvents } from "../shared/IpcEvents";
|
||||
import { invoke, sendSync } from "./typedIpc";
|
||||
@@ -19,12 +20,21 @@ ipcRenderer.on(IpcEvents.SPELLCHECK_RESULT, (_, w: string, s: string[]) => {
|
||||
spellCheckCallbacks.forEach(cb => cb(w, s));
|
||||
});
|
||||
|
||||
let onDevtoolsOpen = () => {};
|
||||
let onDevtoolsClose = () => {};
|
||||
|
||||
ipcRenderer.on(IpcEvents.DEVTOOLS_OPENED, () => onDevtoolsOpen());
|
||||
ipcRenderer.on(IpcEvents.DEVTOOLS_CLOSED, () => onDevtoolsClose());
|
||||
|
||||
export const VesktopNative = {
|
||||
app: {
|
||||
relaunch: () => invoke<void>(IpcEvents.RELAUNCH),
|
||||
getVersion: () => sendSync<void>(IpcEvents.GET_VERSION),
|
||||
setBadgeCount: (count: number) => invoke<void>(IpcEvents.SET_BADGE_COUNT, count),
|
||||
supportsWindowsTransparency: () => sendSync<boolean>(IpcEvents.SUPPORTS_WINDOWS_TRANSPARENCY)
|
||||
supportsWindowsTransparency: () => sendSync<boolean>(IpcEvents.SUPPORTS_WINDOWS_TRANSPARENCY),
|
||||
getEnableHardwareAcceleration: () => sendSync<boolean>(IpcEvents.GET_ENABLE_HARDWARE_ACCELERATION),
|
||||
isOutdated: () => invoke<boolean>(IpcEvents.UPDATER_IS_OUTDATED),
|
||||
openUpdater: () => invoke<void>(IpcEvents.UPDATER_OPEN)
|
||||
},
|
||||
autostart: {
|
||||
isEnabled: () => sendSync<boolean>(IpcEvents.AUTOSTART_ENABLED),
|
||||
@@ -33,14 +43,17 @@ export const VesktopNative = {
|
||||
},
|
||||
fileManager: {
|
||||
showItemInFolder: (path: string) => invoke<void>(IpcEvents.SHOW_ITEM_IN_FOLDER, path),
|
||||
selectVencordDir: () => invoke<LiteralUnion<"cancelled" | "invalid", string>>(IpcEvents.SELECT_VENCORD_DIR)
|
||||
getVencordDir: () => sendSync<string | undefined>(IpcEvents.GET_VENCORD_DIR),
|
||||
selectVencordDir: (value?: null) => invoke<"cancelled" | "invalid" | "ok">(IpcEvents.SELECT_VENCORD_DIR, value),
|
||||
chooseUserAsset: (asset: string, value?: null) =>
|
||||
invoke<"cancelled" | "invalid" | "ok" | "failed">(IpcEvents.CHOOSE_USER_ASSET, asset, value)
|
||||
},
|
||||
settings: {
|
||||
get: () => sendSync<Settings>(IpcEvents.GET_SETTINGS),
|
||||
set: (settings: Settings, path?: string) => invoke<void>(IpcEvents.SET_SETTINGS, settings, path)
|
||||
},
|
||||
spellcheck: {
|
||||
setLanguages: (languages: readonly string[]) => invoke<void>(IpcEvents.SPELLCHECK_SET_LANGUAGES, languages),
|
||||
getAvailableLanguages: () => sendSync<string[]>(IpcEvents.SPELLCHECK_GET_AVAILABLE_LANGUAGES),
|
||||
onSpellcheckResult(cb: SpellCheckerResultCallback) {
|
||||
spellCheckCallbacks.add(cb);
|
||||
},
|
||||
@@ -52,9 +65,13 @@ export const VesktopNative = {
|
||||
},
|
||||
win: {
|
||||
focus: () => invoke<void>(IpcEvents.FOCUS),
|
||||
close: () => invoke<void>(IpcEvents.CLOSE),
|
||||
minimize: () => invoke<void>(IpcEvents.MINIMIZE),
|
||||
maximize: () => invoke<void>(IpcEvents.MAXIMIZE)
|
||||
close: (key?: string) => invoke<void>(IpcEvents.CLOSE, key),
|
||||
minimize: (key?: string) => invoke<void>(IpcEvents.MINIMIZE, key),
|
||||
maximize: (key?: string) => invoke<void>(IpcEvents.MAXIMIZE, key),
|
||||
setDevtoolsCallbacks: (onOpen: () => void, onClose: () => void) => {
|
||||
onDevtoolsOpen = onOpen;
|
||||
onDevtoolsClose = onClose;
|
||||
}
|
||||
},
|
||||
capturer: {
|
||||
getLargeThumbnail: (id: string) => invoke<string>(IpcEvents.CAPTURER_GET_LARGE_THUMBNAIL, id)
|
||||
@@ -62,14 +79,25 @@ export const VesktopNative = {
|
||||
/** only available on Linux. */
|
||||
virtmic: {
|
||||
list: () =>
|
||||
invoke<{ ok: false; isGlibcxxToOld: boolean } | { ok: true; targets: string[] }>(IpcEvents.VIRT_MIC_LIST),
|
||||
start: (target: string) => invoke<void>(IpcEvents.VIRT_MIC_START, target),
|
||||
startSystem: () => invoke<void>(IpcEvents.VIRT_MIC_START_SYSTEM),
|
||||
invoke<
|
||||
{ ok: false; isGlibCxxOutdated: boolean } | { ok: true; targets: Node[]; hasPipewirePulse: boolean }
|
||||
>(IpcEvents.VIRT_MIC_LIST),
|
||||
start: (include: Node[]) => invoke<void>(IpcEvents.VIRT_MIC_START, include),
|
||||
startSystem: (exclude: Node[]) => invoke<void>(IpcEvents.VIRT_MIC_START_SYSTEM, exclude),
|
||||
stop: () => invoke<void>(IpcEvents.VIRT_MIC_STOP)
|
||||
},
|
||||
arrpc: {
|
||||
onActivity(cb: (data: string) => void) {
|
||||
ipcRenderer.on(IpcEvents.ARRPC_ACTIVITY, (_, data: string) => cb(data));
|
||||
}
|
||||
clipboard: {
|
||||
copyImage: (imageBuffer: Uint8Array, imageSrc: string) =>
|
||||
invoke<void>(IpcEvents.CLIPBOARD_COPY_IMAGE, imageBuffer, imageSrc)
|
||||
},
|
||||
debug: {
|
||||
launchGpu: () => invoke<void>(IpcEvents.DEBUG_LAUNCH_GPU),
|
||||
launchWebrtcInternals: () => invoke<void>(IpcEvents.DEBUG_LAUNCH_WEBRTC_INTERNALS)
|
||||
},
|
||||
commands: {
|
||||
onCommand(cb: (message: IpcMessage) => void) {
|
||||
ipcRenderer.on(IpcEvents.IPC_COMMAND, (_, message) => cb(message));
|
||||
},
|
||||
respond: (response: IpcResponse) => ipcRenderer.send(IpcEvents.IPC_COMMAND, response)
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/*
|
||||
* SPDX-License-Identifier: GPL-3.0
|
||||
* Vesktop, a desktop app aiming to give you a snappier Discord Experience
|
||||
* Copyright (c) 2023 Vendicated and Vencord contributors
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { contextBridge, ipcRenderer, webFrame } from "electron";
|
||||
@@ -40,5 +40,3 @@ if (IS_DEV) {
|
||||
});
|
||||
}
|
||||
// #endregion
|
||||
|
||||
VesktopNative.spellcheck.setLanguages(window.navigator.languages);
|
||||
|
||||
13
src/preload/splash.ts
Normal file
13
src/preload/splash.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
/*
|
||||
* Vesktop, a desktop app aiming to give you a snappier Discord Experience
|
||||
* Copyright (c) 2025 Vendicated and Vesktop contributors
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { contextBridge, ipcRenderer } from "electron";
|
||||
|
||||
contextBridge.exposeInMainWorld("VesktopSplashNative", {
|
||||
onUpdateMessage(callback: (message: string) => void) {
|
||||
ipcRenderer.on("update-splash-message", (_, message: string) => callback(message));
|
||||
}
|
||||
});
|
||||
@@ -1,16 +1,16 @@
|
||||
/*
|
||||
* SPDX-License-Identifier: GPL-3.0
|
||||
* Vesktop, a desktop app aiming to give you a snappier Discord Experience
|
||||
* Copyright (c) 2023 Vendicated and Vencord contributors
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { ipcRenderer } from "electron";
|
||||
import { IpcEvents } from "shared/IpcEvents";
|
||||
import { IpcEvents, UpdaterIpcEvents } from "shared/IpcEvents";
|
||||
|
||||
export function invoke<T = any>(event: IpcEvents, ...args: any[]) {
|
||||
export function invoke<T = any>(event: IpcEvents | UpdaterIpcEvents, ...args: any[]) {
|
||||
return ipcRenderer.invoke(event, ...args) as Promise<T>;
|
||||
}
|
||||
|
||||
export function sendSync<T = any>(event: IpcEvents, ...args: any[]) {
|
||||
export function sendSync<T = any>(event: IpcEvents | UpdaterIpcEvents, ...args: any[]) {
|
||||
return ipcRenderer.sendSync(event, ...args) as T;
|
||||
}
|
||||
|
||||
24
src/preload/updater.ts
Normal file
24
src/preload/updater.ts
Normal file
@@ -0,0 +1,24 @@
|
||||
/*
|
||||
* Vesktop, a desktop app aiming to give you a snappier Discord Experience
|
||||
* Copyright (c) 2025 Vendicated and Vesktop contributors
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { contextBridge, ipcRenderer } from "electron";
|
||||
import type { UpdateInfo } from "electron-updater";
|
||||
import { UpdaterIpcEvents } from "shared/IpcEvents";
|
||||
|
||||
import { invoke } from "./typedIpc";
|
||||
|
||||
contextBridge.exposeInMainWorld("VesktopUpdaterNative", {
|
||||
getData: () => invoke<UpdateInfo>(UpdaterIpcEvents.GET_DATA),
|
||||
installUpdate: () => invoke(UpdaterIpcEvents.INSTALL),
|
||||
onProgress: (cb: (percent: number) => void) => {
|
||||
ipcRenderer.on(UpdaterIpcEvents.DOWNLOAD_PROGRESS, (_, percent: number) => cb(percent));
|
||||
},
|
||||
onError: (cb: (message: string) => void) => {
|
||||
ipcRenderer.on(UpdaterIpcEvents.ERROR, (_, message: string) => cb(message));
|
||||
},
|
||||
snoozeUpdate: () => invoke(UpdaterIpcEvents.SNOOZE_UPDATE),
|
||||
ignoreUpdate: () => invoke(UpdaterIpcEvents.IGNORE_UPDATE)
|
||||
});
|
||||
@@ -1,12 +1,13 @@
|
||||
/*
|
||||
* SPDX-License-Identifier: GPL-3.0
|
||||
* Vesktop, a desktop app aiming to give you a snappier Discord Experience
|
||||
* Copyright (c) 2023 Vendicated and Vencord contributors
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { filters, waitFor } from "@vencord/types/webpack";
|
||||
import { RelationshipStore } from "@vencord/types/webpack/common";
|
||||
|
||||
import { VesktopLogger } from "./logger";
|
||||
import { Settings } from "./settings";
|
||||
|
||||
let GuildReadStateStore: any;
|
||||
@@ -26,7 +27,7 @@ export function setBadge() {
|
||||
|
||||
VesktopNative.app.setBadgeCount(totalCount);
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
VesktopLogger.error("Failed to update badge count", e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
68
src/renderer/arrpc.ts
Normal file
68
src/renderer/arrpc.ts
Normal file
@@ -0,0 +1,68 @@
|
||||
/*
|
||||
* Vesktop, a desktop app aiming to give you a snappier Discord Experience
|
||||
* Copyright (c) 2025 Vendicated and Vencord contributors
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { Logger } from "@vencord/types/utils";
|
||||
import { findLazy, findStoreLazy, onceReady } from "@vencord/types/webpack";
|
||||
import { FluxDispatcher, InviteActions } from "@vencord/types/webpack/common";
|
||||
import { IpcCommands } from "shared/IpcEvents";
|
||||
|
||||
import { onIpcCommand } from "./ipcCommands";
|
||||
import { Settings } from "./settings";
|
||||
|
||||
const logger = new Logger("VesktopRPC", "#5865f2");
|
||||
const StreamerModeStore = findStoreLazy("StreamerModeStore");
|
||||
|
||||
const arRPC = Vencord.Plugins.plugins["WebRichPresence (arRPC)"] as any as {
|
||||
handleEvent(e: MessageEvent): void;
|
||||
};
|
||||
|
||||
onIpcCommand(IpcCommands.RPC_ACTIVITY, async jsonData => {
|
||||
if (!Settings.store.arRPC) return;
|
||||
|
||||
await onceReady;
|
||||
|
||||
const data = JSON.parse(jsonData);
|
||||
|
||||
if (data.socketId === "STREAMERMODE" && StreamerModeStore.autoToggle) {
|
||||
FluxDispatcher.dispatch({
|
||||
type: "STREAMER_MODE_UPDATE",
|
||||
key: "enabled",
|
||||
value: data.activity?.application_id === "STREAMERMODE"
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
arRPC.handleEvent(new MessageEvent("message", { data: jsonData }));
|
||||
});
|
||||
|
||||
onIpcCommand(IpcCommands.RPC_INVITE, async code => {
|
||||
const { invite } = await InviteActions.resolveInvite(code, "Desktop Modal");
|
||||
if (!invite) return false;
|
||||
|
||||
VesktopNative.win.focus();
|
||||
|
||||
FluxDispatcher.dispatch({
|
||||
type: "INVITE_MODAL_OPEN",
|
||||
invite,
|
||||
code,
|
||||
context: "APP"
|
||||
});
|
||||
|
||||
return true;
|
||||
});
|
||||
|
||||
const { DEEP_LINK } = findLazy(m => m.DEEP_LINK?.handler);
|
||||
|
||||
onIpcCommand(IpcCommands.RPC_DEEP_LINK, async data => {
|
||||
logger.debug("Opening deep link:", data);
|
||||
try {
|
||||
DEEP_LINK.handler({ args: data });
|
||||
return true;
|
||||
} catch (err) {
|
||||
logger.error("Failed to open deep link:", err);
|
||||
return false;
|
||||
}
|
||||
});
|
||||
9
src/renderer/common.ts
Normal file
9
src/renderer/common.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
/*
|
||||
* Vesktop, a desktop app aiming to give you a snappier Discord Experience
|
||||
* Copyright (c) 2025 Vendicated and Vesktop contributors
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { findStoreLazy } from "@vencord/types/webpack";
|
||||
|
||||
export const MediaEngineStore = findStoreLazy("MediaEngineStore");
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user