fixes etc
This commit is contained in:
@@ -13,3 +13,5 @@ export const AppEvents = new EventEmitter<{
|
||||
userAssetChanged: [UserAssetType];
|
||||
setTrayVariant: ["tray" | "trayUnread"];
|
||||
}>();
|
||||
|
||||
export const appReady = new Promise<void>(resolve => AppEvents.once("appLoaded", () => resolve()));
|
||||
|
||||
+1
-1
@@ -167,5 +167,5 @@ app.on("second-instance", (_e, _argv, _cwd, data) => {
|
||||
const { args } = data as { args: typeof CommandLine; IS_DEV: boolean };
|
||||
const { "run-shortcut": shortcut } = args.values;
|
||||
|
||||
if (shortcut) sendRendererCommand(IpcCommands.HANDLE_KEY_BIND, shortcut);
|
||||
if (shortcut) sendRendererCommand(IpcCommands.KEY_BINDS_HANDLE, shortcut);
|
||||
});
|
||||
|
||||
@@ -8,6 +8,7 @@ import { randomUUID } from "crypto";
|
||||
import { ipcMain } from "electron";
|
||||
import { IpcEvents } from "shared/IpcEvents";
|
||||
|
||||
import { appReady } from "./events";
|
||||
import { mainWin } from "./mainWindow";
|
||||
|
||||
const resolvers = new Map<string, Record<"resolve" | "reject", (data: any) => void>>();
|
||||
@@ -30,7 +31,9 @@ export interface IpcResponse {
|
||||
*
|
||||
* You must add a handler for the message in the renderer process.
|
||||
*/
|
||||
export function sendRendererCommand<T = any>(message: string, data?: any) {
|
||||
export async function sendRendererCommand<T = any>(message: string, data?: any) {
|
||||
await appReady;
|
||||
|
||||
if (mainWin.isDestroyed()) {
|
||||
console.warn("Main window is destroyed, cannot send IPC command:", message);
|
||||
return Promise.reject(new Error("Main window is destroyed"));
|
||||
|
||||
@@ -12,7 +12,6 @@ import { Settings } from "./settings";
|
||||
|
||||
function unregisterKeyBinds() {
|
||||
if (!app.isReady()) return;
|
||||
|
||||
globalShortcut.unregisterAll();
|
||||
}
|
||||
|
||||
@@ -21,15 +20,18 @@ export async function registerKeyBinds() {
|
||||
|
||||
unregisterKeyBinds();
|
||||
|
||||
let success = true;
|
||||
|
||||
for (const { action, enabled, key } of Settings.store.keyBinds || []) {
|
||||
if (!enabled || !key || action === "unassigned") continue;
|
||||
|
||||
console.log(
|
||||
key,
|
||||
"SUCCESS",
|
||||
globalShortcut.register(key, () => sendRendererCommand(IpcCommands.HANDLE_KEY_BIND, action))
|
||||
const registeredSuccessfully = globalShortcut.register(key, () =>
|
||||
sendRendererCommand(IpcCommands.KEY_BINDS_HANDLE, action)
|
||||
);
|
||||
success &&= registeredSuccessfully;
|
||||
}
|
||||
|
||||
sendRendererCommand(IpcCommands.KEY_BINDS_SET_STATUS, success);
|
||||
}
|
||||
|
||||
Settings.addChangeListener("keyBinds", registerKeyBinds);
|
||||
|
||||
@@ -9,7 +9,7 @@ import "./settings.css";
|
||||
import { classNameFactory } from "@vencord/types/api/Styles";
|
||||
import { BaseText, Divider, ErrorBoundary } from "@vencord/types/components";
|
||||
import { ComponentType } from "react";
|
||||
import { KeyBindSettings } from "renderer/globalShortcuts/ShortcutSettings";
|
||||
import { KeybindsButton } from "renderer/globalShortcuts/ShortcutSettings";
|
||||
import { Settings, useSettings } from "renderer/settings";
|
||||
import { isMac, isWindows } from "renderer/utils";
|
||||
|
||||
@@ -123,7 +123,8 @@ const SettingsOptions: Record<string, Array<BooleanSetting | SettingsComponent>>
|
||||
title: "Disable smooth scrolling",
|
||||
description: "Disables smooth scrolling",
|
||||
defaultValue: false
|
||||
}
|
||||
},
|
||||
KeybindsButton
|
||||
],
|
||||
Notifications: [
|
||||
NotificationBadgeToggle,
|
||||
@@ -149,7 +150,6 @@ const SettingsOptions: Record<string, Array<BooleanSetting | SettingsComponent>>
|
||||
defaultValue: false
|
||||
}
|
||||
],
|
||||
KeyBinds: [KeyBindSettings],
|
||||
"Developer Options": [DeveloperOptionsButton]
|
||||
};
|
||||
|
||||
|
||||
@@ -1,6 +1,13 @@
|
||||
.vcd-shortcuts-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1em;
|
||||
padding-block: 1em;
|
||||
}
|
||||
|
||||
.vcd-shortcuts-group {
|
||||
display: grid;
|
||||
gap: 0.25em 1em;
|
||||
gap: 0.25em 0.5em;
|
||||
grid-template-columns: minmax(0, 1fr) minmax(0, 1fr) auto auto;
|
||||
align-items: center;
|
||||
}
|
||||
@@ -21,7 +28,8 @@
|
||||
border-radius: var(--radius-sm);
|
||||
width: 100%;
|
||||
min-height: calc(var(--select-option-height) - 2px);
|
||||
padding: 0.25em;
|
||||
padding: 3px 4px 3px 12px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.vcd-shortcuts-record-input {
|
||||
|
||||
@@ -6,10 +6,23 @@
|
||||
|
||||
import "./ShortcutSettings.css";
|
||||
|
||||
import { BaseText, Button, DeleteIcon, Switch } from "@vencord/types/components";
|
||||
import { classNameFactory, identity } from "@vencord/types/utils";
|
||||
import { BaseText, Button, Card, DeleteIcon, Switch } from "@vencord/types/components";
|
||||
import {
|
||||
classNameFactory,
|
||||
identity,
|
||||
ModalCloseButton,
|
||||
ModalContent,
|
||||
ModalHeader,
|
||||
ModalRoot,
|
||||
ModalSize,
|
||||
openModal
|
||||
} from "@vencord/types/utils";
|
||||
import { React, Select, useState } from "@vencord/types/webpack/common";
|
||||
import { SettingsComponent } from "renderer/components/settings/Settings";
|
||||
import { onIpcCommand } from "renderer/ipcCommands";
|
||||
import { reactiveValue } from "renderer/reactiveState";
|
||||
import { Settings } from "renderer/settings";
|
||||
import { IpcCommands } from "shared/IpcEvents";
|
||||
|
||||
import { recordKeybind } from "./recordKeybind";
|
||||
|
||||
@@ -20,13 +33,14 @@ const ShortcutActions = {
|
||||
toggleMute: "Toggle Mute",
|
||||
toggleDeafen: "Toggle Deafen",
|
||||
toggleStreamerMode: "Toggle Streamer Mode",
|
||||
toggleCamera: "Toggle Camera",
|
||||
toggleScreenShare: "Toggle Screen Share",
|
||||
disconnectFromVoiceChannel: "Disconnect from Voice Channel"
|
||||
disconnectFromVoiceChannel: "Disconnect from VC"
|
||||
} as const;
|
||||
|
||||
export type ShortcutAction = keyof typeof ShortcutActions;
|
||||
|
||||
const shortcutStatus = reactiveValue(true);
|
||||
onIpcCommand(IpcCommands.KEY_BINDS_SET_STATUS, status => (shortcutStatus.value = status));
|
||||
|
||||
export interface KeyBind {
|
||||
id: string;
|
||||
enabled: boolean;
|
||||
@@ -109,9 +123,18 @@ function KeyBindGroup({
|
||||
|
||||
export function KeyBindSettings() {
|
||||
const [keyBinds, setKeyBinds] = useState<KeyBind[]>(Settings.store.keyBinds ?? []);
|
||||
const shortcutsStatus = shortcutStatus.use();
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className={cl("container")}>
|
||||
{shortcutsStatus === false && (
|
||||
<Card variant="danger">
|
||||
<BaseText size="md" weight="medium">
|
||||
Some of your keybinds failed to register.
|
||||
</BaseText>
|
||||
</Card>
|
||||
)}
|
||||
|
||||
{keyBinds.map((keyBind, index) => (
|
||||
<KeyBindGroup
|
||||
key={keyBind.id}
|
||||
@@ -147,3 +170,24 @@ export function KeyBindSettings() {
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export function openKeybindsModal() {
|
||||
openModal(props => (
|
||||
<ModalRoot {...props} size={ModalSize.MEDIUM}>
|
||||
<ModalHeader>
|
||||
<BaseText size="lg" weight="semibold" tag="h3" style={{ flexGrow: 1 }}>
|
||||
Keybinds
|
||||
</BaseText>
|
||||
<ModalCloseButton onClick={props.onClose} />
|
||||
</ModalHeader>
|
||||
|
||||
<ModalContent>
|
||||
<KeyBindSettings />
|
||||
</ModalContent>
|
||||
</ModalRoot>
|
||||
));
|
||||
}
|
||||
|
||||
export const KeybindsButton: SettingsComponent = () => {
|
||||
return <Button onClick={() => openKeybindsModal()}>Configure Keybinds</Button>;
|
||||
};
|
||||
|
||||
@@ -90,7 +90,7 @@ export async function recordKeybind(signal?: AbortSignal): Promise<string> {
|
||||
}
|
||||
|
||||
function cleanup() {
|
||||
document.removeEventListener("keydown", onKeyDown);
|
||||
document.removeEventListener("keydown", onKeyDown, { capture: true });
|
||||
signal?.removeEventListener("abort", onAbort);
|
||||
}
|
||||
|
||||
@@ -101,6 +101,7 @@ export async function recordKeybind(signal?: AbortSignal): Promise<string> {
|
||||
|
||||
function onKeyDown(event: KeyboardEvent) {
|
||||
event.preventDefault();
|
||||
event.stopImmediatePropagation();
|
||||
|
||||
if (MODIFIER_KEYS.has(event.key)) return;
|
||||
|
||||
@@ -120,6 +121,6 @@ export async function recordKeybind(signal?: AbortSignal): Promise<string> {
|
||||
}
|
||||
|
||||
signal?.addEventListener("abort", onAbort, { once: true });
|
||||
document.addEventListener("keydown", onKeyDown);
|
||||
document.addEventListener("keydown", onKeyDown, { capture: true });
|
||||
});
|
||||
}
|
||||
|
||||
@@ -54,4 +54,4 @@ onIpcCommand(IpcCommands.GET_LANGUAGES, () => navigator.languages);
|
||||
|
||||
onIpcCommand(IpcCommands.SCREEN_SHARE_PICKER, data => openScreenSharePicker(data.screens, data.skipPicker));
|
||||
|
||||
onIpcCommand(IpcCommands.HANDLE_KEY_BIND, handleKeyBind);
|
||||
onIpcCommand(IpcCommands.KEY_BINDS_HANDLE, handleKeyBind);
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
/*
|
||||
* Vesktop, a desktop app aiming to give you a snappier Discord Experience
|
||||
* Copyright (c) 2026 Vendicated and Vesktop contributors
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { Button, ErrorBoundary } from "@vencord/types/components";
|
||||
import { openKeybindsModal } from "renderer/globalShortcuts/ShortcutSettings";
|
||||
|
||||
import { addPatch } from "./shared";
|
||||
|
||||
addPatch({
|
||||
patches: [
|
||||
{
|
||||
find: "#{intl::KEYBIND_IN_BROSWER_NOTICE}",
|
||||
replacement: {
|
||||
match: /:(.{0,10}jsx.{0,50}messageType:.{0,100}#{intl::KEYBIND_IN_BROSWER_NOTICE})/,
|
||||
replace: ":true?$self.renderKeybindsButton():$1"
|
||||
}
|
||||
}
|
||||
],
|
||||
|
||||
renderKeybindsButton: ErrorBoundary.wrap(
|
||||
() => <Button onClick={() => openKeybindsModal()}>Configure Vesktop Keybinds</Button>,
|
||||
{ noop: true }
|
||||
)
|
||||
});
|
||||
@@ -0,0 +1,40 @@
|
||||
/*
|
||||
* Vesktop, a desktop app aiming to give you a snappier Discord Experience
|
||||
* Copyright (c) 2026 Vendicated and Vesktop contributors
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { useForceUpdater } from "@vencord/types/utils";
|
||||
import { useEffect } from "@vencord/types/webpack/common";
|
||||
|
||||
interface ReactiveValue<T> {
|
||||
value: T;
|
||||
use(): T;
|
||||
}
|
||||
|
||||
export function reactiveValue<T>(initialValue: T): ReactiveValue<T>;
|
||||
export function reactiveValue<T>(initialValue?: T): ReactiveValue<T | undefined>;
|
||||
export function reactiveValue<T>(initialValue?: T) {
|
||||
const changeListeners = new Set<() => void>();
|
||||
let value = initialValue;
|
||||
|
||||
return {
|
||||
get value() {
|
||||
return value;
|
||||
},
|
||||
set value(newValue) {
|
||||
value = newValue;
|
||||
changeListeners.forEach(cb => cb());
|
||||
},
|
||||
use() {
|
||||
const update = useForceUpdater();
|
||||
|
||||
useEffect(() => {
|
||||
changeListeners.add(update);
|
||||
return () => void changeListeners.delete(update);
|
||||
}, []);
|
||||
|
||||
return value;
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -85,5 +85,6 @@ export const enum IpcCommands {
|
||||
|
||||
SCREEN_SHARE_PICKER = "screenshare:picker",
|
||||
|
||||
HANDLE_KEY_BIND = "keyBinds:handle"
|
||||
KEY_BINDS_HANDLE = "keyBinds:handle",
|
||||
KEY_BINDS_SET_STATUS = "keyBinds:setStatus"
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user