main
1import KeyTable from "./keysym.js";
2import keysyms from "./keysymdef.js";
3import vkeys from "./vkeys.js";
4import fixedkeys from "./fixedkeys.js";
5import DOMKeyTable from "./domkeytable.js";
6import * as browser from "../util/browser.js";
7
8// Get 'KeyboardEvent.code', handling legacy browsers
9export function getKeycode(evt) {
10 // Are we getting proper key identifiers?
11 // (unfortunately Firefox and Chrome are crappy here and gives
12 // us an empty string on some platforms, rather than leaving it
13 // undefined)
14 if (evt.code) {
15 // Mozilla isn't fully in sync with the spec yet
16 switch (evt.code) {
17 case 'OSLeft': return 'MetaLeft';
18 case 'OSRight': return 'MetaRight';
19 }
20
21 return evt.code;
22 }
23
24 // The de-facto standard is to use Windows Virtual-Key codes
25 // in the 'keyCode' field for non-printable characters
26 if (evt.keyCode in vkeys) {
27 let code = vkeys[evt.keyCode];
28
29 // macOS has messed up this code for some reason
30 if (browser.isMac() && (code === 'ContextMenu')) {
31 code = 'MetaRight';
32 }
33
34 // The keyCode doesn't distinguish between left and right
35 // for the standard modifiers
36 if (evt.location === 2) {
37 switch (code) {
38 case 'ShiftLeft': return 'ShiftRight';
39 case 'ControlLeft': return 'ControlRight';
40 case 'AltLeft': return 'AltRight';
41 }
42 }
43
44 // Nor a bunch of the numpad keys
45 if (evt.location === 3) {
46 switch (code) {
47 case 'Delete': return 'NumpadDecimal';
48 case 'Insert': return 'Numpad0';
49 case 'End': return 'Numpad1';
50 case 'ArrowDown': return 'Numpad2';
51 case 'PageDown': return 'Numpad3';
52 case 'ArrowLeft': return 'Numpad4';
53 case 'ArrowRight': return 'Numpad6';
54 case 'Home': return 'Numpad7';
55 case 'ArrowUp': return 'Numpad8';
56 case 'PageUp': return 'Numpad9';
57 case 'Enter': return 'NumpadEnter';
58 }
59 }
60
61 return code;
62 }
63
64 return 'Unidentified';
65}
66
67// Get 'KeyboardEvent.key', handling legacy browsers
68export function getKey(evt) {
69 // Are we getting a proper key value?
70 if ((evt.key !== undefined) && (evt.key !== 'Unidentified')) {
71 // Mozilla isn't fully in sync with the spec yet
72 switch (evt.key) {
73 case 'OS': return 'Meta';
74 case 'LaunchMyComputer': return 'LaunchApplication1';
75 case 'LaunchCalculator': return 'LaunchApplication2';
76 }
77
78 // iOS leaks some OS names
79 switch (evt.key) {
80 case 'UIKeyInputUpArrow': return 'ArrowUp';
81 case 'UIKeyInputDownArrow': return 'ArrowDown';
82 case 'UIKeyInputLeftArrow': return 'ArrowLeft';
83 case 'UIKeyInputRightArrow': return 'ArrowRight';
84 case 'UIKeyInputEscape': return 'Escape';
85 }
86
87 // Broken behaviour in Chrome
88 if ((evt.key === '\x00') && (evt.code === 'NumpadDecimal')) {
89 return 'Delete';
90 }
91
92 return evt.key;
93 }
94
95 // Try to deduce it based on the physical key
96 const code = getKeycode(evt);
97 if (code in fixedkeys) {
98 return fixedkeys[code];
99 }
100
101 // If that failed, then see if we have a printable character
102 if (evt.charCode) {
103 return String.fromCharCode(evt.charCode);
104 }
105
106 // At this point we have nothing left to go on
107 return 'Unidentified';
108}
109
110// Get the most reliable keysym value we can get from a key event
111export function getKeysym(evt) {
112 const key = getKey(evt);
113
114 if (key === 'Unidentified') {
115 return null;
116 }
117
118 // First look up special keys
119 if (key in DOMKeyTable) {
120 let location = evt.location;
121
122 // Safari screws up location for the right cmd key
123 if ((key === 'Meta') && (location === 0)) {
124 location = 2;
125 }
126
127 // And for Clear
128 if ((key === 'Clear') && (location === 3)) {
129 let code = getKeycode(evt);
130 if (code === 'NumLock') {
131 location = 0;
132 }
133 }
134
135 if ((location === undefined) || (location > 3)) {
136 location = 0;
137 }
138
139 // The original Meta key now gets confused with the Windows key
140 // https://bugs.chromium.org/p/chromium/issues/detail?id=1020141
141 // https://bugzilla.mozilla.org/show_bug.cgi?id=1232918
142 if (key === 'Meta') {
143 let code = getKeycode(evt);
144 if (code === 'AltLeft') {
145 return KeyTable.XK_Meta_L;
146 } else if (code === 'AltRight') {
147 return KeyTable.XK_Meta_R;
148 }
149 }
150
151 // macOS has Clear instead of NumLock, but the remote system is
152 // probably not macOS, so lying here is probably best...
153 if (key === 'Clear') {
154 let code = getKeycode(evt);
155 if (code === 'NumLock') {
156 return KeyTable.XK_Num_Lock;
157 }
158 }
159
160 // Windows sends alternating symbols for some keys when using a
161 // Japanese layout. We have no way of synchronising with the IM
162 // running on the remote system, so we send some combined keysym
163 // instead and hope for the best.
164 if (browser.isWindows()) {
165 switch (key) {
166 case 'Zenkaku':
167 case 'Hankaku':
168 return KeyTable.XK_Zenkaku_Hankaku;
169 case 'Romaji':
170 case 'KanaMode':
171 return KeyTable.XK_Romaji;
172 }
173 }
174
175 return DOMKeyTable[key][location];
176 }
177
178 // Now we need to look at the Unicode symbol instead
179
180 // Special key? (FIXME: Should have been caught earlier)
181 if (key.length !== 1) {
182 return null;
183 }
184
185 const codepoint = key.charCodeAt();
186 if (codepoint) {
187 return keysyms.lookup(codepoint);
188 }
189
190 return null;
191}