]>
git.proxmox.com Git - mirror_novnc.git/blob - include/input.js
2 * noVNC: HTML5 VNC client
3 * Copyright (C) 2012 Joel Martin
4 * Copyright (C) 2013 Samuel Mannehed for Cendio AB
5 * Licensed under MPL 2.0 or any later version (see LICENSE.txt)
8 /*jslint browser: true, white: false, bitwise: false */
9 /*global window, Util */
13 // Keyboard event handler
16 function Keyboard(defaults
) {
19 var that
= {}, // Public API methods
20 conf
= {}, // Configuration attributes
22 keyDownList
= []; // List of depressed keys
23 // (even if they are happy)
25 // Configuration attributes
26 Util
.conf_defaults(conf
, that
, defaults
, [
27 ['target', 'wo', 'dom', document
, 'DOM element that captures keyboard input'],
28 ['focused', 'rw', 'bool', true, 'Capture and send key events'],
30 ['onKeyPress', 'rw', 'func', null, 'Handler for key press/release']
38 // From the event keyCode return the keysym value for keys that need
39 // to be suppressed otherwise they may trigger unintended browser
41 function getKeysymSpecial(evt
) {
44 switch ( evt
.keyCode
) {
45 // These generate a keyDown and keyPress in Firefox and Opera
46 case 8 : keysym
= 0xFF08; break; // BACKSPACE
47 case 13 : keysym
= 0xFF0D; break; // ENTER
49 // This generates a keyDown and keyPress in Opera
50 case 9 : keysym
= 0xFF09; break; // TAB
54 if (evt
.type
=== 'keydown') {
55 switch ( evt
.keyCode
) {
56 case 27 : keysym
= 0xFF1B; break; // ESCAPE
57 case 46 : keysym
= 0xFFFF; break; // DELETE
59 case 36 : keysym
= 0xFF50; break; // HOME
60 case 35 : keysym
= 0xFF57; break; // END
61 case 33 : keysym
= 0xFF55; break; // PAGE_UP
62 case 34 : keysym
= 0xFF56; break; // PAGE_DOWN
63 case 45 : keysym
= 0xFF63; break; // INSERT
64 // '-' during keyPress
65 case 37 : keysym
= 0xFF51; break; // LEFT
66 case 38 : keysym
= 0xFF52; break; // UP
67 case 39 : keysym
= 0xFF53; break; // RIGHT
68 case 40 : keysym
= 0xFF54; break; // DOWN
69 case 16 : keysym
= 0xFFE1; break; // SHIFT
70 case 17 : keysym
= 0xFFE3; break; // CONTROL
71 //case 18 : keysym = 0xFFE7; break; // Left Meta (Mac Option)
72 case 18 : keysym
= 0xFFE9; break; // Left ALT (Mac Command)
74 case 112 : keysym
= 0xFFBE; break; // F1
75 case 113 : keysym
= 0xFFBF; break; // F2
76 case 114 : keysym
= 0xFFC0; break; // F3
77 case 115 : keysym
= 0xFFC1; break; // F4
78 case 116 : keysym
= 0xFFC2; break; // F5
79 case 117 : keysym
= 0xFFC3; break; // F6
80 case 118 : keysym
= 0xFFC4; break; // F7
81 case 119 : keysym
= 0xFFC5; break; // F8
82 case 120 : keysym
= 0xFFC6; break; // F9
83 case 121 : keysym
= 0xFFC7; break; // F10
84 case 122 : keysym
= 0xFFC8; break; // F11
85 case 123 : keysym
= 0xFFC9; break; // F12
87 case 225 : keysym
= 0xFE03; break; // AltGr
88 case 91 : keysym
= 0xFFEC; break; // Super_R (Win Key)
89 case 93 : keysym
= 0xFF67; break; // Menu (Win Menu)
95 if ((!keysym
) && (evt
.ctrlKey
|| evt
.altKey
)) {
96 if ((typeof(evt
.which
) !== "undefined") && (evt
.which
> 0)) {
100 // Firefox and Opera when ctrl/alt + special
101 Util
.Warn("which not set, using keyCode");
102 keysym
= evt
.keyCode
;
107 case 186 : keysym
= 59; break; // ; (IE)
108 case 187 : keysym
= 61; break; // = (IE)
109 case 188 : keysym
= 44; break; // , (Mozilla, IE)
110 case 109 : // - (Mozilla, Opera)
111 if (Util
.Engine
.gecko
|| Util
.Engine
.presto
) {
114 case 173 : // - (Mozilla)
115 if (Util
.Engine
.gecko
) {
118 case 189 : keysym
= 45; break; // - (IE)
119 case 190 : keysym
= 46; break; // . (Mozilla, IE)
120 case 191 : keysym
= 47; break; // / (Mozilla, IE)
121 case 192 : keysym
= 96; break; // ` (Mozilla, IE)
122 case 219 : keysym
= 91; break; // [ (Mozilla, IE)
123 case 220 : keysym
= 92; break; // \ (Mozilla, IE)
124 case 221 : keysym
= 93; break; // ] (Mozilla, IE)
125 case 222 : keysym
= 39; break; // ' (Mozilla, IE)
128 /* Remap shifted and unshifted keys */
129 if (!!evt
.shiftKey
) {
131 case 48 : keysym
= 41 ; break; // ) (shifted 0)
132 case 49 : keysym
= 33 ; break; // ! (shifted 1)
133 case 50 : keysym
= 64 ; break; // @ (shifted 2)
134 case 51 : keysym
= 35 ; break; // # (shifted 3)
135 case 52 : keysym
= 36 ; break; // $ (shifted 4)
136 case 53 : keysym
= 37 ; break; // % (shifted 5)
137 case 54 : keysym
= 94 ; break; // ^ (shifted 6)
138 case 55 : keysym
= 38 ; break; // & (shifted 7)
139 case 56 : keysym
= 42 ; break; // * (shifted 8)
140 case 57 : keysym
= 40 ; break; // ( (shifted 9)
142 case 59 : keysym
= 58 ; break; // : (shifted `)
143 case 61 : keysym
= 43 ; break; // + (shifted ;)
144 case 44 : keysym
= 60 ; break; // < (shifted ,)
145 case 45 : keysym
= 95 ; break; // _ (shifted -)
146 case 46 : keysym
= 62 ; break; // > (shifted .)
147 case 47 : keysym
= 63 ; break; // ? (shifted /)
148 case 96 : keysym
= 126; break; // ~ (shifted `)
149 case 91 : keysym
= 123; break; // { (shifted [)
150 case 92 : keysym
= 124; break; // | (shifted \)
151 case 93 : keysym
= 125; break; // } (shifted ])
152 case 39 : keysym
= 34 ; break; // " (shifted ')
154 } else if ((keysym
>= 65) && (keysym
<=90)) {
155 /* Remap unshifted A-Z */
157 } else if (evt
.keyLocation
=== 3) {
160 case 96 : keysym
= 48; break; // 0
161 case 97 : keysym
= 49; break; // 1
162 case 98 : keysym
= 50; break; // 2
163 case 99 : keysym
= 51; break; // 3
164 case 100: keysym
= 52; break; // 4
165 case 101: keysym
= 53; break; // 5
166 case 102: keysym
= 54; break; // 6
167 case 103: keysym
= 55; break; // 7
168 case 104: keysym
= 56; break; // 8
169 case 105: keysym
= 57; break; // 9
170 case 109: keysym
= 45; break; // -
171 case 110: keysym
= 46; break; // .
172 case 111: keysym
= 47; break; // /
180 /* Translate DOM keyPress event to keysym value */
181 function getKeysym(evt
) {
184 if (typeof(evt
.which
) !== "undefined") {
185 // WebKit, Firefox, Opera
189 Util
.Warn("which not set, using keyCode");
190 keysym
= evt
.keyCode
;
193 if ((keysym
> 255) && (keysym
< 0xFF00)) {
194 msg
= "Mapping character code " + keysym
;
195 // Map Unicode outside Latin 1 to X11 keysyms
196 keysym
= unicodeTable
[keysym
];
197 if (typeof(keysym
) === 'undefined') {
200 Util
.Debug(msg
+ " to " + keysym
);
206 function show_keyDownList(kind
) {
208 var msg
= "keyDownList (" + kind
+ "):\n";
209 for (c
= 0; c
< keyDownList
.length
; c
++) {
210 msg
= msg
+ " " + c
+ " - keyCode: " + keyDownList
[c
].keyCode
+
211 " - which: " + keyDownList
[c
].which
+ "\n";
216 function copyKeyEvent(evt
) {
217 var members
= ['type', 'keyCode', 'charCode', 'which',
218 'altKey', 'ctrlKey', 'shiftKey',
219 'keyLocation', 'keyIdentifier'], i
, obj
= {};
220 for (i
= 0; i
< members
.length
; i
++) {
221 if (typeof(evt
[members
[i
]]) !== "undefined") {
222 obj
[members
[i
]] = evt
[members
[i
]];
228 function pushKeyEvent(fevt
) {
229 keyDownList
.push(fevt
);
232 function getKeyEvent(keyCode
, pop
) {
234 for (i
= keyDownList
.length
-1; i
>= 0; i
--) {
235 if (keyDownList
[i
].keyCode
=== keyCode
) {
236 if ((typeof(pop
) !== "undefined") && (pop
)) {
237 fevt
= keyDownList
.splice(i
, 1)[0];
239 fevt
= keyDownList
[i
];
247 function ignoreKeyEvent(evt
) {
248 // Blarg. Some keys have a different keyCode on keyDown vs keyUp
249 if (evt
.keyCode
=== 229) {
250 // French AZERTY keyboard dead key.
251 // Lame thing is that the respective keyUp is 219 so we can't
252 // properly ignore the keyUp event
260 // Key Event Handling:
262 // There are several challenges when dealing with key events:
263 // - The meaning and use of keyCode, charCode and which depends on
264 // both the browser and the event type (keyDown/Up vs keyPress).
265 // - We cannot automatically determine the keyboard layout
266 // - The keyDown and keyUp events have a keyCode value that has not
267 // been translated by modifier keys.
268 // - The keyPress event has a translated (for layout and modifiers)
269 // character code but the attribute containing it differs. keyCode
270 // contains the translated value in WebKit (Chrome/Safari), Opera
271 // 11 and IE9. charCode contains the value in WebKit and Firefox.
272 // The which attribute contains the value on WebKit, Firefox and
274 // - The keyDown/Up keyCode value indicates (sort of) the physical
275 // key was pressed but only for standard US layout. On a US
276 // keyboard, the '-' and '_' characters are on the same key and
277 // generate a keyCode value of 189. But on an AZERTY keyboard even
278 // though they are different physical keys they both still
279 // generate a keyCode of 189!
280 // - To prevent a key event from propagating to the browser and
281 // causing unwanted default actions (such as closing a tab,
282 // opening a menu, shifting focus, etc) we must suppress this
283 // event in both keyDown and keyPress because not all key strokes
284 // generate on a keyPress event. Also, in WebKit and IE9
285 // suppressing the keyDown prevents a keyPress but other browsers
286 // still generated a keyPress even if keyDown is suppressed.
288 // For safe key events, we wait until the keyPress event before
289 // reporting a key down event. For unsafe key events, we report a key
290 // down event when the keyDown event fires and we suppress any further
291 // actions (including keyPress).
293 // In order to report a key up event that matches what we reported
294 // for the key down event, we keep a list of keys that are currently
295 // down. When the keyDown event happens, we add the key event to the
296 // list. If it is a safe key event, then we update the which attribute
297 // in the most recent item on the list when we received a keyPress
298 // event (keyPress should immediately follow keyDown). When we
299 // received a keyUp event we search for the event on the list with
300 // a matching keyCode and we report the character code using the value
301 // in the 'which' attribute that was stored with that key.
304 function onKeyDown(e
) {
305 if (! conf
.focused
) {
308 var fevt
= null, evt
= (e
? e
: window
.event
),
309 keysym
= null, suppress
= false;
310 //Util.Debug("onKeyDown kC:" + evt.keyCode + " cC:" + evt.charCode + " w:" + evt.which);
312 fevt
= copyKeyEvent(evt
);
314 keysym
= getKeysymSpecial(evt
);
315 // Save keysym decoding for use in keyUp
316 fevt
.keysym
= keysym
;
318 // If it is a key or key combination that might trigger
319 // browser behaviors or it has no corresponding keyPress
320 // event, then send it immediately
321 if (conf
.onKeyPress
&& !ignoreKeyEvent(evt
)) {
322 Util
.Debug("onKeyPress down, keysym: " + keysym
+
323 " (onKeyDown key: " + evt
.keyCode
+
324 ", which: " + evt
.which
+ ")");
325 conf
.onKeyPress(keysym
, 1, evt
);
330 if (! ignoreKeyEvent(evt
)) {
331 // Add it to the list of depressed keys
333 //show_keyDownList('down');
337 // Suppress bubbling/default actions
341 // Allow the event to bubble and become a keyPress event which
342 // will have the character code translated
347 function onKeyPress(e
) {
348 if (! conf
.focused
) {
351 var evt
= (e
? e
: window
.event
),
352 kdlen
= keyDownList
.length
, keysym
= null;
353 //Util.Debug("onKeyPress kC:" + evt.keyCode + " cC:" + evt.charCode + " w:" + evt.which);
355 if (((evt
.which
!== "undefined") && (evt
.which
=== 0)) ||
356 (getKeysymSpecial(evt
))) {
357 // Firefox and Opera generate a keyPress event even if keyDown
358 // is suppressed. But the keys we want to suppress will have
360 // - the which attribute set to 0
361 // - getKeysymSpecial() will identify it
362 Util
.Debug("Ignoring special key in keyPress");
367 keysym
= getKeysym(evt
);
369 // Modify the the which attribute in the depressed keys list so
370 // that the keyUp event will be able to have the character code
371 // translation available.
373 keyDownList
[kdlen
-1].keysym
= keysym
;
375 Util
.Warn("keyDownList empty when keyPress triggered");
378 //show_keyDownList('press');
380 // Send the translated keysym
381 if (conf
.onKeyPress
&& (keysym
> 0)) {
382 Util
.Debug("onKeyPress down, keysym: " + keysym
+
383 " (onKeyPress key: " + evt
.keyCode
+
384 ", which: " + evt
.which
+ ")");
385 conf
.onKeyPress(keysym
, 1, evt
);
388 // Stop keypress events just in case
393 function onKeyUp(e
) {
394 if (! conf
.focused
) {
397 var fevt
= null, evt
= (e
? e
: window
.event
), keysym
;
398 //Util.Debug("onKeyUp kC:" + evt.keyCode + " cC:" + evt.charCode + " w:" + evt.which);
400 fevt
= getKeyEvent(evt
.keyCode
, true);
403 keysym
= fevt
.keysym
;
405 Util
.Warn("Key event (keyCode = " + evt
.keyCode
+
406 ") not found on keyDownList");
410 //show_keyDownList('up');
412 if (conf
.onKeyPress
&& (keysym
> 0)) {
413 //Util.Debug("keyPress up, keysym: " + keysym +
414 // " (key: " + evt.keyCode + ", which: " + evt.which + ")");
415 Util
.Debug("onKeyPress up, keysym: " + keysym
+
416 " (onKeyPress key: " + evt
.keyCode
+
417 ", which: " + evt
.which
+ ")");
418 conf
.onKeyPress(keysym
, 0, evt
);
424 function allKeysUp() {
425 Util
.Debug(">> Keyboard.allKeysUp");
426 if (keyDownList
.length
> 0) {
427 Util
.Info("Releasing pressed/down keys");
429 var i
, keysym
, fevt
= null;
430 for (i
= keyDownList
.length
-1; i
>= 0; i
--) {
431 fevt
= keyDownList
.splice(i
, 1)[0];
432 keysym
= fevt
.keysym
;
433 if (conf
.onKeyPress
&& (keysym
> 0)) {
434 Util
.Debug("allKeysUp, keysym: " + keysym
+
435 " (keyCode: " + fevt
.keyCode
+
436 ", which: " + fevt
.which
+ ")");
437 conf
.onKeyPress(keysym
, 0, fevt
);
440 Util
.Debug("<< Keyboard.allKeysUp");
445 // Public API interface functions
448 that
.grab = function() {
449 //Util.Debug(">> Keyboard.grab");
452 Util
.addEvent(c
, 'keydown', onKeyDown
);
453 Util
.addEvent(c
, 'keyup', onKeyUp
);
454 Util
.addEvent(c
, 'keypress', onKeyPress
);
456 // Release (key up) if window loses focus
457 Util
.addEvent(window
, 'blur', allKeysUp
);
459 //Util.Debug("<< Keyboard.grab");
462 that
.ungrab = function() {
463 //Util.Debug(">> Keyboard.ungrab");
466 Util
.removeEvent(c
, 'keydown', onKeyDown
);
467 Util
.removeEvent(c
, 'keyup', onKeyUp
);
468 Util
.removeEvent(c
, 'keypress', onKeyPress
);
469 Util
.removeEvent(window
, 'blur', allKeysUp
);
471 // Release (key up) all keys that are in a down state
474 //Util.Debug(">> Keyboard.ungrab");
477 return that
; // Return the public API interface
479 } // End of Keyboard()
483 // Mouse event handler
486 function Mouse(defaults
) {
489 var that
= {}, // Public API methods
490 conf
= {}, // Configuration attributes
491 mouseCaptured
= false;
493 var doubleClickTimer
= null,
496 // Configuration attributes
497 Util
.conf_defaults(conf
, that
, defaults
, [
498 ['target', 'ro', 'dom', document
, 'DOM element that captures mouse input'],
499 ['focused', 'rw', 'bool', true, 'Capture and send mouse clicks/movement'],
500 ['scale', 'rw', 'float', 1.0, 'Viewport scale factor 0.0 - 1.0'],
502 ['onMouseButton', 'rw', 'func', null, 'Handler for mouse button click/release'],
503 ['onMouseMove', 'rw', 'func', null, 'Handler for mouse movement'],
504 ['touchButton', 'rw', 'int', 1, 'Button mask (1, 2, 4) for touch devices (0 means ignore clicks)']
507 function captureMouse() {
508 // capturing the mouse ensures we get the mouseup event
509 if (conf
.target
.setCapture
) {
510 conf
.target
.setCapture();
513 // some browsers give us mouseup events regardless,
514 // so if we never captured the mouse, we can disregard the event
515 mouseCaptured
= true;
518 function releaseMouse() {
519 if (conf
.target
.releaseCapture
) {
520 conf
.target
.releaseCapture();
522 mouseCaptured
= false;
528 function resetDoubleClickTimer() {
529 doubleClickTimer
= null;
532 function onMouseButton(e
, down
) {
534 if (! conf
.focused
) {
537 evt
= (e
? e
: window
.event
);
538 pos
= Util
.getEventPosition(e
, conf
.target
, conf
.scale
);
540 if (e
.touches
|| e
.changedTouches
) {
543 // When two touches occur within 500 ms of each other and are
544 // closer than 20 pixels together a double click is triggered.
546 if (doubleClickTimer
== null) {
549 clearTimeout(doubleClickTimer
);
551 // When the distance between the two touches is small enough
552 // force the position of the latter touch to the position of
555 var xs
= lastTouchPos
.x
- pos
.x
;
556 var ys
= lastTouchPos
.y
- pos
.y
;
557 var d
= Math
.sqrt((xs
* xs
) + (ys
* ys
));
559 // The goal is to trigger on a certain physical width, the
560 // devicePixelRatio brings us a bit closer but is not optimal.
561 if (d
< 20 * window
.devicePixelRatio
) {
565 doubleClickTimer
= setTimeout(resetDoubleClickTimer
, 500);
567 bmask
= conf
.touchButton
;
569 } else if (evt
.which
) {
570 /* everything except IE */
571 bmask
= 1 << evt
.button
;
574 bmask
= (evt
.button
& 0x1) + // Left
575 (evt
.button
& 0x2) * 2 + // Right
576 (evt
.button
& 0x4) / 2; // Middle
578 //Util.Debug("mouse " + pos.x + "," + pos.y + " down: " + down +
579 // " bmask: " + bmask + "(evt.button: " + evt.button + ")");
580 if (bmask
> 0 && conf
.onMouseButton
) {
581 Util
.Debug("onMouseButton " + (down
? "down" : "up") +
582 ", x: " + pos
.x
+ ", y: " + pos
.y
+ ", bmask: " + bmask
);
583 conf
.onMouseButton(pos
.x
, pos
.y
, down
, bmask
);
589 function onMouseDown(e
) {
594 function onMouseUp(e
) {
595 if (!mouseCaptured
) {
603 function onMouseWheel(e
) {
604 var evt
, pos
, bmask
, wheelData
;
605 if (! conf
.focused
) {
608 evt
= (e
? e
: window
.event
);
609 pos
= Util
.getEventPosition(e
, conf
.target
, conf
.scale
);
610 wheelData
= evt
.detail
? evt
.detail
* -1 : evt
.wheelDelta
/ 40;
616 //Util.Debug('mouse scroll by ' + wheelData + ':' + pos.x + "," + pos.y);
617 if (conf
.onMouseButton
) {
618 conf
.onMouseButton(pos
.x
, pos
.y
, 1, bmask
);
619 conf
.onMouseButton(pos
.x
, pos
.y
, 0, bmask
);
625 function onMouseMove(e
) {
627 if (! conf
.focused
) {
630 evt
= (e
? e
: window
.event
);
631 pos
= Util
.getEventPosition(e
, conf
.target
, conf
.scale
);
632 //Util.Debug('mouse ' + evt.which + '/' + evt.button + ' up:' + pos.x + "," + pos.y);
633 if (conf
.onMouseMove
) {
634 conf
.onMouseMove(pos
.x
, pos
.y
);
640 function onMouseDisable(e
) {
642 if (! conf
.focused
) {
645 evt
= (e
? e
: window
.event
);
646 pos
= Util
.getEventPosition(e
, conf
.target
, conf
.scale
);
647 /* Stop propagation if inside canvas area */
648 if ((pos
.x
>= 0) && (pos
.y
>= 0) &&
649 (pos
.x
< conf
.target
.offsetWidth
) &&
650 (pos
.y
< conf
.target
.offsetHeight
)) {
651 //Util.Debug("mouse event disabled");
655 //Util.Debug("mouse event not disabled");
660 // Public API interface functions
663 that
.grab = function() {
664 //Util.Debug(">> Mouse.grab");
667 if ('ontouchstart' in document
.documentElement
) {
668 Util
.addEvent(c
, 'touchstart', onMouseDown
);
669 Util
.addEvent(window
, 'touchend', onMouseUp
);
670 Util
.addEvent(c
, 'touchend', onMouseUp
);
671 Util
.addEvent(c
, 'touchmove', onMouseMove
);
673 Util
.addEvent(c
, 'mousedown', onMouseDown
);
674 Util
.addEvent(window
, 'mouseup', onMouseUp
);
675 Util
.addEvent(c
, 'mouseup', onMouseUp
);
676 Util
.addEvent(c
, 'mousemove', onMouseMove
);
677 Util
.addEvent(c
, (Util
.Engine
.gecko
) ? 'DOMMouseScroll' : 'mousewheel',
681 /* Work around right and middle click browser behaviors */
682 Util
.addEvent(document
, 'click', onMouseDisable
);
683 Util
.addEvent(document
.body
, 'contextmenu', onMouseDisable
);
685 //Util.Debug("<< Mouse.grab");
688 that
.ungrab = function() {
689 //Util.Debug(">> Mouse.ungrab");
692 if ('ontouchstart' in document
.documentElement
) {
693 Util
.removeEvent(c
, 'touchstart', onMouseDown
);
694 Util
.removeEvent(window
, 'touchend', onMouseUp
);
695 Util
.removeEvent(c
, 'touchend', onMouseUp
);
696 Util
.removeEvent(c
, 'touchmove', onMouseMove
);
698 Util
.removeEvent(c
, 'mousedown', onMouseDown
);
699 Util
.removeEvent(window
, 'mouseup', onMouseUp
);
700 Util
.removeEvent(c
, 'mouseup', onMouseUp
);
701 Util
.removeEvent(c
, 'mousemove', onMouseMove
);
702 Util
.removeEvent(c
, (Util
.Engine
.gecko
) ? 'DOMMouseScroll' : 'mousewheel',
706 /* Work around right and middle click browser behaviors */
707 Util
.removeEvent(document
, 'click', onMouseDisable
);
708 Util
.removeEvent(document
.body
, 'contextmenu', onMouseDisable
);
710 //Util.Debug(">> Mouse.ungrab");
713 return that
; // Return the public API interface
719 * Browser keypress to X11 keysym for Unicode characters > U+00FF
1581 //0x01D2 : 0x10001d2,