/* * Vencord, a Discord client mod * Copyright (c) 2026 Evandro * SPDX-License-Identifier: GPL-3.0-or-later */ import { IS_LINUX } from "@utils/constants"; import { Logger } from "@utils/Logger"; import definePlugin from "@utils/types"; import { findStoreLazy } from "@webpack"; import { adjustOptions, Callback, createDispatcher, Dispatcher, fixPortalState, KeyEventOptions, planRegistration, PortalState, Scheduler } from "./logic"; import { PATCHES } from "./patches"; type Shortcut = [number, number, ...unknown[]][]; // Discord's own native keybind bridge (the object the KeybindsStore registers through) interface Bridge { inputEventRegister(id: number, shortcut: Shortcut, callback: Callback, options: KeyEventOptions): void; inputEventUnregister(id: number): void; } const dispatchers = new Map(); const scheduler: Scheduler = { now: () => performance.now(), setTimeout: (fn, ms) => setTimeout(fn, ms), clearTimeout: handle => clearTimeout(handle as ReturnType) }; const logger = new Logger("WaylandKeybinds"); const KeybindsStore = findStoreLazy("KeybindsStore"); function discordUtils() { return DiscordNative.nativeModules.requireModule("discord_utils"); } function portalTrigger(shortcut: Shortcut): string { return discordUtils().getWaylandPortalTrigger?.(shortcut.map(([type, code]) => [type, code])) ?? ""; } export default definePlugin({ name: "WaylandKeybinds", description: "Makes Discord's global keybinds reliable on GNOME Wayland: toggles (mute, deafen) fire on key press and push-to-talk recovers from key releases GNOME drops", authors: [{ name: "Evandro", id: 281463271758888960n }], // only changes behaviour when Discord registers keybinds through the Wayland GlobalShortcuts portal enabledByDefault: true, patches: PATCHES, wrapRegistration(bridge: Bridge, id: number, shortcut: Shortcut, callback: Callback, options: KeyEventOptions): [Callback, KeyEventOptions] { try { const portalActive = IS_LINUX && discordUtils().isWaylandGlobalShortcutsAvailable?.() === true; const mode = planRegistration(options, portalActive, portalActive && portalTrigger(shortcut) !== ""); const adjusted = adjustOptions(options, mode); dispatchers.get(id)?.cancel(); const dispatcher = createDispatcher(callback, mode, () => { bridge.inputEventUnregister(id); bridge.inputEventRegister(id, shortcut, dispatcher.dispatch, adjusted); }, scheduler); dispatchers.set(id, dispatcher); return [dispatcher.dispatch, adjusted]; } catch (e) { logger.error("Failed to adapt keybind registration", e); return [callback, options]; } }, unregister(bridge: Bridge, id: number) { // stop probing first, or the next re-registration would bring a deleted keybind back dispatchers.get(id)?.cancel(); dispatchers.delete(id); bridge.inputEventUnregister(id); }, fixPortalState(state: S): S { try { const shortcut: Shortcut | undefined = KeybindsStore.getUserAgnosticState()?.[state.eventId]?.shortcut; return fixPortalState(state, shortcut?.length ? portalTrigger(shortcut) : undefined); } catch (e) { logger.error("Failed to check portal shortcut state", e); return state; } } });