]> git.proxmox.com Git - mirror_novnc.git/blame - core/input/devices.js
Stop using window.event
[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
ae510306 11/* [module]
bd5340c7
SR
12 * import Util from "../util";
13 * import KeyboardUtil from "./util";
ae510306
SR
14 */
15
16/* [module] export */ var Keyboard;
d6e281ba
SR
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
ae510306
SR
35 this._handler = new KeyboardUtil.KeyEventDecoder(KeyboardUtil.ModifierSync(),
36 KeyboardUtil.VerifyCharModifier( /* jshint newcap: false */
37 KeyboardUtil.TrackKeyState(
38 KeyboardUtil.EscapeModifiers(this._handleRfbEvent.bind(this))
d6e281ba
SR
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 + ")");
8f06673a 59 this._onKeyPress(e);
d6e281ba
SR
60 }
61 },
d3796c14 62
8f06673a 63 setQEMUVNCKeyboardHandler: function () {
ae510306
SR
64 this._handler = new KeyboardUtil.QEMUKeyEventDecoder(KeyboardUtil.ModifierSync(),
65 KeyboardUtil.TrackQEMUKeyState(
8f06673a
DHB
66 this._handleRfbEvent.bind(this)
67 )
68 );
69 },
70
d6e281ba
SR
71 _handleKeyDown: function (e) {
72 if (!this._focused) { return true; }
d3796c14 73
d6e281ba
SR
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 },
d3796c14 84
d6e281ba
SR
85 _handleKeyPress: function (e) {
86 if (!this._focused) { return true; }
c96f9003 87
d6e281ba
SR
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 },
d3796c14 98
d6e281ba
SR
99 _handleKeyUp: function (e) {
100 if (!this._focused) { return true; }
d3796c14 101
d6e281ba
SR
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
b4ef49ea
SR
125 c.addEventListener('keydown', this._eventHandlers.keydown);
126 c.addEventListener('keyup', this._eventHandlers.keyup);
127 c.addEventListener('keypress', this._eventHandlers.keypress);
d6e281ba
SR
128
129 // Release (key up) if window loses focus
b4ef49ea 130 window.addEventListener('blur', this._eventHandlers.blur);
d6e281ba
SR
131
132 //Util.Debug("<< Keyboard.grab");
133 },
134
135 ungrab: function () {
136 //Util.Debug(">> Keyboard.ungrab");
137 var c = this._target;
138
b4ef49ea
SR
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);
d3796c14 143
d6e281ba
SR
144 // Release (key up) all keys that are in a down state
145 this._allKeysUp();
d3796c14 146
d6e281ba
SR
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
5210330a 160 ]);
ae510306 161})();
d3796c14 162
ae510306 163/* [module] export */ var Mouse;
d6e281ba 164
ae510306 165(function () {
d6e281ba
SR
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
86d15a49 193 Util.setCapture(this._target);
d6e281ba
SR
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 () {
b69dda9b 201 Util.releaseCapture();
d6e281ba
SR
202 this._mouseCaptured = false;
203 },
204
205 _resetDoubleClickTimer: function () {
206 this._doubleClickTimer = null;
207 },
b2f1961a 208
d6e281ba
SR
209 _handleMouseButton: function (e, down) {
210 if (!this._focused) { return true; }
cf19ad37 211
d6e281ba
SR
212 if (this._notify) {
213 this._notify(e);
214 }
b2f1961a 215
a0e3ec0a 216 var pos = this._getMousePosition(e);
d6e281ba
SR
217
218 var bmask;
219 if (e.touches || e.changedTouches) {
220 // Touch device
221
222 // When two touches occur within 500 ms of each other and are
f52105bc 223 // close enough together a double click is triggered.
d6e281ba
SR
224 if (down == 1) {
225 if (this._doubleClickTimer === null) {
226 this._lastTouchPos = pos;
227 } else {
228 clearTimeout(this._doubleClickTimer);
229
230 // When the distance between the two touches is small enough
231 // force the position of the latter touch to the position of
232 // the first.
233
234 var xs = this._lastTouchPos.x - pos.x;
235 var ys = this._lastTouchPos.y - pos.y;
236 var d = Math.sqrt((xs * xs) + (ys * ys));
237
238 // The goal is to trigger on a certain physical width, the
239 // devicePixelRatio brings us a bit closer but is not optimal.
f52105bc 240 var threshold = 20 * (window.devicePixelRatio || 1);
241 if (d < threshold) {
d6e281ba
SR
242 pos = this._lastTouchPos;
243 }
244 }
245 this._doubleClickTimer = setTimeout(this._resetDoubleClickTimer.bind(this), 500);
a4ec2f5c 246 }
d6e281ba
SR
247 bmask = this._touchButton;
248 // If bmask is set
a0e3ec0a 249 } else if (e.which) {
d6e281ba 250 /* everything except IE */
a0e3ec0a 251 bmask = 1 << e.button;
d6e281ba
SR
252 } else {
253 /* IE including 9 */
a0e3ec0a
SM
254 bmask = (e.button & 0x1) + // Left
255 (e.button & 0x2) * 2 + // Right
256 (e.button & 0x4) / 2; // Middle
b2f1961a 257 }
d6e281ba
SR
258
259 if (this._onMouseButton) {
260 Util.Debug("onMouseButton " + (down ? "down" : "up") +
261 ", x: " + pos.x + ", y: " + pos.y + ", bmask: " + bmask);
262 this._onMouseButton(pos.x, pos.y, down, bmask);
263 }
264 Util.stopEvent(e);
265 return false;
266 },
267
268 _handleMouseDown: function (e) {
269 this._captureMouse();
270 this._handleMouseButton(e, 1);
271 },
272
273 _handleMouseUp: function (e) {
274 if (!this._mouseCaptured) { return; }
275
276 this._handleMouseButton(e, 0);
277 this._releaseMouse();
278 },
279
280 _handleMouseWheel: function (e) {
281 if (!this._focused) { return true; }
282
283 if (this._notify) {
284 this._notify(e);
285 }
286
a0e3ec0a 287 var pos = this._getMousePosition(e);
d6e281ba
SR
288
289 if (this._onMouseButton) {
a0e3ec0a 290 if (e.deltaX < 0) {
ebb9086a
PO
291 this._onMouseButton(pos.x, pos.y, 1, 1 << 5);
292 this._onMouseButton(pos.x, pos.y, 0, 1 << 5);
a0e3ec0a 293 } else if (e.deltaX > 0) {
ebb9086a
PO
294 this._onMouseButton(pos.x, pos.y, 1, 1 << 6);
295 this._onMouseButton(pos.x, pos.y, 0, 1 << 6);
296 }
297
a0e3ec0a 298 if (e.deltaY < 0) {
ebb9086a
PO
299 this._onMouseButton(pos.x, pos.y, 1, 1 << 3);
300 this._onMouseButton(pos.x, pos.y, 0, 1 << 3);
a0e3ec0a 301 } else if (e.deltaY > 0) {
ebb9086a
PO
302 this._onMouseButton(pos.x, pos.y, 1, 1 << 4);
303 this._onMouseButton(pos.x, pos.y, 0, 1 << 4);
304 }
d6e281ba 305 }
ebb9086a 306
d6e281ba
SR
307 Util.stopEvent(e);
308 return false;
309 },
310
311 _handleMouseMove: function (e) {
312 if (! this._focused) { return true; }
313
314 if (this._notify) {
315 this._notify(e);
316 }
317
a0e3ec0a 318 var pos = this._getMousePosition(e);
d6e281ba
SR
319 if (this._onMouseMove) {
320 this._onMouseMove(pos.x, pos.y);
321 }
322 Util.stopEvent(e);
323 return false;
324 },
325
326 _handleMouseDisable: function (e) {
327 if (!this._focused) { return true; }
328
b69dda9b
SM
329 /*
330 * Stop propagation if inside canvas area
331 * Note: This is only needed for the 'click' event as it fails
332 * to fire properly for the target element so we have
333 * to listen on the document element instead.
334 */
a0e3ec0a 335 if (e.target == this._target) {
d6e281ba
SR
336 //Util.Debug("mouse event disabled");
337 Util.stopEvent(e);
338 return false;
339 }
340
341 return true;
342 },
343
af1b2ae1
SM
344 // Return coordinates relative to target
345 _getMousePosition: function(e) {
346 e = Util.getPointerEvent(e);
347 var bounds = this._target.getBoundingClientRect();
348 var x, y;
349 // Clip to target bounds
350 if (e.clientX < bounds.left) {
351 x = 0;
352 } else if (e.clientX >= bounds.right) {
353 x = bounds.width - 1;
354 } else {
355 x = e.clientX - bounds.left;
356 }
357 if (e.clientY < bounds.top) {
358 y = 0;
359 } else if (e.clientY >= bounds.bottom) {
360 y = bounds.height - 1;
361 } else {
362 y = e.clientY - bounds.top;
363 }
364 x = x / this._scale;
365 y = y / this._scale;
366 return {x:x, y:y};
367 },
368
d6e281ba
SR
369
370 // Public methods
371 grab: function () {
372 var c = this._target;
373
bea2b3fd 374 if (Util.isTouchDevice) {
b4ef49ea
SR
375 c.addEventListener('touchstart', this._eventHandlers.mousedown);
376 window.addEventListener('touchend', this._eventHandlers.mouseup);
377 c.addEventListener('touchend', this._eventHandlers.mouseup);
378 c.addEventListener('touchmove', this._eventHandlers.mousemove);
d6e281ba 379 }
4b20f236 380 c.addEventListener('mousedown', this._eventHandlers.mousedown);
381 window.addEventListener('mouseup', this._eventHandlers.mouseup);
382 c.addEventListener('mouseup', this._eventHandlers.mouseup);
383 c.addEventListener('mousemove', this._eventHandlers.mousemove);
ebb9086a 384 c.addEventListener('wheel', this._eventHandlers.mousewheel);
d6e281ba 385
b69dda9b 386 /* Prevent middle-click pasting (see above for why we bind to document) */
b4ef49ea 387 document.addEventListener('click', this._eventHandlers.mousedisable);
b69dda9b
SM
388
389 /* preventDefault() on mousedown doesn't stop this event for some
390 reason so we have to explicitly block it */
391 c.addEventListener('contextmenu', this._eventHandlers.mousedisable);
d6e281ba
SR
392 },
393
394 ungrab: function () {
395 var c = this._target;
396
bea2b3fd 397 if (Util.isTouchDevice) {
b4ef49ea
SR
398 c.removeEventListener('touchstart', this._eventHandlers.mousedown);
399 window.removeEventListener('touchend', this._eventHandlers.mouseup);
400 c.removeEventListener('touchend', this._eventHandlers.mouseup);
401 c.removeEventListener('touchmove', this._eventHandlers.mousemove);
d6e281ba 402 }
4b20f236 403 c.removeEventListener('mousedown', this._eventHandlers.mousedown);
404 window.removeEventListener('mouseup', this._eventHandlers.mouseup);
405 c.removeEventListener('mouseup', this._eventHandlers.mouseup);
406 c.removeEventListener('mousemove', this._eventHandlers.mousemove);
ebb9086a 407 c.removeEventListener('wheel', this._eventHandlers.mousewheel);
d6e281ba 408
b4ef49ea 409 document.removeEventListener('click', this._eventHandlers.mousedisable);
d6e281ba 410
b69dda9b 411 c.removeEventListener('contextmenu', this._eventHandlers.mousedisable);
b2f1961a 412 }
d6e281ba
SR
413 };
414
415 Util.make_properties(Mouse, [
416 ['target', 'ro', 'dom'], // DOM element that captures mouse input
417 ['notify', 'ro', 'func'], // Function to call to notify whenever a mouse event is received
418 ['focused', 'rw', 'bool'], // Capture and send mouse clicks/movement
419 ['scale', 'rw', 'float'], // Viewport scale factor 0.0 - 1.0
420
421 ['onMouseButton', 'rw', 'func'], // Handler for mouse button click/release
422 ['onMouseMove', 'rw', 'func'], // Handler for mouse movement
423 ['touchButton', 'rw', 'int'] // Button mask (1, 2, 4) for touch devices (0 means ignore clicks)
424 ]);
425})();