]>
git.proxmox.com Git - mirror_novnc.git/blob - core/input/devices.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 */
9 /*global window, Util */
11 import * as Log
from '../util/logging.js';
12 import { isTouchDevice
} from '../util/browsers.js'
13 import { setCapture
, releaseCapture
, stopEvent
, getPointerEvent
} from '../util/events.js';
14 import { set_defaults
, make_properties
} from '../util/properties.js';
15 import * as KeyboardUtil
from "./util.js";
18 // Keyboard event handler
21 const Keyboard = function (defaults
) {
22 this._keyDownList
= {}; // List of depressed keys
23 // (even if they are happy)
24 this._pendingKey
= null; // Key waiting for keypress
26 this._modifierState
= KeyboardUtil
.ModifierSync();
28 set_defaults(this, defaults
, {
33 // keep these here so we can refer to them later
34 this._eventHandlers
= {
35 'keyup': this._handleKeyUp
.bind(this),
36 'keydown': this._handleKeyDown
.bind(this),
37 'keypress': this._handleKeyPress
.bind(this),
38 'blur': this._allKeysUp
.bind(this)
42 Keyboard
.prototype = {
45 _sendKeyEvent: function (keysym
, code
, down
) {
46 if (!this._onKeyEvent
) {
50 Log
.Debug("onKeyEvent " + (down
? "down" : "up") +
51 ", keysym: " + keysym
, ", code: " + code
);
53 this._onKeyEvent(keysym
, code
, down
);
56 _getKeyCode: function (e
) {
57 var code
= KeyboardUtil
.getKeycode(e
);
58 if (code
=== 'Unidentified') {
59 // Unstable, but we don't have anything else to go on
60 // (don't use it for 'keypress' events thought since
61 // WebKit sets it to the same as charCode)
62 if (e
.keyCode
&& (e
.type
!== 'keypress')) {
63 code
= 'Platform' + e
.keyCode
;
70 _handleKeyDown: function (e
) {
71 if (!this._focused
) { return; }
73 this._modifierState
.keydown(e
);
75 var code
= this._getKeyCode(e
);
76 var keysym
= KeyboardUtil
.getKeysym(e
);
78 // We cannot handle keys we cannot track, but we also need
79 // to deal with virtual keyboards which omit key info
80 if (code
=== 'Unidentified') {
82 // If it's a virtual keyboard then it should be
83 // sufficient to just send press and release right
85 this._sendKeyEvent(keysym
, 'Unidentified', true);
86 this._sendKeyEvent(keysym
, 'Unidentified', false);
93 // Is this key already pressed? If so, then we must use the
94 // same keysym or we'll confuse the server
95 if (code
in this._keyDownList
) {
96 keysym
= this._keyDownList
[code
];
99 // If this is a legacy browser then we'll need to wait for
100 // a keypress event as well
102 this._pendingKey
= code
;
106 this._pendingKey
= null;
109 // if a char modifier is pressed, get the keys it consists
110 // of (on Windows, AltGr is equivalent to Ctrl+Alt)
111 var active
= this._modifierState
.activeCharModifier();
113 // If we have a char modifier down, and we're able to
114 // determine a keysym reliably then (a) we know to treat
115 // the modifier as a char modifier, and (b) we'll have to
116 // "escape" the modifier to undo the modifier when sending
119 var isCharModifier
= false;
120 for (var i
= 0; i
< active
.length
; ++i
) {
121 if (active
[i
] === keysym
) {
122 isCharModifier
= true;
125 if (!isCharModifier
) {
126 var escape
= this._modifierState
.activeCharModifier();
130 this._keyDownList
[code
] = keysym
;
134 for (var i
= 0; i
< escape
.length
; ++i
) {
135 this._sendKeyEvent(escape
[i
], 'Unidentified', false);
139 // send the character event
140 this._sendKeyEvent(keysym
, code
, true);
144 for (i
= 0; i
< escape
.length
; ++i
) {
145 this._sendKeyEvent(escape
[i
], 'Unidentified', true);
150 // Legacy event for browsers without code/key
151 _handleKeyPress: function (e
) {
152 if (!this._focused
) { return; }
156 // Are we expecting a keypress?
157 if (this._pendingKey
=== null) {
161 var code
= this._getKeyCode(e
);
162 var keysym
= KeyboardUtil
.getKeysym(e
);
164 // The key we were waiting for?
165 if ((code
!== 'Unidentified') && (code
!= this._pendingKey
)) {
169 code
= this._pendingKey
;
170 this._pendingKey
= null;
172 // if a char modifier is pressed, get the keys it consists
173 // of (on Windows, AltGr is equivalent to Ctrl+Alt)
174 var active
= this._modifierState
.activeCharModifier();
176 // If we have a char modifier down, and we're able to
177 // determine a keysym reliably then (a) we know to treat
178 // the modifier as a char modifier, and (b) we'll have to
179 // "escape" the modifier to undo the modifier when sending
181 if (active
&& keysym
) {
182 var isCharModifier
= false;
183 for (var i
= 0; i
< active
.length
; ++i
) {
184 if (active
[i
] === keysym
) {
185 isCharModifier
= true;
188 if (!isCharModifier
) {
189 var escape
= this._modifierState
.activeCharModifier();
194 console
.log('keypress with no keysym:', e
);
198 this._keyDownList
[code
] = keysym
;
202 for (var i
= 0; i
< escape
.length
; ++i
) {
203 this._sendKeyEvent(escape
[i
], 'Unidentified', false);
207 // send the character event
208 this._sendKeyEvent(keysym
, code
, true);
212 for (i
= 0; i
< escape
.length
; ++i
) {
213 this._sendKeyEvent(escape
[i
], 'Unidentified', true);
218 _handleKeyUp: function (e
) {
219 if (!this._focused
) { return; }
223 this._modifierState
.keyup(e
);
225 var code
= this._getKeyCode(e
);
227 // Do we really think this key is down?
228 if (!(code
in this._keyDownList
)) {
232 this._sendKeyEvent(this._keyDownList
[code
], code
, false);
234 delete this._keyDownList
[code
];
237 _allKeysUp: function () {
238 Log
.Debug(">> Keyboard.allKeysUp");
239 for (var code
in this._keyDownList
) {
240 this._sendKeyEvent(this._keyDownList
[code
], code
, false);
242 this._keyDownList
= {};
243 Log
.Debug("<< Keyboard.allKeysUp");
249 //Log.Debug(">> Keyboard.grab");
250 var c
= this._target
;
252 c
.addEventListener('keydown', this._eventHandlers
.keydown
);
253 c
.addEventListener('keyup', this._eventHandlers
.keyup
);
254 c
.addEventListener('keypress', this._eventHandlers
.keypress
);
256 // Release (key up) if window loses focus
257 window
.addEventListener('blur', this._eventHandlers
.blur
);
259 //Log.Debug("<< Keyboard.grab");
262 ungrab: function () {
263 //Log.Debug(">> Keyboard.ungrab");
264 var c
= this._target
;
266 c
.removeEventListener('keydown', this._eventHandlers
.keydown
);
267 c
.removeEventListener('keyup', this._eventHandlers
.keyup
);
268 c
.removeEventListener('keypress', this._eventHandlers
.keypress
);
269 window
.removeEventListener('blur', this._eventHandlers
.blur
);
271 // Release (key up) all keys that are in a down state
274 //Log.Debug(">> Keyboard.ungrab");
278 make_properties(Keyboard
, [
279 ['target', 'wo', 'dom'], // DOM element that captures keyboard input
280 ['focused', 'rw', 'bool'], // Capture and send key events
282 ['onKeyEvent', 'rw', 'func'] // Handler for key press/release
285 const Mouse = function (defaults
) {
286 this._mouseCaptured
= false;
288 this._doubleClickTimer
= null;
289 this._lastTouchPos
= null;
291 // Configuration attributes
292 set_defaults(this, defaults
, {
298 this._eventHandlers
= {
299 'mousedown': this._handleMouseDown
.bind(this),
300 'mouseup': this._handleMouseUp
.bind(this),
301 'mousemove': this._handleMouseMove
.bind(this),
302 'mousewheel': this._handleMouseWheel
.bind(this),
303 'mousedisable': this._handleMouseDisable
.bind(this)
309 _captureMouse: function () {
310 // capturing the mouse ensures we get the mouseup event
311 setCapture(this._target
);
313 // some browsers give us mouseup events regardless,
314 // so if we never captured the mouse, we can disregard the event
315 this._mouseCaptured
= true;
318 _releaseMouse: function () {
320 this._mouseCaptured
= false;
323 _resetDoubleClickTimer: function () {
324 this._doubleClickTimer
= null;
327 _handleMouseButton: function (e
, down
) {
328 if (!this._focused
) { return; }
330 var pos
= this._getMousePosition(e
);
333 if (e
.touches
|| e
.changedTouches
) {
336 // When two touches occur within 500 ms of each other and are
337 // close enough together a double click is triggered.
339 if (this._doubleClickTimer
=== null) {
340 this._lastTouchPos
= pos
;
342 clearTimeout(this._doubleClickTimer
);
344 // When the distance between the two touches is small enough
345 // force the position of the latter touch to the position of
348 var xs
= this._lastTouchPos
.x
- pos
.x
;
349 var ys
= this._lastTouchPos
.y
- pos
.y
;
350 var d
= Math
.sqrt((xs
* xs
) + (ys
* ys
));
352 // The goal is to trigger on a certain physical width, the
353 // devicePixelRatio brings us a bit closer but is not optimal.
354 var threshold
= 20 * (window
.devicePixelRatio
|| 1);
356 pos
= this._lastTouchPos
;
359 this._doubleClickTimer
= setTimeout(this._resetDoubleClickTimer
.bind(this), 500);
361 bmask
= this._touchButton
;
363 } else if (e
.which
) {
364 /* everything except IE */
365 bmask
= 1 << e
.button
;
368 bmask
= (e
.button
& 0x1) + // Left
369 (e
.button
& 0x2) * 2 + // Right
370 (e
.button
& 0x4) / 2; // Middle
373 if (this._onMouseButton
) {
374 Log
.Debug("onMouseButton " + (down
? "down" : "up") +
375 ", x: " + pos
.x
+ ", y: " + pos
.y
+ ", bmask: " + bmask
);
376 this._onMouseButton(pos
.x
, pos
.y
, down
, bmask
);
381 _handleMouseDown: function (e
) {
382 this._captureMouse();
383 this._handleMouseButton(e
, 1);
386 _handleMouseUp: function (e
) {
387 if (!this._mouseCaptured
) { return; }
389 this._handleMouseButton(e
, 0);
390 this._releaseMouse();
393 _handleMouseWheel: function (e
) {
394 if (!this._focused
) { return; }
396 var pos
= this._getMousePosition(e
);
398 if (this._onMouseButton
) {
400 this._onMouseButton(pos
.x
, pos
.y
, 1, 1 << 5);
401 this._onMouseButton(pos
.x
, pos
.y
, 0, 1 << 5);
402 } else if (e
.deltaX
> 0) {
403 this._onMouseButton(pos
.x
, pos
.y
, 1, 1 << 6);
404 this._onMouseButton(pos
.x
, pos
.y
, 0, 1 << 6);
408 this._onMouseButton(pos
.x
, pos
.y
, 1, 1 << 3);
409 this._onMouseButton(pos
.x
, pos
.y
, 0, 1 << 3);
410 } else if (e
.deltaY
> 0) {
411 this._onMouseButton(pos
.x
, pos
.y
, 1, 1 << 4);
412 this._onMouseButton(pos
.x
, pos
.y
, 0, 1 << 4);
419 _handleMouseMove: function (e
) {
420 if (! this._focused
) { return; }
422 var pos
= this._getMousePosition(e
);
423 if (this._onMouseMove
) {
424 this._onMouseMove(pos
.x
, pos
.y
);
429 _handleMouseDisable: function (e
) {
430 if (!this._focused
) { return; }
433 * Stop propagation if inside canvas area
434 * Note: This is only needed for the 'click' event as it fails
435 * to fire properly for the target element so we have
436 * to listen on the document element instead.
438 if (e
.target
== this._target
) {
443 // Return coordinates relative to target
444 _getMousePosition: function(e
) {
445 e
= getPointerEvent(e
);
446 var bounds
= this._target
.getBoundingClientRect();
448 // Clip to target bounds
449 if (e
.clientX
< bounds
.left
) {
451 } else if (e
.clientX
>= bounds
.right
) {
452 x
= bounds
.width
- 1;
454 x
= e
.clientX
- bounds
.left
;
456 if (e
.clientY
< bounds
.top
) {
458 } else if (e
.clientY
>= bounds
.bottom
) {
459 y
= bounds
.height
- 1;
461 y
= e
.clientY
- bounds
.top
;
468 var c
= this._target
;
471 c
.addEventListener('touchstart', this._eventHandlers
.mousedown
);
472 window
.addEventListener('touchend', this._eventHandlers
.mouseup
);
473 c
.addEventListener('touchend', this._eventHandlers
.mouseup
);
474 c
.addEventListener('touchmove', this._eventHandlers
.mousemove
);
476 c
.addEventListener('mousedown', this._eventHandlers
.mousedown
);
477 window
.addEventListener('mouseup', this._eventHandlers
.mouseup
);
478 c
.addEventListener('mouseup', this._eventHandlers
.mouseup
);
479 c
.addEventListener('mousemove', this._eventHandlers
.mousemove
);
480 c
.addEventListener('wheel', this._eventHandlers
.mousewheel
);
482 /* Prevent middle-click pasting (see above for why we bind to document) */
483 document
.addEventListener('click', this._eventHandlers
.mousedisable
);
485 /* preventDefault() on mousedown doesn't stop this event for some
486 reason so we have to explicitly block it */
487 c
.addEventListener('contextmenu', this._eventHandlers
.mousedisable
);
490 ungrab: function () {
491 var c
= this._target
;
494 c
.removeEventListener('touchstart', this._eventHandlers
.mousedown
);
495 window
.removeEventListener('touchend', this._eventHandlers
.mouseup
);
496 c
.removeEventListener('touchend', this._eventHandlers
.mouseup
);
497 c
.removeEventListener('touchmove', this._eventHandlers
.mousemove
);
499 c
.removeEventListener('mousedown', this._eventHandlers
.mousedown
);
500 window
.removeEventListener('mouseup', this._eventHandlers
.mouseup
);
501 c
.removeEventListener('mouseup', this._eventHandlers
.mouseup
);
502 c
.removeEventListener('mousemove', this._eventHandlers
.mousemove
);
503 c
.removeEventListener('wheel', this._eventHandlers
.mousewheel
);
505 document
.removeEventListener('click', this._eventHandlers
.mousedisable
);
507 c
.removeEventListener('contextmenu', this._eventHandlers
.mousedisable
);
511 make_properties(Mouse
, [
512 ['target', 'ro', 'dom'], // DOM element that captures mouse input
513 ['focused', 'rw', 'bool'], // Capture and send mouse clicks/movement
515 ['onMouseButton', 'rw', 'func'], // Handler for mouse button click/release
516 ['onMouseMove', 'rw', 'func'], // Handler for mouse movement
517 ['touchButton', 'rw', 'int'] // Button mask (1, 2, 4) for touch devices (0 means ignore clicks)
520 export { Keyboard
, Mouse
};