Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
emscripten-core
GitHub Repository: emscripten-core/emscripten
Path: blob/main/tools/maint/create_dom_pk_codes.py
4150 views
1
#!/usr/bin/env python3
2
# Copyright 2017 The Emscripten Authors. All rights reserved.
3
# Emscripten is available under two separate licenses, the MIT license and the
4
# University of Illinois/NCSA Open Source License. Both these licenses can be
5
# found in the LICENSE file.
6
7
# The DOM KeyboardEvent field 'code' contains a locale/language independent
8
# identifier of a pressed key on the keyboard, i.e. a "physical" keyboard code, which
9
# is often useful for games that want to provide a physical keyboard layout that does
10
# not get confused by the language setting the user has.
11
12
# For example, in Unreal Engine 4 developer mode, the physical keyboard key above the Tab
13
# but below the Esc key should open up the developer console, independent of which keyboard
14
# layout is active. This key produces the backquote (`) character on US keyboard layout,
15
# whereas on the Finnish/Swedish keyboard layout, it generates but the section sign (ยง)
16
# character. Other keyboard layouts might give different characters, and independent of
17
# which character is produced, we would like to generate a layout-agnostic identifier for
18
# the key at this physical location on the keyboard.
19
20
# The DOM KeyboardEvent field 'code' provides such a layout-agnostic identifier.
21
# Unfortunately this identifier is not an integral ID that could be used as an enum
22
# or #define, but it is a human-readable English language string that represents the
23
# physical key. This is very inconvenient for most applications.
24
25
# This utility script creates a mapping from the different documented values of the
26
# KeyboardEvent 'code' field, to integral IDs that can be easily used to identify
27
# physical key locations. This mapping is implemented by constructing a hash function
28
# that is a perfect hash (https://en.wikipedia.org/wiki/Perfect_hash_function) of the
29
# known strings.
30
31
# Use #include <emscripten/dom_pk_codes.h> in your code to access these IDs.
32
33
import os
34
import sys
35
import random
36
37
input_strings = [
38
(0x0, 'Unidentified', 'DOM_PK_UNKNOWN'),
39
(0x1, 'Escape', 'DOM_PK_ESCAPE'),
40
(0x2, 'Digit0', 'DOM_PK_0'),
41
(0x3, 'Digit1', 'DOM_PK_1'),
42
(0x4, 'Digit2', 'DOM_PK_2'),
43
(0x5, 'Digit3', 'DOM_PK_3'),
44
(0x6, 'Digit4', 'DOM_PK_4'),
45
(0x7, 'Digit5', 'DOM_PK_5'),
46
(0x8, 'Digit6', 'DOM_PK_6'),
47
(0x9, 'Digit7', 'DOM_PK_7'),
48
(0xA, 'Digit8', 'DOM_PK_8'),
49
(0xB, 'Digit9', 'DOM_PK_9'),
50
(0xC, 'Minus', 'DOM_PK_MINUS'),
51
(0xD, 'Equal', 'DOM_PK_EQUAL'),
52
(0xE, 'Backspace', 'DOM_PK_BACKSPACE'),
53
(0xF, 'Tab', 'DOM_PK_TAB'),
54
(0x10, 'KeyQ', 'DOM_PK_Q'),
55
(0x11, 'KeyW', 'DOM_PK_W'),
56
(0x12, 'KeyE', 'DOM_PK_E'),
57
(0x13, 'KeyR', 'DOM_PK_R'),
58
(0x14, 'KeyT', 'DOM_PK_T'),
59
(0x15, 'KeyY', 'DOM_PK_Y'),
60
(0x16, 'KeyU', 'DOM_PK_U'),
61
(0x17, 'KeyI', 'DOM_PK_I'),
62
(0x18, 'KeyO', 'DOM_PK_O'),
63
(0x19, 'KeyP', 'DOM_PK_P'),
64
(0x1A, 'BracketLeft', 'DOM_PK_BRACKET_LEFT'),
65
(0x1B, 'BracketRight', 'DOM_PK_BRACKET_RIGHT'),
66
(0x1C, 'Enter', 'DOM_PK_ENTER'),
67
(0x1D, 'ControlLeft', 'DOM_PK_CONTROL_LEFT'),
68
(0x1E, 'KeyA', 'DOM_PK_A'),
69
(0x1F, 'KeyS', 'DOM_PK_S'),
70
(0x20, 'KeyD', 'DOM_PK_D'),
71
(0x21, 'KeyF', 'DOM_PK_F'),
72
(0x22, 'KeyG', 'DOM_PK_G'),
73
(0x23, 'KeyH', 'DOM_PK_H'),
74
(0x24, 'KeyJ', 'DOM_PK_J'),
75
(0x25, 'KeyK', 'DOM_PK_K'),
76
(0x26, 'KeyL', 'DOM_PK_L'),
77
(0x27, 'Semicolon', 'DOM_PK_SEMICOLON'),
78
(0x28, 'Quote', 'DOM_PK_QUOTE'),
79
(0x29, 'Backquote', 'DOM_PK_BACKQUOTE'),
80
(0x2A, 'ShiftLeft', 'DOM_PK_SHIFT_LEFT'),
81
(0x2B, 'Backslash', 'DOM_PK_BACKSLASH'),
82
(0x2C, 'KeyZ', 'DOM_PK_Z'),
83
(0x2D, 'KeyX', 'DOM_PK_X'),
84
(0x2E, 'KeyC', 'DOM_PK_C'),
85
(0x2F, 'KeyV', 'DOM_PK_V'),
86
(0x30, 'KeyB', 'DOM_PK_B'),
87
(0x31, 'KeyN', 'DOM_PK_N'),
88
(0x32, 'KeyM', 'DOM_PK_M'),
89
(0x33, 'Comma', 'DOM_PK_COMMA'),
90
(0x34, 'Period', 'DOM_PK_PERIOD'),
91
(0x35, 'Slash', 'DOM_PK_SLASH'),
92
(0x36, 'ShiftRight', 'DOM_PK_SHIFT_RIGHT'),
93
(0x37, 'NumpadMultiply', 'DOM_PK_NUMPAD_MULTIPLY'),
94
(0x38, 'AltLeft', 'DOM_PK_ALT_LEFT'),
95
(0x39, 'Space', 'DOM_PK_SPACE'),
96
(0x3A, 'CapsLock', 'DOM_PK_CAPS_LOCK'),
97
(0x3B, 'F1', 'DOM_PK_F1'),
98
(0x3C, 'F2', 'DOM_PK_F2'),
99
(0x3D, 'F3', 'DOM_PK_F3'),
100
(0x3E, 'F4', 'DOM_PK_F4'),
101
(0x3F, 'F5', 'DOM_PK_F5'),
102
(0x40, 'F6', 'DOM_PK_F6'),
103
(0x41, 'F7', 'DOM_PK_F7'),
104
(0x42, 'F8', 'DOM_PK_F8'),
105
(0x43, 'F9', 'DOM_PK_F9'),
106
(0x44, 'F10', 'DOM_PK_F10'),
107
(0x45, 'Pause', 'DOM_PK_PAUSE'),
108
(0x46, 'ScrollLock', 'DOM_PK_SCROLL_LOCK'),
109
(0x47, 'Numpad7', 'DOM_PK_NUMPAD_7'),
110
(0x48, 'Numpad8', 'DOM_PK_NUMPAD_8'),
111
(0x49, 'Numpad9', 'DOM_PK_NUMPAD_9'),
112
(0x4A, 'NumpadSubtract', 'DOM_PK_NUMPAD_SUBTRACT'),
113
(0x4B, 'Numpad4', 'DOM_PK_NUMPAD_4'),
114
(0x4C, 'Numpad5', 'DOM_PK_NUMPAD_5'),
115
(0x4D, 'Numpad6', 'DOM_PK_NUMPAD_6'),
116
(0x4E, 'NumpadAdd', 'DOM_PK_NUMPAD_ADD'),
117
(0x4F, 'Numpad1', 'DOM_PK_NUMPAD_1'),
118
(0x50, 'Numpad2', 'DOM_PK_NUMPAD_2'),
119
(0x51, 'Numpad3', 'DOM_PK_NUMPAD_3'),
120
(0x52, 'Numpad0', 'DOM_PK_NUMPAD_0'),
121
(0x53, 'NumpadDecimal', 'DOM_PK_NUMPAD_DECIMAL'),
122
(0x54, 'PrintScreen', 'DOM_PK_PRINT_SCREEN'),
123
# 0x0055 'Unidentified', ''
124
(0x56, 'IntlBackslash', 'DOM_PK_INTL_BACKSLASH'),
125
(0x57, 'F11', 'DOM_PK_F11'),
126
(0x58, 'F12', 'DOM_PK_F12'),
127
(0x59, 'NumpadEqual', 'DOM_PK_NUMPAD_EQUAL'),
128
# 0x005A 'Unidentified', ''
129
# 0x005B 'Unidentified', ''
130
# 0x005C 'Unidentified', ''
131
# 0x005D 'Unidentified', ''
132
# 0x005E 'Unidentified', ''
133
# 0x005F 'Unidentified', ''
134
# 0x0060 'Unidentified', ''
135
# 0x0061 'Unidentified', ''
136
# 0x0062 'Unidentified', ''
137
# 0x0063 'Unidentified', ''
138
(0x64, 'F13', 'DOM_PK_F13'),
139
(0x65, 'F14', 'DOM_PK_F14'),
140
(0x66, 'F15', 'DOM_PK_F15'),
141
(0x67, 'F16', 'DOM_PK_F16'),
142
(0x68, 'F17', 'DOM_PK_F17'),
143
(0x69, 'F18', 'DOM_PK_F18'),
144
(0x6A, 'F19', 'DOM_PK_F19'),
145
(0x6B, 'F20', 'DOM_PK_F20'),
146
(0x6C, 'F21', 'DOM_PK_F21'),
147
(0x6D, 'F22', 'DOM_PK_F22'),
148
(0x6E, 'F23', 'DOM_PK_F23'),
149
# 0x006F 'Unidentified', ''
150
(0x70, 'KanaMode', 'DOM_PK_KANA_MODE'),
151
(0x71, 'Lang2', 'DOM_PK_LANG_2'),
152
(0x72, 'Lang1', 'DOM_PK_LANG_1'),
153
(0x73, 'IntlRo', 'DOM_PK_INTL_RO'),
154
# 0x0074 'Unidentified', ''
155
# 0x0075 'Unidentified', ''
156
(0x76, 'F24', 'DOM_PK_F24'),
157
# 0x0077 'Unidentified', ''
158
# 0x0078 'Unidentified', ''
159
(0x79, 'Convert', 'DOM_PK_CONVERT'),
160
# 0x007A 'Unidentified', ''
161
(0x7B, 'NonConvert', 'DOM_PK_NON_CONVERT'),
162
# 0x007C 'Unidentified', ''
163
(0x7D, 'IntlYen', 'DOM_PK_INTL_YEN'),
164
(0x7E, 'NumpadComma', 'DOM_PK_NUMPAD_COMMA'),
165
# 0x007F 'Unidentified', ''
166
(0xE00A, 'Paste', 'DOM_PK_PASTE'),
167
(0xE010, 'MediaTrackPrevious', 'DOM_PK_MEDIA_TRACK_PREVIOUS'),
168
(0xE017, 'Cut', 'DOM_PK_CUT'),
169
(0xE018, 'Copy', 'DOM_PK_COPY'),
170
(0xE019, 'MediaTrackNext', 'DOM_PK_MEDIA_TRACK_NEXT'),
171
(0xE01C, 'NumpadEnter', 'DOM_PK_NUMPAD_ENTER'),
172
(0xE01D, 'ControlRight', 'DOM_PK_CONTROL_RIGHT'),
173
(0xE020, 'AudioVolumeMute', 'DOM_PK_AUDIO_VOLUME_MUTE'),
174
(0xE020, 'VolumeMute', 'DOM_PK_AUDIO_VOLUME_MUTE', 'duplicate'),
175
(0xE021, 'LaunchApp2', 'DOM_PK_LAUNCH_APP_2'),
176
(0xE022, 'MediaPlayPause', 'DOM_PK_MEDIA_PLAY_PAUSE'),
177
(0xE024, 'MediaStop', 'DOM_PK_MEDIA_STOP'),
178
(0xE02C, 'Eject', 'DOM_PK_EJECT'),
179
(0xE02E, 'AudioVolumeDown', 'DOM_PK_AUDIO_VOLUME_DOWN'),
180
(0xE02E, 'VolumeDown', 'DOM_PK_AUDIO_VOLUME_DOWN', 'duplicate'),
181
(0xE030, 'AudioVolumeUp', 'DOM_PK_AUDIO_VOLUME_UP'),
182
(0xE030, 'VolumeUp', 'DOM_PK_AUDIO_VOLUME_UP', 'duplicate'),
183
(0xE032, 'BrowserHome', 'DOM_PK_BROWSER_HOME'),
184
(0xE035, 'NumpadDivide', 'DOM_PK_NUMPAD_DIVIDE'),
185
# (0xE037, 'PrintScreen', 'DOM_PK_PRINT_SCREEN'),
186
(0xE038, 'AltRight', 'DOM_PK_ALT_RIGHT'),
187
(0xE03B, 'Help', 'DOM_PK_HELP'),
188
(0xE045, 'NumLock', 'DOM_PK_NUM_LOCK'),
189
# (0xE046, 'Pause', 'DOM_PK_'), # Says Ctrl+Pause
190
(0xE047, 'Home', 'DOM_PK_HOME'),
191
(0xE048, 'ArrowUp', 'DOM_PK_ARROW_UP'),
192
(0xE049, 'PageUp', 'DOM_PK_PAGE_UP'),
193
(0xE04B, 'ArrowLeft', 'DOM_PK_ARROW_LEFT'),
194
(0xE04D, 'ArrowRight', 'DOM_PK_ARROW_RIGHT'),
195
(0xE04F, 'End', 'DOM_PK_END'),
196
(0xE050, 'ArrowDown', 'DOM_PK_ARROW_DOWN'),
197
(0xE051, 'PageDown', 'DOM_PK_PAGE_DOWN'),
198
(0xE052, 'Insert', 'DOM_PK_INSERT'),
199
(0xE053, 'Delete', 'DOM_PK_DELETE'),
200
(0xE05B, 'MetaLeft', 'DOM_PK_META_LEFT'),
201
(0xE05B, 'OSLeft', 'DOM_PK_OS_LEFT', 'duplicate'),
202
(0xE05C, 'MetaRight', 'DOM_PK_META_RIGHT'),
203
(0xE05C, 'OSRight', 'DOM_PK_OS_RIGHT', 'duplicate'),
204
(0xE05D, 'ContextMenu', 'DOM_PK_CONTEXT_MENU'),
205
(0xE05E, 'Power', 'DOM_PK_POWER'),
206
(0xE065, 'BrowserSearch', 'DOM_PK_BROWSER_SEARCH'),
207
(0xE066, 'BrowserFavorites', 'DOM_PK_BROWSER_FAVORITES'),
208
(0xE067, 'BrowserRefresh', 'DOM_PK_BROWSER_REFRESH'),
209
(0xE068, 'BrowserStop', 'DOM_PK_BROWSER_STOP'),
210
(0xE069, 'BrowserForward', 'DOM_PK_BROWSER_FORWARD'),
211
(0xE06A, 'BrowserBack', 'DOM_PK_BROWSER_BACK'),
212
(0xE06B, 'LaunchApp1', 'DOM_PK_LAUNCH_APP_1'),
213
(0xE06C, 'LaunchMail', 'DOM_PK_LAUNCH_MAIL'),
214
(0xE06D, 'LaunchMediaPlayer', 'DOM_PK_LAUNCH_MEDIA_PLAYER'),
215
(0xE06D, 'MediaSelect', 'DOM_PK_MEDIA_SELECT', 'duplicate'),
216
# (0xE0F1, 'Lang2', 'DOM_PK_'), Hanja key
217
# (0xE0F2, 'Lang2', 'DOM_PK_'), Han/Yeong
218
]
219
220
221
def hash(s, k1, k2):
222
h = 0
223
for c in s:
224
h = int(int(int(h ^ k1) << k2) ^ ord(c)) & 0xFFFFFFFF
225
return h
226
227
228
def hash_all(k1, k2):
229
hashes = {}
230
str_to_hash = {}
231
for s in input_strings:
232
h = hash(s[1], k1, k2)
233
print('String "' + s[1] + '" hashes to %s ' % hex(h), file=sys.stderr)
234
if h in hashes:
235
print('Collision! Earlier string ' + hashes[h] + ' also hashed to %s!' % hex(h), file=sys.stderr)
236
return None
237
else:
238
hashes[h] = s[1]
239
str_to_hash[s[1]] = h
240
return (hashes, str_to_hash)
241
242
243
# Find an appropriate hash function that is collision free within the set of all input strings
244
# Try hash function format h_i = ((h_(i-1) ^ k_1) << k_2) ^ s_i, where h_i is the hash function
245
# value at step i, k_1 and k_2 are the constants we are searching, and s_i is the i'th input
246
# character
247
perfect_hash_table = None
248
249
# Last used perfect hash constants. Stored here so that this script will
250
# produce the same output it did when the current output was generated.
251
k1 = 0x7E057D79
252
k2 = 3
253
perfect_hash_table = hash_all(k1, k2)
254
255
while not perfect_hash_table:
256
# The search space is super-narrow, but since there are so few items to hash, practically
257
# almost any choice gives a collision free hash.
258
k1 = int(random.randint(0, 0x7FFFFFFF))
259
k2 = int(random.uniform(1, 8))
260
perfect_hash_table = hash_all(k1, k2)
261
262
hash_to_str, str_to_hash = perfect_hash_table
263
264
print('Found collision-free hash function!', file=sys.stderr)
265
print('h_i = ((h_(i-1) ^ %s) << %s) ^ s_i' % (hex(k1), hex(k2)), file=sys.stderr)
266
267
268
def pad_to_length(s, length):
269
return s + max(0, length - len(s)) * ' '
270
271
272
def longest_dom_pk_code_length():
273
return max(map(len, [x[2] for x in input_strings]))
274
275
276
def longest_key_code_length():
277
return max(map(len, [x[1] for x in input_strings]))
278
279
280
script_dir = os.path.dirname(os.path.abspath(__file__))
281
root = os.path.dirname(os.path.dirname(script_dir))
282
283
h_filename = os.path.join(root, 'system/include/emscripten/dom_pk_codes.h')
284
c_filename = os.path.join(root, 'system/lib/html5/dom_pk_codes.c')
285
print(f'Writing: {h_filename}')
286
print(f'Writing: {c_filename}')
287
h_file = open(h_filename, 'w')
288
c_file = open(c_filename, 'w')
289
290
291
# Generate the output file:
292
293
h_file.write('''\
294
/*
295
* Copyright 2018 The Emscripten Authors. All rights reserved.
296
* Emscripten is available under two separate licenses, the MIT license and the
297
* University of Illinois/NCSA Open Source License. Both these licenses can be
298
* found in the LICENSE file.
299
*
300
* This file was automatically generated from script
301
* tools/maint/create_dom_pk_codes.py. Edit that file to make changes here.
302
* Then run:
303
*
304
* tools/maint/create_dom_pk_codes.py
305
*
306
* in Emscripten root directory to regenerate this file.
307
*/
308
309
#pragma once
310
311
#define DOM_PK_CODE_TYPE int
312
313
''')
314
315
c_file.write('''\
316
/*
317
* This file was automatically generated from script
318
* tools/maint/create_dom_pk_codes.py. Edit that file to make changes here.
319
* Then run:
320
*
321
* tools/maint/create_dom_pk_codes.py
322
*
323
* in Emscripten root directory to regenerate this file.
324
*/
325
326
#include <emscripten/dom_pk_codes.h>
327
''')
328
329
for s in input_strings:
330
h_file.write('#define ' + pad_to_length(s[2], longest_dom_pk_code_length()) + ' 0x%04X /* "%s */' % (s[0], pad_to_length(s[1] + '"', longest_key_code_length() + 1)) + '\n')
331
332
h_file.write('''
333
#ifdef __cplusplus
334
extern "C" {
335
#endif
336
/* Maps the EmscriptenKeyboardEvent::code field from emscripten/html5.h to one of the DOM_PK codes above. */
337
DOM_PK_CODE_TYPE emscripten_compute_dom_pk_code(const char *keyCodeString);
338
339
/* Returns the string representation of the given key code ID. Useful for debug printing. */
340
const char *emscripten_dom_pk_code_to_string(DOM_PK_CODE_TYPE code);
341
#ifdef __cplusplus
342
}
343
#endif
344
''')
345
346
c_file.write('''
347
DOM_PK_CODE_TYPE emscripten_compute_dom_pk_code(const char *keyCodeString) {
348
if (!keyCodeString) return 0;
349
350
/* Compute the collision free hash. */
351
unsigned int hash = 0;
352
while (*keyCodeString) hash = ((hash ^ 0x%04XU) << %d) ^ (unsigned int)*keyCodeString++;
353
354
/*
355
* Don't expose the hash values out to the application, but map to fixed IDs.
356
* This is useful for mapping back codes to MDN documentation page at
357
*
358
* https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/code
359
*/
360
switch (hash) {
361
''' % (k1, k2))
362
363
for s in input_strings:
364
c_file.write(' case 0x%08XU /* %s */: return %s /* 0x%04X */' % (str_to_hash[s[1]], pad_to_length(s[1], longest_key_code_length()), pad_to_length(s[2] + ';', longest_dom_pk_code_length() + 1), s[0]) + '\n')
365
366
c_file.write(''' default: return DOM_PK_UNKNOWN;
367
}
368
}
369
370
const char *emscripten_dom_pk_code_to_string(DOM_PK_CODE_TYPE code) {
371
switch (code) {
372
''')
373
374
for s in input_strings:
375
if len(s) == 3:
376
c_file.write(' case %s return "%s";' % (pad_to_length(s[2] + ':', longest_dom_pk_code_length() + 1), s[2]) + '\n')
377
378
c_file.write(''' default: return "Unknown DOM_PK code";
379
}
380
}
381
''')
382
383