64 Commits

Author SHA1 Message Date
Vendicated
032b94e81d fix ci part 2 2023-12-02 17:52:13 +01:00
Vendicated
4988163744 fix ci 2023-12-02 17:44:52 +01:00
Vendicated
ee7e71b9fb ci: stop using action-electron-builder 2023-12-02 17:41:38 +01:00
Vendicated
7efa54687a bump to v0.4.4 2023-12-02 17:27:20 +01:00
Nico
2917ff25e5 Bump dependencies (#258)
Co-authored-by: V <vendicated@riseup.net>
2023-12-02 17:17:59 +01:00
viacoro
e3839c35b7 unblur shiggy in splash screen (#221) 2023-12-02 17:11:10 +01:00
Vendicated
c39678d733 vesktop.desktop: remove faulty WMClass field 2023-12-02 17:10:33 +01:00
V
06dea79c74 Update README.md 2023-12-02 14:54:54 +01:00
Michal Vaniš
96b0652a06 feat: Add option to disable smooth scrolling (#255) 2023-11-24 22:14:23 +00:00
Noah
49e0411be6 chore(deps): bump venmic (#235)
* feat: update venmic

* chore(deps): bump venmic

* chore(deps): bump venmic

* chore(deps): bump venmic

* fix: update pnpm-lock
2023-11-17 00:53:13 +00:00
AAGaming
94819e6f16 Update steamdeck controller layout (#236) 2023-11-12 01:28:57 +00:00
Noah
a232af06ed feat: update venmic (#230)
* feat: update venmic

* chore(deps): bump venmic
2023-11-10 18:12:36 +01:00
AAGaming
b24535483e proper fix for disabling sandbox on steamos (#206)
Co-authored-by: Vendicated <vendicated@riseup.net>
2023-11-01 01:50:18 +00:00
Vendicated
9521c287b6 remove electron-builder-sandbox-fix 2023-11-01 00:13:28 +01:00
Vendicated
55b9edec39 Bump to v0.4.3 2023-10-31 23:18:24 +01:00
AAGaming
1e9c70eed9 SteamOS: fixes & official controller layout (#194) 2023-10-31 22:14:30 +01:00
Mars
3262e083fa fix positioning of traffic lights on darwin (#185)
Co-authored-by: V <vendicated@riseup.net>
2023-10-30 17:35:24 +00:00
Vendicated
ec1c719553 Bump venmic, improves support for older distros 2023-10-30 18:25:05 +01:00
Vendicated
cc62903b9c bump to v0.4.2 2023-10-27 00:43:02 +02:00
Vendicated
b17370cc7b add arm64 venmic binary 2023-10-27 00:42:50 +02:00
Vendicated
886d02f7c3 screenaudio: show better error if glibcxx too old 2023-10-27 00:27:35 +02:00
Nico
0cad71f6ae fix: adjust hiding download button to new discord update (#176) 2023-10-26 23:23:31 +02:00
Vendicated
b5eac15b42 Use correct OS in U-Agent ~ ~should fix captchas 2023-10-26 22:07:35 +02:00
rini
19c3112d52 fix spellcheck patch (#169) 2023-10-25 20:57:08 +02:00
Vendicated
1c308d0e2c Bump to v0.4.1 2023-10-25 00:32:06 +02:00
Vendicated
7f6db5eeda Linux ScreenAudio: Remove Duplicates 2023-10-25 00:31:53 +02:00
Vendicated
10b38e5b41 make updater window close button close proper window 2023-10-25 00:30:42 +02:00
wearr
28282d1d76 Use local shiggy gif for splash (#157)
Co-authored-by: wearrrrr <contact@wearr.dev>
Co-authored-by: V <vendicated@riseup.net>
2023-10-25 00:25:17 +02:00
MiMillieuh
e1512b72f4 Fix : VM class for AppImage has broken Dekstop integrations (#160) 2023-10-25 00:24:49 +02:00
AAGaming
46a2314173 fix turnary soup in main window options (#164) 2023-10-25 00:24:37 +02:00
Vendicated
e6dc026708 Fix for latest canary 2023-10-24 23:12:22 +02:00
Lewis Crichton
cac307d1fc ci: pin wgr to new version (#163) 2023-10-23 17:44:54 +00:00
Ryan Cao
4b8f374856 feat: allow disabling updates check (#155)
Co-authored-by: V <vendicated@riseup.net>
2023-10-22 17:10:25 +02:00
Jack
e6cc11fc0e Build arm64 Linux packages (#151) 2023-10-22 16:53:58 +02:00
Vendicated
43ca479fc8 bump to v0.4.0 2023-10-21 22:27:50 +02:00
Vendicated
7b0f64a9fc remove legacy vencorddesktop aliases 2023-10-21 22:25:16 +02:00
V
573a953a2f add linux audio screensharing (#130)
Co-authored-by: V <vendicated@riseup.net>
Co-authored-by: kaitlynkitty <87152313+kaitlynkittyy@users.noreply.github.com>
Co-authored-by: Curve <fynnbwdt@gmail.com>
2023-10-21 22:15:55 +02:00
Ryan Cao
841cdcf672 feat: add splash window theming (#52)
Co-authored-by: V <vendicated@riseup.net>
2023-10-14 03:04:44 +00:00
Vendicated
0d93e08e99 arrpc: migrate from websocket + plugin to electron ipc 2023-10-13 04:02:10 +02:00
Tornike Khintibidze
c445c6194f MacOs: properly request microphone and camera permission (#121)
Co-authored-by: V <vendicated@riseup.net>
2023-10-12 04:24:16 +02:00
Nick
e29d293855 README: hyperlink Vencord (#143) 2023-10-11 17:24:39 +02:00
Vendicated
e13a4eacb1 bump deps 2023-10-11 17:21:32 +02:00
Vendicated
c070761f9d Bump electron to v27 2023-10-11 17:19:45 +02:00
Lewis Crichton
ae86c28247 ci: force fork user to be bot account (#141) 2023-10-07 16:19:42 +00:00
Lewis Crichton
9c95956c96 ci: allow manual submissions (#140) 2023-10-07 18:10:07 +02:00
Vendicated
45f56c63a0 bump to v0.3.3 2023-09-30 22:45:22 +02:00
Tornike Khintibidze
89af4316d3 Optimize the menu bar for macOS (#120)
Co-authored-by: V <vendicated@riseup.net>
2023-09-30 20:43:27 +00:00
Cynthia Foxwell
a9bfb857ae Bump Electron to 25.8.4 for CVE-2023-5217 (#134) 2023-09-30 19:38:32 +00:00
Vendicated
b9e411ac90 fix deb/rpm icon 2023-09-28 02:31:42 +02:00
Zyrouge
670de01938 fix: appimage not relaunching (#128) 2023-09-27 04:52:00 +02:00
Lewis Crichton
ef064eba3d ci: switch to vedantmgoyal2009/winget-releaser (#103)
Co-authored-by: V <vendicated@riseup.net>
2023-09-26 00:00:17 +02:00
V
d5f63da939 bump to v0.3.2 2023-09-25 22:11:53 +02:00
Pierre
2aadc61af9 Downgrade electron to v25.8.2 (#126) 2023-09-25 22:09:46 +02:00
Zyrouge
061fec44af fix icon missing in some windows (#124)
Co-authored-by: V <vendicated@riseup.net>
2023-09-25 01:19:54 +00:00
V
b876f450c3 bump arrpc 2023-09-25 03:17:50 +02:00
V
5f5febda9d bump electron for important security fixes 2023-09-25 03:12:51 +02:00
Lewis Crichton
94ba59afb5 fix: add correct WMClass to desktop file (#108)
resolves #96
2023-08-31 22:52:12 +02:00
V
566737017c ci: publish as draft 2023-08-27 22:40:37 +02:00
Lewis Crichton
196ee4e42c ci: winget (#101) 2023-08-27 22:27:23 +02:00
V
9bb02f8581 security: make ipc only allow discord origins 2023-08-25 15:32:06 +02:00
V
c76d7195a5 Bump to 0.3.0 2023-08-16 02:05:17 +02:00
V
50a103710d Add Vesktop section to Settings customSections 2023-08-16 02:02:23 +02:00
DaBluLite
e6e66e775c Fix platformClass: change "platform-windows" to "platform-win" (#94) 2023-08-14 13:55:47 +00:00
V
a5ec031a2f Fix windows titlebar on canary 2023-08-12 04:01:10 +02:00
47 changed files with 1900 additions and 873 deletions

View File

@@ -4,3 +4,5 @@
# all permissions at the defaults (public repos read only, 0 permissions):
# https://github.com/settings/personal-access-tokens/new
GITHUB_TOKEN=
ELECTRON_LAUNCH_FLAGS="--ozone-platform-hint=auto --enable-webrtc-pipewire-capturer --enable-features=WaylandWindowDecorations"

View File

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

25
.github/workflows/winget-submission.yml vendored Normal file
View File

@@ -0,0 +1,25 @@
name: Submit to Winget Community Repo
on:
release:
types: [published]
workflow_dispatch:
inputs:
tag:
type: string
description: The release tag to submit
required: true
jobs:
winget:
name: Publish winget package
runs-on: ubuntu-latest
steps:
- name: Submit package to Winget Community Repo
uses: vedantmgoyal2009/winget-releaser@e68d386d5d6a1cef8cb0fb5e62b77ebcb83e7d58 # v2
with:
identifier: Vencord.Vesktop
token: ${{ secrets.WINGET_PAT }}
installers-regex: '\.exe$'
release-tag: ${{ inputs.tag || github.event.release.tag_name }}
fork-user: shiggybot

3
.gitignore vendored
View File

@@ -1,3 +1,6 @@
dist
node_modules
.env
.DS_Store
.idea/
.pnpm-store/

View File

@@ -1,13 +1,15 @@
# Vesktop
Vesktop is a cross platform desktop app aiming to give you a snappier Discord experience with 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**:
- Global Keybinds
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

View File

@@ -1,6 +1,6 @@
{
"name": "VencordDesktop",
"version": "0.2.9",
"version": "0.4.4",
"private": true,
"description": "",
"keywords": [],
@@ -23,34 +23,37 @@
"watch": "pnpm build --watch"
},
"dependencies": {
"arrpc": "github:OpenAsar/arrpc#b47fd7d498e248955c843a7857dd26478b82190a"
"arrpc": "github:OpenAsar/arrpc#3e22fd776273afaa4a80c51deb86077ffdd4d2ae"
},
"optionalDependencies": {
"@vencord/venmic": "^2.1.3"
},
"devDependencies": {
"@fal-works/esbuild-plugin-global-externals": "^2.1.2",
"@types/node": "^20.4.6",
"@types/react": "^18.2.18",
"@typescript-eslint/eslint-plugin": "^6.2.1",
"@typescript-eslint/parser": "^6.2.1",
"@types/node": "^20.10.0",
"@types/react": "^18.2.39",
"@typescript-eslint/eslint-plugin": "^6.13.1",
"@typescript-eslint/parser": "^6.13.1",
"@vencord/types": "^0.1.2",
"dotenv": "^16.3.1",
"electron": "^25.4.0",
"electron-builder": "^24.6.3",
"esbuild": "^0.18.17",
"eslint": "^8.46.0",
"eslint-config-prettier": "^8.9.0",
"electron": "^27.1.2",
"electron-builder": "^24.9.1",
"esbuild": "^0.19.8",
"eslint": "^8.54.0",
"eslint-config-prettier": "^9.0.0",
"eslint-import-resolver-alias": "^1.1.2",
"eslint-plugin-license-header": "^0.6.0",
"eslint-plugin-path-alias": "^1.0.0",
"eslint-plugin-prettier": "^5.0.0",
"eslint-plugin-prettier": "^5.0.1",
"eslint-plugin-simple-import-sort": "^10.0.0",
"eslint-plugin-unused-imports": "^3.0.0",
"prettier": "^3.0.0",
"prettier": "^3.1.0",
"source-map-support": "^0.5.21",
"tsx": "^3.12.7",
"type-fest": "^4.1.0",
"typescript": "^5.1.6"
"tsx": "^4.6.0",
"type-fest": "^4.8.2",
"typescript": "^5.3.2"
},
"packageManager": "pnpm@8.6.11",
"packageManager": "pnpm@8.11.0",
"engines": {
"node": ">=18",
"pnpm": ">=8"
@@ -65,21 +68,48 @@
"package.json",
"LICENSE"
],
"beforePack": "scripts/build/sandboxFix.js",
"linux": {
"icon": "build/icon.icns",
"category": "Network",
"maintainer": "vendicated+vesktop@riseup.net",
"target": [
"deb",
"tar.gz",
"rpm",
"AppImage"
{
"target": "deb",
"arch": [
"x64",
"arm64"
]
},
{
"target": "tar.gz",
"arch": [
"x64",
"arm64"
]
},
{
"target": "rpm",
"arch": [
"x64",
"arm64"
]
},
{
"target": "AppImage",
"arch": [
"x64",
"arm64"
]
}
],
"desktop": {
"Name": "Vesktop",
"GenericName": "Internet Messenger",
"Type": "Application",
"Categories": "Network;InstantMessaging;Chat;",
"Keywords": "discord;vencord;electron;chat;"
"Keywords": "discord;vencord;electron;chat;",
"StartupWMClass": "VencordDesktop"
}
},
"mac": {
@@ -92,7 +122,13 @@
]
}
],
"category": "Network"
"category": "Network",
"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
}
},
"nsis": {
"include": "build/installer.nsh",
@@ -105,8 +141,7 @@
]
},
"publish": {
"provider": "github",
"releaseType": "release"
"provider": "github"
}
}
}

1611
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@@ -5,6 +5,7 @@
*/
import { BuildContext, BuildOptions, context } from "esbuild";
import { copyFile } from "fs/promises";
import vencordDep from "./vencordDep.mjs";
@@ -33,7 +34,23 @@ async function createContext(options: BuildOptions) {
contexts.push(await context(options));
}
async function copyVenmic() {
if (process.platform !== "linux") return;
return Promise.all([
copyFile(
"./node_modules/@vencord/venmic/prebuilds/venmic-addon-linux-x64/node-napi-v7.node",
"./static/dist/venmic-x64.node"
),
copyFile(
"./node_modules/@vencord/venmic/prebuilds/venmic-addon-linux-arm64/node-napi-v7.node",
"./static/dist/venmic-arm64.node"
)
]).catch(() => console.warn("Failed to copy venmic. Building without venmic support"));
}
await Promise.all([
copyVenmic(),
createContext({
...NodeCommonOpts,
entryPoints: ["src/main/index.ts"],
@@ -61,15 +78,9 @@ await Promise.all([
inject: ["./scripts/build/injectReact.mjs"],
jsxFactory: "VencordCreateElement",
jsxFragment: "VencordFragment",
// Work around https://github.com/evanw/esbuild/issues/2460
tsconfig: "./scripts/build/tsconfig.esbuild.json",
external: ["@vencord/types/*"],
plugins: [vencordDep],
// TODO: remove legacy name once main Vencord codebase has migrated and some time has passed.
// this 0 is very important. we run this script via webFrame.executeJavaScript and the last
// expression will be the return value. Without the 0, the return value would be Vesktop which
// leads to "An object could not be cloned"
footer: { js: ";window.VencordDesktop=Vesktop;0 \n//# sourceURL=VCDRenderer" }
footer: { js: "//# sourceURL=VCDRenderer" }
})
]);

View File

@@ -0,0 +1,74 @@
/*
* 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
*/
// Based on https://github.com/gergof/electron-builder-sandbox-fix/blob/master/lib/index.js
const fs = require("fs/promises");
const path = require("path");
let isApplied = false;
const hook = async () => {
if (isApplied) return;
isApplied = true;
if (process.platform !== "linux") {
// this fix is only required on linux
return;
}
const AppImageTarget = require("app-builder-lib/out/targets/AppImageTarget");
const oldBuildMethod = AppImageTarget.default.prototype.build;
AppImageTarget.default.prototype.build = async function (...args) {
console.log("Running AppImage builder hook", args);
const oldPath = args[0];
const newPath = oldPath + "-appimage-sandbox-fix";
// just in case
try {
await fs.rm(newPath, {
recursive: true
});
} catch {}
console.log("Copying to apply appimage fix", oldPath, newPath);
await fs.cp(oldPath, newPath, {
recursive: true
});
args[0] = newPath;
const executable = path.join(newPath, this.packager.executableName);
const loaderScript = `
#!/usr/bin/env bash
SCRIPT_DIR="$( cd "$( dirname "\${BASH_SOURCE[0]}" )" && pwd )"
IS_STEAMOS=0
if [[ "$SteamOS" == "1" && "$SteamGamepadUI" == "1" ]]; then
echo "Running Vesktop on SteamOS, disabling sandbox"
IS_STEAMOS=1
fi
exec "$SCRIPT_DIR/${this.packager.executableName}.bin" "$([ "$IS_STEAMOS" == 1 ] && echo '--no-sandbox')" "$@"
`.trim();
try {
await fs.rename(executable, executable + ".bin");
await fs.writeFile(executable, loaderScript);
await fs.chmod(executable, 0o755);
} catch (e) {
console.error("failed to create loder for sandbox fix: " + e.message);
throw new Error("Failed to create loader for sandbox fix");
}
const ret = await oldBuildMethod.apply(this, args);
await fs.rm(newPath, {
recursive: true
});
return ret;
};
};
module.exports = hook;

View File

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

View File

@@ -8,4 +8,4 @@ import "./utils/dotenv";
import { spawnNodeModuleBin } from "./utils/spawn.mjs";
spawnNodeModuleBin("electron", ["."]);
spawnNodeModuleBin("electron", [".", ...(process.env.ELECTRON_LAUNCH_FLAGS?.split(" ") ?? [])]);

View File

@@ -5,6 +5,7 @@
*/
import Server from "arrpc";
import { IpcEvents } from "shared/IpcEvents";
import { mainWin } from "./mainWindow";
import { Settings } from "./settings";
@@ -17,11 +18,8 @@ export async function initArRPC() {
if (server || !Settings.store.arRPC) return;
try {
// This module starts a server as a side effect, so it needs to be lazy imported
const { send: sendToBridge } = await import("arrpc/src/bridge");
server = await new Server();
server.on("activity", sendToBridge);
server.on("activity", (data: any) => mainWin.webContents.send(IpcEvents.ARRPC_ACTIVITY, JSON.stringify(data)));
server.on("invite", (invite: string, callback: (valid: boolean) => void) => {
invite = String(invite);
if (!inviteCodeRegex.test(invite)) return callback(false);

View File

@@ -25,3 +25,17 @@ export const MIN_WIDTH = 940;
export const MIN_HEIGHT = 500;
export const DEFAULT_WIDTH = 1280;
export const DEFAULT_HEIGHT = 720;
const UserAgents = {
darwin: "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/118.0.0.0 Safari/537.36",
linux: "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/118.0.0.0 Safari/537.36",
windows:
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/118.0.0.0 Safari/537.36"
};
export const UserAgent = UserAgents[process.platform] || UserAgents.windows;
export const enum MessageBoxChoice {
Default,
Cancel
}

View File

@@ -9,7 +9,7 @@ import { BrowserWindow } from "electron/main";
import { copyFileSync, mkdirSync, readdirSync } from "fs";
import { join } from "path";
import { SplashProps } from "shared/browserWinProperties";
import { VIEW_DIR } from "shared/paths";
import { ICON_PATH, VIEW_DIR } from "shared/paths";
import { autoStart } from "./autoStart";
import { DATA_DIR } from "./constants";
@@ -31,7 +31,8 @@ export function createFirstLaunchTour() {
frame: true,
autoHideMenuBar: true,
height: 470,
width: 550
width: 550,
icon: ICON_PATH
});
makeLinksOpenExternally(win);

View File

@@ -12,6 +12,7 @@ import { checkUpdates } from "updater/main";
import { DATA_DIR } from "./constants";
import { createFirstLaunchTour } from "./firstLaunch";
import { createWindows, mainWin } from "./mainWindow";
import { registerMediaPermissionsHandler } from "./mediaPermissions";
import { registerScreenShareHandler } from "./screenShare";
import { Settings } from "./settings";
@@ -23,6 +24,12 @@ if (IS_DEV) {
process.env.VENCORD_USER_DATA_DIR = DATA_DIR;
function init() {
const { disableSmoothScroll } = Settings.store;
if (disableSmoothScroll) {
app.commandLine.appendSwitch("disable-smooth-scrolling");
}
// work around chrome 66 disabling autoplay by default
app.commandLine.appendSwitch("autoplay-policy", "no-user-gesture-required");
@@ -49,6 +56,8 @@ function init() {
if (process.platform === "win32") app.setAppUserModelId("dev.vencord.desktop");
registerScreenShareHandler();
registerMediaPermissionsHandler();
bootstrap();
app.on("activate", () => {

View File

@@ -4,7 +4,10 @@
* Copyright (c) 2023 Vendicated and Vencord contributors
*/
import { app, dialog, ipcMain, session, shell } from "electron";
if (process.platform === "linux") import("./virtmic");
import { execFile } from "child_process";
import { app, BrowserWindow, dialog, RelaunchOptions, session, shell } from "electron";
import { mkdirSync, readFileSync, watch } from "fs";
import { open, readFile } from "fs/promises";
import { release } from "os";
@@ -17,71 +20,65 @@ import { autoStart } from "./autoStart";
import { VENCORD_FILES_DIR, VENCORD_QUICKCSS_FILE, VENCORD_THEMES_DIR } from "./constants";
import { mainWin } from "./mainWindow";
import { Settings } from "./settings";
import { handle, handleSync } from "./utils/ipcWrappers";
import { isValidVencordInstall } from "./utils/vencordLoader";
ipcMain.on(IpcEvents.GET_VENCORD_PRELOAD_FILE, e => {
e.returnValue = join(VENCORD_FILES_DIR, "vencordDesktopPreload.js");
});
handleSync(IpcEvents.GET_VENCORD_PRELOAD_FILE, () => join(VENCORD_FILES_DIR, "vencordDesktopPreload.js"));
handleSync(IpcEvents.GET_VENCORD_RENDERER_SCRIPT, () =>
readFileSync(join(VENCORD_FILES_DIR, "vencordDesktopRenderer.js"), "utf-8")
);
ipcMain.on(IpcEvents.GET_VENCORD_RENDERER_SCRIPT, e => {
e.returnValue = readFileSync(join(VENCORD_FILES_DIR, "vencordDesktopRenderer.js"), "utf-8");
});
handleSync(IpcEvents.GET_RENDERER_SCRIPT, () => readFileSync(join(__dirname, "renderer.js"), "utf-8"));
handleSync(IpcEvents.GET_RENDERER_CSS_FILE, () => join(__dirname, "renderer.css"));
ipcMain.on(IpcEvents.GET_RENDERER_SCRIPT, e => {
e.returnValue = readFileSync(join(__dirname, "renderer.js"), "utf-8");
});
handleSync(IpcEvents.GET_SETTINGS, () => Settings.plain);
handleSync(IpcEvents.GET_VERSION, () => app.getVersion());
ipcMain.on(IpcEvents.GET_RENDERER_CSS_FILE, e => {
e.returnValue = join(__dirname, "renderer.css");
});
handleSync(
IpcEvents.SUPPORTS_WINDOWS_TRANSPARENCY,
() => process.platform === "win32" && Number(release().split(".").pop()) >= 22621
);
ipcMain.on(IpcEvents.GET_SETTINGS, e => {
e.returnValue = Settings.plain;
});
handleSync(IpcEvents.AUTOSTART_ENABLED, () => autoStart.isEnabled());
handle(IpcEvents.ENABLE_AUTOSTART, autoStart.enable);
handle(IpcEvents.DISABLE_AUTOSTART, autoStart.disable);
ipcMain.on(IpcEvents.GET_VERSION, e => {
e.returnValue = app.getVersion();
});
ipcMain.on(IpcEvents.SUPPORTS_WINDOWS_TRANSPARENCY, e => {
e.returnValue = process.platform === "win32" && Number(release().split(".").pop()) >= 22621;
});
ipcMain.on(IpcEvents.AUTOSTART_ENABLED, e => {
e.returnValue = autoStart.isEnabled();
});
ipcMain.handle(IpcEvents.ENABLE_AUTOSTART, autoStart.enable);
ipcMain.handle(IpcEvents.DISABLE_AUTOSTART, autoStart.disable);
ipcMain.handle(IpcEvents.SET_SETTINGS, (_, settings: typeof Settings.store, path?: string) => {
handle(IpcEvents.SET_SETTINGS, (_, settings: typeof Settings.store, path?: string) => {
Settings.setData(settings, path);
});
ipcMain.handle(IpcEvents.RELAUNCH, () => {
app.relaunch();
handle(IpcEvents.RELAUNCH, () => {
const options: RelaunchOptions = {
args: process.argv.slice(1).concat(["--relaunch"])
};
if (app.isPackaged && process.env.APPIMAGE) {
execFile(process.env.APPIMAGE, options.args);
} else {
app.relaunch(options);
}
app.exit();
});
ipcMain.handle(IpcEvents.SHOW_ITEM_IN_FOLDER, (_, path) => {
handle(IpcEvents.SHOW_ITEM_IN_FOLDER, (_, path) => {
shell.showItemInFolder(path);
});
ipcMain.handle(IpcEvents.FOCUS, () => {
handle(IpcEvents.FOCUS, () => {
if (process.platform === "win32") mainWin.minimize(); // Windows is weird
mainWin.restore();
mainWin.show();
});
ipcMain.handle(IpcEvents.CLOSE, e => {
mainWin.close();
handle(IpcEvents.CLOSE, e => {
(BrowserWindow.fromWebContents(e.sender) ?? e.sender).close();
});
ipcMain.handle(IpcEvents.MINIMIZE, e => {
handle(IpcEvents.MINIMIZE, e => {
mainWin.minimize();
});
ipcMain.handle(IpcEvents.MAXIMIZE, e => {
handle(IpcEvents.MAXIMIZE, e => {
if (mainWin.isMaximized()) {
mainWin.unmaximize();
} else {
@@ -89,7 +86,7 @@ ipcMain.handle(IpcEvents.MAXIMIZE, e => {
}
});
ipcMain.handle(IpcEvents.SPELLCHECK_SET_LANGUAGES, (_, languages: string[]) => {
handle(IpcEvents.SPELLCHECK_SET_LANGUAGES, (_, languages: string[]) => {
const ses = session.defaultSession;
const available = ses.availableSpellCheckerLanguages;
@@ -97,15 +94,15 @@ ipcMain.handle(IpcEvents.SPELLCHECK_SET_LANGUAGES, (_, languages: string[]) => {
if (applicable.length) ses.setSpellCheckerLanguages(applicable);
});
ipcMain.handle(IpcEvents.SPELLCHECK_REPLACE_MISSPELLING, (e, word: string) => {
handle(IpcEvents.SPELLCHECK_REPLACE_MISSPELLING, (e, word: string) => {
e.sender.replaceMisspelling(word);
});
ipcMain.handle(IpcEvents.SPELLCHECK_ADD_TO_DICTIONARY, (e, word: string) => {
handle(IpcEvents.SPELLCHECK_ADD_TO_DICTIONARY, (e, word: string) => {
e.sender.session.addWordToSpellCheckerDictionary(word);
});
ipcMain.handle(IpcEvents.SELECT_VENCORD_DIR, async () => {
handle(IpcEvents.SELECT_VENCORD_DIR, async () => {
const res = await dialog.showOpenDialog(mainWin!, {
properties: ["openDirectory"]
});
@@ -117,7 +114,7 @@ ipcMain.handle(IpcEvents.SELECT_VENCORD_DIR, async () => {
return dir;
});
ipcMain.handle(IpcEvents.SET_BADGE_COUNT, (_, count: number) => setBadgeCount(count));
handle(IpcEvents.SET_BADGE_COUNT, (_, count: number) => setBadgeCount(count));
function readCss() {
return readFile(VENCORD_QUICKCSS_FILE, "utf-8").catch(() => "");

View File

@@ -11,6 +11,7 @@ import {
dialog,
Menu,
MenuItemConstructorOptions,
nativeTheme,
Tray
} from "electron";
import { rm } from "fs/promises";
@@ -23,15 +24,27 @@ import type { SettingsStore } from "shared/utils/SettingsStore";
import { ICON_PATH } from "../shared/paths";
import { createAboutWindow } from "./about";
import { initArRPC } from "./arrpc";
import { DATA_DIR, DEFAULT_HEIGHT, DEFAULT_WIDTH, MIN_HEIGHT, MIN_WIDTH, VENCORD_FILES_DIR } from "./constants";
import {
DATA_DIR,
DEFAULT_HEIGHT,
DEFAULT_WIDTH,
MessageBoxChoice,
MIN_HEIGHT,
MIN_WIDTH,
UserAgent,
VENCORD_FILES_DIR
} from "./constants";
import { Settings, VencordSettings } from "./settings";
import { createSplashWindow } from "./splash";
import { makeLinksOpenExternally } from "./utils/makeLinksOpenExternally";
import { applyDeckKeyboardFix, askToApplySteamLayout, isDeckGameMode } from "./utils/steamOS";
import { downloadVencordFiles, ensureVencordFiles } from "./utils/vencordLoader";
let isQuitting = false;
let tray: Tray;
applyDeckKeyboardFix();
app.on("before-quit", () => {
isQuitting = true;
});
@@ -119,11 +132,6 @@ function initTray(win: BrowserWindow) {
});
}
const enum MessageBoxChoice {
Default,
Cancel
}
async function clearData(win: BrowserWindow) {
const { response } = await dialog.showMessageBox(win, {
message: "Are you sure you want to reset Vesktop?",
@@ -147,6 +155,8 @@ async function clearData(win: BrowserWindow) {
app.quit();
}
type MenuItemList = Array<MenuItemConstructorOptions | false>;
function initMenuBar(win: BrowserWindow) {
const isWindows = process.platform === "win32";
const isDarwin = process.platform === "darwin";
@@ -181,14 +191,38 @@ function initMenuBar(win: BrowserWindow) {
app.quit();
}
},
isDarwin && {
label: "Hide",
role: "hide"
},
isDarwin && {
label: "Hide others",
role: "hideOthers"
},
...(!isDarwin
? []
: ([
{
type: "separator"
},
{
label: "Settings",
accelerator: "CmdOrCtrl+,",
async click() {
mainWin.webContents.executeJavaScript(
"Vencord.Webpack.Common.SettingsRouter.open('My Account')"
);
}
},
{
type: "separator"
},
{
label: "Hide Vesktop", // Should probably remove the label, but it says "Hide VencordDesktop" instead of "Hide Vesktop"
role: "hide"
},
{
role: "hideOthers"
},
{
role: "unhide"
},
{
type: "separator"
}
] satisfies MenuItemList)),
{
label: "Quit",
accelerator: wantCtrlQ ? "CmdOrCtrl+Q" : void 0,
@@ -213,7 +247,7 @@ function initMenuBar(win: BrowserWindow) {
role: "zoomIn",
visible: false
}
] satisfies Array<MenuItemConstructorOptions | false>;
] satisfies MenuItemList;
const menu = Menu.buildFromTemplate([
{
@@ -231,6 +265,9 @@ function initMenuBar(win: BrowserWindow) {
}
function getWindowBoundsOptions(): BrowserWindowConstructorOptions {
// We want the default window behaivour to apply in game mode since it expects everything to be fullscreen and maximized.
if (isDeckGameMode) return {};
const { x, y, width, height } = Settings.store.windowBounds ?? {};
const options = {
@@ -251,6 +288,29 @@ function getWindowBoundsOptions(): BrowserWindowConstructorOptions {
return options;
}
function getDarwinOptions(): BrowserWindowConstructorOptions {
const options = {
titleBarStyle: "hidden",
trafficLightPosition: { x: 10, y: 10 }
} as BrowserWindowConstructorOptions;
const { splashTheming, splashBackground } = Settings.store;
const { macosTranslucency } = VencordSettings.store;
if (macosTranslucency) {
options.vibrancy = "sidebar";
options.backgroundColor = "#ffffff00";
} else {
if (splashTheming) {
options.backgroundColor = splashBackground;
} else {
options.backgroundColor = nativeTheme.shouldUseDarkColors ? "#313338" : "#ffffff";
}
}
return options;
}
function initWindowBoundsListeners(win: BrowserWindow) {
const saveState = () => {
Settings.store.maximized = win.isMaximized();
@@ -316,7 +376,8 @@ function createMainWindow() {
removeVencordSettingsListeners();
const { staticTitle, transparencyOption, enableMenu, discordWindowsTitleBar } = Settings.store;
const { frameless, macosTranslucency } = VencordSettings.store;
const { frameless } = VencordSettings.store;
const noFrame = frameless === true || (process.platform === "win32" && discordWindowsTitleBar === true);
@@ -332,28 +393,21 @@ function createMainWindow() {
},
icon: ICON_PATH,
frame: !noFrame,
...(transparencyOption && transparencyOption !== "none"
? {
backgroundColor: "#00000000",
backgroundMaterial: Settings.store.transparencyOption,
transparent: true
}
: {}),
...(staticTitle ? { title: "Vesktop" } : {}),
...(macosTranslucency
? {
vibrancy: "sidebar",
backgroundColor: "#ffffff00"
}
: {}),
...(process.platform === "darwin" ? { titleBarStyle: "hiddenInset" } : {}),
...(transparencyOption &&
transparencyOption !== "none" && {
backgroundColor: "#00000000",
backgroundMaterial: transparencyOption,
transparent: true
}),
...(staticTitle && { title: "Vesktop" }),
...(process.platform === "darwin" && getDarwinOptions()),
...getWindowBoundsOptions(),
autoHideMenuBar: enableMenu
}));
win.setMenuBarVisibility(false);
win.on("close", e => {
const useTray = Settings.store.minimizeToTray !== false && Settings.store.tray !== false;
const useTray = !isDeckGameMode && Settings.store.minimizeToTray !== false && Settings.store.tray !== false;
if (isQuitting || (process.platform !== "darwin" && !useTray)) return;
e.preventDefault();
@@ -367,15 +421,13 @@ function createMainWindow() {
if (Settings.store.staticTitle) win.on("page-title-updated", e => e.preventDefault());
initWindowBoundsListeners(win);
if ((Settings.store.tray ?? true) && process.platform !== "darwin") initTray(win);
if (!isDeckGameMode && (Settings.store.tray ?? true) && process.platform !== "darwin") initTray(win);
initMenuBar(win);
makeLinksOpenExternally(win);
initSettingsListeners(win);
initSpellCheck(win);
win.webContents.setUserAgent(
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Safari/537.36"
);
win.webContents.setUserAgent(UserAgent);
const subdomain =
Settings.store.discordBranch === "canary" || Settings.store.discordBranch === "ptb"
@@ -391,19 +443,27 @@ const runVencordMain = once(() => require(join(VENCORD_FILES_DIR, "vencordDeskto
export async function createWindows() {
const splash = createSplashWindow();
// SteamOS letterboxes and scales it terribly, so just full screen it
if (isDeckGameMode) splash.setFullScreen(true);
await ensureVencordFiles();
runVencordMain();
mainWin = createMainWindow();
mainWin.once("ready-to-show", () => {
mainWin.webContents.on("did-finish-load", () => {
splash.destroy();
mainWin!.show();
if (Settings.store.maximized) {
if (Settings.store.maximized && !isDeckGameMode) {
mainWin!.maximize();
}
if (isDeckGameMode) {
// always use entire display
mainWin!.setFullScreen(true);
askToApplySteamLayout(mainWin);
}
});
initArRPC();

View File

@@ -0,0 +1,24 @@
/*
* 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 { session, systemPreferences } from "electron";
export function registerMediaPermissionsHandler() {
if (process.platform !== "darwin") return;
session.defaultSession.setPermissionRequestHandler(async (_webContents, permission, callback, details) => {
let granted = true;
if (details.mediaTypes?.includes("audio")) {
granted = await systemPreferences.askForMediaAccess("microphone");
}
if (details.mediaTypes?.includes("video")) {
granted &&= await systemPreferences.askForMediaAccess("camera");
}
callback(granted);
});
}

View File

@@ -4,12 +4,17 @@
* Copyright (c) 2023 Vendicated and Vencord contributors
*/
import { desktopCapturer, ipcMain, session, Streams } from "electron";
import { desktopCapturer, session, Streams } from "electron";
import type { StreamPick } from "renderer/components/ScreenSharePicker";
import { IpcEvents } from "shared/IpcEvents";
import { handle } from "./utils/ipcWrappers";
const isWayland =
process.platform === "linux" && (process.env.XDG_SESSION_TYPE === "wayland" || !!process.env.WAYLAND_DISPLAY);
export function registerScreenShareHandler() {
ipcMain.handle(IpcEvents.CAPTURER_GET_LARGE_THUMBNAIL, async (_, id: string) => {
handle(IpcEvents.CAPTURER_GET_LARGE_THUMBNAIL, async (_, id: string) => {
const sources = await desktopCapturer.getSources({
types: ["window", "screen"],
thumbnailSize: {
@@ -21,17 +26,19 @@ export function registerScreenShareHandler() {
});
session.defaultSession.setDisplayMediaRequestHandler(async (request, callback) => {
const sources = await desktopCapturer.getSources({
types: ["window", "screen"],
thumbnailSize: {
width: 176,
height: 99
}
});
// request full resolution on wayland right away because we always only end up with one result anyway
const width = isWayland ? 1920 : 176;
const sources = await desktopCapturer
.getSources({
types: ["window", "screen"],
thumbnailSize: {
width,
height: width * (9 / 16)
}
})
.catch(err => console.error("Error during screenshare picker", err));
const isWayland =
process.platform === "linux" &&
(process.env.XDG_SESSION_TYPE === "wayland" || !!process.env.WAYLAND_DISPLAY);
if (!sources) return callback({});
const data = sources.map(({ id, name, thumbnail }) => ({
id,
@@ -41,14 +48,26 @@ export function registerScreenShareHandler() {
if (isWayland) {
const video = data[0];
callback(video ? { video } : {});
if (video) {
const stream = await request.frame
.executeJavaScript(
`Vesktop.Components.ScreenShare.openScreenSharePicker(${JSON.stringify([video])},true)`
)
.catch(() => null);
if (stream === null) return callback({});
}
callback(video ? { video: sources[0] } : {});
return;
}
const choice = await request.frame
.executeJavaScript(`Vesktop.Components.ScreenShare.openScreenSharePicker(${JSON.stringify(data)})`)
.then(e => e as StreamPick)
.catch(() => null);
.catch(e => {
console.error("Error during screenshare picker", e);
return null;
});
if (!choice) return callback({});

View File

@@ -7,12 +7,32 @@
import { BrowserWindow } from "electron";
import { join } from "path";
import { SplashProps } from "shared/browserWinProperties";
import { VIEW_DIR } from "shared/paths";
import { ICON_PATH, VIEW_DIR } from "shared/paths";
import { Settings } from "./settings";
export function createSplashWindow() {
const splash = new BrowserWindow(SplashProps);
const splash = new BrowserWindow({
...SplashProps,
icon: ICON_PATH
});
splash.loadFile(join(VIEW_DIR, "splash.html"));
const { splashBackground, splashColor, splashTheming } = Settings.store;
if (splashTheming) {
if (splashColor) {
const semiTransparentSplashColor = splashColor.replace("rgb(", "rgba(").replace(")", ", 0.2)");
splash.webContents.insertCSS(`body { --fg: ${splashColor} !important }`);
splash.webContents.insertCSS(`body { --fg-semi-trans: ${semiTransparentSplashColor} !important }`);
}
if (splashBackground) {
splash.webContents.insertCSS(`body { --bg: ${splashBackground} !important }`);
}
}
return splash;
}

View File

@@ -0,0 +1,36 @@
/*
* SPDX-License-Identifier: GPL-3.0
* Vesktop, a desktop app aiming to give you a snappier Discord Experience
* Copyright (c) 2023 Vendicated and Vencord contributors
*/
import { ipcMain, IpcMainEvent, IpcMainInvokeEvent, WebFrameMain } from "electron";
import { IpcEvents } from "shared/IpcEvents";
export function validateSender(frame: WebFrameMain) {
const { hostname, protocol } = new URL(frame.url);
if (protocol === "file:") return;
switch (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) {
ipcMain.on(event, (e, ...args) => {
validateSender(e.senderFrame);
e.returnValue = cb(e, ...args);
});
}
export function handle(event: IpcEvents, cb: (e: IpcMainInvokeEvent, ...args: any[]) => any) {
ipcMain.handle(event, (e, ...args) => {
validateSender(e.senderFrame);
return cb(e, ...args);
});
}

84
src/main/utils/steamOS.ts Normal file
View File

@@ -0,0 +1,84 @@
/*
* 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 { exec as callbackExec } from "child_process";
import { BrowserWindow, dialog } from "electron";
import { sleep } from "shared/utils/sleep";
import { promisify } from "util";
import { MessageBoxChoice } from "../constants";
import { Settings } from "../settings";
const exec = promisify(callbackExec);
// Bump this to re-show the prompt
const layoutVersion = 2;
// Get this from "show details" on the profile after exporting as a shared personal layout or using share with community
const layoutId = "3080264545"; // Vesktop Layout v2
const numberRegex = /^[0-9]*$/;
export const isDeckGameMode = process.env.SteamOS === "1" && process.env.SteamGamepadUI === "1";
export function applyDeckKeyboardFix() {
if (!isDeckGameMode) return;
// Prevent constant virtual keyboard spam that eventually crashes Steam.
process.env.GTK_IM_MODULE = "None";
}
// For some reason SteamAppId is always 0 for non-steam apps so we do this insanity instead.
function getAppId(): string | null {
// /home/deck/.local/share/Steam/steamapps/shadercache/APPID/fozmediav1
const path = process.env.STEAM_COMPAT_MEDIA_PATH;
if (!path) return null;
const pathElems = path?.split("/");
const appId = pathElems[pathElems.length - 2];
if (appId.match(numberRegex)) {
console.log(`Got Steam App ID ${appId}`);
return appId;
}
return null;
}
async function execSteamURL(url: string): Promise<void> {
await exec(`steam -ifrunning ${url}`);
}
async function showLayout(appId: string) {
await 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) {
const appId = getAppId();
if (!appId) return;
if (Settings.store.steamOSLayoutVersion === layoutVersion) return;
const update = Boolean(Settings.store.steamOSLayoutVersion);
// Touch screen breaks in some menus when native touch mode is enabled on latest SteamOS beta, remove most of the update specific text once that's fixed.
const { response } = await dialog.showMessageBox(win, {
message: `${update ? "Update" : "Apply"} Vesktop Steam Input Layout?`,
detail: `Would you like to ${update ? "Update" : "Apply"} Vesktop's recommended Steam Deck controller settings?
${update ? "Click yes using the touchpad" : "Tap yes"}, then press the X button or tap Apply Layout to confirm.${
update ? " Doing so will undo any customizations you have made." : ""
}
${update ? "Click" : "Tap"} no to keep your current layout.`,
buttons: ["Yes", "No"],
cancelId: MessageBoxChoice.Cancel,
defaultId: MessageBoxChoice.Default,
type: "question"
});
if (Settings.store.steamOSLayoutVersion !== layoutVersion) {
Settings.store.steamOSLayoutVersion = layoutVersion;
}
if (response === MessageBoxChoice.Cancel) return;
await showLayout(appId);
}

77
src/main/virtmic.ts Normal file
View File

@@ -0,0 +1,77 @@
/*
* SPDX-License-Identifier: GPL-3.0
* Vesktop, a desktop app aiming to give you a snappier Discord Experience
* Copyright (c) 2023 Vendicated and Vencord contributors
*/
import { app, ipcMain } from "electron";
import { join } from "path";
import { IpcEvents } from "shared/IpcEvents";
import { STATIC_DIR } from "shared/paths";
let initialized = false;
let patchBay: import("@vencord/venmic").PatchBay | undefined;
let isGlibcxxToOld = false;
function getRendererAudioServicePid() {
return (
app
.getAppMetrics()
.find(proc => proc.name === "Audio Service")
?.pid?.toString() ?? "owo"
);
}
function obtainVenmic() {
if (!initialized) {
initialized = true;
try {
const { PatchBay } = require(
join(STATIC_DIR, `dist/venmic-${process.arch}.node`)
) as typeof import("@vencord/venmic");
patchBay = new PatchBay();
} catch (e: any) {
console.error("Failed to initialise venmic. Make sure you're using pipewire", e);
isGlibcxxToOld = (e?.stack || e?.message || "").toLowerCase().includes("glibc");
}
}
return patchBay;
}
ipcMain.handle(IpcEvents.VIRT_MIC_LIST, () => {
const audioPid = getRendererAudioServicePid();
const list = obtainVenmic()
?.list()
.filter(s => s["application.process.id"] !== audioPid)
.map(s => s["application.name"]);
return list
? { ok: true, targets: [...new Set(list)] } // Remove duplicates
: { ok: false, isGlibcxxToOld };
});
ipcMain.handle(
IpcEvents.VIRT_MIC_START,
(_, targets: string[]) =>
obtainVenmic()?.link({
props: targets.map(target => ({ key: "application.name", value: target })),
mode: "include"
})
);
ipcMain.handle(
IpcEvents.VIRT_MIC_START_SYSTEM,
() =>
obtainVenmic()?.link({
props: [
{
key: "application.process.id",
value: getRendererAudioServicePid()
}
],
mode: "exclude"
})
);
ipcMain.handle(IpcEvents.VIRT_MIC_STOP, () => obtainVenmic()?.unlink());

View File

@@ -58,5 +58,18 @@ export const VesktopNative = {
},
capturer: {
getLargeThumbnail: (id: string) => invoke<string>(IpcEvents.CAPTURER_GET_LARGE_THUMBNAIL, id)
},
/** only available on Linux. */
virtmic: {
list: () =>
invoke<{ ok: false; isGlibcxxToOld: boolean } | { ok: true; targets: string[] }>(IpcEvents.VIRT_MIC_LIST),
start: (targets: string[]) => invoke<void>(IpcEvents.VIRT_MIC_START, targets),
startSystem: () => invoke<void>(IpcEvents.VIRT_MIC_START_SYSTEM),
stop: () => invoke<void>(IpcEvents.VIRT_MIC_STOP)
},
arrpc: {
onActivity(cb: (data: string) => void) {
ipcRenderer.on(IpcEvents.ARRPC_ACTIVITY, (_, data: string) => cb(data));
}
}
};

View File

@@ -11,8 +11,6 @@ import { IpcEvents } from "../shared/IpcEvents";
import { VesktopNative } from "./VesktopNative";
contextBridge.exposeInMainWorld("VesktopNative", VesktopNative);
// TODO: remove legacy alias once main Vencord codebase has migrated and some time has passed
contextBridge.exposeInMainWorld("VencordDesktopNative", VesktopNative);
require(ipcRenderer.sendSync(IpcEvents.GET_VENCORD_PRELOAD_FILE));

View File

@@ -15,15 +15,19 @@ let NotificationSettingsStore: any;
export function setBadge() {
if (Settings.store.appBadge === false) return;
const mentionCount = GuildReadStateStore.getTotalMentionCount();
const pendingRequests = RelationshipStore.getPendingCount();
const hasUnread = GuildReadStateStore.hasAnyUnread();
const disableUnreadBadge = NotificationSettingsStore.getDisableUnreadBadge();
try {
const mentionCount = GuildReadStateStore.getTotalMentionCount();
const pendingRequests = RelationshipStore.getPendingCount();
const hasUnread = GuildReadStateStore.hasAnyUnread();
const disableUnreadBadge = NotificationSettingsStore.getDisableUnreadBadge();
let totalCount = mentionCount + pendingRequests;
if (!totalCount && hasUnread && !disableUnreadBadge) totalCount = -1;
let totalCount = mentionCount + pendingRequests;
if (!totalCount && hasUnread && !disableUnreadBadge) totalCount = -1;
VesktopNative.app.setBadgeCount(totalCount);
VesktopNative.app.setBadgeCount(totalCount);
} catch (e) {
console.error(e);
}
}
let toFind = 3;

View File

@@ -7,11 +7,21 @@
import "./screenSharePicker.css";
import { closeModal, Modals, openModal, useAwaiter } from "@vencord/types/utils";
import { findStoreLazy } from "@vencord/types/webpack";
import { Button, Card, Forms, Switch, Text, useState } from "@vencord/types/webpack/common";
import { findStoreLazy, onceReady } from "@vencord/types/webpack";
import {
Button,
Card,
FluxDispatcher,
Forms,
Select,
Switch,
Text,
UserStore,
useState
} from "@vencord/types/webpack/common";
import type { Dispatch, SetStateAction } from "react";
import { addPatch } from "renderer/patches/shared";
import { isWindows } from "renderer/utils";
import { isLinux, isWindows } from "renderer/utils";
const StreamResolutions = ["480", "720", "1080", "1440"] as const;
const StreamFps = ["15", "30", "60"] as const;
@@ -25,6 +35,7 @@ interface StreamSettings {
resolution: StreamResolution;
fps: StreamFps;
audio: boolean;
audioSource?: string;
}
export interface StreamPick extends StreamSettings {
@@ -70,18 +81,41 @@ addPatch({
}
});
export function openScreenSharePicker(screens: Source[]) {
if (isLinux) {
onceReady.then(() => {
FluxDispatcher.subscribe("VOICE_STATE_UPDATES", e => {
for (const state of e.voiceStates) {
if (state.userId === UserStore.getCurrentUser().id && state.oldChannelId && !state.channelId)
VesktopNative.virtmic.stop();
}
});
});
}
export function openScreenSharePicker(screens: Source[], skipPicker: boolean) {
let didSubmit = false;
return new Promise<StreamPick>((resolve, reject) => {
const key = openModal(
props => (
<ModalComponent
screens={screens}
modalProps={props}
submit={resolve}
submit={async v => {
didSubmit = true;
if (v.audioSource && v.audioSource !== "None") {
if (v.audioSource === "Entire System") {
await VesktopNative.virtmic.startSystem();
} else {
await VesktopNative.virtmic.start([v.audioSource]);
}
}
resolve(v);
}}
close={() => {
props.onClose();
reject("Aborted");
if (!didSubmit) reject("Aborted");
}}
skipPicker={skipPicker}
/>
),
{
@@ -112,16 +146,21 @@ function ScreenPicker({ screens, chooseScreen }: { screens: Source[]; chooseScre
function StreamSettings({
source,
settings,
setSettings
setSettings,
skipPicker
}: {
source: Source;
settings: StreamSettings;
setSettings: Dispatch<SetStateAction<StreamSettings>>;
skipPicker: boolean;
}) {
const [thumb] = useAwaiter(() => VesktopNative.capturer.getLargeThumbnail(source.id), {
fallbackValue: source.url,
deps: [source.id]
});
const [thumb] = useAwaiter(
() => (skipPicker ? Promise.resolve(source.url) : VesktopNative.capturer.getLargeThumbnail(source.id)),
{
fallbackValue: source.url,
deps: [source.id]
}
);
return (
<div>
@@ -182,23 +221,76 @@ function StreamSettings({
Stream With Audio
</Switch>
)}
{isLinux && (
<AudioSourcePickerLinux
audioSource={settings.audioSource}
setAudioSource={source => setSettings(s => ({ ...s, audioSource: source }))}
/>
)}
</Card>
</div>
);
}
function AudioSourcePickerLinux({
audioSource,
setAudioSource
}: {
audioSource?: string;
setAudioSource(s: string): void;
}) {
const [sources, _, loading] = useAwaiter(() => VesktopNative.virtmic.list(), {
fallbackValue: { ok: true, targets: [] }
});
const allSources = sources.ok ? ["None", "Entire System", ...sources.targets] : null;
return (
<section>
<Forms.FormTitle>Audio</Forms.FormTitle>
{loading && <Forms.FormTitle>Loading Audio sources...</Forms.FormTitle>}
{!sources.ok &&
(sources.isGlibcxxToOld ? (
<Forms.FormText>
Failed to retrieve Audio Sources because your c++ library is too old to run venmic. If you would
like to stream with Audio, see{" "}
<a href="https://gist.github.com/Vendicated/b655044ffbb16b2716095a448c6d827a" target="_blank">
this guide
</a>
</Forms.FormText>
) : (
<Forms.FormText>
Failed to retrieve Audio Sources. If you would like to stream with Audio, make sure you're using
Pipewire, not Pulseaudio
</Forms.FormText>
))}
{allSources && (
<Select
options={allSources.map(s => ({ label: s, value: s, default: s === "None" }))}
isSelected={s => s === audioSource}
select={setAudioSource}
serialize={String}
/>
)}
</section>
);
}
function ModalComponent({
screens,
modalProps,
submit,
close
close,
skipPicker
}: {
screens: Source[];
modalProps: any;
submit: (data: StreamPick) => void;
close: () => void;
skipPicker: boolean;
}) {
const [selected, setSelected] = useState<string>();
const [selected, setSelected] = useState<string | undefined>(skipPicker ? screens[0].id : void 0);
const [settings, setSettings] = useState<StreamSettings>({
resolution: "1080",
fps: "60",
@@ -220,6 +312,7 @@ function ModalComponent({
source={screens.find(s => s.id === selected)!}
settings={settings}
setSettings={setSettings}
skipPicker={skipPicker}
/>
)}
</Modals.ModalContent>
@@ -259,7 +352,7 @@ function ModalComponent({
Go Live
</Button>
{selected ? (
{selected && !skipPicker ? (
<Button color={Button.Colors.TRANSPARENT} onClick={() => setSelected(void 0)}>
Back
</Button>

View File

@@ -42,11 +42,14 @@ export default function SettingsUi() {
],
["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."],
["disableSmoothScroll", "Disable smooth scrolling", "Disables smooth scrolling in Vesktop", false],
["splashTheming", "Splash theming", "Adapt the splash window colors to your custom theme", false],
[
"openLinksWithElectron",
"Open Links in app (experimental)",
"Opens links in a new Vesktop window instead of your web browser"
]
],
["checkUpdates", "Check for updates", "Automatically check for Vesktop updates", true]
];
const switches = allSwitches.filter(isTruthy);

View File

@@ -8,7 +8,7 @@ import "./hideGarbage.css";
import { waitFor } from "@vencord/types/webpack";
import { isFirstRun, localStorage } from "./utils";
import { isFirstRun, isWindows, localStorage } from "./utils";
// Make clicking Notifications focus the window
const originalSetOnClick = Object.getOwnPropertyDescriptor(Notification.prototype, "onclick")!.set!;
@@ -31,3 +31,14 @@ if (isFirstRun) {
m.setDesktopType("all");
});
}
// 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

@@ -1,5 +1,5 @@
/* 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]) {
[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;
}

View File

@@ -7,6 +7,7 @@
import "./fixes";
import "./appBadge";
import "./patches";
import "./themedSplash";
console.log("read if cute :3");
@@ -14,6 +15,7 @@ export * as Components from "./components";
import { findByPropsLazy } from "@vencord/types/webpack";
import { FluxDispatcher } from "@vencord/types/webpack/common";
import SettingsUi from "./components/Settings";
import { Settings } from "./settings";
export { Settings };
@@ -35,14 +37,23 @@ export async function openInviteModal(code: string) {
return true;
}
const arRPC = Vencord.Plugins.plugins["WebRichPresence (arRPC)"];
const customSettingsSections = (
Vencord.Plugins.plugins.Settings as any as { customSections: ((ID: Record<string, unknown>) => any)[] }
).customSections;
arRPC.required = !!Settings.store.arRPC;
customSettingsSections.push(() => ({
section: "Vesktop",
label: "Vesktop Settings",
element: SettingsUi,
className: "vc-vesktop-settings"
}));
Settings.addChangeListener("arRPC", v => {
arRPC.required = !!v;
if (v && !arRPC.started) Vencord.Plugins.startPlugin(arRPC);
else if (arRPC.started) {
Vencord.Plugins.stopPlugin(arRPC);
}
const arRPC = Vencord.Plugins.plugins["WebRichPresence (arRPC)"] as any as {
handleEvent(e: MessageEvent): void;
};
VesktopNative.arrpc.onActivity(data => {
if (!Settings.store.arRPC) return;
arRPC.handleEvent(new MessageEvent("message", { data }));
});

View File

@@ -8,3 +8,4 @@
import "./spellCheck";
import "./platformClass";
import "./windowsTitleBar";
import "./screenShareAudio";

View File

@@ -23,7 +23,7 @@ addPatch({
getPlatformClass() {
if (isMac) return "platform-osx";
if (isWindows && Settings.store.discordWindowsTitleBar) return "platform-windows";
if (isWindows && Settings.store.discordWindowsTitleBar) return "platform-win";
return "platform-web";
}
});

View File

@@ -0,0 +1,42 @@
/*
* SPDX-License-Identifier: GPL-3.0
* Vesktop, a desktop app aiming to give you a snappier Discord Experience
* Copyright (c) 2023 Vendicated and Vencord contributors
*/
import { isLinux } from "renderer/utils";
if (isLinux) {
const original = navigator.mediaDevices.getDisplayMedia;
async function getVirtmic() {
try {
const devices = await navigator.mediaDevices.enumerateDevices();
const audioDevice = devices.find(({ label }) => label === "vencord-screen-share");
return audioDevice?.deviceId;
} catch (error) {
return null;
}
}
navigator.mediaDevices.getDisplayMedia = async function (opts) {
const stream = await original.call(this, opts);
const id = await getVirtmic();
if (id) {
const audio = await navigator.mediaDevices.getUserMedia({
audio: {
deviceId: {
exact: id
},
autoGainControl: false,
echoCancellation: false,
noiseSuppression: false
}
});
audio.getAudioTracks().forEach(t => stream.addTrack(t));
}
return stream;
};
}

View File

@@ -22,7 +22,7 @@ addPatch({
find: ".enableSpellCheck)",
replacement: {
// if (isDesktop) { DiscordNative.onSpellcheck(openMenu(props)) } else { e.preventDefault(); openMenu(props) }
match: /else\{(.{1,3})\.preventDefault\(\);(.{1,3}\(.{1,3}\))\}(?<=:(.{1,3})\.enableSpellCheck\).+?)/,
match: /else (.{1,3})\.preventDefault\(\),(.{1,3}\(.{1,3}\))(?<=:(.{1,3})\.enableSpellCheck\).+?)/,
// ... else { $self.onSlateContext(() => openMenu(props)) }
replace: "else {$self.onSlateContext($1, $3?.enableSpellCheck, () => $2)}"
}

View File

@@ -21,7 +21,7 @@ if (Settings.store.discordWindowsTitleBar)
replace: 'case "WEB":'
},
...["close", "minimize", "maximize"].map(op => ({
match: new RegExp(String.raw`\i\.default\.${op}\b`),
match: new RegExp(String.raw`\i\.\i\.${op}\b`),
replace: `VesktopNative.win.${op}`
}))
]

View File

@@ -0,0 +1,46 @@
/*
* 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 { Settings } from "./settings";
function isValidColor(color: CSSStyleValue | undefined): color is CSSUnparsedValue & { [0]: string } {
return color instanceof CSSUnparsedValue && typeof color[0] === "string" && CSS.supports("color", color[0]);
}
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;
span.remove();
return rgbColor;
}
const updateSplashColors = () => {
const bodyStyles = document.body.computedStyleMap();
const color = bodyStyles.get("--text-normal");
const backgroundColor = bodyStyles.get("--background-primary");
if (isValidColor(color)) {
Settings.store.splashColor = resolveColor(color[0]);
}
if (isValidColor(backgroundColor)) {
Settings.store.splashBackground = resolveColor(backgroundColor[0]);
}
};
if (document.readyState === "complete") {
updateSplashColors();
} else {
window.addEventListener("load", updateSplashColors);
}
window.addEventListener("beforeunload", updateSplashColors);

View File

@@ -17,3 +17,4 @@ const { platform } = navigator;
export const isWindows = platform.startsWith("Win");
export const isMac = platform.startsWith("Mac");
export const isLinux = platform.startsWith("Linux");

View File

@@ -40,5 +40,12 @@ export const enum IpcEvents {
AUTOSTART_ENABLED = "VCD_AUTOSTART_ENABLED",
ENABLE_AUTOSTART = "VCD_ENABLE_AUTOSTART",
DISABLE_AUTOSTART = "VCD_DISABLE_AUTOSTART"
DISABLE_AUTOSTART = "VCD_DISABLE_AUTOSTART",
VIRT_MIC_LIST = "VCD_VIRT_MIC_LIST",
VIRT_MIC_START = "VCD_VIRT_MIC_START",
VIRT_MIC_START_SYSTEM = "VCD_VIRT_MIC_START_ALL",
VIRT_MIC_STOP = "VCD_VIRT_MIC_STOP",
ARRPC_ACTIVITY = "VCD_ARRPC_ACTIVITY"
}

View File

@@ -15,6 +15,7 @@ export interface Settings {
openLinksWithElectron?: boolean;
staticTitle?: boolean;
enableMenu?: boolean;
disableSmoothScroll?: boolean;
arRPC?: boolean;
appBadge?: boolean;
discordWindowsTitleBar?: boolean;
@@ -24,6 +25,13 @@ export interface Settings {
windowBounds?: Rectangle;
disableMinSize?: boolean;
checkUpdates?: boolean;
skippedUpdate?: string;
firstLaunch?: boolean;
splashTheming?: boolean;
splashColor?: string;
splashBackground?: string;
steamOSLayoutVersion?: number;
}

View File

@@ -12,8 +12,8 @@ type ResolvePropDeep<T, P> = P extends `${infer Pre}.${infer Suf}`
? ResolvePropDeep<T[Pre], Suf>
: any
: P extends keyof T
? T[P]
: any;
? T[P]
: any;
/**
* The SettingsStore allows you to easily create a mutable store that

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
*/
export function sleep(ms: number): Promise<void> {
return new Promise(r => setTimeout(r, ms));
}

View File

@@ -4,13 +4,14 @@
* Copyright (c) 2023 Vendicated and Vencord contributors
*/
import { app, BrowserWindow, ipcMain, shell } from "electron";
import { app, BrowserWindow, shell } from "electron";
import { Settings } from "main/settings";
import { handle } from "main/utils/ipcWrappers";
import { makeLinksOpenExternally } from "main/utils/makeLinksOpenExternally";
import { githubGet, ReleaseData } from "main/utils/vencordLoader";
import { join } from "path";
import { IpcEvents } from "shared/IpcEvents";
import { VIEW_DIR } from "shared/paths";
import { ICON_PATH, VIEW_DIR } from "shared/paths";
export interface UpdateData {
currentVersion: string;
@@ -20,8 +21,8 @@ export interface UpdateData {
let updateData: UpdateData;
ipcMain.handle(IpcEvents.UPDATER_GET_DATA, () => updateData);
ipcMain.handle(IpcEvents.UPDATER_DOWNLOAD, () => {
handle(IpcEvents.UPDATER_GET_DATA, () => updateData);
handle(IpcEvents.UPDATER_DOWNLOAD, () => {
const portable = !!process.env.PORTABLE_EXECUTABLE_FILE;
const { assets } = updateData.release;
@@ -50,7 +51,7 @@ ipcMain.handle(IpcEvents.UPDATER_DOWNLOAD, () => {
shell.openExternal(url);
});
ipcMain.handle(IpcEvents.UPDATE_IGNORE, () => {
handle(IpcEvents.UPDATE_IGNORE, () => {
Settings.store.skippedUpdate = updateData.latestVersion;
});
@@ -76,7 +77,8 @@ function isOutdated(oldVersion: string, newVersion: string) {
}
export async function checkUpdates() {
// if (IS_DEV) return;
if (Settings.store.checkUpdates === false) return;
try {
const raw = await githubGet("/repos/Vencord/Vesktop/releases/latest");
const data = JSON.parse(raw.toString("utf-8")) as ReleaseData;
@@ -107,7 +109,8 @@ function openNewUpdateWindow() {
nodeIntegration: false,
contextIsolation: true,
sandbox: true
}
},
icon: ICON_PATH
});
makeLinksOpenExternally(win);

2
static/dist/.gitignore vendored Normal file
View File

@@ -0,0 +1,2 @@
*
!.gitignore

BIN
static/shiggy.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

View File

@@ -51,6 +51,10 @@
<a href="https://github.com/OpenAsar/arrpc" target="_blank">arrpc</a>
- An open implementation of Discord's Rich Presence server
</li>
<li>
<a href="https://github.com/Soundux/rohrkabel" target="_blank">rohrkabel</a>
- A C++ RAII Pipewire-API Wrapper
</li>
<li>
And many
<a href="https://github.com/Vencord/Vesktop/blob/main/pnpm-lock.yaml" target="_blank"

View File

@@ -24,6 +24,7 @@
img {
width: 6em;
height: 6em;
image-rendering: pixelated;
}
</style>
</head>
@@ -32,7 +33,7 @@
<div class="wrapper">
<img
draggable="false"
src="https://cdn.discordapp.com/emojis/1024751291504791654.gif?size=512"
src="../shiggy.gif"
alt="shiggy"
role="presentation"
/>