]>
git.proxmox.com Git - mirror_novnc.git/blob - include/keyboard.js
1 var kbdUtil
= (function() {
4 function substituteCodepoint(cp
) {
5 // Any Unicode code points which do not have corresponding keysym entries
6 // can be swapped out for another code point by adding them to this table
8 // {S,s} with comma below -> {S,s} with cedilla
11 // {T,t} with comma below -> {T,t} with cedilla
16 var sub
= substitutions
[cp
];
17 return sub
? sub
: cp
;
21 return navigator
&& !!(/macintosh/i).exec(navigator
.appVersion
);
23 function isWindows() {
24 return navigator
&& !!(/windows/i).exec(navigator
.appVersion
);
27 return navigator
&& !!(/linux/i).exec(navigator
.appVersion
);
30 // Return true if a modifier which is not the specified char modifier (and is not shift) is down
31 function hasShortcutModifier(charModifier
, currentModifiers
) {
33 for (var key
in currentModifiers
) {
34 if (parseInt(key
) !== 0xffe1) {
35 mods
[key
] = currentModifiers
[key
];
40 for (var k
in currentModifiers
) {
45 if (hasCharModifier(charModifier
, mods
)) {
46 return sum
> charModifier
.length
;
53 // Return true if the specified char modifier is currently down
54 function hasCharModifier(charModifier
, currentModifiers
) {
55 if (charModifier
.length
=== 0) { return false; }
57 for (var i
= 0; i
< charModifier
.length
; ++i
) {
58 if (!currentModifiers
[charModifier
[i
]]) {
65 // Helper object tracking modifier key state
66 // and generates fake key events to compensate if it gets out of sync
67 function ModifierSync(charModifier
) {
76 // on Mac, Option (AKA Alt) is used as a char modifier
79 else if (isWindows()) {
80 // on Windows, Ctrl+Alt is used as a char modifier
81 charModifier
= [alt
, ctrl
];
84 // on Linux, AltGr is used as a char modifier
85 charModifier
= [altGr
];
99 function sync(evt
, keysym
) {
101 function syncKey(keysym
) {
102 return {keysym
: keysyms
.lookup(keysym
), type
: state
[keysym
] ? 'keydown' : 'keyup'};
105 if (evt
.ctrlKey
!== undefined && evt
.ctrlKey
!== state
[ctrl
] && keysym
!== ctrl
) {
106 state
[ctrl
] = evt
.ctrlKey
;
107 result
.push(syncKey(ctrl
));
109 if (evt
.altKey
!== undefined && evt
.altKey
!== state
[alt
] && keysym
!== alt
) {
110 state
[alt
] = evt
.altKey
;
111 result
.push(syncKey(alt
));
113 if (evt
.altGraphKey
!== undefined && evt
.altGraphKey
!== state
[altGr
] && keysym
!== altGr
) {
114 state
[altGr
] = evt
.altGraphKey
;
115 result
.push(syncKey(altGr
));
117 if (evt
.shiftKey
!== undefined && evt
.shiftKey
!== state
[shift
] && keysym
!== shift
) {
118 state
[shift
] = evt
.shiftKey
;
119 result
.push(syncKey(shift
));
121 if (evt
.metaKey
!== undefined && evt
.metaKey
!== state
[meta
] && keysym
!== meta
) {
122 state
[meta
] = evt
.metaKey
;
123 result
.push(syncKey(meta
));
127 function syncKeyEvent(evt
, down
) {
128 var obj
= getKeysym(evt
);
129 var keysym
= obj
? obj
.keysym
: null;
131 // first, apply the event itself, if relevant
132 if (keysym
!== null && state
[keysym
] !== undefined) {
133 state
[keysym
] = down
;
135 return sync(evt
, keysym
);
139 // sync on the appropriate keyboard event
140 keydown: function(evt
) { return syncKeyEvent(evt
, true);},
141 keyup: function(evt
) { return syncKeyEvent(evt
, false);},
142 // Call this with a non-keyboard event (such as mouse events) to use its modifier state to synchronize anyway
143 syncAny: function(evt
) { return sync(evt
);},
145 // is a shortcut modifier down?
146 hasShortcutModifier: function() { return hasShortcutModifier(charModifier
, state
); },
147 // if a char modifier is down, return the keys it consists of, otherwise return null
148 activeCharModifier: function() { return hasCharModifier(charModifier
, state
) ? charModifier
: null; }
152 // Get a key ID from a keyboard event
153 // May be a string or an integer depending on the available properties
154 function getKey(evt
){
163 // Get the most reliable keysym value we can get from a key event
164 // if char/charCode is available, prefer those, otherwise fall back to key/keyCode/which
165 function getKeysym(evt
){
167 if (evt
.char && evt
.char.length
=== 1) {
168 codepoint
= evt
.char.charCodeAt();
170 else if (evt
.charCode
) {
171 codepoint
= evt
.charCode
;
175 var res
= keysyms
.fromUnicode(substituteCodepoint(codepoint
));
180 // we could check evt.key here.
181 // Legal values are defined in http://www.w3.org/TR/DOM-Level-3-Events/#key-values-list,
182 // so we "just" need to map them to keysym, but AFAIK this is only available in IE10, which also provides evt.key
183 // so we don't *need* it yet
185 return keysyms
.lookup(keysymFromKeyCode(evt
.keyCode
, evt
.shiftKey
));
188 return keysyms
.lookup(keysymFromKeyCode(evt
.which
, evt
.shiftKey
));
193 // Given a keycode, try to predict which keysym it might be.
194 // If the keycode is unknown, null is returned.
195 function keysymFromKeyCode(keycode
, shiftPressed
) {
196 if (typeof(keycode
) !== 'number') {
199 // won't be accurate for azerty
200 if (keycode
>= 0x30 && keycode
<= 0x39) {
201 return keycode
; // digit
203 if (keycode
>= 0x41 && keycode
<= 0x5a) {
204 // remap to lowercase unless shift is down
205 return shiftPressed
? keycode
: keycode
+ 32; // A-Z
207 if (keycode
>= 0x60 && keycode
<= 0x69) {
208 return 0xffb0 + (keycode
- 0x60); // numpad 0-9
212 case 0x20: return 0x20; // space
213 case 0x6a: return 0xffaa; // multiply
214 case 0x6b: return 0xffab; // add
215 case 0x6c: return 0xffac; // separator
216 case 0x6d: return 0xffad; // subtract
217 case 0x6e: return 0xffae; // decimal
218 case 0x6f: return 0xffaf; // divide
219 case 0xbb: return 0x2b; // +
220 case 0xbc: return 0x2c; // ,
221 case 0xbd: return 0x2d; // -
222 case 0xbe: return 0x2e; // .
225 return nonCharacterKey({keyCode
: keycode
});
228 // if the key is a known non-character key (any key which doesn't generate character data)
229 // return its keysym value. Otherwise return null
230 function nonCharacterKey(evt
) {
231 // evt.key not implemented yet
232 if (!evt
.keyCode
) { return null; }
233 var keycode
= evt
.keyCode
;
235 if (keycode
>= 0x70 && keycode
<= 0x87) {
236 return 0xffbe + keycode
- 0x70; // F1-F24
240 case 8 : return 0xFF08; // BACKSPACE
241 case 13 : return 0xFF0D; // ENTER
243 case 9 : return 0xFF09; // TAB
245 case 27 : return 0xFF1B; // ESCAPE
246 case 46 : return 0xFFFF; // DELETE
248 case 36 : return 0xFF50; // HOME
249 case 35 : return 0xFF57; // END
250 case 33 : return 0xFF55; // PAGE_UP
251 case 34 : return 0xFF56; // PAGE_DOWN
252 case 45 : return 0xFF63; // INSERT
254 case 37 : return 0xFF51; // LEFT
255 case 38 : return 0xFF52; // UP
256 case 39 : return 0xFF53; // RIGHT
257 case 40 : return 0xFF54; // DOWN
258 case 16 : return 0xFFE1; // SHIFT
259 case 17 : return 0xFFE3; // CONTROL
260 case 18 : return 0xFFE9; // Left ALT (Mac Option)
262 case 224 : return 0xFE07; // Meta
263 case 225 : return 0xFE03; // AltGr
264 case 91 : return 0xFFEC; // Super_L (Win Key)
265 case 92 : return 0xFFED; // Super_R (Win Key)
266 case 93 : return 0xFF67; // Menu (Win Menu), Mac Command
267 default: return null;
271 hasShortcutModifier
: hasShortcutModifier
,
272 hasCharModifier
: hasCharModifier
,
273 ModifierSync
: ModifierSync
,
275 getKeysym
: getKeysym
,
276 keysymFromKeyCode
: keysymFromKeyCode
,
277 nonCharacterKey
: nonCharacterKey
,
278 substituteCodepoint
: substituteCodepoint
282 // Takes a DOM keyboard event and:
283 // - determines which keysym it represents
284 // - determines a keyId identifying the key that was pressed (corresponding to the key/keyCode properties on the DOM event)
285 // - synthesizes events to synchronize modifier key state between which modifiers are actually down, and which we thought were down
286 // - marks each event with an 'escape' property if a modifier was down which should be "escaped"
287 // - generates a "stall" event in cases where it might be necessary to wait and see if a keypress event follows a keydown
288 // This information is collected into an object which is passed to the next() function. (one call per event)
289 function KeyEventDecoder(modifierState
, next
) {
291 function sendAll(evts
) {
292 for (var i
= 0; i
< evts
.length
; ++i
) {
296 function process(evt
, type
) {
297 var result
= {type
: type
};
298 var keyId
= kbdUtil
.getKey(evt
);
300 result
.keyId
= keyId
;
303 var keysym
= kbdUtil
.getKeysym(evt
);
305 var hasModifier
= modifierState
.hasShortcutModifier() || !!modifierState
.activeCharModifier();
306 // Is this a case where we have to decide on the keysym right away, rather than waiting for the keypress?
307 // "special" keys like enter, tab or backspace don't send keypress events,
308 // and some browsers don't send keypresses at all if a modifier is down
309 if (keysym
&& (type
!== 'keydown' || kbdUtil
.nonCharacterKey(evt
) || hasModifier
)) {
310 result
.keysym
= keysym
;
313 var isShift
= evt
.keyCode
=== 0x10 || evt
.key
=== 'Shift';
315 // Should we prevent the browser from handling the event?
316 // Doing so on a keydown (in most browsers) prevents keypress from being generated
317 // so only do that if we have to.
318 var suppress
= !isShift
&& (type
!== 'keydown' || modifierState
.hasShortcutModifier() || !!kbdUtil
.nonCharacterKey(evt
));
320 // If a char modifier is down on a keydown, we need to insert a stall,
321 // so VerifyCharModifier knows to wait and see if a keypress is comnig
322 var stall
= type
=== 'keydown' && modifierState
.activeCharModifier() && !kbdUtil
.nonCharacterKey(evt
);
324 // if a char modifier is pressed, get the keys it consists of (on Windows, AltGr is equivalent to Ctrl+Alt)
325 var active
= modifierState
.activeCharModifier();
327 // If we have a char modifier down, and we're able to determine a keysym reliably
328 // then (a) we know to treat the modifier as a char modifier,
329 // and (b) we'll have to "escape" the modifier to undo the modifier when sending the char.
330 if (active
&& keysym
) {
331 var isCharModifier
= false;
332 for (var i
= 0; i
< active
.length
; ++i
) {
333 if (active
[i
] === keysym
.keysym
) {
334 isCharModifier
= true;
337 if (type
=== 'keypress' && !isCharModifier
) {
338 result
.escape
= modifierState
.activeCharModifier();
343 // insert a fake "stall" event
344 next({type
: 'stall'});
352 keydown: function(evt
) {
353 sendAll(modifierState
.keydown(evt
));
354 return process(evt
, 'keydown');
356 keypress: function(evt
) {
357 return process(evt
, 'keypress');
359 keyup: function(evt
) {
360 sendAll(modifierState
.keyup(evt
));
361 return process(evt
, 'keyup');
363 syncModifiers: function(evt
) {
364 sendAll(modifierState
.syncAny(evt
));
366 releaseAll: function() { next({type
: 'releaseall'}); }
370 // Combines keydown and keypress events where necessary to handle char modifiers.
371 // On some OS'es, a char modifier is sometimes used as a shortcut modifier.
372 // For example, on Windows, AltGr is synonymous with Ctrl-Alt. On a Danish keyboard layout, AltGr-2 yields a @, but Ctrl-Alt-D does nothing
373 // so when used with the '2' key, Ctrl-Alt counts as a char modifier (and should be escaped), but when used with 'D', it does not.
374 // The only way we can distinguish these cases is to wait and see if a keypress event arrives
375 // When we receive a "stall" event, wait a few ms before processing the next keydown. If a keypress has also arrived, merge the two
376 function VerifyCharModifier(next
) {
384 while (queue
.length
!== 0) {
386 queue
= queue
.splice(1);
389 // insert a delay before processing available events.
390 timer
= setTimeout(function() {
397 // is the next element a keypress? Then we should merge the two
398 if (queue
.length
!== 0 && queue
[0].type
=== 'keypress') {
399 // Firefox sends keypress even when no char is generated.
400 // so, if keypress keysym is the same as we'd have guessed from keydown,
401 // the modifier didn't have any effect, and should not be escaped
402 if (queue
[0].escape
&& (!cur
.keysym
|| cur
.keysym
.keysym
!== queue
[0].keysym
.keysym
)) {
403 cur
.escape
= queue
[0].escape
;
405 cur
.keysym
= queue
[0].keysym
;
406 queue
= queue
.splice(1);
411 // swallow stall events, and pass all others to the next stage
412 if (cur
.type
!== 'stall') {
417 return function(evt
) {
423 // Keeps track of which keys we (and the server) believe are down
424 // When a keyup is received, match it against this list, to determine the corresponding keysym(s)
425 // in some cases, a single key may produce multiple keysyms, so the corresponding keyup event must release all of these chars
426 // key repeat events should be merged into a single entry.
427 // Because we can't always identify which entry a keydown or keyup event corresponds to, we sometimes have to guess
428 function TrackKeyState(next
) {
432 return function (evt
) {
433 var last
= state
.length
!== 0 ? state
[state
.length
-1] : null;
437 // insert a new entry if last seen key was different.
438 if (!last
|| !evt
.keyId
|| last
.keyId
!== evt
.keyId
) {
439 last
= {keyId
: evt
.keyId
, keysyms
: {}};
443 // make sure last event contains this keysym (a single "logical" keyevent
444 // can cause multiple key events to be sent to the VNC server)
445 last
.keysyms
[evt
.keysym
.keysym
] = evt
.keysym
;
446 last
.ignoreKeyPress
= true;
452 last
= {keyId
: evt
.keyId
, keysyms
: {}};
456 console
.log('keypress with no keysym:', evt
);
459 // If we didn't expect a keypress, and already sent a keydown to the VNC server
460 // based on the keydown, make sure to skip this event.
461 if (evt
.keysym
&& !last
.ignoreKeyPress
) {
462 last
.keysyms
[evt
.keysym
.keysym
] = evt
.keysym
;
463 evt
.type
= 'keydown';
468 if (state
.length
=== 0) {
472 // do we have a matching key tracked as being down?
473 for (var i
= 0; i
!== state
.length
; ++i
) {
474 if (state
[i
].keyId
=== evt
.keyId
) {
479 // if we couldn't find a match (it happens), assume it was the last key pressed
481 idx
= state
.length
- 1;
484 var item
= state
.splice(idx
, 1)[0];
485 // for each keysym tracked by this key entry, clone the current event and override the keysym
486 for (var key
in item
.keysyms
) {
487 var clone
= (function(){
489 return function (obj
) { Clone
.prototype=obj
; return new Clone(); };
491 var out
= clone(evt
);
492 out
.keysym
= item
.keysyms
[key
];
497 for (var i
= 0; i
< state
.length
; ++i
) {
498 for (var key
in state
[i
].keysyms
) {
499 var keysym
= state
[i
].keysyms
[key
];
500 next({keyId
: 0, keysym
: keysym
, type
: 'keyup'});
508 // Handles "escaping" of modifiers: if a char modifier is used to produce a keysym (such as AltGr-2 to generate an @),
509 // then the modifier must be "undone" before sending the @, and "redone" afterwards.
510 function EscapeModifiers(next
) {
512 return function(evt
) {
513 if (evt
.type
!== 'keydown' || evt
.escape
=== undefined) {
518 for (var i
= 0; i
< evt
.escape
.length
; ++i
) {
519 next({type
: 'keyup', keyId
: 0, keysym
: keysyms
.lookup(evt
.escape
[i
])});
521 // send the character event
524 for (var i
= 0; i
< evt
.escape
.length
; ++i
) {
525 next({type
: 'keydown', keyId
: 0, keysym
: keysyms
.lookup(evt
.escape
[i
])});