]> git.proxmox.com Git - mirror_novnc.git/blob - core/input/devices.js
91a202c586c8dad0c80cba785335cc2741d70dcc
[mirror_novnc.git] / core / input / devices.js
1 /*
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)
6 */
7
8 /*jslint browser: true, white: false */
9 /*global window, Util */
10
11 /* [module]
12 * import Util from "../util";
13 * import KeyboardUtil from "./util";
14 */
15
16 /* [module] export */ var Keyboard;
17
18 (function () {
19 "use strict";
20
21 //
22 // Keyboard event handler
23 //
24
25 Keyboard = function (defaults) {
26 this._keyDownList = []; // List of depressed keys
27 // (even if they are happy)
28
29 Util.set_defaults(this, defaults, {
30 'target': document,
31 'focused': true
32 });
33
34 // create the keyboard handler
35 this._handler = new KeyboardUtil.KeyEventDecoder(KeyboardUtil.ModifierSync(),
36 KeyboardUtil.VerifyCharModifier( /* jshint newcap: false */
37 KeyboardUtil.TrackKeyState(
38 KeyboardUtil.EscapeModifiers(this._handleRfbEvent.bind(this))
39 )
40 )
41 ); /* jshint newcap: true */
42
43 // keep these here so we can refer to them later
44 this._eventHandlers = {
45 'keyup': this._handleKeyUp.bind(this),
46 'keydown': this._handleKeyDown.bind(this),
47 'keypress': this._handleKeyPress.bind(this),
48 'blur': this._allKeysUp.bind(this)
49 };
50 };
51
52 Keyboard.prototype = {
53 // private methods
54
55 _handleRfbEvent: function (e) {
56 if (this._onKeyPress) {
57 Util.Debug("onKeyPress " + (e.type == 'keydown' ? "down" : "up") +
58 ", keysym: " + e.keysym.keysym + "(" + e.keysym.keyname + ")");
59 this._onKeyPress(e);
60 }
61 },
62
63 setQEMUVNCKeyboardHandler: function () {
64 this._handler = new KeyboardUtil.QEMUKeyEventDecoder(KeyboardUtil.ModifierSync(),
65 KeyboardUtil.TrackQEMUKeyState(
66 this._handleRfbEvent.bind(this)
67 )
68 );
69 },
70
71 _handleKeyDown: function (e) {
72 if (!this._focused) { return true; }
73
74 if (this._handler.keydown(e)) {
75 // Suppress bubbling/default actions
76 Util.stopEvent(e);
77 return false;
78 } else {
79 // Allow the event to bubble and become a keyPress event which
80 // will have the character code translated
81 return true;
82 }
83 },
84
85 _handleKeyPress: function (e) {
86 if (!this._focused) { return true; }
87
88 if (this._handler.keypress(e)) {
89 // Suppress bubbling/default actions
90 Util.stopEvent(e);
91 return false;
92 } else {
93 // Allow the event to bubble and become a keyPress event which
94 // will have the character code translated
95 return true;
96 }
97 },
98
99 _handleKeyUp: function (e) {
100 if (!this._focused) { return true; }
101
102 if (this._handler.keyup(e)) {
103 // Suppress bubbling/default actions
104 Util.stopEvent(e);
105 return false;
106 } else {
107 // Allow the event to bubble and become a keyPress event which
108 // will have the character code translated
109 return true;
110 }
111 },
112
113 _allKeysUp: function () {
114 Util.Debug(">> Keyboard.allKeysUp");
115 this._handler.releaseAll();
116 Util.Debug("<< Keyboard.allKeysUp");
117 },
118
119 // Public methods
120
121 grab: function () {
122 //Util.Debug(">> Keyboard.grab");
123 var c = this._target;
124
125 c.addEventListener('keydown', this._eventHandlers.keydown);
126 c.addEventListener('keyup', this._eventHandlers.keyup);
127 c.addEventListener('keypress', this._eventHandlers.keypress);
128
129 // Release (key up) if window loses focus
130 window.addEventListener('blur', this._eventHandlers.blur);
131
132 //Util.Debug("<< Keyboard.grab");
133 },
134
135 ungrab: function () {
136 //Util.Debug(">> Keyboard.ungrab");
137 var c = this._target;
138
139 c.removeEventListener('keydown', this._eventHandlers.keydown);
140 c.removeEventListener('keyup', this._eventHandlers.keyup);
141 c.removeEventListener('keypress', this._eventHandlers.keypress);
142 window.removeEventListener('blur', this._eventHandlers.blur);
143
144 // Release (key up) all keys that are in a down state
145 this._allKeysUp();
146
147 //Util.Debug(">> Keyboard.ungrab");
148 },
149
150 sync: function (e) {
151 this._handler.syncModifiers(e);
152 }
153 };
154
155 Util.make_properties(Keyboard, [
156 ['target', 'wo', 'dom'], // DOM element that captures keyboard input
157 ['focused', 'rw', 'bool'], // Capture and send key events
158
159 ['onKeyPress', 'rw', 'func'] // Handler for key press/release
160 ]);
161 })();
162
163 /* [module] export */ var Mouse;
164
165 (function () {
166 Mouse = function (defaults) {
167 this._mouseCaptured = false;
168
169 this._doubleClickTimer = null;
170 this._lastTouchPos = null;
171
172 // Configuration attributes
173 Util.set_defaults(this, defaults, {
174 'target': document,
175 'focused': true,
176 'scale': 1.0,
177 'touchButton': 1
178 });
179
180 this._eventHandlers = {
181 'mousedown': this._handleMouseDown.bind(this),
182 'mouseup': this._handleMouseUp.bind(this),
183 'mousemove': this._handleMouseMove.bind(this),
184 'mousewheel': this._handleMouseWheel.bind(this),
185 'mousedisable': this._handleMouseDisable.bind(this)
186 };
187 };
188
189 Mouse.prototype = {
190 // private methods
191 _captureMouse: function () {
192 // capturing the mouse ensures we get the mouseup event
193 Util.setCapture(this._target);
194
195 // some browsers give us mouseup events regardless,
196 // so if we never captured the mouse, we can disregard the event
197 this._mouseCaptured = true;
198 },
199
200 _releaseMouse: function () {
201 Util.releaseCapture();
202 this._mouseCaptured = false;
203 },
204
205 _resetDoubleClickTimer: function () {
206 this._doubleClickTimer = null;
207 },
208
209 _handleMouseButton: function (e, down) {
210 if (!this._focused) { return true; }
211
212 if (this._notify) {
213 this._notify(e);
214 }
215
216 var evt = (e ? e : window.event);
217 var pos = Util.getEventPosition(e, this._target, this._scale);
218
219 var bmask;
220 if (e.touches || e.changedTouches) {
221 // Touch device
222
223 // When two touches occur within 500 ms of each other and are
224 // close enough together a double click is triggered.
225 if (down == 1) {
226 if (this._doubleClickTimer === null) {
227 this._lastTouchPos = pos;
228 } else {
229 clearTimeout(this._doubleClickTimer);
230
231 // When the distance between the two touches is small enough
232 // force the position of the latter touch to the position of
233 // the first.
234
235 var xs = this._lastTouchPos.x - pos.x;
236 var ys = this._lastTouchPos.y - pos.y;
237 var d = Math.sqrt((xs * xs) + (ys * ys));
238
239 // The goal is to trigger on a certain physical width, the
240 // devicePixelRatio brings us a bit closer but is not optimal.
241 var threshold = 20 * (window.devicePixelRatio || 1);
242 if (d < threshold) {
243 pos = this._lastTouchPos;
244 }
245 }
246 this._doubleClickTimer = setTimeout(this._resetDoubleClickTimer.bind(this), 500);
247 }
248 bmask = this._touchButton;
249 // If bmask is set
250 } else if (evt.which) {
251 /* everything except IE */
252 bmask = 1 << evt.button;
253 } else {
254 /* IE including 9 */
255 bmask = (evt.button & 0x1) + // Left
256 (evt.button & 0x2) * 2 + // Right
257 (evt.button & 0x4) / 2; // Middle
258 }
259
260 if (this._onMouseButton) {
261 Util.Debug("onMouseButton " + (down ? "down" : "up") +
262 ", x: " + pos.x + ", y: " + pos.y + ", bmask: " + bmask);
263 this._onMouseButton(pos.x, pos.y, down, bmask);
264 }
265 Util.stopEvent(e);
266 return false;
267 },
268
269 _handleMouseDown: function (e) {
270 this._captureMouse();
271 this._handleMouseButton(e, 1);
272 },
273
274 _handleMouseUp: function (e) {
275 if (!this._mouseCaptured) { return; }
276
277 this._handleMouseButton(e, 0);
278 this._releaseMouse();
279 },
280
281 _handleMouseWheel: function (e) {
282 if (!this._focused) { return true; }
283
284 if (this._notify) {
285 this._notify(e);
286 }
287
288 var evt = (e ? e : window.event);
289 var pos = Util.getEventPosition(e, this._target, this._scale);
290
291 if (this._onMouseButton) {
292 if (evt.deltaX < 0) {
293 this._onMouseButton(pos.x, pos.y, 1, 1 << 5);
294 this._onMouseButton(pos.x, pos.y, 0, 1 << 5);
295 } else if (evt.deltaX > 0) {
296 this._onMouseButton(pos.x, pos.y, 1, 1 << 6);
297 this._onMouseButton(pos.x, pos.y, 0, 1 << 6);
298 }
299
300 if (evt.deltaY < 0) {
301 this._onMouseButton(pos.x, pos.y, 1, 1 << 3);
302 this._onMouseButton(pos.x, pos.y, 0, 1 << 3);
303 } else if (evt.deltaY > 0) {
304 this._onMouseButton(pos.x, pos.y, 1, 1 << 4);
305 this._onMouseButton(pos.x, pos.y, 0, 1 << 4);
306 }
307 }
308
309 Util.stopEvent(e);
310 return false;
311 },
312
313 _handleMouseMove: function (e) {
314 if (! this._focused) { return true; }
315
316 if (this._notify) {
317 this._notify(e);
318 }
319
320 var evt = (e ? e : window.event);
321 var pos = Util.getEventPosition(e, this._target, this._scale);
322 if (this._onMouseMove) {
323 this._onMouseMove(pos.x, pos.y);
324 }
325 Util.stopEvent(e);
326 return false;
327 },
328
329 _handleMouseDisable: function (e) {
330 if (!this._focused) { return true; }
331
332 var evt = (e ? e : window.event);
333 /*
334 * Stop propagation if inside canvas area
335 * Note: This is only needed for the 'click' event as it fails
336 * to fire properly for the target element so we have
337 * to listen on the document element instead.
338 */
339 if (evt.target == this._target) {
340 //Util.Debug("mouse event disabled");
341 Util.stopEvent(e);
342 return false;
343 }
344
345 return true;
346 },
347
348
349 // Public methods
350 grab: function () {
351 var c = this._target;
352
353 if (Util.isTouchDevice) {
354 c.addEventListener('touchstart', this._eventHandlers.mousedown);
355 window.addEventListener('touchend', this._eventHandlers.mouseup);
356 c.addEventListener('touchend', this._eventHandlers.mouseup);
357 c.addEventListener('touchmove', this._eventHandlers.mousemove);
358 }
359 c.addEventListener('mousedown', this._eventHandlers.mousedown);
360 window.addEventListener('mouseup', this._eventHandlers.mouseup);
361 c.addEventListener('mouseup', this._eventHandlers.mouseup);
362 c.addEventListener('mousemove', this._eventHandlers.mousemove);
363 c.addEventListener('wheel', this._eventHandlers.mousewheel);
364
365 /* Prevent middle-click pasting (see above for why we bind to document) */
366 document.addEventListener('click', this._eventHandlers.mousedisable);
367
368 /* preventDefault() on mousedown doesn't stop this event for some
369 reason so we have to explicitly block it */
370 c.addEventListener('contextmenu', this._eventHandlers.mousedisable);
371 },
372
373 ungrab: function () {
374 var c = this._target;
375
376 if (Util.isTouchDevice) {
377 c.removeEventListener('touchstart', this._eventHandlers.mousedown);
378 window.removeEventListener('touchend', this._eventHandlers.mouseup);
379 c.removeEventListener('touchend', this._eventHandlers.mouseup);
380 c.removeEventListener('touchmove', this._eventHandlers.mousemove);
381 }
382 c.removeEventListener('mousedown', this._eventHandlers.mousedown);
383 window.removeEventListener('mouseup', this._eventHandlers.mouseup);
384 c.removeEventListener('mouseup', this._eventHandlers.mouseup);
385 c.removeEventListener('mousemove', this._eventHandlers.mousemove);
386 c.removeEventListener('wheel', this._eventHandlers.mousewheel);
387
388 document.removeEventListener('click', this._eventHandlers.mousedisable);
389
390 c.removeEventListener('contextmenu', this._eventHandlers.mousedisable);
391 }
392 };
393
394 Util.make_properties(Mouse, [
395 ['target', 'ro', 'dom'], // DOM element that captures mouse input
396 ['notify', 'ro', 'func'], // Function to call to notify whenever a mouse event is received
397 ['focused', 'rw', 'bool'], // Capture and send mouse clicks/movement
398 ['scale', 'rw', 'float'], // Viewport scale factor 0.0 - 1.0
399
400 ['onMouseButton', 'rw', 'func'], // Handler for mouse button click/release
401 ['onMouseMove', 'rw', 'func'], // Handler for mouse movement
402 ['touchButton', 'rw', 'int'] // Button mask (1, 2, 4) for touch devices (0 means ignore clicks)
403 ]);
404 })();