Compare commits
11 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
43ca479fc8 | ||
|
|
7b0f64a9fc | ||
|
|
573a953a2f | ||
|
|
841cdcf672 | ||
|
|
0d93e08e99 | ||
|
|
c445c6194f | ||
|
|
e29d293855 | ||
|
|
e13a4eacb1 | ||
|
|
c070761f9d | ||
|
|
ae86c28247 | ||
|
|
9c95956c96 |
@@ -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"
|
||||
8
.github/workflows/winget-submission.yml
vendored
8
.github/workflows/winget-submission.yml
vendored
@@ -3,6 +3,12 @@ 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:
|
||||
@@ -15,3 +21,5 @@ jobs:
|
||||
identifier: Vencord.Vesktop
|
||||
token: ${{ secrets.WINGET_PAT }}
|
||||
installers-regex: '\.exe$'
|
||||
release-tag: ${{ inputs.tag || github.event.release.tag_name }}
|
||||
fork-user: shiggybot
|
||||
|
||||
2
.gitignore
vendored
2
.gitignore
vendored
@@ -2,4 +2,4 @@ dist
|
||||
node_modules
|
||||
.env
|
||||
.DS_Store
|
||||
.idea/
|
||||
.idea/
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# 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
|
||||
|
||||
41
package.json
41
package.json
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "VencordDesktop",
|
||||
"version": "0.3.3",
|
||||
"version": "0.4.0",
|
||||
"private": true,
|
||||
"description": "",
|
||||
"keywords": [],
|
||||
@@ -25,30 +25,33 @@
|
||||
"dependencies": {
|
||||
"arrpc": "github:OpenAsar/arrpc#89f4da610ccfac93f461826a446a17cd3b23953d"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@vencord/venmic": "^1.4.0"
|
||||
},
|
||||
"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.8.4",
|
||||
"@types/react": "^18.2.28",
|
||||
"@typescript-eslint/eslint-plugin": "^6.7.5",
|
||||
"@typescript-eslint/parser": "^6.7.5",
|
||||
"@vencord/types": "^0.1.2",
|
||||
"dotenv": "^16.3.1",
|
||||
"electron": "^25.8.4",
|
||||
"electron-builder": "^24.6.3",
|
||||
"esbuild": "^0.18.17",
|
||||
"eslint": "^8.46.0",
|
||||
"eslint-config-prettier": "^8.9.0",
|
||||
"electron": "^27.0.0",
|
||||
"electron-builder": "^24.6.4",
|
||||
"esbuild": "^0.19.4",
|
||||
"eslint": "^8.51.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.0.3",
|
||||
"source-map-support": "^0.5.21",
|
||||
"tsx": "^3.12.7",
|
||||
"type-fest": "^4.1.0",
|
||||
"typescript": "^5.1.6"
|
||||
"tsx": "^3.13.0",
|
||||
"type-fest": "^4.4.0",
|
||||
"typescript": "^5.2.2"
|
||||
},
|
||||
"packageManager": "pnpm@8.6.11",
|
||||
"engines": {
|
||||
@@ -94,7 +97,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",
|
||||
|
||||
1554
pnpm-lock.yaml
generated
1554
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@@ -5,6 +5,7 @@
|
||||
*/
|
||||
|
||||
import { BuildContext, BuildOptions, context } from "esbuild";
|
||||
import { copyFile } from "fs/promises";
|
||||
|
||||
import vencordDep from "./vencordDep.mjs";
|
||||
|
||||
@@ -34,6 +35,11 @@ async function createContext(options: BuildOptions) {
|
||||
}
|
||||
|
||||
await Promise.all([
|
||||
process.platform === "linux" &&
|
||||
copyFile(
|
||||
"./node_modules/@vencord/venmic/prebuilds/venmic-addon-linux-x64/node-napi-v7.node",
|
||||
"./static/dist/venmic.node"
|
||||
).catch(() => console.warn("Failed to copy venmic. Building without venmic support")),
|
||||
createContext({
|
||||
...NodeCommonOpts,
|
||||
entryPoints: ["src/main/index.ts"],
|
||||
@@ -65,11 +71,7 @@ await Promise.all([
|
||||
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" }
|
||||
})
|
||||
]);
|
||||
|
||||
|
||||
@@ -8,4 +8,4 @@ import "./utils/dotenv";
|
||||
|
||||
import { spawnNodeModuleBin } from "./utils/spawn.mjs";
|
||||
|
||||
spawnNodeModuleBin("electron", ["."]);
|
||||
spawnNodeModuleBin("electron", [".", ...(process.env.ELECTRON_LAUNCH_FLAGS?.split(" ") ?? [])]);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -49,6 +50,8 @@ function init() {
|
||||
if (process.platform === "win32") app.setAppUserModelId("dev.vencord.desktop");
|
||||
|
||||
registerScreenShareHandler();
|
||||
registerMediaPermissionsHandler();
|
||||
|
||||
bootstrap();
|
||||
|
||||
app.on("activate", () => {
|
||||
|
||||
@@ -4,6 +4,8 @@
|
||||
* Copyright (c) 2023 Vendicated and Vencord contributors
|
||||
*/
|
||||
|
||||
if (process.platform === "linux") import("./virtmic");
|
||||
|
||||
import { execFile } from "child_process";
|
||||
import { app, dialog, RelaunchOptions, session, shell } from "electron";
|
||||
import { mkdirSync, readFileSync, watch } from "fs";
|
||||
|
||||
@@ -11,6 +11,7 @@ import {
|
||||
dialog,
|
||||
Menu,
|
||||
MenuItemConstructorOptions,
|
||||
nativeTheme,
|
||||
Tray
|
||||
} from "electron";
|
||||
import { rm } from "fs/promises";
|
||||
@@ -341,7 +342,9 @@ function createMainWindow() {
|
||||
removeSettingsListeners();
|
||||
removeVencordSettingsListeners();
|
||||
|
||||
const { staticTitle, transparencyOption, enableMenu, discordWindowsTitleBar } = Settings.store;
|
||||
const { staticTitle, transparencyOption, splashTheming, splashBackground, enableMenu, discordWindowsTitleBar } =
|
||||
Settings.store;
|
||||
|
||||
const { frameless, macosTranslucency } = VencordSettings.store;
|
||||
|
||||
const noFrame = frameless === true || (process.platform === "win32" && discordWindowsTitleBar === true);
|
||||
@@ -361,7 +364,7 @@ function createMainWindow() {
|
||||
...(transparencyOption && transparencyOption !== "none"
|
||||
? {
|
||||
backgroundColor: "#00000000",
|
||||
backgroundMaterial: Settings.store.transparencyOption,
|
||||
backgroundMaterial: transparencyOption,
|
||||
transparent: true
|
||||
}
|
||||
: {}),
|
||||
@@ -371,7 +374,14 @@ function createMainWindow() {
|
||||
vibrancy: "sidebar",
|
||||
backgroundColor: "#ffffff00"
|
||||
}
|
||||
: {}),
|
||||
: {
|
||||
backgroundColor: splashTheming
|
||||
? splashBackground
|
||||
: nativeTheme.shouldUseDarkColors
|
||||
? "#313338"
|
||||
: "#ffffff",
|
||||
transparent: false
|
||||
}),
|
||||
...(process.platform === "darwin" ? { titleBarStyle: "hiddenInset" } : {}),
|
||||
...getWindowBoundsOptions(),
|
||||
autoHideMenuBar: enableMenu
|
||||
|
||||
24
src/main/mediaPermissions.ts
Normal file
24
src/main/mediaPermissions.ts
Normal 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);
|
||||
});
|
||||
}
|
||||
@@ -10,6 +10,9 @@ 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() {
|
||||
handle(IpcEvents.CAPTURER_GET_LARGE_THUMBNAIL, async (_, id: string) => {
|
||||
const sources = await desktopCapturer.getSources({
|
||||
@@ -23,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,
|
||||
@@ -43,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({});
|
||||
|
||||
|
||||
@@ -9,6 +9,8 @@ import { join } from "path";
|
||||
import { SplashProps } from "shared/browserWinProperties";
|
||||
import { ICON_PATH, VIEW_DIR } from "shared/paths";
|
||||
|
||||
import { Settings } from "./settings";
|
||||
|
||||
export function createSplashWindow() {
|
||||
const splash = new BrowserWindow({
|
||||
...SplashProps,
|
||||
@@ -17,5 +19,20 @@ export function createSplashWindow() {
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
66
src/main/virtmic.ts
Normal file
66
src/main/virtmic.ts
Normal file
@@ -0,0 +1,66 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
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.node")) as typeof import("@vencord/venmic");
|
||||
patchBay = new PatchBay();
|
||||
} catch (e) {
|
||||
console.error("Failed to initialise venmic. Make sure you're using pipewire", e);
|
||||
}
|
||||
}
|
||||
|
||||
return patchBay;
|
||||
}
|
||||
|
||||
ipcMain.handle(IpcEvents.VIRT_MIC_LIST, () => {
|
||||
const audioPid = getRendererAudioServicePid();
|
||||
return obtainVenmic()
|
||||
?.list()
|
||||
.filter(s => s["application.process.id"] !== audioPid)
|
||||
.map(s => s["application.name"]);
|
||||
});
|
||||
|
||||
ipcMain.handle(
|
||||
IpcEvents.VIRT_MIC_START,
|
||||
(_, target: string) =>
|
||||
obtainVenmic()?.link({
|
||||
key: "application.name",
|
||||
value: target,
|
||||
mode: "include"
|
||||
})
|
||||
);
|
||||
|
||||
ipcMain.handle(
|
||||
IpcEvents.VIRT_MIC_START_SYSTEM,
|
||||
() =>
|
||||
obtainVenmic()?.link({
|
||||
key: "application.process.id",
|
||||
value: getRendererAudioServicePid(),
|
||||
mode: "exclude"
|
||||
})
|
||||
);
|
||||
|
||||
ipcMain.handle(IpcEvents.VIRT_MIC_STOP, () => obtainVenmic()?.unlink());
|
||||
@@ -58,5 +58,17 @@ export const VesktopNative = {
|
||||
},
|
||||
capturer: {
|
||||
getLargeThumbnail: (id: string) => invoke<string>(IpcEvents.CAPTURER_GET_LARGE_THUMBNAIL, id)
|
||||
},
|
||||
/** only available on Linux. */
|
||||
virtmic: {
|
||||
list: () => invoke<string[] | null>(IpcEvents.VIRT_MIC_LIST),
|
||||
start: (target: string) => invoke<void>(IpcEvents.VIRT_MIC_START, target),
|
||||
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));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -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));
|
||||
|
||||
|
||||
@@ -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,65 @@ 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: [] });
|
||||
const allSources = sources ? ["None", "Entire System", ...sources] : null;
|
||||
|
||||
return (
|
||||
<section>
|
||||
<Forms.FormTitle>Audio</Forms.FormTitle>
|
||||
{loading && <Forms.FormTitle>Loading Audio sources...</Forms.FormTitle>}
|
||||
{allSources === null && (
|
||||
<Forms.FormTitle>
|
||||
Failed to retrieve Audio Sources. If you would like to stream with Audio, make sure you're using
|
||||
Pipewire, not Pulseaudio
|
||||
</Forms.FormTitle>
|
||||
)}
|
||||
|
||||
{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 +301,7 @@ function ModalComponent({
|
||||
source={screens.find(s => s.id === selected)!}
|
||||
settings={settings}
|
||||
setSettings={setSettings}
|
||||
skipPicker={skipPicker}
|
||||
/>
|
||||
)}
|
||||
</Modals.ModalContent>
|
||||
@@ -259,7 +341,7 @@ function ModalComponent({
|
||||
Go Live
|
||||
</Button>
|
||||
|
||||
{selected ? (
|
||||
{selected && !skipPicker ? (
|
||||
<Button color={Button.Colors.TRANSPARENT} onClick={() => setSelected(void 0)}>
|
||||
Back
|
||||
</Button>
|
||||
|
||||
@@ -42,6 +42,7 @@ 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."],
|
||||
["splashTheming", "Splash theming", "Adapt the splash window colors to your custom theme", false],
|
||||
[
|
||||
"openLinksWithElectron",
|
||||
"Open Links in app (experimental)",
|
||||
|
||||
@@ -3,8 +3,3 @@
|
||||
[class|=listItem]:has(+ [class|=listItem] [data-list-item-id=guildsnav___app-download-button]) {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* FIXME: Remove after 23/08/23 */
|
||||
.vc-desktop-settings {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
import "./fixes";
|
||||
import "./appBadge";
|
||||
import "./patches";
|
||||
import "./themedSplash";
|
||||
|
||||
console.log("read if cute :3");
|
||||
|
||||
@@ -47,14 +48,12 @@ customSettingsSections.push(() => ({
|
||||
className: "vc-vesktop-settings"
|
||||
}));
|
||||
|
||||
const arRPC = Vencord.Plugins.plugins["WebRichPresence (arRPC)"];
|
||||
const arRPC = Vencord.Plugins.plugins["WebRichPresence (arRPC)"] as any as {
|
||||
handleEvent(e: MessageEvent): void;
|
||||
};
|
||||
|
||||
arRPC.required = !!Settings.store.arRPC;
|
||||
VesktopNative.arrpc.onActivity(data => {
|
||||
if (!Settings.store.arRPC) return;
|
||||
|
||||
Settings.addChangeListener("arRPC", v => {
|
||||
arRPC.required = !!v;
|
||||
if (v && !arRPC.started) Vencord.Plugins.startPlugin(arRPC);
|
||||
else if (arRPC.started) {
|
||||
Vencord.Plugins.stopPlugin(arRPC);
|
||||
}
|
||||
arRPC.handleEvent(new MessageEvent("message", { data }));
|
||||
});
|
||||
|
||||
@@ -8,3 +8,4 @@
|
||||
import "./spellCheck";
|
||||
import "./platformClass";
|
||||
import "./windowsTitleBar";
|
||||
import "./screenShareAudio";
|
||||
|
||||
42
src/renderer/patches/screenShareAudio.ts
Normal file
42
src/renderer/patches/screenShareAudio.ts
Normal 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;
|
||||
};
|
||||
}
|
||||
46
src/renderer/themedSplash.ts
Normal file
46
src/renderer/themedSplash.ts
Normal 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);
|
||||
@@ -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");
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
|
||||
4
src/shared/settings.d.ts
vendored
4
src/shared/settings.d.ts
vendored
@@ -26,4 +26,8 @@ export interface Settings {
|
||||
|
||||
skippedUpdate?: string;
|
||||
firstLaunch?: boolean;
|
||||
|
||||
splashTheming?: boolean;
|
||||
splashColor?: string;
|
||||
splashBackground?: string;
|
||||
}
|
||||
|
||||
2
static/dist/.gitignore
vendored
Normal file
2
static/dist/.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
*
|
||||
!.gitignore
|
||||
@@ -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"
|
||||
|
||||
Reference in New Issue
Block a user