]> git.proxmox.com Git - mirror_novnc.git/blame - include/input.js
QEMU RFB extension - new file xtscancodes.js
[mirror_novnc.git] / include / input.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
d6e281ba
SR
11var Keyboard, Mouse;
12
13(function () {
14 "use strict";
15
16 //
17 // Keyboard event handler
18 //
19
20 Keyboard = function (defaults) {
21 this._keyDownList = []; // List of depressed keys
22 // (even if they are happy)
23
24 Util.set_defaults(this, defaults, {
25 'target': document,
26 'focused': true
27 });
28
29 // create the keyboard handler
30 this._handler = new KeyEventDecoder(kbdUtil.ModifierSync(),
31 VerifyCharModifier( /* jshint newcap: false */
32 TrackKeyState(
33 EscapeModifiers(this._handleRfbEvent.bind(this))
34 )
35 )
36 ); /* jshint newcap: true */
37
38 // keep these here so we can refer to them later
39 this._eventHandlers = {
40 'keyup': this._handleKeyUp.bind(this),
41 'keydown': this._handleKeyDown.bind(this),
42 'keypress': this._handleKeyPress.bind(this),
43 'blur': this._allKeysUp.bind(this)
44 };
45 };
46
47 Keyboard.prototype = {
48 // private methods
49
50 _handleRfbEvent: function (e) {
51 if (this._onKeyPress) {
52 Util.Debug("onKeyPress " + (e.type == 'keydown' ? "down" : "up") +
53 ", keysym: " + e.keysym.keysym + "(" + e.keysym.keyname + ")");
54 this._onKeyPress(e.keysym.keysym, e.type == 'keydown');
55 }
56 },
d3796c14 57
d6e281ba
SR
58 _handleKeyDown: function (e) {
59 if (!this._focused) { return true; }
d3796c14 60
d6e281ba
SR
61 if (this._handler.keydown(e)) {
62 // Suppress bubbling/default actions
63 Util.stopEvent(e);
64 return false;
65 } else {
66 // Allow the event to bubble and become a keyPress event which
67 // will have the character code translated
68 return true;
69 }
70 },
d3796c14 71
d6e281ba
SR
72 _handleKeyPress: function (e) {
73 if (!this._focused) { return true; }
c96f9003 74
d6e281ba
SR
75 if (this._handler.keypress(e)) {
76 // Suppress bubbling/default actions
77 Util.stopEvent(e);
78 return false;
79 } else {
80 // Allow the event to bubble and become a keyPress event which
81 // will have the character code translated
82 return true;
83 }
84 },
d3796c14 85
d6e281ba
SR
86 _handleKeyUp: function (e) {
87 if (!this._focused) { return true; }
d3796c14 88
d6e281ba
SR
89 if (this._handler.keyup(e)) {
90 // Suppress bubbling/default actions
91 Util.stopEvent(e);
92 return false;
93 } else {
94 // Allow the event to bubble and become a keyPress event which
95 // will have the character code translated
96 return true;
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 Util.addEvent(c, 'keydown', this._eventHandlers.keydown);
113 Util.addEvent(c, 'keyup', this._eventHandlers.keyup);
114 Util.addEvent(c, 'keypress', this._eventHandlers.keypress);
115
116 // Release (key up) if window loses focus
117 Util.addEvent(window, '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 Util.removeEvent(c, 'keydown', this._eventHandlers.keydown);
127 Util.removeEvent(c, 'keyup', this._eventHandlers.keyup);
128 Util.removeEvent(c, 'keypress', this._eventHandlers.keypress);
129 Util.removeEvent(window, '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 ]);
d3796c14 148
d6e281ba
SR
149 //
150 // Mouse event handler
151 //
152
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 'scale': 1.0,
164 'touchButton': 1
165 });
166
167 this._eventHandlers = {
168 'mousedown': this._handleMouseDown.bind(this),
169 'mouseup': this._handleMouseUp.bind(this),
170 'mousemove': this._handleMouseMove.bind(this),
171 'mousewheel': this._handleMouseWheel.bind(this),
172 'mousedisable': this._handleMouseDisable.bind(this)
173 };
174 };
175
176 Mouse.prototype = {
177 // private methods
178 _captureMouse: function () {
179 // capturing the mouse ensures we get the mouseup event
180 if (this._target.setCapture) {
181 this._target.setCapture();
182 }
183
184 // some browsers give us mouseup events regardless,
185 // so if we never captured the mouse, we can disregard the event
186 this._mouseCaptured = true;
187 },
188
189 _releaseMouse: function () {
190 if (this._target.releaseCapture) {
191 this._target.releaseCapture();
192 }
193 this._mouseCaptured = false;
194 },
195
196 _resetDoubleClickTimer: function () {
197 this._doubleClickTimer = null;
198 },
b2f1961a 199
d6e281ba
SR
200 _handleMouseButton: function (e, down) {
201 if (!this._focused) { return true; }
cf19ad37 202
d6e281ba
SR
203 if (this._notify) {
204 this._notify(e);
205 }
b2f1961a 206
d6e281ba
SR
207 var evt = (e ? e : window.event);
208 var pos = Util.getEventPosition(e, this._target, this._scale);
209
210 var bmask;
211 if (e.touches || e.changedTouches) {
212 // Touch device
213
214 // When two touches occur within 500 ms of each other and are
f52105bc 215 // close enough together a double click is triggered.
d6e281ba
SR
216 if (down == 1) {
217 if (this._doubleClickTimer === null) {
218 this._lastTouchPos = pos;
219 } else {
220 clearTimeout(this._doubleClickTimer);
221
222 // When the distance between the two touches is small enough
223 // force the position of the latter touch to the position of
224 // the first.
225
226 var xs = this._lastTouchPos.x - pos.x;
227 var ys = this._lastTouchPos.y - pos.y;
228 var d = Math.sqrt((xs * xs) + (ys * ys));
229
230 // The goal is to trigger on a certain physical width, the
231 // devicePixelRatio brings us a bit closer but is not optimal.
f52105bc 232 var threshold = 20 * (window.devicePixelRatio || 1);
233 if (d < threshold) {
d6e281ba
SR
234 pos = this._lastTouchPos;
235 }
236 }
237 this._doubleClickTimer = setTimeout(this._resetDoubleClickTimer.bind(this), 500);
a4ec2f5c 238 }
d6e281ba
SR
239 bmask = this._touchButton;
240 // If bmask is set
241 } else if (evt.which) {
242 /* everything except IE */
243 bmask = 1 << evt.button;
244 } else {
245 /* IE including 9 */
246 bmask = (evt.button & 0x1) + // Left
247 (evt.button & 0x2) * 2 + // Right
248 (evt.button & 0x4) / 2; // Middle
b2f1961a 249 }
d6e281ba
SR
250
251 if (this._onMouseButton) {
252 Util.Debug("onMouseButton " + (down ? "down" : "up") +
253 ", x: " + pos.x + ", y: " + pos.y + ", bmask: " + bmask);
254 this._onMouseButton(pos.x, pos.y, down, bmask);
255 }
256 Util.stopEvent(e);
257 return false;
258 },
259
260 _handleMouseDown: function (e) {
261 this._captureMouse();
262 this._handleMouseButton(e, 1);
263 },
264
265 _handleMouseUp: function (e) {
266 if (!this._mouseCaptured) { return; }
267
268 this._handleMouseButton(e, 0);
269 this._releaseMouse();
270 },
271
272 _handleMouseWheel: function (e) {
273 if (!this._focused) { return true; }
274
275 if (this._notify) {
276 this._notify(e);
277 }
278
279 var evt = (e ? e : window.event);
280 var pos = Util.getEventPosition(e, this._target, this._scale);
281 var wheelData = evt.detail ? evt.detail * -1 : evt.wheelDelta / 40;
282 var bmask;
283 if (wheelData > 0) {
284 bmask = 1 << 3;
285 } else {
286 bmask = 1 << 4;
287 }
288
289 if (this._onMouseButton) {
290 this._onMouseButton(pos.x, pos.y, 1, bmask);
291 this._onMouseButton(pos.x, pos.y, 0, bmask);
292 }
293 Util.stopEvent(e);
294 return false;
295 },
296
297 _handleMouseMove: function (e) {
298 if (! this._focused) { return true; }
299
300 if (this._notify) {
301 this._notify(e);
302 }
303
304 var evt = (e ? e : window.event);
305 var pos = Util.getEventPosition(e, this._target, this._scale);
306 if (this._onMouseMove) {
307 this._onMouseMove(pos.x, pos.y);
308 }
309 Util.stopEvent(e);
310 return false;
311 },
312
313 _handleMouseDisable: function (e) {
314 if (!this._focused) { return true; }
315
316 var evt = (e ? e : window.event);
317 var pos = Util.getEventPosition(e, this._target, this._scale);
318
319 /* Stop propagation if inside canvas area */
320 if ((pos.realx >= 0) && (pos.realy >= 0) &&
321 (pos.realx < this._target.offsetWidth) &&
322 (pos.realy < this._target.offsetHeight)) {
323 //Util.Debug("mouse event disabled");
324 Util.stopEvent(e);
325 return false;
326 }
327
328 return true;
329 },
330
331
332 // Public methods
333 grab: function () {
334 var c = this._target;
335
336 if ('ontouchstart' in document.documentElement) {
337 Util.addEvent(c, 'touchstart', this._eventHandlers.mousedown);
338 Util.addEvent(window, 'touchend', this._eventHandlers.mouseup);
339 Util.addEvent(c, 'touchend', this._eventHandlers.mouseup);
340 Util.addEvent(c, 'touchmove', this._eventHandlers.mousemove);
341 } else {
342 Util.addEvent(c, 'mousedown', this._eventHandlers.mousedown);
343 Util.addEvent(window, 'mouseup', this._eventHandlers.mouseup);
344 Util.addEvent(c, 'mouseup', this._eventHandlers.mouseup);
345 Util.addEvent(c, 'mousemove', this._eventHandlers.mousemove);
346 Util.addEvent(c, (Util.Engine.gecko) ? 'DOMMouseScroll' : 'mousewheel',
347 this._eventHandlers.mousewheel);
348 }
349
350 /* Work around right and middle click browser behaviors */
351 Util.addEvent(document, 'click', this._eventHandlers.mousedisable);
352 Util.addEvent(document.body, 'contextmenu', this._eventHandlers.mousedisable);
353 },
354
355 ungrab: function () {
356 var c = this._target;
357
358 if ('ontouchstart' in document.documentElement) {
359 Util.removeEvent(c, 'touchstart', this._eventHandlers.mousedown);
360 Util.removeEvent(window, 'touchend', this._eventHandlers.mouseup);
361 Util.removeEvent(c, 'touchend', this._eventHandlers.mouseup);
362 Util.removeEvent(c, 'touchmove', this._eventHandlers.mousemove);
363 } else {
364 Util.removeEvent(c, 'mousedown', this._eventHandlers.mousedown);
365 Util.removeEvent(window, 'mouseup', this._eventHandlers.mouseup);
366 Util.removeEvent(c, 'mouseup', this._eventHandlers.mouseup);
367 Util.removeEvent(c, 'mousemove', this._eventHandlers.mousemove);
368 Util.removeEvent(c, (Util.Engine.gecko) ? 'DOMMouseScroll' : 'mousewheel',
369 this._eventHandlers.mousewheel);
370 }
371
372 /* Work around right and middle click browser behaviors */
373 Util.removeEvent(document, 'click', this._eventHandlers.mousedisable);
374 Util.removeEvent(document.body, 'contextmenu', this._eventHandlers.mousedisable);
375
b2f1961a 376 }
d6e281ba
SR
377 };
378
379 Util.make_properties(Mouse, [
380 ['target', 'ro', 'dom'], // DOM element that captures mouse input
381 ['notify', 'ro', 'func'], // Function to call to notify whenever a mouse event is received
382 ['focused', 'rw', 'bool'], // Capture and send mouse clicks/movement
383 ['scale', 'rw', 'float'], // Viewport scale factor 0.0 - 1.0
384
385 ['onMouseButton', 'rw', 'func'], // Handler for mouse button click/release
386 ['onMouseMove', 'rw', 'func'], // Handler for mouse movement
387 ['touchButton', 'rw', 'int'] // Button mask (1, 2, 4) for touch devices (0 means ignore clicks)
388 ]);
389})();