]>
git.proxmox.com Git - mirror_novnc.git/blob - core/input/util.js
1 import KeyTable
from "./keysym.js";
2 import keysyms
from "./keysymdef.js";
3 import vkeys
from "./vkeys.js";
4 import fixedkeys
from "./fixedkeys.js";
7 return navigator
&& !!(/mac/i).exec(navigator
.platform
);
10 return navigator
&& !!(/win/i).exec(navigator
.platform
);
13 return navigator
&& !!(/linux/i).exec(navigator
.platform
);
16 // Return true if the specified char modifier is currently down
17 export function hasCharModifier(charModifier
, currentModifiers
) {
18 if (charModifier
.length
=== 0) { return false; }
20 for (var i
= 0; i
< charModifier
.length
; ++i
) {
21 if (!currentModifiers
[charModifier
[i
]]) {
28 // Helper object tracking modifier key state
29 // and generates fake key events to compensate if it gets out of sync
30 export function ModifierSync(charModifier
) {
33 // on Mac, Option (AKA Alt) is used as a char modifier
34 charModifier
= [KeyTable
.XK_Alt_L
];
36 else if (isWindows()) {
37 // on Windows, Ctrl+Alt is used as a char modifier
38 charModifier
= [KeyTable
.XK_Alt_L
, KeyTable
.XK_Control_L
];
41 // on Linux, ISO Level 3 Shift (AltGr) is used as a char modifier
42 charModifier
= [KeyTable
.XK_ISO_Level3_Shift
];
50 state
[KeyTable
.XK_Control_L
] = false;
51 state
[KeyTable
.XK_Alt_L
] = false;
52 state
[KeyTable
.XK_ISO_Level3_Shift
] = false;
53 state
[KeyTable
.XK_Shift_L
] = false;
54 state
[KeyTable
.XK_Meta_L
] = false;
56 function sync(evt
, keysym
) {
58 function syncKey(keysym
) {
59 return {keysym
: keysym
, type
: state
[keysym
] ? 'keydown' : 'keyup'};
62 if (evt
.ctrlKey
!== undefined &&
63 evt
.ctrlKey
!== state
[KeyTable
.XK_Control_L
] && keysym
!== KeyTable
.XK_Control_L
) {
64 state
[KeyTable
.XK_Control_L
] = evt
.ctrlKey
;
65 result
.push(syncKey(KeyTable
.XK_Control_L
));
67 if (evt
.altKey
!== undefined &&
68 evt
.altKey
!== state
[KeyTable
.XK_Alt_L
] && keysym
!== KeyTable
.XK_Alt_L
) {
69 state
[KeyTable
.XK_Alt_L
] = evt
.altKey
;
70 result
.push(syncKey(KeyTable
.XK_Alt_L
));
72 if (evt
.altGraphKey
!== undefined &&
73 evt
.altGraphKey
!== state
[KeyTable
.XK_ISO_Level3_Shift
] && keysym
!== KeyTable
.XK_ISO_Level3_Shift
) {
74 state
[KeyTable
.XK_ISO_Level3_Shift
] = evt
.altGraphKey
;
75 result
.push(syncKey(KeyTable
.XK_ISO_Level3_Shift
));
77 if (evt
.shiftKey
!== undefined &&
78 evt
.shiftKey
!== state
[KeyTable
.XK_Shift_L
] && keysym
!== KeyTable
.XK_Shift_L
) {
79 state
[KeyTable
.XK_Shift_L
] = evt
.shiftKey
;
80 result
.push(syncKey(KeyTable
.XK_Shift_L
));
82 if (evt
.metaKey
!== undefined &&
83 evt
.metaKey
!== state
[KeyTable
.XK_Meta_L
] && keysym
!== KeyTable
.XK_Meta_L
) {
84 state
[KeyTable
.XK_Meta_L
] = evt
.metaKey
;
85 result
.push(syncKey(KeyTable
.XK_Meta_L
));
89 function syncKeyEvent(evt
, down
) {
90 var keysym
= getKeysym(evt
);
92 // first, apply the event itself, if relevant
93 if (keysym
!== null && state
[keysym
] !== undefined) {
96 return sync(evt
, keysym
);
100 // sync on the appropriate keyboard event
101 keydown: function(evt
) { return syncKeyEvent(evt
, true);},
102 keyup: function(evt
) { return syncKeyEvent(evt
, false);},
104 // if a char modifier is down, return the keys it consists of, otherwise return null
105 activeCharModifier: function() { return hasCharModifier(charModifier
, state
) ? charModifier
: null; }
109 // Get 'KeyboardEvent.code', handling legacy browsers
110 export function getKeycode(evt
){
111 // Are we getting proper key identifiers?
112 // (unfortunately Firefox and Chrome are crappy here and gives
113 // us an empty string on some platforms, rather than leaving it
116 // Mozilla isn't fully in sync with the spec yet
118 case 'OSLeft': return 'MetaLeft';
119 case 'OSRight': return 'MetaRight';
125 // The de-facto standard is to use Windows Virtual-Key codes
126 // in the 'keyCode' field for non-printable characters. However
127 // Webkit sets it to the same as charCode in 'keypress' events.
128 if ((evt
.type
!== 'keypress') && (evt
.keyCode
in vkeys
)) {
129 var code
= vkeys
[evt
.keyCode
];
131 // macOS has messed up this code for some reason
132 if (isMac() && (code
=== 'ContextMenu')) {
136 // The keyCode doesn't distinguish between left and right
137 // for the standard modifiers
138 if (evt
.location
=== 2) {
140 case 'ShiftLeft': return 'ShiftRight';
141 case 'ControlLeft': return 'ControlRight';
142 case 'AltLeft': return 'AltRight';
146 // Nor a bunch of the numpad keys
147 if (evt
.location
=== 3) {
149 case 'Delete': return 'NumpadDecimal';
150 case 'Insert': return 'Numpad0';
151 case 'End': return 'Numpad1';
152 case 'ArrowDown': return 'Numpad2';
153 case 'PageDown': return 'Numpad3';
154 case 'ArrowLeft': return 'Numpad4';
155 case 'ArrowRight': return 'Numpad6';
156 case 'Home': return 'Numpad7';
157 case 'ArrowUp': return 'Numpad8';
158 case 'PageUp': return 'Numpad9';
159 case 'Enter': return 'NumpadEnter';
166 return 'Unidentified';
169 // Get the most reliable keysym value we can get from a key event
170 export function getKeysym(evt
){
172 // We start with layout independent keys
173 var code
= getKeycode(evt
);
174 if (code
in fixedkeys
) {
175 return fixedkeys
[code
];
178 // Next with mildly layout or state sensitive stuff
181 if (code
=== 'AltRight') {
182 if (evt
.key
=== 'AltGraph') {
183 return KeyTable
.XK_ISO_Level3_Shift
;
185 return KeyTable
.XK_Alt_R
;
190 if (evt
.location
=== 3) {
193 // IE and Edge use some ancient version of the spec
194 // https://developer.microsoft.com/en-us/microsoft-edge/platform/issues/8860571/
196 case 'Up': key
= 'ArrowUp'; break;
197 case 'Left': key
= 'ArrowLeft'; break;
198 case 'Right': key
= 'ArrowRight'; break;
199 case 'Down': key
= 'ArrowDown'; break;
200 case 'Del': key
= 'Delete'; break;
203 // Safari doesn't support KeyboardEvent.key yet
204 if ((key
=== undefined) && (evt
.charCode
)) {
205 key
= String
.fromCharCode(evt
.charCode
);
209 case '0': return KeyTable
.XK_KP_0
;
210 case '1': return KeyTable
.XK_KP_1
;
211 case '2': return KeyTable
.XK_KP_2
;
212 case '3': return KeyTable
.XK_KP_3
;
213 case '4': return KeyTable
.XK_KP_4
;
214 case '5': return KeyTable
.XK_KP_5
;
215 case '6': return KeyTable
.XK_KP_6
;
216 case '7': return KeyTable
.XK_KP_7
;
217 case '8': return KeyTable
.XK_KP_8
;
218 case '9': return KeyTable
.XK_KP_9
;
219 // There is utter mayhem in the world when it comes to which
220 // character to use as a decimal separator...
221 case '.': return KeyTable
.XK_KP_Decimal
;
222 case ',': return KeyTable
.XK_KP_Separator
;
223 case 'Home': return KeyTable
.XK_KP_Home
;
224 case 'End': return KeyTable
.XK_KP_End
;
225 case 'PageUp': return KeyTable
.XK_KP_Prior
;
226 case 'PageDown': return KeyTable
.XK_KP_Next
;
227 case 'Insert': return KeyTable
.XK_KP_Insert
;
228 case 'Delete': return KeyTable
.XK_KP_Delete
;
229 case 'ArrowUp': return KeyTable
.XK_KP_Up
;
230 case 'ArrowLeft': return KeyTable
.XK_KP_Left
;
231 case 'ArrowRight': return KeyTable
.XK_KP_Right
;
232 case 'ArrowDown': return KeyTable
.XK_KP_Down
;
236 // Now we need to look at the Unicode symbol instead
241 // Special key? (FIXME: Should have been caught earlier)
242 if (evt
.key
.length
!== 1) {
246 codepoint
= evt
.key
.charCodeAt();
247 } else if ('charCode' in evt
) {
248 codepoint
= evt
.charCode
;
252 return keysyms
.lookup(codepoint
);
258 // Takes a DOM keyboard event and:
259 // - determines which keysym it represents
260 // - determines a code identifying the key that was pressed (corresponding to the code/keyCode properties on the DOM event)
261 // - marks each event with an 'escape' property if a modifier was down which should be "escaped"
262 // This information is collected into an object which is passed to the next() function. (one call per event)
263 export function KeyEventDecoder (modifierState
, next
) {
265 function process(evt
, type
) {
266 var result
= {type
: type
};
267 var code
= getKeycode(evt
);
268 if (code
=== 'Unidentified') {
269 // Unstable, but we don't have anything else to go on
270 // (don't use it for 'keypress' events thought since
271 // WebKit sets it to the same as charCode)
272 if (evt
.keyCode
&& (evt
.type
!== 'keypress')) {
273 code
= 'Platform' + evt
.keyCode
;
278 var keysym
= getKeysym(evt
);
280 // Is this a case where we have to decide on the keysym right away, rather than waiting for the keypress?
281 // "special" keys like enter, tab or backspace don't send keypress events,
282 // and some browsers don't send keypresses at all if a modifier is down
284 result
.keysym
= keysym
;
287 // Should we prevent the browser from handling the event?
288 // Doing so on a keydown (in most browsers) prevents keypress from being generated
289 // so only do that if we have to.
290 var suppress
= type
!== 'keydown' || !!keysym
;
292 // if a char modifier is pressed, get the keys it consists of (on Windows, AltGr is equivalent to Ctrl+Alt)
293 var active
= modifierState
.activeCharModifier();
295 // If we have a char modifier down, and we're able to determine a keysym reliably
296 // then (a) we know to treat the modifier as a char modifier,
297 // and (b) we'll have to "escape" the modifier to undo the modifier when sending the char.
298 if (active
&& keysym
) {
299 var isCharModifier
= false;
300 for (var i
= 0; i
< active
.length
; ++i
) {
301 if (active
[i
] === keysym
) {
302 isCharModifier
= true;
305 if (type
=== 'keypress' && !isCharModifier
) {
306 result
.escape
= modifierState
.activeCharModifier();
316 keydown: function(evt
) {
317 modifierState
.keydown(evt
);
318 return process(evt
, 'keydown');
320 keypress: function(evt
) {
321 return process(evt
, 'keypress');
323 keyup: function(evt
) {
324 modifierState
.keyup(evt
);
325 return process(evt
, 'keyup');
327 releaseAll: function() { next({type
: 'releaseall'}); }
331 // Keeps track of which keys we (and the server) believe are down
332 // When a keyup is received, match it against this list, to determine the corresponding keysym(s)
333 // in some cases, a single key may produce multiple keysyms, so the corresponding keyup event must release all of these chars
334 // key repeat events should be merged into a single entry.
335 // Because we can't always identify which entry a keydown or keyup event corresponds to, we sometimes have to guess
336 export function TrackKeyState (next
) {
340 return function (evt
) {
341 var last
= state
.length
!== 0 ? state
[state
.length
-1] : null;
345 // insert a new entry if last seen key was different.
346 if (!last
|| evt
.code
=== 'Unidentified' || last
.code
!== evt
.code
) {
347 last
= {code
: evt
.code
, keysyms
: {}};
351 // make sure last event contains this keysym (a single "logical" keyevent
352 // can cause multiple key events to be sent to the VNC server)
353 last
.keysyms
[evt
.keysym
] = evt
.keysym
;
354 last
.ignoreKeyPress
= true;
360 last
= {code
: evt
.code
, keysyms
: {}};
364 console
.log('keypress with no keysym:', evt
);
367 // If we didn't expect a keypress, and already sent a keydown to the VNC server
368 // based on the keydown, make sure to skip this event.
369 if (evt
.keysym
&& !last
.ignoreKeyPress
) {
370 last
.keysyms
[evt
.keysym
] = evt
.keysym
;
371 evt
.type
= 'keydown';
376 if (state
.length
=== 0) {
380 // do we have a matching key tracked as being down?
381 for (var i
= 0; i
!== state
.length
; ++i
) {
382 if (state
[i
].code
=== evt
.code
) {
387 // if we couldn't find a match (it happens), assume it was the last key pressed
389 idx
= state
.length
- 1;
392 var item
= state
.splice(idx
, 1)[0];
393 // for each keysym tracked by this key entry, clone the current event and override the keysym
394 var clone
= (function(){
396 return function (obj
) { Clone
.prototype=obj
; return new Clone(); };
398 for (var key
in item
.keysyms
) {
399 var out
= clone(evt
);
400 out
.keysym
= item
.keysyms
[key
];
405 /* jshint shadow: true */
406 for (var i
= 0; i
< state
.length
; ++i
) {
407 for (var key
in state
[i
].keysyms
) {
408 var keysym
= state
[i
].keysyms
[key
];
409 next({code
: 'Unidentified', keysym
: keysym
, type
: 'keyup'});
412 /* jshint shadow: false */
418 // Handles "escaping" of modifiers: if a char modifier is used to produce a keysym (such as AltGr-2 to generate an @),
419 // then the modifier must be "undone" before sending the @, and "redone" afterwards.
420 export function EscapeModifiers (next
) {
422 return function(evt
) {
423 if (evt
.type
!== 'keydown' || evt
.escape
=== undefined) {
428 for (var i
= 0; i
< evt
.escape
.length
; ++i
) {
429 next({type
: 'keyup', code
: 'Unidentified', keysym
: evt
.escape
[i
]});
431 // send the character event
434 /* jshint shadow: true */
435 for (var i
= 0; i
< evt
.escape
.length
; ++i
) {
436 next({type
: 'keydown', code
: 'Unidentified', keysym
: evt
.escape
[i
]});
438 /* jshint shadow: false */