fixes etc
This commit is contained in:
@@ -13,3 +13,5 @@ export const AppEvents = new EventEmitter<{
|
|||||||
userAssetChanged: [UserAssetType];
|
userAssetChanged: [UserAssetType];
|
||||||
setTrayVariant: ["tray" | "trayUnread"];
|
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 { args } = data as { args: typeof CommandLine; IS_DEV: boolean };
|
||||||
const { "run-shortcut": shortcut } = args.values;
|
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 { ipcMain } from "electron";
|
||||||
import { IpcEvents } from "shared/IpcEvents";
|
import { IpcEvents } from "shared/IpcEvents";
|
||||||
|
|
||||||
|
import { appReady } from "./events";
|
||||||
import { mainWin } from "./mainWindow";
|
import { mainWin } from "./mainWindow";
|
||||||
|
|
||||||
const resolvers = new Map<string, Record<"resolve" | "reject", (data: any) => void>>();
|
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.
|
* 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()) {
|
if (mainWin.isDestroyed()) {
|
||||||
console.warn("Main window is destroyed, cannot send IPC command:", message);
|
console.warn("Main window is destroyed, cannot send IPC command:", message);
|
||||||
return Promise.reject(new Error("Main window is destroyed"));
|
return Promise.reject(new Error("Main window is destroyed"));
|
||||||
|
|||||||
@@ -12,7 +12,6 @@ import { Settings } from "./settings";
|
|||||||
|
|
||||||
function unregisterKeyBinds() {
|
function unregisterKeyBinds() {
|
||||||
if (!app.isReady()) return;
|
if (!app.isReady()) return;
|
||||||
|
|
||||||
globalShortcut.unregisterAll();
|
globalShortcut.unregisterAll();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -21,15 +20,18 @@ export async function registerKeyBinds() {
|
|||||||
|
|
||||||
unregisterKeyBinds();
|
unregisterKeyBinds();
|
||||||
|
|
||||||
|
let success = true;
|
||||||
|
|
||||||
for (const { action, enabled, key } of Settings.store.keyBinds || []) {
|
for (const { action, enabled, key } of Settings.store.keyBinds || []) {
|
||||||
if (!enabled || !key || action === "unassigned") continue;
|
if (!enabled || !key || action === "unassigned") continue;
|
||||||
|
|
||||||
console.log(
|
const registeredSuccessfully = globalShortcut.register(key, () =>
|
||||||
key,
|
sendRendererCommand(IpcCommands.KEY_BINDS_HANDLE, action)
|
||||||
"SUCCESS",
|
|
||||||
globalShortcut.register(key, () => sendRendererCommand(IpcCommands.HANDLE_KEY_BIND, action))
|
|
||||||
);
|
);
|
||||||
|
success &&= registeredSuccessfully;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sendRendererCommand(IpcCommands.KEY_BINDS_SET_STATUS, success);
|
||||||
}
|
}
|
||||||
|
|
||||||
Settings.addChangeListener("keyBinds", registerKeyBinds);
|
Settings.addChangeListener("keyBinds", registerKeyBinds);
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import "./settings.css";
|
|||||||
import { classNameFactory } from "@vencord/types/api/Styles";
|
import { classNameFactory } from "@vencord/types/api/Styles";
|
||||||
import { BaseText, Divider, ErrorBoundary } from "@vencord/types/components";
|
import { BaseText, Divider, ErrorBoundary } from "@vencord/types/components";
|
||||||
import { ComponentType } from "react";
|
import { ComponentType } from "react";
|
||||||
import { KeyBindSettings } from "renderer/globalShortcuts/ShortcutSettings";
|
import { KeybindsButton } from "renderer/globalShortcuts/ShortcutSettings";
|
||||||
import { Settings, useSettings } from "renderer/settings";
|
import { Settings, useSettings } from "renderer/settings";
|
||||||
import { isMac, isWindows } from "renderer/utils";
|
import { isMac, isWindows } from "renderer/utils";
|
||||||
|
|
||||||
@@ -123,7 +123,8 @@ const SettingsOptions: Record<string, Array<BooleanSetting | SettingsComponent>>
|
|||||||
title: "Disable smooth scrolling",
|
title: "Disable smooth scrolling",
|
||||||
description: "Disables smooth scrolling",
|
description: "Disables smooth scrolling",
|
||||||
defaultValue: false
|
defaultValue: false
|
||||||
}
|
},
|
||||||
|
KeybindsButton
|
||||||
],
|
],
|
||||||
Notifications: [
|
Notifications: [
|
||||||
NotificationBadgeToggle,
|
NotificationBadgeToggle,
|
||||||
@@ -149,7 +150,6 @@ const SettingsOptions: Record<string, Array<BooleanSetting | SettingsComponent>>
|
|||||||
defaultValue: false
|
defaultValue: false
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
KeyBinds: [KeyBindSettings],
|
|
||||||
"Developer Options": [DeveloperOptionsButton]
|
"Developer Options": [DeveloperOptionsButton]
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,13 @@
|
|||||||
|
.vcd-shortcuts-container {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 1em;
|
||||||
|
padding-block: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
.vcd-shortcuts-group {
|
.vcd-shortcuts-group {
|
||||||
display: grid;
|
display: grid;
|
||||||
gap: 0.25em 1em;
|
gap: 0.25em 0.5em;
|
||||||
grid-template-columns: minmax(0, 1fr) minmax(0, 1fr) auto auto;
|
grid-template-columns: minmax(0, 1fr) minmax(0, 1fr) auto auto;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
@@ -21,7 +28,8 @@
|
|||||||
border-radius: var(--radius-sm);
|
border-radius: var(--radius-sm);
|
||||||
width: 100%;
|
width: 100%;
|
||||||
min-height: calc(var(--select-option-height) - 2px);
|
min-height: calc(var(--select-option-height) - 2px);
|
||||||
padding: 0.25em;
|
padding: 3px 4px 3px 12px;
|
||||||
|
box-sizing: border-box;
|
||||||
}
|
}
|
||||||
|
|
||||||
.vcd-shortcuts-record-input {
|
.vcd-shortcuts-record-input {
|
||||||
|
|||||||
@@ -6,10 +6,23 @@
|
|||||||
|
|
||||||
import "./ShortcutSettings.css";
|
import "./ShortcutSettings.css";
|
||||||
|
|
||||||
import { BaseText, Button, DeleteIcon, Switch } from "@vencord/types/components";
|
import { BaseText, Button, Card, DeleteIcon, Switch } from "@vencord/types/components";
|
||||||
import { classNameFactory, identity } from "@vencord/types/utils";
|
import {
|
||||||
|
classNameFactory,
|
||||||
|
identity,
|
||||||
|
ModalCloseButton,
|
||||||
|
ModalContent,
|
||||||
|
ModalHeader,
|
||||||
|
ModalRoot,
|
||||||
|
ModalSize,
|
||||||
|
openModal
|
||||||
|
} from "@vencord/types/utils";
|
||||||
import { React, Select, useState } from "@vencord/types/webpack/common";
|
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 { Settings } from "renderer/settings";
|
||||||
|
import { IpcCommands } from "shared/IpcEvents";
|
||||||
|
|
||||||
import { recordKeybind } from "./recordKeybind";
|
import { recordKeybind } from "./recordKeybind";
|
||||||
|
|
||||||
@@ -20,13 +33,14 @@ const ShortcutActions = {
|
|||||||
toggleMute: "Toggle Mute",
|
toggleMute: "Toggle Mute",
|
||||||
toggleDeafen: "Toggle Deafen",
|
toggleDeafen: "Toggle Deafen",
|
||||||
toggleStreamerMode: "Toggle Streamer Mode",
|
toggleStreamerMode: "Toggle Streamer Mode",
|
||||||
toggleCamera: "Toggle Camera",
|
disconnectFromVoiceChannel: "Disconnect from VC"
|
||||||
toggleScreenShare: "Toggle Screen Share",
|
|
||||||
disconnectFromVoiceChannel: "Disconnect from Voice Channel"
|
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
export type ShortcutAction = keyof typeof ShortcutActions;
|
export type ShortcutAction = keyof typeof ShortcutActions;
|
||||||
|
|
||||||
|
const shortcutStatus = reactiveValue(true);
|
||||||
|
onIpcCommand(IpcCommands.KEY_BINDS_SET_STATUS, status => (shortcutStatus.value = status));
|
||||||
|
|
||||||
export interface KeyBind {
|
export interface KeyBind {
|
||||||
id: string;
|
id: string;
|
||||||
enabled: boolean;
|
enabled: boolean;
|
||||||
@@ -109,9 +123,18 @@ function KeyBindGroup({
|
|||||||
|
|
||||||
export function KeyBindSettings() {
|
export function KeyBindSettings() {
|
||||||
const [keyBinds, setKeyBinds] = useState<KeyBind[]>(Settings.store.keyBinds ?? []);
|
const [keyBinds, setKeyBinds] = useState<KeyBind[]>(Settings.store.keyBinds ?? []);
|
||||||
|
const shortcutsStatus = shortcutStatus.use();
|
||||||
|
|
||||||
return (
|
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) => (
|
{keyBinds.map((keyBind, index) => (
|
||||||
<KeyBindGroup
|
<KeyBindGroup
|
||||||
key={keyBind.id}
|
key={keyBind.id}
|
||||||
@@ -147,3 +170,24 @@ export function KeyBindSettings() {
|
|||||||
</div>
|
</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() {
|
function cleanup() {
|
||||||
document.removeEventListener("keydown", onKeyDown);
|
document.removeEventListener("keydown", onKeyDown, { capture: true });
|
||||||
signal?.removeEventListener("abort", onAbort);
|
signal?.removeEventListener("abort", onAbort);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -101,6 +101,7 @@ export async function recordKeybind(signal?: AbortSignal): Promise<string> {
|
|||||||
|
|
||||||
function onKeyDown(event: KeyboardEvent) {
|
function onKeyDown(event: KeyboardEvent) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
|
event.stopImmediatePropagation();
|
||||||
|
|
||||||
if (MODIFIER_KEYS.has(event.key)) return;
|
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 });
|
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.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",
|
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