]>
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 Util
from "../util.js";
12 import KeyboardUtil
from "./util.js";
21 // Keyboard event handler
24 Keyboard = function (defaults
) {
25 this._keyDownList
= []; // List of depressed keys
26 // (even if they are happy)
28 Util
.set_defaults(this, defaults
, {
33 // create the keyboard handler
34 this._handler
= new KeyboardUtil
.KeyEventDecoder(KeyboardUtil
.ModifierSync(),
35 KeyboardUtil
.VerifyCharModifier( /* jshint newcap: false */
36 KeyboardUtil
.TrackKeyState(
37 KeyboardUtil
.EscapeModifiers(this._handleRfbEvent
.bind(this))
40 ); /* jshint newcap: true */
42 // keep these here so we can refer to them later
43 this._eventHandlers
= {
44 'keyup': this._handleKeyUp
.bind(this),
45 'keydown': this._handleKeyDown
.bind(this),
46 'keypress': this._handleKeyPress
.bind(this),
47 'blur': this._allKeysUp
.bind(this)
51 Keyboard
.prototype = {
54 _handleRfbEvent: function (e
) {
55 if (this._onKeyPress
) {
56 Util
.Debug("onKeyPress " + (e
.type
== 'keydown' ? "down" : "up") +
57 ", keysym: " + e
.keysym
.keysym
+ "(" + e
.keysym
.keyname
+ ")");
62 setQEMUVNCKeyboardHandler: function () {
63 this._handler
= new KeyboardUtil
.QEMUKeyEventDecoder(KeyboardUtil
.ModifierSync(),
64 KeyboardUtil
.TrackQEMUKeyState(
65 this._handleRfbEvent
.bind(this)
70 _handleKeyDown: function (e
) {
71 if (!this._focused
) { return; }
73 if (this._handler
.keydown(e
)) {
74 // Suppress bubbling/default actions
77 // Allow the event to bubble and become a keyPress event which
78 // will have the character code translated
82 _handleKeyPress: function (e
) {
83 if (!this._focused
) { return; }
85 if (this._handler
.keypress(e
)) {
86 // Suppress bubbling/default actions
91 _handleKeyUp: function (e
) {
92 if (!this._focused
) { return; }
94 if (this._handler
.keyup(e
)) {
95 // Suppress bubbling/default actions
100 _allKeysUp: function () {
101 Util
.Debug(">> Keyboard.allKeysUp");
102 this._handler
.releaseAll();
103 Util
.Debug("<< Keyboard.allKeysUp");
109 //Util.Debug(">> Keyboard.grab");
110 var c
= this._target
;
112 c
.addEventListener('keydown', this._eventHandlers
.keydown
);
113 c
.addEventListener('keyup', this._eventHandlers
.keyup
);
114 c
.addEventListener('keypress', this._eventHandlers
.keypress
);
116 // Release (key up) if window loses focus
117 window
.addEventListener('blur', this._eventHandlers
.blur
);
119 //Util.Debug("<< Keyboard.grab");
122 ungrab: function () {
123 //Util.Debug(">> Keyboard.ungrab");
124 var c
= this._target
;
126 c
.removeEventListener('keydown', this._eventHandlers
.keydown
);
127 c
.removeEventListener('keyup', this._eventHandlers
.keyup
);
128 c
.removeEventListener('keypress', this._eventHandlers
.keypress
);
129 window
.removeEventListener('blur', this._eventHandlers
.blur
);
131 // Release (key up) all keys that are in a down state
134 //Util.Debug(">> Keyboard.ungrab");
138 this._handler
.syncModifiers(e
);
142 Util
.make_properties(Keyboard
, [
143 ['target', 'wo', 'dom'], // DOM element that captures keyboard input
144 ['focused', 'rw', 'bool'], // Capture and send key events
146 ['onKeyPress', 'rw', 'func'] // Handler for key press/release
153 Mouse = function (defaults
) {
154 this._mouseCaptured
= false;
156 this._doubleClickTimer
= null;
157 this._lastTouchPos
= null;
159 // Configuration attributes
160 Util
.set_defaults(this, defaults
, {
166 this._eventHandlers
= {
167 'mousedown': this._handleMouseDown
.bind(this),
168 'mouseup': this._handleMouseUp
.bind(this),
169 'mousemove': this._handleMouseMove
.bind(this),
170 'mousewheel': this._handleMouseWheel
.bind(this),
171 'mousedisable': this._handleMouseDisable
.bind(this)
177 _captureMouse: function () {
178 // capturing the mouse ensures we get the mouseup event
179 Util
.setCapture(this._target
);
181 // some browsers give us mouseup events regardless,
182 // so if we never captured the mouse, we can disregard the event
183 this._mouseCaptured
= true;
186 _releaseMouse: function () {
187 Util
.releaseCapture();
188 this._mouseCaptured
= false;
191 _resetDoubleClickTimer: function () {
192 this._doubleClickTimer
= null;
195 _handleMouseButton: function (e
, down
) {
196 if (!this._focused
) { return; }
202 var pos
= this._getMousePosition(e
);
205 if (e
.touches
|| e
.changedTouches
) {
208 // When two touches occur within 500 ms of each other and are
209 // close enough together a double click is triggered.
211 if (this._doubleClickTimer
=== null) {
212 this._lastTouchPos
= pos
;
214 clearTimeout(this._doubleClickTimer
);
216 // When the distance between the two touches is small enough
217 // force the position of the latter touch to the position of
220 var xs
= this._lastTouchPos
.x
- pos
.x
;
221 var ys
= this._lastTouchPos
.y
- pos
.y
;
222 var d
= Math
.sqrt((xs
* xs
) + (ys
* ys
));
224 // The goal is to trigger on a certain physical width, the
225 // devicePixelRatio brings us a bit closer but is not optimal.
226 var threshold
= 20 * (window
.devicePixelRatio
|| 1);
228 pos
= this._lastTouchPos
;
231 this._doubleClickTimer
= setTimeout(this._resetDoubleClickTimer
.bind(this), 500);
233 bmask
= this._touchButton
;
235 } else if (e
.which
) {
236 /* everything except IE */
237 bmask
= 1 << e
.button
;
240 bmask
= (e
.button
& 0x1) + // Left
241 (e
.button
& 0x2) * 2 + // Right
242 (e
.button
& 0x4) / 2; // Middle
245 if (this._onMouseButton
) {
246 Util
.Debug("onMouseButton " + (down
? "down" : "up") +
247 ", x: " + pos
.x
+ ", y: " + pos
.y
+ ", bmask: " + bmask
);
248 this._onMouseButton(pos
.x
, pos
.y
, down
, bmask
);
253 _handleMouseDown: function (e
) {
254 this._captureMouse();
255 this._handleMouseButton(e
, 1);
258 _handleMouseUp: function (e
) {
259 if (!this._mouseCaptured
) { return; }
261 this._handleMouseButton(e
, 0);
262 this._releaseMouse();
265 _handleMouseWheel: function (e
) {
266 if (!this._focused
) { return; }
272 var pos
= this._getMousePosition(e
);
274 if (this._onMouseButton
) {
276 this._onMouseButton(pos
.x
, pos
.y
, 1, 1 << 5);
277 this._onMouseButton(pos
.x
, pos
.y
, 0, 1 << 5);
278 } else if (e
.deltaX
> 0) {
279 this._onMouseButton(pos
.x
, pos
.y
, 1, 1 << 6);
280 this._onMouseButton(pos
.x
, pos
.y
, 0, 1 << 6);
284 this._onMouseButton(pos
.x
, pos
.y
, 1, 1 << 3);
285 this._onMouseButton(pos
.x
, pos
.y
, 0, 1 << 3);
286 } else if (e
.deltaY
> 0) {
287 this._onMouseButton(pos
.x
, pos
.y
, 1, 1 << 4);
288 this._onMouseButton(pos
.x
, pos
.y
, 0, 1 << 4);
295 _handleMouseMove: function (e
) {
296 if (! this._focused
) { return; }
302 var pos
= this._getMousePosition(e
);
303 if (this._onMouseMove
) {
304 this._onMouseMove(pos
.x
, pos
.y
);
309 _handleMouseDisable: function (e
) {
310 if (!this._focused
) { return; }
313 * Stop propagation if inside canvas area
314 * Note: This is only needed for the 'click' event as it fails
315 * to fire properly for the target element so we have
316 * to listen on the document element instead.
318 if (e
.target
== this._target
) {
319 //Util.Debug("mouse event disabled");
324 // Return coordinates relative to target
325 _getMousePosition: function(e
) {
326 e
= Util
.getPointerEvent(e
);
327 var bounds
= this._target
.getBoundingClientRect();
329 // Clip to target bounds
330 if (e
.clientX
< bounds
.left
) {
332 } else if (e
.clientX
>= bounds
.right
) {
333 x
= bounds
.width
- 1;
335 x
= e
.clientX
- bounds
.left
;
337 if (e
.clientY
< bounds
.top
) {
339 } else if (e
.clientY
>= bounds
.bottom
) {
340 y
= bounds
.height
- 1;
342 y
= e
.clientY
- bounds
.top
;
350 var c
= this._target
;
352 if (Util
.isTouchDevice
) {
353 c
.addEventListener('touchstart', this._eventHandlers
.mousedown
);
354 window
.addEventListener('touchend', this._eventHandlers
.mouseup
);
355 c
.addEventListener('touchend', this._eventHandlers
.mouseup
);
356 c
.addEventListener('touchmove', this._eventHandlers
.mousemove
);
358 c
.addEventListener('mousedown', this._eventHandlers
.mousedown
);
359 window
.addEventListener('mouseup', this._eventHandlers
.mouseup
);
360 c
.addEventListener('mouseup', this._eventHandlers
.mouseup
);
361 c
.addEventListener('mousemove', this._eventHandlers
.mousemove
);
362 c
.addEventListener('wheel', this._eventHandlers
.mousewheel
);
364 /* Prevent middle-click pasting (see above for why we bind to document) */
365 document
.addEventListener('click', this._eventHandlers
.mousedisable
);
367 /* preventDefault() on mousedown doesn't stop this event for some
368 reason so we have to explicitly block it */
369 c
.addEventListener('contextmenu', this._eventHandlers
.mousedisable
);
372 ungrab: function () {
373 var c
= this._target
;
375 if (Util
.isTouchDevice
) {
376 c
.removeEventListener('touchstart', this._eventHandlers
.mousedown
);
377 window
.removeEventListener('touchend', this._eventHandlers
.mouseup
);
378 c
.removeEventListener('touchend', this._eventHandlers
.mouseup
);
379 c
.removeEventListener('touchmove', this._eventHandlers
.mousemove
);
381 c
.removeEventListener('mousedown', this._eventHandlers
.mousedown
);
382 window
.removeEventListener('mouseup', this._eventHandlers
.mouseup
);
383 c
.removeEventListener('mouseup', this._eventHandlers
.mouseup
);
384 c
.removeEventListener('mousemove', this._eventHandlers
.mousemove
);
385 c
.removeEventListener('wheel', this._eventHandlers
.mousewheel
);
387 document
.removeEventListener('click', this._eventHandlers
.mousedisable
);
389 c
.removeEventListener('contextmenu', this._eventHandlers
.mousedisable
);
393 Util
.make_properties(Mouse
, [
394 ['target', 'ro', 'dom'], // DOM element that captures mouse input
395 ['notify', 'ro', 'func'], // Function to call to notify whenever a mouse event is received
396 ['focused', 'rw', 'bool'], // Capture and send mouse clicks/movement
398 ['onMouseButton', 'rw', 'func'], // Handler for mouse button click/release
399 ['onMouseMove', 'rw', 'func'], // Handler for mouse movement
400 ['touchButton', 'rw', 'int'] // Button mask (1, 2, 4) for touch devices (0 means ignore clicks)