]>
git.proxmox.com Git - mirror_novnc.git/blob - include/input.js
2 * noVNC: HTML5 VNC client
3 * Copyright (C) 2011 Joel Martin
4 * Licensed under LGPL-2 or any later version (see LICENSE.txt)
7 /*jslint browser: true, white: false, bitwise: false */
8 /*global window, Util */
12 // Keyboard event handler
15 function Keyboard(defaults
) {
18 var that
= {}, // Public API methods
19 conf
= {}, // Configuration attributes
21 keyDownList
= []; // List of depressed keys
22 // (even if they are happy)
24 // Configuration attributes
25 Util
.conf_defaults(conf
, that
, defaults
, [
26 ['target', 'wo', 'dom', document
, 'DOM element that captures keyboard input'],
27 ['focused', 'rw', 'bool', true, 'Capture and send key events'],
29 ['onKeyPress', 'rw', 'func', null, 'Handler for key press/release']
37 // From the event keyCode return the keysym value for keys that need
38 // to be suppressed otherwise they may trigger unintended browser
40 function getKeysymSpecial(evt
) {
43 switch ( evt
.keyCode
) {
44 // These generate a keyDown and keyPress in Firefox and Opera
45 case 8 : keysym
= 0xFF08; break; // BACKSPACE
46 case 13 : keysym
= 0xFF0D; break; // ENTER
48 // This generates a keyDown and keyPress in Opera
49 case 9 : keysym
= 0xFF09; break; // TAB
53 if (evt
.type
=== 'keydown') {
54 switch ( evt
.keyCode
) {
55 case 27 : keysym
= 0xFF1B; break; // ESCAPE
56 case 46 : keysym
= 0xFFFF; break; // DELETE
58 case 36 : keysym
= 0xFF50; break; // HOME
59 case 35 : keysym
= 0xFF57; break; // END
60 case 33 : keysym
= 0xFF55; break; // PAGE_UP
61 case 34 : keysym
= 0xFF56; break; // PAGE_DOWN
62 case 45 : keysym
= 0xFF63; break; // INSERT
63 // '-' during keyPress
64 case 37 : keysym
= 0xFF51; break; // LEFT
65 case 38 : keysym
= 0xFF52; break; // UP
66 case 39 : keysym
= 0xFF53; break; // RIGHT
67 case 40 : keysym
= 0xFF54; break; // DOWN
68 case 16 : keysym
= 0xFFE1; break; // SHIFT
69 case 17 : keysym
= 0xFFE3; break; // CONTROL
70 //case 18 : keysym = 0xFFE7; break; // Left Meta (Mac Option)
71 case 18 : keysym
= 0xFFE9; break; // Left ALT (Mac Command)
73 case 112 : keysym
= 0xFFBE; break; // F1
74 case 113 : keysym
= 0xFFBF; break; // F2
75 case 114 : keysym
= 0xFFC0; break; // F3
76 case 115 : keysym
= 0xFFC1; break; // F4
77 case 116 : keysym
= 0xFFC2; break; // F5
78 case 117 : keysym
= 0xFFC3; break; // F6
79 case 118 : keysym
= 0xFFC4; break; // F7
80 case 119 : keysym
= 0xFFC5; break; // F8
81 case 120 : keysym
= 0xFFC6; break; // F9
82 case 121 : keysym
= 0xFFC7; break; // F10
83 case 122 : keysym
= 0xFFC8; break; // F11
84 case 123 : keysym
= 0xFFC9; break; // F12
90 if ((!keysym
) && (evt
.ctrlKey
|| evt
.altKey
)) {
91 if ((typeof(evt
.which
) !== "undefined") && (evt
.which
> 0)) {
95 // Firefox and Opera when ctrl/alt + special
96 Util
.Warn("which not set, using keyCode");
102 case 186 : keysym
= 59; break; // ; (IE)
103 case 187 : keysym
= 61; break; // = (IE)
104 case 188 : keysym
= 44; break; // , (Mozilla, IE)
105 case 109 : // - (Mozilla, Opera)
106 if (Util
.Engine
.gecko
|| Util
.Engine
.presto
) {
109 case 189 : keysym
= 45; break; // - (IE)
110 case 190 : keysym
= 46; break; // . (Mozilla, IE)
111 case 191 : keysym
= 47; break; // / (Mozilla, IE)
112 case 192 : keysym
= 96; break; // ` (Mozilla, IE)
113 case 219 : keysym
= 91; break; // [ (Mozilla, IE)
114 case 220 : keysym
= 92; break; // \ (Mozilla, IE)
115 case 221 : keysym
= 93; break; // ] (Mozilla, IE)
116 case 222 : keysym
= 39; break; // ' (Mozilla, IE)
119 /* Remap shifted and unshifted keys */
120 if (!!evt
.shiftKey
) {
122 case 48 : keysym
= 41 ; break; // ) (shifted 0)
123 case 49 : keysym
= 33 ; break; // ! (shifted 1)
124 case 50 : keysym
= 64 ; break; // @ (shifted 2)
125 case 51 : keysym
= 35 ; break; // # (shifted 3)
126 case 52 : keysym
= 36 ; break; // $ (shifted 4)
127 case 53 : keysym
= 37 ; break; // % (shifted 5)
128 case 54 : keysym
= 94 ; break; // ^ (shifted 6)
129 case 55 : keysym
= 38 ; break; // & (shifted 7)
130 case 56 : keysym
= 42 ; break; // * (shifted 8)
131 case 57 : keysym
= 40 ; break; // ( (shifted 9)
133 case 59 : keysym
= 58 ; break; // : (shifted `)
134 case 61 : keysym
= 43 ; break; // + (shifted ;)
135 case 44 : keysym
= 60 ; break; // < (shifted ,)
136 case 45 : keysym
= 95 ; break; // _ (shifted -)
137 case 46 : keysym
= 62 ; break; // > (shifted .)
138 case 47 : keysym
= 63 ; break; // ? (shifted /)
139 case 96 : keysym
= 126; break; // ~ (shifted `)
140 case 91 : keysym
= 123; break; // { (shifted [)
141 case 92 : keysym
= 124; break; // | (shifted \)
142 case 93 : keysym
= 125; break; // } (shifted ])
143 case 39 : keysym
= 34 ; break; // " (shifted ')
145 } else if ((keysym
>= 65) && (keysym
<=90)) {
146 /* Remap unshifted A-Z */
148 } else if (evt
.keyLocation
=== 3) {
151 case 96 : keysym
= 48; break; // 0
152 case 97 : keysym
= 49; break; // 1
153 case 98 : keysym
= 50; break; // 2
154 case 99 : keysym
= 51; break; // 3
155 case 100: keysym
= 52; break; // 4
156 case 101: keysym
= 53; break; // 5
157 case 102: keysym
= 54; break; // 6
158 case 103: keysym
= 55; break; // 7
159 case 104: keysym
= 56; break; // 8
160 case 105: keysym
= 57; break; // 9
161 case 109: keysym
= 45; break; // -
162 case 110: keysym
= 46; break; // .
163 case 111: keysym
= 47; break; // /
171 /* Translate DOM keyPress event to keysym value */
172 function getKeysym(evt
) {
175 if (typeof(evt
.which
) !== "undefined") {
176 // WebKit, Firefox, Opera
180 Util
.Warn("which not set, using keyCode");
181 keysym
= evt
.keyCode
;
184 if ((keysym
> 255) && (keysym
< 0xFF00)) {
185 msg
= "Mapping character code " + keysym
;
186 // Map Unicode outside Latin 1 to X11 keysyms
187 keysym
= unicodeTable
[keysym
];
188 if (typeof(keysym
) === 'undefined') {
191 Util
.Debug(msg
+ " to " + keysym
);
197 function show_keyDownList(kind
) {
199 var msg
= "keyDownList (" + kind
+ "):\n";
200 for (c
= 0; c
< keyDownList
.length
; c
++) {
201 msg
= msg
+ " " + c
+ " - keyCode: " + keyDownList
[c
].keyCode
+
202 " - which: " + keyDownList
[c
].which
+ "\n";
207 function copyKeyEvent(evt
) {
208 var members
= ['type', 'keyCode', 'charCode', 'which',
209 'altKey', 'ctrlKey', 'shiftKey',
210 'keyLocation', 'keyIdentifier'], i
, obj
= {};
211 for (i
= 0; i
< members
.length
; i
++) {
212 if (typeof(evt
[members
[i
]]) !== "undefined") {
213 obj
[members
[i
]] = evt
[members
[i
]];
219 function pushKeyEvent(fevt
) {
220 keyDownList
.push(fevt
);
223 function getKeyEvent(keyCode
, pop
) {
225 for (i
= keyDownList
.length
-1; i
>= 0; i
--) {
226 if (keyDownList
[i
].keyCode
=== keyCode
) {
227 if ((typeof(pop
) !== "undefined") && (pop
)) {
228 fevt
= keyDownList
.splice(i
, 1)[0];
230 fevt
= keyDownList
[i
];
238 function ignoreKeyEvent(evt
) {
239 // Blarg. Some keys have a different keyCode on keyDown vs keyUp
240 if (evt
.keyCode
=== 229) {
241 // French AZERTY keyboard dead key.
242 // Lame thing is that the respective keyUp is 219 so we can't
243 // properly ignore the keyUp event
251 // Key Event Handling:
253 // There are several challenges when dealing with key events:
254 // - The meaning and use of keyCode, charCode and which depends on
255 // both the browser and the event type (keyDown/Up vs keyPress).
256 // - We cannot automatically determine the keyboard layout
257 // - The keyDown and keyUp events have a keyCode value that has not
258 // been translated by modifier keys.
259 // - The keyPress event has a translated (for layout and modifiers)
260 // character code but the attribute containing it differs. keyCode
261 // contains the translated value in WebKit (Chrome/Safari), Opera
262 // 11 and IE9. charCode contains the value in WebKit and Firefox.
263 // The which attribute contains the value on WebKit, Firefox and
265 // - The keyDown/Up keyCode value indicates (sort of) the physical
266 // key was pressed but only for standard US layout. On a US
267 // keyboard, the '-' and '_' characters are on the same key and
268 // generate a keyCode value of 189. But on an AZERTY keyboard even
269 // though they are different physical keys they both still
270 // generate a keyCode of 189!
271 // - To prevent a key event from propagating to the browser and
272 // causing unwanted default actions (such as closing a tab,
273 // opening a menu, shifting focus, etc) we must suppress this
274 // event in both keyDown and keyPress because not all key strokes
275 // generate on a keyPress event. Also, in WebKit and IE9
276 // suppressing the keyDown prevents a keyPress but other browsers
277 // still generated a keyPress even if keyDown is suppressed.
279 // For safe key events, we wait until the keyPress event before
280 // reporting a key down event. For unsafe key events, we report a key
281 // down event when the keyDown event fires and we suppress any further
282 // actions (including keyPress).
284 // In order to report a key up event that matches what we reported
285 // for the key down event, we keep a list of keys that are currently
286 // down. When the keyDown event happens, we add the key event to the
287 // list. If it is a safe key event, then we update the which attribute
288 // in the most recent item on the list when we received a keyPress
289 // event (keyPress should immediately follow keyDown). When we
290 // received a keyUp event we search for the event on the list with
291 // a matching keyCode and we report the character code using the value
292 // in the 'which' attribute that was stored with that key.
295 function onKeyDown(e
) {
296 if (! conf
.focused
) {
299 var fevt
= null, evt
= (e
? e
: window
.event
),
300 keysym
= null, suppress
= false;
301 //Util.Debug("onKeyDown kC:" + evt.keyCode + " cC:" + evt.charCode + " w:" + evt.which);
303 fevt
= copyKeyEvent(evt
);
305 keysym
= getKeysymSpecial(evt
);
306 // Save keysym decoding for use in keyUp
307 fevt
.keysym
= keysym
;
309 // If it is a key or key combination that might trigger
310 // browser behaviors or it has no corresponding keyPress
311 // event, then send it immediately
312 if (conf
.onKeyPress
&& !ignoreKeyEvent(evt
)) {
313 Util
.Debug("onKeyPress down, keysym: " + keysym
+
314 " (onKeyDown key: " + evt
.keyCode
+
315 ", which: " + evt
.which
+ ")");
316 conf
.onKeyPress(keysym
, 1, evt
);
321 if (! ignoreKeyEvent(evt
)) {
322 // Add it to the list of depressed keys
324 //show_keyDownList('down');
328 // Suppress bubbling/default actions
332 // Allow the event to bubble and become a keyPress event which
333 // will have the character code translated
338 function onKeyPress(e
) {
339 if (! conf
.focused
) {
342 var evt
= (e
? e
: window
.event
),
343 kdlen
= keyDownList
.length
, keysym
= null;
344 //Util.Debug("onKeyPress kC:" + evt.keyCode + " cC:" + evt.charCode + " w:" + evt.which);
346 if (((evt
.which
!== "undefined") && (evt
.which
=== 0)) ||
347 (getKeysymSpecial(evt
))) {
348 // Firefox and Opera generate a keyPress event even if keyDown
349 // is suppressed. But the keys we want to suppress will have
351 // - the which attribute set to 0
352 // - getKeysymSpecial() will identify it
353 Util
.Debug("Ignoring special key in keyPress");
358 keysym
= getKeysym(evt
);
360 // Modify the the which attribute in the depressed keys list so
361 // that the keyUp event will be able to have the character code
362 // translation available.
364 keyDownList
[kdlen
-1].keysym
= keysym
;
366 Util
.Warn("keyDownList empty when keyPress triggered");
369 //show_keyDownList('press');
371 // Send the translated keysym
372 if (conf
.onKeyPress
&& (keysym
> 0)) {
373 Util
.Debug("onKeyPress down, keysym: " + keysym
+
374 " (onKeyPress key: " + evt
.keyCode
+
375 ", which: " + evt
.which
+ ")");
376 conf
.onKeyPress(keysym
, 1, evt
);
379 // Stop keypress events just in case
384 function onKeyUp(e
) {
385 if (! conf
.focused
) {
388 var fevt
= null, evt
= (e
? e
: window
.event
), keysym
;
389 //Util.Debug("onKeyUp kC:" + evt.keyCode + " cC:" + evt.charCode + " w:" + evt.which);
391 fevt
= getKeyEvent(evt
.keyCode
, true);
394 keysym
= fevt
.keysym
;
396 Util
.Warn("Key event (keyCode = " + evt
.keyCode
+
397 ") not found on keyDownList");
401 //show_keyDownList('up');
403 if (conf
.onKeyPress
&& (keysym
> 0)) {
404 //Util.Debug("keyPress up, keysym: " + keysym +
405 // " (key: " + evt.keyCode + ", which: " + evt.which + ")");
406 Util
.Debug("onKeyPress up, keysym: " + keysym
+
407 " (onKeyPress key: " + evt
.keyCode
+
408 ", which: " + evt
.which
+ ")");
409 conf
.onKeyPress(keysym
, 0, evt
);
416 // Public API interface functions
419 that
.grab = function() {
420 //Util.Debug(">> Keyboard.grab");
423 Util
.addEvent(c
, 'keydown', onKeyDown
);
424 Util
.addEvent(c
, 'keyup', onKeyUp
);
425 Util
.addEvent(c
, 'keypress', onKeyPress
);
427 //Util.Debug("<< Keyboard.grab");
430 that
.ungrab = function() {
431 //Util.Debug(">> Keyboard.ungrab");
434 Util
.removeEvent(c
, 'keydown', onKeyDown
);
435 Util
.removeEvent(c
, 'keyup', onKeyUp
);
436 Util
.removeEvent(c
, 'keypress', onKeyPress
);
438 //Util.Debug(">> Keyboard.ungrab");
441 return that
; // Return the public API interface
443 } // End of Keyboard()
447 // Mouse event handler
450 function Mouse(defaults
) {
453 var that
= {}, // Public API methods
454 conf
= {}; // Configuration attributes
456 // Configuration attributes
457 Util
.conf_defaults(conf
, that
, defaults
, [
458 ['target', 'ro', 'dom', document
, 'DOM element that captures mouse input'],
459 ['focused', 'rw', 'bool', true, 'Capture and send mouse clicks/movement'],
460 ['scale', 'rw', 'float', 1.0, 'Viewport scale factor 0.0 - 1.0'],
462 ['onMouseButton', 'rw', 'func', null, 'Handler for mouse button click/release'],
463 ['onMouseMove', 'rw', 'func', null, 'Handler for mouse movement'],
464 ['touchButton', 'rw', 'int', 1, 'Button mask (1, 2, 4) for touch devices (0 means ignore clicks)']
472 function onMouseButton(e
, down
) {
474 if (! conf
.focused
) {
477 evt
= (e
? e
: window
.event
);
478 pos
= Util
.getEventPosition(e
, conf
.target
, conf
.scale
);
479 if (e
.touches
|| e
.changedTouches
) {
481 bmask
= conf
.touchButton
;
483 } else if (evt
.which
) {
484 /* everything except IE */
485 bmask
= 1 << evt
.button
;
488 bmask
= (evt
.button
& 0x1) + // Left
489 (evt
.button
& 0x2) * 2 + // Right
490 (evt
.button
& 0x4) / 2; // Middle
492 //Util.Debug("mouse " + pos.x + "," + pos.y + " down: " + down +
493 // " bmask: " + bmask + "(evt.button: " + evt.button + ")");
494 if (bmask
> 0 && conf
.onMouseButton
) {
495 Util
.Debug("onMouseButton " + (down
? "down" : "up") +
496 ", x: " + pos
.x
+ ", y: " + pos
.y
+ ", bmask: " + bmask
);
497 conf
.onMouseButton(pos
.x
, pos
.y
, down
, bmask
);
503 function onMouseDown(e
) {
507 function onMouseUp(e
) {
511 function onMouseWheel(e
) {
512 var evt
, pos
, bmask
, wheelData
;
513 if (! conf
.focused
) {
516 evt
= (e
? e
: window
.event
);
517 pos
= Util
.getEventPosition(e
, conf
.target
, conf
.scale
);
518 wheelData
= evt
.detail
? evt
.detail
* -1 : evt
.wheelDelta
/ 40;
524 //Util.Debug('mouse scroll by ' + wheelData + ':' + pos.x + "," + pos.y);
525 if (conf
.onMouseButton
) {
526 conf
.onMouseButton(pos
.x
, pos
.y
, 1, bmask
);
527 conf
.onMouseButton(pos
.x
, pos
.y
, 0, bmask
);
533 function onMouseMove(e
) {
535 if (! conf
.focused
) {
538 evt
= (e
? e
: window
.event
);
539 pos
= Util
.getEventPosition(e
, conf
.target
, conf
.scale
);
540 //Util.Debug('mouse ' + evt.which + '/' + evt.button + ' up:' + pos.x + "," + pos.y);
541 if (conf
.onMouseMove
) {
542 conf
.onMouseMove(pos
.x
, pos
.y
);
548 function onMouseDisable(e
) {
550 if (! conf
.focused
) {
553 evt
= (e
? e
: window
.event
);
554 pos
= Util
.getEventPosition(e
, conf
.target
, conf
.scale
);
555 /* Stop propagation if inside canvas area */
556 if ((pos
.x
>= 0) && (pos
.y
>= 0) &&
557 (pos
.x
< conf
.target
.offsetWidth
) &&
558 (pos
.y
< conf
.target
.offsetHeight
)) {
559 //Util.Debug("mouse event disabled");
563 //Util.Debug("mouse event not disabled");
568 // Public API interface functions
571 that
.grab = function() {
572 //Util.Debug(">> Mouse.grab");
575 if ('ontouchstart' in document
.documentElement
) {
576 Util
.addEvent(c
, 'touchstart', onMouseDown
);
577 Util
.addEvent(c
, 'touchend', onMouseUp
);
578 Util
.addEvent(c
, 'touchmove', onMouseMove
);
580 Util
.addEvent(c
, 'mousedown', onMouseDown
);
581 Util
.addEvent(c
, 'mouseup', onMouseUp
);
582 Util
.addEvent(c
, 'mousemove', onMouseMove
);
583 Util
.addEvent(c
, (Util
.Engine
.gecko
) ? 'DOMMouseScroll' : 'mousewheel',
587 /* Work around right and middle click browser behaviors */
588 Util
.addEvent(document
, 'click', onMouseDisable
);
589 Util
.addEvent(document
.body
, 'contextmenu', onMouseDisable
);
591 //Util.Debug("<< Mouse.grab");
594 that
.ungrab = function() {
595 //Util.Debug(">> Mouse.ungrab");
598 if ('ontouchstart' in document
.documentElement
) {
599 Util
.removeEvent(c
, 'touchstart', onMouseDown
);
600 Util
.removeEvent(c
, 'touchend', onMouseUp
);
601 Util
.removeEvent(c
, 'touchmove', onMouseMove
);
603 Util
.removeEvent(c
, 'mousedown', onMouseDown
);
604 Util
.removeEvent(c
, 'mouseup', onMouseUp
);
605 Util
.removeEvent(c
, 'mousemove', onMouseMove
);
606 Util
.removeEvent(c
, (Util
.Engine
.gecko
) ? 'DOMMouseScroll' : 'mousewheel',
610 /* Work around right and middle click browser behaviors */
611 Util
.removeEvent(document
, 'click', onMouseDisable
);
612 Util
.removeEvent(document
.body
, 'contextmenu', onMouseDisable
);
614 //Util.Debug(">> Mouse.ungrab");
617 return that
; // Return the public API interface
623 * Browser keypress to X11 keysym for Unicode characters > U+00FF