feat(keybinds): basic implementation

This commit is contained in:
D3SOX 2024-02-01 16:00:11 +01:00
parent 6993b2a7d4
commit 073aa6621f
No known key found for this signature in database
GPG key ID: 39EC1673FC37B048
8 changed files with 192 additions and 5 deletions

View file

@ -16,6 +16,7 @@ import { registerMediaPermissionsHandler } from "./mediaPermissions";
import { registerScreenShareHandler } from "./screenShare";
import { Settings, State } from "./settings";
import { isDeckGameMode } from "./utils/steamOS";
import { registerKeybindsHandler } from "./keybinds";
if (IS_DEV) {
require("source-map-support").install();
@ -62,6 +63,7 @@ function init() {
registerScreenShareHandler();
registerMediaPermissionsHandler();
registerKeybindsHandler();
bootstrap();

71
src/main/keybinds.ts Normal file
View 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;
}

View file

@ -454,20 +454,20 @@ export async function createWindows() {
splash.destroy();
if (!startMinimized) {
mainWin!.show();
if (State.store.maximized && !isDeckGameMode) mainWin!.maximize();
mainWin.show();
if (State.store.maximized && !isDeckGameMode) mainWin.maximize();
}
if (isDeckGameMode) {
// always use entire display
mainWin!.setFullScreen(true);
mainWin.setFullScreen(true);
askToApplySteamLayout(mainWin);
}
mainWin.once("show", () => {
if (State.store.maximized && !mainWin!.isMaximized() && !isDeckGameMode) {
mainWin!.maximize();
if (State.store.maximized && !mainWin.isMaximized() && !isDeckGameMode) {
mainWin.maximize();
}
});
});

View file

@ -59,6 +59,11 @@ export const VesktopNative = {
capturer: {
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. */
virtmic: {
list: () =>

View file

@ -6,6 +6,7 @@
// TODO: Possibly auto generate glob if we have more patches in the future
import "./enableNotificationsByDefault";
import "./keybinds";
import "./platformClass";
import "./screenShareAudio";
import "./spellCheck";

View 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);
}
}
}

View file

@ -38,6 +38,9 @@ export const enum IpcEvents {
CAPTURER_GET_LARGE_THUMBNAIL = "VCD_CAPTURER_GET_LARGE_THUMBNAIL",
KEYBIND_UNREGISTER = "VCD_KEYBIND_UNREGISTER",
KEYBIND_REGISTER = "VCD_KEYBIND_REGISTER",
AUTOSTART_ENABLED = "VCD_AUTOSTART_ENABLED",
ENABLE_AUTOSTART = "VCD_ENABLE_AUTOSTART",
DISABLE_AUTOSTART = "VCD_DISABLE_AUTOSTART",

23
src/shared/keybinds.d.ts vendored Normal file
View 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>;
}