Files
fluxer/fluxer_desktop/src/main/GlobalShortcutKeys.ts
T

720 lines
17 KiB
TypeScript

// SPDX-License-Identifier: AGPL-3.0-or-later
import {PUNCTUATION_KEY_CODES} from '@fluxer/desktop_ipc/src/GlobalShortcutKeyNames';
const EVDEV_KEY_CODES: ReadonlyMap<number, string> = new Map([
[0x001, 'Escape'],
[0x002, 'Digit1'],
[0x003, 'Digit2'],
[0x004, 'Digit3'],
[0x005, 'Digit4'],
[0x006, 'Digit5'],
[0x007, 'Digit6'],
[0x008, 'Digit7'],
[0x009, 'Digit8'],
[0x00a, 'Digit9'],
[0x00b, 'Digit0'],
[0x00c, 'Minus'],
[0x00d, 'Equal'],
[0x00e, 'Backspace'],
[0x00f, 'Tab'],
[0x010, 'KeyQ'],
[0x011, 'KeyW'],
[0x012, 'KeyE'],
[0x013, 'KeyR'],
[0x014, 'KeyT'],
[0x015, 'KeyY'],
[0x016, 'KeyU'],
[0x017, 'KeyI'],
[0x018, 'KeyO'],
[0x019, 'KeyP'],
[0x01a, 'BracketLeft'],
[0x01b, 'BracketRight'],
[0x01c, 'Enter'],
[0x01d, 'ControlLeft'],
[0x01e, 'KeyA'],
[0x01f, 'KeyS'],
[0x020, 'KeyD'],
[0x021, 'KeyF'],
[0x022, 'KeyG'],
[0x023, 'KeyH'],
[0x024, 'KeyJ'],
[0x025, 'KeyK'],
[0x026, 'KeyL'],
[0x027, 'Semicolon'],
[0x028, 'Quote'],
[0x029, 'Backquote'],
[0x02a, 'ShiftLeft'],
[0x02b, 'Backslash'],
[0x02c, 'KeyZ'],
[0x02d, 'KeyX'],
[0x02e, 'KeyC'],
[0x02f, 'KeyV'],
[0x030, 'KeyB'],
[0x031, 'KeyN'],
[0x032, 'KeyM'],
[0x033, 'Comma'],
[0x034, 'Period'],
[0x035, 'Slash'],
[0x036, 'ShiftRight'],
[0x037, 'NumpadMultiply'],
[0x038, 'AltLeft'],
[0x039, 'Space'],
[0x03a, 'CapsLock'],
[0x03b, 'F1'],
[0x03c, 'F2'],
[0x03d, 'F3'],
[0x03e, 'F4'],
[0x03f, 'F5'],
[0x040, 'F6'],
[0x041, 'F7'],
[0x042, 'F8'],
[0x043, 'F9'],
[0x044, 'F10'],
[0x045, 'NumLock'],
[0x046, 'ScrollLock'],
[0x047, 'Numpad7'],
[0x048, 'Numpad8'],
[0x049, 'Numpad9'],
[0x04a, 'NumpadSubtract'],
[0x04b, 'Numpad4'],
[0x04c, 'Numpad5'],
[0x04d, 'Numpad6'],
[0x04e, 'NumpadAdd'],
[0x04f, 'Numpad1'],
[0x050, 'Numpad2'],
[0x051, 'Numpad3'],
[0x052, 'Numpad0'],
[0x053, 'NumpadDecimal'],
[0x055, 'Lang5'],
[0x056, 'IntlBackslash'],
[0x057, 'F11'],
[0x058, 'F12'],
[0x059, 'IntlRo'],
[0x05a, 'Lang3'],
[0x05b, 'Lang4'],
[0x05c, 'Convert'],
[0x05d, 'KanaMode'],
[0x05e, 'NonConvert'],
[0x060, 'NumpadEnter'],
[0x061, 'ControlRight'],
[0x062, 'NumpadDivide'],
[0x063, 'PrintScreen'],
[0x064, 'AltRight'],
[0x066, 'Home'],
[0x067, 'ArrowUp'],
[0x068, 'PageUp'],
[0x069, 'ArrowLeft'],
[0x06a, 'ArrowRight'],
[0x06b, 'End'],
[0x06c, 'ArrowDown'],
[0x06d, 'PageDown'],
[0x06e, 'Insert'],
[0x06f, 'Delete'],
[0x071, 'AudioVolumeMute'],
[0x072, 'AudioVolumeDown'],
[0x073, 'AudioVolumeUp'],
[0x074, 'Power'],
[0x075, 'NumpadEqual'],
[0x077, 'Pause'],
[0x078, 'ShowAllWindows'],
[0x079, 'NumpadComma'],
[0x07a, 'Lang1'],
[0x07b, 'Lang2'],
[0x07c, 'IntlYen'],
[0x07d, 'MetaLeft'],
[0x07e, 'MetaRight'],
[0x07f, 'ContextMenu'],
[0x080, 'BrowserStop'],
[0x081, 'Again'],
[0x083, 'Undo'],
[0x084, 'Select'],
[0x085, 'Copy'],
[0x086, 'Open'],
[0x087, 'Paste'],
[0x088, 'Find'],
[0x089, 'Cut'],
[0x08a, 'Help'],
[0x08c, 'LaunchApp2'],
[0x08e, 'Sleep'],
[0x08f, 'WakeUp'],
[0x090, 'LaunchApp1'],
[0x094, 'LaunchApp1'],
[0x095, 'LaunchApp2'],
[0x09b, 'LaunchMail'],
[0x09c, 'BrowserFavorites'],
[0x09e, 'BrowserBack'],
[0x09f, 'BrowserForward'],
[0x0a1, 'Eject'],
[0x0a3, 'MediaTrackNext'],
[0x0a4, 'MediaPlayPause'],
[0x0a5, 'MediaTrackPrevious'],
[0x0a6, 'MediaStop'],
[0x0a7, 'MediaRecord'],
[0x0a8, 'MediaRewind'],
[0x0ab, 'MediaSelect'],
[0x0ac, 'BrowserHome'],
[0x0ad, 'BrowserRefresh'],
[0x0b3, 'NumpadParenLeft'],
[0x0b4, 'NumpadParenRight'],
[0x0b7, 'F13'],
[0x0b8, 'F14'],
[0x0b9, 'F15'],
[0x0ba, 'F16'],
[0x0bb, 'F17'],
[0x0bc, 'F18'],
[0x0bd, 'F19'],
[0x0be, 'F20'],
[0x0bf, 'F21'],
[0x0c0, 'F22'],
[0x0c1, 'F23'],
[0x0c2, 'F24'],
[0x0c9, 'MediaPause'],
[0x0cf, 'MediaPlay'],
[0x0d0, 'MediaFastForward'],
[0x0d9, 'BrowserSearch'],
[0x0e0, 'BrightnessDown'],
[0x0e1, 'BrightnessUp'],
[0x0e2, 'MediaSelect'],
[0x0e3, 'DisplayToggleIntExt'],
[0x0e7, 'MailSend'],
[0x0e8, 'MailReply'],
[0x0e9, 'MailForward'],
[0x16c, 'BrowserFavorites'],
[0x174, 'ZoomToggle'],
[0x1d0, 'Fn'],
[0x243, 'LaunchControlPanel'],
[0x244, 'SelectTask'],
[0x245, 'LaunchScreenSaver'],
[0x247, 'LaunchAssistant'],
[0x248, 'KeyboardLayoutSelect'],
]);
const WINDOWS_SCAN_CODES: ReadonlyMap<number, string> = new Map([
[0x0001, 'Escape'],
[0x0002, 'Digit1'],
[0x0003, 'Digit2'],
[0x0004, 'Digit3'],
[0x0005, 'Digit4'],
[0x0006, 'Digit5'],
[0x0007, 'Digit6'],
[0x0008, 'Digit7'],
[0x0009, 'Digit8'],
[0x000a, 'Digit9'],
[0x000b, 'Digit0'],
[0x000c, 'Minus'],
[0x000d, 'Equal'],
[0x000e, 'Backspace'],
[0x000f, 'Tab'],
[0x0010, 'KeyQ'],
[0x0011, 'KeyW'],
[0x0012, 'KeyE'],
[0x0013, 'KeyR'],
[0x0014, 'KeyT'],
[0x0015, 'KeyY'],
[0x0016, 'KeyU'],
[0x0017, 'KeyI'],
[0x0018, 'KeyO'],
[0x0019, 'KeyP'],
[0x001a, 'BracketLeft'],
[0x001b, 'BracketRight'],
[0x001c, 'Enter'],
[0x001d, 'ControlLeft'],
[0x001e, 'KeyA'],
[0x001f, 'KeyS'],
[0x0020, 'KeyD'],
[0x0021, 'KeyF'],
[0x0022, 'KeyG'],
[0x0023, 'KeyH'],
[0x0024, 'KeyJ'],
[0x0025, 'KeyK'],
[0x0026, 'KeyL'],
[0x0027, 'Semicolon'],
[0x0028, 'Quote'],
[0x0029, 'Backquote'],
[0x002a, 'ShiftLeft'],
[0x002b, 'Backslash'],
[0x002c, 'KeyZ'],
[0x002d, 'KeyX'],
[0x002e, 'KeyC'],
[0x002f, 'KeyV'],
[0x0030, 'KeyB'],
[0x0031, 'KeyN'],
[0x0032, 'KeyM'],
[0x0033, 'Comma'],
[0x0034, 'Period'],
[0x0035, 'Slash'],
[0x0036, 'ShiftRight'],
[0x0037, 'NumpadMultiply'],
[0x0038, 'AltLeft'],
[0x0039, 'Space'],
[0x003a, 'CapsLock'],
[0x003b, 'F1'],
[0x003c, 'F2'],
[0x003d, 'F3'],
[0x003e, 'F4'],
[0x003f, 'F5'],
[0x0040, 'F6'],
[0x0041, 'F7'],
[0x0042, 'F8'],
[0x0043, 'F9'],
[0x0044, 'F10'],
[0x0045, 'Pause'],
[0x0046, 'ScrollLock'],
[0x0047, 'Numpad7'],
[0x0048, 'Numpad8'],
[0x0049, 'Numpad9'],
[0x004a, 'NumpadSubtract'],
[0x004b, 'Numpad4'],
[0x004c, 'Numpad5'],
[0x004d, 'Numpad6'],
[0x004e, 'NumpadAdd'],
[0x004f, 'Numpad1'],
[0x0050, 'Numpad2'],
[0x0051, 'Numpad3'],
[0x0052, 'Numpad0'],
[0x0053, 'NumpadDecimal'],
[0x0054, 'PrintScreen'],
[0x0056, 'IntlBackslash'],
[0x0057, 'F11'],
[0x0058, 'F12'],
[0x0059, 'NumpadEqual'],
[0x0064, 'F13'],
[0x0065, 'F14'],
[0x0066, 'F15'],
[0x0067, 'F16'],
[0x0068, 'F17'],
[0x0069, 'F18'],
[0x006a, 'F19'],
[0x006b, 'F20'],
[0x006c, 'F21'],
[0x006d, 'F22'],
[0x006e, 'F23'],
[0x0070, 'KanaMode'],
[0x0071, 'Lang2'],
[0x0072, 'Lang1'],
[0x0073, 'IntlRo'],
[0x0076, 'F24'],
[0x0077, 'Lang4'],
[0x0078, 'Lang3'],
[0x0079, 'Convert'],
[0x007b, 'NonConvert'],
[0x007d, 'IntlYen'],
[0x007e, 'NumpadComma'],
[0xe008, 'Undo'],
[0xe00a, 'Paste'],
[0xe010, 'MediaTrackPrevious'],
[0xe017, 'Cut'],
[0xe018, 'Copy'],
[0xe019, 'MediaTrackNext'],
[0xe01c, 'NumpadEnter'],
[0xe01d, 'ControlRight'],
[0xe020, 'AudioVolumeMute'],
[0xe021, 'LaunchApp2'],
[0xe022, 'MediaPlayPause'],
[0xe024, 'MediaStop'],
[0xe02c, 'Eject'],
[0xe02e, 'AudioVolumeDown'],
[0xe030, 'AudioVolumeUp'],
[0xe032, 'BrowserHome'],
[0xe035, 'NumpadDivide'],
[0xe037, 'PrintScreen'],
[0xe038, 'AltRight'],
[0xe03b, 'Help'],
[0xe045, 'NumLock'],
[0xe046, 'Pause'],
[0xe047, 'Home'],
[0xe048, 'ArrowUp'],
[0xe049, 'PageUp'],
[0xe04b, 'ArrowLeft'],
[0xe04d, 'ArrowRight'],
[0xe04f, 'End'],
[0xe050, 'ArrowDown'],
[0xe051, 'PageDown'],
[0xe052, 'Insert'],
[0xe053, 'Delete'],
[0xe05b, 'MetaLeft'],
[0xe05c, 'MetaRight'],
[0xe05d, 'ContextMenu'],
[0xe05e, 'Power'],
[0xe05f, 'Sleep'],
[0xe063, 'WakeUp'],
[0xe065, 'BrowserSearch'],
[0xe066, 'BrowserFavorites'],
[0xe067, 'BrowserRefresh'],
[0xe068, 'BrowserStop'],
[0xe069, 'BrowserForward'],
[0xe06a, 'BrowserBack'],
[0xe06b, 'LaunchApp1'],
[0xe06c, 'LaunchMail'],
[0xe06d, 'MediaSelect'],
]);
const WINDOWS_VIRTUAL_KEY_CODES: ReadonlyMap<number, string> = new Map([
[0x08, 'Backspace'],
[0x09, 'Tab'],
[0x0c, 'Numpad5'],
[0x0d, 'Enter'],
[0x10, 'ShiftLeft'],
[0x11, 'ControlLeft'],
[0x12, 'AltLeft'],
[0x13, 'Pause'],
[0x14, 'CapsLock'],
[0x15, 'KanaMode'],
[0x1b, 'Escape'],
[0x1c, 'Convert'],
[0x1d, 'NonConvert'],
[0x20, 'Space'],
[0x21, 'PageUp'],
[0x22, 'PageDown'],
[0x23, 'End'],
[0x24, 'Home'],
[0x25, 'ArrowLeft'],
[0x26, 'ArrowUp'],
[0x27, 'ArrowRight'],
[0x28, 'ArrowDown'],
[0x2c, 'PrintScreen'],
[0x2d, 'Insert'],
[0x2e, 'Delete'],
...Array.from({length: 10}, (_, index): [number, string] => [0x30 + index, `Digit${index}`]),
...Array.from({length: 26}, (_, index): [number, string] => [
0x41 + index,
`Key${String.fromCharCode(0x41 + index)}`,
]),
[0x5b, 'MetaLeft'],
[0x5c, 'MetaRight'],
[0x5d, 'ContextMenu'],
[0x5f, 'Sleep'],
...Array.from({length: 10}, (_, index): [number, string] => [0x60 + index, `Numpad${index}`]),
[0x6a, 'NumpadMultiply'],
[0x6b, 'NumpadAdd'],
[0x6c, 'NumpadComma'],
[0x6d, 'NumpadSubtract'],
[0x6e, 'NumpadDecimal'],
[0x6f, 'NumpadDivide'],
...Array.from({length: 24}, (_, index): [number, string] => [0x70 + index, `F${index + 1}`]),
[0x90, 'NumLock'],
[0x91, 'ScrollLock'],
[0x92, 'NumpadEqual'],
[0xa0, 'ShiftLeft'],
[0xa1, 'ShiftRight'],
[0xa2, 'ControlLeft'],
[0xa3, 'ControlRight'],
[0xa4, 'AltLeft'],
[0xa5, 'AltRight'],
[0xa6, 'BrowserBack'],
[0xa7, 'BrowserForward'],
[0xa8, 'BrowserRefresh'],
[0xa9, 'BrowserStop'],
[0xaa, 'BrowserSearch'],
[0xab, 'BrowserFavorites'],
[0xac, 'BrowserHome'],
[0xad, 'AudioVolumeMute'],
[0xae, 'AudioVolumeDown'],
[0xaf, 'AudioVolumeUp'],
[0xb0, 'MediaTrackNext'],
[0xb1, 'MediaTrackPrevious'],
[0xb2, 'MediaStop'],
[0xb3, 'MediaPlayPause'],
[0xb4, 'LaunchMail'],
[0xb5, 'MediaSelect'],
[0xb6, 'LaunchApp1'],
[0xb7, 'LaunchApp2'],
[0xba, 'Semicolon'],
[0xbb, 'Equal'],
[0xbc, 'Comma'],
[0xbd, 'Minus'],
[0xbe, 'Period'],
[0xbf, 'Slash'],
[0xc0, 'Backquote'],
[0xdb, 'BracketLeft'],
[0xdc, 'Backslash'],
[0xdd, 'BracketRight'],
[0xde, 'Quote'],
[0xe2, 'IntlBackslash'],
]);
const MACOS_KEY_CODES: ReadonlyMap<number, string> = new Map([
[0x00, 'KeyA'],
[0x01, 'KeyS'],
[0x02, 'KeyD'],
[0x03, 'KeyF'],
[0x04, 'KeyH'],
[0x05, 'KeyG'],
[0x06, 'KeyZ'],
[0x07, 'KeyX'],
[0x08, 'KeyC'],
[0x09, 'KeyV'],
[0x0a, 'IntlBackslash'],
[0x0b, 'KeyB'],
[0x0c, 'KeyQ'],
[0x0d, 'KeyW'],
[0x0e, 'KeyE'],
[0x0f, 'KeyR'],
[0x10, 'KeyY'],
[0x11, 'KeyT'],
[0x12, 'Digit1'],
[0x13, 'Digit2'],
[0x14, 'Digit3'],
[0x15, 'Digit4'],
[0x16, 'Digit6'],
[0x17, 'Digit5'],
[0x18, 'Equal'],
[0x19, 'Digit9'],
[0x1a, 'Digit7'],
[0x1b, 'Minus'],
[0x1c, 'Digit8'],
[0x1d, 'Digit0'],
[0x1e, 'BracketRight'],
[0x1f, 'KeyO'],
[0x20, 'KeyU'],
[0x21, 'BracketLeft'],
[0x22, 'KeyI'],
[0x23, 'KeyP'],
[0x24, 'Enter'],
[0x25, 'KeyL'],
[0x26, 'KeyJ'],
[0x27, 'Quote'],
[0x28, 'KeyK'],
[0x29, 'Semicolon'],
[0x2a, 'Backslash'],
[0x2b, 'Comma'],
[0x2c, 'Slash'],
[0x2d, 'KeyN'],
[0x2e, 'KeyM'],
[0x2f, 'Period'],
[0x30, 'Tab'],
[0x31, 'Space'],
[0x32, 'Backquote'],
[0x33, 'Backspace'],
[0x35, 'Escape'],
[0x36, 'MetaRight'],
[0x37, 'MetaLeft'],
[0x38, 'ShiftLeft'],
[0x39, 'CapsLock'],
[0x3a, 'AltLeft'],
[0x3b, 'ControlLeft'],
[0x3c, 'ShiftRight'],
[0x3d, 'AltRight'],
[0x3e, 'ControlRight'],
[0x40, 'F17'],
[0x41, 'NumpadDecimal'],
[0x43, 'NumpadMultiply'],
[0x45, 'NumpadAdd'],
[0x47, 'NumLock'],
[0x48, 'AudioVolumeUp'],
[0x49, 'AudioVolumeDown'],
[0x4a, 'AudioVolumeMute'],
[0x4b, 'NumpadDivide'],
[0x4c, 'NumpadEnter'],
[0x4e, 'NumpadSubtract'],
[0x4f, 'F18'],
[0x50, 'F19'],
[0x51, 'NumpadEqual'],
[0x52, 'Numpad0'],
[0x53, 'Numpad1'],
[0x54, 'Numpad2'],
[0x55, 'Numpad3'],
[0x56, 'Numpad4'],
[0x57, 'Numpad5'],
[0x58, 'Numpad6'],
[0x59, 'Numpad7'],
[0x5a, 'F20'],
[0x5b, 'Numpad8'],
[0x5c, 'Numpad9'],
[0x5d, 'IntlYen'],
[0x5e, 'IntlRo'],
[0x5f, 'NumpadComma'],
[0x60, 'F5'],
[0x61, 'F6'],
[0x62, 'F7'],
[0x63, 'F3'],
[0x64, 'F8'],
[0x65, 'F9'],
[0x66, 'Lang2'],
[0x67, 'F11'],
[0x68, 'Lang1'],
[0x69, 'F13'],
[0x6a, 'F16'],
[0x6b, 'F14'],
[0x6d, 'F10'],
[0x6e, 'ContextMenu'],
[0x6f, 'F12'],
[0x71, 'F15'],
[0x72, 'Insert'],
[0x73, 'Home'],
[0x74, 'PageUp'],
[0x75, 'Delete'],
[0x76, 'F4'],
[0x77, 'End'],
[0x78, 'F2'],
[0x79, 'PageDown'],
[0x7a, 'F1'],
[0x7b, 'ArrowLeft'],
[0x7c, 'ArrowRight'],
[0x7d, 'ArrowDown'],
[0x7e, 'ArrowUp'],
]);
const X11_KEYCODE_OFFSET = 8;
const WINDOWS_EXTENDED_SCAN_PREFIX = 0xe000;
const NAMED_KEY_CODES: Readonly<Record<string, string>> = {
Spacebar: 'Space',
Esc: 'Escape',
Return: 'Enter',
Break: 'Pause',
Clear: 'NumLock',
Apps: 'ContextMenu',
Up: 'ArrowUp',
Down: 'ArrowDown',
Left: 'ArrowLeft',
Right: 'ArrowRight',
Del: 'Delete',
LaunchMediaPlayer: 'MediaSelect',
};
const KNOWN_DOM_CODES: ReadonlySet<string> = new Set([
...EVDEV_KEY_CODES.values(),
...WINDOWS_SCAN_CODES.values(),
...MACOS_KEY_CODES.values(),
]);
export const MODIFIER_KEY_CODES = {
ctrl: ['ControlLeft', 'ControlRight'],
alt: ['AltLeft', 'AltRight'],
shift: ['ShiftLeft', 'ShiftRight'],
meta: ['MetaLeft', 'MetaRight'],
} as const;
export type ModifierKind = keyof typeof MODIFIER_KEY_CODES;
export function modifierKindForCode(code: string | null): ModifierKind | null {
if (code === null) return null;
for (const kind of Object.keys(MODIFIER_KEY_CODES) as Array<ModifierKind>) {
if ((MODIFIER_KEY_CODES[kind] as ReadonlyArray<string>).includes(code)) return kind;
}
return null;
}
export function evdevKeyToDomCode(keycode: number): string | null {
return EVDEV_KEY_CODES.get(keycode) ?? null;
}
export function x11KeycodeToDomCode(keycode: number): string | null {
if (keycode < X11_KEYCODE_OFFSET) return null;
return evdevKeyToDomCode(keycode - X11_KEYCODE_OFFSET);
}
export function windowsScanCodeKey(scanCode: number, extended: boolean): number {
return extended ? WINDOWS_EXTENDED_SCAN_PREFIX | scanCode : scanCode;
}
export function windowsKeyToDomCode(scanCode: number, extended: boolean, virtualKey: number): string | null {
const fromScanCode = scanCode === 0 ? undefined : WINDOWS_SCAN_CODES.get(windowsScanCodeKey(scanCode, extended));
return fromScanCode ?? WINDOWS_VIRTUAL_KEY_CODES.get(virtualKey) ?? null;
}
export function macosKeycodeToDomCode(keycode: number): string | null {
return MACOS_KEY_CODES.get(keycode) ?? null;
}
export function hookKeyNameToDomCode(name: string): string | null {
if (/^[A-Z]$/.test(name)) return `Key${name}`;
if (/^[0-9]$/.test(name)) return `Digit${name}`;
const named = NAMED_KEY_CODES[name];
if (named) return named;
return KNOWN_DOM_CODES.has(name) ? name : null;
}
export function usLayoutKeyToDomCode(key: string): string | null {
if (key.length === 1) {
const punctuation = PUNCTUATION_KEY_CODES[key];
if (punctuation) return punctuation;
const upper = key.toUpperCase();
if (/^[A-Z]$/.test(upper)) return `Key${upper}`;
if (/^[0-9]$/.test(upper)) return `Digit${upper}`;
return null;
}
return hookKeyNameToDomCode(key);
}
const XDG_CODE_KEYSYMS: ReadonlyMap<string, string> = new Map([
...Array.from({length: 26}, (_, index): [string, string] => [
`Key${String.fromCharCode(0x41 + index)}`,
String.fromCharCode(0x61 + index),
]),
...Array.from({length: 10}, (_, index): [string, string] => [`Digit${index}`, `${index}`]),
...Array.from({length: 24}, (_, index): [string, string] => [`F${index + 1}`, `F${index + 1}`]),
['Space', 'space'],
['Enter', 'Return'],
['Tab', 'Tab'],
['Escape', 'Escape'],
['Backspace', 'BackSpace'],
['Minus', 'minus'],
['Equal', 'equal'],
['BracketLeft', 'bracketleft'],
['BracketRight', 'bracketright'],
['Backslash', 'backslash'],
['Semicolon', 'semicolon'],
['Quote', 'apostrophe'],
['Backquote', 'grave'],
['Comma', 'comma'],
['Period', 'period'],
['Slash', 'slash'],
['Home', 'Home'],
['End', 'End'],
['PageUp', 'Prior'],
['PageDown', 'Next'],
['Insert', 'Insert'],
['Delete', 'Delete'],
['ArrowLeft', 'Left'],
['ArrowRight', 'Right'],
['ArrowUp', 'Up'],
['ArrowDown', 'Down'],
['PrintScreen', 'Print'],
['ScrollLock', 'Scroll_Lock'],
['Pause', 'Pause'],
['ContextMenu', 'Menu'],
['MediaPlayPause', 'XF86AudioPlay'],
['MediaTrackNext', 'XF86AudioNext'],
['MediaTrackPrevious', 'XF86AudioPrev'],
['MediaStop', 'XF86AudioStop'],
['AudioVolumeMute', 'XF86AudioMute'],
]);
const XDG_KEYSYM_PATTERN = /^[A-Za-z0-9_]+$/;
function xdgKeysymForCombo(combo: {key: string; code?: string}): string | null {
if (/^[a-z]$/i.test(combo.key) && !combo.code?.startsWith('Numpad')) return combo.key.toLowerCase();
const code = combo.code ?? usLayoutKeyToDomCode(combo.key);
if (code === null) return null;
const keysym = XDG_CODE_KEYSYMS.get(code) ?? null;
return keysym !== null && XDG_KEYSYM_PATTERN.test(keysym) ? keysym : null;
}
export interface XdgTriggerCombo {
key: string;
code?: string;
ctrl: boolean;
alt: boolean;
shift: boolean;
meta: boolean;
mouseButton?: number;
modifierOnly?: boolean;
bothSides?: boolean;
}
export function comboToXdgTrigger(combo: XdgTriggerCombo): string | null {
if (combo.mouseButton !== undefined || combo.modifierOnly || combo.bothSides) return null;
const keysym = xdgKeysymForCombo(combo);
if (keysym === null) return null;
const parts: Array<string> = [];
if (combo.ctrl) parts.push('CTRL');
if (combo.alt) parts.push('ALT');
if (combo.shift) parts.push('SHIFT');
if (combo.meta) parts.push('LOGO');
parts.push(keysym);
return parts.join('+');
}