92 Commits

Author SHA1 Message Date
Vendicated
02907d3248 bump to v1.6.0 2025-10-19 20:07:32 +02:00
Vendicated
c82cc7a963 fix metainfo generation & uploading 2025-10-19 19:59:15 +02:00
Vendicated
6a43e135d0 set CSP in updater view 2025-10-19 03:34:50 +02:00
Vendicated
d232797889 clean up vesktop:// protocol handler 2025-10-19 03:14:33 +02:00
Vendicated
fa23c630cb use custom protocol instead of file:// for better security 2025-10-19 02:59:00 +02:00
V
9f0af48355 Updater: show update dialog instead of forcing updates (#1184) 2025-10-19 00:58:48 +00:00
Vendicated
eb3dae897d --help: document chromium & electron flags 2025-10-18 18:25:25 +02:00
Vendicated
d005dd5ebd make cli parser less strict 2025-10-18 18:02:50 +02:00
Vendicated
6aeacaaf21 add cli flags to spoof user agent 2025-10-18 17:54:05 +02:00
Vendicated
40d9cba2f0 error proof settings ui 2025-10-16 13:57:53 +02:00
V
5734a1d33c libvesktop: native dbus module for autostart & app badge (#1180)
Both setAppBadge and autoStart at system boot now use dbus calls. This means that autoStart will work in Flatpak
2025-10-16 10:52:52 +02:00
Vendicated
8cc34e217c bump dependencies, migrate to new Vencord Switch component 2025-10-07 00:37:45 +02:00
V
a55b1f0250 Add splash/tray image customisation & change default splash (#1179) 2025-10-05 18:14:13 +00:00
Vendicated
e79635f15e fix: explicitly set executableName
fixes an upcoming regression caused by https://github.com/electron-userland/electron-builder/pull/9068
2025-10-04 00:38:17 +02:00
Cookie
6e2da1d294 feat: New Vesktop icon (#865)
changes the app icon and tray

Co-authored-by: Wing <44992537+wingio@users.noreply.github.com>
Co-authored-by: khcrysalis <sam4r16@gmail.com>
Co-authored-by: Vendicated <vendicated@riseup.net>
2025-10-02 22:02:43 +02:00
Vendicated
0d9ca2270c fix Vesktop being registered as html mime handler
This is actually a bug in xdg-settings that was already fixed years ago, but Ubuntu
still ships 7 (!!!!!) years out of date xdg-utils. Please just switch to Fedora what
are we doing man
2025-09-18 03:37:27 +02:00
Tiagoquix
497c251d72 Add icon to auto-start script (#1172) 2025-09-05 00:03:12 +00:00
Vendicated
b221882c5b upgrade dependencies 2025-09-04 19:45:04 +02:00
Vendicated
0ee194698d upgrade to electron 38 2025-09-04 19:43:27 +02:00
V
27293d4ae9 Delete meta/dev.vencord.Vesktop.metainfo.xml
this file has been moved to releases: https://github.com/Vencord/Vesktop/releases/latest/download/dev.vencord.Vesktop.metainfo.xml
2025-07-11 02:48:21 +02:00
V
432e54ace5 Update README.md 2025-07-08 17:11:21 +02:00
Vendicated
4a2d12f273 fix lockfile 2025-07-08 01:08:39 +02:00
Vendicated
3498e39cbb bump to v1.5.8 2025-07-08 01:02:17 +02:00
Vendicated
3278b16923 Remove GTK mixed symbols workaround
This bug is fixed in electron 37, so the workaround is no longer needed
2025-07-06 19:16:07 +02:00
Vendicated
fde447bc1d improve about window 2025-07-06 18:57:57 +02:00
Vendicated
4baf6de472 arrpc: fix link handling 2025-07-06 17:54:58 +02:00
Justin
8aa91b4f01 Move arrpc server into a worker thread to reduce stutters (#1053)
Co-authored-by: V <vendicated@riseup.net>
2025-07-06 17:34:32 +02:00
Vendicated
099845deb7 bump electron again surely this time they fixed every bug 2025-07-06 16:27:04 +02:00
Vendicated
3982e122a7 upgrade to electron 37 2025-06-27 23:08:50 +02:00
Cookie
8d3c9390ae update discord css variable name (#1164) 2025-06-27 03:09:33 +02:00
Vendicated
236fc806de fake markdown wtf 2025-06-26 15:09:35 +02:00
Vendicated
2dcebeca79 revise issue template once again 2025-06-26 15:08:28 +02:00
Vendicated
e6589eacfc fix lockfile 2025-06-21 04:12:21 +02:00
Vendicated
29d1c73d81 okay pnpm 2025-06-21 04:10:45 +02:00
Vendicated
26906b9776 patch electron-updater to fix rpm updater 2025-06-21 04:06:13 +02:00
Vendicated
36c67aa54a bump dependencies 2025-06-21 04:06:00 +02:00
Cookie
3cd4e94762 fix spellcheck (#1163) 2025-06-20 19:29:13 +02:00
Vendicated
6950e0b03a add comment explaining the patch 2025-06-10 17:22:30 +02:00
Vendicated
f98309c7b7 fix random logouts (Discord shitcode moment) 2025-06-10 17:16:26 +02:00
Vendicated
a2dade9140 i am very stupid 2025-06-09 01:36:45 +02:00
Vendicated
83ad4970e5 fix Video HWA wrongly being disabled if HWA setting was never changed 2025-06-09 01:36:15 +02:00
Vending Machine
f123f5fc3c Update dev.vencord.Vesktop.metainfo.xml 2025-06-08 22:07:07 +02:00
Vending Machine
0a856b26da Update meta.yml 2025-06-08 22:04:59 +02:00
Vendicated
dad306a4b5 bump to v1.5.7 2025-06-08 21:47:06 +02:00
Vendicated
a52ee1dbb6 bump dependencies 2025-06-08 21:45:17 +02:00
Vendicated
524f8ff277 ipcCommands: check if mainWin is alive 2025-06-08 21:39:56 +02:00
Vendicated
83104e3625 fix discord titlebar buttons missing 2025-06-08 21:34:31 +02:00
Vendicated
fcb61c8f42 improve handling of no hardware acceleration 2025-06-08 21:21:57 +02:00
Vendicated
48e9aea47e add hardware video acceleration switch & improve settings ui 2025-06-08 20:56:28 +02:00
Vendicated
6e7d912b95 make linux screenshare audio stereo 2025-05-20 01:45:24 +02:00
Vendicated
475012cbed bump electron patch 2025-05-16 22:30:11 +02:00
Vendicated
382dac96f7 fix return value 2025-05-16 21:01:19 +02:00
Vendicated
3295a7d344 migrate executeJavaScript calls to new command system 2025-05-16 21:00:32 +02:00
Vendicated
bb3cec0d13 Fix Invalid URL error 2025-05-16 20:48:34 +02:00
Vendicated
1a673f2318 build: automatically glob all patch files 2025-05-16 20:43:27 +02:00
Vendicated
d9a7e81f71 switch to node inbuilt source map support 2025-05-16 19:59:29 +02:00
Vendicated
73848311b1 oh cookie what are u doing 2025-05-16 19:52:16 +02:00
Vendicated
857779e77b remove legacy code 2025-05-16 19:47:10 +02:00
William
fa627b384f add StartupWMClass - fixes AppImage icon & pinning (#1158)
Co-authored-by: Vending Machine <vendicated@riseup.net>
2025-05-16 19:38:02 +02:00
Cookie
43a8781492 Link Discord connections through the browser instead of a popup (#1159)
Co-authored-by: Vending Machine <vendicated@riseup.net>
2025-05-16 19:34:48 +02:00
Vendicated
b17fef93ba make Discord's auto gain control toggle actually work 2025-05-16 03:37:32 +02:00
Vendicated
fc16fc5404 Fix not launching on GNOME
Reference: https://github.com/electron/electron/issues/46538
Also improves the chromium switch code
2025-05-14 05:26:29 +02:00
Vendicated
e760e58ed7 upgrade to electron 36 2025-05-14 05:26:29 +02:00
Cookie
3936a0a41e fix: handle loadURL() failures correctly (#1093) 2025-04-22 22:03:17 +02:00
Glitchtest
c7d830c57c Revert loopbackWithMute to loopback (#1155)
apparently loopbackWithMute does not mute your own app like initially assumed but rather mutes the system audio for the user
2025-04-17 23:23:42 +02:00
Vendicated
7bf05bd907 add back window menu (minimise, close, etc) on mac 2025-04-17 14:39:19 +02:00
Vending Machine
ae20445301 manually update metainfo 2025-04-13 14:49:49 +02:00
Vending Machine
33d1ac43e3 fix meta.yml 2025-04-13 14:47:32 +02:00
Vending Machine
da51ebb0a7 fix(ci): strip v prefix from version in vencord.dev updater 2025-04-13 14:44:27 +02:00
Vendicated
d0399cbde4 bump to v1.5.6 2025-04-13 14:31:02 +02:00
Vendicated
49784bc1aa fix window button patch 2025-04-13 14:29:13 +02:00
Vendicated
43f59cefda use Vencord's addPatch instead of manually adding patches 2025-04-13 14:20:28 +02:00
Sqaaakoi
c42c1b7bbd Fix Visual Refresh titlebar (#1104) 2025-04-04 15:44:47 +02:00
Ryan Cao
765ffc0b57 MacOs: fix DevTools shortcut (#1141) 2025-04-04 15:39:55 +02:00
khcrysalis
3c87c89b3a README: add homebrew as installation method (#1144) 2025-04-02 06:11:52 +02:00
Vendicated
391ad94b85 bump dependencies 2025-04-01 22:02:42 +02:00
rushii
8f94196646 fix: disable Browser Tab shortcuts like Ctrl+W (#1130)
Fixes #934
---------

Co-authored-by: Vending Machine <vendicated@riseup.net>
2025-03-27 01:01:50 +01:00
Vending Machine
878184cab1 improve build instructions 2025-03-27 00:20:29 +01:00
Vending Machine
156ba6ab7b ScreenShare(Windows): loopback -> loopbackWithMute 2025-03-23 22:43:34 +01:00
v
fd91a23188 Update and rename blank.yml to dev-issue.yml 2025-03-06 01:12:17 +01:00
Vendicated
9ca9e78da7 no more issues 2025-03-06 00:14:35 +01:00
Vendicated
a1d5e4dcdc bump dependencies 2025-02-18 15:28:41 +01:00
Vendicated
da5d0f8f19 update blank issue template 2025-02-18 14:18:50 +01:00
Vendicated
d7bc56660b includes is slightly better than every here 2025-02-12 13:18:20 +01:00
Vendicated
d39c54b3ee fix edge case when ~/package.json exists with type: module 2025-02-12 13:16:53 +01:00
v
670c62267e Add workflow to automatically update version in vencord.dev repo 2025-02-12 12:57:15 +01:00
v
8938fe27b2 metaInfo workflow: add to release and push instead of PR 2025-02-08 06:20:29 +01:00
Vendicated
e6c1a03c59 Revert "use Vencord's addPatch instead of manually adding patches"
This was pushed prematurely since it depends on a change to Vencord that hasn't been released yet

This reverts commit 68930a1f50.
2025-02-08 03:53:33 +01:00
Vendicated
68930a1f50 use Vencord's addPatch instead of manually adding patches 2025-02-07 23:13:28 +01:00
Vendicated
3b76c30db2 fix automatic license header inserter still using 2023 (lmao) 2025-02-07 04:16:15 +01:00
Vendicated
523f657b3b hide app download button in server list 2025-02-07 03:54:52 +01:00
github-actions[bot]
d75ab4af1c update Metainfo for v1.5.5 (#1079)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-02-06 05:51:52 +01:00
157 changed files with 4762 additions and 2451 deletions

View File

@@ -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"

View File

@@ -1,18 +0,0 @@
name: Blank Issue
description: Reserved for developers. Use the bug report or feature request templates instead.
body:
- type: markdown
attributes:
value: |
# READ THIS BEFORE OPENING AN ISSUE
This form is only meant for Vesktop developers. If you don't know what you're doing,
please use the bug report or feature request templates instead.
- type: textarea
id: content
attributes:
label: Content
validations:
required: true

View File

@@ -1,131 +0,0 @@
name: 🐛 Bug / Crash Report
description: Create a bug or crash report for Vesktop
labels: [bug]
title: "[Bug] <title>"
body:
- type: markdown
attributes:
value: |
**Thanks 🩷 for taking the time to fill out this bug report! Before proceeding, please read the following**
Make sure a similar issue doesn't already exist by [searching the existing issues](https://github.com/Vencord/Vesktop/issues?q=is%3Aissue) for keywords!
Make sure both Vesktop and Vencord are fully up to date. You can update Vencord by right-clicking the Vesktop tray icon and pressing "Repair Vencord"
**DO NOT REPORT** any of the following issues:
- Purely graphical glitches like flickering, scaling issues[^1]
- App crashing / not showing window with mentions of the gpu process in the stacktrace[^1]
- Screenshare not starting, black screening or crashing[^2]
- Vencord related issues: This is the Vesktop repo, not Vencord
- Captchas[^3]
- Issues with opening URLs[^4]
- Issues with Notifications[^4]
- Issues with Input Methods[^4]
- Issues with File Drag and Drop[^5]
- Network Errors[^6]
- Anything about Screenshare Performance[^7]
Linux users: Please only report issues with supported packages (flatpak and any builds from the README / releases).
We do not support other packages, like the AUR or Nix packages, so please first make sure your issue is reproducible with official releases,
like [our Flatpak](https://flathub.org/apps/dev.vencord.Vesktop) or [AppImage](https://vencord.dev/download/vesktop/amd64/appimage)
[^1]: GPU issue. Disable hardware acceleration in Vesktop Settings or run with `--disable-gpu`
[^2]: System issue. You will have to fix it
[^3]: If you are receiving a lot of captchas, it means Discord thinks you might be a bot. Make sure you're not using a VPN/Proxy
[^4]: These things are handled by Chromium / Electron, not us. If they don't work, it's either an issue with your system or a bug with Chromium.
[^5]: You are likely using the Vesktop flatpak and trying to drop a file the flatpak can't access. You can fix this by installing Flatseal and using it to grant Vesktop full access to your files
[^6]: Issue on your end, you have to fix it. Try changing your DNS to [1.1.1.1 (Cloudflare DNS)](https://developers.cloudflare.com/1.1.1.1/setup/)
[^7]: Screensharing is managed entirely by Chromium and your System. For optimal performance, make sure you [enable Hardware Acceleration](https://wiki.archlinux.org/title/Chromium#Hardware_video_acceleration).
Depending on GPU driver, it might not be supported. We are actively working on making this work out of the box everywhere.
- type: input
id: discord
attributes:
label: Discord Account
description: Who on Discord is making this request? Not required but encouraged for easier follow-up
placeholder: username#0000
validations:
required: false
- type: input
id: os
attributes:
label: Operating System
description: What operating system are you using (eg Windows 10, macOS Big Sur, Ubuntu 20.04)?
placeholder: Windows 10
validations:
required: true
- type: input
id: linux-de
attributes:
label: Linux Only ~ Desktop Environment
description: If you are on Linux, what Desktop environment are you using (eg GNOME, KDE, XFCE)? Are you using Wayland or Xorg?
placeholder: Gnome on Wayland
validations:
required: false
- type: input
id: install-type
attributes:
label: Package Type
description: What kind of Vesktop package are you using? (Setup exe, Portable, Flatpak, AppImage, Deb, etc)
placeholder: Flatpak
validations:
required: true
- type: textarea
id: bug-description
attributes:
label: What happens when the bug or crash occurs?
description: Where does this bug or crash occur, when does it occur, etc.
placeholder: The bug/crash happens sometimes when I do ..., causing this to not work/the app to crash. I think it happens because of ...
validations:
required: true
- type: textarea
id: expected-behaviour
attributes:
label: What is the expected behaviour?
description: Simply detail what the expected behaviour is.
placeholder: I expect Vencord/Discord to open the ... page instead of ..., it prevents me from doing ...
validations:
required: true
- type: textarea
id: steps-to-take
attributes:
label: How do you recreate this bug or crash?
description: Give us a list of steps in order to recreate the bug or crash.
placeholder: |
1. Do ...
2. Then ...
3. Do this ..., ... and then ...
4. Observe "the bug" or "the crash"
validations:
required: true
- type: textarea
id: debug-logs
attributes:
label: Debug Logs
description: Run vesktop from the command line. Include the relevant command line output here. If there are any lines that seem relevant, try googling them or searching existing issues
placeholder: Paste your crash-log here.
render: shell
validations:
required: true
- type: checkboxes
id: agreement-check
attributes:
label: Request Agreement
description: We only accept reports for bugs that happen on supported and up to date Vesktop releases
options:
- label: I have searched the existing issues and found no similar issue
required: true
- label: I am using the latest Vesktop and Vencord versions
required: true
- label: This issue occurs on an official release (not just the AUR or Nix packages)
required: true

View File

@@ -2,4 +2,4 @@ blank_issues_enabled: false
contact_links:
- name: Vencord Support Server
url: https://discord.gg/D9uwnFnqmd
about: If you need help regarding Vesktop or Vencord, please join our support server!
about: "Need Help? Join our support server and ask in the #vesktop-support channel!"

20
.github/ISSUE_TEMPLATE/dev-issue.yml vendored Normal file
View 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

View File

@@ -1,74 +0,0 @@
name: 🛠️ Feature Request
description: Request a feature for Vesktop
labels: [enhancement]
title: "[Feature Request] <title>"
body:
- type: markdown
attributes:
value: |
**Thanks 🩷 for taking the time to fill out this request! Before proceeding, please read the following**
Make sure a similar request doesn't already exist by [searching the existing issues](https://github.com/Vencord/Vesktop/issues?q=is%3Aissue) for keywords!
This form is only meant for **Vesktop feature requests**.
For plugin requests or Vencord feature requests, go [here](https://github.com/Vencord/plugin-requests/issues/new?template=request.yml) instead!
**DO NOT** make any icon related requests or you will be blocked.
- type: input
id: discord
attributes:
label: Discord Account
description: Who on Discord is making this request? Not required but encouraged for easier follow-up
placeholder: username#0000
validations:
required: false
- type: textarea
id: motivation
attributes:
label: Motivation
description: If your feature request related to a problem? Please describe
placeholder: I'm always frustrated when ..., I think it would be better if ...
validations:
required: true
- type: textarea
id: solution
attributes:
label: Solution
description: Describe the solution you'd like
placeholder: A clear and concise description of what you want to happen.
validations:
required: true
- type: textarea
id: alternatives
attributes:
label: Alternatives
description: Describe alternatives you've considered
placeholder: A clear and concise description of any alternative solutions or features you've considered.
validations:
required: true
- type: textarea
id: additional-context
attributes:
label: Additional context
description: Add any other context here. Screenshots or mockups could help greatly
validations:
required: false
- type: checkboxes
id: agreement-check
attributes:
label: Request Agreement
description: This form is only for Vesktop feature requests. If the following don't apply, re-read the introduction text
options:
- label: I have searched the existing issues and found no similar issue
required: true
- label: This is not a plugin request
required: true
- label: This is not a Vencord feature request
required: true

View File

@@ -6,6 +6,9 @@ on:
- published
workflow_dispatch:
permissions:
contents: write
jobs:
update:
runs-on: ubuntu-latest
@@ -23,16 +26,13 @@ jobs:
run: pnpm i
- name: Update metainfo
run: pnpm updateMeta
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"
git checkout -b ci/meta-update
git add meta/dev.vencord.Vesktop.metainfo.xml
git commit -m "Insert release changes for ${{ github.event.release.tag_name }}"
git push origin ci/meta-update
gh pr create -B main -H ci/meta-update -t "Metainfo for ${{ github.event.release.tag_name }}" -b "This PR updates the metainfo for release ${{ github.event.release.tag_name }}."
gh release upload "${{ github.event.release.tag_name }}" dist/dev.vencord.Vesktop.metainfo.xml
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

View 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

View File

@@ -17,47 +17,15 @@ Vesktop is a custom Discord desktop app
## Installing
### Windows
If you don't know the difference, pick the Installer.
- [Installer](https://vencord.dev/download/vesktop/universal/windows)
- Portable:
- [x64 / amd64](https://vencord.dev/download/vesktop/amd64/windows-portable)
- [Arm® 64](https://vencord.dev/download/vesktop/arm64/windows-portable)
### Mac
[Vesktop.dmg](https://vencord.dev/download/vesktop/universal/dmg)
### Linux
[![Download on Flathub](https://dl.flathub.org/assets/badges/flathub-badge-en.svg)](https://flathub.org/apps/dev.vencord.Vesktop)
If you don't know the difference, pick amd64.
- amd64 / x86_64
- [AppImage](https://vencord.dev/download/vesktop/amd64/appimage)
- [Ubuntu/Debian (.deb)](https://vencord.dev/download/vesktop/amd64/deb)
- [Fedora/RHEL (.rpm)](https://vencord.dev/download/vesktop/amd64/rpm)
- [tarball](https://vencord.dev/download/vesktop/amd64/tar)
- Arm® 64 / aarch64
- [AppImage](https://vencord.dev/download/vesktop/arm64/appimage)
- [Ubuntu/Debian (.deb)](https://vencord.dev/download/vesktop/arm64/deb)
- [Fedora/RHEL (.rpm)](https://vencord.dev/download/vesktop/arm64/rpm)
- [tarball](https://vencord.dev/download/vesktop/arm64/tar)
#### Community packages
Below you can find unofficial packages created by the community. They are not officially supported by us, so before reporting issues, please first confirm the issue also happens on official builds. When in doubt, consult with their packager first. The flatpak and AppImage should work on any distro that [supports them](https://flatpak.org/setup/), so I recommend you just use those instead!
- Arch Linux: [Vesktop on the Arch user repository](https://aur.archlinux.org/packages?K=vesktop)
- NixOS: https://wiki.nixos.org/wiki/Discord#Vesktop
- Slackware: [Vesktop on the SlackBuilds](https://slackbuilds.org/result/?search=vesktop)
- Windows - Scoop: https://scoop.sh/#/apps?q=Vesktop
Visit https://vesktop.dev/install
## Building from Source
You need to have the following dependencies installed:
- [Git](https://git-scm.com/downloads)
- [Node.js](https://nodejs.org/en/download)
- pnpm: `npm install --global pnpm`
Packaging will create builds in the dist/ folder
```sh
@@ -70,10 +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
```
## Building LibVesktop from Source
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.
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

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
build/icon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 364 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.5 KiB

1
build/icon.svg Normal file
View 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

View File

@@ -1,14 +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
*/
//@ts-check
import stylistic from "@stylistic/eslint-plugin";
import pathAlias from "eslint-plugin-path-alias";
import header from "eslint-plugin-simple-header";
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";
@@ -20,7 +20,7 @@ export default tseslint.config(
{
files: ["src/**/*.{tsx,ts,mts,mjs,js,jsx}"],
plugins: {
header,
simpleHeader,
stylistic,
importSort,
unusedImports,
@@ -42,10 +42,11 @@ export default tseslint.config(
}
},
rules: {
"header/header": [
"simpleHeader/header": [
"error",
{
files: ["scripts/header.txt"]
files: ["scripts/header.txt"],
templates: { author: [".*", "Vendicated and Vesktop contributors"] }
}
],

View File

@@ -1,252 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<component type="desktop-application">
<!--Created with jdAppStreamEdit 7.1-->
<id>dev.vencord.Vesktop</id>
<name>Vesktop</name>
<summary>Snappier Discord app with Vencord</summary>
<developer_name>Vencord Contributors</developer_name>
<launchable type="desktop-id">dev.vencord.Vesktop.desktop</launchable>
<metadata_license>CC0-1.0</metadata_license>
<project_license>GPL-3.0</project_license>
<project_group>Vencord</project_group>
<description>
<p>Vesktop is a cross platform desktop app aiming to give you a snappier Discord experience with Vencord pre-installed.</p>
<p>Vesktop comes bundled with Venmic, a purpose-built library to provide functioning audio screenshare.</p>
</description>
<screenshots>
<screenshot type="default">
<caption>Vencord settings page and about window open</caption>
<image type="source">https://vencord.dev/assets/screenshots/vesktop-1-appstream.png</image>
</screenshot>
<screenshot>
<caption>A dialog showing screenshare options</caption>
<image type="source">https://vencord.dev/assets/screenshots/vesktop-2-appstream.png</image>
</screenshot>
<screenshot>
<caption>A screenshot of a Discord server</caption>
<image type="source">https://vencord.dev/assets/screenshots/vesktop-3-appstream.png</image>
</screenshot>
</screenshots>
<releases>
<release version="1.5.4" date="2024-12-05" type="stable">
<url>https://github.com/Vencord/Vesktop/releases/tag/v1.5.4</url>
<description>
<p>What's Changed</p>
<ul>
<li>Upgraded electron to version 33 which brings many improvements and bug fixes</li>
<li>AudioShare: add even more granular selection, Allow device sharing by @Curve</li>
<li>Enable speech-dispatcher support for TTS on linux by @adryd325</li>
<li>fixed screenshare picker window subtitle alignment by @ryawaa</li>
<li>fixed splash corners by @Covkie</li>
</ul>
</description>
</release>
<release version="1.5.3" date="2024-07-04" type="stable">
<url>https://github.com/Vencord/Vesktop/releases/tag/v1.5.3</url>
<description>
<p>Features</p>
<ul>
<li>added arm64 Windows support</li>
<li>windows &amp; macOS builds are now universal</li>
<li>added option to configure spellcheck languages</li>
<li>will auto-update from now on</li>
<li>updated electron to 31 &amp; Chromium to 126</li>
<li>macOS: Added customized dmg background by @khcrysalis</li>
<li>Windows Portable: store settings in portable folder by @MrGarlic1</li>
<li>linux audioshare: added granular selection, more options, better ui by @Curve</li>
<li>changed default screen-sharing quality to 720p 30 FPS by @Tiagoquix</li>
</ul>
<p>Fixes</p>
<ul>
<li>macOS: Added workaround for making things in draggable area clickable by @HAHALOSAH</li>
<li>fixed Screenshare UI for non-linux systems by @PolisanTheEasyNick</li>
<li>fixed opening on screen that was disconnected by @MrGarlic1</li>
<li>mac: hide native window controls with custom titlebar enabled by @MrGarlic1</li>
<li>fixed some broken patches by @D3SOX</li>
<li>fixed framerate in constraints by @kittykel</li>
<li>fixed some first launch switches not applying</li>
<li>fixed potential sandbox escape via custom vencord location</li>
</ul>
</description>
</release>
<release version="1.5.2" date="2024-05-01" type="stable">
<url>https://github.com/Vencord/Vesktop/releases/tag/v1.5.2</url>
<description>
<p>What's Changed</p>
<ul>
<li>Fixed scrollbars looking wrong (actually Discord's fault)</li>
<li>Tray: Added left click hide/show feature by @0bCdian</li>
<li>MacOS: Fixed the app not properly requesting microphone permissions by @ssalggnikool</li>
<li>Linux: Various fixed related to audio screenshare by @Curve</li>
<li>Linux: Overhauled &amp; improved screenshare with better framerate by @kaitlynkittyy</li>
<li>Users can now pass --enable/disable-features command line flags by @takase1121</li>
</ul>
</description>
</release>
<release version="1.5.1" date="2024-03-12" type="stable">
<url>https://github.com/Vencord/Vesktop/releases/tag/v1.5.1</url>
<description>
<p>New Features</p>
<ul>
<li>Added categories to Vesktop settings to reduce visual clutter by @justin13888</li>
<li>Added support for Vencord's transparent window options</li>
</ul>
<p>Fixes</p>
<ul>
<li>Fixed ugly error popups when starting Vesktop without working internet connection</li>
<li>Fixed popout title bars on Windows</li>
<li>Fixed spellcheck entries</li>
<li>Fixed screenshare audio using microphone on debian, by @Curve</li>
<li>Fixed a bug where autostart on Linux won't preserve command line flags</li>
</ul>
</description>
</release>
<release version="1.5.0" date="2024-01-16" type="stable">
<url>https://github.com/Vencord/Vesktop/releases/tag/v1.5.0</url>
<description>
<p>What's Changed</p>
<ul>
<li>fully renamed to Vesktop. You will likely have to login to Discord again. You might have to re-create your vesktop shortcut</li>
<li>added option to disable smooth scrolling by @ZirixCZ</li>
<li>added setting to disable hardware acceleration by @zt64</li>
<li>fixed adding connections</li>
<li>fixed / improved discord popouts</li>
<li>you can now use the custom discord titlebar on linux/mac</li>
<li>the splash window is now draggable</li>
<li>now signed on mac</li>
</ul>
</description>
</release>
<release version="0.4.4" date="2023-12-02" type="stable">
<url>https://github.com/Vencord/Vesktop/releases/tag/v0.4.4</url>
<description>
<p>What's Changed</p>
<ul>
<li>improve venmic system compatibility by @Curve</li>
<li>Update steamdeck controller layout by @AAGaming00</li>
<li>feat: Add option to disable smooth scrolling by @ZirixCZ</li>
<li>unblur shiggy in splash screen by @viacoro</li>
<li>update electron &amp; arrpc @D3SOX</li>
</ul>
</description>
</release>
<release version="0.4.3" date="2023-11-01" type="stable">
<url>https://github.com/Vencord/Vesktop/releases/tag/v0.4.3</url>
</release>
<release version="0.4.2" date="2023-10-26" type="stable">
<url>https://github.com/Vencord/Vesktop/releases/tag/v0.4.2</url>
</release>
<release version="0.4.1" date="2023-10-24" type="stable">
<url>https://github.com/Vencord/Vesktop/releases/tag/v0.4.1</url>
</release>
<release version="0.4.0" date="2023-10-21" type="stable">
<url>https://github.com/Vencord/Vesktop/releases/tag/v0.4.0</url>
</release>
<release version="0.3.3" date="2023-09-30" type="stable">
<url>https://github.com/Vencord/Vesktop/releases/tag/v0.3.3</url>
</release>
<release version="0.3.2" date="2023-09-25" type="stable">
<url>https://github.com/Vencord/Vesktop/releases/tag/v0.3.2</url>
</release>
<release version="0.3.1" date="2023-09-25" type="stable">
<url>https://github.com/Vencord/Vesktop/releases/tag/v0.3.1</url>
</release>
<release version="0.3.0" date="2023-08-16" type="stable">
<url>https://github.com/Vencord/Vesktop/releases/tag/v0.3.0</url>
</release>
<release version="0.2.9" date="2023-08-12" type="stable">
<url>https://github.com/Vencord/Vesktop/releases/tag/v0.2.9</url>
</release>
<release version="0.2.8" date="2023-08-02" type="stable">
<url>https://github.com/Vencord/Vesktop/releases/tag/v0.2.8</url>
</release>
<release version="0.2.7" date="2023-07-26" type="stable">
<url>https://github.com/Vencord/Vesktop/releases/tag/v0.2.7</url>
</release>
<release version="0.2.6" date="2023-07-04" type="stable">
<url>https://github.com/Vencord/Vesktop/releases/tag/v0.2.6</url>
</release>
<release version="0.2.5" date="2023-06-26" type="stable">
<url>https://github.com/Vencord/Vesktop/releases/tag/v0.2.5</url>
</release>
<release version="0.2.4" date="2023-06-25" type="stable">
<url>https://github.com/Vencord/Vesktop/releases/tag/v0.2.4</url>
</release>
<release version="0.2.3" date="2023-06-23" type="stable">
<url>https://github.com/Vencord/Vesktop/releases/tag/v0.2.3</url>
</release>
<release version="0.2.2" date="2023-06-21" type="stable">
<url>https://github.com/Vencord/Vesktop/releases/tag/v0.2.2</url>
</release>
<release version="0.2.1" date="2023-06-21" type="stable">
<url>https://github.com/Vencord/Vesktop/releases/tag/v0.2.1</url>
</release>
<release version="0.2.0" date="2023-05-03" type="stable">
<url>https://github.com/Vencord/Vesktop/releases/tag/v0.2.0</url>
</release>
<release version="0.1.9" date="2023-04-27" type="stable">
<url>https://github.com/Vencord/Vesktop/releases/tag/v0.1.9</url>
</release>
<release version="0.1.8" date="2023-04-15" type="stable">
<url>https://github.com/Vencord/Vesktop/releases/tag/v0.1.8</url>
</release>
<release version="0.1.7" date="2023-04-15" type="stable">
<url>https://github.com/Vencord/Vesktop/releases/tag/v0.1.7</url>
</release>
<release version="0.1.6" date="2023-04-11" type="stable">
<url>https://github.com/Vencord/Vesktop/releases/tag/v0.1.6</url>
</release>
<release version="0.1.5" date="2023-04-10" type="stable">
<url>https://github.com/Vencord/Vesktop/releases/tag/v0.1.5</url>
</release>
<release version="0.1.4" date="2023-04-09" type="stable">
<url>https://github.com/Vencord/Vesktop/releases/tag/v0.1.4</url>
</release>
<release version="0.1.3" date="2023-04-06" type="stable">
<url>https://github.com/Vencord/Vesktop/releases/tag/v0.1.3</url>
</release>
<release version="0.1.2" date="2023-04-05" type="stable">
<url>https://github.com/Vencord/Vesktop/releases/tag/v0.1.2</url>
</release>
<release version="0.1.1" date="2023-04-04" type="stable">
<url>https://github.com/Vencord/Vesktop/releases/tag/v0.1.1</url>
</release>
<release version="0.1.0" date="2023-04-04" type="development">
<url>https://github.com/Vencord/Vesktop/releases/tag/v0.1.0</url>
</release>
</releases>
<url type="homepage">https://vencord.dev/</url>
<url type="bugtracker">https://github.com/Vencord/Vesktop/issues</url>
<url type="faq">https://vencord.dev/faq/</url>
<url type="help">https://github.com/Vencord/Vesktop/issues</url>
<url type="donation">https://github.com/sponsors/Vendicated</url>
<url type="vcs-browser">https://github.com/Vencord/Vesktop</url>
<categories>
<category>InstantMessaging</category>
<category>Network</category>
</categories>
<requires>
<control>pointing</control>
<control>keyboard</control>
<display_length compare="ge">420</display_length>
<internet>always</internet>
</requires>
<recommends>
<control>voice</control>
<display_length compare="ge">760</display_length>
<display_length compare="le">1200</display_length>
</recommends>
<content_rating type="oars-1.1">
<content_attribute id="social-chat">intense</content_attribute>
<content_attribute id="social-audio">intense</content_attribute>
<content_attribute id="social-contacts">intense</content_attribute>
<content_attribute id="social-info">intense</content_attribute>
</content_rating>
<keywords>
<keyword>Discord</keyword>
<keyword>Vencord</keyword>
<keyword>Vesktop</keyword>
<keyword>Privacy</keyword>
<keyword>Mod</keyword>
</keywords>
</component>

View File

@@ -1,16 +1,17 @@
{
"name": "vesktop",
"version": "1.5.5",
"version": "1.6.0",
"private": true,
"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",
@@ -21,43 +22,44 @@
"test": "pnpm lint && pnpm testTypes",
"testTypes": "tsc --noEmit",
"watch": "pnpm build --watch",
"updateMeta": "tsx scripts/utils/updateMeta.mts",
"generateMeta": "tsx scripts/utils/generateMeta.mts",
"updateArrpcDB": "node ./node_modules/arrpc/update_db.js",
"postinstall": "pnpm updateArrpcDB"
},
"dependencies": {
"arrpc": "github:OpenAsar/arrpc#2234e9c9111f4c42ebcc3aa6a2215bfd979eef77",
"electron-updater": "^6.3.9"
"electron-updater": "^6.6.2"
},
"optionalDependencies": {
"@vencord/venmic": "^6.1.0"
},
"devDependencies": {
"@fal-works/esbuild-plugin-global-externals": "^2.1.2",
"@stylistic/eslint-plugin": "^3.0.1",
"@types/node": "^22.13.1",
"@types/react": "18.3.12",
"@vencord/types": "^1.8.4",
"dotenv": "^16.4.7",
"electron": "^34.1.0",
"electron-builder": "^25.1.8",
"esbuild": "^0.24.2",
"eslint": "^9.19.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-path-alias": "^2.1.0",
"eslint-plugin-prettier": "^5.2.3",
"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.1.4",
"prettier": "^3.4.2",
"eslint-plugin-unused-imports": "^4.2.0",
"libvesktop": "link:packages/libvesktop",
"prettier": "^3.6.2",
"source-map-support": "^0.5.21",
"tsx": "^4.19.2",
"type-fest": "^4.33.0",
"typescript": "^5.7.3",
"typescript-eslint": "^8.23.0",
"xml-formatter": "^3.6.4"
"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@9.1.0",
"packageManager": "pnpm@10.7.1",
"engines": {
"node": ">=18",
"pnpm": ">=8"
@@ -65,6 +67,7 @@
"build": {
"appId": "dev.vencord.vesktop",
"productName": "Vesktop",
"executableName": "vesktop",
"files": [
"!*",
"!node_modules",
@@ -79,9 +82,10 @@
"discord"
]
},
"beforePack": "scripts/build/sandboxFix.js",
"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": [
@@ -115,12 +119,15 @@
}
],
"desktop": {
"Name": "Vesktop",
"GenericName": "Internet Messenger",
"Type": "Application",
"Categories": "Network;InstantMessaging;Chat;",
"Keywords": "discord;vencord;electron;chat;",
"MimeType": "x-scheme-handler/discord"
"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": {
@@ -138,7 +145,8 @@
"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
},
@@ -168,6 +176,7 @@
"oneClick": false
},
"win": {
"icon": "build/icon.ico",
"target": [
{
"target": "nsis",
@@ -196,7 +205,13 @@
},
"pnpm": {
"patchedDependencies": {
"arrpc@3.5.0": "patches/arrpc@3.5.0.patch"
}
"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
View File

@@ -0,0 +1 @@
build

View 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

View 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
View 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
View File

@@ -0,0 +1,3 @@
export function getAccentColor(): number | null;
export function requestBackground(autoStart: boolean, commandLine: string[]): boolean;
export function updateUnityLauncherCount(count: number): boolean;

View 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
View 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: {}

Binary file not shown.

Binary file not shown.

View 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)

View 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);
});

View 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"));

2118
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View 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`);
}

View File

@@ -0,0 +1,5 @@
import { addAssetsCar } from "./addAssetsCar.mjs";
export default async function afterPack(context) {
await addAssetsCar(context);
}

View File

@@ -0,0 +1,5 @@
import { applyAppImageSandboxFix } from "./sandboxFix.mjs";
export default async function beforePack() {
await applyAppImageSandboxFix();
}

View File

@@ -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,19 +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/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=VesktopUpdaterPreload" }
}),
createContext({
...CommonOpts,
@@ -73,8 +116,8 @@ await Promise.all([
jsxFactory: "VencordCreateElement",
jsxFragment: "VencordFragment",
external: ["@vencord/types/*"],
plugins: [vencordDep],
footer: { js: "//# sourceURL=VCDRenderer" }
plugins: [vencordDep, includeDirPlugin("patches", "src/renderer/patches")],
footer: { js: "//# sourceURL=VesktopRenderer" }
})
]);

View 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
};
});
}
};
}

View File

@@ -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);

View File

@@ -1,23 +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
*/
// Based on https://github.com/gergof/electron-builder-sandbox-fix/blob/master/lib/index.js
const fs = require("fs/promises");
const path = require("path");
import fs from "fs/promises";
import path from "path";
import AppImageTarget from "app-builder-lib/out/targets/AppImageTarget.js";
let isApplied = false;
const hook = async () => {
if (isApplied) return;
isApplied = true;
export async function applyAppImageSandboxFix() {
if (process.platform !== "linux") {
// this fix is only required on linux
return;
}
const AppImageTarget = require("app-builder-lib/out/targets/AppImageTarget");
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);
@@ -69,6 +72,4 @@ exec "$SCRIPT_DIR/${this.packager.executableName}.bin" "$([ "$IS_STEAMOS" == 1 ]
return ret;
};
};
module.exports = hook;
}

View File

@@ -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";

View File

@@ -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
*/

View File

@@ -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";

View File

@@ -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";

View File

@@ -1,11 +1,11 @@
/*
* SPDX-License-Identifier: GPL-3.0
* Vesktop, a desktop app aiming to give you a snappier Discord Experience
* Copyright (c) 2023 Vendicated and Vencord contributors
* SPDX-License-Identifier: GPL-3.0-or-later
*/
import { promises as fs } from "node:fs";
import { mkdir } from "node:fs/promises";
import { DOMParser, XMLSerializer } from "@xmldom/xmldom";
import xmlFormat from "xml-formatter";
@@ -50,7 +50,9 @@ const latestReleaseInformation = await fetch("https://api.github.com/repos/Venco
}
}).then(res => res.json());
const metaInfo = await fs.readFile("./meta/dev.vencord.Vesktop.metainfo.xml", "utf-8");
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");
@@ -90,4 +92,7 @@ const output = xmlFormat(new XMLSerializer().serializeToString(parser), {
indentation: " "
});
await fs.writeFile("./meta/dev.vencord.Vesktop.metainfo.xml", output, "utf-8");
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");

View File

@@ -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
View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -1,37 +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 { IpcCommands } from "shared/IpcEvents";
import { sendRendererCommand } from "./ipcCommands";
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) => sendRendererCommand(IpcCommands.RPC_ACTIVITY, JSON.stringify(data)));
server.on("invite", async (invite: string, callback: (valid: boolean) => void) => {
invite = String(invite);
if (!inviteCodeRegex.test(invite)) return callback(false);
await sendRendererCommand(IpcCommands.RPC_INVITE, invite).then(callback);
});
server.on("link", async (data: any, deepCallback: (valid: boolean) => void) => {
await sendRendererCommand(IpcCommands.RPC_DEEP_LINK, data).then(deepCallback);
});
} catch (e) {
console.error("Failed to start arRPC server", e);
}
}
Settings.addChangeListener("arRPC", initArRPC);

77
src/main/arrpc/index.ts Normal file
View 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
View 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
View 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;
}
}
});
})();

View File

@@ -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, renameSync, rmSync, writeFileSync } from "fs";
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,32 +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, "vesktop.desktop");
// IM STUPID
const legacyName = join(dir, "vencord.desktop");
if (existsSync(legacyName)) renameSync(legacyName, file);
// "Quoting must be done by enclosing the argument between double quotes and escaping the double quote character,
// backtick character ("`"), dollar sign ("$") and backslash character ("\") by preceding it with an additional backslash character"
// https://specifications.freedesktop.org/desktop-entry-spec/desktop-entry-spec-latest.html#exec-variables
const commandLine = process.argv.map(arg => '"' + arg.replace(/["$`\\]/g, "\\$&") + '"').join(" ");
return {
isEnabled: () => existsSync(file),
enable() {
const desktopFile = `
[Desktop Entry]
Type=Application
Name=Vesktop
Comment=Vesktop autostart script
Exec=${commandLine}
StartupNotify=false
Terminal=false
`.trim();
const desktopFile = stripIndent`
[Desktop Entry]
Type=Application
Name=Vesktop
Comment=Vesktop autostart script
Exec=${getEscapedCommandLine().join(" ")}
StartupNotify=false
Terminal=false
Icon=vesktop
`;
mkdirSync(dir, { recursive: true });
writeFileSync(file, desktopFile);
@@ -48,10 +52,49 @@ Terminal=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
View 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);
}
}
}

View File

@@ -1,13 +1,15 @@
/*
* SPDX-License-Identifier: GPL-3.0
* Vesktop, a desktop app aiming to give you a snappier Discord Experience
* Copyright (c) 2023 Vendicated and Vencord contributors
* SPDX-License-Identifier: GPL-3.0-or-later
*/
import { app } from "electron";
import { existsSync, mkdirSync, readdirSync, renameSync, rmdirSync } from "fs";
import { existsSync, mkdirSync } from "fs";
import { dirname, join } from "path";
import { CommandLine } from "./cli";
const vesktopDir = dirname(process.execPath);
export const PORTABLE =
@@ -15,29 +17,12 @@ export const PORTABLE =
!process.execPath.toLowerCase().endsWith("electron.exe") &&
!existsSync(join(vesktopDir, "Uninstall Vesktop.exe"));
const LEGACY_DATA_DIR = join(app.getPath("appData"), "VencordDesktop", "VencordDesktop");
export const DATA_DIR =
process.env.VENCORD_USER_DATA_DIR || (PORTABLE ? join(vesktopDir, "Data") : join(app.getPath("userData")));
mkdirSync(DATA_DIR, { recursive: true });
// TODO: remove eventually
if (existsSync(LEGACY_DATA_DIR)) {
try {
console.warn("Detected legacy settings dir", LEGACY_DATA_DIR + ".", "migrating to", DATA_DIR);
for (const file of readdirSync(LEGACY_DATA_DIR)) {
renameSync(join(LEGACY_DATA_DIR, file), join(DATA_DIR, file));
}
rmdirSync(LEGACY_DATA_DIR);
renameSync(
join(app.getPath("appData"), "VencordDesktop", "IndexedDB"),
join(DATA_DIR, "sessionData", "IndexedDB")
);
} catch (e) {
console.error("Migration failed", e);
}
}
const SESSION_DATA_DIR = join(DATA_DIR, "sessionData");
export const SESSION_DATA_DIR = join(DATA_DIR, "sessionData");
app.setPath("sessionData", SESSION_DATA_DIR);
export const VENCORD_SETTINGS_DIR = join(DATA_DIR, "settings");
@@ -45,12 +30,6 @@ 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")).State.store.vencordDir ||
join(SESSION_DATA_DIR, "vencordFiles");
export const USER_AGENT = `Vesktop/${app.getVersion()} (https://github.com/Vencord/Vesktop)`;
// dimensions shamelessly stolen from Discord Desktop :3
@@ -68,9 +47,14 @@ const BrowserUserAgents = {
windows: `Mozilla/5.0 (Windows NT 10.0; Win64; x64) ${VersionString}`
};
export const BrowserUserAgent = BrowserUserAgents[process.platform] || BrowserUserAgents.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
View 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
View 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"];
}>();

View File

@@ -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,13 +9,13 @@ 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, State } from "./settings";
import { makeLinksOpenExternally } from "./utils/makeLinksOpenExternally";
import { loadView } from "./vesktopStatic";
interface Data {
discordBranch: "stable" | "canary" | "ptb";
@@ -32,13 +32,12 @@ export function createFirstLaunchTour() {
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();

View File

@@ -1,50 +1,60 @@
/*
* 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, nativeTheme } from "electron";
import { autoUpdater } from "electron-updater";
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, State } from "./settings";
import { setAsDefaultProtocolClient } from "./utils/setAsDefaultProtocolClient";
import { isDeckGameMode } from "./utils/steamOS";
if (IS_DEV) {
require("source-map-support").install();
} else {
autoUpdater.checkForUpdatesAndNotify();
}
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() {
app.setAsDefaultProtocolClient("discord");
setAsDefaultProtocolClient("discord");
const { disableSmoothScroll, hardwareAcceleration } = Settings.store;
const { disableSmoothScroll, hardwareAcceleration, hardwareVideoAcceleration } = Settings.store;
const enabledFeatures = app.commandLine.getSwitchValue("enable-features").split(",");
const disabledFeatures = app.commandLine.getSwitchValue("disable-features").split(",");
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) {
if (hardwareAcceleration === false || process.argv.includes("--disable-gpu")) {
enableHardwareAcceleration = false;
app.disableHardwareAcceleration();
} else {
enabledFeatures.push(
"AcceleratedVideoDecodeLinuxGL",
"AcceleratedVideoEncoder",
"AcceleratedVideoDecoder",
"AcceleratedVideoDecodeLinuxZeroCopyGL"
);
if (hardwareVideoAcceleration) {
enabledFeatures.add("AcceleratedVideoEncoder");
enabledFeatures.add("AcceleratedVideoDecoder");
if (isLinux) {
enabledFeatures.add("AcceleratedVideoDecodeLinuxGL");
enabledFeatures.add("AcceleratedVideoDecodeLinuxZeroCopyGL");
}
}
}
if (disableSmoothScroll) {
@@ -58,22 +68,37 @@ function init() {
app.commandLine.appendSwitch("disable-background-timer-throttling");
app.commandLine.appendSwitch("disable-backgrounding-occluded-windows");
if (process.platform === "win32") {
disabledFeatures.push("CalculateNativeWinOcclusion");
disabledFeatures.add("CalculateNativeWinOcclusion");
}
// work around chrome 66 disabling autoplay by default
app.commandLine.appendSwitch("autoplay-policy", "no-user-gesture-required");
// WinRetrieveSuggestionsOnlyOnDemand: Work around electron 13 bug w/ async spellchecking on Windows.
// HardwareMediaKeyHandling,MediaSessionService: Prevent Discord from registering as a media service.
//
// WidgetLayering (Vencord Added): Fix DevTools context menus https://github.com/electron/electron/issues/38790
disabledFeatures.push("WinRetrieveSuggestionsOnlyOnDemand", "HardwareMediaKeyHandling", "MediaSessionService");
// HardwareMediaKeyHandling, MediaSessionService: Prevent Discord from registering as a media service.
disabledFeatures.add("WinRetrieveSuggestionsOnlyOnDemand");
disabledFeatures.add("HardwareMediaKeyHandling");
disabledFeatures.add("MediaSessionService");
// Support TTS on Linux using speech-dispatcher
app.commandLine.appendSwitch("enable-speech-dispatcher");
if (isLinux) {
// Support TTS on Linux using https://wiki.archlinux.org/title/Speech_dispatcher
app.commandLine.appendSwitch("enable-speech-dispatcher");
}
app.commandLine.appendSwitch("enable-features", [...new Set(enabledFeatures)].filter(Boolean).join(","));
app.commandLine.appendSwitch("disable-features", [...new Set(disabledFeatures)].filter(Boolean).join(","));
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";

View File

@@ -1,15 +1,26 @@
/*
* SPDX-License-Identifier: GPL-3.0
* Vesktop, a desktop app aiming to give you a snappier Discord Experience
* Copyright (c) 2023 Vendicated and Vencord contributors
* SPDX-License-Identifier: GPL-3.0-or-later
*/
if (process.platform === "linux") import("./venmic");
import { execFile } from "child_process";
import { app, BrowserWindow, clipboard, dialog, nativeImage, RelaunchOptions, session, shell } from "electron";
import {
app,
BrowserWindow,
clipboard,
dialog,
IpcMainInvokeEvent,
nativeImage,
RelaunchOptions,
session,
shell
} from "electron";
import { mkdirSync, readFileSync, watch } from "fs";
import { open, readFile } from "fs/promises";
import { enableHardwareAcceleration } from "main";
import { release } from "os";
import { join } from "path";
import { debounce } from "shared/utils/debounce";
@@ -17,13 +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, 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, () =>
@@ -35,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,
@@ -68,28 +81,29 @@ handle(IpcEvents.SHOW_ITEM_IN_FOLDER, (_, path) => {
shell.showItemInFolder(path);
});
function getWindow(e: IpcMainInvokeEvent, key?: string) {
return key ? PopoutWindows.get(key)! : (BrowserWindow.fromWebContents(e.sender) ?? mainWin);
}
handle(IpcEvents.FOCUS, () => {
mainWin.show();
mainWin.setSkipTaskbar(false);
});
handle(IpcEvents.CLOSE, (e, key?: string) => {
const popout = PopoutWindows.get(key!);
if (popout) return popout.close();
const win = BrowserWindow.fromWebContents(e.sender) ?? e.sender;
win.close();
getWindow(e, key).close();
});
handle(IpcEvents.MINIMIZE, e => {
mainWin.minimize();
handle(IpcEvents.MINIMIZE, (e, key?: string) => {
getWindow(e, key).minimize();
});
handle(IpcEvents.MAXIMIZE, e => {
if (mainWin.isMaximized()) {
mainWin.unmaximize();
handle(IpcEvents.MAXIMIZE, (e, key?: string) => {
const win = getWindow(e, key);
if (win.isMaximized()) {
win.unmaximize();
} else {
mainWin.maximize();
win.maximize();
}
});

View File

@@ -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
* Copyright (c) 2025 Vendicated and Vencord contributors
* SPDX-License-Identifier: GPL-3.0-or-later
*/
import { randomUUID } from "crypto";
@@ -31,6 +31,11 @@ export interface IpcResponse {
* 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) => {

View File

@@ -1,51 +1,42 @@
/*
* 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,
screen,
session,
Tray
session
} from "electron";
import { rm } from "fs/promises";
import { join } from "path";
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 {
BrowserUserAgent,
DATA_DIR,
DEFAULT_HEIGHT,
DEFAULT_WIDTH,
MessageBoxChoice,
MIN_HEIGHT,
MIN_WIDTH,
VENCORD_FILES_DIR
} from "./constants";
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 } from "./splash";
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();
@@ -76,84 +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 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() {
isQuitting = true;
app.quit();
}
}
]);
tray = new Tray(ICON_PATH);
tray.setToolTip("Vesktop");
tray.setContextMenu(trayMenu);
tray.on("click", onTrayClick);
}
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) {
@@ -245,7 +158,7 @@ function initMenuBar(win: BrowserWindow) {
}
] satisfies MenuItemList;
const menu = Menu.buildFromTemplate([
const menuItems = [
{
label: "Vesktop",
role: "appMenu",
@@ -254,8 +167,10 @@ function initMenuBar(win: BrowserWindow) {
{ role: "fileMenu" },
{ role: "editMenu" },
{ role: "viewMenu" },
{ role: "windowMenu" }
]);
isDarwin && { role: "windowMenu" }
] satisfies MenuItemList;
const menu = Menu.buildFromTemplate(menuItems.filter(isTruthy));
Menu.setApplicationMenu(menu);
}
@@ -330,8 +245,8 @@ 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 => {
@@ -385,6 +300,15 @@ function initSpellCheck(win: BrowserWindow) {
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();
@@ -427,7 +351,6 @@ function createMainWindow() {
// disable renderer backgrounding to prevent the app from unloading when in the background
backgroundThrottling: false
},
icon: ICON_PATH,
frame: !noFrame,
...(transparent && {
transparent: true,
@@ -465,17 +388,22 @@ function createMainWindow() {
});
initWindowBoundsListeners(win);
if (!isDeckGameMode && (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(BrowserUserAgent);
// 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;
}
@@ -485,11 +413,23 @@ const runVencordMain = once(() => require(join(VENCORD_FILES_DIR, "vencordDeskto
export function loadUrl(uri: string | undefined) {
const branch = Settings.store.discordBranch;
const subdomain = branch === "canary" || branch === "ptb" ? `${branch}.` : "";
mainWin.loadURL(`https://${subdomain}discord.com/${uri ? new URL(uri).pathname.slice(1) || "app" : "app"}`);
// 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 startMinimized = process.argv.includes("--start-minimized");
const startMinimized = CommandLine.values["start-minimized"];
let splash: BrowserWindow | undefined;
if (Settings.store.enableSplashScreen !== false) {
@@ -504,7 +444,7 @@ export async function createWindows() {
mainWin = createMainWindow();
mainWin.webContents.on("did-finish-load", () => {
AppEvents.on("appLoaded", () => {
splash?.destroy();
if (!startMinimized) {
@@ -527,6 +467,8 @@ export async function createWindows() {
});
mainWin.webContents.on("did-navigate", (_, url: string, responseCode: number) => {
updateSplashMessage(""); // clear the splash message
// check url to ensure app doesn't loop
if (responseCode >= 300 && new URL(url).pathname !== `/app`) {
loadUrl(undefined);

View File

@@ -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";

View File

@@ -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({});

View File

@@ -1,10 +1,10 @@
/*
* SPDX-License-Identifier: GPL-3.0
* Vesktop, a desktop app aiming to give you a snappier Discord Experience
* Copyright (c) 2023 Vendicated and Vencord contributors
* SPDX-License-Identifier: GPL-3.0-or-later
*/
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
import { mkdirSync, readFileSync, writeFileSync } from "fs";
import { dirname, join } from "path";
import type { Settings as TSettings, State as TState } from "shared/settings";
import { SettingsStore } from "shared/utils/SettingsStore";
@@ -35,18 +35,5 @@ function loadSettings<T extends object = any>(file: string, name: string) {
}
export const Settings = loadSettings<TSettings>(SETTINGS_FILE, "Vesktop settings");
export const VencordSettings = loadSettings<any>(VENCORD_SETTINGS_FILE, "Vencord settings");
if (Object.hasOwn(Settings.plain, "firstLaunch") && !existsSync(STATE_FILE)) {
console.warn("legacy state in settings.json detected. migrating to state.json");
const state = {} as TState;
for (const prop of ["firstLaunch", "maximized", "minimized", "steamOSLayoutVersion", "windowBounds"] as const) {
state[prop] = Settings.plain[prop];
delete Settings.plain[prop];
}
Settings.markAsChanged();
writeFileSync(STATE_FILE, JSON.stringify(state, null, 4));
}
export const State = loadSettings<TState>(STATE_FILE, "Vesktop state");

View File

@@ -1,24 +1,28 @@
/*
* 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";
let splash: BrowserWindow | undefined;
export function createSplashWindow(startMinimized = false) {
const splash = new BrowserWindow({
splash = new BrowserWindow({
...SplashProps,
icon: ICON_PATH,
show: !startMinimized
show: !startMinimized,
webPreferences: {
preload: join(__dirname, "splashPreload.js")
}
});
splash.loadFile(join(VIEW_DIR, "splash.html"));
loadView(splash, "splash.html");
const { splashBackground, splashColor, splashTheming } = Settings.store;
@@ -37,3 +41,7 @@ export function createSplashWindow(startMinimized = false) {
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
View 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
View 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
View 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";
}
});

View 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();
}

View 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;
}

View 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);
}

View File

@@ -1,7 +1,7 @@
/*
* SPDX-License-Identifier: GPL-3.0
* Vesktop, a desktop app aiming to give you a snappier Discord Experience
* Copyright (c) 2023 Vendicated and Vencord contributors
* SPDX-License-Identifier: GPL-3.0-or-later
*/
import { createWriteStream } from "fs";

View File

@@ -1,32 +1,40 @@
/*
* SPDX-License-Identifier: GPL-3.0
* Vesktop, a desktop app aiming to give you a snappier Discord Experience
* Copyright (c) 2023 Vendicated and Vencord contributors
* SPDX-License-Identifier: GPL-3.0-or-later
*/
import { ipcMain, IpcMainEvent, IpcMainInvokeEvent, WebFrameMain } from "electron";
import { DISCORD_HOSTNAMES } from "main/constants";
import { IpcEvents } from "shared/IpcEvents";
import { IpcEvents, UpdaterIpcEvents } from "shared/IpcEvents";
export function validateSender(frame: WebFrameMain | null) {
if (!frame) throw new Error("ipc: No sender frame");
export function validateSender(frame: WebFrameMain | null, event: string) {
if (!frame) throw new Error(`ipc[${event}]: No sender frame`);
if (!frame.url) return;
const { hostname, protocol } = new URL(frame.url);
if (protocol === "file:") return;
try {
var { hostname, protocol } = new URL(frame.url);
} catch (e) {
throw new Error(`ipc[${event}]: Invalid URL ${frame.url}`);
}
if (!DISCORD_HOSTNAMES.includes(hostname)) throw new Error("ipc: Disallowed host " + hostname);
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);
});
}

View 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;
}

View File

@@ -1,7 +1,7 @@
/*
* SPDX-License-Identifier: GPL-3.0
* Vesktop, a desktop app aiming to give you a snappier Discord Experience
* Copyright (c) 2023 Vendicated and Vencord contributors
* SPDX-License-Identifier: GPL-3.0-or-later
*/
import { BrowserWindow, shell } from "electron";
@@ -50,7 +50,7 @@ export function handleExternalUrl(url: string, protocol?: string): { action: "de
export function makeLinksOpenExternally(win: BrowserWindow) {
win.webContents.setWindowOpenHandler(({ url, frameName, features }) => {
try {
var { protocol, hostname, pathname } = new URL(url);
var { protocol, hostname, pathname, searchParams } = new URL(url);
} catch {
return { action: "deny" };
}
@@ -59,8 +59,10 @@ export function makeLinksOpenExternally(win: BrowserWindow) {
return createOrFocusPopup(frameName, features);
}
if (url === "about:blank" || (frameName === "authorize" && DISCORD_HOSTNAMES.includes(hostname)))
return { action: "allow" };
if (url === "about:blank") return { action: "allow" };
// Drop the static temp page Discord web loads for the connections popout
if (frameName === "authorize" && searchParams.get("loading") === "true") return { action: "deny" };
return handleExternalUrl(url, protocol);
});

View File

@@ -1,7 +1,7 @@
/*
* SPDX-License-Identifier: GPL-3.0
* Vesktop, a desktop app aiming to give you a snappier Discord Experience
* Copyright (c) 2023 Vendicated and Vencord contributors
* SPDX-License-Identifier: GPL-3.0-or-later
*/
import { BrowserWindow, BrowserWindowConstructorOptions } from "electron";

View 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);
});
});
}

View File

@@ -1,7 +1,7 @@
/*
* SPDX-License-Identifier: GPL-3.0
* Vesktop, a desktop app aiming to give you a snappier Discord Experience
* Copyright (c) 2023 Vendicated and Vencord contributors
* SPDX-License-Identifier: GPL-3.0-or-later
*/
import { BrowserWindow, dialog } from "electron";

View File

@@ -1,14 +1,15 @@
/*
* SPDX-License-Identifier: GPL-3.0
* Vesktop, a desktop app aiming to give you a snappier Discord Experience
* Copyright (c) 2023 Vendicated and Vencord contributors
* SPDX-License-Identifier: GPL-3.0-or-later
*/
import { mkdirSync } from "fs";
import { access, constants as FsConstants } from "fs/promises";
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 { USER_AGENT } from "../constants";
import { downloadFile, fetchie } from "./http";
const API_BASE = "https://api.github.com";
@@ -63,7 +64,8 @@ const existsAsync = (path: string) =>
.catch(() => false);
export async function isValidVencordInstall(dir: string) {
return Promise.all(FILES_TO_DOWNLOAD.map(f => existsAsync(join(dir, f)))).then(arr => !arr.includes(false));
const results = await Promise.all(["package.json", ...FILES_TO_DOWNLOAD].map(f => existsAsync(join(dir, f))));
return !results.includes(false);
}
export async function ensureVencordFiles() {
@@ -71,5 +73,5 @@ export async function ensureVencordFiles() {
mkdirSync(VENCORD_FILES_DIR, { recursive: true });
await downloadVencordFiles();
await Promise.all([downloadVencordFiles(), writeFile(join(VENCORD_FILES_DIR, "package.json"), "{}")]);
}

View 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");

View File

@@ -1,7 +1,7 @@
/*
* SPDX-License-Identifier: GPL-3.0
* Vesktop, a desktop app aiming to give you a snappier Discord Experience
* Copyright (c) 2023 Vendicated and Vencord contributors
* SPDX-License-Identifier: GPL-3.0-or-later
*/
import type { LinkData, Node, PatchBay as PatchBayType } from "@vencord/venmic";

View 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
View 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());
}

10
src/module.d.ts vendored Normal file
View 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;
}

View File

@@ -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 { Node } from "@vencord/venmic";
@@ -20,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),
@@ -35,7 +44,9 @@ export const VesktopNative = {
fileManager: {
showItemInFolder: (path: string) => invoke<void>(IpcEvents.SHOW_ITEM_IN_FOLDER, path),
getVencordDir: () => sendSync<string | undefined>(IpcEvents.GET_VENCORD_DIR),
selectVencordDir: (value?: null) => invoke<"cancelled" | "invalid" | "ok">(IpcEvents.SELECT_VENCORD_DIR, value)
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),
@@ -55,8 +66,12 @@ export const VesktopNative = {
win: {
focus: () => invoke<void>(IpcEvents.FOCUS),
close: (key?: string) => invoke<void>(IpcEvents.CLOSE, key),
minimize: () => invoke<void>(IpcEvents.MINIMIZE),
maximize: () => invoke<void>(IpcEvents.MAXIMIZE)
minimize: (key?: string) => invoke<void>(IpcEvents.MINIMIZE, key),
maximize: (key?: string) => invoke<void>(IpcEvents.MAXIMIZE, key),
setDevtoolsCallbacks: (onOpen: () => void, onClose: () => void) => {
onDevtoolsOpen = onOpen;
onDevtoolsClose = onClose;
}
},
capturer: {
getLargeThumbnail: (id: string) => invoke<string>(IpcEvents.CAPTURER_GET_LARGE_THUMBNAIL, id)

View File

@@ -1,7 +1,7 @@
/*
* SPDX-License-Identifier: GPL-3.0
* Vesktop, a desktop app aiming to give you a snappier Discord Experience
* Copyright (c) 2023 Vendicated and Vencord contributors
* SPDX-License-Identifier: GPL-3.0-or-later
*/
import { contextBridge, ipcRenderer, webFrame } from "electron";

13
src/preload/splash.ts Normal file
View 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));
}
});

View File

@@ -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
View 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)
});

View File

@@ -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 { filters, waitFor } from "@vencord/types/webpack";

View File

@@ -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
* Copyright (c) 2025 Vendicated and Vencord contributors
* SPDX-License-Identifier: GPL-3.0-or-later
*/
import { Logger } from "@vencord/types/utils";

9
src/renderer/common.ts Normal file
View 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");

View File

@@ -1,37 +1,28 @@
/*
* SPDX-License-Identifier: GPL-3.0
* Vesktop, a desktop app aiming to give you a snappier Discord Experience
* Copyright (c) 2023 Vendicated and Vencord contributors
* SPDX-License-Identifier: GPL-3.0-or-later
*/
import "./screenSharePicker.css";
import { classNameFactory } from "@vencord/types/api/Styles";
import { FormSwitch } from "@vencord/types/components";
import { closeModal, Logger, Modals, ModalSize, openModal, useAwaiter } from "@vencord/types/utils";
import { findStoreLazy, onceReady } from "@vencord/types/webpack";
import {
Button,
Card,
FluxDispatcher,
Forms,
Select,
Switch,
Text,
UserStore,
useState
} from "@vencord/types/webpack/common";
import { onceReady } from "@vencord/types/webpack";
import { Button, Card, FluxDispatcher, Forms, Select, Text, UserStore, useState } from "@vencord/types/webpack/common";
import { Node } from "@vencord/venmic";
import type { Dispatch, SetStateAction } from "react";
import { MediaEngineStore } from "renderer/common";
import { addPatch } from "renderer/patches/shared";
import { State, useSettings, useVesktopState } from "renderer/settings";
import { classNameFactory, isLinux, isWindows } from "renderer/utils";
import { isLinux, isWindows } from "renderer/utils";
const StreamResolutions = ["480", "720", "1080", "1440", "2160"] as const;
const StreamFps = ["15", "30", "60"] as const;
const cl = classNameFactory("vcd-screen-picker-");
const MediaEngineStore = findStoreLazy("MediaEngineStore");
export type StreamResolution = (typeof StreamResolutions)[number];
export type StreamFps = (typeof StreamFps)[number];
@@ -203,67 +194,64 @@ function AudioSettingsModal({
<Modals.ModalCloseButton onClick={close} />
</Modals.ModalHeader>
<Modals.ModalContent className={cl("modal")}>
<Switch
hideBorder
onChange={v => (Settings.audio = { ...Settings.audio, workaround: v })}
value={Settings.audio?.workaround ?? false}
note={
<FormSwitch
title="Microphone Workaround"
description={
<>
Work around an issue that causes the microphone to be shared instead of the correct audio.
Only enable if you're experiencing this issue.
</>
}
>
Microphone Workaround
</Switch>
<Switch
hideBorder
onChange={v => (Settings.audio = { ...Settings.audio, onlySpeakers: v })}
value={Settings.audio?.onlySpeakers ?? true}
note={
onChange={v => (Settings.audio = { ...Settings.audio, workaround: v })}
value={Settings.audio?.workaround ?? false}
/>
<FormSwitch
title="Only Speakers"
description={
<>
When sharing entire desktop audio, only share apps that play to a speaker. You may want to
disable this when using "mix bussing".
</>
}
>
Only Speakers
</Switch>
<Switch
hideBorder
onChange={v => (Settings.audio = { ...Settings.audio, onlyDefaultSpeakers: v })}
value={Settings.audio?.onlyDefaultSpeakers ?? true}
note={
onChange={v => (Settings.audio = { ...Settings.audio, onlySpeakers: v })}
value={Settings.audio?.onlySpeakers ?? true}
/>
<FormSwitch
title="Only Default Speakers"
description={
<>
When sharing entire desktop audio, only share apps that play to the <b>default</b> speakers.
You may want to disable this when using "mix bussing".
</>
}
>
Only Default Speakers
</Switch>
<Switch
hideBorder
onChange={v => (Settings.audio = { ...Settings.audio, onlyDefaultSpeakers: v })}
value={Settings.audio?.onlyDefaultSpeakers ?? true}
/>
<FormSwitch
title="Ignore Inputs"
description={<>Exclude nodes that are intended to capture audio.</>}
hideBorder
onChange={v => (Settings.audio = { ...Settings.audio, ignoreInputMedia: v })}
value={Settings.audio?.ignoreInputMedia ?? true}
note={<>Exclude nodes that are intended to capture audio.</>}
>
Ignore Inputs
</Switch>
<Switch
hideBorder
onChange={v => (Settings.audio = { ...Settings.audio, ignoreVirtual: v })}
value={Settings.audio?.ignoreVirtual ?? false}
note={
/>
<FormSwitch
title="Ignore Virtual"
description={
<>
Exclude virtual nodes, such as nodes belonging to loopbacks. This might be useful when using
"mix bussing".
</>
}
>
Ignore Virtual
</Switch>
<Switch
hideBorder
onChange={v => (Settings.audio = { ...Settings.audio, ignoreVirtual: v })}
value={Settings.audio?.ignoreVirtual ?? false}
/>
<FormSwitch
title="Ignore Devices"
description={<>Exclude device nodes, such as nodes belonging to microphones or speakers.</>}
hideBorder
onChange={v =>
(Settings.audio = {
@@ -273,22 +261,25 @@ function AudioSettingsModal({
})
}
value={Settings.audio?.ignoreDevices ?? true}
note={<>Exclude device nodes, such as nodes belonging to microphones or speakers.</>}
>
Ignore Devices
</Switch>
<Switch
/>
<FormSwitch
title="Granular Selection"
description={<>Allow to select applications more granularly.</>}
hideBorder
onChange={value => {
Settings.audio = { ...Settings.audio, granularSelect: value };
setAudioSources("None");
}}
value={Settings.audio?.granularSelect ?? false}
note={<>Allow to select applications more granularly.</>}
>
Granular Selection
</Switch>
<Switch
/>
<FormSwitch
title="Device Selection"
description={
<>
Allow to select devices such as microphones. Requires <b>Ignore Devices</b> to be turned
off.
</>
}
hideBorder
onChange={value => {
Settings.audio = { ...Settings.audio, deviceSelect: value };
@@ -296,15 +287,7 @@ function AudioSettingsModal({
}}
value={Settings.audio?.deviceSelect ?? false}
disabled={Settings.audio?.ignoreDevices}
note={
<>
Allow to select devices such as microphones. Requires <b>Ignore Devices</b> to be turned
off.
</>
}
>
Device Selection
</Switch>
/>
</Modals.ModalContent>
<Modals.ModalFooter className={cl("footer")}>
<Button color={Button.Colors.TRANSPARENT} onClick={close}>
@@ -428,14 +411,13 @@ function StreamSettingsUi({
</div>
</div>
{isWindows && (
<Switch
<FormSwitch
title="Stream With Audio"
hideBorder
value={settings.audio}
onChange={checked => setSettings(s => ({ ...s, audio: checked }))}
hideBorder
className={cl("audio")}
>
Stream With Audio
</Switch>
/>
)}
</section>
</div>

View File

@@ -1,7 +1,7 @@
/*
* SPDX-License-Identifier: GPL-3.0
* Vesktop, a desktop app aiming to give you a snappier Discord Experience
* Copyright (c) 2023 Vendicated and Vencord contributors
* SPDX-License-Identifier: GPL-3.0-or-later
*/
export * as ScreenShare from "./ScreenSharePicker";

View File

@@ -1,26 +1,36 @@
/*
* SPDX-License-Identifier: GPL-3.0
* Vesktop, a desktop app aiming to give you a snappier Discord Experience
* Copyright (c) 2023 Vendicated and Vencord contributors
* SPDX-License-Identifier: GPL-3.0-or-later
*/
import { Switch, useState } from "@vencord/types/webpack/common";
import { useState } from "@vencord/types/webpack/common";
import { SettingsComponent } from "./Settings";
import { VesktopSettingsSwitch } from "./VesktopSettingsSwitch";
export const AutoStartToggle: SettingsComponent = () => {
export const AutoStartToggle: SettingsComponent = ({ settings }) => {
const [autoStartEnabled, setAutoStartEnabled] = useState(VesktopNative.autostart.isEnabled());
return (
<Switch
value={autoStartEnabled}
onChange={async v => {
await VesktopNative.autostart[v ? "enable" : "disable"]();
setAutoStartEnabled(v);
}}
note="Automatically start Vesktop on computer start-up"
>
Start With System
</Switch>
<>
<VesktopSettingsSwitch
title="Start With System"
description="Automatically start Vesktop on computer start-up"
value={autoStartEnabled}
onChange={async v => {
await VesktopNative.autostart[v ? "enable" : "disable"]();
setAutoStartEnabled(v);
}}
/>
<VesktopSettingsSwitch
title="Auto Start Minimized"
description={"Start Vesktop minimized when starting with system"}
value={settings.autoStartMinimized ?? false}
onChange={v => (settings.autoStartMinimized = v)}
disabled={!autoStartEnabled}
/>
</>
);
};

View File

@@ -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
* Copyright (c) 2025 Vendicated and Vencord contributors
* SPDX-License-Identifier: GPL-3.0-or-later
*/
import {
@@ -17,7 +17,7 @@ import {
import { Button, Forms, Text, Toasts } from "@vencord/types/webpack/common";
import { Settings } from "shared/settings";
import { SettingsComponent } from "./Settings";
import { cl, SettingsComponent } from "./Settings";
export const DeveloperOptionsButton: SettingsComponent = ({ settings }) => {
return <Button onClick={() => openDeveloperOptionsModal(settings)}>Open Developer Settings</Button>;
@@ -41,7 +41,7 @@ function openDeveloperOptionsModal(settings: Settings) {
<Forms.FormTitle tag="h5" className={Margins.top16}>
Debugging
</Forms.FormTitle>
<div className="vcd-settings-button-grid">
<div className={cl("button-grid")}>
<Button onClick={() => VesktopNative.debug.launchGpu()}>Open chrome://gpu</Button>
<Button onClick={() => VesktopNative.debug.launchWebrtcInternals()}>
Open chrome://webrtc-internals
@@ -75,7 +75,7 @@ const VencordLocationPicker: SettingsComponent = ({ settings }) => {
"the default location"
)}
</Forms.FormText>
<div className="vcd-settings-button-grid">
<div className={cl("button-grid")}>
<Button
size={Button.Sizes.SMALL}
onClick={async () => {

View File

@@ -1,26 +1,29 @@
/*
* SPDX-License-Identifier: GPL-3.0
* Vesktop, a desktop app aiming to give you a snappier Discord Experience
* Copyright (c) 2023 Vendicated and Vencord contributors
* SPDX-License-Identifier: GPL-3.0-or-later
*/
import { ErrorBoundary } from "@vencord/types/components";
import { Select } from "@vencord/types/webpack/common";
import { SettingsComponent } from "./Settings";
export const DiscordBranchPicker: SettingsComponent = ({ settings }) => {
return (
<Select
placeholder="Stable"
options={[
{ label: "Stable", value: "stable", default: true },
{ label: "Canary", value: "canary" },
{ label: "PTB", value: "ptb" }
]}
closeOnSelect={true}
select={v => (settings.discordBranch = v)}
isSelected={v => v === settings.discordBranch}
serialize={s => s}
/>
<ErrorBoundary noop>
<Select
placeholder="Stable"
options={[
{ label: "Stable", value: "stable", default: true },
{ label: "Canary", value: "canary" },
{ label: "PTB", value: "ptb" }
]}
closeOnSelect={true}
select={v => (settings.discordBranch = v)}
isSelected={v => v === settings.discordBranch}
serialize={s => s}
/>
</ErrorBoundary>
);
};

View File

@@ -1,26 +1,25 @@
/*
* SPDX-License-Identifier: GPL-3.0
* Vesktop, a desktop app aiming to give you a snappier Discord Experience
* Copyright (c) 2023 Vendicated and Vencord contributors
* SPDX-License-Identifier: GPL-3.0-or-later
*/
import { Switch } from "@vencord/types/webpack/common";
import { setBadge } from "renderer/appBadge";
import { SettingsComponent } from "./Settings";
import { VesktopSettingsSwitch } from "./VesktopSettingsSwitch";
export const NotificationBadgeToggle: SettingsComponent = ({ settings }) => {
return (
<Switch
<VesktopSettingsSwitch
title="Notification Badge"
description="Show mention badge on the app icon"
value={settings.appBadge ?? true}
onChange={v => {
settings.appBadge = v;
if (v) setBadge();
else VesktopNative.app.setBadgeCount(0);
}}
note="Show mention badge on the app icon"
>
Notification Badge
</Switch>
/>
);
};

View File

@@ -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 "./settings.css";
import { classNameFactory } from "@vencord/types/api/Styles";
import { ErrorBoundary } from "@vencord/types/components";
import { Forms, Switch, Text } from "@vencord/types/webpack/common";
import { Forms, Text } from "@vencord/types/webpack/common";
import { ComponentType } from "react";
import { Settings, useSettings } from "renderer/settings";
import { isMac, isWindows } from "renderer/utils";
@@ -16,6 +17,9 @@ import { AutoStartToggle } from "./AutoStartToggle";
import { DeveloperOptionsButton } from "./DeveloperOptions";
import { DiscordBranchPicker } from "./DiscordBranchPicker";
import { NotificationBadgeToggle } from "./NotificationBadgeToggle";
import { Updater } from "./Updater";
import { UserAssetsButton } from "./UserAssets";
import { VesktopSettingsSwitch } from "./VesktopSettingsSwitch";
import { WindowsTransparencyControls } from "./WindowsTransparencyControls";
interface BooleanSetting {
@@ -27,6 +31,8 @@ interface BooleanSetting {
invisible?(): boolean;
}
export const cl = classNameFactory("vcd-settings-");
export type SettingsComponent = ComponentType<{ settings: typeof Settings.store }>;
const SettingsOptions: Record<string, Array<BooleanSetting | SettingsComponent>> = {
@@ -38,6 +44,14 @@ const SettingsOptions: Record<string, Array<BooleanSetting | SettingsComponent>>
title: "Hardware Acceleration",
description: "Enable hardware acceleration",
defaultValue: true
},
{
key: "hardwareVideoAcceleration",
title: "Video Hardware Acceleration",
description:
"Enable hardware video acceleration. This can improve performance of screenshare and video playback, but may cause graphical glitches and infinitely loading streams.",
defaultValue: false,
disabled: () => Settings.store.hardwareAcceleration === false
}
],
"User Interface": [
@@ -73,7 +87,8 @@ const SettingsOptions: Record<string, Array<BooleanSetting | SettingsComponent>>
description: "Adapt the splash window colors to your custom theme",
defaultValue: true
},
WindowsTransparencyControls
WindowsTransparencyControls,
UserAssetsButton
],
Behaviour: [
{
@@ -132,32 +147,34 @@ const SettingsOptions: Record<string, Array<BooleanSetting | SettingsComponent>>
function SettingsSections() {
const Settings = useSettings();
const sections = Object.entries(SettingsOptions).map(([title, settings]) => (
<Forms.FormSection
title={title}
key={title}
className="vcd-settings-section"
titleClassName="vcd-settings-title"
>
{settings.map(Setting => {
if (typeof Setting === "function") return <Setting settings={Settings} />;
const sections = Object.entries(SettingsOptions).map(([title, settings], i, arr) => (
<div key={title} className={cl("category")}>
<Text variant="heading-lg/semibold" color="header-primary" className={cl("category-title")}>
{title}
</Text>
const { defaultValue, title, description, key, disabled, invisible } = Setting;
if (invisible?.()) return null;
<div className={cl("category-content")}>
{settings.map(Setting => {
if (typeof Setting === "function") return <Setting settings={Settings} />;
return (
<Switch
value={Settings[key as any] ?? defaultValue}
onChange={v => (Settings[key as any] = v)}
note={description}
disabled={disabled?.()}
key={key}
>
{title}
</Switch>
);
})}
</Forms.FormSection>
const { defaultValue, title, description, key, disabled, invisible } = Setting;
if (invisible?.()) return null;
return (
<VesktopSettingsSwitch
title={title}
description={description}
value={Settings[key as any] ?? defaultValue}
onChange={v => (Settings[key as any] = v)}
disabled={disabled?.()}
key={key}
/>
);
})}
</div>
{i < arr.length - 1 && <Forms.FormDivider className={cl("category-divider")} />}
</div>
));
return <>{sections}</>;
@@ -166,13 +183,13 @@ function SettingsSections() {
export default ErrorBoundary.wrap(
function SettingsUI() {
return (
<Forms.FormSection>
<Text variant="heading-lg/semibold" style={{ color: "var(--header-primary)" }} tag="h2">
<section>
<Text variant="heading-xl/semibold" color="header-primary" className={cl("title")}>
Vesktop Settings
</Text>
<Updater />
<SettingsSections />
</Forms.FormSection>
</section>
);
},
{

View 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 { useAwaiter } from "@vencord/types/utils";
import { Button, Text } from "@vencord/types/webpack/common";
import { cl } from "./Settings";
export function Updater() {
const [isOutdated] = useAwaiter(VesktopNative.app.isOutdated);
if (!isOutdated) return null;
return (
<div className={cl("updater-card")}>
<Text variant="text-md/semibold">Your Vesktop is outdated!</Text>
<Text variant="text-sm/normal">Staying up to date is important for security and stability.</Text>
<Button
onClick={() => VesktopNative.app.openUpdater()}
size={Button.Sizes.SMALL}
color={Button.Colors.TRANSPARENT}
>
Open Updater
</Button>
</div>
);
}

Some files were not shown because too many files have changed in this diff Show More