Merge branch 'main' into 171-middle-click-autoscroll

This commit is contained in:
Nullmatic 2024-01-17 21:25:15 -06:00
commit 8a1bff56f1
No known key found for this signature in database
GPG key ID: 96CC7E11020FBE77
40 changed files with 1442 additions and 1154 deletions

View file

@ -22,7 +22,13 @@
"eqeqeq": ["error", "always", { "null": "ignore" }], "eqeqeq": ["error", "always", { "null": "ignore" }],
"spaced-comment": ["error", "always", { "markers": ["!"] }], "spaced-comment": ["error", "always", { "markers": ["!"] }],
"yoda": "error", "yoda": "error",
"prefer-destructuring": ["error", { "object": true, "array": false }], "prefer-destructuring": [
"error",
{
"VariableDeclarator": { "array": false, "object": true },
"AssignmentExpression": { "array": false, "object": false }
}
],
"operator-assignment": ["error", "always"], "operator-assignment": ["error", "always"],
"no-useless-computed-key": "error", "no-useless-computed-key": "error",
"no-unneeded-ternary": ["error", { "defaultAssignment": false }], "no-unneeded-ternary": ["error", { "defaultAssignment": false }],

32
.github/ISSUE_TEMPLATE/bug_report.md vendored Normal file
View file

@ -0,0 +1,32 @@
---
name: Bug report
about: Create a report to help us improve
title: ''
labels: bug
assignees: ''
---
**Describe the bug**
A clear and concise description of what the bug is.
**To Reproduce**
Steps to reproduce the behavior:
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
4. See error
**Expected behavior**
A clear and concise description of what you expected to happen.
**Screenshots**
If applicable, add screenshots to help explain your problem.
**Desktop (please complete the following information):**
- OS/Distro: [e.g. Windows / Fedora Linux / MacOs]
- Desktop Environment (linux only): [e.g. gnome, kde, sway]
- Version: [e.g. 22]
**Additional context**
Add any other context about the problem here.

10
.github/ISSUE_TEMPLATE/custom.md vendored Normal file
View file

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

View file

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

38
.github/workflows/meta.yml vendored Normal file
View file

@ -0,0 +1,38 @@
name: Update metainfo on release
on:
release:
types:
- published
workflow_dispatch:
jobs:
update:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: pnpm/action-setup@v2 # Install pnpm using packageManager key in package.json
- name: Use Node.js 18.18.2
uses: actions/setup-node@v3
with:
node-version: 18.18.2
- name: Install dependencies
run: pnpm i
- name: Update metainfo
run: pnpm updateMeta
- 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 }}. @lewisakura @Vendicated"
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

View file

@ -12,17 +12,41 @@ jobs:
strategy: strategy:
matrix: matrix:
os: [macos-latest, ubuntu-latest, windows-latest] os: [macos-latest, ubuntu-latest, windows-latest]
include:
- os: macos-latest
platform: mac
- os: ubuntu-latest
platform: linux
- os: windows-latest
platform: windows
steps: steps:
- name: Check out Git repository - uses: actions/checkout@v3
uses: actions/checkout@v3 - uses: pnpm/action-setup@v2 # Install pnpm using packageManager key in package.json
- uses: actions/setup-node@v3 - name: Use Node.js 18.18.2
uses: actions/setup-node@v3
with: with:
node-version: 18 node-version: 18.18.2
cache: "pnpm"
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Build
run: pnpm build
- name: Run Electron Builder - name: Run Electron Builder
uses: samuelmeuli/action-electron-builder@e4b12cd06ddf023422f1ac4e39632bd76f6e6928 if: ${{ matrix.platform != 'mac' }}
with: run: |
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} pnpm electron-builder --${{ matrix.platform }} --publish always
RELEASE: true env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Run Electron Builder
if: ${{ matrix.platform == 'mac' }}
run: |
pnpm electron-builder --${{ matrix.platform }} --publish always
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
CSC_LINK_: ${{ secrets.APPLE_SIGNING_CERT }}

View file

@ -14,10 +14,10 @@ jobs:
- uses: actions/checkout@v3 - uses: actions/checkout@v3
- uses: pnpm/action-setup@v2 # Install pnpm using packageManager key in package.json - uses: pnpm/action-setup@v2 # Install pnpm using packageManager key in package.json
- name: Use Node.js 18 - name: Use Node.js 18.18.2
uses: actions/setup-node@v3 uses: actions/setup-node@v3
with: with:
node-version: 18 node-version: 18.18.2
cache: "pnpm" cache: "pnpm"
- name: Install dependencies - name: Install dependencies

View file

