|
| 1 | +import { ucs2keysym } from "./keysym2ucs"; |
| 2 | +import { name2keysym } from "./name2keysym"; |
| 3 | +import { |
| 4 | + CharMapping, |
| 5 | + CharMappingWithModifiers, |
| 6 | + HORIZONTAL_TAB, |
| 7 | + KeyMap, |
| 8 | + KeyModifier, |
| 9 | + LINE_FEED, |
| 10 | +} from "./types"; |
| 11 | + |
| 12 | +// scancodes based on '@novnc/novnc/lib/input/xtscancodes' |
| 13 | +export const modifierToCharMapping: { [key in KeyModifier]: CharMapping } = { |
| 14 | + altgr: { |
| 15 | + keysym: 0xffea /* XK_Alt_R */, |
| 16 | + scanCode: 0xe038 /* "AltRight" */, |
| 17 | + }, |
| 18 | + control: { |
| 19 | + keysym: 0xffe4 /* XK_Control_R */, |
| 20 | + scanCode: 0xe01d /* "ControlRight" */, |
| 21 | + }, |
| 22 | + numlock: { |
| 23 | + keysym: 0xff7f /* XK_Num_Lock */, |
| 24 | + scanCode: 0x45 /*"NumLock"*/, |
| 25 | + }, |
| 26 | + shift: { |
| 27 | + keysym: 0xffe1 /* XK_Shift_L */, |
| 28 | + scanCode: 0x36 /*"ShiftRight"*/, |
| 29 | + }, |
| 30 | +}; |
| 31 | + |
| 32 | +export const ENTER_MAPPING: CharMapping = { |
| 33 | + char: "Enter", |
| 34 | + keysym: 0xff0d /*XK_Return*/, |
| 35 | + scanCode: 0x1c /*"Enter" */, |
| 36 | +}; |
| 37 | +export const HORIZONTAL_TAB_MAPPING = { |
| 38 | + char: "Tab", |
| 39 | + keysym: 0xff09 /* XK_Tab*/, |
| 40 | + scanCode: 0xf /*"Tab" */, |
| 41 | +}; |
| 42 | + |
| 43 | +const emptyMapping = ( |
| 44 | + char: string, |
| 45 | + keysym: number |
| 46 | +): CharMappingWithModifiers => ({ |
| 47 | + mapping: { char, keysym, scanCode: 0 }, |
| 48 | + modifiers: [], |
| 49 | +}); |
| 50 | + |
| 51 | +export const resolveCharMapping = ( |
| 52 | + char: string, |
| 53 | + keymap: KeyMap |
| 54 | +): { |
| 55 | + mapping: CharMapping; |
| 56 | + modifiers: CharMapping[]; |
| 57 | +} => { |
| 58 | + const codePoint = char.codePointAt(0); |
| 59 | + if (codePoint === LINE_FEED) { |
| 60 | + return { |
| 61 | + mapping: ENTER_MAPPING, |
| 62 | + modifiers: [], |
| 63 | + }; |
| 64 | + } |
| 65 | + |
| 66 | + if (codePoint === HORIZONTAL_TAB) { |
| 67 | + return { |
| 68 | + mapping: HORIZONTAL_TAB_MAPPING, |
| 69 | + modifiers: [], |
| 70 | + }; |
| 71 | + } |
| 72 | + |
| 73 | + const unicode = char.codePointAt(0); |
| 74 | + if (unicode === undefined) { |
| 75 | + return emptyMapping(char, 0); |
| 76 | + } |
| 77 | + const keysym = ucs2keysym(unicode); |
| 78 | + |
| 79 | + // based on https://github.com/qemu/qemu/blob/b69801dd6b1eb4d107f7c2f643adf0a4e3ec9124/ui/keymaps.c#L43 |
| 80 | + // some rules are based on names in format UXXXX |
| 81 | + // FIXME: (only)'no' keymap uses keysyms as names in few cases (likely a bug) |
| 82 | + // example: 0x010000d7 which seems U00D7 |
| 83 | + const unicodeBasedName = `U${Number(unicode) |
| 84 | + .toString(16) |
| 85 | + .toUpperCase() |
| 86 | + .padStart(4, "0")}`; |
| 87 | + const [mnemonicName = unicodeBasedName] = name2keysym |
| 88 | + .filter(([, keysymCode]) => keysymCode === keysym) |
| 89 | + .map(([_name]) => _name); |
| 90 | + |
| 91 | + const [resolved] = keymap.filter(([name]) => name === mnemonicName); |
| 92 | + if (!resolved) { |
| 93 | + // no match in the keymap (by name) |
| 94 | + return emptyMapping(char, keysym); |
| 95 | + } |
| 96 | + |
| 97 | + const [, scanCode, ...modifiers] = resolved; |
| 98 | + |
| 99 | + return { |
| 100 | + mapping: { |
| 101 | + char, |
| 102 | + keysym, |
| 103 | + scanCode, |
| 104 | + }, |
| 105 | + modifiers: modifiers.map((m) => modifierToCharMapping[m]), |
| 106 | + }; |
| 107 | +}; |
0 commit comments