import type { ActionInvocation, ActionMetadata } from '@yaakapp-internal/proxy-lib'; import { rpc } from './rpc'; type ActionBinding = { invocation: ActionInvocation; meta: ActionMetadata; keys: { key: string; ctrl: boolean; shift: boolean; alt: boolean; meta: boolean }; }; /** Parse a hotkey string like "Ctrl+Shift+P" into its parts. */ function parseHotkey(hotkey: string): ActionBinding['keys'] { const parts = hotkey.split('+').map((p) => p.trim().toLowerCase()); return { ctrl: parts.includes('ctrl') || parts.includes('control'), shift: parts.includes('shift'), alt: parts.includes('alt'), meta: parts.includes('meta') || parts.includes('cmd') || parts.includes('command'), key: parts.filter( (p) => !['ctrl', 'control', 'shift', 'alt', 'meta', 'cmd', 'command'].includes(p), )[0] ?? '', }; } function matchesEvent(binding: ActionBinding['keys'], e: KeyboardEvent): boolean { return ( e.ctrlKey === binding.ctrl && e.shiftKey === binding.shift && e.altKey === binding.alt && e.metaKey === binding.meta && e.key.toLowerCase() === binding.key ); } /** Fetch all actions from Rust and register a global keydown listener. */ export async function initHotkeys(): Promise<() => void> { const { actions } = await rpc('list_actions', {}); const bindings: ActionBinding[] = actions .filter( (entry): entry is [ActionInvocation, ActionMetadata & { defaultHotkey: string }] => entry[1].defaultHotkey != null, ) .map(([invocation, meta]) => ({ invocation, meta, keys: parseHotkey(meta.defaultHotkey), })); function onKeyDown(e: KeyboardEvent) { for (const binding of bindings) { if (matchesEvent(binding.keys, e)) { e.preventDefault(); rpc('execute_action', binding.invocation); return; } } } window.addEventListener('keydown', onKeyDown); return () => window.removeEventListener('keydown', onKeyDown); }