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