diff --git a/package.json b/package.json index ca5cf495..d8d3fedd 100644 --- a/package.json +++ b/package.json @@ -25,7 +25,8 @@ }, "dependencies": { "arrpc": "github:OpenAsar/arrpc#c62ec6a04c8d870530aa6944257fe745f6c59a24", - "electron-updater": "^6.2.1" + "electron-updater": "^6.2.1", + "venbind": "^0.0.2" }, "optionalDependencies": { "@vencord/venmic": "^6.1.0" diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index e67ad18f..64b614cb 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -19,6 +19,9 @@ importers: electron-updater: specifier: ^6.2.1 version: 6.2.1 + venbind: + specifier: ^0.0.2 + version: 0.0.2 optionalDependencies: '@vencord/venmic': specifier: ^6.1.0 @@ -2784,6 +2787,9 @@ packages: util-deprecate@1.0.2: resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} + venbind@0.0.2: + resolution: {integrity: sha512-TlkghnnV43lmINTMOuZWvgRIIZJ3I5oNykXbGZPC22GE7gdoen9uPBWWEKLtN7BG4wjtT/dIAv2Xz9gHz9P4Gw==} + verror@1.10.1: resolution: {integrity: sha512-veufcmxri4e3XSrT0xwfUR7kguIkaxBeosDg00yDWhk49wdwkSUrvvsm7nc75e1PUyvIeZj6nS8VQRYz2/S4Xg==} engines: {node: '>=0.6.0'} @@ -3441,7 +3447,7 @@ snapshots: app-builder-bin@5.0.0-alpha.6: {} - app-builder-lib@24.13.3(dmg-builder@25.0.1(electron-builder-squirrel-windows@24.13.3(dmg-builder@25.0.1)))(electron-builder-squirrel-windows@24.13.3(dmg-builder@25.0.1)): + app-builder-lib@24.13.3(dmg-builder@25.0.1(electron-builder-squirrel-windows@24.13.3))(electron-builder-squirrel-windows@24.13.3(dmg-builder@25.0.1)): dependencies: '@develar/schema-utils': 2.6.5 '@electron/notarize': 2.2.1 @@ -3455,7 +3461,7 @@ snapshots: builder-util-runtime: 9.2.4 chromium-pickle-js: 0.2.0 debug: 4.3.5 - dmg-builder: 25.0.1(electron-builder-squirrel-windows@24.13.3(dmg-builder@25.0.1)) + dmg-builder: 25.0.1(electron-builder-squirrel-windows@24.13.3) ejs: 3.1.10 electron-builder-squirrel-windows: 24.13.3(dmg-builder@25.0.1) electron-publish: 24.13.1 @@ -3475,7 +3481,7 @@ snapshots: transitivePeerDependencies: - supports-color - app-builder-lib@25.0.1(dmg-builder@25.0.1(electron-builder-squirrel-windows@24.13.3(dmg-builder@25.0.1)))(electron-builder-squirrel-windows@24.13.3(dmg-builder@25.0.1)): + app-builder-lib@25.0.1(dmg-builder@25.0.1(electron-builder-squirrel-windows@24.13.3))(electron-builder-squirrel-windows@24.13.3(dmg-builder@25.0.1)): dependencies: '@develar/schema-utils': 2.6.5 '@electron/notarize': 2.3.2 @@ -3490,7 +3496,7 @@ snapshots: builder-util-runtime: 9.2.5 chromium-pickle-js: 0.2.0 debug: 4.3.5 - dmg-builder: 25.0.1(electron-builder-squirrel-windows@24.13.3(dmg-builder@25.0.1)) + dmg-builder: 25.0.1(electron-builder-squirrel-windows@24.13.3) ejs: 3.1.10 electron-builder-squirrel-windows: 24.13.3(dmg-builder@25.0.1) electron-publish: 25.0.1 @@ -4051,9 +4057,9 @@ snapshots: '@types/react': 17.0.2 moment: 2.30.1 - dmg-builder@25.0.1(electron-builder-squirrel-windows@24.13.3(dmg-builder@25.0.1)): + dmg-builder@25.0.1(electron-builder-squirrel-windows@24.13.3): dependencies: - app-builder-lib: 25.0.1(dmg-builder@25.0.1(electron-builder-squirrel-windows@24.13.3(dmg-builder@25.0.1)))(electron-builder-squirrel-windows@24.13.3(dmg-builder@25.0.1)) + app-builder-lib: 25.0.1(dmg-builder@25.0.1(electron-builder-squirrel-windows@24.13.3))(electron-builder-squirrel-windows@24.13.3(dmg-builder@25.0.1)) builder-util: 25.0.1 builder-util-runtime: 9.2.5 fs-extra: 10.1.0 @@ -4104,7 +4110,7 @@ snapshots: electron-builder-squirrel-windows@24.13.3(dmg-builder@25.0.1): dependencies: - app-builder-lib: 24.13.3(dmg-builder@25.0.1(electron-builder-squirrel-windows@24.13.3(dmg-builder@25.0.1)))(electron-builder-squirrel-windows@24.13.3(dmg-builder@25.0.1)) + app-builder-lib: 24.13.3(dmg-builder@25.0.1(electron-builder-squirrel-windows@24.13.3))(electron-builder-squirrel-windows@24.13.3(dmg-builder@25.0.1)) archiver: 5.3.2 builder-util: 24.13.1 fs-extra: 10.1.0 @@ -4114,11 +4120,11 @@ snapshots: electron-builder@25.0.1(electron-builder-squirrel-windows@24.13.3(dmg-builder@25.0.1)): dependencies: - app-builder-lib: 25.0.1(dmg-builder@25.0.1(electron-builder-squirrel-windows@24.13.3(dmg-builder@25.0.1)))(electron-builder-squirrel-windows@24.13.3(dmg-builder@25.0.1)) + app-builder-lib: 25.0.1(dmg-builder@25.0.1(electron-builder-squirrel-windows@24.13.3))(electron-builder-squirrel-windows@24.13.3(dmg-builder@25.0.1)) builder-util: 25.0.1 builder-util-runtime: 9.2.5 chalk: 4.1.2 - dmg-builder: 25.0.1(electron-builder-squirrel-windows@24.13.3(dmg-builder@25.0.1)) + dmg-builder: 25.0.1(electron-builder-squirrel-windows@24.13.3) fs-extra: 10.1.0 is-ci: 3.0.1 lazy-val: 1.0.5 @@ -5983,6 +5989,8 @@ snapshots: util-deprecate@1.0.2: {} + venbind@0.0.2: {} + verror@1.10.1: dependencies: assert-plus: 1.0.0 diff --git a/scripts/build/build.mts b/scripts/build/build.mts index 243381ba..4758d312 100644 --- a/scripts/build/build.mts +++ b/scripts/build/build.mts @@ -49,8 +49,18 @@ async function copyVenmic() { ]).catch(() => console.warn("Failed to copy venmic. Building without venmic support")); } +async function copyVenbind() { + return Promise.all([ + copyFile( + "./node_modules/venbind/prebuilds/linux-x86_64/venbind-linux-x86_64.node", + "./static/dist/venbind-linux-x86_64.node" + ) + ]).catch(() => console.warn("Failed to copy venbind. Building without venbind support")); +} + await Promise.all([ copyVenmic(), + copyVenbind(), createContext({ ...NodeCommonOpts, entryPoints: ["src/main/index.ts"], diff --git a/src/main/index.ts b/src/main/index.ts index 2e0d6f76..1fe2aeea 100644 --- a/src/main/index.ts +++ b/src/main/index.ts @@ -16,6 +16,7 @@ import { registerMediaPermissionsHandler } from "./mediaPermissions"; import { registerScreenShareHandler } from "./screenShare"; import { Settings, State } from "./settings"; import { isDeckGameMode } from "./utils/steamOS"; +import { startVenbind } from "./venbind"; if (IS_DEV) { require("source-map-support").install(); @@ -66,8 +67,18 @@ function init() { // In the Flatpak on SteamOS the theme is detected as light, but SteamOS only has a dark mode, so we just override it if (isDeckGameMode) nativeTheme.themeSource = "dark"; - app.on("second-instance", (_event, _cmdLine, _cwd, data: any) => { - if (data.IS_DEV) app.quit(); + app.on("second-instance", (_event, cmdLine, _cwd, data: any) => { + const keybindIndex = cmdLine.indexOf("--keybind"); + + if (keybindIndex !== -1) { + if (cmdLine[keybindIndex + 2] === "keyup" || cmdLine[keybindIndex + 2] === "keydown") { + mainWin.webContents.executeJavaScript( + `Vesktop.keybindCallbacks[${cmdLine[keybindIndex + 1]}](${cmdLine[keybindIndex + 2] === "keydown" ? "true" : "false"})` + ); + } else { + mainWin.webContents.executeJavaScript(`Vesktop.keybindCallbacks[${cmdLine[keybindIndex + 1]}](false)`); + } + } else if (data.IS_DEV) app.quit(); else if (mainWin) { if (mainWin.isMinimized()) mainWin.restore(); if (!mainWin.isVisible()) mainWin.show(); @@ -78,6 +89,7 @@ function init() { app.whenReady().then(async () => { if (process.platform === "win32") app.setAppUserModelId("dev.vencord.vesktop"); + startVenbind(); registerScreenShareHandler(); registerMediaPermissionsHandler(); @@ -90,15 +102,24 @@ function init() { } if (!app.requestSingleInstanceLock({ IS_DEV })) { - if (IS_DEV) { - console.log("Vesktop is already running. Quitting previous instance..."); - init(); - } else { - console.log("Vesktop is already running. Quitting..."); + if (process.argv.includes("--keybind")) { app.quit(); + } else { + if (IS_DEV) { + console.log("Vesktop is already running. Quitting previous instance..."); + init(); + } else { + console.log("Vesktop is already running. Quitting..."); + app.quit(); + } } } else { - init(); + if (process.argv.includes("--keybind")) { + console.error("No instances running! cannot issue a keybind!"); + app.quit(); + } else { + init(); + } } async function bootstrap() { diff --git a/src/main/venbind.ts b/src/main/venbind.ts new file mode 100644 index 00000000..5a075f1a --- /dev/null +++ b/src/main/venbind.ts @@ -0,0 +1,61 @@ +/* + * 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 { join } from "path"; +import { IpcEvents } from "shared/IpcEvents"; +import { STATIC_DIR } from "shared/paths"; +import type { Venbind as VenbindType } from "venbind"; + +import { mainWin } from "./mainWindow"; +import { handle } from "./utils/ipcWrappers"; + +let venbind: VenbindType | null = null; +export function obtainVenbind() { + if (venbind == null) { + // TODO?: make binary outputs consistant with node's apis + let os: string; + let arch: string; + + switch (process.platform) { + case "linux": + os = "linux"; + break; + // case "win32": + // os = "windows"; + // case "darwin": + // os = "darwin"; + default: + return null; + } + switch (process.arch) { + case "x64": + arch = "x86_64"; + break; + // case "arm64": + // arch = "aarch64"; + // break; + default: + return null; + } + + venbind = require(join(STATIC_DIR, `dist/venbind-${os}-${arch}.node`)); + } + return venbind; +} + +export function startVenbind() { + const venbind = obtainVenbind(); + venbind?.startKeybinds(null, x => { + mainWin.webContents.executeJavaScript(`Vesktop.keybindCallbacks[${x}](false)`); + }); +} + +handle(IpcEvents.KEYBIND_REGISTER, (_, id: number, shortcut: string, options: any) => { + obtainVenbind()?.registerKeybind(shortcut, id); +}); +handle(IpcEvents.KEYBIND_UNREGISTER, (_, id: number) => { + obtainVenbind()?.unregisterKeybind(id); +}); diff --git a/src/preload/VesktopNative.ts b/src/preload/VesktopNative.ts index 7a8b9775..5f93cfe9 100644 --- a/src/preload/VesktopNative.ts +++ b/src/preload/VesktopNative.ts @@ -78,5 +78,10 @@ export const VesktopNative = { clipboard: { copyImage: (imageBuffer: Uint8Array, imageSrc: string) => invoke(IpcEvents.CLIPBOARD_COPY_IMAGE, imageBuffer, imageSrc) + }, + keybind: { + register: (id: number, shortcut: string, options: any) => + invoke(IpcEvents.KEYBIND_REGISTER, id, shortcut), + unregister: (id: number) => invoke(IpcEvents.KEYBIND_UNREGISTER, id) } }; diff --git a/src/renderer/index.ts b/src/renderer/index.ts index e8ad31ca..01c80cea 100644 --- a/src/renderer/index.ts +++ b/src/renderer/index.ts @@ -21,6 +21,8 @@ export { Settings }; const InviteActions = findByPropsLazy("resolveInvite"); +export const keybindCallbacks: { [id: number]: Function } = {}; + export async function openInviteModal(code: string) { const { invite } = await InviteActions.resolveInvite(code, "Desktop Modal"); if (!invite) return false; diff --git a/src/renderer/patches/index.ts b/src/renderer/patches/index.ts index aabff3e0..ee419043 100644 --- a/src/renderer/patches/index.ts +++ b/src/renderer/patches/index.ts @@ -12,3 +12,4 @@ import "./hideVenmicInput"; import "./screenShareFixes"; import "./spellCheck"; import "./windowsTitleBar"; +import "./keybinds"; diff --git a/src/renderer/patches/keybinds.ts b/src/renderer/patches/keybinds.ts new file mode 100644 index 00000000..b8550c53 --- /dev/null +++ b/src/renderer/patches/keybinds.ts @@ -0,0 +1,61 @@ +/* + * 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 { findByCodeLazy } from "@vencord/types/webpack"; +import { keybindCallbacks } from "renderer"; + +import { addPatch } from "./shared"; +const toShortcutString = findByCodeLazy('return"gamepad".'); + +addPatch({ + patches: [ + { + find: "keybindActionTypes", + replacement: [ + { + // eslint-disable-next-line no-useless-escape + match: /\i\.isPlatformEmbedded/g, + replace: "true" + }, + { + // eslint-disable-next-line no-useless-escape + match: /\(0,\i\.isDesktop\)\(\)/g, + replace: "true" + }, + { + // THIS PATCH IS TEMPORARY + // eslint-disable-next-line no-useless-escape + match: /\.keybindGroup,\i.card\),children:\[/g, + replace: "$&`ID: ${this.props.keybind.id}`," + } + ] + }, + { + find: "[kb store] KeybindStore", + replacement: [ + { + // eslint-disable-next-line no-useless-escape + match: /inputEventRegister\((parseInt\(\i\),\i,\i,\i)\);else\{/, + replace: "$&$self.registerKeybind($1);return;" + }, + { + // eslint-disable-next-line no-useless-escape + match: /inputEventUnregister\((parseInt\(\i,10\))\);else/, + replace: "$&{$self.unregisterKeybind($1);return;}" + } + ] + } + ], + + registerKeybind: function (id, shortcut, callback, options) { + keybindCallbacks[id] = callback; + VesktopNative.keybind.register(id, toShortcutString(shortcut), options); + }, + unregisterKeybind: function (id) { + delete keybindCallbacks[id]; + VesktopNative.keybind.unregister(id); + } +}); diff --git a/src/shared/IpcEvents.ts b/src/shared/IpcEvents.ts index 51d2a281..62ea94c3 100644 --- a/src/shared/IpcEvents.ts +++ b/src/shared/IpcEvents.ts @@ -50,5 +50,7 @@ export const enum IpcEvents { ARRPC_ACTIVITY = "VCD_ARRPC_ACTIVITY", - CLIPBOARD_COPY_IMAGE = "VCD_CLIPBOARD_COPY_IMAGE" + CLIPBOARD_COPY_IMAGE = "VCD_CLIPBOARD_COPY_IMAGE", + KEYBIND_REGISTER = "VCD_KEYBIND_REGISTER", + KEYBIND_UNREGISTER = "VCD_KEYBIND_UNREGISTER" }