@ -1,7 +1,7 @@
{ {
"editor.formatOnSave": true, "editor.formatOnSave": true,
"editor.codeActionsOnSave": { "editor.codeActionsOnSave": {
"source.fixAll.eslint": true "source.fixAll.eslint": "explicit"
}, },
"[typescript]": { "[typescript]": {
"editor.defaultFormatter": "esbenp.prettier-vscode" "editor.defaultFormatter": "esbenp.prettier-vscode"

View file

@ -3,11 +3,13 @@
Vesktop is a cross platform desktop app aiming to give you a snappier Discord experience with [Vencord](https://github.com/Vendicated/Vencord) pre-installed Vesktop is a cross platform desktop app aiming to give you a snappier Discord experience with [Vencord](https://github.com/Vendicated/Vencord) pre-installed
**Not yet supported**: **Not yet supported**:
- Global Keybinds
- Global Keybinds
Bug reports, feature requests & contributions are highly appreciated!! Bug reports, feature requests & contributions are highly appreciated!!
![image](https://user-images.githubusercontent.com/45497981/235024615-94565eaf-f412-4384-a3f5-d8cde7458f6d.png) ![](https://github.com/Vencord/Vesktop/assets/45497981/8608a899-96a9-4027-9725-2cb02ba189fd)
![grafik](https://github.com/Vencord/Vesktop/assets/45497981/8701e5de-52c4-4346-a990-719cb971642e)
## Installing ## Installing
@ -21,6 +23,8 @@ Download and run Vesktop-VERSION.dmg from [releases](https://github.com/Vencord/
### Linux ### Linux
[![](https://dl.flathub.org/assets/badges/flathub-badge-en.svg)](https://flathub.org/apps/dev.vencord.Vesktop)
#### Arch based #### Arch based
Install [vencord-desktop-git](https://aur.archlinux.org/packages/vencord-desktop-git) from the AUR using your favourite AUR helper, for example [yay](https://github.com/Jguer/yay) Install [vencord-desktop-git](https://aur.archlinux.org/packages/vencord-desktop-git) from the AUR using your favourite AUR helper, for example [yay](https://github.com/Jguer/yay)
@ -35,9 +39,9 @@ Download Vesktop-VERSION.rpm from [releases](https://github.com/Vencord/Vesktop/
#### Other #### Other
Either download Vesktop-VERSION.AppImage and just run it directly or grab Vesktop-VERSION.tar.gz, extract it somewhere and run `vencorddesktop`. Either download Vesktop-VERSION.AppImage and just run it directly or grab Vesktop-VERSION.tar.gz, extract it somewhere and run `vesktop`.
A flatpak is planned, if you want packages for other repos, feel free to create them and they can be linked as unofficial here If other packages are created, feel free to open an issue and we'll link them here.
## Building ## Building
@ -64,7 +68,3 @@ pnpm package:dir
## Motivation ## Motivation
The official Discord Desktop app is very resource heavy compared to Discord in your Browser. There are multiple alternative Electron apps (ArmCord, WebCord, probably more) that prove how much of a performance gain you can gain by using a custom app. ArmCord already supports Vencord but makes it pretty limited for us. Making our own standalone app gives us much more control. The official Discord Desktop app is very resource heavy compared to Discord in your Browser. There are multiple alternative Electron apps (ArmCord, WebCord, probably more) that prove how much of a performance gain you can gain by using a custom app. ArmCord already supports Vencord but makes it pretty limited for us. Making our own standalone app gives us much more control.
This is just a random idea I (V) got, and might not actually ever be finished heh
Gluon also seems very attractive for this because of how lightweight it can be and because unlike electron, streaming just works out of the box like in any chromium browser. However, at the time of writing this, it still lacks some features necessary to make it work (synchronous ipc or a way to get node process variables into the onLoad function for instance, plus onLoad seems to load a little too late sometimes)

View file

@ -1,8 +1,8 @@
!macro preInit !macro preInit
SetRegView 64 SetRegView 64
WriteRegExpandStr HKLM "${INSTALL_REGISTRY_KEY}" InstallLocation "$LocalAppData\VencordDesktop" WriteRegExpandStr HKLM "${INSTALL_REGISTRY_KEY}" InstallLocation "$LocalAppData\vesktop"
WriteRegExpandStr HKCU "${INSTALL_REGISTRY_KEY}" InstallLocation "$LocalAppData\VencordDesktop" WriteRegExpandStr HKCU "${INSTALL_REGISTRY_KEY}" InstallLocation "$LocalAppData\vesktop"
SetRegView 32 SetRegView 32
WriteRegExpandStr HKLM "${INSTALL_REGISTRY_KEY}" InstallLocation "$LocalAppData\VencordDesktop" WriteRegExpandStr HKLM "${INSTALL_REGISTRY_KEY}" InstallLocation "$LocalAppData\vesktop"
WriteRegExpandStr HKCU "${INSTALL_REGISTRY_KEY}" InstallLocation "$LocalAppData\VencordDesktop" WriteRegExpandStr HKCU "${INSTALL_REGISTRY_KEY}" InstallLocation "$LocalAppData\vesktop"
!macroend !macroend

View file

@ -0,0 +1,183 @@
<?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.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>AudioVideo</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>
<internet>always</internet>
</recommends>
<supports>
<internet>always</internet>
</supports>
<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,6 +1,6 @@
{ {
"name": "VencordDesktop", "name": "vesktop",
"version": "0.4.3", "version": "1.5.0",
"private": true, "private": true,
"description": "", "description": "",
"keywords": [], "keywords": [],
@ -20,46 +20,48 @@
"start:watch": "pnpm build:dev && tsx scripts/startWatch.mts", "start:watch": "pnpm build:dev && tsx scripts/startWatch.mts",
"test": "pnpm lint && pnpm testTypes", "test": "pnpm lint && pnpm testTypes",
"testTypes": "tsc --noEmit", "testTypes": "tsc --noEmit",
"watch": "pnpm build --watch" "watch": "pnpm build --watch",
"updateMeta": "tsx scripts/utils/updateMeta.mts"
}, },
"dependencies": { "dependencies": {
"arrpc": "github:OpenAsar/arrpc#89f4da610ccfac93f461826a446a17cd3b23953d" "arrpc": "github:OpenAsar/arrpc#98879cae0565e6fce34e4cb6f544bf42c6a7e7c8"
}, },
"optionalDependencies": { "optionalDependencies": {
"@vencord/venmic": "^2.1.2" "@vencord/venmic": "^3.2.3"
}, },
"devDependencies": { "devDependencies": {
"@fal-works/esbuild-plugin-global-externals": "^2.1.2", "@fal-works/esbuild-plugin-global-externals": "^2.1.2",
"@types/node": "^20.8.4", "@types/node": "^20.11.2",
"@types/react": "^18.2.28", "@types/react": "^18.2.48",
"@typescript-eslint/eslint-plugin": "^6.7.5", "@typescript-eslint/eslint-plugin": "^6.19.0",
"@typescript-eslint/parser": "^6.7.5", "@typescript-eslint/parser": "^6.19.0",
"@vencord/types": "^0.1.2", "@vencord/types": "^0.1.2",
"dotenv": "^16.3.1", "dotenv": "^16.3.1",
"electron": "^27.0.0", "electron": "^28.1.3",
"electron-builder": "^24.6.4", "electron-builder": "^24.9.1",
"esbuild": "^0.19.4", "esbuild": "^0.19.11",
"eslint": "^8.51.0", "eslint": "^8.56.0",
"eslint-config-prettier": "^9.0.0", "eslint-config-prettier": "^9.1.0",
"eslint-import-resolver-alias": "^1.1.2", "eslint-import-resolver-alias": "^1.1.2",
"eslint-plugin-license-header": "^0.6.0", "eslint-plugin-license-header": "^0.6.0",
"eslint-plugin-path-alias": "^1.0.0", "eslint-plugin-path-alias": "^1.0.0",
"eslint-plugin-prettier": "^5.0.1", "eslint-plugin-prettier": "^5.1.3",
"eslint-plugin-simple-import-sort": "^10.0.0", "eslint-plugin-simple-import-sort": "^10.0.0",
"eslint-plugin-unused-imports": "^3.0.0", "eslint-plugin-unused-imports": "^3.0.0",
"prettier": "^3.0.3", "prettier": "^3.2.2",
"source-map-support": "^0.5.21", "source-map-support": "^0.5.21",
"tsx": "^3.13.0", "tsx": "^4.7.0",
"type-fest": "^4.4.0", "type-fest": "^4.9.0",
"typescript": "^5.2.2" "typescript": "^5.3.3",
"xml-formatter": "^3.6.0"
}, },
"packageManager": "pnpm@8.6.11", "packageManager": "pnpm@8.11.0",
"engines": { "engines": {
"node": ">=18", "node": ">=18",
"pnpm": ">=8" "pnpm": ">=8"
}, },
"build": { "build": {
"appId": "dev.vencord.desktop", "appId": "dev.vencord.vesktop",
"productName": "Vesktop", "productName": "Vesktop",
"files": [ "files": [
"!*", "!*",
@ -108,9 +110,7 @@
"GenericName": "Internet Messenger", "GenericName": "Internet Messenger",
"Type": "Application", "Type": "Application",
"Categories": "Network;InstantMessaging;Chat;", "Categories": "Network;InstantMessaging;Chat;",
"Keywords": "discord;vencord;electron;chat;", "Keywords": "discord;vencord;electron;chat;"
"WMClass": "VencordDesktop",
"StartupWMClass": "VencordDesktop"
} }
}, },
"mac": { "mac": {

1499
pnpm-lock.yaml generated

File diff suppressed because it is too large Load diff

View file

@ -78,8 +78,6 @@ await Promise.all([
inject: ["./scripts/build/injectReact.mjs"], inject: ["./scripts/build/injectReact.mjs"],
jsxFactory: "VencordCreateElement", jsxFactory: "VencordCreateElement",
jsxFragment: "VencordFragment", jsxFragment: "VencordFragment",
// Work around https://github.com/evanw/esbuild/issues/2460
tsconfig: "./scripts/build/tsconfig.esbuild.json",
external: ["@vencord/types/*"], external: ["@vencord/types/*"],
plugins: [vencordDep], plugins: [vencordDep],
footer: { js: "//# sourceURL=VCDRenderer" } footer: { js: "//# sourceURL=VCDRenderer" }

View file

@ -1,7 +0,0 @@
// Work around https://github.com/evanw/esbuild/issues/2460
{
"extends": "../../tsconfig.json",
"compilerOptions": {
"jsx": "react"
}
}

View file

@ -0,0 +1,93 @@
/*
* 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 { promises as fs } from "node:fs";
import { DOMParser, XMLSerializer } from "@xmldom/xmldom";
import xmlFormat from "xml-formatter";
function generateDescription(description: string, descriptionNode: Element) {
const lines = description.replace(/\r/g, "").split("\n");
let currentList: Element | null = null;
for (let i = 0; i < lines.length; i++) {
const line = lines[i];
if (line.includes("New Contributors")) {
// we're done, don't parse any more since the new contributors section is the last one
break;
}
if (line.startsWith("## ")) {
const pNode = descriptionNode.ownerDocument.createElement("p");
pNode.textContent = line.slice(3);
descriptionNode.appendChild(pNode);
} else if (line.startsWith("* ")) {
const liNode = descriptionNode.ownerDocument.createElement("li");
liNode.textContent = line.slice(2).split("in https://github.com")[0].trim(); // don't include links to github
if (!currentList) {
currentList = descriptionNode.ownerDocument.createElement("ul");
}
currentList.appendChild(liNode);
}
if (currentList && !lines[i + 1].startsWith("* ")) {
descriptionNode.appendChild(currentList);
currentList = null;
}
}
}
const latestReleaseInformation = await fetch("https://api.github.com/repos/Vencord/Vesktop/releases/latest", {
headers: {
Accept: "application/vnd.github+json",
"X-Github-Api-Version": "2022-11-28"
}
}).then(res => res.json());
const metaInfo = await fs.readFile("./meta/dev.vencord.Vesktop.metainfo.xml", "utf-8");
const parser = new DOMParser().parseFromString(metaInfo, "text/xml");
const releaseList = parser.getElementsByTagName("releases")[0];
for (let i = 0; i < releaseList.childNodes.length; i++) {
const release = releaseList.childNodes[i] as Element;
if (release.nodeType === 1 && release.getAttribute("version") === latestReleaseInformation.name) {
console.log("Latest release already added, nothing to be done");
process.exit(0);
}
}
const release = parser.createElement("release");
release.setAttribute("version", latestReleaseInformation.name);
release.setAttribute("date", latestReleaseInformation.published_at.split("T")[0]);
release.setAttribute("type", "stable");
const releaseUrl = parser.createElement("url");
releaseUrl.textContent = latestReleaseInformation.html_url;
release.appendChild(releaseUrl);
const description = parser.createElement("description");
// we're not using a full markdown parser here since we don't have a lot of formatting options to begin with
generateDescription(latestReleaseInformation.body, description);
release.appendChild(description);
releaseList.insertBefore(release, releaseList.childNodes[0]);
const output = xmlFormat(new XMLSerializer().serializeToString(parser), {
lineSeparator: "\n",
collapseContent: true,
indentation: " "
});
await fs.writeFile("./meta/dev.vencord.Vesktop.metainfo.xml", output, "utf-8");

View file

@ -5,9 +5,29 @@
*/ */
import { app } from "electron"; import { app } from "electron";
import { existsSync, readdirSync, renameSync, rmdirSync } from "fs";
import { join } from "path"; import { join } from "path";
export const DATA_DIR = process.env.VENCORD_USER_DATA_DIR || join(app.getPath("userData"), "VencordDesktop"); const LEGACY_DATA_DIR = join(app.getPath("appData"), "VencordDesktop", "VencordDesktop");
export const DATA_DIR = process.env.VENCORD_USER_DATA_DIR || join(app.getPath("userData"));
// 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);
}
}
app.setPath("sessionData", join(DATA_DIR, "sessionData"));
export const VENCORD_SETTINGS_DIR = join(DATA_DIR, "settings"); export const VENCORD_SETTINGS_DIR = join(DATA_DIR, "settings");
export const VENCORD_QUICKCSS_FILE = join(VENCORD_SETTINGS_DIR, "quickCss.css"); export const VENCORD_QUICKCSS_FILE = join(VENCORD_SETTINGS_DIR, "quickCss.css");
export const VENCORD_SETTINGS_FILE = join(VENCORD_SETTINGS_DIR, "settings.json"); export const VENCORD_SETTINGS_FILE = join(VENCORD_SETTINGS_DIR, "settings.json");
@ -26,11 +46,13 @@ export const MIN_HEIGHT = 500;
export const DEFAULT_WIDTH = 1280; export const DEFAULT_WIDTH = 1280;
export const DEFAULT_HEIGHT = 720; export const DEFAULT_HEIGHT = 720;
export const DISCORD_HOSTNAMES = ["discord.com", "canary.discord.com", "ptb.discord.com"];
const UserAgents = { const UserAgents = {
darwin: "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/118.0.0.0 Safari/537.36", darwin: "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
linux: "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/118.0.0.0 Safari/537.36", linux: "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
windows: windows:
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/118.0.0.0 Safari/537.36" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36"
}; };
export const UserAgent = UserAgents[process.platform] || UserAgents.windows; export const UserAgent = UserAgents[process.platform] || UserAgents.windows;

View file

@ -14,7 +14,7 @@ import { ICON_PATH, VIEW_DIR } from "shared/paths";
import { autoStart } from "./autoStart"; import { autoStart } from "./autoStart";
import { DATA_DIR } from "./constants"; import { DATA_DIR } from "./constants";
import { createWindows } from "./mainWindow"; import { createWindows } from "./mainWindow";
import { Settings } from "./settings"; import { Settings, State } from "./settings";
import { makeLinksOpenExternally } from "./utils/makeLinksOpenExternally"; import { makeLinksOpenExternally } from "./utils/makeLinksOpenExternally";
interface Data { interface Data {
@ -44,9 +44,9 @@ export function createFirstLaunchTour() {
if (!msg.startsWith("form:")) return; if (!msg.startsWith("form:")) return;
const data = JSON.parse(msg.slice(5)) as Data; const data = JSON.parse(msg.slice(5)) as Data;
State.store.firstLaunch = false;
Settings.store.minimizeToTray = data.minimizeToTray; Settings.store.minimizeToTray = data.minimizeToTray;
Settings.store.discordBranch = data.discordBranch; Settings.store.discordBranch = data.discordBranch;
Settings.store.firstLaunch = false;
Settings.store.arRPC = data.richPresence; Settings.store.arRPC = data.richPresence;
if (data.autoStart) autoStart.enable(); if (data.autoStart) autoStart.enable();

View file

@ -6,7 +6,7 @@
import "./ipc"; import "./ipc";
import { app, BrowserWindow } from "electron"; import { app, BrowserWindow, nativeTheme } from "electron";
import { checkUpdates } from "updater/main"; import { checkUpdates } from "updater/main";
import { DATA_DIR } from "./constants"; import { DATA_DIR } from "./constants";
@ -14,7 +14,8 @@ import { createFirstLaunchTour } from "./firstLaunch";
import { createWindows, mainWin } from "./mainWindow"; import { createWindows, mainWin } from "./mainWindow";
import { registerMediaPermissionsHandler } from "./mediaPermissions"; import { registerMediaPermissionsHandler } from "./mediaPermissions";
import { registerScreenShareHandler } from "./screenShare"; import { registerScreenShareHandler } from "./screenShare";
import { Settings } from "./settings"; import { Settings, State } from "./settings";
import { isDeckGameMode } from "./utils/steamOS";
if (IS_DEV) { if (IS_DEV) {
require("source-map-support").install(); require("source-map-support").install();
@ -24,8 +25,9 @@ if (IS_DEV) {
process.env.VENCORD_USER_DATA_DIR = DATA_DIR; process.env.VENCORD_USER_DATA_DIR = DATA_DIR;
function init() { function init() {
const { disableSmoothScroll } = Settings.store; const { disableSmoothScroll, hardwareAcceleration } = Settings.store;
if (hardwareAcceleration === false) app.disableHardwareAcceleration();
if (disableSmoothScroll) { if (disableSmoothScroll) {
app.commandLine.appendSwitch("disable-smooth-scrolling"); app.commandLine.appendSwitch("disable-smooth-scrolling");
} }
@ -42,6 +44,9 @@ function init() {
"WinRetrieveSuggestionsOnlyOnDemand,HardwareMediaKeyHandling,MediaSessionService,WidgetLayering" "WinRetrieveSuggestionsOnlyOnDemand,HardwareMediaKeyHandling,MediaSessionService,WidgetLayering"
); );
// In the Flatpak on SteamOS the theme is detected as light, but SteamOS only has a dark mode, so we just override it
if (isDeckGameMode) nativeTheme.themeSource = "dark";
app.on("second-instance", (_event, _cmdLine, _cwd, data: any) => { app.on("second-instance", (_event, _cmdLine, _cwd, data: any) => {
if (data.IS_DEV) app.quit(); if (data.IS_DEV) app.quit();
else if (mainWin) { else if (mainWin) {
@ -53,7 +58,7 @@ function init() {
app.whenReady().then(async () => { app.whenReady().then(async () => {
checkUpdates(); checkUpdates();
if (process.platform === "win32") app.setAppUserModelId("dev.vencord.desktop"); if (process.platform === "win32") app.setAppUserModelId("dev.vencord.vesktop");
registerScreenShareHandler(); registerScreenShareHandler();
registerMediaPermissionsHandler(); registerMediaPermissionsHandler();
@ -79,7 +84,7 @@ if (!app.requestSingleInstanceLock({ IS_DEV })) {
} }
async function bootstrap() { async function bootstrap() {
if (!Object.hasOwn(Settings.store, "firstLaunch")) { if (!Object.hasOwn(State.store, "firstLaunch")) {
createFirstLaunchTour(); createFirstLaunchTour();
} else { } else {
createWindows(); createWindows();

View file

@ -4,10 +4,10 @@
* Copyright (c) 2023 Vendicated and Vencord contributors * Copyright (c) 2023 Vendicated and Vencord contributors
*/ */
if (process.platform === "linux") import("./virtmic"); if (process.platform === "linux") import("./venmic");
import { execFile } from "child_process"; import { execFile } from "child_process";
import { app, BrowserWindow, dialog, RelaunchOptions, session, shell } from "electron"; import { app, BrowserWindow, clipboard, dialog, nativeImage, RelaunchOptions, session, shell } from "electron";
import { mkdirSync, readFileSync, watch } from "fs"; import { mkdirSync, readFileSync, watch } from "fs";
import { open, readFile } from "fs/promises"; import { open, readFile } from "fs/promises";
import { release } from "os"; import { release } from "os";
@ -21,6 +21,7 @@ import { VENCORD_FILES_DIR, VENCORD_QUICKCSS_FILE, VENCORD_THEMES_DIR } from "./
import { mainWin } from "./mainWindow"; import { mainWin } from "./mainWindow";
import { Settings } from "./settings"; import { Settings } from "./settings";
import { handle, handleSync } from "./utils/ipcWrappers"; import { handle, handleSync } from "./utils/ipcWrappers";
import { isDeckGameMode, showGamePage } from "./utils/steamOS";
import { isValidVencordInstall } from "./utils/vencordLoader"; import { isValidVencordInstall } from "./utils/vencordLoader";
handleSync(IpcEvents.GET_VENCORD_PRELOAD_FILE, () => join(VENCORD_FILES_DIR, "vencordDesktopPreload.js")); handleSync(IpcEvents.GET_VENCORD_PRELOAD_FILE, () => join(VENCORD_FILES_DIR, "vencordDesktopPreload.js"));
@ -47,11 +48,14 @@ handle(IpcEvents.SET_SETTINGS, (_, settings: typeof Settings.store, path?: strin
Settings.setData(settings, path); Settings.setData(settings, path);
}); });
handle(IpcEvents.RELAUNCH, () => { handle(IpcEvents.RELAUNCH, async () => {
const options: RelaunchOptions = { const options: RelaunchOptions = {
args: process.argv.slice(1).concat(["--relaunch"]) args: process.argv.slice(1).concat(["--relaunch"])
}; };
if (app.isPackaged && process.env.APPIMAGE) { if (isDeckGameMode) {
// We can't properly relaunch when running under gamescope, but we can at least navigate to our page in Steam.
await showGamePage();
} else if (app.isPackaged && process.env.APPIMAGE) {
execFile(process.env.APPIMAGE, options.args); execFile(process.env.APPIMAGE, options.args);
} else { } else {
app.relaunch(options); app.relaunch(options);
@ -116,6 +120,13 @@ handle(IpcEvents.SELECT_VENCORD_DIR, async () => {
handle(IpcEvents.SET_BADGE_COUNT, (_, count: number) => setBadgeCount(count)); handle(IpcEvents.SET_BADGE_COUNT, (_, count: number) => setBadgeCount(count));
handle(IpcEvents.CLIPBOARD_COPY_IMAGE, async (_, buf: ArrayBuffer, src: string) => {
clipboard.write({
html: `<img src="${src.replaceAll('"', '\\"')}">`,
image: nativeImage.createFromBuffer(Buffer.from(buf))
});
});
function readCss() { function readCss() {
return readFile(VENCORD_QUICKCSS_FILE, "utf-8").catch(() => ""); return readFile(VENCORD_QUICKCSS_FILE, "utf-8").catch(() => "");
} }

View file

@ -34,7 +34,7 @@ import {
UserAgent, UserAgent,
VENCORD_FILES_DIR VENCORD_FILES_DIR
} from "./constants"; } from "./constants";
import { Settings, VencordSettings } from "./settings"; import { Settings, State, VencordSettings } from "./settings";
import { createSplashWindow } from "./splash"; import { createSplashWindow } from "./splash";
import { makeLinksOpenExternally } from "./utils/makeLinksOpenExternally"; import { makeLinksOpenExternally } from "./utils/makeLinksOpenExternally";
import { applyDeckKeyboardFix, askToApplySteamLayout, isDeckGameMode } from "./utils/steamOS"; import { applyDeckKeyboardFix, askToApplySteamLayout, isDeckGameMode } from "./utils/steamOS";
@ -78,8 +78,7 @@ function initTray(win: BrowserWindow) {
label: "Open", label: "Open",
click() { click() {
win.show(); win.show();
}, }
enabled: false
}, },
{ {
label: "About", label: "About",
@ -122,14 +121,6 @@ function initTray(win: BrowserWindow) {
tray.setToolTip("Vesktop"); tray.setToolTip("Vesktop");
tray.setContextMenu(trayMenu); tray.setContextMenu(trayMenu);
tray.on("click", () => win.show()); tray.on("click", () => win.show());
win.on("show", () => {
trayMenu.items[0].enabled = false;
});
win.on("hide", () => {
trayMenu.items[0].enabled = true;
});
} }
async function clearData(win: BrowserWindow) { async function clearData(win: BrowserWindow) {
@ -210,7 +201,6 @@ function initMenuBar(win: BrowserWindow) {
type: "separator" type: "separator"
}, },
{ {
label: "Hide Vesktop", // Should probably remove the label, but it says "Hide VencordDesktop" instead of "Hide Vesktop"
role: "hide" role: "hide"
}, },
{ {
@ -268,7 +258,7 @@ function getWindowBoundsOptions(): BrowserWindowConstructorOptions {
// We want the default window behaivour to apply in game mode since it expects everything to be fullscreen and maximized. // We want the default window behaivour to apply in game mode since it expects everything to be fullscreen and maximized.
if (isDeckGameMode) return {}; if (isDeckGameMode) return {};
const { x, y, width, height } = Settings.store.windowBounds ?? {}; const { x, y, width, height } = State.store.windowBounds ?? {};
const options = { const options = {
width: width ?? DEFAULT_WIDTH, width: width ?? DEFAULT_WIDTH,
@ -313,8 +303,8 @@ function getDarwinOptions(): BrowserWindowConstructorOptions {
function initWindowBoundsListeners(win: BrowserWindow) { function initWindowBoundsListeners(win: BrowserWindow) {
const saveState = () => { const saveState = () => {
Settings.store.maximized = win.isMaximized(); State.store.maximized = win.isMaximized();
Settings.store.minimized = win.isMinimized(); State.store.minimized = win.isMinimized();
}; };
win.on("maximize", saveState); win.on("maximize", saveState);
@ -322,7 +312,7 @@ function initWindowBoundsListeners(win: BrowserWindow) {
win.on("unmaximize", saveState); win.on("unmaximize", saveState);
const saveBounds = () => { const saveBounds = () => {
Settings.store.windowBounds = win.getBounds(); State.store.windowBounds = win.getBounds();
}; };
win.on("resize", saveBounds); win.on("resize", saveBounds);
@ -375,11 +365,11 @@ function createMainWindow() {
removeSettingsListeners(); removeSettingsListeners();
removeVencordSettingsListeners(); removeVencordSettingsListeners();
const { staticTitle, transparencyOption, enableMenu, discordWindowsTitleBar } = Settings.store; const { staticTitle, transparencyOption, enableMenu, customTitleBar } = Settings.store;
const { frameless } = VencordSettings.store; const { frameless } = VencordSettings.store;
const noFrame = frameless === true || (process.platform === "win32" && discordWindowsTitleBar === true); const noFrame = frameless === true || customTitleBar === true;
const win = (mainWin = new BrowserWindow({ const win = (mainWin = new BrowserWindow({
show: false, show: false,
@ -397,7 +387,12 @@ function createMainWindow() {
...(transparencyOption && ...(transparencyOption &&
transparencyOption !== "none" && { transparencyOption !== "none" && {
backgroundColor: "#00000000", backgroundColor: "#00000000",
backgroundMaterial: transparencyOption, backgroundMaterial: transparencyOption
}),
// Fix transparencyOption for custom discord titlebar
...(customTitleBar &&
transparencyOption &&
transparencyOption !== "none" && {
transparent: true transparent: true
}), }),
...(staticTitle && { title: "Vesktop" }), ...(staticTitle && { title: "Vesktop" }),
@ -443,7 +438,8 @@ function createMainWindow() {
const runVencordMain = once(() => require(join(VENCORD_FILES_DIR, "vencordDesktopMain.js"))); const runVencordMain = once(() => require(join(VENCORD_FILES_DIR, "vencordDesktopMain.js")));
export async function createWindows() { export async function createWindows() {
const splash = createSplashWindow(); const startMinimized = process.argv.includes("--start-minimized");
const splash = createSplashWindow(startMinimized);
// SteamOS letterboxes and scales it terribly, so just full screen it // SteamOS letterboxes and scales it terribly, so just full screen it
if (isDeckGameMode) splash.setFullScreen(true); if (isDeckGameMode) splash.setFullScreen(true);
await ensureVencordFiles(); await ensureVencordFiles();
@ -453,10 +449,10 @@ export async function createWindows() {
mainWin.webContents.on("did-finish-load", () => { mainWin.webContents.on("did-finish-load", () => {
splash.destroy(); splash.destroy();
mainWin!.show();
if (Settings.store.maximized && !isDeckGameMode) { if (!startMinimized) {
mainWin!.maximize(); mainWin!.show();
if (State.store.maximized && !isDeckGameMode) mainWin!.maximize();
} }
if (isDeckGameMode) { if (isDeckGameMode) {
@ -465,6 +461,12 @@ export async function createWindows() {
askToApplySteamLayout(mainWin); askToApplySteamLayout(mainWin);
} }
mainWin.once("show", () => {
if (State.store.maximized && !mainWin!.isMaximized() && !isDeckGameMode) {
mainWin!.maximize();
}
});
}); });
initArRPC(); initArRPC();

View file

@ -4,14 +4,15 @@
* Copyright (c) 2023 Vendicated and Vencord contributors * Copyright (c) 2023 Vendicated and Vencord contributors
*/ */
import { mkdirSync, readFileSync, writeFileSync } from "fs"; import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
import { dirname, join } from "path"; import { dirname, join } from "path";
import type { Settings as TSettings } from "shared/settings"; import type { Settings as TSettings, State as TState } from "shared/settings";
import { SettingsStore } from "shared/utils/SettingsStore"; import { SettingsStore } from "shared/utils/SettingsStore";
import { DATA_DIR, VENCORD_SETTINGS_FILE } from "./constants"; import { DATA_DIR, VENCORD_SETTINGS_FILE } from "./constants";
const SETTINGS_FILE = join(DATA_DIR, "settings.json"); const SETTINGS_FILE = join(DATA_DIR, "settings.json");
const STATE_FILE = join(DATA_DIR, "state.json");
function loadSettings<T extends object = any>(file: string, name: string) { function loadSettings<T extends object = any>(file: string, name: string) {
let settings = {} as T; let settings = {} as T;
@ -20,7 +21,7 @@ function loadSettings<T extends object = any>(file: string, name: string) {
try { try {
settings = JSON.parse(content); settings = JSON.parse(content);
} catch (err) { } catch (err) {
console.error(`Failed to parse ${name} settings.json:`, err); console.error(`Failed to parse ${name}.json:`, err);
} }
} catch {} } catch {}
@ -33,5 +34,31 @@ function loadSettings<T extends object = any>(file: string, name: string) {
return store; return store;
} }
export const Settings = loadSettings<TSettings>(SETTINGS_FILE, "Vesktop"); export const Settings = loadSettings<TSettings>(SETTINGS_FILE, "Vesktop settings");
export const VencordSettings = loadSettings<any>(VENCORD_SETTINGS_FILE, "Vencord"); if (Object.hasOwn(Settings.plain, "discordWindowsTitleBar")) {
Settings.plain.customTitleBar = Settings.plain.discordWindowsTitleBar;
delete Settings.plain.discordWindowsTitleBar;
Settings.markAsChanged();
}
export const VencordSettings = loadSettings<any>(VENCORD_SETTINGS_FILE, "Vencord settings");
if (Object.hasOwn(Settings.plain, "firstLaunch") && !existsSync(STATE_FILE)) {
console.warn("legacy state in settings.json detected. migrating to state.json");
const state = {} as TState;
for (const prop of [
"firstLaunch",
"maximized",
"minimized",
"skippedUpdate",
"steamOSLayoutVersion",
"windowBounds"
] as const) {
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

@ -11,10 +11,11 @@ import { ICON_PATH, VIEW_DIR } from "shared/paths";
import { Settings } from "./settings"; import { Settings } from "./settings";
export function createSplashWindow() { export function createSplashWindow(startMinimized = false) {
const splash = new BrowserWindow({ const splash = new BrowserWindow({
...SplashProps, ...SplashProps,
icon: ICON_PATH icon: ICON_PATH,
show: !startMinimized
}); });
splash.loadFile(join(VIEW_DIR, "splash.html")); splash.loadFile(join(VIEW_DIR, "splash.html"));

View file

@ -5,20 +5,14 @@
*/ */
import { ipcMain, IpcMainEvent, IpcMainInvokeEvent, WebFrameMain } from "electron"; import { ipcMain, IpcMainEvent, IpcMainInvokeEvent, WebFrameMain } from "electron";
import { DISCORD_HOSTNAMES } from "main/constants";
import { IpcEvents } from "shared/IpcEvents"; import { IpcEvents } from "shared/IpcEvents";
export function validateSender(frame: WebFrameMain) { export function validateSender(frame: WebFrameMain) {
const { hostname, protocol } = new URL(frame.url); const { hostname, protocol } = new URL(frame.url);
if (protocol === "file:") return; if (protocol === "file:") return;
switch (hostname) { if (!DISCORD_HOSTNAMES.includes(hostname)) throw new Error("ipc: Disallowed host " + hostname);
case "discord.com":
case "ptb.discord.com":
case "canary.discord.com":
break;
default:
throw new Error("ipc: Disallowed host " + hostname);
}
} }
export function handleSync(event: IpcEvents, cb: (e: IpcMainEvent, ...args: any[]) => any) { export function handleSync(event: IpcEvents, cb: (e: IpcMainEvent, ...args: any[]) => any) {

View file

@ -5,36 +5,67 @@
*/ */
import { BrowserWindow, shell } from "electron"; import { BrowserWindow, shell } from "electron";
import { DISCORD_HOSTNAMES } from "main/constants";
import { Settings } from "../settings"; import { Settings } from "../settings";
import { createOrFocusPopup, setupPopout } from "./popout";
import { execSteamURL, isDeckGameMode, steamOpenURL } from "./steamOS";
export function handleExternalUrl(url: string, protocol?: string): { action: "deny" | "allow" } {
if (protocol == null) {
try {
protocol = new URL(url).protocol;
} catch {
return { action: "deny" };
}
}
switch (protocol) {
case "http:":
case "https:":
if (Settings.store.openLinksWithElectron) {
return { action: "allow" };
}
// eslint-disable-next-line no-fallthrough
case "mailto:":
case "spotify:":
if (isDeckGameMode) {
steamOpenURL(url);
} else {
shell.openExternal(url);
}
break;
case "steam:":
if (isDeckGameMode) {
execSteamURL(url);
} else {
shell.openExternal(url);
}
break;
}
return { action: "deny" };
}
export function makeLinksOpenExternally(win: BrowserWindow) { export function makeLinksOpenExternally(win: BrowserWindow) {
win.webContents.setWindowOpenHandler(({ url }) => { win.webContents.setWindowOpenHandler(({ url, frameName, features }) => {
switch (url) {
case "about:blank":
case "https://discord.com/popout":
return { action: "allow" };
}
try { try {
var { protocol } = new URL(url); var { protocol, hostname, pathname } = new URL(url);
} catch { } catch {
return { action: "deny" }; return { action: "deny" };
} }
switch (protocol) { if (frameName.startsWith("DISCORD_") && pathname === "/popout" && DISCORD_HOSTNAMES.includes(hostname)) {
case "http:": return createOrFocusPopup(frameName, features);
case "https:":
if (Settings.store.openLinksWithElectron) {
return { action: "allow" };
}
// eslint-disable-next-line no-fallthrough
case "mailto:":
case "steam:":
case "spotify:":
shell.openExternal(url);
} }
return { action: "deny" }; if (url === "about:blank" || (frameName === "authorize" && DISCORD_HOSTNAMES.includes(hostname)))
return { action: "allow" };
return handleExternalUrl(url, protocol);
});
win.webContents.on("did-create-window", (win, { frameName }) => {
if (frameName.startsWith("DISCORD_")) setupPopout(win, frameName);
}); });
} }

112
src/main/utils/popout.ts Normal file
View file

@ -0,0 +1,112 @@
/*
* 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 { BrowserWindow, BrowserWindowConstructorOptions } from "electron";
import { handleExternalUrl } from "./makeLinksOpenExternally";
const ALLOWED_FEATURES = new Set([
"width",
"height",
"left",
"top",
"resizable",
"movable",
"alwaysOnTop",
"frame",
"transparent",
"hasShadow",
"closable",
"skipTaskbar",
"backgroundColor",
"menubar",
"toolbar",
"location",
"directories",
"titleBarStyle"
]);
const MIN_POPOUT_WIDTH = 320;
const MIN_POPOUT_HEIGHT = 180;
const DEFAULT_POPOUT_OPTIONS: BrowserWindowConstructorOptions = {
title: "Discord Popout",
backgroundColor: "#2f3136",
minWidth: MIN_POPOUT_WIDTH,
minHeight: MIN_POPOUT_HEIGHT,
frame: process.platform === "linux",
titleBarStyle: process.platform === "darwin" ? "hidden" : undefined,
trafficLightPosition:
process.platform === "darwin"
? {
x: 10,
y: 3
}
: undefined,
webPreferences: {
nodeIntegration: false,
contextIsolation: true
}
};
const PopoutWindows = new Map<string, BrowserWindow>();
function focusWindow(window: BrowserWindow) {
window.setAlwaysOnTop(true);
window.focus();
window.setAlwaysOnTop(false);
}
function parseFeatureValue(feature: string) {
if (feature === "yes") return true;
if (feature === "no") return false;
const n = Number(feature);
if (!isNaN(n)) return n;
return feature;
}
function parseWindowFeatures(features: string) {
const keyValuesParsed = features.split(",");
return keyValuesParsed.reduce((features, feature) => {
const [key, value] = feature.split("=");
if (ALLOWED_FEATURES.has(key)) features[key] = parseFeatureValue(value);
return features;
}, {});
}
export function createOrFocusPopup(key: string, features: string) {
const existingWindow = PopoutWindows.get(key);
if (existingWindow) {
focusWindow(existingWindow);
return <const>{ action: "deny" };
}
return <const>{
action: "allow",
overrideBrowserWindowOptions: {
...DEFAULT_POPOUT_OPTIONS,
...parseWindowFeatures(features)
}
};
}
export function setupPopout(win: BrowserWindow, key: string) {
PopoutWindows.set(key, win);
/* win.webContents.on("will-navigate", (evt, url) => {
// maybe prevent if not origin match
})*/
win.webContents.setWindowOpenHandler(({ url }) => handleExternalUrl(url));
win.once("closed", () => {
win.removeAllListeners();
PopoutWindows.delete(key);
});
}

View file

@ -4,15 +4,12 @@
* Copyright (c) 2023 Vendicated and Vencord contributors * Copyright (c) 2023 Vendicated and Vencord contributors
*/ */
import { exec as callbackExec } from "child_process";
import { BrowserWindow, dialog } from "electron"; import { BrowserWindow, dialog } from "electron";
import { sleep } from "shared/utils/sleep"; import { writeFile } from "fs/promises";
import { promisify } from "util"; import { join } from "path";
import { MessageBoxChoice } from "../constants"; import { MessageBoxChoice } from "../constants";
import { Settings } from "../settings"; import { State } from "../settings";
const exec = promisify(callbackExec);
// Bump this to re-show the prompt // Bump this to re-show the prompt
const layoutVersion = 2; const layoutVersion = 2;
@ -20,6 +17,8 @@ const layoutVersion = 2;
const layoutId = "3080264545"; // Vesktop Layout v2 const layoutId = "3080264545"; // Vesktop Layout v2
const numberRegex = /^[0-9]*$/; const numberRegex = /^[0-9]*$/;
let steamPipeQueue = Promise.resolve();
export const isDeckGameMode = process.env.SteamOS === "1" && process.env.SteamGamepadUI === "1"; export const isDeckGameMode = process.env.SteamOS === "1" && process.env.SteamGamepadUI === "1";
export function applyDeckKeyboardFix() { export function applyDeckKeyboardFix() {
@ -42,23 +41,37 @@ function getAppId(): string | null {
return null; return null;
} }
async function execSteamURL(url: string): Promise<void> { export function execSteamURL(url: string) {
await exec(`steam -ifrunning ${url}`); // This doesn't allow arbitrary execution despite the weird syntax.
steamPipeQueue = steamPipeQueue.then(() =>
writeFile(
join(process.env.HOME || "/home/deck", ".steam", "steam.pipe"),
// replace ' to prevent argument injection
`'${process.env.HOME}/.local/share/Steam/ubuntu12_32/steam' '-ifrunning' '${url.replaceAll("'", "%27")}'\n`,
"utf-8"
)
);
}
export function steamOpenURL(url: string) {
execSteamURL(`steam://openurl/${url}`);
}
export async function showGamePage() {
const appId = getAppId();
if (!appId) return;
await execSteamURL(`steam://nav/games/details/${appId}`);
} }
async function showLayout(appId: string) { async function showLayout(appId: string) {
await execSteamURL(`steam://controllerconfig/${appId}/${layoutId}`); execSteamURL(`steam://controllerconfig/${appId}/${layoutId}`);
// because the UI doesn't consistently reload after the data for the config has loaded...
// HOW HAS NOBODY AT VALVE RUN INTO THIS YET
await sleep(100);
await execSteamURL(`steam://controllerconfig/${appId}/${layoutId}`);
} }
export async function askToApplySteamLayout(win: BrowserWindow) { export async function askToApplySteamLayout(win: BrowserWindow) {
const appId = getAppId(); const appId = getAppId();
if (!appId) return; if (!appId) return;
if (Settings.store.steamOSLayoutVersion === layoutVersion) return; if (State.store.steamOSLayoutVersion === layoutVersion) return;
const update = Boolean(Settings.store.steamOSLayoutVersion); const update = Boolean(State.store.steamOSLayoutVersion);
// Touch screen breaks in some menus when native touch mode is enabled on latest SteamOS beta, remove most of the update specific text once that's fixed. // Touch screen breaks in some menus when native touch mode is enabled on latest SteamOS beta, remove most of the update specific text once that's fixed.
const { response } = await dialog.showMessageBox(win, { const { response } = await dialog.showMessageBox(win, {
@ -74,8 +87,8 @@ ${update ? "Click" : "Tap"} no to keep your current layout.`,
type: "question" type: "question"
}); });
if (Settings.store.steamOSLayoutVersion !== layoutVersion) { if (State.store.steamOSLayoutVersion !== layoutVersion) {
Settings.store.steamOSLayoutVersion = layoutVersion; State.store.steamOSLayoutVersion = layoutVersion;
} }
if (response === MessageBoxChoice.Cancel) return; if (response === MessageBoxChoice.Cancel) return;

View file

@ -51,27 +51,17 @@ ipcMain.handle(IpcEvents.VIRT_MIC_LIST, () => {
: { ok: false, isGlibcxxToOld }; : { ok: false, isGlibcxxToOld };
}); });
ipcMain.handle( ipcMain.handle(IpcEvents.VIRT_MIC_START, (_, targets: string[]) =>
IpcEvents.VIRT_MIC_START, obtainVenmic()?.link({
(_, targets: string[]) => include: targets.map(target => ({ key: "application.name", value: target })),
obtainVenmic()?.link({ exclude: [{ key: "application.process.id", value: getRendererAudioServicePid() }]
props: targets.map(target => ({ key: "application.name", value: target })), })
mode: "include"
})
); );
ipcMain.handle( ipcMain.handle(IpcEvents.VIRT_MIC_START_SYSTEM, () =>
IpcEvents.VIRT_MIC_START_SYSTEM, obtainVenmic()?.link({
() => exclude: [{ key: "application.process.id", value: getRendererAudioServicePid() }]
obtainVenmic()?.link({ })
props: [
{
key: "application.process.id",
value: getRendererAudioServicePid()
}
],
mode: "exclude"
})
); );
ipcMain.handle(IpcEvents.VIRT_MIC_STOP, () => obtainVenmic()?.unlink()); ipcMain.handle(IpcEvents.VIRT_MIC_STOP, () => obtainVenmic()?.unlink());

View file

@ -71,5 +71,9 @@ export const VesktopNative = {
onActivity(cb: (data: string) => void) { onActivity(cb: (data: string) => void) {
ipcRenderer.on(IpcEvents.ARRPC_ACTIVITY, (_, data: string) => cb(data)); ipcRenderer.on(IpcEvents.ARRPC_ACTIVITY, (_, data: string) => cb(data));
} }
},
clipboard: {
copyImage: (imageBuffer: Uint8Array, imageSrc: string) =>
invoke<void>(IpcEvents.CLIPBOARD_COPY_IMAGE, imageBuffer, imageSrc)
} }
}; };

View file

@ -10,7 +10,7 @@ import { Margins } from "@vencord/types/utils";
import { Button, Forms, Select, Switch, Text, Toasts, useState } from "@vencord/types/webpack/common"; import { Button, Forms, Select, Switch, Text, Toasts, useState } from "@vencord/types/webpack/common";
import { setBadge } from "renderer/appBadge"; import { setBadge } from "renderer/appBadge";
import { useSettings } from "renderer/settings"; import { useSettings } from "renderer/settings";
import { isMac, isWindows, isLinux } from "renderer/utils"; import { isMac } from "renderer/utils";
import { isTruthy } from "shared/utils/guards"; import { isTruthy } from "shared/utils/guards";
export default function SettingsUi() { export default function SettingsUi() {
@ -21,10 +21,10 @@ export default function SettingsUi() {
const [autoStartEnabled, setAutoStartEnabled] = useState(autostart.isEnabled()); const [autoStartEnabled, setAutoStartEnabled] = useState(autostart.isEnabled());
const allSwitches: Array<false | [keyof typeof Settings, string, string, boolean?, (() => boolean)?]> = [ const allSwitches: Array<false | [keyof typeof Settings, string, string, boolean?, (() => boolean)?]> = [
isWindows && [ [
"discordWindowsTitleBar", "customTitleBar",
"Discord Titlebar", "Discord Titlebar",
"Use Discord's custom title bar instead of the Windows one. Requires a full restart." "Use Discord's custom title bar instead of the native system one. Requires a full restart."
], ],
!isMac && ["tray", "Tray Icon", "Add a tray icon for Vesktop", true], !isMac && ["tray", "Tray Icon", "Add a tray icon for Vesktop", true],
!isMac && [ !isMac && [
@ -34,7 +34,7 @@ export default function SettingsUi() {
true, true,
() => Settings.tray ?? true () => Settings.tray ?? true
], ],
isLinux && ["middleClickAutoscroll", "Middle Click Autoscroll", "Enables middle-click scrolling (Requires a full restart)", false], !isMac && ["middleClickAutoscroll", "Middle Click Autoscroll", "Enables middle-click scrolling (Requires a full restart)", false],
["arRPC", "Rich Presence", "Enables Rich Presence via arRPC", false], ["arRPC", "Rich Presence", "Enables Rich Presence via arRPC", false],
[ [
"disableMinSize", "disableMinSize",
@ -44,6 +44,7 @@ export default function SettingsUi() {
["staticTitle", "Static Title", 'Makes the window title "Vesktop" instead of changing to the current page'], ["staticTitle", "Static Title", 'Makes the window title "Vesktop" instead of changing to the current page'],
["enableMenu", "Enable Menu Bar", "Enables the application menu bar. Press ALT to toggle visibility."], ["enableMenu", "Enable Menu Bar", "Enables the application menu bar. Press ALT to toggle visibility."],
["disableSmoothScroll", "Disable smooth scrolling", "Disables smooth scrolling in Vesktop", false], ["disableSmoothScroll", "Disable smooth scrolling", "Disables smooth scrolling in Vesktop", false],
["hardwareAcceleration", "Hardware Acceleration", "Enable hardware acceleration", true],
["splashTheming", "Splash theming", "Adapt the splash window colors to your custom theme", false], ["splashTheming", "Splash theming", "Adapt the splash window colors to your custom theme", false],
[ [
"openLinksWithElectron", "openLinksWithElectron",

View file

@ -6,9 +6,7 @@
import "./hideGarbage.css"; import "./hideGarbage.css";
import { waitFor } from "@vencord/types/webpack"; import { isWindows, localStorage } from "./utils";
import { isFirstRun, isWindows, localStorage } from "./utils";
// Make clicking Notifications focus the window // Make clicking Notifications focus the window
const originalSetOnClick = Object.getOwnPropertyDescriptor(Notification.prototype, "onclick")!.set!; const originalSetOnClick = Object.getOwnPropertyDescriptor(Notification.prototype, "onclick")!.set!;
@ -22,15 +20,8 @@ Object.defineProperty(Notification.prototype, "onclick", {
configurable: true configurable: true
}); });
if (isFirstRun) { // Hide "Download Discord Desktop now!!!!" banner
// Hide "Download Discord Desktop now!!!!" banner localStorage.setItem("hideNag", "true");
localStorage.setItem("hideNag", "true");
// Enable Desktop Notifications by default
waitFor("setDesktopType", m => {
m.setDesktopType("all");
});
}
// FIXME: Remove eventually. // FIXME: Remove eventually.
// Originally, Vencord always used a Windows user agent. This seems to cause captchas // Originally, Vencord always used a Windows user agent. This seems to cause captchas

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: '"NotificationSettingsStore',
replacement: {
// FIXME: fix eslint rule
// eslint-disable-next-line no-useless-escape
match: /\.isPlatformEmbedded(?=\?\i\.DesktopNotificationTypes\.ALL)/g,
replace: "$&||true"
}
}
]
});

View file

@ -5,7 +5,8 @@
*/ */
// TODO: Possibly auto generate glob if we have more patches in the future // TODO: Possibly auto generate glob if we have more patches in the future
import "./spellCheck"; import "./enableNotificationsByDefault";
import "./platformClass"; import "./platformClass";
import "./windowsTitleBar";
import "./screenShareAudio"; import "./screenShareAudio";
import "./spellCheck";
import "./windowsTitleBar";

View file

@ -5,7 +5,7 @@
*/ */
import { Settings } from "renderer/settings"; import { Settings } from "renderer/settings";
import { isMac, isWindows } from "renderer/utils"; import { isMac } from "renderer/utils";
import { addPatch } from "./shared"; import { addPatch } from "./shared";
@ -22,8 +22,8 @@ addPatch({
], ],
getPlatformClass() { getPlatformClass() {
if (Settings.store.customTitleBar) return "platform-win";
if (isMac) return "platform-osx"; if (isMac) return "platform-osx";
if (isWindows && Settings.store.discordWindowsTitleBar) return "platform-win";
return "platform-web"; return "platform-web";
} }
}); });

View file

@ -8,7 +8,7 @@ import { Settings } from "renderer/settings";
import { addPatch } from "./shared"; import { addPatch } from "./shared";
if (Settings.store.discordWindowsTitleBar) if (Settings.store.customTitleBar)
addPatch({ addPatch({
patches: [ patches: [
{ {

View file

@ -47,5 +47,7 @@ export const enum IpcEvents {
VIRT_MIC_START_SYSTEM = "VCD_VIRT_MIC_START_ALL", VIRT_MIC_START_SYSTEM = "VCD_VIRT_MIC_START_ALL",
VIRT_MIC_STOP = "VCD_VIRT_MIC_STOP", VIRT_MIC_STOP = "VCD_VIRT_MIC_STOP",
ARRPC_ACTIVITY = "VCD_ARRPC_ACTIVITY" ARRPC_ACTIVITY = "VCD_ARRPC_ACTIVITY",
CLIPBOARD_COPY_IMAGE = "VCD_CLIPBOARD_COPY_IMAGE"
} }

View file

@ -17,22 +17,29 @@ export interface Settings {
staticTitle?: boolean; staticTitle?: boolean;
enableMenu?: boolean; enableMenu?: boolean;
disableSmoothScroll?: boolean; disableSmoothScroll?: boolean;
hardwareAcceleration?: boolean;
arRPC?: boolean; arRPC?: boolean;
appBadge?: boolean; appBadge?: boolean;
discordWindowsTitleBar?: boolean;
maximized?: boolean;
minimized?: boolean;
windowBounds?: Rectangle;
disableMinSize?: boolean; disableMinSize?: boolean;
/** @deprecated use customTitleBar */
discordWindowsTitleBar?: boolean;
customTitleBar?: boolean;
checkUpdates?: boolean; checkUpdates?: boolean;
skippedUpdate?: string;
firstLaunch?: boolean;
splashTheming?: boolean; splashTheming?: boolean;
splashColor?: string; splashColor?: string;
splashBackground?: string; splashBackground?: string;
}
export interface State {
maximized?: boolean;
minimized?: boolean;
windowBounds?: Rectangle;
skippedUpdate?: string;
firstLaunch?: boolean;
steamOSLayoutVersion?: number; steamOSLayoutVersion?: number;
} }

View file

@ -12,8 +12,8 @@ type ResolvePropDeep<T, P> = P extends `${infer Pre}.${infer Suf}`
? ResolvePropDeep<T[Pre], Suf> ? ResolvePropDeep<T[Pre], Suf>
: any : any
: P extends keyof T : P extends keyof T
? T[P] ? T[P]
: any; : any;
/** /**
* The SettingsStore allows you to easily create a mutable store that * The SettingsStore allows you to easily create a mutable store that
@ -144,4 +144,11 @@ export class SettingsStore<T extends object> {
listeners.delete(cb); listeners.delete(cb);
if (!listeners.size) this.pathListeners.delete(path as string); if (!listeners.size) this.pathListeners.delete(path as string);
} }
/**
* Call all global change listeners
*/
public markAsChanged() {
this.globalListeners.forEach(cb => cb(this.plain, ""));
}
} }

View file

@ -5,7 +5,7 @@
*/ */
import { app, BrowserWindow, shell } from "electron"; import { app, BrowserWindow, shell } from "electron";
import { Settings } from "main/settings"; import { Settings, State } from "main/settings";
import { handle } from "main/utils/ipcWrappers"; import { handle } from "main/utils/ipcWrappers";
import { makeLinksOpenExternally } from "main/utils/makeLinksOpenExternally"; import { makeLinksOpenExternally } from "main/utils/makeLinksOpenExternally";
import { githubGet, ReleaseData } from "main/utils/vencordLoader"; import { githubGet, ReleaseData } from "main/utils/vencordLoader";
@ -52,7 +52,7 @@ handle(IpcEvents.UPDATER_DOWNLOAD, () => {
}); });
handle(IpcEvents.UPDATE_IGNORE, () => { handle(IpcEvents.UPDATE_IGNORE, () => {
Settings.store.skippedUpdate = updateData.latestVersion; State.store.skippedUpdate = updateData.latestVersion;
}); });
function isOutdated(oldVersion: string, newVersion: string) { function isOutdated(oldVersion: string, newVersion: string) {
@ -91,7 +91,7 @@ export async function checkUpdates() {
release: data release: data
}; };
if (Settings.store.skippedUpdate !== newVersion && isOutdated(oldVersion, newVersion)) { if (State.store.skippedUpdate !== newVersion && isOutdated(oldVersion, newVersion)) {
openNewUpdateWindow(); openNewUpdateWindow();
} }
} catch (e) { } catch (e) {

View file

@ -2,8 +2,9 @@
<link rel="stylesheet" href="./style.css" type="text/css" /> <link rel="stylesheet" href="./style.css" type="text/css" />
<style> <style>
* { body {
user-select: none; user-select: none;
-webkit-app-region: drag;
} }
.wrapper { .wrapper {
@ -22,8 +23,9 @@
} }
img { img {
width: 6em; width: 128px;
height: 6em; height: 128px;
image-rendering: pixelated;
} }
</style> </style>
</head> </head>