]> git.proxmox.com Git - mirror_novnc.git/blame_incremental - core/input/devices.js
Uncomment ES6 module syntax
[mirror_novnc.git] / core / input / devices.js
... / ...
CommitLineData
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
11import Util from "../util.js";
12import KeyboardUtil from "./util.js";
13
14
15export var Keyboard;
16
17(function () {
18 "use strict";
19
20 //
21 // Keyboard event handler
22 //
23
24 Keyboard = function (defaults) {
25 this._keyDownList = []; // List of depressed keys
26 // (even if they are happy)
27
28 Util.set_defaults(this, defaults, {
29 'target': document,
30 'focused': true
31 });
32
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))
38 )
39 )
40 ); /* jshint newcap: true */
41
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)
48 };
49 };
50
51 Keyboard.prototype = {
52 // private methods
53
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 + ")");
58 this._onKeyPress(e);
59 }
60 },
61
62 setQEMUVNCKeyboardHandler: function () {
63 this._handler = new KeyboardUtil.QEMUKeyEventDecoder(KeyboardUtil.ModifierSync(),
64 KeyboardUtil.TrackQEMUKeyState(
65 this._handleRfbEvent.bind(this)
66 )
67 );
68 },
69
70 _handleKeyDown: function (e) {
71 if (!this._focused) { return; }
72
73 if (this._handler.keydown(e)) {
74 // Suppress bubbling/default actions
75 Util.stopEvent(e);
76 } else {
77 // Allow the event to bubble and become a keyPress event which
78 // will have the character code translated
79 }
80 },
81
82 _handleKeyPress: function (e) {
83 if (!this._focused) { return; }
84
85 if (this._handler.keypress(e)) {
86 // Suppress bubbling/default actions
87 Util.stopEvent(e);
88 }
89 },
90
91 _handleKeyUp: function (e) {
92 if (!this._focused) { return; }
93
94 if (this._handler.keyup(e)) {
95 // Suppress bubbling/default actions
96 Util.stopEvent(e);
97 }
98 },
99
100 _allKeysUp: function () {
101 Util.Debug(">> Keyboard.allKeysUp");
102 this._handler.releaseAll();
103 Util.Debug("<< Keyboard.allKeysUp");
104 },
105
106 // Public methods
107
108 grab: function () {
109 //Util.Debug(">> Keyboard.grab");
110 var c = this._target;
111
112 c.addEventListener('keydown', this._eventHandlers.keydown);
113 c.addEventListener('keyup', this._eventHandlers.keyup);
114 c.addEventListener('keypress', this._eventHandlers.keypress);
115
116 // Release (key up) if window loses focus
117 window.addEventListener('blur', this._eventHandlers.blur);
118
119 //Util.Debug("<< Keyboard.grab");
120 },
121
122 ungrab: function () {
123 //Util.Debug(">> Keyboard.ungrab");
124 var c = this._target;
125
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);
130
131 // Release (key up) all keys that are in a down state
132 this._allKeysUp();
133
134 //Util.Debug(">> Keyboard.ungrab");
135 },
136
137 sync: function (e) {
138 this._handler.syncModifiers(e);
139 }
140 };
141
142 Util.make_properties(Keyboard, [
143 ['target', 'wo', 'dom'], // DOM element that captures keyboard input
144 ['focused', 'rw', 'bool'], // Capture and send key events
145
146 ['onKeyPress', 'rw', 'func'] // Handler for key press/release
147 ]);
148})();
149
150export var Mouse;
151
152(function () {
153 Mouse = function (defaults) {
154 this._mouseCaptured = false;
155
156 this._doubleClickTimer = null;
157 this._lastTouchPos = null;
158
159 // Configuration attributes
160 Util.set_defaults(this, defaults, {
161 'target': document,
162 'focused': true,
163 'touchButton': 1
164 });
165
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)
172 };
173 };
174
175 Mouse.prototype = {
176 // private methods
177 _captureMouse: function () {
178 // capturing the mouse ensures we get the mouseup event
179 Util.setCapture(this._target);
180
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;
184 },
185
186 _releaseMouse: function () {
187 Util.releaseCapture();
188 this._mouseCaptured = false;
189 },
190
191 _resetDoubleClickTimer: function () {
192 this._doubleClickTimer = null;
193 },
194
195 _handleMouseButton: function (e, down) {
196 if (!this._focused) { return; }
197
198 if (this._notify) {
199 this._notify(e);
200 }
201
202 var pos = this._getMousePosition(e);
203
204 var bmask;
205 if (e.touches || e.changedTouches) {
206 // Touch device
207
208 // When two touches occur within 500 ms of each other and are
209 // close enough together a double click is triggered.
210 if (down == 1) {
211 if (this._doubleClickTimer === null) {
212 this._lastTouchPos = pos;
213 } else {
214 clearTimeout(this._doubleClickTimer);
215
216 // When the distance between the two touches is small enough
217 // force the position of the latter touch to the position of
218 // the first.
219
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));
223
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);
227 if (d < threshold) {
228 pos = this._lastTouchPos;
229 }
230 }
231 this._doubleClickTimer = setTimeout(this._resetDoubleClickTimer.bind(this), 500);
232 }
233 bmask = this._touchButton;
234 // If bmask is set
235 } else if (e.which) {
236 /* everything except IE */
237 bmask = 1 << e.button;
238 } else {
239 /* IE including 9 */
240 bmask = (e.button & 0x1) + // Left
241 (e.button & 0x2) * 2 + // Right
242 (e.button & 0x4) / 2; // Middle
243 }
244
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);
249 }
250 Util.stopEvent(e);
251 },
252
253 _handleMouseDown: function (e) {
254 this._captureMouse();
255 this._handleMouseButton(e, 1);
256 },
257
258 _handleMouseUp: function (e) {
259 if (!this._mouseCaptured) { return; }
260
261 this._handleMouseButton(e, 0);
262 this._releaseMouse();
263 },
264
265 _handleMouseWheel: function (e) {
266 if (!this._focused) { return; }
267
268 if (this._notify) {
269 this._notify(e);
270 }
271
272 var pos = this._getMousePosition(e);
273
274 if (this._onMouseButton) {
275 if (e.deltaX < 0) {
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);
281 }
282
283 if (e.deltaY < 0) {
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);
289 }
290 }
291
292 Util.stopEvent(e);
293 },
294
295 _handleMouseMove: function (e) {
296 if (! this._focused) { return; }
297
298 if (this._notify) {
299 this._notify(e);
300 }
301
302 var pos = this._getMousePosition(e);
303 if (this._onMouseMove) {
304 this._onMouseMove(pos.x, pos.y);
305 }
306 Util.stopEvent(e);
307 },
308
309 _handleMouseDisable: function (e) {
310 if (!this._focused) { return; }
311
312 /*
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.
317 */
318 if (e.target == this._target) {
319 //Util.Debug("mouse event disabled");
320 Util.stopEvent(e);
321 }
322 },
323
324 // Return coordinates relative to target
325 _getMousePosition: function(e) {
326 e = Util.getPointerEvent(e);
327 var bounds = this._target.getBoundingClientRect();
328 var x, y;
329 // Clip to target bounds
330 if (e.clientX < bounds.left) {
331 x = 0;
332 } else if (e.clientX >= bounds.right) {
333 x = bounds.width - 1;
334 } else {
335 x = e.clientX - bounds.left;
336 }
337 if (e.clientY < bounds.top) {
338 y = 0;
339 } else if (e.clientY >= bounds.bottom) {
340 y = bounds.height - 1;
341 } else {
342 y = e.clientY - bounds.top;
343 }
344 return {x:x, y:y};
345 },
346
347
348 // Public methods
349 grab: function () {
350 var c = this._target;
351
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);
357 }
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);
363
364 /* Prevent middle-click pasting (see above for why we bind to document) */
365 document.addEventListener('click', this._eventHandlers.mousedisable);
366
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);
370 },
371
372 ungrab: function () {
373 var c = this._target;
374
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);
380 }
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);
386
387 document.removeEventListener('click', this._eventHandlers.mousedisable);
388
389 c.removeEventListener('contextmenu', this._eventHandlers.mousedisable);
390 }
391 };
392
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
397
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)
401 ]);
402})();