]> git.proxmox.com Git - mirror_novnc.git/blame - core/input/devices.js
Uncomment ES6 module syntax
[mirror_novnc.git] / core / input / devices.js
CommitLineData
d3796c14
JM
1/*
2 * noVNC: HTML5 VNC client
1d728ace 3 * Copyright (C) 2012 Joel Martin
b2f1961a 4 * Copyright (C) 2013 Samuel Mannehed for Cendio AB
1d728ace 5 * Licensed under MPL 2.0 or any later version (see LICENSE.txt)
d3796c14
JM
6 */
7
d6e281ba 8/*jslint browser: true, white: false */
d3796c14
JM
9/*global window, Util */
10
3ae0bb09
SR
11import Util from "../util.js";
12import KeyboardUtil from "./util.js";
13
ae510306 14
3ae0bb09 15export var Keyboard;
d6e281ba
SR
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
ae510306
SR
34 this._handler = new KeyboardUtil.KeyEventDecoder(KeyboardUtil.ModifierSync(),
35 KeyboardUtil.VerifyCharModifier( /* jshint newcap: false */
36 KeyboardUtil.TrackKeyState(
37 KeyboardUtil.EscapeModifiers(this._handleRfbEvent.bind(this))
d6e281ba
SR
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 + ")");
8f06673a 58 this._onKeyPress(e);
d6e281ba
SR
59 }
60 },
d3796c14 61
8f06673a 62 setQEMUVNCKeyboardHandler: function () {
ae510306
SR
63 this._handler = new KeyboardUtil.QEMUKeyEventDecoder(KeyboardUtil.ModifierSync(),
64 KeyboardUtil.TrackQEMUKeyState(
8f06673a
DHB
65 this._handleRfbEvent.bind(this)
66 )
67 );
68 },
69
d6e281ba 70 _handleKeyDown: function (e) {
10d1ecc1 71 if (!this._focused) { return; }
d3796c14 72
d6e281ba
SR
73 if (this._handler.keydown(e)) {
74 // Suppress bubbling/default actions
75 Util.stopEvent(e);
d6e281ba
SR
76 } else {
77 // Allow the event to bubble and become a keyPress event which
78 // will have the character code translated
d6e281ba
SR
79 }
80 },
d3796c14 81
d6e281ba 82 _handleKeyPress: function (e) {
10d1ecc1 83 if (!this._focused) { return; }
c96f9003 84
d6e281ba
SR
85 if (this._handler.keypress(e)) {
86 // Suppress bubbling/default actions
87 Util.stopEvent(e);
d6e281ba
SR
88 }
89 },
d3796c14 90
d6e281ba 91 _handleKeyUp: function (e) {
10d1ecc1 92 if (!this._focused) { return; }
d3796c14 93
d6e281ba
SR
94 if (this._handler.keyup(e)) {
95 // Suppress bubbling/default actions
96 Util.stopEvent(e);
d6e281ba
SR
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
b4ef49ea
SR
112 c.addEventListener('keydown', this._eventHandlers.keydown);
113 c.addEventListener('keyup', this._eventHandlers.keyup);
114 c.addEventListener('keypress', this._eventHandlers.keypress);
d6e281ba
SR
115
116 // Release (key up) if window loses focus
b4ef49ea 117 window.addEventListener('blur', this._eventHandlers.blur);
d6e281ba
SR
118
119 //Util.Debug("<< Keyboard.grab");
120 },
121
122 ungrab: function () {
123 //Util.Debug(">> Keyboard.ungrab");
124 var c = this._target;
125
b4ef49ea
SR
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);
d3796c14 130
d6e281ba
SR
131 // Release (key up) all keys that are in a down state
132 this._allKeysUp();
d3796c14 133
d6e281ba
SR
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
5210330a 147 ]);
ae510306 148})();
d3796c14 149
3ae0bb09 150export var Mouse;
d6e281ba 151
ae510306 152(function () {
d6e281ba
SR
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,
d6e281ba
SR
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
86d15a49 179 Util.setCapture(this._target);
d6e281ba
SR
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 () {
b69dda9b 187 Util.releaseCapture();
d6e281ba
SR
188 this._mouseCaptured = false;
189 },
190
191 _resetDoubleClickTimer: function () {
192 this._doubleClickTimer = null;
193 },
b2f1961a 194
d6e281ba 195 _handleMouseButton: function (e, down) {
10d1ecc1 196 if (!this._focused) { return; }
cf19ad37 197
d6e281ba
SR
198 if (this._notify) {
199 this._notify(e);
200 }
b2f1961a 201
a0e3ec0a 202 var pos = this._getMousePosition(e);
d6e281ba
SR
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
f52105bc 209 // close enough together a double click is triggered.
d6e281ba
SR
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.
f52105bc 226 var threshold = 20 * (window.devicePixelRatio || 1);
227 if (d < threshold) {
d6e281ba
SR
228 pos = this._lastTouchPos;
229 }
230 }
231 this._doubleClickTimer = setTimeout(this._resetDoubleClickTimer.bind(this), 500);
a4ec2f5c 232 }
d6e281ba
SR
233 bmask = this._touchButton;
234 // If bmask is set
a0e3ec0a 235 } else if (e.which) {
d6e281ba 236 /* everything except IE */
a0e3ec0a 237 bmask = 1 << e.button;
d6e281ba
SR
238 } else {
239 /* IE including 9 */
a0e3ec0a
SM
240 bmask = (e.button & 0x1) + // Left
241 (e.button & 0x2) * 2 + // Right
242 (e.button & 0x4) / 2; // Middle
b2f1961a 243 }
d6e281ba
SR
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);
d6e281ba
SR
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) {
10d1ecc1 266 if (!this._focused) { return; }
d6e281ba
SR
267
268 if (this._notify) {
269 this._notify(e);
270 }
271
a0e3ec0a 272 var pos = this._getMousePosition(e);
d6e281ba
SR
273
274 if (this._onMouseButton) {
a0e3ec0a 275 if (e.deltaX < 0) {
ebb9086a
PO
276 this._onMouseButton(pos.x, pos.y, 1, 1 << 5);
277 this._onMouseButton(pos.x, pos.y, 0, 1 << 5);
a0e3ec0a 278 } else if (e.deltaX > 0) {
ebb9086a
PO
279 this._onMouseButton(pos.x, pos.y, 1, 1 << 6);
280 this._onMouseButton(pos.x, pos.y, 0, 1 << 6);
281 }
282
a0e3ec0a 283 if (e.deltaY < 0) {
ebb9086a
PO
284 this._onMouseButton(pos.x, pos.y, 1, 1 << 3);
285 this._onMouseButton(pos.x, pos.y, 0, 1 << 3);
a0e3ec0a 286 } else if (e.deltaY > 0) {
ebb9086a
PO
287 this._onMouseButton(pos.x, pos.y, 1, 1 << 4);
288 this._onMouseButton(pos.x, pos.y, 0, 1 << 4);
289 }
d6e281ba 290 }
ebb9086a 291
d6e281ba 292 Util.stopEvent(e);
d6e281ba
SR
293 },
294
295 _handleMouseMove: function (e) {
10d1ecc1 296 if (! this._focused) { return; }
d6e281ba
SR
297
298 if (this._notify) {
299 this._notify(e);
300 }
301
a0e3ec0a 302 var pos = this._getMousePosition(e);
d6e281ba
SR
303 if (this._onMouseMove) {
304 this._onMouseMove(pos.x, pos.y);
305 }
306 Util.stopEvent(e);
d6e281ba
SR
307 },
308
309 _handleMouseDisable: function (e) {
10d1ecc1 310 if (!this._focused) { return; }
d6e281ba 311
b69dda9b
SM
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 */
a0e3ec0a 318 if (e.target == this._target) {
d6e281ba
SR
319 //Util.Debug("mouse event disabled");
320 Util.stopEvent(e);
d6e281ba 321 }
d6e281ba
SR
322 },
323
af1b2ae1
SM
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 }
af1b2ae1
SM
344 return {x:x, y:y};
345 },
346
d6e281ba
SR
347
348 // Public methods
349 grab: function () {
350 var c = this._target;
351
bea2b3fd 352 if (Util.isTouchDevice) {
b4ef49ea
SR
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);
d6e281ba 357 }
4b20f236 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);
ebb9086a 362 c.addEventListener('wheel', this._eventHandlers.mousewheel);
d6e281ba 363
b69dda9b 364 /* Prevent middle-click pasting (see above for why we bind to document) */
b4ef49ea 365 document.addEventListener('click', this._eventHandlers.mousedisable);
b69dda9b
SM
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);
d6e281ba
SR
370 },
371
372 ungrab: function () {
373 var c = this._target;
374
bea2b3fd 375 if (Util.isTouchDevice) {
b4ef49ea
SR
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);
d6e281ba 380 }
4b20f236 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);
ebb9086a 385 c.removeEventListener('wheel', this._eventHandlers.mousewheel);
d6e281ba 386
b4ef49ea 387 document.removeEventListener('click', this._eventHandlers.mousedisable);
d6e281ba 388
b69dda9b 389 c.removeEventListener('contextmenu', this._eventHandlers.mousedisable);
b2f1961a 390 }
d6e281ba
SR
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
d6e281ba
SR
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})();