50 Commits

Author SHA1 Message Date
Vendicated
7e33780743 Fix Splash colour retrieval & show main window earlier if splash is disabled 2025-02-06 05:36:10 +01:00
Vendicated
9905592b24 add option to disable splash screen 2025-02-06 05:16:32 +01:00
Vendicated
6e1913cafc bump to v1.5.5 2025-02-06 04:56:54 +01:00
Vendicated
b620e07445 remember stream resolution & fps settings 2025-02-06 04:51:20 +01:00
Vendicated
9b0503f49d fix naming of displayid -> displayId 2025-02-06 04:27:52 +01:00
Vendicated
c0b79e6e93 make static title option apply without restart 2025-02-06 04:19:57 +01:00
Vendicated
57cae6f9f1 Settings should be wrapped in ErrorBoundary 2025-02-06 04:13:03 +01:00
Vendicated
2e72fa6589 use proper loggers 2025-02-06 04:00:14 +01:00
Vendicated
4ee57da6f3 remove legacy migration code 2025-02-06 03:47:38 +01:00
Cookie
7560727372 Support discord:// uri scheme (#813)
Co-authored-by: v <vendicated@riseup.net>
2025-02-06 03:45:20 +01:00
BlurOne!
6ba562c663 Enhance debug-logs textarea (#1078)
Update the debug-logs textarea to use the render feature, which automatically makes the content of the textarea into a code block, instead of requiring the default value with backticks.
2025-02-05 23:58:59 +01:00
Vendicated
872b60be1c bump deps so cookie quits yelling at me about arrpc 2025-02-05 20:27:32 +01:00
Cookie
8cd80f4af1 fix: First start ui being blank on windows (#1043) 2025-02-05 20:15:20 +01:00
Cookie
7b5e1ed4da arrpc: support OBS auto streamer mode (#822)
Co-authored-by: Vendicated <vendicated@riseup.net>
2025-02-05 19:12:38 +00:00
Cookie
56442ae1e9 feat(rpc): implement arrpc's link event (#1016)
Co-authored-by: Vendicated <vendicated@riseup.net>
2025-02-05 20:00:40 +01:00
Vendicated
c9be618164 add main to renderer command API 2025-02-02 03:17:45 +01:00
Cookie
eddbe27c4d update hardware accel flags for chromium 131 (#1058)
Co-authored-by: v <vendicated@riseup.net>
2025-02-02 01:51:04 +01:00
Vendicated
00fb658355 clean up and optimise Screenshare UI & CSS 2025-02-01 19:49:56 +01:00
v
67a1847cea please stop opening issues about screenshare stuff 2025-01-31 05:08:02 +01:00
Vendicated
030ffca499 settings: add buttons to open chrome://gpu & webrtc-internals 2025-01-24 01:09:25 +01:00
Vendicated
53913c07bf npm postinstall: update arrpc detectables database 2025-01-22 22:14:27 +01:00
Vendicated
f6e29231f5 update dependencies 2025-01-22 22:14:27 +01:00
RSKYS
7a19c81964 README.md: add slackware link to unofficial repos (#1056) 2025-01-20 12:05:31 +01:00
makin
5a72491ab0 Add 4k resolution to screenshare (#1024) 2025-01-15 17:40:56 +01:00
github-actions[bot]
6c4ecc0d64 Metainfo for v1.5.4 (#990)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2024-12-05 23:07:25 +01:00
Vendicated
524fc8bc0a bump to v1.5.4 2024-12-05 20:49:47 +01:00
Cookie
5b6c1c6d81 correctly set splash background (#937)
fixes #858
2024-12-03 04:02:34 +01:00
Vendicated
852410a43b Update Electron ~ fixes blue & light mode devtools 2024-12-03 03:58:54 +01:00
Vendicated
5d675efb64 upgrade to electron33 it probably goes crazy idk what they changed 2024-10-26 13:56:28 +02:00
lewisakura
03c7ad4cc0 ci: remove mentions from meta workflow [skip ci] 2024-10-22 20:22:00 +01:00
δDD
135a369848 fix typo in Settings ui (#924)
Fixed spelling mistake (Miscelleanous → Miscellaneous)
2024-10-22 13:44:49 +00:00
ryanamay
8993b0d520 fix screenshare picker window subtitle alignment (#875)
Co-authored-by: v <vendicated@riseup.net>
2024-10-14 17:40:47 +02:00
adryd
ccff1ac3ef Enable speech-dispatcher support for TTS on linux (#874) 2024-10-04 18:51:55 +00:00
Tiagoquix
062b536617 Update text in bug report (#873) 2024-09-24 23:55:04 +02:00
Vendicated
d008f90399 update arrpc to 5aadc307cb 2024-09-20 20:27:26 +02:00
Vendicated
4fdf43ea6a https://tenor.com/view/ah-eto-bleh-27178660 2024-09-20 20:23:57 +02:00
Vendicated
b94379f5bd workaround electron/electron#43367 2024-09-20 20:16:11 +02:00
Vendicated
37db07807a upgrade electron to v32 2024-09-20 20:07:17 +02:00
Aiden
4274647c81 update ESLint to v9 (#859) 2024-09-20 20:02:15 +02:00
v
24fbf35542 https://x.com/ArmCordClient/status/1834617546445672703 2024-09-13 23:16:42 +02:00
lewisakura
c8eccc7e9d ci: add signing certificate password for mac [skip ci] 2024-09-13 22:02:34 +01:00
v
a318f6b407 Update feature-request.yml 2024-08-27 12:59:57 +02:00
donCESAR12345
75354ad8e6 fix(rpm): don't generate build_id links to avoid conflicts (#826)
...with other packages
2024-08-24 00:17:27 +02:00
lewisakura
af9ed58eef ci: update winget releaser [skip ci] 2024-08-10 10:21:31 +01:00
v
e0453418bd Update bug_report.yml 2024-08-05 19:18:22 +02:00
lewisakura
22344512ad feat: notarization (#776) 2024-07-23 15:10:50 +01:00
Noah
9acc6652ff AudioShare: Even more granular selection, Allow device sharing (#750) 2024-07-12 16:14:18 +02:00
vee
61bbd7f6aa Update bug_report.yml 2024-07-11 05:31:07 +02:00
github-actions[bot]
f31f06f5c4 meta: Insert release changes for v1.5.3 (#724) 2024-07-04 20:21:38 +02:00
vee
786fe131b8 Update install instructions 2024-07-04 20:21:14 +02:00
47 changed files with 3258 additions and 1799 deletions

View File

@@ -1,69 +0,0 @@
{
"root": true,
"parser": "@typescript-eslint/parser",
"ignorePatterns": ["dist", "node_modules"],
"plugins": [
"@typescript-eslint",
"license-header",
"simple-import-sort",
"unused-imports",
"path-alias",
"prettier"
],
"settings": {
"import/resolver": {
"alias": {
"map": []
}
}
},
"rules": {
"license-header/header": ["error", "scripts/header.txt"],
"eqeqeq": ["error", "always", { "null": "ignore" }],
"spaced-comment": ["error", "always", { "markers": ["!"] }],
"yoda": "error",
"prefer-destructuring": [
"error",
{
"VariableDeclarator": { "array": false, "object": true },
"AssignmentExpression": { "array": false, "object": false }
}
],
"operator-assignment": ["error", "always"],
"no-useless-computed-key": "error",
"no-unneeded-ternary": ["error", { "defaultAssignment": false }],
"no-invalid-regexp": "error",
"no-constant-condition": ["error", { "checkLoops": false }],
"no-duplicate-imports": "error",
"no-extra-semi": "error",
"dot-notation": "error",
"no-useless-escape": "error",
"no-fallthrough": "error",
"for-direction": "error",
"no-async-promise-executor": "error",
"no-cond-assign": "error",
"no-dupe-else-if": "error",
"no-duplicate-case": "error",
"no-irregular-whitespace": "error",
"no-loss-of-precision": "error",
"no-misleading-character-class": "error",
"no-prototype-builtins": "error",
"no-regex-spaces": "error",
"no-shadow-restricted-names": "error",
"no-unexpected-multiline": "error",
"no-unsafe-optional-chaining": "error",
"no-useless-backreference": "error",
"use-isnan": "error",
"prefer-const": "error",
"prefer-spread": "error",
"simple-import-sort/imports": "error",
"simple-import-sort/exports": "error",
"unused-imports/no-unused-imports": "error",
"path-alias/no-relative": "error",
"prettier/prettier": "error"
}
}

View File

@@ -11,18 +11,35 @@ body:
Make sure a similar issue doesn't already exist by [searching the existing issues](https://github.com/Vencord/Vesktop/issues?q=is%3Aissue) for keywords!
Make sure both Vesktop and Vencord are fully up to date. You can update Vencord by right-clicking the Vesktop tray icon and pressing "Update Vencord"
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, etc: Issue with your gpu. Nothing we can do, update drivers or disable hardware acceleration
- 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
- **SCREENSHARE NOT STARTING** / black screening on Linux: Issue with your desktop environment, specifically its xdg-desktop-portal.
If you're on flatpak, try using native version. If that also doesn't work, you have to fix your systen. Inspect errors and google around.
- 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:
@@ -50,6 +67,15 @@ body:
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:
@@ -85,11 +111,9 @@ body:
id: debug-logs
attributes:
label: Debug Logs
description: Run vesktop from the command line. Include the relevant command line output here
value: |
```
Replace this text with your crash-log. Do not remove the backticks
```
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

View File

@@ -14,6 +14,8 @@ body:
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:

View File

@@ -33,6 +33,6 @@ jobs:
git add meta/dev.vencord.Vesktop.metainfo.xml
git commit -m "Insert release changes for ${{ github.event.release.tag_name }}"
git push origin ci/meta-update
gh pr create -B main -H ci/meta-update -t "Metainfo for ${{ github.event.release.tag_name }}" -b "This PR updates the metainfo for release ${{ github.event.release.tag_name }}. @lewisakura @Vendicated"
gh pr create -B main -H ci/meta-update -t "Metainfo for ${{ github.event.release.tag_name }}" -b "This PR updates the metainfo for release ${{ github.event.release.tag_name }}."
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

View File

@@ -47,7 +47,13 @@ jobs:
- name: Run Electron Builder
if: ${{ matrix.platform == 'mac' }}
run: |
echo "$API_KEY" > apple.p8
pnpm electron-builder --${{ matrix.platform }} --publish always
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
CSC_LINK: ${{ secrets.APPLE_SIGNING_CERT }}
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
CSC_LINK: ${{ secrets.APPLE_SIGNING_CERT }}
CSC_KEY_PASSWORD: ${{ secrets.APPLE_SIGNING_CERT_PASSWORD }}
API_KEY: ${{ secrets.APPLE_API_KEY }}
APPLE_API_KEY: apple.p8
APPLE_API_KEY_ID: ${{ secrets.APPLE_API_KEY_ID }}
APPLE_API_ISSUER: ${{ secrets.APPLE_API_ISSUER }}

View File

@@ -16,7 +16,7 @@ jobs:
runs-on: windows-latest
steps:
- name: Submit package to Winget Community Repo
uses: vedantmgoyal2009/winget-releaser@4614300d5812e5df91cb02ef0edbece623d5dea8
uses: vedantmgoyal2009/winget-releaser@0db4f0a478166abd0fa438c631849f0b8dcfb99f
with:
identifier: Vencord.Vesktop
token: ${{ secrets.WINGET_PAT }}

View File

@@ -21,15 +21,14 @@ Vesktop is a custom Discord desktop app
If you don't know the difference, pick the Installer.
- [Installer](https://vencord.dev/download/vesktop/amd64/windows)
- [Portable](https://vencord.dev/download/vesktop/amd64/windows-portable)
- [Installer](https://vencord.dev/download/vesktop/universal/windows)
- Portable:
- [x64 / amd64](https://vencord.dev/download/vesktop/amd64/windows-portable)
- [Arm® 64](https://vencord.dev/download/vesktop/arm64/windows-portable)
### Mac
If you don't know the difference, pick the Intel build.
- [Intel build (amd64)](https://vencord.dev/download/vesktop/amd64/dmg)
- [Apple Silicon (arm64)](https://vencord.dev/download/vesktop/arm64/dmg)
[Vesktop.dmg](https://vencord.dev/download/vesktop/universal/dmg)
### Linux
@@ -42,7 +41,7 @@ If you don't know the difference, pick amd64.
- [Ubuntu/Debian (.deb)](https://vencord.dev/download/vesktop/amd64/deb)
- [Fedora/RHEL (.rpm)](https://vencord.dev/download/vesktop/amd64/rpm)
- [tarball](https://vencord.dev/download/vesktop/amd64/tar)
- arm64 / aarch64
- Arm® 64 / aarch64
- [AppImage](https://vencord.dev/download/vesktop/arm64/appimage)
- [Ubuntu/Debian (.deb)](https://vencord.dev/download/vesktop/arm64/deb)
- [Fedora/RHEL (.rpm)](https://vencord.dev/download/vesktop/arm64/rpm)
@@ -54,6 +53,7 @@ Below you can find unofficial packages created by the community. They are not of
- Arch Linux: [Vesktop on the Arch user repository](https://aur.archlinux.org/packages?K=vesktop)
- NixOS: https://wiki.nixos.org/wiki/Discord#Vesktop
- Slackware: [Vesktop on the SlackBuilds](https://slackbuilds.org/result/?search=vesktop)
- Windows - Scoop: https://scoop.sh/#/apps?q=Vesktop
## Building from Source

102
eslint.config.mjs Normal file
View File

@@ -0,0 +1,102 @@
/*
* SPDX-License-Identifier: GPL-3.0
* Vesktop, a desktop app aiming to give you a snappier Discord Experience
* Copyright (c) 2023 Vendicated and Vencord contributors
*/
//@ts-check
import stylistic from "@stylistic/eslint-plugin";
import pathAlias from "eslint-plugin-path-alias";
import header from "eslint-plugin-simple-header";
import importSort from "eslint-plugin-simple-import-sort";
import unusedImports from "eslint-plugin-unused-imports";
import tseslint from "typescript-eslint";
import prettier from "eslint-plugin-prettier";
export default tseslint.config(
{ ignores: ["dist"] },
{
files: ["src/**/*.{tsx,ts,mts,mjs,js,jsx}"],
plugins: {
header,
stylistic,
importSort,
unusedImports,
pathAlias,
prettier
},
settings: {
"import/resolver": {
alias: {
map: []
}
}
},
languageOptions: {
parser: tseslint.parser,
parserOptions: {
project: true,
tsconfigRootDir: import.meta.dirname
}
},
rules: {
"header/header": [
"error",
{
files: ["scripts/header.txt"]
}
],
// ESLint Rules
yoda: "error",
eqeqeq: ["error", "always", { null: "ignore" }],
"prefer-destructuring": [
"error",
{
VariableDeclarator: { array: false, object: true },
AssignmentExpression: { array: false, object: false }
}
],
"operator-assignment": ["error", "always"],
"no-useless-computed-key": "error",
"no-unneeded-ternary": ["error", { defaultAssignment: false }],
"no-invalid-regexp": "error",
"no-constant-condition": ["error", { checkLoops: false }],
"no-duplicate-imports": "error",
"dot-notation": "error",
"no-useless-escape": "error",
"no-fallthrough": "error",
"for-direction": "error",
"no-async-promise-executor": "error",
"no-cond-assign": "error",
"no-dupe-else-if": "error",
"no-duplicate-case": "error",
"no-irregular-whitespace": "error",
"no-loss-of-precision": "error",
"no-misleading-character-class": "error",
"no-prototype-builtins": "error",
"no-regex-spaces": "error",
"no-shadow-restricted-names": "error",
"no-unexpected-multiline": "error",
"no-unsafe-optional-chaining": "error",
"no-useless-backreference": "error",
"use-isnan": "error",
"prefer-const": "error",
"prefer-spread": "error",
// Styling Rules
"stylistic/spaced-comment": ["error", "always", { markers: ["!"] }],
"stylistic/no-extra-semi": "error",
// Plugin Rules
"importSort/imports": "error",
"importSort/exports": "error",
"unusedImports/no-unused-imports": "error",
"pathAlias/no-relative": "error",
"prettier/prettier": "error"
}
}
);

View File

@@ -28,6 +28,47 @@
</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>
@@ -208,4 +249,4 @@
<keyword>Privacy</keyword>
<keyword>Mod</keyword>
</keywords>
</component>
</component>

View File

@@ -1,8 +1,8 @@
{
"name": "vesktop",
"version": "1.5.3",
"version": "1.5.5",
"private": true,
"description": "",
"description": "Vesktop is a custom Discord desktop app",
"keywords": [],
"homepage": "https://vencord.dev/",
"license": "GPL-3.0",
@@ -13,7 +13,7 @@
"build:dev": "pnpm build --dev",
"package": "pnpm build && electron-builder",
"package:dir": "pnpm build && electron-builder --dir",
"lint": "eslint . --ext .js,.jsx,.ts,.tsx,.mts,.mjs",
"lint": "eslint",
"lint:fix": "pnpm lint --fix",
"start": "pnpm build && electron .",
"start:dev": "pnpm build:dev && electron .",
@@ -21,40 +21,41 @@
"test": "pnpm lint && pnpm testTypes",
"testTypes": "tsc --noEmit",
"watch": "pnpm build --watch",
"updateMeta": "tsx scripts/utils/updateMeta.mts"
"updateMeta": "tsx scripts/utils/updateMeta.mts",
"updateArrpcDB": "node ./node_modules/arrpc/update_db.js",
"postinstall": "pnpm updateArrpcDB"
},
"dependencies": {
"arrpc": "github:OpenAsar/arrpc#c62ec6a04c8d870530aa6944257fe745f6c59a24",
"electron-updater": "^6.2.1"
"arrpc": "github:OpenAsar/arrpc#2234e9c9111f4c42ebcc3aa6a2215bfd979eef77",
"electron-updater": "^6.3.9"
},
"optionalDependencies": {
"@vencord/venmic": "^6.1.0"
},
"devDependencies": {
"@fal-works/esbuild-plugin-global-externals": "^2.1.2",
"@types/node": "^20.11.26",
"@types/react": "^18.2.0",
"@typescript-eslint/eslint-plugin": "^7.2.0",
"@typescript-eslint/parser": "^7.2.0",
"@stylistic/eslint-plugin": "^3.0.1",
"@types/node": "^22.13.1",
"@types/react": "18.3.12",
"@vencord/types": "^1.8.4",
"dotenv": "^16.4.5",
"electron": "^31.1.0",
"electron-builder": "^24.13.3",
"esbuild": "^0.20.1",
"eslint": "^8.57.0",
"eslint-config-prettier": "^9.1.0",
"dotenv": "^16.4.7",
"electron": "^34.1.0",
"electron-builder": "^25.1.8",
"esbuild": "^0.24.2",
"eslint": "^9.19.0",
"eslint-import-resolver-alias": "^1.1.2",
"eslint-plugin-license-header": "^0.6.0",
"eslint-plugin-path-alias": "^1.0.0",
"eslint-plugin-prettier": "^5.1.3",
"eslint-plugin-simple-import-sort": "^12.0.0",
"eslint-plugin-unused-imports": "^3.1.0",
"prettier": "^3.2.5",
"eslint-plugin-path-alias": "^2.1.0",
"eslint-plugin-prettier": "^5.2.3",
"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",
"source-map-support": "^0.5.21",
"tsx": "^4.7.1",
"type-fest": "^4.12.0",
"typescript": "^5.4.2",
"xml-formatter": "^3.6.2"
"tsx": "^4.19.2",
"type-fest": "^4.33.0",
"typescript": "^5.7.3",
"typescript-eslint": "^8.23.0",
"xml-formatter": "^3.6.4"
},
"packageManager": "pnpm@9.1.0",
"engines": {
@@ -72,6 +73,12 @@
"package.json",
"LICENSE"
],
"protocols": {
"name": "Discord",
"schemes": [
"discord"
]
},
"beforePack": "scripts/build/sandboxFix.js",
"linux": {
"icon": "build/icon.icns",
@@ -112,7 +119,8 @@
"GenericName": "Internet Messenger",
"Type": "Application",
"Categories": "Network;InstantMessaging;Chat;",
"Keywords": "discord;vencord;electron;chat;"
"Keywords": "discord;vencord;electron;chat;",
"MimeType": "x-scheme-handler/discord"
}
},
"mac": {
@@ -124,13 +132,15 @@
]
}
],
"category": "Network",
"category": "public.app-category.social-networking",
"darkModeSupport": true,
"extendInfo": {
"NSMicrophoneUsageDescription": "This app needs access to the microphone",
"NSCameraUsageDescription": "This app needs access to the camera",
"com.apple.security.device.audio-input": true,
"com.apple.security.device.camera": true
}
},
"notarize": true
},
"dmg": {
"background": "build/background.tiff",
@@ -177,11 +187,16 @@
},
"publish": {
"provider": "github"
},
"rpm": {
"fpm": [
"--rpm-rpmbuild-define=_build_id_links none"
]
}
},
"pnpm": {
"patchedDependencies": {
"arrpc@3.4.0": "patches/arrpc@3.4.0.patch"
"arrpc@3.5.0": "patches/arrpc@3.5.0.patch"
}
}
}

View File

@@ -1,14 +1,27 @@
diff --git a/src/process/index.js b/src/process/index.js
index 97ea6514b54dd9c5df588c78f0397d31ab5f882a..c2bdbd6aaa5611bc6ff1d993beeb380b1f5ec575 100644
index 389b0845256a34b4536d6da99edb00d17f13a6b4..f17a0ac687e9110ebfd33cb91fd2f6250d318643 100644
--- a/src/process/index.js
+++ b/src/process/index.js
@@ -5,8 +5,7 @@ import fs from 'node:fs';
@@ -5,8 +5,20 @@ import fs from 'node:fs';
import { dirname, join } from 'path';
import { fileURLToPath } from 'url';
-const __dirname = dirname(fileURLToPath(import.meta.url));
-const DetectableDB = JSON.parse(fs.readFileSync(join(__dirname, 'detectable.json'), 'utf8'));
+const DetectableDB = require('./detectable.json');
+DetectableDB.push(
+ {
+ aliases: ["Obs"],
+ executables: [
+ { is_launcher: false, name: "obs", os: "linux" },
+ { is_launcher: false, name: "obs.exe", os: "win32" },
+ { is_launcher: false, name: "obs.app", os: "darwin" }
+ ],
+ hook: true,
+ id: "STREAMERMODE",
+ name: "OBS"
+ }
+);
import * as Natives from './native/index.js';
const Native = Natives[process.platform];

3621
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@@ -5,9 +5,9 @@
*/
import Server from "arrpc";
import { IpcEvents } from "shared/IpcEvents";
import { IpcCommands } from "shared/IpcEvents";
import { mainWin } from "./mainWindow";
import { sendRendererCommand } from "./ipcCommands";
import { Settings } from "./settings";
let server: any;
@@ -19,16 +19,15 @@ export async function initArRPC() {
try {
server = await new Server();
server.on("activity", (data: any) => mainWin.webContents.send(IpcEvents.ARRPC_ACTIVITY, JSON.stringify(data)));
server.on("invite", (invite: string, callback: (valid: boolean) => void) => {
server.on("activity", (data: any) => sendRendererCommand(IpcCommands.RPC_ACTIVITY, JSON.stringify(data)));
server.on("invite", async (invite: string, callback: (valid: boolean) => void) => {
invite = String(invite);
if (!inviteCodeRegex.test(invite)) return callback(false);
mainWin.webContents
// Safety: Result of JSON.stringify should always be safe to equal
// Also, just to be super super safe, invite is regex validated above
.executeJavaScript(`Vesktop.openInviteModal(${JSON.stringify(invite)})`)
.then(callback);
await sendRendererCommand(IpcCommands.RPC_INVITE, invite).then(callback);
});
server.on("link", async (data: any, deepCallback: (valid: boolean) => void) => {
await sendRendererCommand(IpcCommands.RPC_DEEP_LINK, data).then(deepCallback);
});
} catch (e) {
console.error("Failed to start arRPC server", e);

View File

@@ -61,11 +61,11 @@ export const DEFAULT_HEIGHT = 720;
export const DISCORD_HOSTNAMES = ["discord.com", "canary.discord.com", "ptb.discord.com"];
const VersionString = `AppleWebKit/537.36 (KHTML, like Gecko) Chrome/${process.versions.chrome.split(".")[0]}.0.0.0 Safari/537.36`;
const BrowserUserAgents = {
darwin: "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36",
linux: "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36",
windows:
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36"
darwin: `Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) ${VersionString}`,
linux: `Mozilla/5.0 (X11; Linux x86_64) ${VersionString}`,
windows: `Mozilla/5.0 (Windows NT 10.0; Win64; x64) ${VersionString}`
};
export const BrowserUserAgent = BrowserUserAgents[process.platform] || BrowserUserAgents.windows;

View File

@@ -28,6 +28,7 @@ interface Data {
export function createFirstLaunchTour() {
const win = new BrowserWindow({
...SplashProps,
transparent: false,
frame: true,
autoHideMenuBar: true,
height: 470,

View File

@@ -23,10 +23,14 @@ if (IS_DEV) {
autoUpdater.checkForUpdatesAndNotify();
}
console.log("Vesktop v" + app.getVersion());
// Make the Vencord files use our DATA_DIR
process.env.VENCORD_USER_DATA_DIR = DATA_DIR;
function init() {
app.setAsDefaultProtocolClient("discord");
const { disableSmoothScroll, hardwareAcceleration } = Settings.store;
const enabledFeatures = app.commandLine.getSwitchValue("enable-features").split(",");
@@ -35,7 +39,12 @@ function init() {
if (hardwareAcceleration === false) {
app.disableHardwareAcceleration();
} else {
enabledFeatures.push("VaapiVideoDecodeLinuxGL", "VaapiVideoEncoder", "VaapiVideoDecoder");
enabledFeatures.push(
"AcceleratedVideoDecodeLinuxGL",
"AcceleratedVideoEncoder",
"AcceleratedVideoDecoder",
"AcceleratedVideoDecodeLinuxZeroCopyGL"
);
}
if (disableSmoothScroll) {
@@ -60,6 +69,9 @@ function init() {
// WidgetLayering (Vencord Added): Fix DevTools context menus https://github.com/electron/electron/issues/38790
disabledFeatures.push("WinRetrieveSuggestionsOnlyOnDemand", "HardwareMediaKeyHandling", "MediaSessionService");
// Support TTS on Linux using speech-dispatcher
app.commandLine.appendSwitch("enable-speech-dispatcher");
app.commandLine.appendSwitch("enable-features", [...new Set(enabledFeatures)].filter(Boolean).join(","));
app.commandLine.appendSwitch("disable-features", [...new Set(disabledFeatures)].filter(Boolean).join(","));
@@ -109,6 +121,12 @@ async function bootstrap() {
}
}
// MacOS only event
export let darwinURL: string | undefined;
app.on("open-url", (_, url) => {
darwinURL = url;
});
app.on("window-all-closed", () => {
if (process.platform !== "darwin") app.quit();
});

View File

@@ -135,6 +135,17 @@ handle(IpcEvents.CLIPBOARD_COPY_IMAGE, async (_, buf: ArrayBuffer, src: string)
});
});
function openDebugPage(page: string) {
const win = new BrowserWindow({
autoHideMenuBar: true
});
win.loadURL(page);
}
handle(IpcEvents.DEBUG_LAUNCH_GPU, () => openDebugPage("chrome://gpu"));
handle(IpcEvents.DEBUG_LAUNCH_WEBRTC_INTERNALS, () => openDebugPage("chrome://webrtc-internals"));
function readCss() {
return readFile(VENCORD_QUICKCSS_FILE, "utf-8").catch(() => "");
}

56
src/main/ipcCommands.ts Normal file
View File

@@ -0,0 +1,56 @@
/*
* 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 { randomUUID } from "crypto";
import { ipcMain } from "electron";
import { IpcEvents } from "shared/IpcEvents";
import { mainWin } from "./mainWindow";
const resolvers = new Map<string, Record<"resolve" | "reject", (data: any) => void>>();
export interface IpcMessage {
nonce: string;
message: string;
data?: any;
}
export interface IpcResponse {
nonce: string;
ok: boolean;
data?: any;
}
/**
* Sends a message to the renderer process and waits for a response.
* `data` must be serializable as it will be sent over IPC.
*
* You must add a handler for the message in the renderer process.
*/
export function sendRendererCommand<T = any>(message: string, data?: any) {
const nonce = randomUUID();
const promise = new Promise<T>((resolve, reject) => {
resolvers.set(nonce, { resolve, reject });
});
mainWin.webContents.send(IpcEvents.IPC_COMMAND, { nonce, message, data });
return promise;
}
ipcMain.on(IpcEvents.IPC_COMMAND, (_event, { nonce, ok, data }: IpcResponse) => {
const resolver = resolvers.get(nonce);
if (!resolver) throw new Error(`Unknown message: ${nonce}`);
if (ok) {
resolver.resolve(data);
} else {
resolver.reject(data);
}
resolvers.delete(nonce);
});

View File

@@ -18,7 +18,7 @@ import {
} from "electron";
import { rm } from "fs/promises";
import { join } from "path";
import { IpcEvents } from "shared/IpcEvents";
import { IpcCommands, IpcEvents } from "shared/IpcEvents";
import { isTruthy } from "shared/utils/guards";
import { once } from "shared/utils/once";
import type { SettingsStore } from "shared/utils/SettingsStore";
@@ -36,6 +36,8 @@ import {
MIN_WIDTH,
VENCORD_FILES_DIR
} from "./constants";
import { darwinURL } from "./index";
import { sendRendererCommand } from "./ipcCommands";
import { Settings, State, VencordSettings } from "./settings";
import { createSplashWindow } from "./splash";
import { makeLinksOpenExternally } from "./utils/makeLinksOpenExternally";
@@ -198,9 +200,7 @@ function initMenuBar(win: BrowserWindow) {
label: "Settings",
accelerator: "CmdOrCtrl+,",
async click() {
mainWin.webContents.executeJavaScript(
"Vencord.Webpack.Common.SettingsRouter.open('My Account')"
);
sendRendererCommand(IpcCommands.NAVIGATE_SETTINGS);
}
},
{
@@ -271,7 +271,7 @@ function getWindowBoundsOptions(): BrowserWindowConstructorOptions {
height: height ?? DEFAULT_HEIGHT
} as BrowserWindowConstructorOptions;
const storedDisplay = screen.getAllDisplays().find(display => display.id === State.store.displayid);
const storedDisplay = screen.getAllDisplays().find(display => display.id === State.store.displayId);
if (x != null && y != null && storedDisplay) {
options.x = x;
@@ -299,7 +299,7 @@ function getDarwinOptions(): BrowserWindowConstructorOptions {
options.vibrancy = "sidebar";
options.backgroundColor = "#ffffff00";
} else {
if (splashTheming) {
if (splashTheming !== false) {
options.backgroundColor = splashBackground;
} else {
options.backgroundColor = nativeTheme.shouldUseDarkColors ? "#313338" : "#ffffff";
@@ -321,7 +321,7 @@ function initWindowBoundsListeners(win: BrowserWindow) {
const saveBounds = () => {
State.store.windowBounds = win.getBounds();
State.store.displayid = screen.getDisplayMatching(State.store.windowBounds).id;
State.store.displayId = screen.getDisplayMatching(State.store.windowBounds).id;
};
win.on("resize", saveBounds);
@@ -333,6 +333,7 @@ function initSettingsListeners(win: BrowserWindow) {
if (enable) initTray(win);
else tray?.destroy();
});
addSettingsListener("disableMinSize", disable => {
if (disable) {
// 0 no work
@@ -366,7 +367,7 @@ function initSettingsListeners(win: BrowserWindow) {
}
async function initSpellCheckLanguages(win: BrowserWindow, languages?: string[]) {
languages ??= await win.webContents.executeJavaScript("[...new Set(navigator.languages)]").catch(() => []);
languages ??= await sendRendererCommand(IpcCommands.GET_LANGUAGES);
if (!languages) return;
const ses = session.defaultSession;
@@ -384,19 +385,38 @@ function initSpellCheck(win: BrowserWindow) {
initSpellCheckLanguages(win, Settings.store.spellCheckLanguages);
}
function initStaticTitle(win: BrowserWindow) {
const listener = (e: { preventDefault: Function }) => e.preventDefault();
if (Settings.store.staticTitle) win.on("page-title-updated", listener);
addSettingsListener("staticTitle", enabled => {
if (enabled) {
win.setTitle("Vesktop");
win.on("page-title-updated", listener);
} else {
win.off("page-title-updated", listener);
}
});
}
function createMainWindow() {
// Clear up previous settings listeners
removeSettingsListeners();
removeVencordSettingsListeners();
const { staticTitle, transparencyOption, enableMenu, customTitleBar } = Settings.store;
const { staticTitle, transparencyOption, enableMenu, customTitleBar, splashTheming, splashBackground } =
Settings.store;
const { frameless, transparent } = VencordSettings.store;
const noFrame = frameless === true || customTitleBar === true;
const backgroundColor =
splashTheming !== false ? splashBackground : nativeTheme.shouldUseDarkColors ? "#313338" : "#ffffff";
const win = (mainWin = new BrowserWindow({
show: false,
show: Settings.store.enableSplashScreen === false,
backgroundColor,
webPreferences: {
nodeIntegration: false,
sandbox: false,
@@ -444,44 +464,51 @@ function createMainWindow() {
return false;
});
if (Settings.store.staticTitle) win.on("page-title-updated", e => e.preventDefault());
initWindowBoundsListeners(win);
if (!isDeckGameMode && (Settings.store.tray ?? true) && process.platform !== "darwin") initTray(win);
initMenuBar(win);
makeLinksOpenExternally(win);
initSettingsListeners(win);
initSpellCheck(win);
initStaticTitle(win);
win.webContents.setUserAgent(BrowserUserAgent);
const subdomain =
Settings.store.discordBranch === "canary" || Settings.store.discordBranch === "ptb"
? `${Settings.store.discordBranch}.`
: "";
win.loadURL(`https://${subdomain}discord.com/app`);
// if the open-url event is fired (in index.ts) while starting up, darwinURL will be set. If not fall back to checking the process args (which Windows and Linux use for URI calling.)
loadUrl(darwinURL || process.argv.find(arg => arg.startsWith("discord://")));
return win;
}
const runVencordMain = once(() => require(join(VENCORD_FILES_DIR, "vencordDesktopMain.js")));
export function loadUrl(uri: string | undefined) {
const branch = Settings.store.discordBranch;
const subdomain = branch === "canary" || branch === "ptb" ? `${branch}.` : "";
mainWin.loadURL(`https://${subdomain}discord.com/${uri ? new URL(uri).pathname.slice(1) || "app" : "app"}`);
}
export async function createWindows() {
const startMinimized = process.argv.includes("--start-minimized");
const splash = createSplashWindow(startMinimized);
// SteamOS letterboxes and scales it terribly, so just full screen it
if (isDeckGameMode) splash.setFullScreen(true);
let splash: BrowserWindow | undefined;
if (Settings.store.enableSplashScreen !== false) {
splash = createSplashWindow(startMinimized);
// SteamOS letterboxes and scales it terribly, so just full screen it
if (isDeckGameMode) splash.setFullScreen(true);
}
await ensureVencordFiles();
runVencordMain();
mainWin = createMainWindow();
mainWin.webContents.on("did-finish-load", () => {
splash.destroy();
splash?.destroy();
if (!startMinimized) {
mainWin!.show();
if (splash) mainWin!.show();
if (State.store.maximized && !isDeckGameMode) mainWin!.maximize();
}
@@ -499,5 +526,13 @@ export async function createWindows() {
});
});
mainWin.webContents.on("did-navigate", (_, url: string, responseCode: number) => {
// check url to ensure app doesn't loop
if (responseCode >= 300 && new URL(url).pathname !== `/app`) {
loadUrl(undefined);
console.warn(`'did-navigate': Caught bad page response: ${responseCode}, redirecting to main app`);
}
});
initArRPC();
}

View File

@@ -49,8 +49,8 @@ export function registerScreenShareHandler() {
if (isWayland) {
const video = data[0];
if (video) {
const stream = await request.frame
.executeJavaScript(
const stream = await request
.frame!.executeJavaScript(
`Vesktop.Components.ScreenShare.openScreenSharePicker(${JSON.stringify([video])},true)`
)
.catch(() => null);
@@ -61,8 +61,8 @@ export function registerScreenShareHandler() {
return;
}
const choice = await request.frame
.executeJavaScript(`Vesktop.Components.ScreenShare.openScreenSharePicker(${JSON.stringify(data)})`)
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);

View File

@@ -22,7 +22,7 @@ export function createSplashWindow(startMinimized = false) {
const { splashBackground, splashColor, splashTheming } = Settings.store;
if (splashTheming) {
if (splashTheming !== false) {
if (splashColor) {
const semiTransparentSplashColor = splashColor.replace("rgb(", "rgba(").replace(")", ", 0.2)");

View File

@@ -8,7 +8,9 @@ import { ipcMain, IpcMainEvent, IpcMainInvokeEvent, WebFrameMain } from "electro
import { DISCORD_HOSTNAMES } from "main/constants";
import { IpcEvents } from "shared/IpcEvents";
export function validateSender(frame: WebFrameMain) {
export function validateSender(frame: WebFrameMain | null) {
if (!frame) throw new Error("ipc: No sender frame");
const { hostname, protocol } = new URL(frame.url);
if (protocol === "file:") return;

View File

@@ -72,7 +72,7 @@ ipcMain.handle(IpcEvents.VIRT_MIC_LIST, () => {
const { granularSelect } = Settings.store.audio ?? {};
const targets = obtainVenmic()
?.list(granularSelect ? ["application.process.id"] : undefined)
?.list(granularSelect ? ["node.name"] : undefined)
.filter(s => s["application.process.id"] !== audioPid);
return targets ? { ok: true, targets, hasPipewirePulse } : { ok: false, isGlibCxxOutdated };

View File

@@ -6,6 +6,7 @@
import { Node } from "@vencord/venmic";
import { ipcRenderer } from "electron";
import { IpcMessage, IpcResponse } from "main/ipcCommands";
import type { Settings } from "shared/settings";
import { IpcEvents } from "../shared/IpcEvents";
@@ -70,13 +71,18 @@ export const VesktopNative = {
startSystem: (exclude: Node[]) => invoke<void>(IpcEvents.VIRT_MIC_START_SYSTEM, exclude),
stop: () => invoke<void>(IpcEvents.VIRT_MIC_STOP)
},
arrpc: {
onActivity(cb: (data: string) => void) {
ipcRenderer.on(IpcEvents.ARRPC_ACTIVITY, (_, data: string) => cb(data));
}
},
clipboard: {
copyImage: (imageBuffer: Uint8Array, imageSrc: string) =>
invoke<void>(IpcEvents.CLIPBOARD_COPY_IMAGE, imageBuffer, imageSrc)
},
debug: {
launchGpu: () => invoke<void>(IpcEvents.DEBUG_LAUNCH_GPU),
launchWebrtcInternals: () => invoke<void>(IpcEvents.DEBUG_LAUNCH_WEBRTC_INTERNALS)
},
commands: {
onCommand(cb: (message: IpcMessage) => void) {
ipcRenderer.on(IpcEvents.IPC_COMMAND, (_, message) => cb(message));
},
respond: (response: IpcResponse) => ipcRenderer.send(IpcEvents.IPC_COMMAND, response)
}
};

View File

@@ -7,6 +7,7 @@
import { filters, waitFor } from "@vencord/types/webpack";
import { RelationshipStore } from "@vencord/types/webpack/common";
import { VesktopLogger } from "./logger";
import { Settings } from "./settings";
let GuildReadStateStore: any;
@@ -26,7 +27,7 @@ export function setBadge() {
VesktopNative.app.setBadgeCount(totalCount);
} catch (e) {
console.error(e);
VesktopLogger.error("Failed to update badge count", e);
}
}

68
src/renderer/arrpc.ts Normal file
View File

@@ -0,0 +1,68 @@
/*
* 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 { Logger } from "@vencord/types/utils";
import { findLazy, findStoreLazy, onceReady } from "@vencord/types/webpack";
import { FluxDispatcher, InviteActions } from "@vencord/types/webpack/common";
import { IpcCommands } from "shared/IpcEvents";
import { onIpcCommand } from "./ipcCommands";
import { Settings } from "./settings";
const logger = new Logger("VesktopRPC", "#5865f2");
const StreamerModeStore = findStoreLazy("StreamerModeStore");
const arRPC = Vencord.Plugins.plugins["WebRichPresence (arRPC)"] as any as {
handleEvent(e: MessageEvent): void;
};
onIpcCommand(IpcCommands.RPC_ACTIVITY, async jsonData => {
if (!Settings.store.arRPC) return;
await onceReady;
const data = JSON.parse(jsonData);
if (data.socketId === "STREAMERMODE" && StreamerModeStore.autoToggle) {
FluxDispatcher.dispatch({
type: "STREAMER_MODE_UPDATE",
key: "enabled",
value: data.activity?.application_id === "STREAMERMODE"
});
return;
}
arRPC.handleEvent(new MessageEvent("message", { data: jsonData }));
});
onIpcCommand(IpcCommands.RPC_INVITE, async code => {
const { invite } = await InviteActions.resolveInvite(code, "Desktop Modal");
if (!invite) return false;
VesktopNative.win.focus();
FluxDispatcher.dispatch({
type: "INVITE_MODAL_OPEN",
invite,
code,
context: "APP"
});
return true;
});
const { DEEP_LINK } = findLazy(m => m.DEEP_LINK?.handler);
onIpcCommand(IpcCommands.RPC_DEEP_LINK, async data => {
logger.debug("Opening deep link:", data);
try {
DEEP_LINK.handler({ args: data });
return true;
} catch (err) {
logger.error("Failed to open deep link:", err);
return false;
}
});

View File

@@ -22,12 +22,14 @@ import {
import { Node } from "@vencord/venmic";
import type { Dispatch, SetStateAction } from "react";
import { addPatch } from "renderer/patches/shared";
import { useSettings } from "renderer/settings";
import { isLinux, isWindows } from "renderer/utils";
import { State, useSettings, useVesktopState } from "renderer/settings";
import { classNameFactory, isLinux, isWindows } from "renderer/utils";
const StreamResolutions = ["480", "720", "1080", "1440"] as const;
const StreamResolutions = ["480", "720", "1080", "1440", "2160"] as const;
const StreamFps = ["15", "30", "60"] as const;
const cl = classNameFactory("vcd-screen-picker-");
const MediaEngineStore = findStoreLazy("MediaEngineStore");
export type StreamResolution = (typeof StreamResolutions)[number];
@@ -44,8 +46,6 @@ interface AudioItem {
}
interface StreamSettings {
resolution: StreamResolution;
fps: StreamFps;
audio: boolean;
contentHint?: string;
includeSources?: AudioSources;
@@ -77,10 +77,11 @@ addPatch({
}
],
patchStreamQuality(opts: any) {
if (!currentSettings) return;
const { screenshareQuality } = State.store;
if (!screenshareQuality) return;
const framerate = Number(currentSettings.fps);
const height = Number(currentSettings.resolution);
const framerate = Number(screenshareQuality.frameRate);
const height = Number(screenshareQuality.resolution);
const width = Math.round(height * (16 / 9));
Object.assign(opts, {
@@ -161,13 +162,21 @@ export function openScreenSharePicker(screens: Source[], skipPicker: boolean) {
function ScreenPicker({ screens, chooseScreen }: { screens: Source[]; chooseScreen: (id: string) => void }) {
return (
<div className="vcd-screen-picker-grid">
<div className={cl("screen-grid")}>
{screens.map(({ id, name, url }) => (
<label key={id}>
<input type="radio" name="screen" value={id} onChange={() => chooseScreen(id)} />
<label key={id} className={cl("screen-label")}>
<input
type="radio"
className={cl("screen-radio")}
name="screen"
value={id}
onChange={() => chooseScreen(id)}
/>
<img src={url} alt="" />
<Text variant="text-sm/normal">{name}</Text>
<Text className={cl("screen-name")} variant="text-sm/normal">
{name}
</Text>
</label>
))}
</div>
@@ -187,11 +196,13 @@ function AudioSettingsModal({
return (
<Modals.ModalRoot {...modalProps} size={ModalSize.MEDIUM}>
<Modals.ModalHeader className="vcd-screen-picker-header">
<Forms.FormTitle tag="h2">Venmic Settings</Forms.FormTitle>
<Modals.ModalHeader className={cl("header")}>
<Forms.FormTitle tag="h2" className={cl("header-title")}>
Venmic Settings
</Forms.FormTitle>
<Modals.ModalCloseButton onClick={close} />
</Modals.ModalHeader>
<Modals.ModalContent className="vcd-screen-picker-modal">
<Modals.ModalContent className={cl("modal")}>
<Switch
hideBorder
onChange={v => (Settings.audio = { ...Settings.audio, workaround: v })}
@@ -254,7 +265,13 @@ function AudioSettingsModal({
</Switch>
<Switch
hideBorder
onChange={v => (Settings.audio = { ...Settings.audio, ignoreDevices: v })}
onChange={v =>
(Settings.audio = {
...Settings.audio,
ignoreDevices: v,
deviceSelect: v ? false : Settings.audio?.deviceSelect
})
}
value={Settings.audio?.ignoreDevices ?? true}
note={<>Exclude device nodes, such as nodes belonging to microphones or speakers.</>}
>
@@ -271,8 +288,25 @@ function AudioSettingsModal({
>
Granular Selection
</Switch>
<Switch
hideBorder
onChange={value => {
Settings.audio = { ...Settings.audio, deviceSelect: value };
setAudioSources("None");
}}
value={Settings.audio?.deviceSelect ?? false}
disabled={Settings.audio?.ignoreDevices}
note={
<>
Allow to select devices such as microphones. Requires <b>Ignore Devices</b> to be turned
off.
</>
}
>
Device Selection
</Switch>
</Modals.ModalContent>
<Modals.ModalFooter className="vcd-screen-picker-footer">
<Modals.ModalFooter className={cl("footer")}>
<Button color={Button.Colors.TRANSPARENT} onClick={close}>
Back
</Button>
@@ -281,7 +315,35 @@ function AudioSettingsModal({
);
}
function StreamSettings({
function OptionRadio<Settings extends object, Key extends keyof Settings>(props: {
options: Array<string> | ReadonlyArray<string>;
labels?: Array<string>;
settings: Settings;
settingsKey: Key;
onChange: (option: string) => void;
}) {
const { options, settings, settingsKey, labels, onChange } = props;
return (
<div className={cl("option-radios")}>
{(options as string[]).map((option, idx) => (
<label className={cl("option-radio")} data-checked={settings[settingsKey] === option} key={option}>
<Text variant="text-sm/bold">{labels?.[idx] ?? option}</Text>
<input
className={cl("option-input")}
type="radio"
name="fps"
value={option}
checked={settings[settingsKey] === option}
onChange={() => onChange(option)}
/>
</label>
))}
</div>
);
}
function StreamSettingsUi({
source,
settings,
setSettings,
@@ -293,6 +355,7 @@ function StreamSettings({
skipPicker: boolean;
}) {
const Settings = useSettings();
const qualitySettings = State.store.screenshareQuality!;
const [thumb] = useAwaiter(
() => (skipPicker ? Promise.resolve(source.url) : VesktopNative.capturer.getLargeThumbnail(source.id)),
@@ -317,88 +380,47 @@ function StreamSettings({
return (
<div>
<Forms.FormTitle>What you're streaming</Forms.FormTitle>
<Card className="vcd-screen-picker-card vcd-screen-picker-preview">
<img
src={thumb}
alt=""
className={isLinux ? "vcd-screen-picker-preview-img-linux" : "vcd-screen-picker-preview-img"}
/>
<Card className={cl("card", "preview")}>
<img src={thumb} alt="" className={cl(isLinux ? "preview-img-linux" : "preview-img")} />
<Text variant="text-sm/normal">{source.name}</Text>
</Card>
<Forms.FormTitle>Stream Settings</Forms.FormTitle>
<Card className="vcd-screen-picker-card">
<div className="vcd-screen-picker-quality">
<section>
<Card className={cl("card")}>
<div className={cl("quality")}>
<section className={cl("quality-section")}>
<Forms.FormTitle>Resolution</Forms.FormTitle>
<div className="vcd-screen-picker-radios">
{StreamResolutions.map(res => (
<label className="vcd-screen-picker-radio" data-checked={settings.resolution === res}>
<Text variant="text-sm/bold">{res}</Text>
<input
type="radio"
name="resolution"
value={res}
checked={settings.resolution === res}
onChange={() => setSettings(s => ({ ...s, resolution: res }))}
/>
</label>
))}
</div>
<OptionRadio
options={StreamResolutions}
settings={qualitySettings}
settingsKey="resolution"
onChange={value => (qualitySettings.resolution = value)}
/>
</section>
<section>
<section className={cl("quality-section")}>
<Forms.FormTitle>Frame Rate</Forms.FormTitle>
<div className="vcd-screen-picker-radios">
{StreamFps.map(fps => (
<label className="vcd-screen-picker-radio" data-checked={settings.fps === fps}>
<Text variant="text-sm/bold">{fps}</Text>
<input
type="radio"
name="fps"
value={fps}
checked={settings.fps === fps}
onChange={() => setSettings(s => ({ ...s, fps }))}
/>
</label>
))}
</div>
<OptionRadio
options={StreamFps}
settings={qualitySettings}
settingsKey="frameRate"
onChange={value => (qualitySettings.frameRate = value)}
/>
</section>
</div>
<div className="vcd-screen-picker-quality">
<section>
<div className={cl("quality")}>
<section className={cl("quality-section")}>
<Forms.FormTitle>Content Type</Forms.FormTitle>
<div>
<div className="vcd-screen-picker-radios">
<label
className="vcd-screen-picker-radio"
data-checked={settings.contentHint === "motion"}
>
<Text variant="text-sm/bold">Prefer Smoothness</Text>
<input
type="radio"
name="contenthint"
value="motion"
checked={settings.contentHint === "motion"}
onChange={() => setSettings(s => ({ ...s, contentHint: "motion" }))}
/>
</label>
<label
className="vcd-screen-picker-radio"
data-checked={settings.contentHint === "detail"}
>
<Text variant="text-sm/bold">Prefer Clarity</Text>
<input
type="radio"
name="contenthint"
value="detail"
checked={settings.contentHint === "detail"}
onChange={() => setSettings(s => ({ ...s, contentHint: "detail" }))}
/>
</label>
</div>
<div className="vcd-screen-picker-hint-description">
<OptionRadio
options={["motion", "detail"]}
labels={["Prefer Smoothness", "Prefer Clarity"]}
settings={settings}
settingsKey="contentHint"
onChange={option => setSettings(s => ({ ...s, contentHint: option }))}
/>
<div className={cl("hint-description")}>
<p>
Choosing "Prefer Clarity" will result in a significantly lower framerate in exchange
for a much sharper and clearer image.
@@ -410,7 +432,7 @@ function StreamSettings({
value={settings.audio}
onChange={checked => setSettings(s => ({ ...s, audio: checked }))}
hideBorder
className="vcd-screen-picker-audio"
className={cl("audio")}
>
Stream With Audio
</Switch>
@@ -423,6 +445,7 @@ function StreamSettings({
openSettings={openSettings}
includeSources={settings.includeSources}
excludeSources={settings.excludeSources}
deviceSelect={Settings.audio?.deviceSelect}
granularSelect={Settings.audio?.granularSelect}
setIncludeSources={sources => setSettings(s => ({ ...s, includeSources: sources }))}
setExcludeSources={sources => setSettings(s => ({ ...s, excludeSources: sources }))}
@@ -441,13 +464,23 @@ function hasMatchingProps(value: Node, other: Node) {
return Object.keys(value).every(key => value[key] === other[key]);
}
function mapToAudioItem(node: AudioSource, granularSelect?: boolean): AudioItem[] {
function mapToAudioItem(node: AudioSource, granularSelect?: boolean, deviceSelect?: boolean): AudioItem[] {
if (isSpecialSource(node)) {
return [{ name: node, value: node }];
}
const rtn: AudioItem[] = [];
const mediaClass = node["media.class"];
if (mediaClass?.includes("Video") || mediaClass?.includes("Midi")) {
return rtn;
}
if (!deviceSelect && node["device.id"]) {
return rtn;
}
const name = node["application.name"];
if (name) {
@@ -458,9 +491,15 @@ function mapToAudioItem(node: AudioSource, granularSelect?: boolean): AudioItem[
return rtn;
}
const binary = node["application.process.binary"];
const rawName = node["node.name"];
if (!name) {
rtn.push({ name: rawName, value: { "node.name": rawName } });
}
const binary = node["application.process.binary"];
if (!name && binary) {
rtn.push({ name: binary, value: { "application.process.binary": binary } });
}
@@ -469,10 +508,12 @@ function mapToAudioItem(node: AudioSource, granularSelect?: boolean): AudioItem[
const first = rtn[0];
const firstValues = first.value as Node;
rtn.push({
name: `${first.name} (${pid})`,
value: { ...firstValues, "application.process.id": pid }
});
if (pid) {
rtn.push({
name: `${first.name} (${pid})`,
value: { ...firstValues, "application.process.id": pid }
});
}
const mediaName = node["media.name"];
@@ -483,17 +524,13 @@ function mapToAudioItem(node: AudioSource, granularSelect?: boolean): AudioItem[
});
}
const mediaClass = node["media.class"];
if (!mediaClass) {
return rtn;
if (mediaClass) {
rtn.push({
name: `${first.name} [${mediaClass}]`,
value: { ...firstValues, "media.class": mediaClass }
});
}
rtn.push({
name: `${first.name} [${mediaClass}]`,
value: { ...firstValues, "media.class": mediaClass }
});
return rtn;
}
@@ -535,6 +572,7 @@ function updateItems(setSources: (s: AudioSources) => void, sources?: AudioSourc
function AudioSourcePickerLinux({
includeSources,
excludeSources,
deviceSelect,
granularSelect,
openSettings,
setIncludeSources,
@@ -542,6 +580,7 @@ function AudioSourcePickerLinux({
}: {
includeSources?: AudioSources;
excludeSources?: AudioSources;
deviceSelect?: boolean;
granularSelect?: boolean;
openSettings: () => void;
setIncludeSources: (s: AudioSources) => void;
@@ -592,14 +631,14 @@ function AudioSourcePickerLinux({
const allSources = sources.ok
? [...specialSources, ...sources.targets]
.map(target => mapToAudioItem(target, granularSelect))
.map(target => mapToAudioItem(target, granularSelect, deviceSelect))
.flat()
.filter(uniqueName)
: [];
return (
<>
<div className={includeSources === "Entire System" ? "vcd-screen-picker-quality" : undefined}>
<div className={cl({ quality: includeSources === "Entire System" })}>
<section>
<Forms.FormTitle>{loading ? "Loading Sources..." : "Audio Sources"}</Forms.FormTitle>
<Select
@@ -635,11 +674,7 @@ function AudioSourcePickerLinux({
</section>
)}
</div>
<Button
color={Button.Colors.TRANSPARENT}
onClick={openSettings}
className="vcd-screen-picker-settings-button"
>
<Button color={Button.Colors.TRANSPARENT} onClick={openSettings} className={cl("settings-button")}>
Open Audio Settings
</Button>
</>
@@ -661,24 +696,26 @@ function ModalComponent({
}) {
const [selected, setSelected] = useState<string | undefined>(skipPicker ? screens[0].id : void 0);
const [settings, setSettings] = useState<StreamSettings>({
resolution: "720",
fps: "30",
contentHint: "motion",
audio: true,
includeSources: "None"
});
const qualitySettings = (useVesktopState().screenshareQuality ??= {
resolution: "720",
frameRate: "30"
});
return (
<Modals.ModalRoot {...modalProps} size={ModalSize.MEDIUM}>
<Modals.ModalHeader className="vcd-screen-picker-header">
<Modals.ModalHeader className={cl("header")}>
<Forms.FormTitle tag="h2">ScreenShare</Forms.FormTitle>
<Modals.ModalCloseButton onClick={close} />
</Modals.ModalHeader>
<Modals.ModalContent className="vcd-screen-picker-modal">
<Modals.ModalContent className={cl("modal")}>
{!selected ? (
<ScreenPicker screens={screens} chooseScreen={setSelected} />
) : (
<StreamSettings
<StreamSettingsUi
source={screens.find(s => s.id === selected)!}
settings={settings}
setSettings={setSettings}
@@ -686,14 +723,14 @@ function ModalComponent({
/>
)}
</Modals.ModalContent>
<Modals.ModalFooter className="vcd-screen-picker-footer">
<Modals.ModalFooter className={cl("footer")}>
<Button
disabled={!selected}
onClick={() => {
currentSettings = settings;
try {
const frameRate = Number(settings.fps);
const height = Number(settings.resolution);
const frameRate = Number(qualitySettings.frameRate);
const height = Number(qualitySettings.resolution);
const width = Math.round(height * (16 / 9));
const conn = [...MediaEngineStore.getMediaEngine().connections].find(

View File

@@ -2,7 +2,7 @@
padding: 1em;
}
.vcd-screen-picker-header h1 {
.vcd-screen-picker-header-title {
margin: 0;
}
@@ -15,34 +15,32 @@
flex-grow: 1;
}
.vcd-screen-picker-grid {
/* Screen Grid */
.vcd-screen-picker-screen-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 2em 1em;
}
.vcd-screen-picker-grid input {
.vcd-screen-picker-screen-radio {
appearance: none;
cursor: pointer;
}
.vcd-screen-picker-selected img {
border: 2px solid var(--brand-500);
border-radius: 3px;
}
.vcd-screen-picker-grid label {
.vcd-screen-picker-screen-label {
overflow: hidden;
padding: 4px 0px;
padding: 8px;
cursor: pointer;
display: grid;
justify-items: center;
}
.vcd-screen-picker-grid label:hover {
.vcd-screen-picker-screen-label:hover {
outline: 2px solid var(--brand-500);
}
.vcd-screen-picker-grid div {
.vcd-screen-picker-screen-name {
white-space: nowrap;
text-overflow: ellipsis;
overflow: hidden;
@@ -74,38 +72,48 @@
margin-bottom: 1em;
}
.vcd-screen-picker-radio input {
display: none;
/* Option Radios */
.vcd-screen-picker-option-radios {
display: flex;
width: 100%;
border-radius: 3px;
}
.vcd-screen-picker-radio {
.vcd-screen-picker-option-radio {
flex: 1 1 auto;
text-align: center;
background-color: var(--background-secondary);
border: 1px solid var(--primary-800);
padding: 0.3em;
cursor: pointer;
}
.vcd-screen-picker-radio h2 {
margin: 0;
.vcd-screen-picker-option-radio:first-child {
border-radius: 3px 0 0 3px;
}
.vcd-screen-picker-radio[data-checked="true"] {
.vcd-screen-picker-option-radio:last-child {
border-radius: 0 3px 3px 0;
}
.vcd-screen-picker-option-input {
display: none;
}
.vcd-screen-picker-option-radio[data-checked="true"] {
background-color: var(--brand-500);
border-color: var(--brand-500);
}
.vcd-screen-picker-radio[data-checked="true"] h2 {
color: var(--interactive-active);
}
.vcd-screen-picker-quality {
display: flex;
gap: 1em;
margin-bottom: 0.5em;
}
.vcd-screen-picker-quality section {
.vcd-screen-picker-quality-section {
flex: 1 1 auto;
}
@@ -114,24 +122,6 @@
margin-top: 0.3rem;
}
.vcd-screen-picker-radios {
display: flex;
width: 100%;
border-radius: 3px;
}
.vcd-screen-picker-radios label {
flex: 1 1 auto;
text-align: center;
}
.vcd-screen-picker-radios label:first-child {
border-radius: 3px 0 0 3px;
}
.vcd-screen-picker-radios label:last-child {
border-radius: 0 3px 3px 0;
}
.vcd-screen-picker-audio {
margin-bottom: 0;

View File

@@ -4,12 +4,56 @@
* Copyright (c) 2023 Vendicated and Vencord contributors
*/
import { useForceUpdater } from "@vencord/types/utils";
import { Button, Forms, Toasts } from "@vencord/types/webpack/common";
import {
Margins,
ModalCloseButton,
ModalContent,
ModalHeader,
ModalRoot,
ModalSize,
openModal,
useForceUpdater
} from "@vencord/types/utils";
import { Button, Forms, Text, Toasts } from "@vencord/types/webpack/common";
import { Settings } from "shared/settings";
import { SettingsComponent } from "./Settings";
export const VencordLocationPicker: SettingsComponent = ({ settings }) => {
export const DeveloperOptionsButton: SettingsComponent = ({ settings }) => {
return <Button onClick={() => openDeveloperOptionsModal(settings)}>Open Developer Settings</Button>;
};
function openDeveloperOptionsModal(settings: Settings) {
openModal(props => (
<ModalRoot {...props} size={ModalSize.MEDIUM}>
<ModalHeader>
<Text variant="heading-lg/semibold" style={{ flexGrow: 1 }}>
Vesktop Developer Options
</Text>
<ModalCloseButton onClick={props.onClose} />
</ModalHeader>
<ModalContent>
<div style={{ padding: "1em 0" }}>
<Forms.FormTitle tag="h5">Vencord Location</Forms.FormTitle>
<VencordLocationPicker settings={settings} />
<Forms.FormTitle tag="h5" className={Margins.top16}>
Debugging
</Forms.FormTitle>
<div className="vcd-settings-button-grid">
<Button onClick={() => VesktopNative.debug.launchGpu()}>Open chrome://gpu</Button>
<Button onClick={() => VesktopNative.debug.launchWebrtcInternals()}>
Open chrome://webrtc-internals
</Button>
</div>
</div>
</ModalContent>
</ModalRoot>
));
}
const VencordLocationPicker: SettingsComponent = ({ settings }) => {
const forceUpdate = useForceUpdater();
const vencordDir = VesktopNative.fileManager.getVencordDir();
@@ -31,7 +75,7 @@ export const VencordLocationPicker: SettingsComponent = ({ settings }) => {
"the default location"
)}
</Forms.FormText>
<div className="vcd-location-btns">
<div className="vcd-settings-button-grid">
<Button
size={Button.Sizes.SMALL}
onClick={async () => {

View File

@@ -6,15 +6,16 @@
import "./settings.css";
import { ErrorBoundary } from "@vencord/types/components";
import { Forms, Switch, Text } from "@vencord/types/webpack/common";
import { ComponentType } from "react";
import { Settings, useSettings } from "renderer/settings";
import { isMac, isWindows } from "renderer/utils";
import { AutoStartToggle } from "./AutoStartToggle";
import { DeveloperOptionsButton } from "./DeveloperOptions";
import { DiscordBranchPicker } from "./DiscordBranchPicker";
import { NotificationBadgeToggle } from "./NotificationBadgeToggle";
import { VencordLocationPicker } from "./VencordLocationPicker";
import { WindowsTransparencyControls } from "./WindowsTransparencyControls";
interface BooleanSetting {
@@ -59,11 +60,18 @@ const SettingsOptions: Record<string, Array<BooleanSetting | SettingsComponent>>
defaultValue: false,
disabled: () => Settings.store.customTitleBar ?? isWindows
},
{
key: "enableSplashScreen",
title: "Enable Splash Screen",
description:
"Shows a small splash screen while Vesktop is loading. Disabling this option will show the main window earlier while it's still loading.",
defaultValue: true
},
{
key: "splashTheming",
title: "Splash theming",
description: "Adapt the splash window colors to your custom theme",
defaultValue: false
defaultValue: true
},
WindowsTransparencyControls
],
@@ -103,7 +111,7 @@ const SettingsOptions: Record<string, Array<BooleanSetting | SettingsComponent>>
}
],
Notifications: [NotificationBadgeToggle],
Miscelleanous: [
Miscellaneous: [
{
key: "arRPC",
title: "Rich Presence",
@@ -118,7 +126,7 @@ const SettingsOptions: Record<string, Array<BooleanSetting | SettingsComponent>>
defaultValue: false
}
],
"Vencord Location": [VencordLocationPicker]
"Developer Options": [DeveloperOptionsButton]
};
function SettingsSections() {
@@ -155,14 +163,20 @@ function SettingsSections() {
return <>{sections}</>;
}
export default function SettingsUi() {
return (
<Forms.FormSection>
<Text variant="heading-lg/semibold" style={{ color: "var(--header-primary)" }} tag="h2">
Vesktop Settings
</Text>
export default ErrorBoundary.wrap(
function SettingsUI() {
return (
<Forms.FormSection>
<Text variant="heading-lg/semibold" style={{ color: "var(--header-primary)" }} tag="h2">
Vesktop Settings
</Text>
<SettingsSections />
</Forms.FormSection>
);
}
<SettingsSections />
</Forms.FormSection>
);
},
{
message:
"Failed to render the Vesktop Settings tab. If this issue persists, try to right click the Vesktop tray icon, then click 'Repair Vencord'. And make sure your Vesktop is up to date."
}
);

View File

@@ -1,4 +1,4 @@
.vcd-location-btns {
.vcd-settings-button-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 0.5em;

View File

@@ -1,15 +1,3 @@
/* Download Desktop button in guilds list */
[class^=listItem_]:has([data-list-item-id=guildsnav___app-download-button]),
[class^=listItem_]:has(+ [class^=listItem_] [data-list-item-id=guildsnav___app-download-button]) {
display: none;
}
/* FIXME: remove this once Discord fixes their css to not explode scrollbars on chromium >=121 */
* {
scrollbar-width: unset !important;
scrollbar-color: unset !important;
}
/* Workaround for making things in the draggable area clickable again on macOS */
.platform-osx [class*=topic_], .platform-osx [class*=notice_] button {
-webkit-app-region: no-drag;

View File

@@ -6,7 +6,7 @@
import "./fixes.css";
import { isWindows, localStorage } from "./utils";
import { localStorage } from "./utils";
// Make clicking Notifications focus the window
const originalSetOnClick = Object.getOwnPropertyDescriptor(Notification.prototype, "onclick")!.set!;
@@ -22,14 +22,3 @@ Object.defineProperty(Notification.prototype, "onclick", {
// Hide "Download Discord Desktop now!!!!" banner
localStorage.setItem("hideNag", "true");
// FIXME: Remove eventually.
// Originally, Vencord always used a Windows user agent. This seems to cause captchas
// Now, we use a platform specific UA - HOWEVER, discord FOR SOME REASON????? caches
// device props in localStorage. This code fixes their cache to properly update the platform in SuperProps
if (!isWindows)
try {
const deviceProperties = localStorage.getItem("deviceProperties");
if (deviceProperties && JSON.parse(deviceProperties).os === "Windows")
localStorage.removeItem("deviceProperties");
} catch {}

View File

@@ -4,38 +4,22 @@
* Copyright (c) 2023 Vendicated and Vencord contributors
*/
import "./fixes";
import "./themedSplash";
import "./ipcCommands";
import "./appBadge";
import "./patches";
import "./themedSplash";
console.log("read if cute :3");
import "./fixes";
import "./arrpc";
export * as Components from "./components";
import { findByPropsLazy, onceReady } from "@vencord/types/webpack";
import { Alerts, FluxDispatcher } from "@vencord/types/webpack/common";
import SettingsUi from "./components/settings/Settings";
import { VesktopLogger } from "./logger";
import { Settings } from "./settings";
export { Settings };
const InviteActions = findByPropsLazy("resolveInvite");
export async function openInviteModal(code: string) {
const { invite } = await InviteActions.resolveInvite(code, "Desktop Modal");
if (!invite) return false;
VesktopNative.win.focus();
FluxDispatcher.dispatch({
type: "INVITE_MODAL_OPEN",
invite,
code,
context: "APP"
});
return true;
}
VesktopLogger.log("read if cute :3");
VesktopLogger.log("Vesktop v" + VesktopNative.app.getVersion());
const customSettingsSections = (
Vencord.Plugins.plugins.Settings as any as { customSections: ((ID: Record<string, unknown>) => any)[] }
@@ -47,31 +31,3 @@ customSettingsSections.push(() => ({
element: SettingsUi,
className: "vc-vesktop-settings"
}));
const arRPC = Vencord.Plugins.plugins["WebRichPresence (arRPC)"] as any as {
handleEvent(e: MessageEvent): void;
};
VesktopNative.arrpc.onActivity(async data => {
if (!Settings.store.arRPC) return;
await onceReady;
arRPC.handleEvent(new MessageEvent("message", { data }));
});
// TODO: remove soon
const vencordDir = "vencordDir" as keyof typeof Settings.store;
if (Settings.store[vencordDir]) {
onceReady.then(() =>
setTimeout(
() =>
Alerts.show({
title: "Custom Vencord Location",
body: "Due to security hardening changes in Vesktop, your custom Vencord location had to be reset. Please configure it again in the settings.",
onConfirm: () => delete Settings.store[vencordDir]
}),
5000
)
);
}

View File

@@ -0,0 +1,49 @@
/*
* 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 { SettingsRouter } from "@vencord/types/webpack/common";
import { IpcCommands } from "shared/IpcEvents";
type IpcCommandHandler = (data: any) => any;
const handlers = new Map<string, IpcCommandHandler>();
function respond(nonce: string, ok: boolean, data: any) {
VesktopNative.commands.respond({ nonce, ok, data });
}
VesktopNative.commands.onCommand(async ({ message, nonce, data }) => {
const handler = handlers.get(message);
if (!handler) {
return respond(nonce, false, `No handler for message: ${message}`);
}
try {
const result = await handler(data);
respond(nonce, true, result);
} catch (err) {
respond(nonce, false, String(err));
}
});
export function onIpcCommand(channel: string, handler: IpcCommandHandler) {
if (handlers.has(channel)) {
throw new Error(`Handler for message ${channel} already exists`);
}
handlers.set(channel, handler);
}
export function offIpcCommand(channel: string) {
handlers.delete(channel);
}
/* Generic Handlers */
onIpcCommand(IpcCommands.NAVIGATE_SETTINGS, () => {
SettingsRouter.open("My Account");
});
onIpcCommand(IpcCommands.GET_LANGUAGES, () => navigator.languages);

9
src/renderer/logger.ts Normal file
View File

@@ -0,0 +1,9 @@
/*
* 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 { Logger } from "@vencord/types/utils";
export const VesktopLogger = new Logger("Vesktop", "#d3869b");

View File

@@ -12,3 +12,5 @@ import "./hideVenmicInput";
import "./screenShareFixes";
import "./spellCheck";
import "./windowsTitleBar";
import "./streamerMode";
import "./nativeFocus";

View File

@@ -0,0 +1,23 @@
/*
* 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 { addPatch } from "./shared";
addPatch({
patches: [
{
find: ".DEEP_LINK]:{",
replacement: [
{
// TODO: Fix eslint rule
// eslint-disable-next-line no-useless-escape
match: /(?<=\.DEEP_LINK.{0,200}?)\i\.\i\.focus\(\)/,
replace: "VesktopNative.win.focus()"
}
]
}
]
});

View File

@@ -6,6 +6,7 @@
import { Logger } from "@vencord/types/utils";
import { currentSettings } from "renderer/components/ScreenSharePicker";
import { State } from "renderer/settings";
import { isLinux } from "renderer/utils";
const logger = new Logger("VesktopStreamFixes");
@@ -27,8 +28,8 @@ if (isLinux) {
const stream = await original.call(this, opts);
const id = await getVirtmic();
const frameRate = Number(currentSettings?.fps);
const height = Number(currentSettings?.resolution);
const frameRate = Number(State.store.screenshareQuality?.frameRate ?? 30);
const height = Number(State.store.screenshareQuality?.resolution ?? 720);
const width = Math.round(height * (16 / 9));
const track = stream.getVideoTracks()[0];

View File

@@ -0,0 +1,21 @@
/*
* SPDX-License-Identifier: GPL-3.0
* Vesktop, a desktop app aiming to give you a snappier Discord Experience
* Copyright (c) 2023 Vendicated and Vencord contributors
*/
import { addPatch } from "./shared";
addPatch({
patches: [
{
find: ".STREAMER_MODE_ENABLE,",
replacement: {
// remove if (platformEmbedded) check from streamer mode toggle
// eslint-disable-next-line no-useless-escape
match: /if\(\i\.\i\)(?=return.{0,200}?"autoToggle")/g,
replace: ""
}
}
]
});

View File

@@ -7,6 +7,9 @@
import { useEffect, useReducer } from "@vencord/types/webpack/common";
import { SettingsStore } from "shared/utils/SettingsStore";
import { VesktopLogger } from "./logger";
import { localStorage } from "./utils";
export const Settings = new SettingsStore(VesktopNative.settings.get());
Settings.addGlobalChangeListener((o, p) => VesktopNative.settings.set(o, p));
@@ -28,3 +31,38 @@ export function getValueAndOnChange(key: keyof typeof Settings.store) {
onChange: (value: any) => (Settings.store[key] = value)
};
}
interface TState {
screenshareQuality?: {
resolution: string;
frameRate: string;
};
}
const stateKey = "VesktopState";
const currentState: TState = (() => {
const stored = localStorage.getItem(stateKey);
if (!stored) return {};
try {
return JSON.parse(stored);
} catch (e) {
VesktopLogger.error("Failed to parse stored state", e);
return {};
}
})();
export const State = new SettingsStore<TState>(currentState);
State.addGlobalChangeListener((o, p) => localStorage.setItem(stateKey, JSON.stringify(o)));
export function useVesktopState() {
const [, update] = useReducer(x => x + 1, 0);
useEffect(() => {
State.addGlobalChangeListener(update);
return () => State.removeGlobalChangeListener(update);
}, []);
return State.store;
}

View File

@@ -10,15 +10,49 @@ function isValidColor(color: CSSStyleValue | undefined): color is CSSUnparsedVal
return color instanceof CSSUnparsedValue && typeof color[0] === "string" && CSS.supports("color", color[0]);
}
// https://gist.github.com/earthbound19/e7fe15fdf8ca3ef814750a61bc75b5ce
function clamp(value: number, min: number, max: number) {
return Math.max(Math.min(value, max), min);
}
const linearToGamma = (c: number) => (c >= 0.0031308 ? 1.055 * Math.pow(c, 1 / 2.4) - 0.055 : 12.92 * c);
function oklabToSRGB({ L, a, b }: { L: number; a: number; b: number }) {
let l = L + a * +0.3963377774 + b * +0.2158037573;
let m = L + a * -0.1055613458 + b * -0.0638541728;
let s = L + a * -0.0894841775 + b * -1.291485548;
l **= 3;
m **= 3;
s **= 3;
let R = l * +4.0767416621 + m * -3.3077115913 + s * +0.2309699292;
let G = l * -1.2684380046 + m * +2.6097574011 + s * -0.3413193965;
let B = l * -0.0041960863 + m * -0.7034186147 + s * +1.707614701;
R = 255 * linearToGamma(R);
G = 255 * linearToGamma(G);
B = 255 * linearToGamma(B);
R = Math.round(clamp(R, 0, 255));
G = Math.round(clamp(G, 0, 255));
B = Math.round(clamp(B, 0, 255));
return `rgb(${R}, ${G}, ${B})`;
}
function resolveColor(color: string) {
const span = document.createElement("span");
span.style.color = color;
span.style.display = "none";
document.body.append(span);
const rgbColor = getComputedStyle(span).color;
let rgbColor = getComputedStyle(span).color;
span.remove();
if (rgbColor.startsWith("oklab(")) {
// scam
const [_, L, a, b] = rgbColor.match(/oklab\((.+?)[, ]+(.+?)[, ]+(.+?)\)/) ?? [];
if (L && a && b) {
rgbColor = oklabToSRGB({ L: parseFloat(L), a: parseFloat(a), b: parseFloat(b) });
}
}
return rgbColor;
}

View File

@@ -4,6 +4,7 @@
* Copyright (c) 2023 Vendicated and Vencord contributors
*/
// Discord deletes this from the window so we need to capture it in a variable
export const { localStorage } = window;
export const isFirstRun = (() => {
@@ -18,3 +19,26 @@ const { platform } = navigator;
export const isWindows = platform.startsWith("Win");
export const isMac = platform.startsWith("Mac");
export const isLinux = platform.startsWith("Linux");
type ClassNameFactoryArg = string | string[] | Record<string, unknown> | false | null | undefined | 0 | "";
/**
* @param prefix The prefix to add to each class, defaults to `""`
* @returns A classname generator function
* @example
* const cl = classNameFactory("plugin-");
*
* cl("base", ["item", "editable"], { selected: null, disabled: true })
* // => "plugin-base plugin-item plugin-editable plugin-disabled"
*/
export const classNameFactory =
(prefix: string = "") =>
(...args: ClassNameFactoryArg[]) => {
const classNames = new Set<string>();
for (const arg of args) {
if (arg && typeof arg === "string") classNames.add(arg);
else if (Array.isArray(arg)) arg.forEach(name => classNames.add(name));
else if (arg && typeof arg === "object")
Object.entries(arg).forEach(([name, value]) => value && classNames.add(name));
}
return Array.from(classNames, name => prefix + name).join(" ");
};

View File

@@ -50,5 +50,18 @@ export const enum IpcEvents {
ARRPC_ACTIVITY = "VCD_ARRPC_ACTIVITY",
CLIPBOARD_COPY_IMAGE = "VCD_CLIPBOARD_COPY_IMAGE"
CLIPBOARD_COPY_IMAGE = "VCD_CLIPBOARD_COPY_IMAGE",
DEBUG_LAUNCH_GPU = "VCD_DEBUG_LAUNCH_GPU",
DEBUG_LAUNCH_WEBRTC_INTERNALS = "VCD_DEBUG_LAUNCH_WEBRTC",
IPC_COMMAND = "VCD_IPC_COMMAND"
}
export const enum IpcCommands {
RPC_ACTIVITY = "rpc:activity",
RPC_INVITE = "rpc:invite",
RPC_DEEP_LINK = "rpc:link",
NAVIGATE_SETTINGS = "navigate:settings",
GET_LANGUAGES = "navigator.languages"
}

View File

@@ -22,6 +22,7 @@ export interface Settings {
clickTrayToShowHide?: boolean;
customTitleBar?: boolean;
enableSplashScreen?: boolean;
splashTheming?: boolean;
splashColor?: string;
splashBackground?: string;
@@ -30,6 +31,8 @@ export interface Settings {
audio?: {
workaround?: boolean;
deviceSelect?: boolean;
granularSelect?: boolean;
ignoreVirtual?: boolean;
@@ -45,7 +48,7 @@ export interface State {
maximized?: boolean;
minimized?: boolean;
windowBounds?: Rectangle;
displayid: int;
displayId: int;
firstLaunch?: boolean;

View File

@@ -26,11 +26,11 @@ export class SettingsStore<T extends object> {
/**
* The store object. Making changes to this object will trigger the applicable change listeners
*/
public declare store: T;
declare public store: T;
/**
* The plain data. Changes to this object will not trigger any change listeners
*/
public declare plain: T;
declare public plain: T;
public constructor(plain: T) {
this.plain = plain;

View File

@@ -3,6 +3,7 @@
<style>
body {
background: none;
user-select: none;
-webkit-app-region: drag;
}
@@ -16,6 +17,7 @@
align-items: center;
border-radius: 8px;
border: 1px solid var(--fg-semi-trans);
background: var(--bg);
}
p {