Skip to content
Source
Sign inSign up
Open History to see the last commit

keyCodes.ts

273 lines5.9 KB

1import { isMacPlatform, US_SHIFTED_TO_UNSHIFTED_KEYS } from "@t3tools/shared/keybindings";
2​
3// This order mirrors GhosttyKey in ghostty/vt/key/event.h. The values are
4// intentionally derived from the official W3C-aligned enum instead of
5// maintaining a second keyboard protocol.
6const ghosttyKeyboardCodes = [
7 "Unidentified",
8 "Backquote",
9 "Backslash",
10 "BracketLeft",
11 "BracketRight",
12 "Comma",
13 "Digit0",
14 "Digit1",
15 "Digit2",
16 "Digit3",
17 "Digit4",
18 "Digit5",
19 "Digit6",
20 "Digit7",
21 "Digit8",
22 "Digit9",
23 "Equal",
24 "IntlBackslash",
25 "IntlRo",
26 "IntlYen",
27 "KeyA",
28 "KeyB",
29 "KeyC",
30 "KeyD",
31 "KeyE",
32 "KeyF",
33 "KeyG",
34 "KeyH",
35 "KeyI",
36 "KeyJ",
37 "KeyK",
38 "KeyL",
39 "KeyM",
40 "KeyN",
41 "KeyO",
42 "KeyP",
43 "KeyQ",
44 "KeyR",
45 "KeyS",
46 "KeyT",
47 "KeyU",
48 "KeyV",
49 "KeyW",
50 "KeyX",
51 "KeyY",
52 "KeyZ",
53 "Minus",
54 "Period",
55 "Quote",
56 "Semicolon",
57 "Slash",
58 "AltLeft",
59 "AltRight",
60 "Backspace",
61 "CapsLock",
62 "ContextMenu",
63 "ControlLeft",
64 "ControlRight",
65 "Enter",
66 "MetaLeft",
67 "MetaRight",
68 "ShiftLeft",
69 "ShiftRight",
70 "Space",
71 "Tab",
72 "Convert",
73 "KanaMode",
74 "NonConvert",
75 "Delete",
76 "End",
77 "Help",
78 "Home",
79 "Insert",
80 "PageDown",
81 "PageUp",
82 "ArrowDown",
83 "ArrowLeft",
84 "ArrowRight",
85 "ArrowUp",
86 "NumLock",
87 "Numpad0",
88 "Numpad1",
89 "Numpad2",
90 "Numpad3",
91 "Numpad4",
92 "Numpad5",
93 "Numpad6",
94 "Numpad7",
95 "Numpad8",
96 "Numpad9",
97 "NumpadAdd",
98 "NumpadBackspace",
99 "NumpadClear",
100 "NumpadClearEntry",
101 "NumpadComma",
102 "NumpadDecimal",
103 "NumpadDivide",
104 "NumpadEnter",
105 "NumpadEqual",
106 "NumpadMemoryAdd",
107 "NumpadMemoryClear",
108 "NumpadMemoryRecall",
109 "NumpadMemoryStore",
110 "NumpadMemorySubtract",
111 "NumpadMultiply",
112 "NumpadParenLeft",
113 "NumpadParenRight",
114 "NumpadSubtract",
115 "NumpadSeparator",
116 "NumpadArrowUp",
117 "NumpadArrowDown",
118 "NumpadArrowRight",
119 "NumpadArrowLeft",
120 "NumpadBegin",
121 "NumpadHome",
122 "NumpadEnd",
123 "NumpadInsert",
124 "NumpadDelete",
125 "NumpadPageUp",
126 "NumpadPageDown",
127 "Escape",
128 "F1",
129 "F2",
130 "F3",
131 "F4",
132 "F5",
133 "F6",
134 "F7",
135 "F8",
136 "F9",
137 "F10",
138 "F11",
139 "F12",
140 "F13",
141 "F14",
142 "F15",
143 "F16",
144 "F17",
145 "F18",
146 "F19",
147 "F20",
148 "F21",
149 "F22",
150 "F23",
151 "F24",
152 "F25",
153 "Fn",
154 "FnLock",
155 "PrintScreen",
156 "ScrollLock",
157 "Pause",
158 "BrowserBack",
159 "BrowserFavorites",
160 "BrowserForward",
161 "BrowserHome",
162 "BrowserRefresh",
163 "BrowserSearch",
164 "BrowserStop",
165 "Eject",
166 "LaunchApp1",
167 "LaunchApp2",
168 "LaunchMail",
169 "MediaPlayPause",
170 "MediaSelect",
171 "MediaStop",
172 "MediaTrackNext",
173 "MediaTrackPrevious",
174 "Power",
175 "Sleep",
176 "AudioVolumeDown",
177 "AudioVolumeMute",
178 "AudioVolumeUp",
179 "WakeUp",
180 "Copy",
181 "Cut",
182 "Paste",
183] as const;
184​
185const codeToGhosttyKey = new Map<string, number>(
186 ghosttyKeyboardCodes.map((code, index) => [code, index]),
187);
188​
189export function ghosttyKeyForCode(code: string): number {
190 return codeToGhosttyKey.get(code) ?? 0;
192​
193// With NumLock off, keypad digits report navigation keys; Ghostty encodes those as keypad variants.
194const NUMPAD_NAVIGATION_KEYS = new Map<string, number>(
195 (
196 [
197 ["ArrowDown", "NumpadArrowDown"],
198 ["ArrowLeft", "NumpadArrowLeft"],
199 ["ArrowRight", "NumpadArrowRight"],
200 ["ArrowUp", "NumpadArrowUp"],
201 ["Clear", "NumpadBegin"],
202 ["Delete", "NumpadDelete"],
203 ["End", "NumpadEnd"],
204 ["Home", "NumpadHome"],
205 ["Insert", "NumpadInsert"],
206 ["PageDown", "NumpadPageDown"],
207 ["PageUp", "NumpadPageUp"],
208 ] as const
209 ).map(([key, code]) => [key, ghosttyKeyForCode(code)]),
210);
211​
212export function ghosttyKeyForEvent(event: Pick<KeyboardEvent, "code" | "key">): number {
213 return (
214 (event.code.startsWith("Numpad") && NUMPAD_NAVIGATION_KEYS.get(event.key)) ||
215 ghosttyKeyForCode(event.code) ||
216 // Android browsers can omit the physical code for keys such as Backspace.
217 ghosttyKeyForCode(event.key)
218 );
220​
221export interface GhosttyKeyboardLayoutMap {
222 get(code: string): string | undefined;
224​
225let keyboardLayoutMapPromise: Promise<GhosttyKeyboardLayoutMap | undefined> | undefined;
226​
227export function loadGhosttyKeyboardLayoutMap(): Promise<GhosttyKeyboardLayoutMap | undefined> {
228 if (keyboardLayoutMapPromise) return keyboardLayoutMapPromise;
229 const browserNavigator = globalThis.navigator as
230 | (Navigator & {
231 readonly keyboard?: {
232 getLayoutMap(): Promise<GhosttyKeyboardLayoutMap>;
233 };
234 })
235 | undefined;
236 const keyboard = browserNavigator?.keyboard;
237 const promise = keyboard?.getLayoutMap().catch(() => undefined) ?? Promise.resolve(undefined);
238 keyboardLayoutMapPromise = promise;
239 return promise;
241​
242// Browsers omit consumed modifiers; macOS Option composes layout characters too.
243export function ghosttyConsumedMods(
244 event: Pick<KeyboardEvent, "altKey" | "ctrlKey" | "key" | "metaKey" | "shiftKey">,
245 platform = navigator.platform,
246): number {
247 if (event.ctrlKey || event.metaKey || [...event.key].length !== 1) return 0;
248 if (event.altKey && !isMacPlatform(platform)) return 0;
249 return (event.shiftKey ? 1 : 0) | (event.altKey ? 1 << 2 : 0);
251​
252export function ghosttyUnshiftedCodepoint(
253 event: Pick<KeyboardEvent, "code" | "key" | "shiftKey">,
254 layoutMap?: GhosttyKeyboardLayoutMap,
255): number {
256 if ([...event.key].length !== 1) return 0;
257 const layoutCharacter = layoutMap?.get(event.code);
258 if (layoutCharacter && [...layoutCharacter].length === 1) {
259 return layoutCharacter.codePointAt(0) ?? 0;
260 }
261 if (/^[A-Z]$/u.test(event.key)) return event.key.charCodeAt(0) + 32;
262 if (event.shiftKey) {
263 const unshiftedCharacter = US_SHIFTED_TO_UNSHIFTED_KEYS.get(event.key);
264 if (unshiftedCharacter) return unshiftedCharacter.codePointAt(0) ?? 0;
265 const lowercase = event.key.toLowerCase();
266 if (lowercase !== event.key && [...lowercase].length === 1) {
267 return lowercase.codePointAt(0) ?? 0;
268 }
269 // Without layout data, the unshifted character is unknown; guessing corrupts Kitty alternate keys.
270 return 0;
271 }
272 return event.key.codePointAt(0) ?? 0;