input.js revision 1d80c6a8d34f59543f7df1963c22d7efa292bcb0
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 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 }, 57 58 _handleKeyDown: function (e) { 59 if (!this._focused) { return true; } 60 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 }, 71 72 _handleKeyPress: function (e) { 73 if (!this._focused) { return true; } 74 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 }, 85 86 _handleKeyUp: function (e) { 87 if (!this._focused) { return true; } 88 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); 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 // 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 }, 199 200 _handleMouseButton: function (e, down) { 201 if (!this._focused) { return true; } 202 203 if (this._notify) { 204 this._notify(e); 205 } 206 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 215 // closer than 20 pixels together a double click is triggered. 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. 232 if (d < 20 * window.devicePixelRatio) { 233 pos = this._lastTouchPos; 234 } 235 } 236 this._doubleClickTimer = setTimeout(this._resetDoubleClickTimer.bind(this), 500); 237 } 238 bmask = this._touchButton; 239 // If bmask is set 240 } else if (evt.which) { 241 /* everything except IE */ 242 bmask = 1 << evt.button; 243 } else { 244 /* IE including 9 */ 245 bmask = (evt.button & 0x1) + // Left 246 (evt.button & 0x2) * 2 + // Right 247 (evt.button & 0x4) / 2; // Middle 248 } 249 250 if (this._onMouseButton) { 251 Util.Debug("onMouseButton " + (down ? "down" : "up") + 252 ", x: " + pos.x + ", y: " + pos.y + ", bmask: " + bmask); 253 this._onMouseButton(pos.x, pos.y, down, bmask); 254 } 255 Util.stopEvent(e); 256 return false; 257 }, 258 259 _handleMouseDown: function (e) { 260 this._captureMouse(); 261 this._handleMouseButton(e, 1); 262 }, 263 264 _handleMouseUp: function (e) { 265 if (!this._mouseCaptured) { return; } 266 267 this._handleMouseButton(e, 0); 268 this._releaseMouse(); 269 }, 270 271 _handleMouseWheel: function (e) { 272 if (!this._focused) { return true; } 273 274 if (this._notify) { 275 this._notify(e); 276 } 277 278 var evt = (e ? e : window.event); 279 var pos = Util.getEventPosition(e, this._target, this._scale); 280 var wheelData = evt.detail ? evt.detail * -1 : evt.wheelDelta / 40; 281 var bmask; 282 if (wheelData > 0) { 283 bmask = 1 << 3; 284 } else { 285 bmask = 1 << 4; 286 } 287 288 if (this._onMouseButton) { 289 this._onMouseButton(pos.x, pos.y, 1, bmask); 290 this._onMouseButton(pos.x, pos.y, 0, bmask); 291 } 292 Util.stopEvent(e); 293 return false; 294 }, 295 296 _handleMouseMove: function (e) { 297 if (! this._focused) { return true; } 298 299 if (this._notify) { 300 this._notify(e); 301 } 302 303 var evt = (e ? e : window.event); 304 var pos = Util.getEventPosition(e, this._target, this._scale); 305 if (this._onMouseMove) { 306 this._onMouseMove(pos.x, pos.y); 307 } 308 Util.stopEvent(e); 309 return false; 310 }, 311 312 _handleMouseDisable: function (e) { 313 if (!this._focused) { return true; } 314 315 var evt = (e ? e : window.event); 316 var pos = Util.getEventPosition(e, this._target, this._scale); 317 318 /* Stop propagation if inside canvas area */ 319 if ((pos.realx >= 0) && (pos.realy >= 0) && 320 (pos.realx < this._target.offsetWidth) && 321 (pos.realy < this._target.offsetHeight)) { 322 //Util.Debug("mouse event disabled"); 323 Util.stopEvent(e); 324 return false; 325 } 326 327 return true; 328 }, 329 330 331 // Public methods 332 grab: function () { 333 var c = this._target; 334 335 if ('ontouchstart' in document.documentElement) { 336 Util.addEvent(c, 'touchstart', this._eventHandlers.mousedown); 337 Util.addEvent(window, 'touchend', this._eventHandlers.mouseup); 338 Util.addEvent(c, 'touchend', this._eventHandlers.mouseup); 339 Util.addEvent(c, 'touchmove', this._eventHandlers.mousemove); 340 } else { 341 Util.addEvent(c, 'mousedown', this._eventHandlers.mousedown); 342 Util.addEvent(window, 'mouseup', this._eventHandlers.mouseup); 343 Util.addEvent(c, 'mouseup', this._eventHandlers.mouseup); 344 Util.addEvent(c, 'mousemove', this._eventHandlers.mousemove); 345 Util.addEvent(c, (Util.Engine.gecko) ? 'DOMMouseScroll' : 'mousewheel', 346 this._eventHandlers.mousewheel); 347 } 348 349 /* Work around right and middle click browser behaviors */ 350 Util.addEvent(document, 'click', this._eventHandlers.mousedisable); 351 Util.addEvent(document.body, 'contextmenu', this._eventHandlers.mousedisable); 352 }, 353 354 ungrab: function () { 355 var c = this._target; 356 357 if ('ontouchstart' in document.documentElement) { 358 Util.removeEvent(c, 'touchstart', this._eventHandlers.mousedown); 359 Util.removeEvent(window, 'touchend', this._eventHandlers.mouseup); 360 Util.removeEvent(c, 'touchend', this._eventHandlers.mouseup); 361 Util.removeEvent(c, 'touchmove', this._eventHandlers.mousemove); 362 } else { 363 Util.removeEvent(c, 'mousedown', this._eventHandlers.mousedown); 364 Util.removeEvent(window, 'mouseup', this._eventHandlers.mouseup); 365 Util.removeEvent(c, 'mouseup', this._eventHandlers.mouseup); 366 Util.removeEvent(c, 'mousemove', this._eventHandlers.mousemove); 367 Util.removeEvent(c, (Util.Engine.gecko) ? 'DOMMouseScroll' : 'mousewheel', 368 this._eventHandlers.mousewheel); 369 } 370 371 /* Work around right and middle click browser behaviors */ 372 Util.removeEvent(document, 'click', this._eventHandlers.mousedisable); 373 Util.removeEvent(document.body, 'contextmenu', this._eventHandlers.mousedisable); 374 375 } 376 }; 377 378 Util.make_properties(Mouse, [ 379 ['target', 'ro', 'dom'], // DOM element that captures mouse input 380 ['notify', 'ro', 'func'], // Function to call to notify whenever a mouse event is received 381 ['focused', 'rw', 'bool'], // Capture and send mouse clicks/movement 382 ['scale', 'rw', 'float'], // Viewport scale factor 0.0 - 1.0 383 384 ['onMouseButton', 'rw', 'func'], // Handler for mouse button click/release 385 ['onMouseMove', 'rw', 'func'], // Handler for mouse movement 386 ['touchButton', 'rw', 'int'] // Button mask (1, 2, 4) for touch devices (0 means ignore clicks) 387 ]); 388})(); 389