feat(keybinds): basic implementation
This commit is contained in:
parent
6993b2a7d4
commit
073aa6621f
8 changed files with 192 additions and 5 deletions
|
@ -16,6 +16,7 @@ import { registerMediaPermissionsHandler } from "./mediaPermissions";
|
||||||
import { registerScreenShareHandler } from "./screenShare";
|
import { registerScreenShareHandler } from "./screenShare";
|
||||||
import { Settings, State } from "./settings";
|
import { Settings, State } from "./settings";
|
||||||
import { isDeckGameMode } from "./utils/steamOS";
|
import { isDeckGameMode } from "./utils/steamOS";
|
||||||
|
import { registerKeybindsHandler } from "./keybinds";
|
||||||
|
|
||||||
if (IS_DEV) {
|
if (IS_DEV) {
|
||||||
require("source-map-support").install();
|
require("source-map-support").install();
|
||||||
|
@ -62,6 +63,7 @@ function init() {
|
||||||
|
|
||||||
registerScreenShareHandler();
|
registerScreenShareHandler();
|
||||||
registerMediaPermissionsHandler();
|
registerMediaPermissionsHandler();
|
||||||
|
registerKeybindsHandler();
|
||||||
|
|
||||||
bootstrap();
|
bootstrap();
|
||||||
|
|
||||||
|
|
71
src/main/keybinds.ts
Normal file
71
src/main/keybinds.ts
Normal file
|
@ -0,0 +1,71 @@
|
||||||
|
/*
|
||||||
|
* 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 { globalShortcut } from "electron";
|
||||||
|
|
||||||
|
import { IpcEvents } from "../shared/IpcEvents";
|
||||||
|
import { handle } from "./utils/ipcWrappers";
|
||||||
|
|
||||||
|
// mapping the discord ids to the electron accelerators
|
||||||
|
const registeredKeybinds = new Map<string, string>();
|
||||||
|
|
||||||
|
export function registerKeybindsHandler() {
|
||||||
|
handle(IpcEvents.KEYBIND_REGISTER, async (_, id: string, shortcut: number[][], callback: () => void) => {
|
||||||
|
const accelerator = discordShortcutToElectronAccelerator(shortcut);
|
||||||
|
globalShortcut.register(accelerator, callback);
|
||||||
|
registeredKeybinds.set(id, accelerator);
|
||||||
|
console.log("Registered keybind", id, shortcut, accelerator);
|
||||||
|
});
|
||||||
|
handle(IpcEvents.KEYBIND_UNREGISTER, async (_, id: string) => {
|
||||||
|
const keybind = registeredKeybinds.get(id);
|
||||||
|
if (keybind) {
|
||||||
|
globalShortcut.unregister(keybind);
|
||||||
|
registeredKeybinds.delete(id);
|
||||||
|
console.log("Unregistered keybind", id, keybind);
|
||||||
|
} else {
|
||||||
|
console.warn("Tried to unregister keybind", id, "but it was not registered");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function discordShortcutToElectronAccelerator(shortcut: number[][]): string {
|
||||||
|
// discords shortcuts are an array of an array of numbers, where each number is a key code
|
||||||
|
// electron expects strings like "Control+Shift+B"
|
||||||
|
|
||||||
|
let accelerator = "";
|
||||||
|
|
||||||
|
// for some reason array[0] is always 0 and array[2] is always 4 TODO: investigate what these are for
|
||||||
|
const keyCodes = shortcut.map(keybind => keybind[1]);
|
||||||
|
|
||||||
|
// convert modifier keys to strings
|
||||||
|
// 16 = shift, 17 = control, 18 = alt
|
||||||
|
for (const keyCode of keyCodes) {
|
||||||
|
switch (keyCode) {
|
||||||
|
case 16:
|
||||||
|
accelerator += "Shift+";
|
||||||
|
break;
|
||||||
|
case 17:
|
||||||
|
accelerator += "Control+";
|
||||||
|
break;
|
||||||
|
case 18:
|
||||||
|
accelerator += "Alt+";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// convert other keys to strings
|
||||||
|
// numbers are from 48 to 57
|
||||||
|
// letters are from 65 to 90
|
||||||
|
for (const keyCode of keyCodes) {
|
||||||
|
if ((keyCode >= 48 && keyCode <= 57) || (keyCode >= 65 && keyCode <= 90)) {
|
||||||
|
// String.fromCharCode only works for numbers and letters so only those are support as of now
|
||||||
|
// TODO: improve this method to add support for more keys
|
||||||
|
accelerator += String.fromCharCode(keyCode);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return accelerator;
|
||||||
|
}
|
|
@ -454,20 +454,20 @@ export async function createWindows() {
|
||||||
splash.destroy();
|
splash.destroy();
|
||||||
|
|
||||||
if (!startMinimized) {
|
if (!startMinimized) {
|
||||||
mainWin!.show();
|
mainWin.show();
|
||||||
if (State.store.maximized && !isDeckGameMode) mainWin!.maximize();
|
if (State.store.maximized && !isDeckGameMode) mainWin.maximize();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isDeckGameMode) {
|
if (isDeckGameMode) {
|
||||||
// always use entire display
|
// always use entire display
|
||||||
mainWin!.setFullScreen(true);
|
mainWin.setFullScreen(true);
|
||||||
|
|
||||||
askToApplySteamLayout(mainWin);
|
askToApplySteamLayout(mainWin);
|
||||||
}
|
}
|
||||||
|
|
||||||
mainWin.once("show", () => {
|
mainWin.once("show", () => {
|
||||||
if (State.store.maximized && !mainWin!.isMaximized() && !isDeckGameMode) {
|
if (State.store.maximized && !mainWin.isMaximized() && !isDeckGameMode) {
|
||||||
mainWin!.maximize();
|
mainWin.maximize();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -59,6 +59,11 @@ export const VesktopNative = {
|
||||||
capturer: {
|
capturer: {
|
||||||
getLargeThumbnail: (id: string) => invoke<string>(IpcEvents.CAPTURER_GET_LARGE_THUMBNAIL, id)
|
getLargeThumbnail: (id: string) => invoke<string>(IpcEvents.CAPTURER_GET_LARGE_THUMBNAIL, id)
|
||||||
},
|
},
|
||||||
|
keybinds: {
|
||||||
|
register: (id: string, shortcut: number[][], callback: () => void) =>
|
||||||
|
invoke<void>(IpcEvents.KEYBIND_REGISTER, id, shortcut, callback),
|
||||||
|
unregister: (id: string) => invoke<void>(IpcEvents.KEYBIND_UNREGISTER, id)
|
||||||
|
},
|
||||||
/** only available on Linux. */
|
/** only available on Linux. */
|
||||||
virtmic: {
|
virtmic: {
|
||||||
list: () =>
|
list: () =>
|
||||||
|
|
|
@ -6,6 +6,7 @@
|
||||||
|
|
||||||
// TODO: Possibly auto generate glob if we have more patches in the future
|
// TODO: Possibly auto generate glob if we have more patches in the future
|
||||||
import "./enableNotificationsByDefault";
|
import "./enableNotificationsByDefault";
|
||||||
|
import "./keybinds";
|
||||||
import "./platformClass";
|
import "./platformClass";
|
||||||
import "./screenShareAudio";
|
import "./screenShareAudio";
|
||||||
import "./spellCheck";
|
import "./spellCheck";
|
||||||
|
|
82
src/renderer/patches/keybinds.tsx
Normal file
82
src/renderer/patches/keybinds.tsx
Normal file
|
@ -0,0 +1,82 @@
|
||||||
|
/*
|
||||||
|
* 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 { findByPropsLazy, findStoreLazy, onceReady } from "@vencord/types/webpack";
|
||||||
|
import { FluxDispatcher } from "@vencord/types/webpack/common";
|
||||||
|
|
||||||
|
import { Keybind, KeybindsStoreType } from "../../shared/keybinds";
|
||||||
|
import { addPatch } from "./shared";
|
||||||
|
|
||||||
|
const KeybindsStore: KeybindsStoreType = findStoreLazy("KeybindsStore");
|
||||||
|
const MuteActions: {
|
||||||
|
toggleSelfMute: () => void;
|
||||||
|
toggleSelfDeaf: () => void;
|
||||||
|
} = findByPropsLazy("MuteActions");
|
||||||
|
|
||||||
|
// Re-enable Discord's keybind editor
|
||||||
|
addPatch({
|
||||||
|
patches: [
|
||||||
|
{
|
||||||
|
// maybe one day they will fix the typo "broswer"
|
||||||
|
find: "Messages.KEYBIND_IN_BROSWER_NOTICE",
|
||||||
|
replacement: [
|
||||||
|
{
|
||||||
|
match: /(\i)\.isPlatformEmbedded/,
|
||||||
|
replace: "true"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
});
|
||||||
|
|
||||||
|
onceReady.then(() => {
|
||||||
|
// register keybinds on load
|
||||||
|
toggleAllKeybinds(true);
|
||||||
|
|
||||||
|
// we only need this event as this gets fired when the keybinds page is opened/closed and this is the only place where we need to adjust our keybinds
|
||||||
|
FluxDispatcher.subscribe("KEYBINDS_ENABLE_ALL_KEYBINDS", ({ enable }: { enable: boolean }) => {
|
||||||
|
console.log("keybinds enable all keybinds", enable);
|
||||||
|
toggleAllKeybinds(enable);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
function shouldAllowKeybind(keybind: Keybind) {
|
||||||
|
return keybind.enabled && !keybind.managed && keybind.shortcut && keybind.action !== "UNASSIGNED";
|
||||||
|
}
|
||||||
|
|
||||||
|
function getKeybindHandler(action: Keybind["action"]) {
|
||||||
|
return () => {
|
||||||
|
// execute the action
|
||||||
|
switch (action) {
|
||||||
|
case "TOGGLE_MUTE":
|
||||||
|
MuteActions.toggleSelfMute();
|
||||||
|
break;
|
||||||
|
case "TOGGLE_DEAFEN":
|
||||||
|
MuteActions.toggleSelfDeaf();
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
console.warn("Unknown keybind action", action);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function toggleAllKeybinds(enable: boolean) {
|
||||||
|
const keybinds = KeybindsStore.getState();
|
||||||
|
for (const keybind of Object.values(keybinds)) {
|
||||||
|
const { id, shortcut, action } = keybind;
|
||||||
|
if (!shouldAllowKeybind(keybind)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (enable) {
|
||||||
|
VesktopNative.keybinds.register(id, shortcut, getKeybindHandler(action));
|
||||||
|
console.log("keybind registered", action);
|
||||||
|
} else {
|
||||||
|
VesktopNative.keybinds.unregister(id);
|
||||||
|
console.log("keybind unregistered", action);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -38,6 +38,9 @@ export const enum IpcEvents {
|
||||||
|
|
||||||
CAPTURER_GET_LARGE_THUMBNAIL = "VCD_CAPTURER_GET_LARGE_THUMBNAIL",
|
CAPTURER_GET_LARGE_THUMBNAIL = "VCD_CAPTURER_GET_LARGE_THUMBNAIL",
|
||||||
|
|
||||||
|
KEYBIND_UNREGISTER = "VCD_KEYBIND_UNREGISTER",
|
||||||
|
KEYBIND_REGISTER = "VCD_KEYBIND_REGISTER",
|
||||||
|
|
||||||
AUTOSTART_ENABLED = "VCD_AUTOSTART_ENABLED",
|
AUTOSTART_ENABLED = "VCD_AUTOSTART_ENABLED",
|
||||||
ENABLE_AUTOSTART = "VCD_ENABLE_AUTOSTART",
|
ENABLE_AUTOSTART = "VCD_ENABLE_AUTOSTART",
|
||||||
DISABLE_AUTOSTART = "VCD_DISABLE_AUTOSTART",
|
DISABLE_AUTOSTART = "VCD_DISABLE_AUTOSTART",
|
||||||
|
|
23
src/shared/keybinds.d.ts
vendored
Normal file
23
src/shared/keybinds.d.ts
vendored
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
/*
|
||||||
|
* 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 { Constants } from "discord-types/other";
|
||||||
|
|
||||||
|
type ValueOf<T> = T[keyof T];
|
||||||
|
export type GlobalKeybindAction = ValueOf<Constants["GlobalKeybindActions"]>;
|
||||||
|
|
||||||
|
export interface Keybind {
|
||||||
|
id: string;
|
||||||
|
enabled: boolean;
|
||||||
|
action: GlobalKeybindAction;
|
||||||
|
shortcut: number[][];
|
||||||
|
managed: boolean;
|
||||||
|
params: any;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface KeybindsStoreType {
|
||||||
|
getState(): Record<number, Keybind>;
|
||||||
|
}
|
Reference in a new issue