]>
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(conf
) {
18 conf
= conf
|| {}; // Configuration
19 var that
= {}, // Public API interface
21 keyDownList
= []; // List of depressed keys
22 // (even if they are happy)
25 // Configuration settings
26 function cdef(v
, type
, defval
, desc
) {
27 Util
.conf_default(conf
, that
, v
, type
, defval
, desc
); }
29 // Capability settings, default can be overridden
30 cdef('target', 'dom', document
, 'DOM element that captures keyboard input');
31 cdef('focused', 'bool', true, 'Capture and send key events');
33 cdef('onKeyPress', 'func', null, 'Handler for key press/release');
35 that
.set_target = function() { throw("target cannot be changed"); };
42 // From the event keyCode return the keysym value for keys that need
43 // to be suppressed otherwise they may trigger unintended browser
45 function getKeysymSpecial(evt
) {
48 switch ( evt
.keyCode
) {
49 // These generate a keyDown and keyPress in Firefox and Opera
50 case 8 : keysym
= 0xFF08; break; // BACKSPACE
51 case 13 : keysym
= 0xFF0D; break; // ENTER
53 // This generates a keyDown and keyPress in Opera
54 case 9 : keysym
= 0xFF09; break; // TAB
58 if (evt
.type
=== 'keydown') {
59 switch ( evt
.keyCode
) {
60 case 27 : keysym
= 0xFF1B; break; // ESCAPE
61 case 46 : keysym
= 0xFFFF; break; // DELETE
63 case 36 : keysym
= 0xFF50; break; // HOME
64 case 35 : keysym
= 0xFF57; break; // END
65 case 33 : keysym
= 0xFF55; break; // PAGE_UP
66 case 34 : keysym
= 0xFF56; break; // PAGE_DOWN
67 case 45 : keysym
= 0xFF63; break; // INSERT
68 // '-' during keyPress
69 case 37 : keysym
= 0xFF51; break; // LEFT
70 case 38 : keysym
= 0xFF52; break; // UP
71 case 39 : keysym
= 0xFF53; break; // RIGHT
72 case 40 : keysym
= 0xFF54; break; // DOWN
73 case 16 : keysym
= 0xFFE1; break; // SHIFT
74 case 17 : keysym
= 0xFFE3; break; // CONTROL
75 //case 18 : keysym = 0xFFE7; break; // Left Meta (Mac Option)
76 case 18 : keysym
= 0xFFE9; break; // Left ALT (Mac Command)
78 case 112 : keysym
= 0xFFBE; break; // F1
79 case 113 : keysym
= 0xFFBF; break; // F2
80 case 114 : keysym
= 0xFFC0; break; // F3
81 case 115 : keysym
= 0xFFC1; break; // F4
82 case 116 : keysym
= 0xFFC2; break; // F5
83 case 117 : keysym
= 0xFFC3; break; // F6
84 case 118 : keysym
= 0xFFC4; break; // F7
85 case 119 : keysym
= 0xFFC5; break; // F8
86 case 120 : keysym
= 0xFFC6; break; // F9
87 case 121 : keysym
= 0xFFC7; break; // F10
88 case 122 : keysym
= 0xFFC8; break; // F11
89 case 123 : keysym
= 0xFFC9; break; // F12
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 189 : keysym
= 45; break; // - (IE)
115 case 190 : keysym
= 46; break; // . (Mozilla, IE)
116 case 191 : keysym
= 47; break; // / (Mozilla, IE)
117 case 192 : keysym
= 96; break; // ` (Mozilla, IE)
118 case 219 : keysym
= 91; break; // [ (Mozilla, IE)
119 case 220 : keysym
= 92; break; // \ (Mozilla, IE)
120 case 221 : keysym
= 93; break; // ] (Mozilla, IE)
121 case 222 : keysym
= 39; break; // ' (Mozilla, IE)
124 /* Remap shifted and unshifted keys */
125 if (!!evt
.shiftKey
) {
127 case 48 : keysym
= 41 ; break; // ) (shifted 0)
128 case 49 : keysym
= 33 ; break; // ! (shifted 1)
129 case 50 : keysym
= 64 ; break; // @ (shifted 2)
130 case 51 : keysym
= 35 ; break; // # (shifted 3)
131 case 52 : keysym
= 36 ; break; // $ (shifted 4)
132 case 53 : keysym
= 37 ; break; // % (shifted 5)
133 case 54 : keysym
= 94 ; break; // ^ (shifted 6)
134 case 55 : keysym
= 38 ; break; // & (shifted 7)
135 case 56 : keysym
= 42 ; break; // * (shifted 8)
136 case 57 : keysym
= 40 ; break; // ( (shifted 9)
138 case 59 : keysym
= 58 ; break; // : (shifted `)
139 case 61 : keysym
= 43 ; break; // + (shifted ;)
140 case 44 : keysym
= 60 ; break; // < (shifted ,)
141 case 45 : keysym
= 95 ; break; // _ (shifted -)
142 case 46 : keysym
= 62 ; break; // > (shifted .)
143 case 47 : keysym
= 63 ; break; // ? (shifted /)
144 case 96 : keysym
= 126; break; // ~ (shifted `)
145 case 91 : keysym
= 123; break; // { (shifted [)
146 case 92 : keysym
= 124; break; // | (shifted \)
147 case 93 : keysym
= 125; break; // } (shifted ])
148 case 39 : keysym
= 34 ; break; // " (shifted ')
150 } else if ((keysym
>= 65) && (keysym
<=90)) {
151 /* Remap unshifted A-Z */
153 } else if (evt
.keyLocation
=== 3) {
156 case 96 : keysym
= 48; break; // 0
157 case 97 : keysym
= 49; break; // 1
158 case 98 : keysym
= 50; break; // 2
159 case 99 : keysym
= 51; break; // 3
160 case 100: keysym
= 52; break; // 4
161 case 101: keysym
= 53; break; // 5
162 case 102: keysym
= 54; break; // 6
163 case 103: keysym
= 55; break; // 7
164 case 104: keysym
= 56; break; // 8
165 case 105: keysym
= 57; break; // 9
166 case 109: keysym
= 45; break; // -
167 case 110: keysym
= 46; break; // .
168 case 111: keysym
= 47; break; // /
176 /* Translate DOM keyPress event to keysym value */
177 function getKeysym(evt
) {
180 if (typeof(evt
.which
) !== "undefined") {
181 // WebKit, Firefox, Opera
185 Util
.Warn("which not set, using keyCode");
186 keysym
= evt
.keyCode
;
189 if ((keysym
> 255) && (keysym
< 0xFF00)) {
190 msg
= "Mapping character code " + keysym
;
191 // Map Unicode outside Latin 1 to X11 keysyms
192 keysym
= unicodeTable
[keysym
];
193 if (typeof(keysym
) === 'undefined') {
196 Util
.Debug(msg
+ " to " + keysym
);
202 function show_keyDownList(kind
) {
204 var msg
= "keyDownList (" + kind
+ "):\n";
205 for (c
= 0; c
< keyDownList
.length
; c
++) {
206 msg
= msg
+ " " + c
+ " - keyCode: " + keyDownList
[c
].keyCode
+
207 " - which: " + keyDownList
[c
].which
+ "\n";
212 function copyKeyEvent(evt
) {
213 var members
= ['type', 'keyCode', 'charCode', 'which',
214 'altKey', 'ctrlKey', 'shiftKey',
215 'keyLocation', 'keyIdentifier'], i
, obj
= {};
216 for (i
= 0; i
< members
.length
; i
++) {
217 if (typeof(evt
[members
[i
]]) !== "undefined") {
218 obj
[members
[i
]] = evt
[members
[i
]];
224 function pushKeyEvent(fevt
) {
225 keyDownList
.push(fevt
);
228 function getKeyEvent(keyCode
, pop
) {
230 for (i
= keyDownList
.length
-1; i
>= 0; i
--) {
231 if (keyDownList
[i
].keyCode
=== keyCode
) {
232 if ((typeof(pop
) !== "undefined") && (pop
)) {
233 fevt
= keyDownList
.splice(i
, 1)[0];
235 fevt
= keyDownList
[i
];
243 function ignoreKeyEvent(evt
) {
244 // Blarg. Some keys have a different keyCode on keyDown vs keyUp
245 if (evt
.keyCode
=== 229) {
246 // French AZERTY keyboard dead key.
247 // Lame thing is that the respective keyUp is 219 so we can't
248 // properly ignore the keyUp event
256 // Key Event Handling:
258 // There are several challenges when dealing with key events:
259 // - The meaning and use of keyCode, charCode and which depends on
260 // both the browser and the event type (keyDown/Up vs keyPress).
261 // - We cannot automatically determine the keyboard layout
262 // - The keyDown and keyUp events have a keyCode value that has not
263 // been translated by modifier keys.
264 // - The keyPress event has a translated (for layout and modifiers)
265 // character code but the attribute containing it differs. keyCode
266 // contains the translated value in WebKit (Chrome/Safari), Opera
267 // 11 and IE9. charCode contains the value in WebKit and Firefox.
268 // The which attribute contains the value on WebKit, Firefox and
270 // - The keyDown/Up keyCode value indicates (sort of) the physical
271 // key was pressed but only for standard US layout. On a US
272 // keyboard, the '-' and '_' characters are on the same key and
273 // generate a keyCode value of 189. But on an AZERTY keyboard even
274 // though they are different physical keys they both still
275 // generate a keyCode of 189!
276 // - To prevent a key event from propagating to the browser and
277 // causing unwanted default actions (such as closing a tab,
278 // opening a menu, shifting focus, etc) we must suppress this
279 // event in both keyDown and keyPress because not all key strokes
280 // generate on a keyPress event. Also, in WebKit and IE9
281 // suppressing the keyDown prevents a keyPress but other browsers
282 // still generated a keyPress even if keyDown is suppressed.
284 // For safe key events, we wait until the keyPress event before
285 // reporting a key down event. For unsafe key events, we report a key
286 // down event when the keyDown event fires and we suppress any further
287 // actions (including keyPress).
289 // In order to report a key up event that matches what we reported
290 // for the key down event, we keep a list of keys that are currently
291 // down. When the keyDown event happens, we add the key event to the
292 // list. If it is a safe key event, then we update the which attribute
293 // in the most recent item on the list when we received a keyPress
294 // event (keyPress should immediately follow keyDown). When we
295 // received a keyUp event we search for the event on the list with
296 // a matching keyCode and we report the character code using the value
297 // in the 'which' attribute that was stored with that key.
300 function onKeyDown(e
) {
301 if (! conf
.focused
) {
304 var fevt
= null, evt
= (e
? e
: window
.event
),
305 keysym
= null, suppress
= false;
306 //Util.Debug("onKeyDown kC:" + evt.keyCode + " cC:" + evt.charCode + " w:" + evt.which);
308 fevt
= copyKeyEvent(evt
);
310 keysym
= getKeysymSpecial(evt
);
311 // Save keysym decoding for use in keyUp
312 fevt
.keysym
= keysym
;
314 // If it is a key or key combination that might trigger
315 // browser behaviors or it has no corresponding keyPress
316 // event, then send it immediately
317 if (conf
.onKeyPress
&& !ignoreKeyEvent(evt
)) {
318 Util
.Debug("onKeyPress down, keysym: " + keysym
+
319 " (onKeyDown key: " + evt
.keyCode
+
320 ", which: " + evt
.which
+ ")");
321 conf
.onKeyPress(keysym
, 1, evt
);
326 if (! ignoreKeyEvent(evt
)) {
327 // Add it to the list of depressed keys
329 //show_keyDownList('down');
333 // Suppress bubbling/default actions
337 // Allow the event to bubble and become a keyPress event which
338 // will have the character code translated
343 function onKeyPress(e
) {
344 if (! conf
.focused
) {
347 var evt
= (e
? e
: window
.event
),
348 kdlen
= keyDownList
.length
, keysym
= null;
349 //Util.Debug("onKeyPress kC:" + evt.keyCode + " cC:" + evt.charCode + " w:" + evt.which);
351 if (((evt
.which
!== "undefined") && (evt
.which
=== 0)) ||
352 (getKeysymSpecial(evt
))) {
353 // Firefox and Opera generate a keyPress event even if keyDown
354 // is suppressed. But the keys we want to suppress will have
356 // - the which attribute set to 0
357 // - getKeysymSpecial() will identify it
358 Util
.Debug("Ignoring special key in keyPress");
363 keysym
= getKeysym(evt
);
365 // Modify the the which attribute in the depressed keys list so
366 // that the keyUp event will be able to have the character code
367 // translation available.
369 keyDownList
[kdlen
-1].keysym
= keysym
;
371 Util
.Warn("keyDownList empty when keyPress triggered");
374 //show_keyDownList('press');
376 // Send the translated keysym
377 if (conf
.onKeyPress
&& (keysym
> 0)) {
378 Util
.Debug("onKeyPress down, keysym: " + keysym
+
379 " (onKeyPress key: " + evt
.keyCode
+
380 ", which: " + evt
.which
+ ")");
381 conf
.onKeyPress(keysym
, 1, evt
);
384 // Stop keypress events just in case
389 function onKeyUp(e
) {
390 if (! conf
.focused
) {
393 var fevt
= null, evt
= (e
? e
: window
.event
), keysym
;
394 //Util.Debug("onKeyUp kC:" + evt.keyCode + " cC:" + evt.charCode + " w:" + evt.which);
396 fevt
= getKeyEvent(evt
.keyCode
, true);
399 keysym
= fevt
.keysym
;
401 Util
.Warn("Key event (keyCode = " + evt
.keyCode
+
402 ") not found on keyDownList");
406 //show_keyDownList('up');
408 if (conf
.onKeyPress
&& (keysym
> 0)) {
409 //Util.Debug("keyPress up, keysym: " + keysym +
410 // " (key: " + evt.keyCode + ", which: " + evt.which + ")");
411 Util
.Debug("onKeyPress up, keysym: " + keysym
+
412 " (onKeyPress key: " + evt
.keyCode
+
413 ", which: " + evt
.which
+ ")");
414 conf
.onKeyPress(keysym
, 0, evt
);
421 // Public API interface functions
424 that
.grab = function() {
425 //Util.Debug(">> Keyboard.grab");
428 Util
.addEvent(c
, 'keydown', onKeyDown
);
429 Util
.addEvent(c
, 'keyup', onKeyUp
);
430 Util
.addEvent(c
, 'keypress', onKeyPress
);
432 //Util.Debug("<< Keyboard.grab");
435 that
.ungrab = function() {
436 //Util.Debug(">> Keyboard.ungrab");
439 Util
.removeEvent(c
, 'keydown', onKeyDown
);
440 Util
.removeEvent(c
, 'keyup', onKeyUp
);
441 Util
.removeEvent(c
, 'keypress', onKeyPress
);
443 //Util.Debug(">> Keyboard.ungrab");
446 return that
; // Return the public API interface
448 } // End of Keyboard()
452 // Mouse event handler
455 function Mouse(conf
) {
458 conf
= conf
|| {}; // Configuration
459 var that
= {}; // Public API interface
462 // Configuration settings
463 function cdef(v
, type
, defval
, desc
) {
464 Util
.conf_default(conf
, that
, v
, type
, defval
, desc
); }
466 // Capability settings, default can be overridden
467 cdef('target', 'dom', document
, 'DOM element that captures mouse input');
468 cdef('focused', 'bool', true, 'Capture and send mouse clicks/movement');
469 cdef('scale', 'float', 1.0, 'Viewport scale factor 0.0 - 1.0');
471 cdef('onMouseButton', 'func', null, 'Handler for mouse button click/release');
472 cdef('onMouseMove', 'func', null, 'Handler for mouse movement');
474 that
.set_target = function() { throw("target cannot be changed"); };
480 function onMouseButton(e
, down
) {
482 if (! conf
.focused
) {
485 evt
= (e
? e
: window
.event
);
486 pos
= Util
.getEventPosition(e
, conf
.target
, conf
.scale
);
488 /* everything except IE */
489 bmask
= 1 << evt
.button
;
492 bmask
= (evt
.button
& 0x1) + // Left
493 (evt
.button
& 0x2) * 2 + // Right
494 (evt
.button
& 0x4) / 2; // Middle
496 //Util.Debug("mouse " + pos.x + "," + pos.y + " down: " + down +
497 // " bmask: " + bmask + "(evt.button: " + evt.button + ")");
498 if (conf
.onMouseButton
) {
499 Util
.Debug("onMouseButton " + (down
? "down" : "up") +
500 ", x: " + pos
.x
+ ", y: " + pos
.y
+ ", bmask: " + bmask
);
501 conf
.onMouseButton(pos
.x
, pos
.y
, down
, bmask
);
507 function onMouseDown(e
) {
511 function onMouseUp(e
) {
515 function onMouseWheel(e
) {
516 var evt
, pos
, bmask
, wheelData
;
517 if (! conf
.focused
) {
520 evt
= (e
? e
: window
.event
);
521 pos
= Util
.getEventPosition(e
, conf
.target
, conf
.scale
);
522 wheelData
= evt
.detail
? evt
.detail
* -1 : evt
.wheelDelta
/ 40;
528 //Util.Debug('mouse scroll by ' + wheelData + ':' + pos.x + "," + pos.y);
529 if (conf
.onMouseButton
) {
530 conf
.onMouseButton(pos
.x
, pos
.y
, 1, bmask
);
531 conf
.onMouseButton(pos
.x
, pos
.y
, 0, bmask
);
537 function onMouseMove(e
) {
539 if (! conf
.focused
) {
542 evt
= (e
? e
: window
.event
);
543 pos
= Util
.getEventPosition(e
, conf
.target
, conf
.scale
);
544 //Util.Debug('mouse ' + evt.which + '/' + evt.button + ' up:' + pos.x + "," + pos.y);
545 if (conf
.onMouseMove
) {
546 conf
.onMouseMove(pos
.x
, pos
.y
);
550 function onMouseDisable(e
) {
552 if (! conf
.focused
) {
555 evt
= (e
? e
: window
.event
);
556 pos
= Util
.getEventPosition(e
, conf
.target
, conf
.scale
);
557 /* Stop propagation if inside canvas area */
558 if ((pos
.x
>= 0) && (pos
.y
>= 0) &&
559 (pos
.x
< conf
.target
.offsetWidth
) &&
560 (pos
.y
< conf
.target
.offsetHeight
)) {
561 //Util.Debug("mouse event disabled");
565 //Util.Debug("mouse event not disabled");
570 // Public API interface functions
573 that
.grab = function() {
574 //Util.Debug(">> Mouse.grab");
577 Util
.addEvent(c
, 'mousedown', onMouseDown
);
578 Util
.addEvent(c
, 'mouseup', onMouseUp
);
579 Util
.addEvent(c
, 'mousemove', onMouseMove
);
580 Util
.addEvent(c
, (Util
.Engine
.gecko
) ? 'DOMMouseScroll' : 'mousewheel',
583 /* Work around right and middle click browser behaviors */
584 Util
.addEvent(document
, 'click', onMouseDisable
);
585 Util
.addEvent(document
.body
, 'contextmenu', onMouseDisable
);
587 //Util.Debug("<< Mouse.grab");
590 that
.ungrab = function() {
591 //Util.Debug(">> Mouse.ungrab");
594 Util
.removeEvent(c
, 'mousedown', onMouseDown
);
595 Util
.removeEvent(c
, 'mouseup', onMouseUp
);
596 Util
.removeEvent(c
, 'mousemove', onMouseMove
);
597 Util
.removeEvent(c
, (Util
.Engine
.gecko
) ? 'DOMMouseScroll' : 'mousewheel',
600 /* Work around right and middle click browser behaviors */
601 Util
.removeEvent(document
, 'click', onMouseDisable
);
602 Util
.removeEvent(document
.body
, 'contextmenu', onMouseDisable
);
604 //Util.Debug(">> Mouse.ungrab");
607 return that
; // Return the public API interface
613 * Browser keypress to X11 keysym for Unicode characters > U+00FF