- Info panel in the plugin modal: live status with the number of keybinds GNOME registered, the three setup steps and a button that opens Discord's Keybinds page (closes only the plugin modal, which otherwise blocks settings navigation). - Follows Discord's language: English, Portuguese (pt-BR) and Spanish (es-ES and es-419 use each region's action names); bold labels quote Discord's own strings. - Shared helpers moved to discord.ts; copy and locale choice in copy.ts, covered by basis-path tests (41 tests). - README: current settings path (User Settings > System > Keybinds).
76 lines
3.1 KiB
TypeScript
76 lines
3.1 KiB
TypeScript
/*
|
|
* Vencord, a Discord client mod
|
|
* Copyright (c) 2026 Evandro
|
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
|
*/
|
|
|
|
import { Logger } from "@utils/Logger";
|
|
import definePlugin from "@utils/types";
|
|
|
|
import { AboutPanel } from "./about";
|
|
import { KeybindsStore, portalActive, portalTrigger, Shortcut } from "./discord";
|
|
import { adjustOptions, Callback, createDispatcher, Dispatcher, fixPortalState, KeyEventOptions, planRegistration, PortalState, Scheduler } from "./logic";
|
|
import { PATCHES } from "./patches";
|
|
import managedStyle from "./style.css?managed";
|
|
|
|
// 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<number, Dispatcher>();
|
|
const scheduler: Scheduler = {
|
|
now: () => performance.now(),
|
|
setTimeout: (fn, ms) => setTimeout(fn, ms),
|
|
clearTimeout: handle => clearTimeout(handle as ReturnType<typeof setTimeout>)
|
|
};
|
|
|
|
const logger = new Logger("WaylandKeybinds");
|
|
|
|
export default definePlugin({
|
|
name: "WaylandKeybinds",
|
|
description: "Makes Discord's keybinds work in every app on Linux Wayland: set them up in Discord's Keybinds page as usual and allow the system prompt",
|
|
authors: [{ name: "Evandro", id: 281463271758888960n }],
|
|
// only changes behaviour when Discord registers keybinds through the Wayland GlobalShortcuts portal
|
|
enabledByDefault: true,
|
|
patches: PATCHES,
|
|
managedStyle,
|
|
settingsAboutComponent: AboutPanel,
|
|
|
|
wrapRegistration(bridge: Bridge, id: number, shortcut: Shortcut, callback: Callback, options: KeyEventOptions): [Callback, KeyEventOptions] {
|
|
try {
|
|
const active = portalActive();
|
|
const mode = planRegistration(options, active, active && 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<S extends PortalState & { eventId: number; }>(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;
|
|
}
|
|
}
|
|
});
|